Merge pull request #141 from IronRookieCoder/fix/search-extra-tools-discover-schema
fix(search-extra-tools): return input schema in discover mode results
This commit is contained in:
commit
02827e97cd
|
|
@ -58,6 +58,17 @@ export const outputSchema = lazySchema(() =>
|
|||
pending_mcp_servers: z.array(z.string()).optional(),
|
||||
/** Matches that are already loaded (core tools) and can be called directly. */
|
||||
already_loaded: z.array(z.string()).optional(),
|
||||
/** Tool details (name, description, schema) for discover mode. */
|
||||
details: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
score: z.number(),
|
||||
inputSchema: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
type OutputSchema = ReturnType<typeof outputSchema>
|
||||
|
|
@ -131,6 +142,7 @@ function buildSearchResult(
|
|||
totalDeferredTools: number,
|
||||
pendingMcpServers?: string[],
|
||||
alreadyLoaded?: string[],
|
||||
details?: Output['details'],
|
||||
): { data: Output } {
|
||||
return {
|
||||
data: {
|
||||
|
|
@ -143,6 +155,7 @@ function buildSearchResult(
|
|||
...(alreadyLoaded && alreadyLoaded.length > 0
|
||||
? { already_loaded: alreadyLoaded }
|
||||
: {}),
|
||||
...(details && details.length > 0 ? { details } : {}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -446,17 +459,12 @@ export const SearchExtraToolsTool = buildTool({
|
|||
const discoverQuery = discoverMatch[1]!.trim()
|
||||
const index = await getToolIndex(deferredTools)
|
||||
const tfIdfResults = searchTools(discoverQuery, index, max_results)
|
||||
const textResults = tfIdfResults.map(r => {
|
||||
let line = `**${r.name}** (score: ${r.score.toFixed(2)})\n${r.description}`
|
||||
if (r.inputSchema) {
|
||||
line += `\nSchema: ${JSON.stringify(r.inputSchema)}`
|
||||
}
|
||||
return line
|
||||
})
|
||||
const text =
|
||||
textResults.length > 0
|
||||
? `Found ${textResults.length} tools:\n${textResults.join('\n\n')}`
|
||||
: 'No matching deferred tools found'
|
||||
const details = tfIdfResults.map(r => ({
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
score: r.score,
|
||||
...(r.inputSchema ? { inputSchema: r.inputSchema as Record<string, unknown> } : {}),
|
||||
}))
|
||||
logSearchOutcome(
|
||||
tfIdfResults.map(r => r.name),
|
||||
'keyword',
|
||||
|
|
@ -465,6 +473,9 @@ export const SearchExtraToolsTool = buildTool({
|
|||
tfIdfResults.map(r => r.name),
|
||||
query,
|
||||
deferredTools.length,
|
||||
undefined,
|
||||
undefined,
|
||||
details,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -559,6 +570,26 @@ export const SearchExtraToolsTool = buildTool({
|
|||
}
|
||||
}
|
||||
|
||||
// Discover mode: render tool details with name, description, and input schema.
|
||||
// This lets the model inspect parameters before selecting and invoking a tool.
|
||||
if (content.details && content.details.length > 0) {
|
||||
const lines = content.details.map(d => {
|
||||
let line = `**${d.name}** (score: ${d.score.toFixed(2)})\n${d.description}`
|
||||
if (d.inputSchema) {
|
||||
line += `\nSchema: ${JSON.stringify(d.inputSchema)}`
|
||||
}
|
||||
return line
|
||||
})
|
||||
const text =
|
||||
`Found ${lines.length} tool(s) matching your query:\n\n` +
|
||||
lines.join('\n\n')
|
||||
return {
|
||||
type: 'tool_result',
|
||||
tool_use_id: toolUseID,
|
||||
content: text,
|
||||
}
|
||||
}
|
||||
|
||||
// Separate already-loaded (core) tools from truly deferred tools
|
||||
const alreadyLoadedNames = content.already_loaded ?? []
|
||||
const deferredNames = content.matches.filter(
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ function makeContext(tools: unknown[] = []) {
|
|||
}
|
||||
|
||||
describe('SearchExtraToolsTool search enhancements', () => {
|
||||
test('discover: prefix triggers TF-IDF search and returns matches', async () => {
|
||||
test('discover: prefix triggers TF-IDF search and returns matches with details', async () => {
|
||||
const mockTool = makeDeferredTool('CronCreate', 'Schedule cron jobs')
|
||||
mockGetToolIndex.mockResolvedValueOnce([])
|
||||
mockSearchTools.mockReturnValueOnce([
|
||||
|
|
@ -112,11 +112,11 @@ describe('SearchExtraToolsTool search enhancements', () => {
|
|||
score: 0.85,
|
||||
isMcp: false,
|
||||
isDeferred: true,
|
||||
inputSchema: undefined,
|
||||
inputSchema: { interval: { type: 'string' } },
|
||||
},
|
||||
])
|
||||
|
||||
const result: { data: { matches: string[] } } = await (
|
||||
const result: { data: { matches: string[]; details?: Array<{ name: string; description: string; score: number; inputSchema?: Record<string, unknown> }> } } = await (
|
||||
SearchExtraToolsTool as any
|
||||
).call(
|
||||
{ query: 'discover:schedule cron job', max_results: 5 },
|
||||
|
|
@ -127,6 +127,11 @@ describe('SearchExtraToolsTool search enhancements', () => {
|
|||
)
|
||||
|
||||
expect(result.data.matches).toContain('CronCreate')
|
||||
expect(result.data.details).toBeDefined()
|
||||
expect(result.data.details!.length).toBe(1)
|
||||
expect(result.data.details![0]!.name).toBe('CronCreate')
|
||||
expect(result.data.details![0]!.description).toBe('Schedule cron jobs')
|
||||
expect(result.data.details![0]!.inputSchema).toEqual({ interval: { type: 'string' } })
|
||||
})
|
||||
|
||||
test('keyword + TF-IDF parallel search merges results', async () => {
|
||||
|
|
@ -232,4 +237,29 @@ describe('SearchExtraToolsTool search enhancements', () => {
|
|||
|
||||
expect(blockParam.content).toContain('No matching deferred tools found')
|
||||
})
|
||||
|
||||
test('discover mode renders details with description and schema', async () => {
|
||||
const blockParam = SearchExtraToolsTool.mapToolResultToToolResultBlockParam(
|
||||
{
|
||||
matches: ['CronCreate'],
|
||||
query: 'discover:schedule',
|
||||
total_deferred_tools: 5,
|
||||
details: [
|
||||
{
|
||||
name: 'CronCreate',
|
||||
description: 'Schedule cron jobs',
|
||||
score: 0.85,
|
||||
inputSchema: { interval: { type: 'string' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
'tool-use-123',
|
||||
)
|
||||
|
||||
expect(typeof blockParam.content).toBe('string')
|
||||
expect(blockParam.content as string).toContain('CronCreate')
|
||||
expect(blockParam.content as string).toContain('Schedule cron jobs')
|
||||
expect(blockParam.content as string).toContain('Schema')
|
||||
expect(blockParam.content as string).toContain('"interval"')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ IMPORTANT: ExecuteExtraTool is always available in your tool list. After this se
|
|||
|
||||
Query forms:
|
||||
- "select:CronCreate,Snip" — fetch these exact tools by name
|
||||
- "discover:schedule cron job" — pure discovery, returns tool info (name, description) without loading. Use when you want to understand available tools before deciding which to invoke.
|
||||
- "discover:schedule cron job" — pure discovery, returns tool info (name, description, input schema) without loading. Use when you need to understand a tool's parameters before invoking it, then call ExecuteExtraTool with the discovered tool name and params.
|
||||
- "notebook jupyter" — keyword search, up to max_results best matches
|
||||
- "+slack send" — require "slack" in the name, rank by remaining terms`
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user