feat: add the code-execution capability seam (ctx.codeRuntime)
New group packages/code-runtime/ with the interface package @deepseek-ai/dsh-code-runtime, per the Code Mode RFC: abstract CodeRuntime service (run() resolves program failures as an error field, rejects only for seam misuse), the CodeRunRequest/CodeBindingNamespace/CodeRunResult/ CodeLogEntry/CodeRunFailure vocabulary, and readonly language/isolation backend descriptors. Registered in the tsconfig maps, packages/README, architecture service map, and the doc-graph service-role classification; catalogs regenerated. The RFC's one forward path token to the worker package becomes an npm-name mention until PR3 creates that directory (verify-package-paths is drift-scoped: the now-existing group made the token checkable). docs/architecture.md ceiling 1630 -> 1640: the doc gained a genuinely new capability-service row; the row itself is already minimal.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# @deepseek-ai/dsh-code-runtime
|
||||
|
||||
The **code-execution seam**: an abstract `CodeRuntime` service (`ctx.codeRuntime`) 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.
|
||||
|
||||
This package is the interface third of the capability (the bash trio is the template — see [capability seams](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): implementations subclass `CodeRuntime` and register the service; the consumer is the tool registry's Code Mode, which generates the model-facing SDK and bridges tool dispatch — both specified in the [Code Mode RFC](../../../docs/rfc/proposed/feature/2026-06-15-code-mode.md), whose first implementation is a Node worker-thread backend. The runtime knows nothing about tools or sessions: it is handed named async functions and a program string, and everything tool-shaped stays with the consumer.
|
||||
|
||||
## Service API (`ctx.codeRuntime`)
|
||||
|
||||
| Member | Semantics |
|
||||
|---|---|
|
||||
| `run(request)` | Execute one program against the request's bindings. **Resolves with an error FIELD for every program outcome** — parse/transform failure, thrown exception, budget expiry, abort, substrate death (`CodeRunFailure`'s orthogonal `kind` taxonomy); it rejects only for caller misuse of the seam itself (e.g. a run submitted after disposal). The program runs as the body of an async function: top-level `await`/`return` work, and the completion value becomes `result.value` when it survives the serialization boundary. |
|
||||
| `language` | Readonly descriptor: the source language `run` expects (`'typescript'` is the well-known value). Informational, not gating — a consumer that generates language-specific presentation switches on it and fails loud on a language it cannot present. |
|
||||
| `isolation` | Readonly descriptor: the execution substrate (`'worker-thread'`, `'process'`, `'container'`). A label for deployments and diagnostics, **not a security claim**. |
|
||||
|
||||
Semantics every implementation must honor (contract details in the class JSDoc): binding calls bridge to the caller's functions verbatim with structured-cloneable arguments/resolutions; the program is treated as a hostile peer (arbitrary binding names are own properties, malformed traffic never crashes the host); no state survives between runs; disposal terminates in-flight runs AND awaits their exit before completing.
|
||||
|
||||
## Vocabulary
|
||||
|
||||
`CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions`), each exposed to the program as one global object of async callables. `CodeRunResult` reports the completion `value?`, the ordered `logs` (`CodeLogEntry`: `console`/`stdout`/`stderr` source, console `level`, capped text), and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts.
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-code-runtime",
|
||||
"description": "Abstract code-execution seam (ctx.codeRuntime) for the DeepSeek Harness",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./src/*": "./src/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/types/**/*.d.ts",
|
||||
"lib/types/**/*.d.ts.map",
|
||||
"src"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"cordis": "^4.0.0-rc.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cordis": "^4.0.0-rc.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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/proposed/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
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Vocabulary types for the code-execution seam: what a caller hands a
|
||||
* {@link ../index.ts | CodeRuntime} and what it gets back. Pure types — no
|
||||
* runtime code lives here.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-code-runtime/src/types
|
||||
*/
|
||||
|
||||
/**
|
||||
* One host-side function exposed to the program as an async callable. The
|
||||
* runtime bridges calls to it (possibly across a serialization boundary), so
|
||||
* `args` and the resolution value MUST be structured-cloneable; a runtime
|
||||
* rejects a non-cloneable value with a descriptive error rather than
|
||||
* corrupting the run. A rejection of this function surfaces inside the
|
||||
* program as a rejection of the corresponding call.
|
||||
*/
|
||||
export type CodeBindingFunction = (args: unknown) => Promise<unknown>
|
||||
|
||||
/**
|
||||
* A named group of {@link CodeBindingFunction}s the runtime exposes to the
|
||||
* program as one global object (e.g. `tools`). Function names are arbitrary
|
||||
* strings — a runtime must treat names like `__proto__` or `constructor` as
|
||||
* ordinary own properties (null-prototype construction), never as prototype
|
||||
* collisions.
|
||||
*/
|
||||
export interface CodeBindingNamespace {
|
||||
/** The global identifier the program sees (must be a valid JS identifier). */
|
||||
global: string
|
||||
/** The callable members, keyed by the exact name the program calls. */
|
||||
functions: Record<string, CodeBindingFunction>
|
||||
}
|
||||
|
||||
/**
|
||||
* One run: the program source plus everything the runtime acts on. Per the
|
||||
* explicit-over-implicit convention, defaulting (time budgets, output caps)
|
||||
* is the implementation's validated config — a request carries no optional
|
||||
* tuning knobs for a hidden `??` to fill in.
|
||||
*/
|
||||
export interface CodeRunRequest {
|
||||
/**
|
||||
* The program source, in the runtime's {@link ../index.ts | language}. It
|
||||
* runs as the body of an async function: top-level `await` and `return`
|
||||
* are available, and the completion value becomes
|
||||
* {@link CodeRunResult.value}.
|
||||
*/
|
||||
program: string
|
||||
/** Host functions exposed to the program, one global object per namespace. */
|
||||
bindings: CodeBindingNamespace[]
|
||||
/**
|
||||
* Abort the run: the runtime stops the program (hard, even mid-loop) and
|
||||
* resolves with a {@link CodeRunFailure} of kind `'abort'`. In-flight
|
||||
* binding calls are the CALLER's to settle — the runtime only stops asking.
|
||||
*/
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
/**
|
||||
* One captured output entry, in emission order. `source` says which channel
|
||||
* produced it: the program's `console` (shimmed by the runtime), or a stray
|
||||
* write to the underlying stdout/stderr streams.
|
||||
*/
|
||||
export interface CodeLogEntry {
|
||||
/** Which channel produced the text. */
|
||||
source: 'console' | 'stdout' | 'stderr'
|
||||
/** The console method used; present only when `source` is `'console'`. */
|
||||
level?: 'log' | 'info' | 'warn' | 'error' | 'debug'
|
||||
/** The captured text (possibly truncated by the implementation's caps, marked in-band). */
|
||||
text: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a run failed. The kinds are orthogonal outcomes reported independently
|
||||
* (per docs/defensive-patterns.md): a budget expiry is not an exception, an
|
||||
* abort is not a timeout, and a substrate death is neither.
|
||||
*
|
||||
* - `'exception'` — the program threw or failed to parse/transform.
|
||||
* - `'timeout'` — an implementation-owned budget expired; the message says which.
|
||||
* - `'abort'` — {@link CodeRunRequest.signal} fired.
|
||||
* - `'worker-exit'` — the execution substrate died without settling (e.g. OOM).
|
||||
*/
|
||||
export interface CodeRunFailure {
|
||||
/** The failure class (see the interface doc for each kind's meaning). */
|
||||
kind: 'exception' | 'timeout' | 'abort' | 'worker-exit'
|
||||
/** Human-readable detail, suitable for feeding back to a model to self-correct. */
|
||||
message: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The outcome of one run. An error is a FIELD on a resolved result, never a
|
||||
* rejection of `run()` — reporting a failed program is the caller's job, not
|
||||
* an exception path.
|
||||
*/
|
||||
export interface CodeRunResult {
|
||||
/**
|
||||
* The program's completion value (its top-level `return`), when it ran to
|
||||
* completion and the value survived the runtime's serialization boundary;
|
||||
* a non-transferable value is replaced by a string rendering, and a failed
|
||||
* or value-less run leaves this absent.
|
||||
*/
|
||||
value?: unknown
|
||||
/** Everything the program emitted, in order (capped by the implementation). */
|
||||
logs: CodeLogEntry[]
|
||||
/** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
|
||||
error?: CodeRunFailure
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
|
||||
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
|
||||
|
||||
/**
|
||||
* Minimal concrete runtime: records requests, "executes" by invoking every
|
||||
* binding once in declaration order, and lets tests script the outcome. The
|
||||
* seam package ships no implementation, so the contract is exercised through
|
||||
* the smallest subclass that honors it.
|
||||
*/
|
||||
class StubRuntime extends CodeRuntime {
|
||||
readonly language = 'typescript'
|
||||
readonly isolation = 'in-process-stub'
|
||||
requests: CodeRunRequest[] = []
|
||||
nextResult: CodeRunResult = { logs: [] }
|
||||
|
||||
async run(request: CodeRunRequest): Promise<CodeRunResult> {
|
||||
this.requests.push(request)
|
||||
if (request.signal?.aborted) {
|
||||
return { logs: [], error: { kind: 'abort', message: String(request.signal.reason) } }
|
||||
}
|
||||
for (const namespace of request.bindings) {
|
||||
for (const fn of Object.values(namespace.functions)) {
|
||||
await fn({ from: 'stub' })
|
||||
}
|
||||
}
|
||||
return this.nextResult
|
||||
}
|
||||
}
|
||||
|
||||
async function setup() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(StubRuntime)
|
||||
const runtime = ctx.codeRuntime as StubRuntime
|
||||
return { ctx, runtime }
|
||||
}
|
||||
|
||||
describe('CodeRuntime service seam', () => {
|
||||
it('registers as ctx.codeRuntime and serves the abstract API', async () => {
|
||||
const { runtime } = await setup()
|
||||
expect(runtime.language).toBe('typescript')
|
||||
expect(runtime.isolation).toBe('in-process-stub')
|
||||
|
||||
const calls: unknown[] = []
|
||||
const result = await runtime.run({
|
||||
program: 'return 1',
|
||||
bindings: [{ global: 'tools', functions: { probe: async args => void calls.push(args) } }],
|
||||
})
|
||||
expect(result).toEqual({ logs: [] })
|
||||
expect(calls).toEqual([{ from: 'stub' }])
|
||||
expect(runtime.requests).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('reports a failed run as an error field on a resolved result, never a rejection', async () => {
|
||||
const { runtime } = await setup()
|
||||
runtime.nextResult = {
|
||||
logs: [{ source: 'console', level: 'error', text: 'boom' }],
|
||||
error: { kind: 'exception', message: 'boom' },
|
||||
}
|
||||
const result = await runtime.run({ program: 'throw new Error("boom")', bindings: [] })
|
||||
expect(result.error).toEqual({ kind: 'exception', message: 'boom' })
|
||||
expect(result.value).toBeUndefined()
|
||||
})
|
||||
|
||||
it('reports a pre-aborted signal as an abort failure', async () => {
|
||||
const { runtime } = await setup()
|
||||
const controller = new AbortController()
|
||||
controller.abort('cancelled')
|
||||
const result = await runtime.run({ program: 'return 1', bindings: [], signal: controller.signal })
|
||||
expect(result.error).toEqual({ kind: 'abort', message: 'cancelled' })
|
||||
})
|
||||
|
||||
it('is removed from the context when the providing fiber disposes (HMR safety)', async () => {
|
||||
const ctx = new Context()
|
||||
const fiber = await ctx.plugin(StubRuntime)
|
||||
expect(ctx.get('codeRuntime')).toBeInstanceOf(StubRuntime)
|
||||
|
||||
await fiber.dispose()
|
||||
expect(ctx.get('codeRuntime')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects a second implementation in the same context (duplicate service)', async () => {
|
||||
const { ctx } = await setup()
|
||||
await expect(ctx.plugin(StubRuntime)).rejects.toThrow(/registered/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../vendor/cosmokit"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user