The dsh-tools half of the Code Mode RFC (its fourth, final change): the registry gains its first config — mode: native | code | both — and OWNS how its tools reach the model. 'code' contributes exactly one wire tool, run_code, plus a lazy tools:sdk prompt section declaring every other tool as a generated TypeScript API (jsonSchemaToTs: total over the defineTool subset, unknown degradation, lexicographic byte-identical rendering); 'both' ships both representations; 'native' is byte-for-byte the old behavior. Non-native modes fail every assembly loudly without a typescript-language ctx.codeRuntime. run_code's dispatch bridge: JSON-normalizes each binding argument before dispatch (what dispatches is what the tool/code-dispatch event logs — the append can never fail on payload shape; BigInt/circulars reject that one call), serializes all program tool calls through a per-run queue (even Promise.all — no concurrency-safety metadata yet), routes every sub-call through tools/pre-execute → tools/post-execute (a deny rejects the program-side promise), drops sub-call additionalContext (no safe outlet mid-run; pinned), owns a run-scoped abort that follows the outer signal in and fires on settlement (in-flight sub-dispatch aborted, queued abandoned, queue drained before returning), and converts a failed run into CodeRunFailedError → a structured isError carrying kind + captured logs. tool/code-dispatch joins SessionEventMap by declaration merging (log-only; deriveMessages ignores it). The composed surface: the tools config forwards through agent-core and both app packages; examples/code-agent + demo:code run the worker runtime under mode code (keyless boot smoke + a with-key e2e proving the collapsed [run_code] header, the dispatch events, and the file the program wrote); two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK section, collapsed header, dispatch events, and result card — each its own header-pinning class (the harness gains per-scenario config overlays and per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the RFC (moved to implemented/, restructured to decision-era headings) updated in the same change.
94 lines
3.8 KiB
TypeScript
94 lines
3.8 KiB
TypeScript
/**
|
|
* The code-execution seam (`ctx.codeRuntime`): an abstract service defining
|
|
* WHAT a code runtime does — run one model-written program against a set of
|
|
* host-provided async bindings and report `{ value, logs, error? }` — without
|
|
* saying HOW. Implementations subclass {@link CodeRuntime} and register
|
|
* themselves as the `codeRuntime` service; backends may differ by execution
|
|
* substrate (worker thread, separate process, container) and by source
|
|
* language, both declared as readonly descriptors. The design and its
|
|
* consumer (the tool registry's Code Mode) are specified in the Code Mode RFC
|
|
* (docs/rfc/implemented/feature/2026-06-15-code-mode.md).
|
|
*
|
|
* The split mirrors the bash seam (`BashExecutor`): the runtime knows nothing
|
|
* about tools or sessions — it is handed named async functions and a program,
|
|
* and everything tool-shaped stays with the consumer.
|
|
*
|
|
* @module @deepseek-ai/dsh-code-runtime
|
|
*/
|
|
|
|
import { Context, Service } from 'cordis'
|
|
import type { CodeRunRequest, CodeRunResult } from './types.ts'
|
|
|
|
export type {
|
|
CodeBindingFunction,
|
|
CodeBindingNamespace,
|
|
CodeLogEntry,
|
|
CodeRunFailure,
|
|
CodeRunRequest,
|
|
CodeRunResult,
|
|
} from './types.ts'
|
|
|
|
declare module 'cordis' {
|
|
interface Context {
|
|
codeRuntime: CodeRuntime
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Abstract code-execution service. Subclass, implement {@link run} and the
|
|
* two descriptors, and load the subclass as a plugin — it registers as
|
|
* `ctx.codeRuntime` (one implementation per context; loading a second throws,
|
|
* cordis' standard duplicate-service behavior).
|
|
*
|
|
* Semantics every implementation must honor:
|
|
* - {@link run} resolves with an error FIELD for every program outcome —
|
|
* parse/transform failures, thrown exceptions, budget expiry, abort,
|
|
* substrate death ({@link CodeRunFailure}'s taxonomy). It REJECTS only for
|
|
* caller misuse of the seam itself (e.g. a run submitted after disposal).
|
|
* - Binding calls bridge to the caller's {@link CodeBindingFunction}s
|
|
* verbatim; arguments and resolutions must be structured-cloneable, and the
|
|
* runtime treats the program as a hostile peer (arbitrary binding names are
|
|
* own properties, malformed traffic is rejected or ignored, never crashes
|
|
* the host).
|
|
* - Runs are isolated from each other: no state survives from one run to the
|
|
* next through the runtime.
|
|
* - Disposal reaches quiescence: in-flight runs are terminated AND awaited
|
|
* before the service's own teardown completes (no orphan substrate survives
|
|
* `fiber.dispose()`).
|
|
*/
|
|
export abstract class CodeRuntime extends Service {
|
|
/**
|
|
* The source language {@link run} expects `program` to be written in, as a
|
|
* lowercase identifier. Informational, not gating — a consumer that
|
|
* generates language-specific presentation (typed SDK stubs, usage
|
|
* instructions) switches on it and fails loud on a language it cannot
|
|
* present. Well-known value: `'typescript'`.
|
|
*/
|
|
abstract readonly language: string
|
|
|
|
/**
|
|
* The execution substrate, as a lowercase identifier. Informational, not
|
|
* gating — a descriptor so deployments and diagnostics can tell backends
|
|
* apart, not a security claim. Well-known values: `'worker-thread'`,
|
|
* `'process'`, `'container'`.
|
|
*/
|
|
abstract readonly isolation: string
|
|
|
|
constructor(ctx: Context) {
|
|
super(ctx, 'codeRuntime')
|
|
}
|
|
|
|
/**
|
|
* Execute one program against the request's bindings and capture what it
|
|
* emitted. See the class doc for the resolution contract (error is a result
|
|
* field; rejection means seam misuse only).
|
|
* @param request - the program, its bindings, and the abort signal; the
|
|
* request carries everything the runtime acts on, with no hidden defaults.
|
|
* @returns the run's outcome: completion value (when transferable), the
|
|
* ordered log capture, and the failure (if any).
|
|
*/
|
|
abstract run(request: CodeRunRequest): Promise<CodeRunResult>
|
|
}
|
|
|
|
export default CodeRuntime
|