Skip to content

Commit 3f4021b

Browse files
committed
test(tools): enforce that the two tool-id resolvers never diverge
`resolveToolId` now exists twice on purpose — `@/tools/utils` resolves against the live registry (so a tool added before regeneration still resolves at runtime), `@/tools/tool-ids` against the generated id list (so client code resolves without importing 4,300 tools). Nothing structurally kept them in step; a change to versioning logic in one would silently drift from the other. `tool-metadata:check` now asserts they agree across every id, every stripped base name, and an unknown — 4,404 probes — and only after the staleness check passes, so a missing regeneration reports as staleness rather than as drift. Verified it fails: breaking resolution for `gmail*` exits 1; restoring it passes. It cannot live in a vitest suite. `vitest.setup.ts` globally mocks `@/tools/registry` to an empty map, so `getTool` resolves nothing there — a parity test written as a spec passes or fails for the wrong reason. Both facts are recorded where the code is. Both resolvers stay exported. An earlier pass here un-exported the `@/tools/utils` one as dead; `tools/utils.server.ts` imports it through a multi-line import that a grep missed, and `tsc` caught it. Its doc now says which resolver a caller should reach for instead of leaving two identically-named functions unexplained.
1 parent 46fa6f2 commit 3f4021b

4 files changed

Lines changed: 433 additions & 1 deletion

File tree

