Skip to content

Commit 5e8fdd4

Browse files
committed
Add tool usage and fix messaging history
1 parent 7113d72 commit 5e8fdd4

File tree

4 files changed

+504
-15
lines changed

4 files changed

+504
-15
lines changed

lib/ai/tools.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// AI Tool definitions for function calling
2+
import Searcher from '../search/search.js';
3+
4+
// Initialize search instance
5+
const searchFields = ["filename", "category", "type", "region"];
6+
const searcher = new Searcher(searchFields);
7+
8+
// Default search settings
9+
const defaultSearchSettings = {
10+
boost: {
11+
filename: 2,
12+
category: 1,
13+
type: 1,
14+
region: 1
15+
},
16+
combineWith: "AND",
17+
fields: searchFields,
18+
fuzzy: 0,
19+
prefix: true,
20+
hideNonGame: true,
21+
useOldResults: false,
22+
pageSize: 10,
23+
page: 0,
24+
sort: ""
25+
};
26+
27+
/**
28+
* Available tools for the AI assistant
29+
*/
30+
export const tools = [
31+
{
32+
type: "function",
33+
function: {
34+
name: "search_games",
35+
description: "Search for retro games and ROMs in the Myrient database. Use simple, flexible text searches for best results. The search is fuzzy and will find partial matches.",
36+
parameters: {
37+
type: "object",
38+
properties: {
39+
query: {
40+
type: "string",
41+
description: "The search query - game name or title. Examples: 'Super Mario', 'The Last of Us', 'Final Fantasy'. Keep it simple for best results."
42+
},
43+
limit: {
44+
type: "number",
45+
description: "Maximum number of results to return (1-50, default 10)",
46+
minimum: 1,
47+
maximum: 50
48+
}
49+
},
50+
required: ["query"]
51+
}
52+
}
53+
},
54+
{
55+
type: "function",
56+
function: {
57+
name: "get_search_suggestions",
58+
description: "Get search suggestions based on a partial query. Useful for helping users discover games or correct typos.",
59+
parameters: {
60+
type: "object",
61+
properties: {
62+
query: {
63+
type: "string",
64+
description: "Partial search query to get suggestions for"
65+
}
66+
},
67+
required: ["query"]
68+
}
69+
}
70+
}
71+
];
72+
73+
/**
74+
* Execute a tool call
75+
*/
76+
export async function executeToolCall(toolCall) {
77+
const { name, arguments: argsString } = toolCall.function;
78+
79+
try {
80+
// Parse arguments from JSON string
81+
let args;
82+
try {
83+
args = typeof argsString === 'string' ? JSON.parse(argsString) : argsString;
84+
} catch (parseError) {
85+
throw new Error(`Invalid JSON arguments for ${name}: ${parseError.message}`);
86+
}
87+
88+
switch (name) {
89+
case 'search_games':
90+
return await searchGames(args);
91+
case 'get_search_suggestions':
92+
return await getSearchSuggestions(args);
93+
default:
94+
throw new Error(`Unknown tool: ${name}`);
95+
}
96+
} catch (error) {
97+
console.error(`Tool execution error for ${name}:`, error);
98+
return {
99+
error: `Failed to execute ${name}: ${error.message}`
100+
};
101+
}
102+
}
103+
104+
/**
105+
* Search for games using the existing search infrastructure
106+
*/
107+
async function searchGames(args) {
108+
const { query, limit = 10 } = args;
109+
110+
if (!query || typeof query !== 'string') {
111+
throw new Error('Query is required and must be a string');
112+
}
113+
114+
// Build search options - simplified for fuzzy/flexible search
115+
const searchOptions = { ...defaultSearchSettings };
116+
searchOptions.pageSize = Math.min(Math.max(1, limit), 50);
117+
118+
// Enable fuzzy search for better matching
119+
searchOptions.fuzzy = 1; // Allow some typos/variations
120+
searchOptions.prefix = true; // Allow partial matches
121+
122+
try {
123+
const results = await searcher.findAllMatches(query.trim(), searchOptions);
124+
125+
// Use results as-is without strict filtering
126+
let filteredItems = results.items;
127+
128+
// Format results for the AI
129+
const formattedResults = filteredItems.slice(0, searchOptions.pageSize).map(item => ({
130+
id: item.file.id,
131+
filename: item.file.filename,
132+
category: item.file.category,
133+
type: item.file.type,
134+
region: item.file.region,
135+
size: item.file.size,
136+
score: item.score,
137+
// Add URLs for linking to games
138+
urls: {
139+
info: `/info/${item.file.id}`,
140+
play: `/play/${item.file.id}`, // For emulator (if compatible)
141+
download: item.file.path // Direct download link
142+
},
143+
metadata: item.metadata ? {
144+
title: item.metadata.title,
145+
description: item.metadata.summary,
146+
releaseDate: item.metadata.first_release_date,
147+
rating: item.metadata.rating,
148+
genres: item.metadata.genres
149+
} : null
150+
}));
151+
152+
return {
153+
query,
154+
results: formattedResults,
155+
total_found: results.count,
156+
total_returned: formattedResults.length,
157+
search_time: results.elapsed
158+
};
159+
160+
} catch (error) {
161+
throw new Error(`Search failed: ${error.message}`);
162+
}
163+
}
164+
165+
/**
166+
* Get search suggestions
167+
*/
168+
async function getSearchSuggestions(args) {
169+
const { query } = args;
170+
171+
if (!query || typeof query !== 'string') {
172+
throw new Error('Query is required and must be a string');
173+
}
174+
175+
try {
176+
const suggestions = await searcher.getSuggestions(query.trim(), defaultSearchSettings);
177+
178+
return {
179+
query,
180+
suggestions: suggestions || []
181+
};
182+
183+
} catch (error) {
184+
throw new Error(`Failed to get suggestions: ${error.message}`);
185+
}
186+
}

0 commit comments

Comments
 (0)