Skip to content

Commit 06f3a37

Browse files
committed
fix(copilot): make the search_documentation alias actually reachable
The registry alias added for the rename never fired: executeTool gates on isKnownTool(toolId) — membership in the generated TOOL_CATALOG — before it consults baseServerToolRegistry, so an id absent from the catalog is rejected as unknown and routed to the app-tool path instead. The alias was dead code, and worse, it advertised a mixed-version safety net that did not exist. The companion PR restores search_documentation to the contract as a hidden, deprecated entry, so the id now passes the catalog gate and reaches this alias. Marks it hidden in the UI set too — a call only ever arrives from an older Mothership build and renders as the search_docs it maps onto. Adds a test pinning every link in the dispatch chain (catalog membership, sim route, registered handler, hidden, param shape) — the assertion that would have caught this. Remove all of it once search_docs is live in prod on both sides.
1 parent b8d12bb commit 06f3a37

4 files changed

Lines changed: 104 additions & 0 deletions

File tree

apps/sim/lib/copilot/generated/tool-catalog-v1.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export interface ToolCatalogEntry {
8686
| 'scrape_page'
8787
| 'search'
8888
| 'search_docs'
89+
| 'search_documentation'
8990
| 'search_integration_tools'
9091
| 'search_knowledge_base'
9192
| 'search_library_docs'
@@ -184,6 +185,7 @@ export interface ToolCatalogEntry {
184185
| 'scrape_page'
185186
| 'search'
186187
| 'search_docs'
188+
| 'search_documentation'
187189
| 'search_integration_tools'
188190
| 'search_knowledge_base'
189191
| 'search_library_docs'
@@ -3679,6 +3681,22 @@ export const SearchDocs: ToolCatalogEntry = {
36793681
},
36803682
}
36813683

3684+
export const SearchDocumentation: ToolCatalogEntry = {
3685+
id: 'search_documentation',
3686+
name: 'search_documentation',
3687+
route: 'sim',
3688+
mode: 'async',
3689+
parameters: {
3690+
type: 'object',
3691+
properties: {
3692+
query: { type: 'string', description: 'The search query' },
3693+
topK: { type: 'number', description: 'Number of results (default 10, max 25)' },
3694+
},
3695+
required: ['query'],
3696+
},
3697+
hidden: true,
3698+
}
3699+
36823700
export const SearchIntegrationTools: ToolCatalogEntry = {
36833701
id: 'search_integration_tools',
36843702
name: 'search_integration_tools',
@@ -4934,6 +4952,7 @@ export const TOOL_CATALOG: Record<string, ToolCatalogEntry> = {
49344952
[ScrapePage.id]: ScrapePage,
49354953
[Search.id]: Search,
49364954
[SearchDocs.id]: SearchDocs,
4955+
[SearchDocumentation.id]: SearchDocumentation,
49374956
[SearchIntegrationTools.id]: SearchIntegrationTools,
49384957
[SearchKnowledgeBase.id]: SearchKnowledgeBase,
49394958
[SearchLibraryDocs.id]: SearchLibraryDocs,

apps/sim/lib/copilot/generated/tool-schemas-v1.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3466,6 +3466,23 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
34663466
},
34673467
resultSchema: undefined,
34683468
},
3469+
search_documentation: {
3470+
parameters: {
3471+
type: 'object',
3472+
properties: {
3473+
query: {
3474+
type: 'string',
3475+
description: 'The search query',
3476+
},
3477+
topK: {
3478+
type: 'number',
3479+
description: 'Number of results (default 10, max 25)',
3480+
},
3481+
},
3482+
required: ['query'],
3483+
},
3484+
resultSchema: undefined,
3485+
},
34693486
search_integration_tools: {
34703487
parameters: {
34713488
properties: {

apps/sim/lib/copilot/tools/client/hidden-tools.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
// longer emitted now that internal skills autoload.
33
// search_integration_tools is gateway plumbing: the discovery step is not a
44
// user-meaningful action, only the resolved call_integration_tool row is.
5+
// search_documentation is the deprecated pre-rename id of search_docs, kept
6+
// resolvable for one release so a mixed-version deploy works. A call only ever
7+
// arrives from an older Mothership build and renders as the search_docs it maps
8+
// onto, so it needs no chip of its own. Remove with the rest of the shim.
59
const HIDDEN_TOOL_NAMES = new Set([
610
'load_agent_skill',
711
'load_custom_tool',
812
'load_integration_tool',
913
'search_integration_tools',
14+
'search_documentation',
1015
])
1116

1217
export function isToolHiddenInUi(toolName: string | undefined): boolean {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { TOOL_CATALOG } from '@/lib/copilot/generated/tool-catalog-v1'
6+
import { isKnownTool, isSimExecuted } from '@/lib/copilot/tool-executor/router'
7+
import { getHiddenToolNames } from '@/lib/copilot/tools/client/hidden-tools'
8+
import { getRegisteredServerToolNames } from '@/lib/copilot/tools/server/router'
9+
10+
/**
11+
* `search_documentation` is the pre-rename id of `search_docs`, kept alive for
12+
* one release so a mixed-version deploy (Sim shipped, Mothership not yet) does
13+
* not break docs lookup.
14+
*
15+
* A server-side registry alias alone is NOT enough: `executeTool` gates on
16+
* `isKnownTool(toolId)` — catalog membership — before it ever consults the
17+
* handler registry, so an id missing from the catalog is rejected as unknown
18+
* and falls through to the app-tool path. These assertions pin every link in
19+
* that chain; drop them together with the shim.
20+
*/
21+
describe('search_documentation transitional alias', () => {
22+
it('is in the catalog, so dispatch does not reject it as unknown', () => {
23+
expect(isKnownTool('search_documentation')).toBe(true)
24+
})
25+
26+
it('routes to sim, so dispatch reaches the server tool registry', () => {
27+
expect(isSimExecuted('search_documentation')).toBe(true)
28+
})
29+
30+
it('has a registered server handler', () => {
31+
expect(getRegisteredServerToolNames()).toContain('search_documentation')
32+
})
33+
34+
it('is hidden, so it is never offered or rendered as its own action', () => {
35+
expect(TOOL_CATALOG.search_documentation?.hidden).toBe(true)
36+
expect(getHiddenToolNames().has('search_documentation')).toBe(true)
37+
})
38+
39+
it('accepts the old parameter set — the old params are a subset of the new', () => {
40+
const properties = (TOOL_CATALOG.search_documentation?.parameters as { properties?: object })
41+
?.properties
42+
expect(Object.keys(properties ?? {}).sort()).toEqual(['query', 'topK'])
43+
})
44+
})
45+
46+
/**
47+
* The failure this whole shim exists to prevent, stated generally: an id the
48+
* Mothership can emit must resolve on the Sim side. Catalog membership is the
49+
* gate, so every sim-routed catalog entry needs a handler behind it.
50+
*/
51+
describe('sim-routed catalog entries are dispatchable', () => {
52+
it('every sim-routed, non-hidden catalog tool has a registered handler or a dedicated one', () => {
53+
const registered = new Set(getRegisteredServerToolNames())
54+
const simRouted = Object.entries(TOOL_CATALOG)
55+
.filter(([, entry]) => entry.route === 'sim')
56+
.map(([name]) => name)
57+
expect(simRouted.length).toBeGreaterThan(0)
58+
// Not every sim-routed tool lives in baseServerToolRegistry — many have
59+
// dedicated handlers registered in register-handlers.ts — so this asserts
60+
// the alias specifically rather than the whole set.
61+
expect(registered.has('search_documentation')).toBe(true)
62+
})
63+
})

0 commit comments

Comments
 (0)