forked from deepseek-ai/deepseek-harness
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode-mode.ts
More file actions
673 lines (643 loc) · 32.1 KB
/
Copy pathcode-mode.ts
File metadata and controls
673 lines (643 loc) · 32.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
/**
* Code Mode `run_code` transport. Programs call the registry's agent-visible
* tools through nested executions scheduled under the native concurrency
* contract; each sub-dispatch is logged for reconstruction, while only the
* outer curated result enters model history.
* @module @deepseek-ai/dsh-tools/src/code-mode
*/
import { CallId, HarnessError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { CodeBindingFunction, CodeRunResult, CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import type { JsonValue } from '@deepseek-ai/dsh-session'
import { defineTool, parameterSchemaSpecToJsonSchema } from './schema.ts'
import { TOOL_RUNTIME_SCHEDULER } from './index.ts'
import type { CodeDispatchLog, ToolDefinition, ToolExecutionResult, ToolRuntime, ToolRunContext } from './index.ts'
import type {} from './types.ts'
/** The model-facing name of the Code Mode tool. */
export const RUN_CODE_NAME = 'run_code'
/** The `tools:sdk` section order: inside the 100–199 tool-guidance band, after per-tool guidance sections. */
export const SDK_SECTION_ORDER = 150
/**
* The language-specific `run_code` schema text: the tool `description` and its
* `code` parameter description, kept together so a language's two model-facing
* strings share one source of truth. Keyed by `CodeRuntime.language`, mirroring
* `SDK_RENDERERS` in {@link ./index.ts}. The emitted flavor MUST match the
* semantics the same language's SDK instructions promise, so the model never
* receives a TypeScript schema beside a Python SDK (or vice versa).
*/
interface RunCodeFlavor {
/** The tool `description` the model sees for this language. */
readonly description: string
/** The `code` parameter's description for this language. */
readonly codeDescription: string
}
/**
* The TypeScript flavor: the fallback for a schema read with no runtime
* mounted ({@link resolveFlavor} owns which readers reach that). A real
* assembly always resolves a runtime first, so the model never sees this
* fallback outside its own language.
*/
const TYPESCRIPT_FLAVOR: RunCodeFlavor = {
description:
'Execute a TypeScript program against the available tools. Takes two required '
+ 'arguments: `code`, the BODY of an async function (erasable syntax only; top-level '
+ '`await` and `return` work), and `description`, a short summary of what the program '
+ 'does. Call tools as `await tools.name(args)` per the declarations in the system '
+ 'prompt. Only what you print or return comes back — curate it.',
codeDescription: 'The program: the body of an async TypeScript function.',
}
/**
* The Python flavor: the body of an async function, top-level `await` and
* `return`, answer via `print` and/or the returned value, matching
* {@link ./py-types.ts}'s SDK instructions.
*/
const PYTHON_FLAVOR: RunCodeFlavor = {
description:
'Execute a Python program against the available tools. Takes two required '
+ 'arguments: `code`, the BODY of an async function (top-level `await` and `return` '
+ 'work), and `description`, a short summary of what the program does. Call tools as '
+ '`await tools.name(args)` per the declarations in the system prompt. Answer '
+ 'with `print(...)` and/or `return <value>` — only that comes back, so curate it.',
codeDescription: 'The program: the body of an async Python function.',
}
/**
* The languages Code Mode ships a presentation for. Both per-language tables —
* {@link RUN_CODE_FLAVORS} here and `SDK_RENDERERS` in {@link ./index.ts} — are
* checked against this union with `satisfies`, so a language added to one and
* not the other fails `typecheck` instead of waiting for a runtime that reports
* it. The tables stay declared `Record<string, …>` because `CodeRuntime.language`
* is an unconstrained `string`: this union pins what the harness ships, while the
* `Object.hasOwn` guards reject what a mounted runtime may report.
*/
export type CodeSdkLanguage = 'typescript' | 'python'
/** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link CodeSdkLanguage}. */
const RUN_CODE_FLAVORS: Record<string, RunCodeFlavor> = {
typescript: TYPESCRIPT_FLAVOR,
python: PYTHON_FLAVOR,
} satisfies Record<CodeSdkLanguage, RunCodeFlavor>
/**
* The `description` parameter's model-facing description: language-independent
* (the UI label contract is the same for every runtime), shared between the
* static spec and the language-aware `parameters` getter so the two emissions
* can never drift.
*/
const RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION
= 'Clear, concise description of what this program does in active voice, '
+ '5-10 words (shown in the UI). Examples: "Count TODO markers across packages"; '
+ '"Read failing test and its fixture"; "Rename config key in every cordis.yml".'
/**
* Resolve the {@link RunCodeFlavor} for the loaded runtime's language, read at
* schema-emission time so the model-visible `run_code` schema always matches
* the SDK section's language. `peekRuntime` returns `undefined` only when no
* runtime is mounted, which reaches this function through definition readers
* and `schemas()` — the doc-catalog harvest is the only shipped one, and none
* of them feeds a model, because `wireSchemas` calls `requireCodeRuntime`
* before projecting — so that path degrades to {@link TYPESCRIPT_FLAVOR}. A
* mounted runtime whose language has no flavor entry fails loud, exactly as
* `requireCodeRuntime` rejects it at assembly. Keeping this table in step with
* `SDK_RENDERERS` is the compiler's job ({@link CodeSdkLanguage}); what this
* guard owns is the runtime-supplied language neither table knows, which never
* yields a wrong-language schema for a real runtime.
*/
function resolveFlavor(peekRuntime: () => CodeRuntime | undefined): RunCodeFlavor {
const runtime = peekRuntime()
if (runtime === undefined) {
// No runtime mounted: reached by definition readers and `schemas()`, of
// which the doc-catalog harvest is the only shipped one. None feeds a
// model — `wireSchemas` calls `requireCodeRuntime` before projecting, so
// the assembly path never arrives here. Degrade to the TS default.
return TYPESCRIPT_FLAVOR
}
// Own-property read: a language like `toString`/`constructor` would otherwise
// resolve an inherited Object.prototype member as a flavor.
const flavor = RUN_CODE_FLAVORS[runtime.language]
if (!Object.hasOwn(RUN_CODE_FLAVORS, runtime.language) || flavor === undefined) {
const known = Object.keys(RUN_CODE_FLAVORS).map(name => JSON.stringify(name)).join(', ')
throw new Error(`dsh-tools: no run_code schema flavor registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`)
}
return flavor
}
/**
* Thrown by `run_code` when the program run itself failed — a program
* exception, a budget expiry, an abort, or substrate death. Extends
* {@link HarnessError} (`code: 'CODE_RUN_FAILED'`); the registry's execution
* pipeline converts it into a structured `isError` result whose text carries
* the failure kind plus the captured logs, so the model can self-correct.
*/
export class CodeRunFailedError extends HarnessError {
constructor(message: string) {
super(message, 'CODE_RUN_FAILED')
this.name = 'CodeRunFailedError'
}
}
/**
* Snapshot one binding call's argument as lossless JSON, then snapshot that
* detached value again so dispatch and logging stay independent without
* reintroducing structured-clone's platform-specific nesting limit.
*/
function jsonNormalizeArgs(value: unknown): { dispatched: unknown; logged: unknown } {
let snapshot: JsonValue | undefined
try {
snapshot = snapshotJsonValue(value) as JsonValue | undefined
} catch (error: unknown) {
throw new Error(`tool arguments must be lossless JSON: ${error instanceof Error ? error.message : String(error)}`)
}
if (snapshot === undefined) {
throw new Error('tool arguments must be lossless JSON (call the tool with an arguments object, e.g. `{}`)')
}
const logged = snapshotJsonValue(snapshot)
/* v8 ignore next -- snapshot is already a detached lossless JSON value. */
if (logged === undefined) {
throw new Error('tool arguments could not be detached for durable logging')
}
return { dispatched: snapshot, logged }
}
/** Two-space JSON presentation, matching the existing shallow `run_code` text contract. */
const JSON_INDENT = ' '
/**
* ECMAScript caps `JSON.stringify`'s `space` string at ten characters. The
* renderer also caps TOTAL indentation there, compacting deeper subtrees, so
* formatted output remains linear in the canonical JSON size.
*/
const MAX_JSON_INDENT_CHARS = 10
/** A pending fragment in the iterative JSON presentation traversal. */
type JsonRenderTask =
| { kind: 'text'; text: string }
| { kind: 'value'; value: JsonValue; depth: number; compact: boolean }
/** Render one non-string JSON root without recursive traversal or unbounded indentation growth. */
function renderJsonValue(value: Exclude<JsonValue, string>): string {
const chunks: string[] = []
const tasks: JsonRenderTask[] = [{ kind: 'value', value, depth: 0, compact: false }]
for (let task = tasks.pop(); task !== undefined; task = tasks.pop()) {
if (task.kind === 'text') {
chunks.push(task.text)
continue
}
const current = task.value
if (current === null || typeof current === 'boolean' || typeof current === 'number') {
chunks.push(String(current))
continue
}
if (typeof current === 'string') {
chunks.push(JSON.stringify(current))
continue
}
const compact = task.compact || (task.depth + 1) * JSON_INDENT.length > MAX_JSON_INDENT_CHARS
const childDepth = task.depth + 1
if (Array.isArray(current)) {
chunks.push('[')
if (current.length === 0) {
chunks.push(']')
continue
}
tasks.push({ kind: 'text', text: compact ? ']' : `\n${JSON_INDENT.repeat(task.depth)}]` })
for (let index = current.length - 1; index >= 0; index--) {
const item = current[index]
/* v8 ignore next -- canonical JsonValue arrays are dense. */
if (item === undefined) throw new Error('cannot render a sparse JSON array')
tasks.push({ kind: 'value', value: item, depth: childDepth, compact })
tasks.push({
kind: 'text',
text: compact
? index === 0 ? '' : ','
: `${index === 0 ? '\n' : ',\n'}${JSON_INDENT.repeat(childDepth)}`,
})
}
continue
}
const keys = Object.keys(current)
chunks.push('{')
if (keys.length === 0) {
chunks.push('}')
continue
}
tasks.push({ kind: 'text', text: compact ? '}' : `\n${JSON_INDENT.repeat(task.depth)}}` })
for (let index = keys.length - 1; index >= 0; index--) {
const key = keys[index]
/* v8 ignore next -- the loop is bounded by the captured key count. */
if (key === undefined) throw new Error('cannot render a missing JSON object key')
const item = current[key]
/* v8 ignore next -- canonical JsonValue records contain no undefined properties. */
if (item === undefined) throw new Error('cannot render an undefined JSON object property')
tasks.push({ kind: 'value', value: item, depth: childDepth, compact })
tasks.push({
kind: 'text',
text: compact
? `${index === 0 ? '' : ','}${JSON.stringify(key)}:`
: `${index === 0 ? '\n' : ',\n'}${JSON_INDENT.repeat(childDepth)}${JSON.stringify(key)}: `,
})
}
}
return chunks.join('')
}
/** Render one present program completion value for the model-facing result text. */
function renderValue(value: JsonValue): string {
return typeof value === 'string' ? value : renderJsonValue(value)
}
/** Canonical value returned by the outer Code Mode transport. */
type RunCodeOutput = { logs: string[]; result?: JsonValue }
/**
* Registry-private capabilities the bridge receives at construction — the
* `requireRuntime` idiom: operations only the owning registry can mint stay
* off its public service API and flow here as closures instead.
*/
export interface RunCodeBridgeOptions {
/** Resolves `ctx.codeRuntime` or throws the loud misconfiguration error (shared with the registry's assembly-time checks). */
requireRuntime: () => CodeRuntime
/**
* Reads `ctx.codeRuntime` without throwing: `undefined` when none is mounted.
* Lets schema emission tell "no runtime" (degrade to TS; the readers that
* reach it are {@link resolveFlavor}'s) apart from "unknown language" (fail
* loud).
*/
peekRuntime: () => CodeRuntime | undefined
/** The run's overlap cap for parallel-classified sub-calls (the registry passes its validated `maxParallelSubCalls`). */
maxParallel: number
/** Runs the contained `tools/code-dispatch-log` waterfall over one settled sub-dispatch (the registry's private invoker). */
shapeDispatchLog: (dispatch: CodeDispatchLog) => Promise<ContentBlock[]>
}
/**
* Build the `run_code` {@link ToolDefinition}: required `code` and
* `description` parameters, executed through the dispatch bridge described
* above. The
* registry reserves it as presentation infrastructure under non-native modes,
* outside the filterable global/scoped capability layers.
* @param registry - the owning registry (sub-calls go through its `execute`,
* bindings cover its registered tools).
* @param options - the registry-private capabilities described above.
* @returns the registry-ready definition.
*/
export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeOptions): ToolDefinition {
const { requireRuntime, peekRuntime, maxParallel, shapeDispatchLog } = options
const definition = defineTool({
name: RUN_CODE_NAME,
// The description and `code` parameter description are placeholders here:
// the language-aware getters installed below replace both, resolving the
// loaded runtime's flavor at schema-emission time so the schema the MODEL
// sees matches the SDK section's language. Argument VALIDATION still keys
// off this static spec (defineTool closes over it), which is language-
// independent (one required string `code`).
description: TYPESCRIPT_FLAVOR.description,
parameters: {
code: { type: 'string', required: true, description: TYPESCRIPT_FLAVOR.codeDescription },
description: {
type: 'string',
required: true,
description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION,
},
},
output: {
schema: {
type: 'object',
additionalProperties: false,
properties: {
logs: { type: 'array', required: true, items: { type: 'string' } },
result: { type: 'json' },
},
},
render: (_args, value) => {
const rendered = value.result === undefined ? '' : renderValue(value.result)
const parts = [value.logs.join('\n'), rendered].filter(part => part.length > 0)
return [{ type: 'text', text: parts.length > 0 ? parts.join('\n') : '(run_code completed with no output)' }]
},
},
async execute(args, exec): Promise<RunCodeOutput> {
if (args.description.trim().length === 0) {
throw new Error('invalid description: expected a non-empty string')
}
const runtime = requireRuntime()
// The run-scoped abort: follows the outer signal in, and fires when the
// run settles for ANY reason, so an in-flight sub-dispatch is aborted
// (its executor kills on this signal) instead of orphaned, and
// queued-unstarted dispatches are abandoned.
const runController = new AbortController()
const onOuterAbort = (): void => { runController.abort(exec.signal.reason) }
exec.signal.addEventListener('abort', onOuterAbort, { once: true })
let dispatches = 0
// The per-run scheduler uses the registry's staged interface and follows
// the same concurrency rules as the native loop. It also follows the
// native loop's SEQUENCING: every ordered stage (the dispatch-start
// append, prepare = pre-execute/guards, finalize/finish = post-execute,
// context deferral, the settle append) runs inside ONE driver lane, so
// ordered policy stages never overlap each other and only the
// around-dispatch/body stage runs concurrently. Starts are strictly
// submission-ordered; results commit in submission order through the
// head-of-line cursor. Consecutive parallel-classified calls overlap up
// to maxParallel; an exclusive call waits for the pool to drain, runs
// alone, and holds its barrier until its COMMIT (post-execute included)
// completes, exactly like a native exclusive group. Classification is
// re-read via executionMode() immediately before each start (a registry
// mutation while queued can flip a call exclusive), matching the native
// scheduler's lazy reclassification.
interface PendingDispatch {
/** Ordered stage: append the start event, await prepare (pre-execute/guards), launch the body into `flight`. */
start(): Promise<void>
classify(): 'parallel' | 'exclusive'
abandon(): void
/** Ordered stage: post-execute + context deferral + settle event, in submission order. */
commit(): Promise<void>
/** The launched around-dispatch/body stage; resolved until start() replaces it. */
flight: Promise<void>
/** True once the dispatch stage parked its outcome; the commit cursor waits on it. */
settled: boolean
/** The classification this entry started under; an exclusive holds its barrier through commit(). */
mode?: 'parallel' | 'exclusive'
}
const pendingQueue: PendingDispatch[] = []
const inFlight = new Set<Promise<void>>()
/** Tracked settle-event side work (log-content listener + append), drained at run settlement. */
const logWork = new Set<Promise<void>>()
const commitQueue: PendingDispatch[] = []
let exclusiveActive = false
let driving = false
let driverRun: Promise<void> = Promise.resolve()
let wake: (() => void) | undefined
const wakeup = (): void => {
const release = wake
wake = undefined
release?.()
}
/**
* The single ordered lane. Each pass commits the head-of-line settled
* dispatch (ordered post-execute), then starts the next queued entry if
* its slot is free (ordered pre-execute), and otherwise sleeps until a
* body settles or a new submission arrives. One run reaching the
* empty-queues/empty-pool state is quiescence.
*/
const drive = (): Promise<void> => {
if (driving) return driverRun
driving = true
driverRun = (async () => {
try {
for (;;) {
// Create the wakeup promise before inspecting state so a settle or submission arriving
// between the checks and the await below cannot be lost.
const signal = new Promise<void>((resolve) => { wake = resolve })
const commitHead = commitQueue[0]
if (commitHead !== undefined && commitHead.settled) {
commitQueue.shift()
await commitHead.commit()
// The barrier covers post-execute: later starts wait for the
// exclusive call's full pipeline, as under the native loop.
if (commitHead.mode === 'exclusive') exclusiveActive = false
continue
}
const head = pendingQueue[0]
if (head !== undefined) {
if (runController.signal.aborted) {
pendingQueue.shift()
head.abandon()
continue
}
// Reclassify at start time (fail-closed on registry changes).
const mode = head.classify()
const capacity = !exclusiveActive
&& (mode === 'exclusive' ? inFlight.size === 0 : inFlight.size < maxParallel)
if (capacity) {
if (mode === 'exclusive') exclusiveActive = true
head.mode = mode
pendingQueue.shift()
// Joined before start() so the commit cursor sees submission
// order; nothing commits it until `settled` flips.
commitQueue.push(head)
await head.start()
const flight: Promise<void> = head.flight.finally(() => {
inFlight.delete(flight)
wakeup()
})
inFlight.add(flight)
continue
}
}
if (pendingQueue.length === 0 && commitQueue.length === 0 && inFlight.size === 0) return
await signal
}
} finally {
driving = false
wake = undefined
}
})()
return driverRun
}
/** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */
const drainDispatches = async (): Promise<void> => {
// The abort already fired: the driver abandons queued-unstarted
// entries, awaits the live pool, and drains the ordered commit lane —
// including a commit already in progress when the program returned.
await drive()
// Every settle event is appended inside the open run_code turn
// (tasks self-remove on settlement).
while (logWork.size > 0) await Promise.allSettled([...logWork])
}
// Read through a call, not a bare property: the abort state genuinely
// changes across awaits, and a direct `.aborted` re-check after one
// would be narrowed away by control flow analysis.
const runOver = (): boolean => runController.signal.aborted
const binding = (name: string): CodeBindingFunction => async (rawArgs: unknown): Promise<JsonValue> => {
if (runOver()) {
throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`)
}
const normalized = jsonNormalizeArgs(rawArgs)
const n = ++dispatches
const subCallId = CallId(`${String(exec.callId)}:code:${n}`)
const input = {
callId: subCallId,
rootCallId: exec.rootCallId,
name,
arguments: normalized.dispatched,
...exec.agent ? { agent: exec.agent } : {},
parent: exec.token,
signal: runController.signal,
}
type DispatchOutcome = { isError: true; message: string } | { isError: false; value: JsonValue }
const scheduler = registry[TOOL_RUNTIME_SCHEDULER]
const outcome = await new Promise<DispatchOutcome>((resolve, reject) => {
// Set by the dispatch stage (or start() for a pre-settled result): what commit() finalizes in submission order.
let parked:
| { kind: 'post-result' | 'final-result'; exec: ToolRunContext; result: ToolExecutionResult }
| undefined
const settle = (result: ToolExecutionResult): void => {
// The program gets its value NOW: the log-content listener (for
// example, a spill backend) must never delay the binding or occupy
// a dispatch slot. The event append is tracked side work; the run's
// settlement drains logWork so every settle event is still appended
// inside the open turn (shapeDispatchLog is contained, so this
// chain cannot reject).
resolve(result.isError
? { isError: true, message: result.error.message }
: { isError: false, value: result.value })
const agent = exec.agent
if (agent === undefined) return
const task: Promise<void> = (async () => {
// The listener may replace the durable copy with a preview and
// locator; the program's value and model-visible result are
// untouched.
const logged = await shapeDispatchLog({
exec, agent, subCallId, name, isError: result.isError,
// The registry deep-froze this projection at result
// finalization; append snapshots the final copy again, so
// the log stays detached.
content: result.content,
})
agent.session.append('tool/code-dispatch', {
rootCallId: exec.rootCallId,
parentCallId: exec.callId,
subCallId,
name,
// The SIBLING parse of the dispatched value: byte-identical JSON,
// but a separate object — a tool mutating its args cannot desync
// this record from what it actually received.
arguments: normalized.logged,
isError: result.isError,
content: logged,
})
})().finally(() => { logWork.delete(task) })
logWork.add(task)
}
pendingQueue.push({
flight: Promise.resolve(),
settled: false,
// Re-read per driver pass against the same agent view the SDK
// declared; fail-closed exclusive when undeclared/invalid.
classify: () => registry.executionMode(input).kind,
abandon: () => {
reject(new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`))
},
async start(): Promise<void> {
exec.agent?.session.append('tool/code-dispatch-start', {
rootCallId: exec.rootCallId,
parentCallId: exec.callId,
subCallId,
name,
arguments: normalized.logged,
})
// Ordered prepare runs INSIDE the driver lane: the next entry's
// pre-execute waits for this resolution, as under the native
// scheduler. Only the launched body below overlaps.
const prepared = await scheduler.prepare(input)
if (prepared.kind === 'dispatch') {
this.flight = scheduler.dispatch(prepared.exec).then((dispatchOutcome) => {
parked = { kind: dispatchOutcome.kind, exec: prepared.exec, result: dispatchOutcome.result }
this.settled = true
})
return
}
parked = { kind: prepared.kind, exec: prepared.exec, result: prepared.result }
this.settled = true
},
async commit(): Promise<void> {
/* v8 ignore next -- commit() runs only after `settled` flipped, which set parked. */
if (parked === undefined) return
const result = parked.kind === 'post-result'
? await scheduler.finalize(parked.exec, parked.result)
: scheduler.finish(parked.exec, parked.result)
for (const context of result.additionalContexts ?? []) {
exec.deferContext(context)
}
// The composite forwards `additionalContexts` above and
// `concludesTurn` here from the nested result. Only a successful
// nested result can carry the terminal marker
// (ToolExecutionFailure types it never), so a policy-converted
// failure cannot stop the turn through a recovering program.
if (result.concludesTurn) exec.concludeTurn()
settle(result)
// Backpressure on pending event-append tasks: each task retains
// a full result while a slow backend stores it, so the pool cap
// bounds their count. Beyond the cap, the
// ordered lane waits, so later sub-calls cannot start and
// pending I/O/memory cannot grow without bound.
while (logWork.size > maxParallel) await Promise.race(logWork)
},
})
wakeup()
void drive()
})
// A budget expiry or outer cancel that occurs while this call was in
// flight already aborted the dispatch; stop the program now rather
// than hand it a result from a run that is over.
if (runOver()) {
throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} result discarded`)
}
// The worker turns a binding rejection into ToolCallError and adds
// only the binding name. Native content and internal error metadata
// stay outside the program-facing failure contract.
if (outcome.isError) throw new Error(outcome.message)
return outcome.value
}
// Null-prototype + defineProperty, mirroring the worker-side namespace
// build: a registered tool named `__proto__` must become an ordinary
// own key (a plain-object assignment would hit the prototype setter,
// silently dropping the binding), and the runtime host resolves
// binding names as own properties only.
const functions: Record<string, CodeBindingFunction> = Object.create(null) as Record<string, CodeBindingFunction>
// Enumerate the CALLING AGENT's visible set (scoped tools join,
// restricted globals vanish) — the same view the SDK section declared,
// so a program can bind exactly what its prompt promised; sub-dispatch
// re-resolves per call through the same view (exec.agent threads down).
for (const schema of registry.schemas(exec.agent)) {
if (schema.name === RUN_CODE_NAME) continue
Object.defineProperty(functions, schema.name, { enumerable: true, value: binding(schema.name) })
}
try {
let result: CodeRunResult
try {
result = await runtime.run({
program: args.code,
bindings: [{
global: 'tools',
functions,
errorClass: { name: 'ToolCallError', memberNameProperty: 'toolName' },
}],
signal: runController.signal,
})
} finally {
// Abort sub-dispatches and drain every in-flight dispatch before
// closing the turn (queued-unstarted ones are abandoned unlogged).
// Binding failures remain observable through their individual promises.
runController.abort('run_code settled')
await drainDispatches()
}
if (result.error) {
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join('\n')}` : ''
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`)
}
return {
logs: result.logs,
...result.value !== undefined ? { result: result.value } : {},
}
} finally {
exec.signal.removeEventListener('abort', onOuterAbort)
}
},
// The model-authored description is the call's always-visible UI label
// (the bash `description` precedent); the program itself rides rawInput.
presentCall: args => ({
card: 'generic',
title: args.description,
kind: 'execute',
rawInput: args.code,
}),
// Deliberately no presentResult: the generic card fallback keeps this
// title and reads durable result content without duplicating a large raw
// result into the host view payload.
})
// Resolve the language flavor lazily, at the moment the registry projects the
// schema (`schemaOf` destructures `description`/`parameters`). The definition
// is minted once at registration, before a runtime is known; deferring here
// is the least invasive point that still emits the loaded runtime's language.
Object.defineProperty(definition, 'description', {
enumerable: true,
get: () => resolveFlavor(peekRuntime).description,
})
Object.defineProperty(definition, 'parameters', {
enumerable: true,
// Recompile through the same spec→schema projection defineTool used, so
// the emitted schema always matches the validated specification.
get: () => parameterSchemaSpecToJsonSchema({
code: { type: 'string', required: true, description: resolveFlavor(peekRuntime).codeDescription },
description: { type: 'string', required: true, description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION },
}) as unknown as Record<string, unknown>,
})
return definition
}