@dsh-cc/tools 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +209 -0
  4. package/README.zh.md +200 -0
  5. package/lib/abort-utils.d.ts +80 -0
  6. package/lib/abort-utils.d.ts.map +1 -0
  7. package/lib/abort-utils.js +186 -0
  8. package/lib/abort-utils.js.map +1 -0
  9. package/lib/cc-names.d.ts +100 -0
  10. package/lib/cc-names.d.ts.map +1 -0
  11. package/lib/cc-names.js +197 -0
  12. package/lib/cc-names.js.map +1 -0
  13. package/lib/code-mode.d.ts +46 -0
  14. package/lib/code-mode.d.ts.map +1 -0
  15. package/lib/code-mode.js +380 -0
  16. package/lib/code-mode.js.map +1 -0
  17. package/lib/define-tool.d.ts +103 -0
  18. package/lib/define-tool.d.ts.map +1 -0
  19. package/lib/define-tool.js +354 -0
  20. package/lib/define-tool.js.map +1 -0
  21. package/lib/index.d.ts +348 -0
  22. package/lib/index.d.ts.map +1 -0
  23. package/lib/index.js +289 -0
  24. package/lib/index.js.map +1 -0
  25. package/lib/invariant.d.ts +13 -0
  26. package/lib/invariant.d.ts.map +1 -0
  27. package/lib/invariant.js +123 -0
  28. package/lib/invariant.js.map +1 -0
  29. package/lib/json-render.d.ts +18 -0
  30. package/lib/json-render.d.ts.map +1 -0
  31. package/lib/json-render.js +111 -0
  32. package/lib/json-render.js.map +1 -0
  33. package/lib/json-schema-value.d.ts +18 -0
  34. package/lib/json-schema-value.d.ts.map +1 -0
  35. package/lib/json-schema-value.js +238 -0
  36. package/lib/json-schema-value.js.map +1 -0
  37. package/lib/json-schema.d.ts +101 -0
  38. package/lib/json-schema.d.ts.map +1 -0
  39. package/lib/json-schema.js +351 -0
  40. package/lib/json-schema.js.map +1 -0
  41. package/lib/presentation.d.ts +367 -0
  42. package/lib/presentation.d.ts.map +1 -0
  43. package/lib/presentation.js +8 -0
  44. package/lib/presentation.js.map +1 -0
  45. package/lib/py-names.d.ts +163 -0
  46. package/lib/py-names.d.ts.map +1 -0
  47. package/lib/py-names.js +236 -0
  48. package/lib/py-names.js.map +1 -0
  49. package/lib/py-render.d.ts +23 -0
  50. package/lib/py-render.d.ts.map +1 -0
  51. package/lib/py-render.js +434 -0
  52. package/lib/py-render.js.map +1 -0
  53. package/lib/py-sdk-doc.d.ts +29 -0
  54. package/lib/py-sdk-doc.d.ts.map +1 -0
  55. package/lib/py-sdk-doc.js +114 -0
  56. package/lib/py-sdk-doc.js.map +1 -0
  57. package/lib/py-types.d.ts +34 -0
  58. package/lib/py-types.d.ts.map +1 -0
  59. package/lib/py-types.js +40 -0
  60. package/lib/py-types.js.map +1 -0
  61. package/lib/run-code-defs.d.ts +77 -0
  62. package/lib/run-code-defs.d.ts.map +1 -0
  63. package/lib/run-code-defs.js +98 -0
  64. package/lib/run-code-defs.js.map +1 -0
  65. package/lib/runtime-code.d.ts +65 -0
  66. package/lib/runtime-code.d.ts.map +1 -0
  67. package/lib/runtime-code.js +129 -0
  68. package/lib/runtime-code.js.map +1 -0
  69. package/lib/runtime-core.d.ts +130 -0
  70. package/lib/runtime-core.d.ts.map +1 -0
  71. package/lib/runtime-core.js +33 -0
  72. package/lib/runtime-core.js.map +1 -0
  73. package/lib/runtime-execute.d.ts +82 -0
  74. package/lib/runtime-execute.d.ts.map +1 -0
  75. package/lib/runtime-execute.js +333 -0
  76. package/lib/runtime-execute.js.map +1 -0
  77. package/lib/runtime-registry.d.ts +131 -0
  78. package/lib/runtime-registry.d.ts.map +1 -0
  79. package/lib/runtime-registry.js +269 -0
  80. package/lib/runtime-registry.js.map +1 -0
  81. package/lib/runtime-results.d.ts +32 -0
  82. package/lib/runtime-results.d.ts.map +1 -0
  83. package/lib/runtime-results.js +163 -0
  84. package/lib/runtime-results.js.map +1 -0
  85. package/lib/runtime-schemas.d.ts +112 -0
  86. package/lib/runtime-schemas.d.ts.map +1 -0
  87. package/lib/runtime-schemas.js +222 -0
  88. package/lib/runtime-schemas.js.map +1 -0
  89. package/lib/scheduler.d.ts +22 -0
  90. package/lib/scheduler.d.ts.map +1 -0
  91. package/lib/scheduler.js +21 -0
  92. package/lib/scheduler.js.map +1 -0
  93. package/lib/schema-spec.d.ts +154 -0
  94. package/lib/schema-spec.d.ts.map +1 -0
  95. package/lib/schema-spec.js +9 -0
  96. package/lib/schema-spec.js.map +1 -0
  97. package/lib/schema.d.ts +5 -0
  98. package/lib/schema.d.ts.map +1 -0
  99. package/lib/schema.js +3 -0
  100. package/lib/schema.js.map +1 -0
  101. package/lib/testing.d.ts +25 -0
  102. package/lib/testing.d.ts.map +1 -0
  103. package/lib/testing.js +25 -0
  104. package/lib/testing.js.map +1 -0
  105. package/lib/tool-layer.d.ts +49 -0
  106. package/lib/tool-layer.d.ts.map +1 -0
  107. package/lib/tool-layer.js +57 -0
  108. package/lib/tool-layer.js.map +1 -0
  109. package/lib/tool-types.d.ts +385 -0
  110. package/lib/tool-types.d.ts.map +1 -0
  111. package/lib/tool-types.js +11 -0
  112. package/lib/tool-types.js.map +1 -0
  113. package/lib/ts-types.d.ts +37 -0
  114. package/lib/ts-types.d.ts.map +1 -0
  115. package/lib/ts-types.js +265 -0
  116. package/lib/ts-types.js.map +1 -0
  117. package/lib/types.d.ts +55 -0
  118. package/lib/types.d.ts.map +1 -0
  119. package/lib/types.js +7 -0
  120. package/lib/types.js.map +1 -0
  121. package/package.json +65 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Claude Code → harness tool-name translation. CC-authored tool names flow