apps/sim/tools/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ export function getLatestVersionTools(
5858
/**
5959
* Resolves a tool name to its actual tool ID in the registry.
6060
* Handles both stripped names (e.g., 'notion_search') and versioned names (e.g., 'notion_search_v2').
61+
*
62+
* Server-side counterpart to `resolveToolId` in `@/tools/tool-ids`. Both exist
63+
* deliberately: this one reads the live registry, so a tool added but not yet
64+
* regenerated stays resolvable; that one resolves against the generated id list
65+
* without pulling 4,300 tools into a client graph. Client code wants that one.
66+
* `tool-metadata:check` asserts the two never diverge.
67+
*
6168
* @param toolName The tool name to resolve (may or may not have version suffix)
6269
* @returns The actual tool ID in the registry, or the original name if not found
6370
*/

apps/sim/tools/zoho/types.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import type { ToolResponse } from '@/tools/types'
2+
3+
/** Params shared by every Zoho tool: the OAuth token and the account's region. */
4+
export interface ZohoBaseParams {
5+
accessToken: string
6+
dataCenter?: string
7+
}
8+
9+
/** Params shared by every Zoho Desk tool — Desk requires an `orgId` header. */
10+
export interface ZohoDeskBaseParams extends ZohoBaseParams {
11+
orgId: string
12+
}
13+
14+
/**
15+
* Pagination block returned by CRM list/search endpoints.
16+
* @see https://www.zoho.com/crm/developer/docs/api/v8/get-records.html
17+
*/
18+
export interface ZohoCrmPageInfo {
19+
page: number | null
20+
perPage: number | null
21+
count: number | null
22+
moreRecords: boolean
23+
}
24+
25+
export const ZOHO_CRM_PAGE_INFO_OUTPUT = {
26+
type: 'object' as const,
27+
description: 'Pagination metadata returned by Zoho CRM',
28+
properties: {
29+
page: { type: 'number' as const, description: 'Current page number', optional: true },
30+
perPage: { type: 'number' as const, description: 'Records requested per page', optional: true },
31+
count: {
32+
type: 'number' as const,
33+
description: 'Number of records in this page',
34+
optional: true,
35+
},
36+
moreRecords: { type: 'boolean' as const, description: 'Whether further pages are available' },
37+
},
38+
}
39+
40+
/** A CRM write result entry, as returned in the `data` array of insert/update/upsert. */
41+
export interface ZohoCrmWriteResult {
42+
id: string | null
43+
code: string | null
44+
status: string | null
45+
message: string | null
46+
}
47+
48+
export const ZOHO_CRM_WRITE_RESULT_OUTPUT = {
49+
type: 'array' as const,
50+
description: 'Per-record results returned by Zoho CRM',
51+
properties: {
52+
id: { type: 'string' as const, description: 'Record ID', optional: true },
53+
code: {
54+
type: 'string' as const,
55+
description: 'Zoho result code (e.g. SUCCESS)',
56+
optional: true,
57+
},
58+
status: { type: 'string' as const, description: 'Result status', optional: true },
59+
message: {
60+
type: 'string' as const,
61+
description: 'Human-readable result message',
62+
optional: true,
63+
},
64+
},
65+
}
66+
67+
export interface ZohoCrmGetRecordsParams extends ZohoBaseParams {
68+
module: string
69+
recordId?: string
70+
fields?: string
71+
page?: string | number
72+
perPage?: string | number
73+
sortBy?: string
74+
sortOrder?: string
75+
}
76+
77+
export interface ZohoCrmGetRecordsResponse extends ToolResponse {
78+
output: {
79+
records: Record<string, unknown>[]
80+
record?: Record<string, unknown> | null
81+
info: ZohoCrmPageInfo
82+
}
83+
}
84+
85+
export interface ZohoCrmCreateRecordsParams extends ZohoBaseParams {
86+
module: string
87+
records: unknown
88+
trigger?: string
89+
}
90+
91+
export interface ZohoCrmWriteResponse extends ToolResponse {
92+
output: {
93+
results: ZohoCrmWriteResult[]
94+
}
95+
}
96+
97+
export interface ZohoCrmUpdateRecordParams extends ZohoBaseParams {
98+
module: string
99+
recordId: string
100+
record: unknown
101+
trigger?: string
102+
}
103+
104+
export interface ZohoCrmUpsertRecordsParams extends ZohoBaseParams {
105+
module: string
106+
records: unknown
107+
duplicateCheckFields?: string
108+
trigger?: string
109+
}
110+
111+
export interface ZohoCrmDeleteRecordParams extends ZohoBaseParams {
112+
module: string
113+
recordId: string
114+
wfTrigger?: string
115+
}
116+
117+
export interface ZohoCrmSearchRecordsParams extends ZohoBaseParams {
118+
module: string
119+
criteria?: string
120+
email?: string
121+
phone?: string
122+
word?: string
123+
fields?: string
124+
page?: string | number
125+
perPage?: string | number
126+
}
127+
128+
export interface ZohoCrmCoqlQueryParams extends ZohoBaseParams {
129+
selectQuery: string
130+
}
131+
132+
export interface ZohoCrmCoqlQueryResponse extends ToolResponse {
133+
output: {
134+
records: Record<string, unknown>[]
135+
info: ZohoCrmPageInfo
136+
}
137+
}
138+
139+
export interface ZohoCrmGetModulesParams extends ZohoBaseParams {}
140+
141+
export interface ZohoCrmGetModulesResponse extends ToolResponse {
142+
output: {
143+
modules: Array<{
144+
apiName: string | null
145+
moduleName: string | null
146+
id: string | null
147+
pluralLabel: string | null
148+
singularLabel: string | null
149+
creatable: boolean | null
150+
editable: boolean | null
151+
deletable: boolean | null
152+
}>
153+
}
154+
}
155+
156+
export interface ZohoCrmGetFieldsParams extends ZohoBaseParams {
157+
module: string
158+
}
159+
160+
export interface ZohoCrmGetFieldsResponse extends ToolResponse {
161+
output: {
162+
fields: Array<{
163+
apiName: string | null
164+
displayLabel: string | null
165+
dataType: string | null
166+
id: string | null
167+
required: boolean | null
168+
readOnly: boolean | null
169+
length: number | null
170+
}>
171+
}
172+
}
173+
174+
export interface ZohoCrmGetUsersParams extends ZohoBaseParams {
175+
type?: string
176+
page?: string | number
177+
perPage?: string | number
178+
}
179+
180+
export interface ZohoCrmGetUsersResponse extends ToolResponse {
181+
output: {
182+
users: Record<string, unknown>[]
183+
info: ZohoCrmPageInfo
184+
}
185+
}
186+
187+
export interface ZohoCrmAddNoteParams extends ZohoBaseParams {
188+
parentId: string
189+
seModule: string
190+
noteTitle?: string
191+
noteContent: string
192+
}
193+
194+
export type ZohoCrmResponse =
195+
| ZohoCrmGetRecordsResponse
196+
| ZohoCrmWriteResponse
197+
| ZohoCrmCoqlQueryResponse
198+
| ZohoCrmGetModulesResponse
199+
| ZohoCrmGetFieldsResponse
200+
| ZohoCrmGetUsersResponse

0 commit comments

Comments
 (0)