You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(tools): point the boundary skill at the three metadata modules
The skill still routed `hasToolMetadata` and `getToolIds` to `@/tools/metadata`,
but this PR moved id resolution into `@/tools/tool-ids`. Left as-is it would
send the next caller to the 4 MB module for an existence check that costs
110 KB — the exact mistake the skill exists to prevent.
Also records the two properties a caller can silently get wrong: lookups guard
with `Object.hasOwn` (a bare bracket lookup returns inherited prototype members),
and they resolve unversioned names (246 tools are versioned, and a plain lookup
reports them missing rather than crashing).
Copy file name to clipboardExpand all lines: .agents/skills/tool-registry-boundary/SKILL.md
+12-8Lines changed: 12 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,17 +19,20 @@ You keep the 4,300-tool executable registry out of module graphs that don't exec
19
19
20
20
| you need | import | notes |
21
21
| --- | --- | --- |
22
-
| whether a tool id exists |`hasToolMetadata` from `@/tools/metadata`||
23
-
| a tool's params |`getToolParams` / `getToolMetadata` from `@/tools/metadata`||
24
-
| a tool's declared outputs |`getToolOutputsMetadata` from `@/tools/metadata-outputs`| separate module on purpose — see below |
25
-
| every tool id |`getToolIds` from `@/tools/metadata`||
22
+
| whether a tool id exists |`hasToolId` from `@/tools/tool-ids`|~110 KB — the cheapest module |
23
+
| to resolve an unversioned name |`resolveToolId` from `@/tools/tool-ids`||
24
+
| every tool id |`getToolIds` from `@/tools/tool-ids`||
25
+
| a tool's params |`getToolParams` / `getToolMetadata` from `@/tools/metadata`|~4 MB |
26
+
| a tool's declared outputs |`getToolOutputsMetadata` from `@/tools/metadata-outputs`|~4 MB, separate on purpose |
26
27
| to **execute** a tool |`getTool` from `@/tools/utils`, or `@/tools/utils.server`| server paths only |
27
28
28
-
Outputs live in their own module because they are roughly two thirds of the generated data and have a single consumer. Importing `@/tools/metadata` must never pull them — do not "helpfully" re-export one from the other.
29
+
Three modules, cheapest first. Ids are their own artifact because resolution and existence checks need only the key set; outputs are their own because they are the larger half of the data with a single consumer. `@/tools/metadata` and `@/tools/metadata-outputs` both resolve ids through `@/tools/tool-ids`, which is what keeps them independent of each other — do not "helpfully" re-export one from another, or every caller pays for all three.
30
+
31
+
All lookups guard with `Object.hasOwn`. `JSON.parse` yields an object with the normal prototype, so a bare bracket lookup returns inherited members: `getToolMetadata('constructor')` returned a *function* typed as tool metadata before that was fixed.
29
32
30
33
## The generated artifacts
31
34
32
-
`apps/sim/tools/generated/tool-metadata.ts` and `tool-outputs.ts` are produced by `scripts/sync-tool-metadata.ts`:
35
+
`apps/sim/tools/generated/tool-ids.ts`, `tool-metadata.ts` and `tool-outputs.ts` are produced by `scripts/sync-tool-metadata.ts`:
33
36
34
37
```bash
35
38
bun run tool-metadata:generate # after adding/changing a tool
@@ -43,6 +46,7 @@ Three non-obvious properties, each of which was measured and is easy to undo by
43
46
-**The data is a JSON string parsed at runtime, not an imported `.json` and not an object literal.** With `resolveJsonModule` (which this repo enables), a `.json` import makes TypeScript infer a literal type for all 4,300+ entries and takes `tsc --noEmit` from **12.6s to 8m07s** — a 38x regression. An ambient `declare module` does *not* short-circuit it, and an object literal costs the same. A single string literal is one cheap token for both the compiler and the bundler, and `JSON.parse` beats evaluating the equivalent literal at runtime. Do not "clean this up" into a `.json` import.
44
47
-**The generator refuses to emit function values.** If you add a field to `METADATA_FIELDS` that contains a closure, generation fails loudly rather than shipping executable config to the client. `hosting` and `schemaEnrichment` are excluded for exactly this reason (`hosting.enabled`, `pricing`, and `enrichSchema` are functions) — they are server-only.
45
48
-**Empty param entries are stripped.** The registry contains one (`stt_deepgram_v2`), which crashes callers that read `param.type` while iterating.
49
+
-**Lookups resolve versions.**`getTool` maps an unversioned name onto the newest version, and 246 tools are versioned. A plain key lookup would silently report them missing — a quiet correctness bug, not a crash. `resolveToolId` reproduces that against the id set and is differentially tested against the original.
46
50
47
51
## How to verify an edge actually got cut
48
52
@@ -59,9 +63,9 @@ Reference points measured on this repo:
59
63
|`tools/registry.ts` reachable |~4,900 |
60
64
|`tools/merge-params.ts` (leaf) | 2 |
61
65
|`providers/utils.ts` after cutting its `params` edge | 22 |
62
-
|`app/workspace/[workspaceId]/w/page.tsx` (canvas) | 6,591, of which 4,689 are the registry|
The canvas route reaches the registry through **four** redundant edges — `providers/utils` (via `tools/params`), `lib/workflows/blocks/block-outputs`, `lib/workflows/sanitization/validation`, and `serializer/index`. Cutting any one alone moves the module count by ~1. They must all be cut before anything improves; measure the route, not the file you edited.
68
+
The canvas route reached the registry through **four** redundant edges — `providers/utils` (via `tools/params`), `lib/workflows/blocks/block-outputs`, `lib/workflows/sanitization/validation`, and `serializer/index`. Cutting any one alone moved the module count by ~1. They all had to go before anything improved; measure the route, not the file you edited.
Copy file name to clipboardExpand all lines: .claude/commands/tool-registry-boundary.md
+12-8Lines changed: 12 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,17 +18,20 @@ You keep the 4,300-tool executable registry out of module graphs that don't exec
18
18
19
19
| you need | import | notes |
20
20
| --- | --- | --- |
21
-
| whether a tool id exists |`hasToolMetadata` from `@/tools/metadata`||
22
-
| a tool's params |`getToolParams` / `getToolMetadata` from `@/tools/metadata`||
23
-
| a tool's declared outputs |`getToolOutputsMetadata` from `@/tools/metadata-outputs`| separate module on purpose — see below |
24
-
| every tool id |`getToolIds` from `@/tools/metadata`||
21
+
| whether a tool id exists |`hasToolId` from `@/tools/tool-ids`|~110 KB — the cheapest module |
22
+
| to resolve an unversioned name |`resolveToolId` from `@/tools/tool-ids`||
23
+
| every tool id |`getToolIds` from `@/tools/tool-ids`||
24
+
| a tool's params |`getToolParams` / `getToolMetadata` from `@/tools/metadata`|~4 MB |
25
+
| a tool's declared outputs |`getToolOutputsMetadata` from `@/tools/metadata-outputs`|~4 MB, separate on purpose |
25
26
| to **execute** a tool |`getTool` from `@/tools/utils`, or `@/tools/utils.server`| server paths only |
26
27
27
-
Outputs live in their own module because they are roughly two thirds of the generated data and have a single consumer. Importing `@/tools/metadata` must never pull them — do not "helpfully" re-export one from the other.
28
+
Three modules, cheapest first. Ids are their own artifact because resolution and existence checks need only the key set; outputs are their own because they are the larger half of the data with a single consumer. `@/tools/metadata` and `@/tools/metadata-outputs` both resolve ids through `@/tools/tool-ids`, which is what keeps them independent of each other — do not "helpfully" re-export one from another, or every caller pays for all three.
29
+
30
+
All lookups guard with `Object.hasOwn`. `JSON.parse` yields an object with the normal prototype, so a bare bracket lookup returns inherited members: `getToolMetadata('constructor')` returned a *function* typed as tool metadata before that was fixed.
28
31
29
32
## The generated artifacts
30
33
31
-
`apps/sim/tools/generated/tool-metadata.ts` and `tool-outputs.ts` are produced by `scripts/sync-tool-metadata.ts`:
34
+
`apps/sim/tools/generated/tool-ids.ts`, `tool-metadata.ts` and `tool-outputs.ts` are produced by `scripts/sync-tool-metadata.ts`:
32
35
33
36
```bash
34
37
bun run tool-metadata:generate # after adding/changing a tool
@@ -42,6 +45,7 @@ Three non-obvious properties, each of which was measured and is easy to undo by
42
45
-**The data is a JSON string parsed at runtime, not an imported `.json` and not an object literal.** With `resolveJsonModule` (which this repo enables), a `.json` import makes TypeScript infer a literal type for all 4,300+ entries and takes `tsc --noEmit` from **12.6s to 8m07s** — a 38x regression. An ambient `declare module` does *not* short-circuit it, and an object literal costs the same. A single string literal is one cheap token for both the compiler and the bundler, and `JSON.parse` beats evaluating the equivalent literal at runtime. Do not "clean this up" into a `.json` import.
43
46
-**The generator refuses to emit function values.** If you add a field to `METADATA_FIELDS` that contains a closure, generation fails loudly rather than shipping executable config to the client. `hosting` and `schemaEnrichment` are excluded for exactly this reason (`hosting.enabled`, `pricing`, and `enrichSchema` are functions) — they are server-only.
44
47
-**Empty param entries are stripped.** The registry contains one (`stt_deepgram_v2`), which crashes callers that read `param.type` while iterating.
48
+
-**Lookups resolve versions.**`getTool` maps an unversioned name onto the newest version, and 246 tools are versioned. A plain key lookup would silently report them missing — a quiet correctness bug, not a crash. `resolveToolId` reproduces that against the id set and is differentially tested against the original.
45
49
46
50
## How to verify an edge actually got cut
47
51
@@ -58,9 +62,9 @@ Reference points measured on this repo:
58
62
|`tools/registry.ts` reachable |~4,900 |
59
63
|`tools/merge-params.ts` (leaf) | 2 |
60
64
|`providers/utils.ts` after cutting its `params` edge | 22 |
61
-
|`app/workspace/[workspaceId]/w/page.tsx` (canvas) | 6,591, of which 4,689 are the registry|
The canvas route reaches the registry through **four** redundant edges — `providers/utils` (via `tools/params`), `lib/workflows/blocks/block-outputs`, `lib/workflows/sanitization/validation`, and `serializer/index`. Cutting any one alone moves the module count by ~1. They must all be cut before anything improves; measure the route, not the file you edited.
67
+
The canvas route reached the registry through **four** redundant edges — `providers/utils` (via `tools/params`), `lib/workflows/blocks/block-outputs`, `lib/workflows/sanitization/validation`, and `serializer/index`. Cutting any one alone moved the module count by ~1. They all had to go before anything improved; measure the route, not the file you edited.
Copy file name to clipboardExpand all lines: .cursor/commands/tool-registry-boundary.md
+12-8Lines changed: 12 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,17 +14,20 @@ You keep the 4,300-tool executable registry out of module graphs that don't exec
14
14
15
15
| you need | import | notes |
16
16
| --- | --- | --- |
17
-
| whether a tool id exists |`hasToolMetadata` from `@/tools/metadata`||
18
-
| a tool's params |`getToolParams` / `getToolMetadata` from `@/tools/metadata`||
19
-
| a tool's declared outputs |`getToolOutputsMetadata` from `@/tools/metadata-outputs`| separate module on purpose — see below |
20
-
| every tool id |`getToolIds` from `@/tools/metadata`||
17
+
| whether a tool id exists |`hasToolId` from `@/tools/tool-ids`|~110 KB — the cheapest module |
18
+
| to resolve an unversioned name |`resolveToolId` from `@/tools/tool-ids`||
19
+
| every tool id |`getToolIds` from `@/tools/tool-ids`||
20
+
| a tool's params |`getToolParams` / `getToolMetadata` from `@/tools/metadata`|~4 MB |
21
+
| a tool's declared outputs |`getToolOutputsMetadata` from `@/tools/metadata-outputs`|~4 MB, separate on purpose |
21
22
| to **execute** a tool |`getTool` from `@/tools/utils`, or `@/tools/utils.server`| server paths only |
22
23
23
-
Outputs live in their own module because they are roughly two thirds of the generated data and have a single consumer. Importing `@/tools/metadata` must never pull them — do not "helpfully" re-export one from the other.
24
+
Three modules, cheapest first. Ids are their own artifact because resolution and existence checks need only the key set; outputs are their own because they are the larger half of the data with a single consumer. `@/tools/metadata` and `@/tools/metadata-outputs` both resolve ids through `@/tools/tool-ids`, which is what keeps them independent of each other — do not "helpfully" re-export one from another, or every caller pays for all three.
25
+
26
+
All lookups guard with `Object.hasOwn`. `JSON.parse` yields an object with the normal prototype, so a bare bracket lookup returns inherited members: `getToolMetadata('constructor')` returned a *function* typed as tool metadata before that was fixed.
24
27
25
28
## The generated artifacts
26
29
27
-
`apps/sim/tools/generated/tool-metadata.ts` and `tool-outputs.ts` are produced by `scripts/sync-tool-metadata.ts`:
30
+
`apps/sim/tools/generated/tool-ids.ts`, `tool-metadata.ts` and `tool-outputs.ts` are produced by `scripts/sync-tool-metadata.ts`:
28
31
29
32
```bash
30
33
bun run tool-metadata:generate # after adding/changing a tool
@@ -38,6 +41,7 @@ Three non-obvious properties, each of which was measured and is easy to undo by
38
41
-**The data is a JSON string parsed at runtime, not an imported `.json` and not an object literal.** With `resolveJsonModule` (which this repo enables), a `.json` import makes TypeScript infer a literal type for all 4,300+ entries and takes `tsc --noEmit` from **12.6s to 8m07s** — a 38x regression. An ambient `declare module` does *not* short-circuit it, and an object literal costs the same. A single string literal is one cheap token for both the compiler and the bundler, and `JSON.parse` beats evaluating the equivalent literal at runtime. Do not "clean this up" into a `.json` import.
39
42
-**The generator refuses to emit function values.** If you add a field to `METADATA_FIELDS` that contains a closure, generation fails loudly rather than shipping executable config to the client. `hosting` and `schemaEnrichment` are excluded for exactly this reason (`hosting.enabled`, `pricing`, and `enrichSchema` are functions) — they are server-only.
40
43
-**Empty param entries are stripped.** The registry contains one (`stt_deepgram_v2`), which crashes callers that read `param.type` while iterating.
44
+
-**Lookups resolve versions.**`getTool` maps an unversioned name onto the newest version, and 246 tools are versioned. A plain key lookup would silently report them missing — a quiet correctness bug, not a crash. `resolveToolId` reproduces that against the id set and is differentially tested against the original.
41
45
42
46
## How to verify an edge actually got cut
43
47
@@ -54,9 +58,9 @@ Reference points measured on this repo:
54
58
|`tools/registry.ts` reachable |~4,900 |
55
59
|`tools/merge-params.ts` (leaf) | 2 |
56
60
|`providers/utils.ts` after cutting its `params` edge | 22 |
57
-
|`app/workspace/[workspaceId]/w/page.tsx` (canvas) | 6,591, of which 4,689 are the registry|
The canvas route reaches the registry through **four** redundant edges — `providers/utils` (via `tools/params`), `lib/workflows/blocks/block-outputs`, `lib/workflows/sanitization/validation`, and `serializer/index`. Cutting any one alone moves the module count by ~1. They must all be cut before anything improves; measure the route, not the file you edited.
63
+
The canvas route reached the registry through **four** redundant edges — `providers/utils` (via `tools/params`), `lib/workflows/blocks/block-outputs`, `lib/workflows/sanitization/validation`, and `serializer/index`. Cutting any one alone moved the module count by ~1. They all had to go before anything improved; measure the route, not the file you edited.
0 commit comments