1
/**2
* PTC mode `run_code` transport. Programs call the registry's agent-visible3
* tools through nested executions scheduled under the native concurrency4
* contract; each sub-dispatch is logged for reconstruction, while only the5
* outer curated result enters model history.6
* @module @deepseek-ai/dsh-tools/src/ptc7
*/9
import { brandString } from '@deepseek-ai/dsh-brand'10
import { createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm'11
declare module '@deepseek-ai/dsh-llm' {12
interface MessageSourceMap {13
/** Images deferred from a successful PTC subcall's final result. */14
'ptc-mode': { kind: 'ptc-mode' }15
}16
}18
import type { ContentBlock, ToolCallId, ToolSchema } from '@deepseek-ai/dsh-llm'19
import type { PtcBindingFunction, PtcRunResult, PtcRunSandbox, PtcRuntime } from '@deepseek-ai/dsh-ptc-runtime'20
import { approveEscalation, ESCALATION_TARGETS, validateEscalationArgs } from '@deepseek-ai/dsh-sandbox'21
import type { SandboxExecutionPolicy } from '@deepseek-ai/dsh-sandbox'22
import type { ApprovalService } from '@deepseek-ai/dsh-user-approval'23
import { deepFreeze, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'24
import { defineTool, parameterSchemaSpecToJsonSchema } from './schema.ts'25
import { TOOL_RUNTIME_SCHEDULER } from './index.ts'26
import type { PtcDispatchLog, ToolDefinition, ToolExecutionResult, ToolRuntime, ToolRunContext } from './index.ts'27
import type {} from './types.ts'29
/** The model-facing name of the PTC mode tool. */30
export const RUN_CODE_NAME = 'run_code'32
/**33
* The language-specific `run_code` schema text: the tool `description` and its34
* `code` parameter description, kept together so a language's two model-facing35
* strings share one source of truth. Keyed by `PtcRuntime.language`, mirroring36
* `SDK_RENDERERS` in {@link ./index.ts}. The emitted flavor MUST match the37
* semantics the same language's SDK instructions promise, so the model never38
* receives a TypeScript schema beside a Python SDK (or vice versa).39
*/40
interface RunCodeFlavor {41
/** The tool `description` the model sees for this language. */42
readonly description: string43
/** The `code` parameter's description for this language. */44
readonly codeDescription: string45
}47
/**48
* The TypeScript flavor: the fallback for a schema read with no runtime49
* mounted ({@link resolveFlavor} owns which readers reach that). A real50
* assembly always resolves a runtime first, so the model never sees this51
* fallback outside its own language.52
*/53
const TYPESCRIPT_FLAVOR: RunCodeFlavor = {54
description:55
'Execute a TypeScript program against the available tools. Takes two required '56
+ 'arguments: `description`, a short summary of what the program does, and `code`, '57
+ 'the BODY of an async function (erasable syntax only; top-level `await` and '58
+ '`return` work). Call tools as `await tools.name(args)` per the declarations in the system '59
+ 'prompt. Only what you print or return is program output — curate it. Image-bearing '60
+ 'subtool results are attached after the run.',61
codeDescription: 'The program: the body of an async TypeScript function.',62
}64
/**65
* The Python flavor: the body of an async function, top-level `await` and66
* `return`, answer via `print` and/or the returned value, matching67
* {@link ./py-types.ts}'s SDK instructions.68
*/69
const PYTHON_FLAVOR: RunCodeFlavor = {70
description:71
'Execute a Python program against the available tools. Takes two required '72
+ 'arguments: `description`, a short summary of what the program does, and `code`, '73
+ 'the BODY of an async function (top-level `await` and `return` work). Call tools as '74
+ '`await tools.name(args)` per the declarations in the system prompt. Use '75
+ '`print(...)` and/or `return <value>` for program output — curate it. Image-bearing '76
+ 'subtool results are attached after the run.',77
codeDescription: 'The program: the body of an async Python function.',78
}80
/**81
* The languages PTC mode ships a presentation for. Both per-language tables —82
* {@link RUN_CODE_FLAVORS} here and `SDK_RENDERERS` in {@link ./index.ts} — are83
* checked against this union with `satisfies`, so a language added to one and84
* not the other fails `typecheck` instead of waiting for a runtime that reports85
* it. The tables stay declared `Record<string, …>` because `PtcRuntime.language`86
* is an unconstrained `string`: this union pins what the harness ships, while the87
* `Object.hasOwn` guards reject what a mounted runtime may report.88
*/89
export type PtcSdkLanguage = 'typescript' | 'python'91
/** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link PtcSdkLanguage}. */92
const RUN_CODE_FLAVORS: Record<string, RunCodeFlavor> = {93
typescript: TYPESCRIPT_FLAVOR,94
python: PYTHON_FLAVOR,95
} satisfies Record<PtcSdkLanguage, RunCodeFlavor>97
/**98
* The `description` parameter's model-facing description: language-independent99
* (the UI label contract is the same for every runtime), shared between the100
* static spec and the language-aware `parameters` getter so the two emissions101
* can never drift.102
*/103
const RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION104
= 'Clear, concise description of what this program does in active voice, '105
+ '5-10 words (shown in the UI). Provide `description` before `code` in the arguments. '106
+ 'Examples: "Count TODO markers across packages"; '107
+ '"Read failing test and its fixture"; "Rename config key in every cordis.yml".'109
const RUN_CODE_CONTROLS = {110
timeoutMs: { type: 'number', description: 'Positive elapsed-time budget in milliseconds, capped by the deployment maximum.' },111
sandbox_permissions: { type: 'string', enum: [...ESCALATION_TARGETS], description: 'Wider sandbox mode for this complete program execution; requires justification and approval.' },112
justification: { type: 'string', description: 'Reason this complete program needs wider access, shown to the user for approval. Use the language of the user’s current request.' },113
} as const115
function controlParameters(runtime: PtcRuntime | undefined) {116
// Catalog readers have no mounted runtime; real model assembly requires one.117
if (runtime === undefined) return RUN_CODE_CONTROLS118
return {119
...runtime.timeout === undefined ? {} : {120
timeoutMs: { ...RUN_CODE_CONTROLS.timeoutMs,121
description: `Positive elapsed-time budget in milliseconds, including nested tool and approval waits. Default ${runtime.timeout.defaultMs}; capped at ${runtime.timeout.maxMs}. Zero does not disable the deadline.` },122
},123
...runtime.sandboxMode === undefined ? {} : {124
sandbox_permissions: RUN_CODE_CONTROLS.sandbox_permissions,125
justification: RUN_CODE_CONTROLS.justification,126
},127
}128
}130
function escalationGuidance(runtime: PtcRuntime | undefined): string {131
return runtime?.sandboxMode === undefined ? ''132
: ' A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.'133
}135
/**136
* Resolve the {@link RunCodeFlavor} for the loaded runtime's language, read at137
* schema-emission time so the model-visible `run_code` schema always matches138
* the SDK section's language. `peekRuntime` returns `undefined` only when no139
* runtime is mounted, which reaches this function through definition readers140
* and `schemas()` — the doc-catalog harvest is the only shipped one, and none141
* of them feeds a model, because `wireSchemas` calls `requirePtcRuntime`142
* before projecting — so that path degrades to {@link TYPESCRIPT_FLAVOR}. A143
* mounted runtime whose language has no flavor entry fails loud, exactly as144
* `requirePtcRuntime` rejects it at assembly. Keeping this table in step with145
* `SDK_RENDERERS` is the compiler's job ({@link PtcSdkLanguage}); what this146
* guard owns is the runtime-supplied language neither table knows, which never147
* yields a wrong-language schema for a real runtime.148
*/149
function resolveFlavor(peekRuntime: () => PtcRuntime | undefined): RunCodeFlavor {150
const runtime = peekRuntime()151
if (runtime === undefined) {152
// No runtime mounted: reached by definition readers and `schemas()`, of153
// which the doc-catalog harvest is the only shipped one. None feeds a154
// model — `wireSchemas` calls `requirePtcRuntime` before projecting, so155
// the assembly path never arrives here. Degrade to the TS default.156
return TYPESCRIPT_FLAVOR157
}158
// Own-property read: a language like `toString`/`constructor` would otherwise159
// resolve an inherited Object.prototype member as a flavor.160
const flavor = RUN_CODE_FLAVORS[runtime.language]161
if (!Object.hasOwn(RUN_CODE_FLAVORS, runtime.language) || flavor === undefined) {162
const known = Object.keys(RUN_CODE_FLAVORS).map(name => JSON.stringify(name)).join(', ')163
throw new Error(`dsh-tools: no run_code schema flavor registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`)164
}165
return flavor166
}168
/**169
* Thrown by `run_code` when the program run itself failed — a program170
* exception, a budget expiry, an abort, or substrate death. Extends171
* {@link HarnessError} (`code: 'CODE_RUN_FAILED'`); the registry's execution172
* pipeline converts it into a structured `isError` result whose text carries173
* the failure kind plus the captured logs, so the model can self-correct.174
*/175
export class CodeRunFailedError extends HarnessError {176
constructor(message: string) {177
super(message, 'CODE_RUN_FAILED')178
this.name = 'CodeRunFailedError'179
}180
}182
/**183
* Snapshot one binding call's argument as lossless JSON, then snapshot that184
* detached value again so dispatch and logging stay independent without185
* reintroducing structured-clone's platform-specific nesting limit.186
*/187
function jsonNormalizeArgs(value: unknown): { dispatched: unknown; logged: unknown } {188
let snapshot: JsonValue | undefined189
try {190
snapshot = snapshotJsonValue(value) as JsonValue | undefined191
} catch (error: unknown) {192
throw new Error(`tool arguments must be lossless JSON: ${error instanceof Error ? error.message : String(error)}`)193
}194
if (snapshot === undefined) {195
throw new Error('tool arguments must be lossless JSON (call the tool with an arguments object, e.g. `{}`)')196
}197
const logged = snapshotJsonValue(snapshot)198
/* v8 ignore next -- snapshot is already a detached lossless JSON value. */199
if (logged === undefined) {200
throw new Error('tool arguments could not be detached for durable logging')201
}202
return { dispatched: snapshot, logged }203
}205
/** Two-space JSON presentation, matching the existing shallow `run_code` text contract. */206
const JSON_INDENT = ' '208
/**209
* ECMAScript caps `JSON.stringify`'s `space` string at ten characters. The210
* renderer also caps TOTAL indentation there, compacting deeper subtrees, so211
* formatted output remains linear in the canonical JSON size.212
*/213
const MAX_JSON_INDENT_CHARS = 10215
/** A pending fragment in the iterative JSON presentation traversal. */216
type JsonRenderTask =217
| { kind: 'text'; text: string }218
| { kind: 'value'; value: JsonValue; depth: number; compact: boolean }220
/** Render one non-string JSON root without recursive traversal or unbounded indentation growth. */221
function renderJsonValue(value: Exclude<JsonValue, string>): string {222
const chunks: string[] = []223
const tasks: JsonRenderTask[] = [{ kind: 'value', value, depth: 0, compact: false }]224
for (let task = tasks.pop(); task !== undefined; task = tasks.pop()) {225
if (task.kind === 'text') {226
chunks.push(task.text)227
continue228
}230
const current = task.value231
if (current === null || typeof current === 'boolean' || typeof current === 'number') {232
chunks.push(String(current))233
continue234
}235
if (typeof current === 'string') {236
chunks.push(JSON.stringify(current))237
continue238
}240
const compact = task.compact || (task.depth + 1) * JSON_INDENT.length > MAX_JSON_INDENT_CHARS241
const childDepth = task.depth + 1242
if (Array.isArray(current)) {243
chunks.push('[')244
if (current.length === 0) {245
chunks.push(']')246
continue247
}248
tasks.push({ kind: 'text', text: compact ? ']' : `\n${JSON_INDENT.repeat(task.depth)}]` })249
for (let index = current.length - 1; index >= 0; index--) {250
const item = current[index]251
/* v8 ignore next -- canonical JsonValue arrays are dense. */252
if (item === undefined) throw new Error('cannot render a sparse JSON array')253
tasks.push({ kind: 'value', value: item, depth: childDepth, compact })254
tasks.push({255
kind: 'text',256
text: compact257
? index === 0 ? '' : ','258
: `${index === 0 ? '\n' : ',\n'}${JSON_INDENT.repeat(childDepth)}`,259
})260
}261
continue262
}264
const keys = Object.keys(current)265
chunks.push('{')266
if (keys.length === 0) {267
chunks.push('}')268
continue269
}270
tasks.push({ kind: 'text', text: compact ? '}' : `\n${JSON_INDENT.repeat(task.depth)}}` })271
for (let index = keys.length - 1; index >= 0; index--) {272
const key = keys[index]273
/* v8 ignore next -- the loop is bounded by the captured key count. */274
if (key === undefined) throw new Error('cannot render a missing JSON object key')275
const item = current[key]276
/* v8 ignore next -- canonical JsonValue records contain no undefined properties. */277
if (item === undefined) throw new Error('cannot render an undefined JSON object property')278
tasks.push({ kind: 'value', value: item, depth: childDepth, compact })279
tasks.push({280
kind: 'text',281
text: compact282
? `${index === 0 ? '' : ','}${JSON.stringify(key)}:`283
: `${index === 0 ? '\n' : ',\n'}${JSON_INDENT.repeat(childDepth)}${JSON.stringify(key)}: `,284
})285
}286
}287
return chunks.join('')288
}290
/** Render one present program completion value for the model-facing result text. */291
function renderValue(value: JsonValue): string {292
return typeof value === 'string' ? value : renderJsonValue(value)293
}295
/** Canonical value returned by the outer PTC mode transport. */296
type RunCodeOutput = { logs: string[]; result?: JsonValue; sandbox?: PtcRunSandbox }298
/**299
* Registry-private capabilities the bridge receives at construction — the300
* `requireRuntime` idiom: operations only the owning registry can mint stay301
* off its public service API and flow here as closures instead.302
*/303
export interface RunCodeBridgeOptions {304
/** Reads the approval channel when a program requests a wider sandbox mode. */305
peekApprover: () => ApprovalService | undefined306
/** Resolves standing Session authority only for a runtime that enforces file policy. */307
resolveSandboxPolicy: (exec: ToolRunContext) => SandboxExecutionPolicy308
/** Resolves `ctx.ptcRuntime` or throws the loud misconfiguration error (shared with the registry's assembly-time checks). */309
requireRuntime: () => PtcRuntime310
/**311
* Reads `ctx.ptcRuntime` without throwing: `undefined` when none is mounted.312
* Lets schema emission tell "no runtime" (degrade to TS; the readers that313
* reach it are {@link resolveFlavor}'s) apart from "unknown language" (fail314
* loud).315
*/316
peekRuntime: () => PtcRuntime | undefined317
/** The run's overlap cap for parallel-classified sub-calls (the registry passes its validated `maxParallelSubCalls`). */318
maxParallel: number319
/** Runs the contained `tools/ptc-dispatch-log` waterfall over one settled sub-dispatch (the registry's private invoker). */320
shapeDispatchLog: (dispatch: PtcDispatchLog) => Promise<ContentBlock[]>321
}323
/**324
* Build the `run_code` {@link ToolDefinition}: required `description` and325
* `code` parameters, executed through the dispatch bridge described326
* above. The327
* registry reserves it as presentation infrastructure under non-native modes,328
* outside the filterable global/scoped capability layers.329
* @param registry - the owning registry (sub-calls go through its `execute`,330
* bindings cover its registered tools).331
* @param options - the registry-private capabilities described above.332
* @returns the registry-ready definition.333
*/334
export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeOptions): ToolDefinition {335
const { requireRuntime, peekRuntime, maxParallel, shapeDispatchLog } = options336
const definition = defineTool({337
name: RUN_CODE_NAME,338
// The description and `code` parameter description are placeholders here:339
// the language-aware getters installed below replace both, resolving the340
// loaded runtime's flavor at schema-emission time so the schema the MODEL341
// sees matches the SDK section's language. Argument VALIDATION still keys342
// off this static spec (defineTool closes over it), which is language-343
// independent (one required string `code`).344
description: TYPESCRIPT_FLAVOR.description,345
parameters: {346
description: {347
type: 'string',348
required: true,349
description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION,350
},351
code: { type: 'string', required: true, description: TYPESCRIPT_FLAVOR.codeDescription },352
...RUN_CODE_CONTROLS,353
},354
output: {355
schema: {356
type: 'object',357
additionalProperties: false,358
properties: {359
logs: { type: 'array', required: true, items: { type: 'string' } },360
result: { type: 'json' },361
sandbox: {362
type: 'object',363
additionalProperties: false,364
properties: {365
mode: { type: 'string', required: true, enum: ['read-only', 'workspace-write', 'danger-full-access'] },366
denied: { type: 'boolean', required: true },367
enforcement: { type: 'string', enum: ['full', 'partial'] },368
},369
},370
},371
},372
render: (_args, value) => {373
const rendered = value.result === undefined ? '' : renderValue(value.result)374
const parts = [value.logs.join('\n'), rendered].filter(part => part.length > 0)375
if (value.sandbox?.enforcement === 'partial') parts.push('File sandbox enforcement is partial on this host.')376
if (value.sandbox?.denied) parts.push(`The ${value.sandbox.mode} file sandbox denied an operation.${escalationGuidance(peekRuntime())}`)377
return [{ type: 'text', text: parts.length > 0 ? parts.join('\n') : '(run_code completed with no output)' }]378
},379
},380
async execute(args, exec): Promise<RunCodeOutput> {381
if (args.description.trim().length === 0) {382
throw new Error('invalid description: expected a non-empty string')383
}384
const runtime = requireRuntime()385
validateEscalationArgs(args.sandbox_permissions, args.justification)386
if (args.timeoutMs !== undefined && runtime.timeout === undefined) {387
throw new Error('timeoutMs is not available for this PTC runtime')388
}389
if (args.timeoutMs !== undefined && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) {390
throw new Error('invalid timeoutMs: expected a positive finite number')391
}392
const standingPolicy = runtime.sandboxMode === undefined ? undefined : options.resolveSandboxPolicy(exec)393
let policy = standingPolicy394
if (args.sandbox_permissions !== undefined && args.justification !== undefined) {395
if (standingPolicy === undefined) throw new Error('sandbox_permissions is not available for this PTC runtime')396
const approvedMode = await approveEscalation({397
requestedMode: args.sandbox_permissions,398
justification: args.justification,399
effectiveMode: standingPolicy.mode,400
subject: 'program',401
}, {402
approver: options.peekApprover(), agent: exec.agent, callId: exec.callId,403
toolName: RUN_CODE_NAME, signal: exec.signal,404
})405
policy = { ...standingPolicy, mode: approvedMode }406
}407
exec.signal.throwIfAborted()409
// The run-scoped abort: follows the outer signal in, and fires when the410
// run settles for ANY reason, so an in-flight sub-dispatch is aborted411
// (its executor kills on this signal) instead of orphaned, and412
// queued-unstarted dispatches are abandoned.413
const runController = new AbortController()414
const onOuterAbort = (): void => { runController.abort(exec.signal.reason) }415
exec.signal.addEventListener('abort', onOuterAbort, { once: true })417
let dispatches = 0418
// The per-run scheduler uses the registry's staged interface and follows419
// the same concurrency rules as the native loop. It also follows the420
// native loop's SEQUENCING: every ordered stage (the dispatch-start421
// append, prepare = pre-execute/guards, finalize/finish = post-execute,422
// context deferral, the settle append) runs inside ONE driver lane, so423
// ordered policy stages never overlap each other and only the424
// around-dispatch/body stage runs concurrently. Starts are strictly425
// submission-ordered; results commit in submission order through the426
// head-of-line cursor. Consecutive parallel-classified calls overlap up427
// to maxParallel; an exclusive call waits for the pool to drain, runs428
// alone, and holds its barrier until its COMMIT (post-execute included)429
// completes, exactly like a native exclusive group. Classification is430
// re-read via executionMode() immediately before each start (a registry431
// mutation while queued can flip a call exclusive), matching the native432
// scheduler's lazy reclassification.433
interface PendingDispatch {434
/** Ordered stage: append the start event, await prepare (pre-execute/guards), launch the body into `flight`. */435
start(): Promise<void>436
classify(): 'parallel' | 'exclusive'437
abandon(): void438
/** Ordered stage: post-execute + context deferral + settle event, in submission order. */439
commit(): Promise<void>440
/** The launched around-dispatch/body stage; resolved until start() replaces it. */441
flight: Promise<void>442
/** True once the dispatch stage parked its outcome; the commit cursor waits on it. */443
settled: boolean444
/** The classification this entry started under; an exclusive holds its barrier through commit(). */445
mode?: 'parallel' | 'exclusive'446
}447
const pendingQueue: PendingDispatch[] = []448
const inFlight = new Set<Promise<void>>()449
/** Tracked settle-event side work (log-content listener + append), drained at run settlement. */450
const logWork = new Set<Promise<void>>()451
const commitQueue: PendingDispatch[] = []452
let exclusiveActive = false453
let driving = false454
let driverRun: Promise<void> = Promise.resolve()455
let wake: (() => void) | undefined456
const wakeup = (): void => {457
const release = wake458
wake = undefined459
release?.()460
}461
/**462
* The single ordered lane. Each pass commits the head-of-line settled463
* dispatch (ordered post-execute), then starts the next queued entry if464
* its slot is free (ordered pre-execute), and otherwise sleeps until a465
* body settles or a new submission arrives. One run reaching the466
* empty-queues/empty-pool state is quiescence.467
*/468
const drive = (): Promise<void> => {469
if (driving) return driverRun470
driving = true471
driverRun = (async () => {472
try {473
for (;;) {474
// Create the wakeup promise before inspecting state so a settle or submission arriving475
// between the checks and the await below cannot be lost.476
const signal = new Promise<void>((resolve) => { wake = resolve })477
const commitHead = commitQueue[0]478
if (commitHead !== undefined && commitHead.settled) {479
commitQueue.shift()480
await commitHead.commit()481
// The barrier covers post-execute: later starts wait for the482
// exclusive call's full pipeline, as under the native loop.483
if (commitHead.mode === 'exclusive') exclusiveActive = false484
continue485
}486
const head = pendingQueue[0]487
if (head !== undefined) {488
if (runController.signal.aborted) {489
pendingQueue.shift()490
head.abandon()491
continue492
}493
// Reclassify at start time (fail-closed on registry changes).494
const mode = head.classify()495
const capacity = !exclusiveActive496
&& (mode === 'exclusive' ? inFlight.size === 0 : inFlight.size < maxParallel)497
if (capacity) {498
if (mode === 'exclusive') exclusiveActive = true499
head.mode = mode500
pendingQueue.shift()501
// Joined before start() so the commit cursor sees submission502
// order; nothing commits it until `settled` flips.503
commitQueue.push(head)504
await head.start()505
const flight: Promise<void> = head.flight.finally(() => {506
inFlight.delete(flight)507
wakeup()508
})509
inFlight.add(flight)510
continue511
}512
}513
if (pendingQueue.length === 0 && commitQueue.length === 0 && inFlight.size === 0) return514
await signal515
}516
} finally {517
driving = false518
wake = undefined519
}520
})()521
return driverRun522
}523
/** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */524
const drainDispatches = async (): Promise<void> => {525
// The abort already fired: the driver abandons queued-unstarted526
// entries, awaits the live pool, and drains the ordered commit lane —527
// including a commit already in progress when the program returned.528
await drive()529
// Every settle event is appended inside the open run_code turn530
// (tasks self-remove on settlement).531
while (logWork.size > 0) await Promise.allSettled([...logWork])532
}534
// Read through a call, not a bare property: the abort state genuinely535
// changes across awaits, and a direct `.aborted` re-check after one536
// would be narrowed away by control flow analysis.537
const runOver = (): boolean => runController.signal.aborted539
const binding = (schema: ToolSchema): PtcBindingFunction => async (rawArgs: unknown): Promise<JsonValue> => {540
const { name } = schema541
if (runOver()) {542
throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`)543
}544
const normalized = jsonNormalizeArgs(rawArgs)545
const n = ++dispatches546
const subCallId = brandString<ToolCallId>(`${String(exec.callId)}:ptc:${n}`)547
const input = {548
callId: subCallId,549
rootCallId: exec.rootCallId,550
name,551
schema,552
arguments: normalized.dispatched,553
...exec.agent ? { agent: exec.agent } : {},554
parent: exec.token,555
signal: runController.signal,556
}557
type DispatchOutcome = { isError: true; message: string } | { isError: false; value: JsonValue }558
const scheduler = registry[TOOL_RUNTIME_SCHEDULER]559
const outcome = await new Promise<DispatchOutcome>((resolve, reject) => {560
// Set by the dispatch stage (or start() for a pre-settled result): what commit() finalizes in submission order.561
let parked:562
| { kind: 'post-result' | 'final-result'; exec: ToolRunContext; result: ToolExecutionResult }563
| undefined564
const settle = (result: ToolExecutionResult): void => {565
// The program gets its value NOW: the log-content listener (for566
// example, a spill backend) must never delay the binding or occupy567
// a dispatch slot. The event append is tracked side work; the run's568
// settlement drains logWork so every settle event is still appended569
// inside the open turn (shapeDispatchLog is contained, so this570
// chain cannot reject).571
resolve(result.isError572
? { isError: true, message: result.error.message }573
: { isError: false, value: result.value })574
const agent = exec.agent575
if (agent === undefined) return576
const task: Promise<void> = (async () => {577
// The listener may replace the durable copy with a preview and578
// locator; the program's value and model-visible result are579
// untouched.580
const logged = await shapeDispatchLog({581
exec, agent, subCallId, name, isError: result.isError,582
// The registry deep-froze this projection at result583
// finalization; append snapshots the final copy again, so584
// the log stays detached.585
content: result.content,586
})587
agent.session.append('tool/ptc-dispatch', {588
rootCallId: exec.rootCallId,589
parentCallId: exec.callId,590
subCallId,591
name,592
// The SIBLING parse of the dispatched value: byte-identical JSON,593
// but a separate object — a tool mutating its args cannot desync594
// this record from what it actually received.595
arguments: normalized.logged,596
isError: result.isError,597
...result.error?.info === undefined ? {} : { error: result.error.info },598
content: logged,599
})600
})().finally(() => { logWork.delete(task) })601
logWork.add(task)602
}603
pendingQueue.push({604
flight: Promise.resolve(),605
settled: false,606
// Re-read per driver pass against the same agent view the SDK607
// declared; fail-closed exclusive when undeclared/invalid.608
classify: () => registry.executionMode(input).kind,609
abandon: () => {610
reject(new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`))611
},612
async start(): Promise<void> {613
exec.agent?.session.append('tool/ptc-dispatch-start', {614
rootCallId: exec.rootCallId,615
parentCallId: exec.callId,616
subCallId,617
name,618
arguments: normalized.logged,619
})620
// Ordered prepare runs INSIDE the driver lane: the next entry's621
// pre-execute waits for this resolution, as under the native622
// scheduler. Only the launched body below overlaps.623
const prepared = await scheduler.prepare(input)624
if (prepared.kind === 'dispatch') {625
this.flight = scheduler.dispatch(prepared.exec).then((dispatchOutcome) => {626
parked = { kind: dispatchOutcome.kind, exec: prepared.exec, result: dispatchOutcome.result }627
this.settled = true628
})629
return630
}631
parked = { kind: prepared.kind, exec: prepared.exec, result: prepared.result }632
this.settled = true633
},634
async commit(): Promise<void> {635
/* v8 ignore next -- commit() runs only after `settled` flipped, which set parked. */636
if (parked === undefined) return637
const result = parked.kind === 'post-result'638
? await scheduler.finalize(parked.exec, parked.result)639
: scheduler.finish(parked.exec, parked.result)640
if (!result.isError && result.content.some(block => block.type === 'image')) {641
exec.deferContext(createUserMessage({642
content: result.content,643
source: { kind: 'ptc-mode' },644
}))645
}646
for (const context of result.additionalContexts ?? []) {647
exec.deferContext(context)648
}649
// The composite forwards `additionalContexts` above and650
// `concludesTurn` here from the nested result. Only a successful651
// nested result can carry the terminal marker652
// (ToolExecutionFailure types it never), so a policy-converted653
// failure cannot stop the turn through a recovering program.654
if (result.concludesTurn) exec.concludeTurn()655
settle(result)656
// Backpressure on pending event-append tasks: each task retains657
// a full result while a slow backend stores it, so the pool cap658
// bounds their count. Beyond the cap, the659
// ordered lane waits, so later sub-calls cannot start and660
// pending I/O/memory cannot grow without bound.661
while (logWork.size > maxParallel) await Promise.race(logWork)662
},663
})664
wakeup()665
void drive()666
})667
// A budget expiry or outer cancel that occurs while this call was in668
// flight already aborted the dispatch; stop the program now rather669
// than hand it a result from a run that is over.670
if (runOver()) {671
throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} result discarded`)672
}673
// The worker turns a binding rejection into ToolCallError and adds674
// only the binding name. Native content and internal error metadata675
// stay outside the program-facing failure contract.676
if (outcome.isError) throw new Error(outcome.message)677
return outcome.value678
}680
// Null-prototype + defineProperty, mirroring the worker-side namespace681
// build: a registered tool named `__proto__` must become an ordinary682
// own key (a plain-object assignment would hit the prototype setter,683
// silently dropping the binding), and the runtime host resolves684
// binding names as own properties only.685
const functions: Record<string, PtcBindingFunction> = Object.create(null) as Record<string, PtcBindingFunction>686
// Enumerate the CALLING AGENT's visible set (scoped tools join,687
// restricted globals vanish) — the same view the SDK section declared,688
// so a program can bind exactly what its prompt promised; sub-dispatch689
// re-resolves per call through the same view (exec.agent threads down).690
for (const schema of registry.schemas(exec.agent)) {691
if (schema.name === RUN_CODE_NAME) continue692
Object.defineProperty(functions, schema.name, { enumerable: true, value: binding(deepFreeze(schema)) })693
}695
try {696
let result: PtcRunResult697
try {698
result = await runtime.run(runtime.resolve({699
program: args.code,700
bindings: [{701
global: 'tools',702
functions,703
errorClass: { name: 'ToolCallError', memberNameProperty: 'toolName' },704
}],705
signal: runController.signal,706
...exec.agent?.session.header.cwd !== undefined ? { cwd: exec.agent.session.header.cwd } : {},707
...policy !== undefined ? { sandboxPolicy: policy } : {},708
...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {},709
}))710
} finally {711
// Abort sub-dispatches and drain every in-flight dispatch before712
// closing the turn (queued-unstarted ones are abandoned unlogged).713
// Binding failures remain observable through their individual promises.714
runController.abort('run_code settled')715
await drainDispatches()716
}718
if (result.error) {719
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join('\n')}` : ''720
const sandboxText = result.sandbox === undefined ? ''721
: `\nFile sandbox: ${result.sandbox.mode}${result.sandbox.enforcement === undefined ? '' : `; enforcement: ${result.sandbox.enforcement}`}${result.sandbox.denied ? '; operation denied' : ''}.`722
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ''}`)723
}724
return {725
logs: result.logs,726
...result.sandbox === undefined ? {} : { sandbox: result.sandbox },727
...result.value !== undefined ? { result: result.value } : {},728
}729
} finally {730
exec.signal.removeEventListener('abort', onOuterAbort)731
}732
},733
// The model-authored description is the call's always-visible UI label734
// (the bash `description` precedent); the program itself rides rawInput.735
presentCall: args => ({736
card: 'generic',737
title: args.description,738
kind: 'execute',739
rawInput: args.code,740
}),741
// Deliberately no presentResult: the generic card fallback keeps this742
// title and reads durable result content without duplicating a large raw743
// result into the host view payload.744
})745
// Resolve the language flavor lazily, at the moment the registry projects the746
// schema (`schemaOf` destructures `description`/`parameters`). The definition747
// is minted once at registration, before a runtime is known; deferring here748
// is the least invasive point that still emits the loaded runtime's language.749
Object.defineProperty(definition, 'description', {750
enumerable: true,751
get: () => {752
const runtime = peekRuntime()753
const instructions = runtime?.executionInstructions754
return resolveFlavor(peekRuntime).description755
+ (instructions ? ` ${instructions}` : '')756
+ (runtime === undefined ? '' : " The working directory is the Session's current directory.")757
+ escalationGuidance(runtime)758
},759
})760
Object.defineProperty(definition, 'parameters', {761
enumerable: true,762
// Recompile through the same spec→schema projection defineTool used, so763
// the emitted schema always matches the validated specification.764
get: () => parameterSchemaSpecToJsonSchema({765
description: { type: 'string', required: true, description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION },766
code: { type: 'string', required: true, description: resolveFlavor(peekRuntime).codeDescription },767
...controlParameters(peekRuntime()),768
}),769
})770
return definition771
}