3
+ * into `tools.restrict()` and other name-level seams through the preset and
4
+ * skill-loading plugins; the harness registers its own authoritative,
5
+ * mostly-lowercase global tool set, and a capitalized CC name handed verbatim
6
+ * to `tools.restrict()` fails the whole session because restrict validates
7
+ * names strictly. This module owns the canonical mapping between the two
8
+ * vocabularies so plugins never hand-code the table.
9
+ * @module @dsh-cc/tools/src/cc-names
10
+ */
11
+ /**
12
+ * The authoritative list of global tools the deepseek-harness registers,
13
+ * observed from its web profile. This is the universe every CC name must be
14
+ * translated INTO and the lenient policy uses it to distinguish a known
15
+ * harness name from an unknown CC name. If the harness registry changes, the
16
+ * {@link KNOWN_HARNESS_TOOLS} test fails loudly rather than silently drifting.
17
+ */
18
+ const HARNESS_TOOLS = [
19
+ 'EnterWorktree', 'ExitWorktree', 'NotebookEdit', 'Sleep', 'ToolSearch',
20
+ 'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode',
21
+ 'get_goal', 'glob', 'grep', 'interrupt_agent', 'job_kill', 'job_list',
22
+ 'job_output', 'list_agents', 'ralph', 'read', 'read_image', 'send_message',
23
+ 'skill', 'subagent', 'subagent_fork', 'todo_write', 'update_goal',
24
+ 'web_fetch', 'web_search', 'workflow', 'write',
25
+ ];
26
+ /**
27
+ * The exact set of authoritative harness tool names {@link KNOWN_HARNESS_TOOLS}
28
+ * is derived from, kept as a module-private source constant so the exported
29
+ * set and the reverse index in {@link ccToolAliases} share one spelling.
30
+ */
31
+ const HARNESS_TOOL_NAMES = HARNESS_TOOLS;
32
+ /**
33
+ * The set of global tool names the deepseek-harness registers (see
34
+ * {@link HARNESS_TOOLS}). The lenient translation policy uses it to pass a
35
+ * known harness name through untouched instead of dropping it, since such a
36
+ * name is already in the restrict vocabulary and needs no translation.
37
+ */
38
+ export const KNOWN_HARNESS_TOOLS = new Set(HARNESS_TOOL_NAMES);
39
+ /**
40
+ * Claude Code tool name → harness tool name(s). One CC tool may answer through
41
+ * several harness tools (e.g. `Read` covers both `read` and `read_image`), so
42
+ * each value is a list; an entry that has no distinct harness equivalent maps
43
+ * to its own capitalized form (the harness exposes it under that exact name,
44
+ * e.g. `NotebookEdit`).
45
+ *
46
+ * Deliberately NOT mapped (and hence rejected-or-dropped by
47
+ * {@link translateToolNames} depending on policy):
48
+ * - `LS` — legacy Claude Code alias with no harness equivalent.
49
+ * - `EnterPlanMode` — the harness has no plan-mode entry point (model-driven
50
+ * plan mode is not a tool).
51
+ * - `CronCreate`/`CronDelete`/`CronList` — lifecycle utilities with no harness
52
+ * equivalent in the authoritative global set.
53
+ * - `mcp__*` names — namespace-qualified MCP paths; the harness exposes MCP
54
+ * tools under their own names, which a caller should list explicitly.
55
+ */
56
+ export const CC_TO_HARNESS_TOOLS = {
57
+ Read: ['read', 'read_image'],
58
+ Write: ['write'],
59
+ Edit: ['edit'],
60
+ MultiEdit: ['edit'],
61
+ NotebookEdit: ['NotebookEdit'],
62
+ Bash: ['bash'],
63
+ BashOutput: ['job_output'],
64
+ KillBash: ['job_kill'],
65
+ Grep: ['grep'],
66
+ Glob: ['glob'],
67
+ WebFetch: ['web_fetch'],
68
+ WebSearch: ['web_search'],
69
+ TodoWrite: ['todo_write'],
70
+ Task: ['subagent', 'subagent_fork'],
71
+ Skill: ['skill'],
72
+ AskUserQuestion: ['ask_user_question'],
73
+ ExitPlanMode: ['exit_plan_mode'],
74
+ Workflow: ['workflow'],
75
+ Sleep: ['Sleep'],
76
+ ToolSearch: ['ToolSearch'],
77
+ EnterWorktree: ['EnterWorktree'],
78
+ ExitWorktree: ['ExitWorktree'],
79
+ };
80
+ /**
81
+ * Strip a trailing parenthesized argument specification from a CC name, e.g.
82
+ * `Bash(git status)` → `Bash` and `WebFetch(domain:example.com)` → `WebFetch`.
83
+ * `tools.restrict()` is name-level only, so stripping deliberately widens a
84
+ * name+args constraint to name-level — this is a decision, not an accident: a
85
+ * name-level restrict cannot honor a per-args constraint, and the alternative
86
+ * (keeping the arg-spec) would produce a name `tools.restrict()` rejects.
87
+ * @param name - the raw CC name as it appears in the tool list.
88
+ * @returns the name with any trailing `(...)` argument specification removed.
89
+ */
90
+ function stripArgSpec(name) {
91
+ const paren = name.indexOf('(');
92
+ return paren === -1 ? name : name.slice(0, paren);
93
+ }
94
+ /**
95
+ * Translate a list of Claude Code tool names into harness tool names for a
96
+ * `tools.restrict()`-style name-level filter.
97
+ *
98
+ * Each entry is (optionally) stripped of its trailing arg-spec (see
99
+ * {@link stripArgSpec}), then translated:
100
+ * - A name in {@link CC_TO_HARNESS_TOOLS} expands to every mapped harness name.
101
+ * - Under `'strict'`, every other name passes through VERBATIM (unknown names
102
+ * then fail loudly on `tools.restrict()`'s own strict validation — fail-fast
103
+ * is intended at config/agent load time).
104
+ * - Under `'lenient'`, a name in {@link KNOWN_HARNESS_TOOLS} passes through,
105
+ * and any other name is dropped with a diagnostic via `onDiagnostic`, so a
106
+ * single unknown or model-invented name never kills the session.
107
+ *
108
+ * Results are deduplicated preserving first-occurrence order.
109
+ *
110
+ * @param names - the tool-name list to translate.
111
+ * @param policy - `'strict'` (trusted config, fail-fast) or `'lenient'` (user/model data, drop unknown).
112
+ * @param onDiagnostic - invoked for each dropped name (and for an empty lenient result); default no-op.
113
+ * @returns the translated harness-name list, or `undefined` under `'lenient'`
114
+ * when every input was dropped (meaning "no restriction"). Under `'strict'`,
115
+ * always returns an array — an empty input yields an empty array copy, never `undefined`.
116
+ */
117
+ export function translateToolNames(names, policy, onDiagnostic = () => { }) {
118
+ const diagnose = onDiagnostic;
119
+ const results = [];
120
+ for (const rawName of names) {
121
+ const name = stripArgSpec(rawName);
122
+ const mapped = CC_TO_HARNESS_TOOLS[name];
123
+ if (mapped !== undefined) {
124
+ for (const harnessName of mapped) {
125
+ if (!results.includes(harnessName))
126
+ results.push(harnessName);
127
+ }
128
+ continue;
129
+ }
130
+ if (policy === 'strict') {
131
+ if (!results.includes(name))
132
+ results.push(name);
133
+ continue;
134
+ }
135
+ if (KNOWN_HARNESS_TOOLS.has(name)) {
136
+ if (!results.includes(name))
137
+ results.push(name);
138
+ continue;
139
+ }
140
+ diagnose(`dropping unknown tool name "${name}" from CC tool list`);
141
+ }
142
+ if (policy === 'lenient') {
143
+ if (results.length === 0) {
144
+ diagnose('dropping all CC tool names — resulting tool restriction is empty');
145
+ return undefined;
146
+ }
147
+ }
148
+ return [...results];
149
+ }
150
+ /**
151
+ * The distinct names one harness tool answers to for match-time comparison
152
+ * (permission rules, hook matchers). Returns the input name itself plus every
153
+ * CC name whose mapping includes the input, derived from
154
+ * {@link CC_TO_HARNESS_TOOLS} via a reverse index — never a hand-written second
155
+ * table. When the input IS a CC name found in the map, returns the CC name
156
+ * first followed by its mapped harness names.
157
+ *
158
+ * Examples: `'read'` → `['read', 'Read']`; `'read_image'` → `['read_image',
159
+ * 'Read']`; `'bash'` → `['bash', 'Bash']`; `'edit'` → `['edit', 'Edit',
160
+ * 'MultiEdit']`; `'Bash'` → `['Bash', 'bash']`; `'pwsh'` → `['pwsh']`.
161
+ * @param name - a harness tool name or Claude Code tool name.
162
+ * @returns every distinct name the harness tool is matched by, input first.
163
+ */
164
+ export function ccToolAliases(name) {
165
+ const mapped = CC_TO_HARNESS_TOOLS[name];
166
+ if (mapped !== undefined)
167
+ return [name, ...mapped];
168
+ const aliases = [name];
169
+ for (const [ccName, harnessNames] of Object.entries(CC_TO_HARNESS_TOOLS)) {
170
+ if (harnessNames.includes(name) && !aliases.includes(ccName))
171
+ aliases.push(ccName);
172
+ }
173
+ return aliases;
174
+ }
175
+ /**
176
+ * The CC canonical name for a harness tool — the first CC name in
177
+ * {@link CC_TO_HARNESS_TOOLS} whose mapping contains the input. This is the
178
+ * inverse of {@link translateToolNames} for the name-identity direction CC-facing
179
+ * surfaces need (hook payloads, permissions reports): a hook script written for
180
+ * Claude Code expects the CC spelling, not the harness's lowercase one.
181
+ *
182
+ * Examples: `'read'` → `'Read'`; `'read_image'` → `'Read'`; `'edit'` →
183
+ * `'Edit'`; `'subagent'` → `'Task'`; `'subagent_fork'` → `'Task'`; `'Bash'` →
184
+ * `'Bash'`; `'pwsh'` → `'pwsh'` (no CC alias, unchanged); `'ralph'` →
185
+ * `'ralph'` (harness-only, unchanged).
186
+ * @param name - a harness tool name (lowercase) or an already-CC canonical name.
187
+ * @returns the first CC name matching the input, or the input unchanged when no
188
+ * CC alias exists (including when the input is itself an existing CC name).
189
+ */
190
+ export function ccCanonicalToolName(name) {
191
+ for (const [ccName, harnessNames] of Object.entries(CC_TO_HARNESS_TOOLS)) {
192
+ if (harnessNames.includes(name))
193
+ return ccName;
194
+ }
195
+ return name;
196
+ }
197
+ //# sourceMappingURL=cc-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cc-names.js","sourceRoot":"","sources":["../src/cc-names.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Code Mode `run_code` transport. Programs call the registry's agent-visible
3
+ * tools through nested executions scheduled under the native concurrency
4
+ * contract; each sub-dispatch is logged for reconstruction, while only the
5
+ * outer curated result enters model history.
6
+ * @module @dsh-cc/tools/src/code-mode
7
+ */
8
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm';
9
+ import type { CodeRuntime } from '@deepseek-ai/dsh-code-runtime';
10
+ import type { ToolRuntimeCore } from './runtime-core.ts';
11
+ import type { CodeDispatchLog, ToolDefinition } from './index.ts';
12
+ export { CodeRunFailedError, RUN_CODE_NAME, SDK_SECTION_ORDER } from './run-code-defs.ts';
13
+ export type { CodeSdkLanguage } from './run-code-defs.ts';
14
+ /**
15
+ * Registry-private capabilities the bridge receives at construction — the
16
+ * `requireRuntime` idiom: operations only the owning registry can mint stay
17
+ * off its public service API and flow here as closures instead.
18
+ */
19
+ export interface RunCodeBridgeOptions {
20
+ /** Resolves `ctx.codeRuntime` or throws the loud misconfiguration error (shared with the registry's assembly-time checks). */
21
+ requireRuntime: () => CodeRuntime;
22
+ /**
23
+ * Reads `ctx.codeRuntime` without throwing: `undefined` when none is mounted.
24
+ * Lets schema emission tell "no runtime" (degrade to TS; the readers that
25
+ * reach it are {@link resolveFlavor}'s) apart from "unknown language" (fail
26
+ * loud).
27
+ */
28
+ peekRuntime: () => CodeRuntime | undefined;
29
+ /** The run's overlap cap for parallel-classified sub-calls (the registry passes its validated `maxParallelSubCalls`). */
30
+ maxParallel: number;
31
+ /** Runs the contained `tools/code-dispatch-log` waterfall over one settled sub-dispatch (the registry's private invoker). */
32
+ shapeDispatchLog: (dispatch: CodeDispatchLog) => Promise<ContentBlock[]>;
33
+ }
34
+ /**
35
+ * Build the `run_code` {@link ToolDefinition}: required `code` and
36
+ * `description` parameters, executed through the dispatch bridge described
37
+ * above. The
38
+ * registry reserves it as presentation infrastructure under non-native modes,
39
+ * outside the filterable global/scoped capability layers.
40
+ * @param registry - the owning registry (sub-calls go through its `execute`,
41
+ * bindings cover its registered tools).
42
+ * @param options - the registry-private capabilities described above.
43
+ * @returns the registry-ready definition.
44
+ */
45
+ export declare function createRunCodeTool(registry: ToolRuntimeCore, options: RunCodeBridgeOptions): ToolDefinition;
46
+ //# sourceMappingURL=code-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-mode.d.ts","sourceRoot":"","sources":["../src/code-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EAAsC,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAMpG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAuC,MAAM,YAAY,CAAA;AAGtG,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACzF,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAKzD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,8HAA8H;IAC9H,cAAc,EAAE,MAAM,WAAW,CAAA;IACjC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,WAAW,GAAG,SAAS,CAAA;IAC1C,yHAAyH;IACzH,WAAW,EAAE,MAAM,CAAA;IACnB,6HAA6H;IAC7H,gBAAgB,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;CACzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,oBAAoB,GAAG,cAAc,CA2X1G"}
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Code Mode `run_code` transport. Programs call the registry's agent-visible
3
+ * tools through nested executions scheduled under the native concurrency
4
+ * contract; each sub-dispatch is logged for reconstruction, while only the
5
+ * outer curated result enters model history.
6
+ * @module @dsh-cc/tools/src/code-mode
7
+ */
8
+ import { CallId } from '@deepseek-ai/dsh-llm';
9
+ import { jsonNormalizeArgs, renderValue } from "./json-render.js";
10
+ import { CodeRunFailedError, resolveFlavor, RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION, RUN_CODE_NAME, TYPESCRIPT_FLAVOR } from "./run-code-defs.js";
11
+ import { defineTool, parameterSchemaSpecToJsonSchema } from "./schema.js";
12
+ import { TOOL_RUNTIME_SCHEDULER } from "./scheduler.js";
13
+ export { CodeRunFailedError, RUN_CODE_NAME, SDK_SECTION_ORDER } from "./run-code-defs.js";
14
+ /**
15
+ * Build the `run_code` {@link ToolDefinition}: required `code` and
16
+ * `description` parameters, executed through the dispatch bridge described
17
+ * above. The
18
+ * registry reserves it as presentation infrastructure under non-native modes,
19
+ * outside the filterable global/scoped capability layers.
20
+ * @param registry - the owning registry (sub-calls go through its `execute`,
21
+ * bindings cover its registered tools).
22
+ * @param options - the registry-private capabilities described above.
23
+ * @returns the registry-ready definition.
24
+ */
25
+ export function createRunCodeTool(registry, options) {
26
+ const { requireRuntime, peekRuntime, maxParallel, shapeDispatchLog } = options;
27
+ const definition = defineTool({
28
+ name: RUN_CODE_NAME,
29
+ // The description and `code` parameter description are placeholders here:
30
+ // the language-aware getters installed below replace both, resolving the
31
+ // loaded runtime's flavor at schema-emission time so the schema the MODEL
32
+ // sees matches the SDK section's language. Argument VALIDATION still keys
33
+ // off this static spec (defineTool closes over it), which is language-
34
+ // independent (one required string `code`).
35
+ description: TYPESCRIPT_FLAVOR.description,
36
+ parameters: {
37
+ code: { type: 'string', required: true, description: TYPESCRIPT_FLAVOR.codeDescription },
38
+ description: {
39
+ type: 'string',
40
+ required: true,
41
+ description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION,
42
+ },
43
+ },
44
+ output: {
45
+ schema: {
46
+ type: 'object',
47
+ additionalProperties: false,
48
+ properties: {
49
+ logs: { type: 'array', required: true, items: { type: 'string' } },
50
+ result: { type: 'json' },
51
+ },
52
+ },
53
+ render: (_args, value) => {
54
+ const rendered = value.result === undefined ? '' : renderValue(value.result);
55
+ const parts = [value.logs.join('\n'), rendered].filter(part => part.length > 0);
56
+ return [{ type: 'text', text: parts.length > 0 ? parts.join('\n') : '(run_code completed with no output)' }];
57
+ },
58
+ },
59
+ async execute(args, exec) {
60
+ if (args.description.trim().length === 0) {
61
+ throw new Error('invalid description: expected a non-empty string');
62
+ }
63
+ const runtime = requireRuntime();
64
+ // The run-scoped abort: follows the outer signal in, and fires when the
65
+ // run settles for ANY reason, so an in-flight sub-dispatch is aborted
66
+ // (its executor kills on this signal) instead of orphaned, and
67
+ // queued-unstarted dispatches are abandoned.
68
+ const runController = new AbortController();
69
+ const onOuterAbort = () => { runController.abort(exec.signal.reason); };
70
+ exec.signal.addEventListener('abort', onOuterAbort, { once: true });
71
+ let dispatches = 0;
72
+ const pendingQueue = [];
73
+ const inFlight = new Set();
74
+ /** Tracked settle-event side work (log-content listener + append), drained at run settlement. */
75
+ const logWork = new Set();
76
+ const commitQueue = [];
77
+ let exclusiveActive = false;
78
+ let driving = false;
79
+ let driverRun = Promise.resolve();
80
+ let wake;
81
+ const wakeup = () => {
82
+ const release = wake;
83
+ wake = undefined;
84
+ release?.();
85
+ };
86
+ /**
87
+ * The single ordered lane. Each pass commits the head-of-line settled
88
+ * dispatch (ordered post-execute), then starts the next queued entry if
89
+ * its slot is free (ordered pre-execute), and otherwise sleeps until a
90
+ * body settles or a new submission arrives. One run reaching the
91
+ * empty-queues/empty-pool state is quiescence.
92
+ */
93
+ const drive = () => {
94
+ if (driving)
95
+ return driverRun;
96
+ driving = true;
97
+ driverRun = (async () => {
98
+ try {
99
+ for (;;) {
100
+ // Create the wakeup promise before inspecting state so a settle or submission arriving
101
+ // between the checks and the await below cannot be lost.
102
+ const signal = new Promise((resolve) => { wake = resolve; });
103
+ const commitHead = commitQueue[0];
104
+ if (commitHead !== undefined && commitHead.settled) {
105
+ commitQueue.shift();
106
+ await commitHead.commit();
107
+ // The barrier covers post-execute: later starts wait for the
108
+ // exclusive call's full pipeline, as under the native loop.
109
+ if (commitHead.mode === 'exclusive')
110
+ exclusiveActive = false;
111
+ continue;
112
+ }
113
+ const head = pendingQueue[0];
114
+ if (head !== undefined) {
115
+ if (runController.signal.aborted) {
116
+ pendingQueue.shift();
117
+ head.abandon();
118
+ continue;
119
+ }
120
+ // Reclassify at start time (fail-closed on registry changes).
121
+ const mode = head.classify();
122
+ const capacity = !exclusiveActive
123
+ && (mode === 'exclusive' ? inFlight.size === 0 : inFlight.size < maxParallel);
124
+ if (capacity) {
125
+ if (mode === 'exclusive')
126
+ exclusiveActive = true;
127
+ head.mode = mode;
128
+ pendingQueue.shift();
129
+ // Joined before start() so the commit cursor sees submission
130
+ // order; nothing commits it until `settled` flips.
131
+ commitQueue.push(head);
132
+ await head.start();
133
+ const flight = head.flight.finally(() => {
134
+ inFlight.delete(flight);
135
+ wakeup();
136
+ });
137
+ inFlight.add(flight);
138
+ continue;
139
+ }
140
+ }
141
+ if (pendingQueue.length === 0 && commitQueue.length === 0 && inFlight.size === 0)
142
+ return;
143
+ await signal;
144
+ }
145
+ }
146
+ finally {
147
+ driving = false;
148
+ wake = undefined;
149
+ }
150
+ })();
151
+ return driverRun;
152
+ };
153
+ /** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */
154
+ const drainDispatches = async () => {
155
+ // The abort already fired: the driver abandons queued-unstarted
156
+ // entries, awaits the live pool, and drains the ordered commit lane —
157
+ // including a commit already in progress when the program returned.
158
+ await drive();
159
+ // Every settle event is appended inside the open run_code turn
160
+ // (tasks self-remove on settlement).
161
+ while (logWork.size > 0)
162
+ await Promise.allSettled([...logWork]);
163
+ };
164
+ // Read through a call, not a bare property: the abort state genuinely
165
+ // changes across awaits, and a direct `.aborted` re-check after one
166
+ // would be narrowed away by control flow analysis.
167
+ const runOver = () => runController.signal.aborted;
168
+ const binding = (name) => async (rawArgs) => {
169
+ if (runOver()) {
170
+ throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
171
+ }
172
+ const normalized = jsonNormalizeArgs(rawArgs);
173
+ const n = ++dispatches;
174
+ const subCallId = CallId(`${String(exec.callId)}:code:${n}`);
175
+ const input = {
176
+ callId: subCallId,
177
+ rootCallId: exec.rootCallId,
178
+ name,
179
+ arguments: normalized.dispatched,
180
+ ...exec.agent ? { agent: exec.agent } : {},
181
+ parent: exec.token,
182
+ signal: runController.signal,
183
+ };
184
+ const scheduler = registry[TOOL_RUNTIME_SCHEDULER];
185
+ const outcome = await new Promise((resolve, reject) => {
186
+ // Set by the dispatch stage (or start() for a pre-settled result): what commit() finalizes in submission order.
187
+ let parked;
188
+ const settle = (result) => {
189
+ // The program gets its value NOW: the log-content listener (for
190
+ // example, a spill backend) must never delay the binding or occupy
191
+ // a dispatch slot. The event append is tracked side work; the run's
192
+ // settlement drains logWork so every settle event is still appended
193
+ // inside the open turn (shapeDispatchLog is contained, so this
194
+ // chain cannot reject).
195
+ resolve(result.isError
196
+ ? { isError: true, message: result.error.message }
197
+ : { isError: false, value: result.value });
198
+ const agent = exec.agent;
199
+ if (agent === undefined)
200
+ return;
201
+ const task = (async () => {
202
+ // The listener may replace the durable copy with a preview and
203
+ // locator; the program's value and model-visible result are
204
+ // untouched.
205
+ const logged = await shapeDispatchLog({
206
+ exec, agent, subCallId, name, isError: result.isError,
207
+ // The registry deep-froze this projection at result
208
+ // finalization; append snapshots the final copy again, so
209
+ // the log stays detached.
210
+ content: result.content,
211
+ });
212
+ agent.session.append('tool/code-dispatch', {
213
+ rootCallId: exec.rootCallId,
214
+ parentCallId: exec.callId,
215
+ subCallId,
216
+ name,
217
+ // The SIBLING parse of the dispatched value: byte-identical JSON,
218
+ // but a separate object — a tool mutating its args cannot desync
219
+ // this record from what it actually received.
220
+ arguments: normalized.logged,
221
+ isError: result.isError,
222
+ content: logged,
223
+ });
224
+ })().finally(() => { logWork.delete(task); });
225
+ logWork.add(task);
226
+ };
227
+ pendingQueue.push({
228
+ flight: Promise.resolve(),
229
+ settled: false,
230
+ // Re-read per driver pass against the same agent view the SDK
231
+ // declared; fail-closed exclusive when undeclared/invalid.
232
+ classify: () => registry.executionMode(input).kind,
233
+ abandon: () => {
234
+ reject(new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`));
235
+ },
236
+ async start() {
237
+ exec.agent?.session.append('tool/code-dispatch-start', {
238
+ rootCallId: exec.rootCallId,
239
+ parentCallId: exec.callId,
240
+ subCallId,
241
+ name,
242
+ arguments: normalized.logged,
243
+ });
244
+ // Ordered prepare runs INSIDE the driver lane: the next entry's
245
+ // pre-execute waits for this resolution, as under the native
246
+ // scheduler. Only the launched body below overlaps.
247
+ const prepared = await scheduler.prepare(input);
248
+ if (prepared.kind === 'dispatch') {
249
+ this.flight = scheduler.dispatch(prepared.exec).then((dispatchOutcome) => {
250
+ parked = { kind: dispatchOutcome.kind, exec: prepared.exec, result: dispatchOutcome.result };
251
+ this.settled = true;
252
+ });
253
+ return;
254
+ }
255
+ parked = { kind: prepared.kind, exec: prepared.exec, result: prepared.result };
256
+ this.settled = true;
257
+ },
258
+ async commit() {
259
+ /* v8 ignore next -- commit() runs only after `settled` flipped, which set parked. */
260
+ if (parked === undefined)
261
+ return;
262
+ const result = parked.kind === 'post-result'
263
+ ? await scheduler.finalize(parked.exec, parked.result)
264
+ : scheduler.finish(parked.exec, parked.result);
265
+ for (const context of result.additionalContexts ?? []) {
266
+ exec.deferContext(context);
267
+ }
268
+ // The composite forwards `additionalContexts` above and
269
+ // `concludesTurn` here from the nested result. Only a successful
270
+ // nested result can carry the terminal marker
271
+ // (ToolExecutionFailure types it never), so a policy-converted
272
+ // failure cannot stop the turn through a recovering program.
273
+ if (result.concludesTurn)
274
+ exec.concludeTurn();
275
+ settle(result);
276
+ // Backpressure on pending event-append tasks: each task retains
277
+ // a full result while a slow backend stores it, so the pool cap
278
+ // bounds their count. Beyond the cap, the
279
+ // ordered lane waits, so later sub-calls cannot start and
280
+ // pending I/O/memory cannot grow without bound.
281
+ while (logWork.size > maxParallel)
282
+ await Promise.race(logWork);
283
+ },
284
+ });
285
+ wakeup();
286
+ void drive();
287
+ });
288
+ // A budget expiry or outer cancel that occurs while this call was in
289
+ // flight already aborted the dispatch; stop the program now rather
290
+ // than hand it a result from a run that is over.
291
+ if (runOver()) {
292
+ throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} result discarded`);
293
+ }
294
+ // The worker turns a binding rejection into ToolCallError and adds
295
+ // only the binding name. Native content and internal error metadata
296
+ // stay outside the program-facing failure contract.
297
+ if (outcome.isError)
298
+ throw new Error(outcome.message);
299
+ return outcome.value;
300
+ };
301
+ // Null-prototype + defineProperty, mirroring the worker-side namespace
302
+ // build: a registered tool named `__proto__` must become an ordinary
303
+ // own key (a plain-object assignment would hit the prototype setter,
304
+ // silently dropping the binding), and the runtime host resolves
305
+ // binding names as own properties only.
306
+ const functions = Object.create(null);
307
+ // Enumerate the CALLING AGENT's visible set (scoped tools join,
308
+ // restricted globals vanish) — the same view the SDK section declared,
309
+ // so a program can bind exactly what its prompt promised; sub-dispatch
310
+ // re-resolves per call through the same view (exec.agent threads down).
311
+ for (const schema of registry.schemas(exec.agent)) {
312
+ if (schema.name === RUN_CODE_NAME)
313
+ continue;
314
+ Object.defineProperty(functions, schema.name, { enumerable: true, value: binding(schema.name) });
315
+ }
316
+ try {
317
+ let result;
318
+ try {
319
+ result = await runtime.run({
320
+ program: args.code,
321
+ bindings: [{
322
+ global: 'tools',
323
+ functions,
324
+ errorClass: { name: 'ToolCallError', memberNameProperty: 'toolName' },
325
+ }],
326
+ signal: runController.signal,
327
+ });
328
+ }
329
+ finally {
330
+ // Abort sub-dispatches and drain every in-flight dispatch before
331
+ // closing the turn (queued-unstarted ones are abandoned unlogged).
332
+ // Binding failures remain observable through their individual promises.
333
+ runController.abort('run_code settled');
334
+ await drainDispatches();
335
+ }
336
+ if (result.error) {
337
+ const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join('\n')}` : '';
338
+ throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`);
339
+ }
340
+ return {
341
+ logs: result.logs,
342
+ ...result.value !== undefined ? { result: result.value } : {},
343
+ };
344
+ }
345
+ finally {
346
+ exec.signal.removeEventListener('abort', onOuterAbort);
347
+ }
348
+ },
349
+ // The model-authored description is the call's always-visible UI label
350
+ // (the bash `description` precedent); the program itself rides rawInput.
351
+ presentCall: args => ({
352
+ card: 'generic',
353
+ title: args.description,
354
+ kind: 'execute',
355
+ rawInput: args.code,
356
+ }),
357
+ // Deliberately no presentResult: the generic card fallback keeps this
358
+ // title and reads durable result content without duplicating a large raw
359
+ // result into the host view payload.
360
+ });
361
+ // Resolve the language flavor lazily, at the moment the registry projects the
362
+ // schema (`schemaOf` destructures `description`/`parameters`). The definition
363
+ // is minted once at registration, before a runtime is known; deferring here
364
+ // is the least invasive point that still emits the loaded runtime's language.
365
+ Object.defineProperty(definition, 'description', {
366
+ enumerable: true,
367
+ get: () => resolveFlavor(peekRuntime).description,
368
+ });
369
+ Object.defineProperty(definition, 'parameters', {
370
+ enumerable: true,
371
+ // Recompile through the same spec→schema projection defineTool used, so
372
+ // the emitted schema always matches the validated specification.
373
+ get: () => parameterSchemaSpecToJsonSchema({
374
+ code: { type: 'string', required: true, description: resolveFlavor(peekRuntime).codeDescription },
375
+ description: { type: 'string', required: true, description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION },
376
+ }),
377
+ });
378
+ return definition;
379
+ }
380
+ //# sourceMappingURL=code-mode.js.map