mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
docs: rebuild the documentation skill and standards (#2983)
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/code-runtime/README.md
|
||||
README.md: 6f91a163dea33b39875c76885e82ff5d7448bad4
|
||||
README.zh.md: bdcee0c5523614e5e0d83cb86e54dc58f513f536
|
||||
README.md: aea19fd7bcfa4cb018c387a0ef0fd8ad6ad499c2
|
||||
README.zh.md: 142d9bcc666d420536c774957973312c52ce33a3
|
||||
|
||||
@@ -1,14 +1,52 @@
|
||||
---
|
||||
description: "Package map for the code-execution capability family: what program execution does for you, and which package owns each part."
|
||||
kind: "package-group"
|
||||
---
|
||||
|
||||
# code-runtime/ — code-execution capability family
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
The code-execution capability seam (see [capability seams](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)): a runtime Service Definition for executing one model-written program against host-provided async bindings, capturing what it printed and returned; replaceable providers; and the tool registry's [Code Mode](../core/tools/README.md) Consumer (`tools: { mode: code }` — the `run_code` tool and the SDK generated in the loaded runtime's `language`). Design is in the [Code Mode Agent Note](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md). **Product** packages.
|
||||
## Summary
|
||||
|
||||
The `code-runtime/` group provides program execution: a model writes one program that calls host-provided functions as ordinary async calls, and a runtime executes it in isolation and returns only what the program printed and returned. One package defines the shared capability (`ctx.codeRuntime`), a second executes TypeScript programs in a fresh Node worker thread, and a third owns the wire protocol between a Node host and a CPython subprocess for the Python backend. Every run is independent — no state carries from one program to the next — and failures come back as part of the result, so the caller can see why a program failed and feed that back to the model.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Packages](#packages)
|
||||
- [Related documentation](#related-documentation)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="packages"></a>
|
||||
## Packages
|
||||
|
||||
These three packages together provide program execution; each README describes what its part does.
|
||||
|
||||
| Package | Role | ctx key |
|
||||
|---|---|---|
|
||||
| [`code-runtime/`](code-runtime/README.md) | Service Definition and shared vocabulary | `ctx.codeRuntime` |
|
||||
| [`code-runtime-worker/`](code-runtime-worker-thread/README.md) | Worker-thread backend | registers `ctx.codeRuntime` |
|
||||
| [`code-runtime/`](code-runtime/README.md) | Defines what a code runtime does: run one program against host-provided bindings and report what it printed and returned | `ctx.codeRuntime` |
|
||||
| [`code-runtime-worker-thread/`](code-runtime-worker-thread/README.md) | Executes TypeScript programs, each in a fresh Node worker thread | registers `ctx.codeRuntime` |
|
||||
| [`code-runtime-python/`](code-runtime-python/README.md) | Owns the fd-3 wire protocol between a Node host and a CPython subprocess, the Python backend's protocol layer | — |
|
||||
|
||||
Providers register the service without changing its Consumer. The child READMEs own language, isolation, and execution-budget details.
|
||||
-----
|
||||
|
||||
The subsystem reference — run requests/results, binding namespaces, the failure taxonomy — is [docs/subsystems/code-runtime.md](../../docs/subsystems/code-runtime.md).
|
||||
<a id="related-documentation"></a>
|
||||
## Related documentation
|
||||
|
||||
Start with the subsystem reference for the service contract, then the Code Mode design that consumes this capability and the capability-seam model it follows.
|
||||
|
||||
- [Code runtime subsystem reference](../../docs/subsystems/code-runtime.md) — request/result vocabulary, bindings, and the `ctx.codeRuntime` cordis surface.
|
||||
- [Code Mode Agent Note](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) — how the tool registry presents `run_code` to the model.
|
||||
- [Capability seams](../../docs/capability-seams.md) — the Service Definition / Service Provider / Consumer split this family follows.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## Dev Note
|
||||
|
||||
<details>
|
||||
<summary>Working context for maintainers — click to expand</summary>
|
||||
|
||||
None.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,14 +1,52 @@
|
||||
# code-runtime/ — 代码执行能力家族
|
||||
---
|
||||
description: "代码执行能力族的包映射:程序执行能为你做什么,以及每个部分由哪个包负责。"
|
||||
kind: "package-group"
|
||||
---
|
||||
|
||||
# code-runtime/——代码执行能力族
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
代码执行能力 seam(参见[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)):运行时 Service Definition,用于对宿主提供的异步绑定执行模型编写的程序,并捕获它打印和返回的内容;可替换的提供方;以及工具注册表的 [Code Mode](../core/tools/README.zh.md) Consumer(`tools: { mode: code }`,即 `run_code` 工具和按所加载运行时 `language` 生成的 SDK)。设计见 [Code Mode Agent Note](../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md)。这些全是**产品**包。
|
||||
## 概述
|
||||
|
||||
| 包 | 职责 | ctx key |
|
||||
`code-runtime/` 组提供程序执行能力:模型编写一个程序,把宿主提供的函数当作普通异步调用,运行时在隔离环境中执行它,只返回程序打印和返回的内容。一个包定义共享能力(`ctx.codeRuntime`),第二个包在全新的 Node Worker 线程中执行 TypeScript 程序,第三个包持有 Node host 与 CPython 子进程之间的 fd-3 协议格式(wire protocol),为 Python 后端服务。每次运行彼此独立——程序之间不保留任何状态——失败也会作为结果的一部分返回,调用方因此能知道程序为何失败,并把它反馈给模型。
|
||||
|
||||
## 目录
|
||||
|
||||
- [包](#packages)
|
||||
- [相关文档](#related-documentation)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="packages"></a>
|
||||
## 包
|
||||
|
||||
这三个包共同提供程序执行能力;每个 README 描述其各自部分做什么。
|
||||
|
||||
| 包 | 角色 | ctx 键 |
|
||||
|---|---|---|
|
||||
| [`code-runtime/`](code-runtime/README.zh.md) | Service Definition 与共享词汇 | `ctx.codeRuntime` |
|
||||
| [`code-runtime-worker/`](code-runtime-worker-thread/README.zh.md) | Worker 线程后端 | 注册 `ctx.codeRuntime` |
|
||||
| [`code-runtime/`](code-runtime/README.zh.md) | 定义代码运行时做什么:针对宿主提供的绑定运行一个程序,并报告其打印和返回的内容 | `ctx.codeRuntime` |
|
||||
| [`code-runtime-worker-thread/`](code-runtime-worker-thread/README.zh.md) | 在全新的 Node Worker 线程中执行 TypeScript 程序 | 注册 `ctx.codeRuntime` |
|
||||
| [`code-runtime-python/`](code-runtime-python/README.zh.md) | 持有 Node host 与 CPython 子进程之间的 fd-3 协议格式,即 Python 后端的协议层 | — |
|
||||
|
||||
提供方在不改变Consumer的情况下注册该服务。子 README 负责语言、隔离和执行预算细节。
|
||||
-----
|
||||
|
||||
子系统参考——运行请求/结果、绑定命名空间、失败分类体系——见 [docs/subsystems/code-runtime.md](../../docs/subsystems/code-runtime.zh.md)。
|
||||
<a id="related-documentation"></a>
|
||||
## 相关文档
|
||||
|
||||
先从子系统参考了解服务约定,再看消费此能力的 Code Mode 设计,以及它所遵循的能力 seam 模型。
|
||||
|
||||
- [代码运行时子系统参考](../../docs/subsystems/code-runtime.zh.md)——请求/结果词汇、绑定与 `ctx.codeRuntime` 的 cordis 接口面。
|
||||
- [Code Mode Agent Note](../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md)——工具注册表如何把 `run_code` 呈现给模型。
|
||||
- [能力 seam](../../docs/capability-seams.zh.md)——本家族遵循的 Service Definition / Service Provider / Consumer 拆分。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## 开发备注
|
||||
|
||||
<details>
|
||||
<summary>维护者的工作上下文——点击展开</summary>
|
||||
|
||||
无。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime-python/README.md
|
||||
README.md: 1fc19b89b751ce5063d6937d588b96f2f36ae69e
|
||||
README.zh.md: 779b802b4013995bc526150c21b3d86391c6dc74
|
||||
README.md: 22015809b88dd9d71d6cc8410b993a8b2ca0d93b
|
||||
README.zh.md: 124ac64d326c5d0a779ceefe6f17cb18bcca8904
|
||||
|
||||
@@ -1,23 +1,100 @@
|
||||
---
|
||||
description: "fd-3 wire protocol between a Node host and a CPython subprocess for users and maintainers building or debugging the Python code-execution backend."
|
||||
kind: "package-library"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-code-runtime-python
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
CPython-subprocess implementation of the [`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.md) seam. Companion to [`@deepseek-ai/dsh-code-runtime-worker-thread`](../code-runtime-worker-thread/README.md); trades the Node worker thread for a fresh `python3` subprocess so model code is Python instead of TypeScript.
|
||||
## Summary
|
||||
|
||||
The package owns the wire protocol for that seam: the host-side frame codec and the Python-side mirror of the same message vocabulary.
|
||||
`dsh-code-runtime-python` owns the versionless wire protocol between a Node host and a CPython subprocess for the [`dsh-code-runtime`](../code-runtime/README.md) seam: one JSON object per line on the child's fd 3, leaving stdout/stderr free for the program's own output. The package ships the host-side frame codec and hostile-frame validators (`src/protocol.ts`) plus the Python-side mirror of the same message vocabulary (`py/protocol.py`), so every consumer of the wire shares one vocabulary. It is the protocol layer for the Python backend — the package carries no subprocess execution path, so nothing here spawns `python3` outside the cross-language mirror test. The host treats every inbound frame as hostile, because model code has full access to fd 3 and can post anything through it.
|
||||
|
||||
## Wire protocol
|
||||
## Table of Contents
|
||||
|
||||
The host and the CPython subprocess exchange a versionless, JSON-lines protocol on the child's fd 3 — one JSON object per line, leaving stdout/stderr free for the program's own output. `src/protocol.ts` is the host side; `py/protocol.py` mirrors its message shapes and the shared truncation-marker text on the Python side.
|
||||
- [Use this package](#use-this-package)
|
||||
- [Understand the implementation](#understand-the-implementation)
|
||||
- [Further Exploration](#further-exploration)
|
||||
- [Model Experience](#model-experience)
|
||||
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
- **fd 3, not stdout** — Node pins the channel positionally with `stdio: ['pipe','pipe','pipe','pipe']`; the Python bootstrap reads the same `PROTOCOL_FD` constant. JSON-lines framing.
|
||||
- **Host treats every inbound frame as hostile** — model code has full access to fd 3 and can post anything through it, so `validateChildFrame` shape-validates and REBUILDS each frame before the host reads it: forged extra fields never ride along, a non-number call id can never be echoed into a reply, and junk drops to `undefined` rather than throwing in the host's message handler. The Python side trusts host replies (the host is not model-controlled).
|
||||
- **Lossless-JSON crossing** — completion values and binding arguments cross as exact JSON. `encodeJsonPlain` serializes a `JSON.parse`-produced value without recursion, so a deep value below the byte budget crosses intact instead of dying on `JSON.stringify`'s stack limit; `checkDoneValue` meters a forged completion value's byte length AND number losslessness in one traversal that rejects an over-budget payload before the incremental work it would add (the enqueued children; strings and keys are metered by a non-allocating escaped-size scan, so the escaped copy is never materialized) — the frame's own width is already parsed and capped upstream by the host's fd-3 receive buffer, not re-bounded here; `hasUnsafeIntegerToken` reads the raw frame text to catch an integer token that `JSON.parse` would silently round; `hasNonLosslessNumber` rejects a non-finite or negative-zero number in unbounded `call.args`. Beyond-safe-range integral doubles serialize through `BigInt` digits so the exact integer crosses, not the rounded `String()` form.
|
||||
- **Shared truncation marker** — `logTruncationMarker(maxBytes)` produces byte-identical text on both sides, so a truncated log run reads the same however the cap was hit. The `log` frame's `truncated` flag distinguishes the child ledger's own marker from program output.
|
||||
-----
|
||||
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
Choose this package when you build or consume the CPython code-runtime wire: implement the Python backend or the host that drives it, or debug a Python code run's framing. The package is the wire protocol intended for a CPython code-runtime provider — such a provider runs each model program in a fresh `python3 -I` subprocess — and this package supplies the protocol both sides speak, so its exports are the single TS-side source of truth for the wire.
|
||||
|
||||
### What you get
|
||||
|
||||
The package re-exports the host-side protocol vocabulary from `src/index.ts`: `validateChildFrame` (rebuilds every inbound frame before the host reads it), the lossless-JSON codec and meters (`encodeJsonPlain`, `checkDoneValue`, `hasUnsafeIntegerToken`, `hasNonLosslessNumber`), and `logTruncationMarker` (the shared truncation-marker text). The Python side mirrors the message shapes as `TypedDict`s in `py/protocol.py` and re-declares the two surfaces both sides execute against — `PROTOCOL_FD = 3` and the marker text.
|
||||
|
||||
### The wire
|
||||
|
||||
Frames travel on the child's fd 3 as JSON-lines — one object per line — so stdout/stderr stay clear for the program's own output. Child → host: `boot-ack`, `call`, `log`, `done`. Host → child: `boot` (first frame, carrying every cap and the namespace declarations), `run` (after `boot-ack`, carrying only the program body), and one `reply` per `call`. A forged frame can carry both `value` and `error` on `done`, so a consumer must check `error` first and ignore `value` when it is set.
|
||||
|
||||
### What can go wrong
|
||||
|
||||
Host-side validation drops junk without throwing, so a malformed or forged frame never crashes the host process: `validateChildFrame` returns `undefined` for anything that does not rebuild cleanly, a non-number call id can never be echoed into a reply, and forged extra fields never ride along. A completion value that is not lossless JSON, or that exceeds the configured byte budget, is rejected explicitly (`non-lossless` / `over-budget`) rather than silently rounded or truncated.
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## Understand the implementation
|
||||
|
||||
<details>
|
||||
<summary>Implementation internals — click to expand</summary>
|
||||
|
||||
This section explains the design behind the wire protocol; observable behavior is fully covered in [Use this package](#use-this-package).
|
||||
|
||||
### Design concept
|
||||
|
||||
The protocol assumes one direction of trust: the host treats every inbound frame as hostile (model code can forge anything on fd 3) and REBUILDS it field by field before reading; the Python side trusts host replies, because the host is not model-controlled. The package is deliberately the protocol layer only — the Python-side JSON codec lives in the backend's bootstrap, not in `py/protocol.py`, so the mirror stays the pure wire-vocabulary counterpart of `src/protocol.ts`.
|
||||
|
||||
### Wire contract
|
||||
|
||||
The frames are `boot` / `run` (host → child) and `boot-ack` / `call` / `log` / `done` plus one `reply` per call (child → host). The `log` frame's `truncated` flag marks the frame that IS the child ledger's truncation marker, so the host stops capturing at the same point the child did instead of inferring it from its own budget. `done.error.kind` is one of `exception`, `invalid-output`, `output-limit`; wall/CPU budgets, aborts, and substrate death are observed host-side, not carried as frames.
|
||||
|
||||
### Lossless JSON crossing
|
||||
|
||||
Completion values and binding arguments cross as exact JSON: values serialize without recursion, so a deep payload below the byte budget survives instead of dying on `JSON.stringify`'s stack limit, and integral doubles beyond the safe range cross as exact digits rather than silently rounded tokens; the meters in [`src/protocol.ts`](src/protocol.ts) enforce byte budgets and number losslessness before anything else reads the payload.
|
||||
|
||||
### Mirror alignment
|
||||
|
||||
`tests/protocol-mirror.e2e.ts` spawns a real `python3` and asserts, against `src/protocol.ts`, both `PROTOCOL_FD` / the truncation-marker text and each `TypedDict`'s required/optional wire field set in `py/protocol.py`, so a renamed or dropped field — or one side making a field optional the other requires — fails the test. Field *types* are not compared across the language boundary; that residue stays with review plus the backend's real-subprocess suite.
|
||||
|
||||
### Source map
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| [`src/index.ts`](src/index.ts) | Plugin entry: re-exports the protocol vocabulary for every consumer of the wire |
|
||||
| [`src/protocol.ts`](src/protocol.ts) | Host side: frame codec, hostile-frame validators, lossless-JSON meters, shared marker text |
|
||||
| [`py/protocol.py`](py/protocol.py) | Python side: `PROTOCOL_FD`, `TypedDict` frame mirrors, `log_truncation_marker` |
|
||||
| [`tests/protocol-mirror.e2e.ts`](tests/protocol-mirror.e2e.ts) | Cross-language mirror test against a real `python3` |
|
||||
| [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; the package registers no mutable data relation) |
|
||||
|
||||
</details>
|
||||
|
||||
-----
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## Further Exploration
|
||||
|
||||
Read these when the protocol contract is not enough. They move from the seam definition to the protocol's design record and the companion backend.
|
||||
|
||||
- [Code runtime seam](../code-runtime/README.md) — the abstract contract the Python backend implements.
|
||||
- [fd-3 protocol Agent Note](../../../.agents/notes/implemented/architecture/2026-07-31-code-runtime-python-fd3-protocol.md) — design rationale, wire contract, and the mirror-alignment decision.
|
||||
- [Worker-thread backend](../code-runtime-worker-thread/README.md) — the shipped TypeScript sibling, the model for the Python backend's behavior.
|
||||
- [Code runtime subsystem reference](../../../docs/subsystems/code-runtime.md) — request/result vocabulary, bindings, and failure taxonomy.
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## Model Experience
|
||||
|
||||
Indirectly, through Code Mode in [`dsh-tools`](../../core/tools/README.md), which renders this backend's exact completion value when it fits (or an explicit `invalid-output` / `output-limit` failure), plus the exact `[dsh-code-runtime-python] log capture truncated at <maxLogBytes> bytes` log marker, into a retained `run_code` result.
|
||||
Indirectly, through Code Mode in `dsh-tools`, which renders the program's completion value or failure into a retained `run_code` result.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
@@ -25,5 +102,20 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **The cross-language guard covers the runtime-executed surfaces and the frame field shapes** — `tests/protocol-mirror.e2e.ts` spawns a real `python3` and asserts, against `src/protocol.ts`, both `PROTOCOL_FD` / the log truncation marker text AND each `TypedDict`'s required/optional wire field set in `py/protocol.py`. What it does not compare is the field *types* (e.g. that `cpuSeconds` is an `int` on both sides): comparing type declarations across TypeScript and Python has no mechanical equivalent here, so a type-level drift is still caught by review plus the backend's real-subprocess suite rather than this package's tests.
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
|
||||
These limits define what the package does and does not cover; they are current package constraints, not a task backlog.
|
||||
|
||||
- **The cross-language guard covers the executed surfaces and the frame field shapes, not the field types** — the mirror e2e compares required/optional field sets, not that `cpuSeconds` is an `int` on both sides; comparing type declarations across TypeScript and Python has no mechanical equivalent here, so a type-level drift is caught by review plus the backend's real-subprocess suite.
|
||||
- **`src/index.ts` exports the protocol vocabulary only** — the package carries no subprocess execution path and no Python-side JSON codec, so nothing here spawns `python3` outside the mirror test.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
<details>
|
||||
<summary>Working context for maintainers — click to expand</summary>
|
||||
|
||||
None.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,29 +1,121 @@
|
||||
---
|
||||
description: "Node host 与 CPython 子进程之间的 fd-3 协议格式(wire protocol),供用户与维护者构建或排查 Python 代码执行后端。"
|
||||
kind: "package-library"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-code-runtime-python
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
[`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.zh.md) seam 的 CPython 子进程实现。与 [`@deepseek-ai/dsh-code-runtime-worker-thread`](../code-runtime-worker-thread/README.zh.md) 配套;以全新的 `python3` 子进程取代 Node worker 线程,让模型代码从 TypeScript 换成 Python。
|
||||
## 概述
|
||||
|
||||
本包持有该 seam 的 wire protocol:host 侧的帧编解码,以及 Python 侧对同一套消息词汇的镜像。
|
||||
`dsh-code-runtime-python` 持有 [`dsh-code-runtime`](../code-runtime/README.zh.md) seam 的 Node host 与 CPython 子进程之间的无版本协议格式(wire protocol):子进程 fd 3 上每行一个 JSON 对象,让 stdout/stderr 空出给程序自己的输出。本包提供 host 侧的帧编解码与敌意帧校验器(`src/protocol.ts`),以及同一套消息词汇的 Python 侧镜像(`py/protocol.py`),因此每个 wire 消费方都共享同一套词汇。它是 Python 后端的协议层——本包不含子进程执行路径,因此除跨语言镜像测试之外,没有任何地方会启动 `python3`。host 把每个入站帧都当作敌意输入,因为模型代码对 fd 3 有完全访问权、可通过它发送任意内容。
|
||||
|
||||
## Wire protocol
|
||||
## 目录
|
||||
|
||||
host 与 CPython 子进程在子进程的 fd 3 上交换一个无版本号的 JSON-lines 协议——每行一个 JSON 对象,让 stdout/stderr 空出给程序自己的输出。`src/protocol.ts` 是 host 侧;`py/protocol.py` 在 Python 侧镜像其帧词汇与共享的截断标记文本。
|
||||
- [使用本包](#use-this-package)
|
||||
- [理解实现](#understand-the-implementation)
|
||||
- [进一步探索](#further-exploration)
|
||||
- [模型体验](#model-experience)
|
||||
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
- **fd 3,而非 stdout** —— Node 通过 `stdio: ['pipe','pipe','pipe','pipe']` 按位置钉住通道;Python bootstrap 读取相同的 `PROTOCOL_FD` 常量。JSON-lines 帧。
|
||||
- **host 把每个入站帧当作敌意输入** —— 模型代码对 fd 3 有完全访问权、可通过它发送任意内容,所以 `validateChildFrame` 在 host 读取前对每个帧做形状校验并重建:伪造的额外字段绝不随行,非数字的 call id 绝不会被回显进 reply,垃圾降为 `undefined` 被丢弃,而不是在 host 的 message handler 里抛错。Python 侧信任 host 回复(host 不受模型控制)。
|
||||
- **lossless-JSON 穿越** —— 完成值与 binding 参数以精确 JSON 穿越。`encodeJsonPlain` 无递归地序列化一个 `JSON.parse` 产出的值,使低于字节预算的深层值能完整穿越,而不是死在 `JSON.stringify` 的栈限制上;`checkDoneValue` 在一次遍历中同时计量伪造完成值的字节长度与数字无损性,在它本会新增的增量工作之前就拒绝超预算 payload(即入栈子节点;字符串与 key 由非分配的转义尺寸扫描计量,从不物化转义副本)——帧自身的宽度已被上游 `JSON.parse` 支付、由 host 的 fd-3 接收缓冲封顶,并非在此重新约束;`hasUnsafeIntegerToken` 读取原始帧文本,捕获 `JSON.parse` 会静默舍入的整数 token;`hasNonLosslessNumber` 拒绝无字节上限的 `call.args` 中的非有限数或负零。超出安全范围的整数型 double 通过 `BigInt` 数字序列化,穿越的是精确整数而非 `String()` 的舍入形式。
|
||||
- **共享截断标记** —— `logTruncationMarker(maxBytes)` 在两侧产出逐字节一致的文本,使被截断的日志运行无论从哪侧触达上限都读起来一致。`log` 帧的 `truncated` 标志把子进程 ledger 自身的标记与程序输出区分开。
|
||||
-----
|
||||
|
||||
## Model Experience
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
经由 [`dsh-tools`](../../core/tools/README.zh.md) 里的 Code Mode 间接生效:Code Mode 把本后端的精确完成值(放得下时)或一个明确的 `invalid-output` / `output-limit` 失败,连同精确的 `[dsh-code-runtime-python] log capture truncated at <maxLogBytes> bytes` 日志标记,渲染进一个保留的 `run_code` 结果。
|
||||
当你要构建或消费 CPython 代码运行时 wire 时选择本包:实现 Python 后端或驱动它的 host,或排查 Python 代码运行的帧。本包是为 CPython code-runtime 提供方准备的 wire 协议——这样的提供方会在全新的 `python3 -I` 子进程中运行每个模型程序——本包提供两侧共同使用的协议,因此其导出是 wire 的 TS 侧唯一真源。
|
||||
|
||||
#### KV Cache effect
|
||||
### 你得到什么
|
||||
|
||||
无直接失效;具名消费者拥有任何请求前缀的变更。
|
||||
本包从 `src/index.ts` 重新导出 host 侧的协议词汇:`validateChildFrame`(在 host 读取前重建每个入站帧)、无损 JSON 编解码与计量器(`encodeJsonPlain`、`checkDoneValue`、`hasUnsafeIntegerToken`、`hasNonLosslessNumber`),以及 `logTruncationMarker`(共享的截断标记文本)。Python 侧在 `py/protocol.py` 中把消息形状镜像为 `TypedDict`,并重新声明两侧都执行的两个表面——`PROTOCOL_FD = 3` 与标记文本。
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
### 协议格式
|
||||
|
||||
- **跨语言 guard 覆盖运行时执行的面与帧字段形状** —— `tests/protocol-mirror.e2e.ts` 启动一个真实 `python3`,对照 `src/protocol.ts` 断言 `PROTOCOL_FD` / 日志截断标记文本,以及 `py/protocol.py` 中每个 `TypedDict` 的必填/可选 wire 字段集。它不比较字段的*类型*(例如 `cpuSeconds` 两侧都是 `int`):跨 TypeScript 与 Python 比较类型声明在此无机械等价物,故类型级漂移仍由 review 加后端真子进程套件捕获,而非本包的测试。
|
||||
- **`src/index.ts` 只导出协议词汇** —— 本包不含子进程执行路径,也不含 Python 侧的 JSON codec,因此除 mirror 测试之外没有任何地方会启动 `python3`。
|
||||
帧在子进程 fd 3 上以 JSON-lines 传输——每行一个对象——因此 stdout/stderr 保持空闲,供程序自己的输出使用。子进程 → host:`boot-ack`、`call`、`log`、`done`。host → 子进程:`boot`(首帧,携带所有上限与命名空间声明)、`run`(在 `boot-ack` 之后,只携带程序主体),以及每个 `call` 一个 `reply`。伪造帧可以在 `done` 上同时携带 `value` 与 `error`,因此消费方必须先检查 `error`,在它存在时忽略 `value`。
|
||||
|
||||
### 可能出什么问题
|
||||
|
||||
host 侧校验会静默丢弃垃圾,因此格式错误或伪造的帧绝不会让宿主进程崩溃:`validateChildFrame` 对任何无法干净重建的内容返回 `undefined`,非数字的 call id 绝不会被回显进 reply,伪造的额外字段绝不随行。不是无损 JSON、或超出配置字节预算的完成值会被明确拒绝(`non-lossless`/`over-budget`),而不会被静默舍入或截断。
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## 理解实现
|
||||
|
||||
<details>
|
||||
<summary>实现细节——点击展开</summary>
|
||||
|
||||
本节解释协议格式(wire protocol)背后的设计;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
||||
|
||||
### 设计理念
|
||||
|
||||
协议假定单向信任:host 把每个入站帧都当作敌意输入(模型代码可以在 fd 3 上伪造任何内容),并在读取前逐字段重建;Python 侧信任 host 回复,因为 host 不受模型控制。本包刻意只是协议层——Python 侧 JSON codec 位于后端的 bootstrap 中,而非 `py/protocol.py`,因此镜像保持为 `src/protocol.ts` 的纯 wire 词汇对侧。
|
||||
|
||||
### 协议约定
|
||||
|
||||
帧为 `boot`/`run`(host → 子进程)与 `boot-ack`/`call`/`log`/`done` 加每个 call 一个 `reply`(子进程 → host)。`log` 帧的 `truncated` 标志标记「就是子进程 ledger 截断标记」的那个帧,因此 host 在子进程停下的同一点停止捕获,而不是根据自己的预算推断。`done.error.kind` 是 `exception`、`invalid-output`、`output-limit` 之一;墙钟/CPU 预算、中止与基底终止在 host 侧观测,不作为帧携带。
|
||||
|
||||
### 无损 JSON 穿越
|
||||
|
||||
完成值与 binding 参数以精确 JSON 穿越:值无递归地序列化,因此低于字节预算的深层 payload 能完整穿越,而不是死在 `JSON.stringify` 的栈限制上;超出安全范围的整数型 double 以精确数字穿越,而不是被静默舍入的 token;[`src/protocol.ts`](src/protocol.ts) 中的计量器在任何其他代码读取 payload 之前强制执行字节预算与数字无损性。
|
||||
|
||||
### 镜像对齐
|
||||
|
||||
`tests/protocol-mirror.e2e.ts` 启动一个真实 `python3`,对照 `src/protocol.ts` 断言 `PROTOCOL_FD`/截断标记文本,以及 `py/protocol.py` 中每个 `TypedDict` 的必填/可选 wire 字段集,因此重命名或删除字段——或一侧把另一侧必填的字段改为可选——都会让测试失败。跨语言边界不比较字段*类型*;该残留由 review 加后端的真子进程套件负责。
|
||||
|
||||
### 源码地图
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| [`src/index.ts`](src/index.ts) | 插件入口:为每个 wire 消费方重新导出协议词汇 |
|
||||
| [`src/protocol.ts`](src/protocol.ts) | host 侧:帧编解码、敌意帧校验器、无损 JSON 计量器、共享标记文本 |
|
||||
| [`py/protocol.py`](py/protocol.py) | Python 侧:`PROTOCOL_FD`、`TypedDict` 帧镜像、`log_truncation_marker` |
|
||||
| [`tests/protocol-mirror.e2e.ts`](tests/protocol-mirror.e2e.ts) | 对照真实 `python3` 的跨语言镜像测试 |
|
||||
| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;本包不注册任何可变数据关系) |
|
||||
|
||||
</details>
|
||||
|
||||
-----
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## 进一步探索
|
||||
|
||||
当协议约定不够用时阅读以下内容。它们从 seam 定义进入协议的设计记录与配套后端。
|
||||
|
||||
- [代码运行时 seam](../code-runtime/README.zh.md)——Python 后端实现的抽象约定。
|
||||
- [fd-3 协议 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-31-code-runtime-python-fd3-protocol.zh.md)——设计理由、协议约定与镜像对齐决策。
|
||||
- [Worker 线程后端](../code-runtime-worker-thread/README.zh.md)——已发布的 TypeScript 兄弟包,是 Python 后端行为的模板。
|
||||
- [代码运行时子系统参考](../../../docs/subsystems/code-runtime.zh.md)——请求/结果词汇、绑定与失败分类体系。
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## 模型体验
|
||||
|
||||
通过 `dsh-tools` 中的 Code Mode 间接提供;后者把程序的完成值或失败渲染进一个保留的 `run_code` 结果。
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
不会直接失效;由上述消费方负责请求前缀变更。
|
||||
|
||||
## 已知限制与延期工作
|
||||
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
|
||||
这些限制说明本包覆盖什么、不覆盖什么;它们是当前包约束,不是任务积压。
|
||||
|
||||
- **跨语言 guard 覆盖执行表面与帧字段形状,但不覆盖字段类型**——镜像 e2e 比较必填/可选字段集,而不比较 `cpuSeconds` 两侧是否都是 `int`;跨 TypeScript 与 Python 比较类型声明在此无机械等价物,因此类型级漂移由 review 加后端的真子进程套件捕获。
|
||||
- **`src/index.ts` 只导出协议词汇**——本包不含子进程执行路径,也不含 Python 侧的 JSON codec,因此除镜像测试之外没有任何地方会启动 `python3`。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
<details>
|
||||
<summary>维护者的工作上下文——点击展开</summary>
|
||||
|
||||
无。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime-worker-thread/README.md
|
||||
README.md: 778d52e1175457a100962b15f61379372a2e8ad0
|
||||
README.zh.md: 92c767cf353aa10cdfce2dfc5eff0ac7a5783dfb
|
||||
README.md: 40f2c735f13efdb41a047c4f582ecf98220825d8
|
||||
README.zh.md: b9389d359dd8327e1e5847c7d0c78c94c2c51ccc
|
||||
|
||||
@@ -1,44 +1,131 @@
|
||||
---
|
||||
description: "Worker-thread code execution for users and maintainers composing, sizing, or debugging the shipped TypeScript backend that runs each program in a fresh Node worker."
|
||||
kind: "package-reference"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-code-runtime-worker-thread
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Worker-thread implementation of the [`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.md) seam: `WorkerThreadCodeRuntime` runs each program in ONE fresh Node `worker_threads.Worker` — TypeScript in, type-stripped host-side, bindings bridged over the message port, `{ value, logs, error? }` out. **Containment, not a security boundary**: trust posture is bash-equivalent by design (the [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) § Trust posture), with containment bash does not have — separate isolate, empty environment, heap cap, hard termination.
|
||||
## Summary
|
||||
|
||||
## Config
|
||||
`dsh-code-runtime-worker-thread` executes TypeScript programs for the [`dsh-code-runtime`](../code-runtime/README.md) seam: each program runs in one fresh Node worker thread with host-provided bindings callable as ordinary async functions, and the run returns `{ value, logs, error? }`. It is the shipped backend for Code Mode in `dsh-tools`, so mounting it is what makes model-written TypeScript execution work in a composition. The runtime contains a program without isolating it: the trust posture is bash-equivalent, with an empty environment, a heap cap, measured busy-time and wall-clock budgets, and hard termination. Programs run once per request with no state carried between runs, and every failure — syntax error, budget expiry, abort, OOM exit, or output overflow — comes back as a result field.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Use this package](#use-this-package)
|
||||
- [Understand the implementation](#understand-the-implementation)
|
||||
- [Further Exploration](#further-exploration)
|
||||
- [Model Experience](#model-experience)
|
||||
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
Mount this backend with the code-runtime seam when a composition should execute model-written TypeScript programs; Code Mode in `dsh-tools` then drives it through `ctx.codeRuntime` whenever the model calls `run_code`. Every execution cap is validated config, so you can size the runtime for your deployment from `cordis.yml`.
|
||||
|
||||
### Minimal configuration
|
||||
|
||||
```yaml
|
||||
- id: code-runtime
|
||||
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
|
||||
- name: '@deepseek-ai/dsh-code-runtime'
|
||||
- name: '@deepseek-ai/dsh-code-runtime-worker-thread'
|
||||
config:
|
||||
computeMs: 60000 # busy-time budget (measured event-loop active time)
|
||||
maxWallMs: 600000 # wall-clock ceiling; never pauses for anything
|
||||
maxOutputBytes: 67108864 # combined serialized outer-output cap (64 MiB)
|
||||
maxOldGenerationSizeMb: 512 # worker heap cap (resourceLimits)
|
||||
computeMs: 60000 # busy-time budget (measured event-loop active time)
|
||||
maxWallMs: 600000 # wall-clock ceiling; never pauses for anything
|
||||
maxOutputBytes: 67108864 # combined serialized outer-output cap (64 MiB)
|
||||
maxOldGenerationSizeMb: 512 # worker heap cap
|
||||
```
|
||||
|
||||
Every field is validated and defaulted; `maxOutputBytes` is a safe integer of at least four bytes, the remaining fields are positive finite numbers, `maxWallMs` is additionally at most `2147483647` (Node's maximum `setTimeout` delay), and there are no other tunables.
|
||||
| Field | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `computeMs` | `60,000` | Busy-time budget: the run fails with `timeout` once the worker's measured event-loop active time exceeds it |
|
||||
| `maxWallMs` | `600,000` | Wall-clock ceiling, the backstop for waits that busy time cannot see; at most `2_147_483_647` |
|
||||
| `maxOutputBytes` | `67,108,864` | Hard cap for serialized logs plus the completion value or failure message; at least `4` |
|
||||
| `maxOldGenerationSizeMb` | `512` | Worker heap cap; overflow kills the worker and surfaces as `worker-exit` |
|
||||
|
||||
## Design
|
||||
Every field is validated and defaulted at load; there are no other tunables. The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-code-runtime-worker-thread) is the exhaustive source for every accepted field.
|
||||
|
||||
- **One fresh worker per run, no pooling** — a program's world dies with its worker: no cross-run state to log, state bleed unrepresentable, runs reconstructable from the session log alone.
|
||||
- **Type-strip host-side, in execution context** — the program is wrapped in an async-function shell, stripped with `node:module`'s `stripTypeScriptTypes` (erasable syntax only — `enum`/namespaces are rejected as a program `exception` and no worker spawns), and sliced back out byte-positioned; it then executes as the body of an `AsyncFunction`, so top-level `await`/`return` work.
|
||||
- **The port assumes a hostile peer** — model code can reach `parentPort` and forge traffic, so every inbound message is shape-validated and REBUILT before anything reads it (`null`, primitives, junk types, and malformed payloads drop without a throw; forged extra fields never ride along), the host answers each call id at most once, resolves binding names as OWN properties only (a forged `constructor` cannot walk a prototype chain), drops post-settlement replies, and validates every binding resolution and completion as lossless JSON. Forged `log`/`done` messages cannot bypass the outer cap: the host repeats validation and accounts every admitted log plus the completion or diagnostic. Worker-side namespaces are null-prototype with `defineProperty`, so `__proto__`-shaped binding names are ordinary keys.
|
||||
- **Binding rejection classes are request data** — an optional namespace descriptor names the constructor global and the own property that receives the failed member name. The worker materializes and injects that real class, so `instanceof` works without hardcoding `tools` or `ToolCallError`; declarations with invalid or colliding globals fail before a worker spawns. Failures use module-captured error and property-definition intrinsics plus null-prototype descriptors, so later model mutations cannot turn a rejected binding into a worker crash.
|
||||
- **Two independent budgets, because the peer is hostile** — `computeMs` meters the worker's MEASURED busy time (`worker.performance.eventLoopUtilization()` polling): a hot loop cannot hide behind a pending decoy dispatch, and a program awaiting a slow tool accrues nothing. `maxWallMs` backstops what busy time cannot see (awaiting a promise nobody resolves). Both funnel into `worker.terminate()`, which ends hot synchronous loops too; heap overflow surfaces as the worker's OOM exit (`kind: 'worker-exit'`). `maxWallMs` is range-checked at load against `MAX_TIMER_DELAY_MS`: `setTimeout` clamps a longer delay to 1 ms, so a positivity check alone would accept a ceiling that expires on the first tick. `computeMs` needs no such bound, being compared against measured utilization rather than fed to a timer.
|
||||
- **Intermediate binding values are complete JSON** — binding arguments and resolutions undergo lossless-JSON validation and transfer without a byte cap. The worker captures the validation primitives before executing untrusted code, so mutations to globals or prototypes cannot weaken validation or byte accounting. Intermediate values never enter the outer-output ledger or model context; provider/executor acquisition bounds and process/worker memory remain the limits.
|
||||
- **Logs stream eagerly into one outer ledger** — console/stdout/stderr text crosses the port in emission order, so a timed-out or killed program still shows what it printed. The worker charges exact JSON-string bytes and preflights completion values and exception diagnostics against the remaining combined budget before posting them; a thrown million-byte stack therefore becomes the fixed `output-limit` diagnostic at the worker boundary. Native writes that bypass the patched stream slots arrive on pipes independent of the completion port, so the host repeats the ledger for those bytes and hostile forged traffic; settlement continues bounded pipe capture until worker termination completes before materializing the result. `maxOutputBytes` accounts the JSON serialization of the outer `logs` array plus the completion value or failure-message payload; fixed `CodeRunResult` field names, braces, the bounded error-kind tag, and later presentation whitespace are outside that variable-payload ledger. At or below the cap the exact value returns; a lossy completion is `invalid-output`, and a combined overflow is `output-limit` rather than a substituted inspected string. The failure retains the fitting captured prefix and later follows the normal outer `run_code` spill policy.
|
||||
- **Empty environment** — the worker gets `env: {}` and `execArgv: []`: no ambient credentials (stronger than the scrubbed-env rule for spawned commands) and no inherited loader flags.
|
||||
- **Dispose to quiescence** — teardown fails in-flight runs as `abort` and AWAITS each worker's exit before resolving.
|
||||
### What a run returns
|
||||
|
||||
## The worker entry, unbuilt and built
|
||||
A successful run returns the program's lossless-JSON completion value as `result.value` and the text it printed, in order, as `result.logs`. Top-level `await` and `return` work, and the program can call the host-provided binding functions (Code Mode exposes one `tools` object) as ordinary async calls.
|
||||
|
||||
Source mode loads erasable-only `src/worker.ts` through Node's native type stripping. Its transitive runtime closure contains only Node built-ins and relative source modules, so a fresh checkout never requires a sibling workspace package's unbuilt `lib/` export. The worker-local and session-owned JSON boundaries both flatten and rebuild validated values around the message port so application nesting never reaches structured clone. Built mode passes the sibling `lib/worker.cjs` as a filesystem path because pkg's VFS Worker hook expects CommonJS; the same path works under ordinary Node. The repository-wide requirement to exercise this published entry path belongs to the [testing policy](../../../docs/testing.md).
|
||||
### Containment, not a security boundary
|
||||
|
||||
The SDK API is the default/named `WorkerThreadCodeRuntime` class plus `Config`. The operational `./worker` subpath exists only as the packaged spawn entry; the wire protocol and bootstrap helpers are source-private implementation details.
|
||||
A program runs with authority comparable to the bash tool: it can reach Node APIs, and the backend deliberately does not promise isolation from the host. What it does provide is containment — a separate isolate, an empty environment (no ambient credentials, no inherited loader flags), a configurable heap cap, and hard termination that also stops a hot synchronous loop. OS processes a program spawns survive `terminate()` and need deployment-level cleanup.
|
||||
|
||||
### What can go wrong
|
||||
|
||||
Every program outcome resolves as a result, so a failed run is a `result.error`, not a rejection: a syntax error or non-erasable TypeScript (`enum`, namespaces) fails as `exception` before any worker spawns; budget expiry is `timeout`; the abort signal is `abort`; a heap overflow or other worker death is `worker-exit`; a completion value that is not lossless JSON is `invalid-output`; and serialized output beyond the cap is `output-limit` — with the fitting captured log prefix retained. Rejection means caller misuse, such as a run submitted after disposal.
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## Understand the implementation
|
||||
|
||||
<details>
|
||||
<summary>Implementation internals — click to expand</summary>
|
||||
|
||||
This section explains the design behind the backend; observable behavior is fully covered in [Use this package](#use-this-package).
|
||||
|
||||
### Design concept
|
||||
|
||||
The backend rests on one separation: **containment, not a security boundary**. Model code has bash-equivalent trust (the [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) Trust posture), so the design optimizes for reconstructability and bounded resource use rather than for a hard multi-tenant boundary — that awaits a container-class backend. Each run gets one fresh worker, so a program's world dies with its worker: no cross-run state exists to leak or to log, and a run is reconstructable from the session log alone.
|
||||
|
||||
### Execution flow
|
||||
|
||||
A run is type-stripped host-side (`node:module`'s `stripTypeScriptTypes`, position-preserving), wrapped as the body of an async function so top-level `await`/`return` work, and sent to a fresh worker whose bootstrap materializes the binding namespaces. Binding calls cross the message port as lossless JSON and are answered at most once per call id. Log text streams to the host eagerly so a killed program still shows what it printed. Exactly one outcome settles the run — a `done` frame, a budget expiry, an abort, or worker death — after which the host terminates the worker and awaits its exit.
|
||||
|
||||
### Hostile-peer port
|
||||
|
||||
Model code can reach `parentPort` and forge traffic, so every inbound message is shape-validated and rebuilt field by field before anything reads it: forged extra fields never ride along, a non-number call id can never be echoed into a reply, binding names resolve as own properties only (a forged `constructor` cannot walk a prototype chain), and junk drops silently. Worker-side namespaces are null-prototype, so `__proto__`-shaped binding names are ordinary keys.
|
||||
|
||||
### Budgets
|
||||
|
||||
Two independent budgets exist because the peer is hostile: `computeMs` meters the worker's measured busy time (`eventLoopUtilization()` polling every 25 ms), so a hot loop expires it whether or not a decoy dispatch is in flight, while a program idling on a slow binding accrues nothing; `maxWallMs` backstops what busy time cannot see, such as a promise nobody resolves. Both funnel into `worker.terminate()`. `maxWallMs` is range-checked at load against `MAX_TIMER_DELAY_MS` because `setTimeout` clamps a longer delay to 1 ms.
|
||||
|
||||
### Output ledger
|
||||
|
||||
`maxOutputBytes` accounts the JSON serialization of the outer `logs` array plus the completion value or failure-message payload; fixed `CodeRunResult` field names and envelope syntax are outside that ledger. At or below the cap the exact value returns; a lossy completion is `invalid-output`, and a combined overflow is `output-limit` rather than a substituted inspected string. The failure retains a fitting captured prefix of the logs.
|
||||
|
||||
### Source map
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, `WorkerThreadCodeRuntime`, run orchestration, output ledger |
|
||||
| [`src/worker.ts`](src/worker.ts) | Source-mode worker entry (erasable TypeScript, no `lib/` dependency) |
|
||||
| [`src/bootstrap.ts`](src/bootstrap.ts) | Worker-side bootstrap: namespace materialization, console shim, log capture |
|
||||
| [`src/protocol.ts`](src/protocol.ts) | Port message vocabulary between host and worker |
|
||||
| [`src/worker-json.ts`](src/worker-json.ts) | Worker-side lossless-JSON encode/decode |
|
||||
| [`src/output-json.ts`](src/output-json.ts) | Byte metering and truncation for the outer ledger |
|
||||
| [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; see its reason) |
|
||||
|
||||
### The worker entry, unbuilt and built
|
||||
|
||||
Source mode loads erasable-only `src/worker.ts` through Node's native type stripping; its transitive runtime closure contains only Node built-ins and relative source modules, so a fresh checkout never requires a sibling workspace package's unbuilt `lib/` export. Built mode passes the sibling `lib/worker.cjs` as a filesystem path because pkg's VFS Worker hook expects CommonJS; the same path works under ordinary Node.
|
||||
|
||||
</details>
|
||||
|
||||
-----
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## Further Exploration
|
||||
|
||||
Read these when the backend contract is not enough. They move from the seam definition to the consumer and the configuration surface.
|
||||
|
||||
- [Code runtime seam](../code-runtime/README.md) — the abstract contract this backend implements.
|
||||
- [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) — how `dsh-tools` consumes `ctx.codeRuntime` and presents `run_code`.
|
||||
- [Code runtime subsystem reference](../../../docs/subsystems/code-runtime.md) — request/result vocabulary, bindings, and failure taxonomy.
|
||||
- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-code-runtime-worker-thread) — every accepted config field and its source declaration.
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## Model Experience
|
||||
|
||||
Indirectly, through Code Mode in [`dsh-tools`](../../core/tools/README.md), which renders the exact outer value when it fits or an explicit `invalid-output` / `output-limit` failure. Only the outer `run_code` result enters model context and its ordinary spill policy; binding traffic and intermediate values remain execution-local.
|
||||
Indirectly, through Code Mode in `dsh-tools`, which renders the exact outer value when it fits or an explicit `invalid-output` / `output-limit` failure, while only the outer `run_code` result enters model context under its ordinary spill policy and binding traffic plus intermediate values remain execution-local.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
@@ -46,9 +133,24 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
|
||||
These limits define when the backend is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
|
||||
|
||||
- **OS processes a program spawns survive termination** — `worker.terminate()` ends the thread only, weaker than bash-local's process-group kill; orphan cleanup is a deployment concern until a container backend exists.
|
||||
- **Type-strip rides Node's experimental `stripTypeScriptTypes` API** — amaro or sucrase are the named drop-in replacements if the relied-on behavior shifts.
|
||||
- **`computeMs` expiry can overshoot by up to one poll interval** — busy time is sampled every 25 ms (an internal constant, deliberately not config).
|
||||
- **Programs get a five-method `console` shim** (`log`/`info`/`warn`/`error`/`debug`) — deliberately not Node's full console API.
|
||||
- **Intermediate binding values have no byte cap** — a program can exhaust process or worker memory with a value that never becomes outer output.
|
||||
- **The 64 MiB default is a rejection boundary, not recoverable storage** — outer spill can save only the bounded logs and diagnostic returned after `output-limit`; bytes rejected beyond the runtime cap never reach the spill layer.
|
||||
- **The default 64 MiB cap is a rejection boundary, not recoverable storage** — outer spill can save only the bounded logs and diagnostic returned after `output-limit`; bytes rejected beyond the runtime cap never reach the spill layer.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
<details>
|
||||
<summary>Working context for maintainers — click to expand</summary>
|
||||
|
||||
None.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,54 +1,156 @@
|
||||
---
|
||||
description: "Worker 线程代码执行,供用户与维护者组合、调优或排查这个已发布的 TypeScript 后端——它在全新的 Node worker 中运行每个程序。"
|
||||
kind: "package-reference"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-code-runtime-worker-thread
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
这是 [`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.zh.md) seam 的 worker 线程实现:`WorkerThreadCodeRuntime` 会在每次运行中使用一个全新的 Node `worker_threads.Worker`,输入 TypeScript,由宿主侧剥离类型,通过消息端口桥接绑定,输出 `{ value, logs, error? }`。**这是隔离措施,而非安全边界**:其信任立场有意与 bash 等价(参见 [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md) 的 Trust posture 章节),但提供 bash 没有的隔离:独立 isolate、空环境、堆上限与强制终止。
|
||||
## 概述
|
||||
|
||||
## 配置
|
||||
`dsh-code-runtime-worker-thread` 为 [`dsh-code-runtime`](../code-runtime/README.zh.md) seam 执行 TypeScript 程序:每个程序都在一个全新的 Node Worker 线程中运行,宿主提供的绑定可作为普通异步函数调用,运行返回 `{ value, logs, error? }`。它是 `dsh-tools` 中 Code Mode 的已发布后端,因此挂载它正是让模型编写的 TypeScript 执行在组合中生效的方式。运行时「包含」程序,但不隔离它:信任立场与 bash 等价,并带有空环境、堆上限、实测忙碌时间与墙钟预算,以及强制终止。程序每次请求只运行一次,运行之间不保留状态;每个失败——语法错误、预算到期、中止、OOM 退出或输出溢出——都以结果字段返回。
|
||||
|
||||
## 目录
|
||||
|
||||
- [使用本包](#use-this-package)
|
||||
- [理解实现](#understand-the-implementation)
|
||||
- [进一步探索](#further-exploration)
|
||||
- [模型体验](#model-experience)
|
||||
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
当组合需要执行模型编写的 TypeScript 程序时,连同 code-runtime seam 一起挂载此后端;只要模型调用 `run_code`,`dsh-tools` 中的 Code Mode 就会通过 `ctx.codeRuntime` 驱动它。每个执行上限都是已验证的配置,因此你可以从 `cordis.yml` 为部署调整运行时规模。
|
||||
|
||||
### 最小配置
|
||||
|
||||
```yaml
|
||||
- id: code-runtime
|
||||
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
|
||||
- name: '@deepseek-ai/dsh-code-runtime'
|
||||
- name: '@deepseek-ai/dsh-code-runtime-worker-thread'
|
||||
config:
|
||||
computeMs: 60000 # busy-time budget (measured event-loop active time)
|
||||
maxWallMs: 600000 # wall-clock ceiling; never pauses for anything
|
||||
maxOutputBytes: 67108864 # combined serialized outer-output cap (64 MiB)
|
||||
maxOldGenerationSizeMb: 512 # worker heap cap (resourceLimits)
|
||||
computeMs: 60000 # busy-time budget (measured event-loop active time)
|
||||
maxWallMs: 600000 # wall-clock ceiling; never pauses for anything
|
||||
maxOutputBytes: 67108864 # combined serialized outer-output cap (64 MiB)
|
||||
maxOldGenerationSizeMb: 512 # worker heap cap
|
||||
```
|
||||
|
||||
每个字段都会验证并提供默认值;`maxOutputBytes` 必须是至少 4 字节的安全整数,其余字段必须是有限正数,`maxWallMs` 还必须不超过 `2147483647`(Node 的 `setTimeout` 最大延迟),此外没有其他可调项。
|
||||
| 字段 | 默认值 | 含义 |
|
||||
|---|---|---|
|
||||
| `computeMs` | `60,000` | 忙碌时间预算:worker 实测事件循环活跃时间超过该值时,运行以 `timeout` 失败 |
|
||||
| `maxWallMs` | `600,000` | 墙钟上限,为忙碌时间无法观测的等待兜底;最大 `2_147_483_647` |
|
||||
| `maxOutputBytes` | `67,108,864` | 序列化日志加完成值或失败消息的硬上限;至少 `4` |
|
||||
| `maxOldGenerationSizeMb` | `512` | worker 堆上限;溢出会杀死 worker,并以 `worker-exit` 呈现 |
|
||||
|
||||
## 设计
|
||||
每个字段在加载时都会验证并提供默认值;没有其他可调项。生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-code-runtime-worker-thread)是每个受支持字段的穷尽式真源。
|
||||
|
||||
- **每次运行使用一个全新 worker,不设池化**:程序所在的世界会随 worker 一同终止,不会留下需要记录的跨运行状态,也无法发生状态泄漏;仅凭会话日志即可重建运行。
|
||||
- **在执行上下文中,由宿主侧剥离类型**:程序会包裹在异步函数外壳中,通过 `node:module` 的 `stripTypeScriptTypes` 剥离类型(只支持可擦除语法;`enum`/namespace 会作为程序 `exception` 被拒绝,且不会启动 worker),再按字节位置切回原内容。之后程序作为 `AsyncFunction` 的函数体执行,因此顶层 `await`/`return` 可用。
|
||||
- **端口把对端视为不可信**:模型代码能够访问 `parentPort` 并伪造通信,因此任何代码读取入站消息前,系统都会验证其形状并重新构建(`null`、原始值、无效类型和格式错误的载荷会被静默丢弃;伪造的额外字段绝不会被带入);宿主对每个调用 id 最多响应一次,只将绑定名称解析为自有属性(伪造的 `constructor` 无法沿原型链访问),丢弃结算后的回复,并验证每个绑定 resolve 值与完成值是否为无损 JSON。伪造的 `log`/`done` 消息无法绕过外层上限:宿主会再次验证,并统计每条获准日志以及完成值或诊断。worker 侧命名空间使用 null-prototype 和 `defineProperty`,因此形似 `__proto__` 的绑定名称只是普通键。
|
||||
- **绑定调用被拒绝时使用的异常类属于请求数据**:可选命名空间描述符会指定构造器全局变量,以及用于接收调用失败的成员名称的自有属性。worker 会创建并注入该真实类,使 `instanceof` 生效,同时无需硬编码 `tools` 或 `ToolCallError`;全局变量无效或冲突的声明会在启动 worker 前失败。失败路径使用模块捕获的错误 intrinsic 与属性定义 intrinsic,以及 null-prototype 描述符,因此模型之后的修改无法把被拒绝的绑定变成 worker 崩溃。
|
||||
- **两个独立预算,因为对端不可信**:`computeMs` 统计 worker 实际测得的忙碌时间(轮询 `worker.performance.eventLoopUtilization()`);热循环无法借助待完成的诱饵 dispatch 隐藏,程序等待慢工具时则不累计。`maxWallMs` 为忙碌时间无法观测的情况兜底(例如等待永远不会 resolve 的 promise)。二者最终都会调用 `worker.terminate()`,连同步热循环也能终止;堆溢出会表现为 worker 的 OOM 退出(`kind: 'worker-exit'`)。`maxWallMs` 在加载时会对照 `MAX_TIMER_DELAY_MS` 做范围校验:`setTimeout` 会把更长的延迟限制为 1 ms,仅有正数校验会放行一个在第一个 tick 就到期的上限。`computeMs` 不需要这道上界,因为它对照的是实测占用率,而不是喂给定时器。
|
||||
- **中间绑定值是完整 JSON**:绑定参数与 resolve 值会接受无损 JSON 校验,并在没有字节上限的情况下传输。worker 会在执行不可信代码前捕获校验原语,因此对全局对象或原型的修改无法削弱校验或字节计量。中间值绝不会进入外层输出账本或模型上下文;上限仍来自提供方/执行器获取限制与进程/worker 内存。
|
||||
- **日志主动流入一个外层账本**:console/stdout/stderr 文本按产生顺序经端口传输,因此超时或被终止的程序仍会显示已经打印的内容。worker 会精确统计 JSON 字符串的字节数,并在发送完成值和异常诊断前,根据组合预算的剩余量预检;因此,抛出的百万字节 stack 会在 worker 边界变成固定的 `output-limit` 诊断。绕过补丁 stream 槽的原生写入会到达独立于完成端口的 pipe,因此宿主会针对这些字节和不可信伪造通信再次执行账本统计;在物化结果前,结算过程会持续进行有界 pipe 捕获,直到 worker 完成终止。`maxOutputBytes` 统计外层 `logs` 数组加完成值或失败消息载荷的 JSON 序列化;固定的 `CodeRunResult` 字段名、花括号、有界错误 kind 标签,以及后续呈现空白不计入这份可变载荷账本。未超过上限时会返回精确值;有损完成值属于 `invalid-output`,组合溢出属于 `output-limit`,不会用 inspected string 代替。失败会保留能容纳的已捕获前缀,之后按普通外层 `run_code` 落盘策略处理。
|
||||
- **空环境**:worker 使用 `env: {}` 和 `execArgv: []`,既不会获得环境变量中的凭据(比 spawn 命令的清理环境规则更严格),也不会继承 loader 标志。
|
||||
- **dispose(资源释放)时等待完全停稳**:清理会使进行中的运行以 `abort` 失败,并会等待每个 worker 退出后再完成。
|
||||
### 运行返回什么
|
||||
|
||||
## 未构建与已构建的 worker 入口
|
||||
成功的运行把程序的无损 JSON 完成值作为 `result.value` 返回,把程序打印的文本按顺序作为 `result.logs` 返回。顶层 `await`/`return` 可用,程序可以把宿主提供的绑定函数(Code Mode 暴露一个 `tools` 对象)当作普通异步调用。
|
||||
|
||||
源代码模式通过 Node 原生类型剥离加载只包含可擦除语法的 `src/worker.ts`。其传递运行时闭包只包含 Node 内置模块和相对源模块,因此全新 checkout 绝不需要兄弟工作区包尚未构建的 `lib/` 导出。worker 本地和会话自有的 JSON 边界都会在消息端口两侧展平并重建已验证值,使应用嵌套永远不会进入 structured clone。构建模式会把兄弟文件 `lib/worker.cjs` 作为文件系统路径传入,因为 pkg 的虚拟文件系统(VFS)Worker hook 要求 CommonJS;同一路径也可在普通 Node 下使用。对这个已发布入口路径进行测试的仓库级要求由[测试策略](../../../docs/testing.zh.md)规定。
|
||||
### 包含而非安全边界
|
||||
|
||||
SDK 对外提供默认及具名导出的 `WorkerThreadCodeRuntime` 类,以及 `Config`。运行所用的 `./worker` 子路径仅作为打包后的 spawn 入口存在;wire 协议与启动辅助模块是源代码私有的实现细节。
|
||||
程序运行时的权限与 bash 工具相当:它可以访问 Node API,后端也刻意不承诺与宿主的隔离。它提供的是包含——独立 isolate、空环境(没有环境变量凭据,也不继承 loader 标志)、可配置堆上限,以及也能终止同步热循环的强制终止。程序派生的 OS 进程在 `terminate()` 后仍然存活,需要部署层面的清理。
|
||||
|
||||
### 可能出什么问题
|
||||
|
||||
每个程序结果都以结果 resolve,因此失败的运行是 `result.error`,而不是 rejection:语法错误或不可擦除的 TypeScript(`enum`、namespace)在任何 worker 启动前就以 `exception` 失败;预算到期是 `timeout`;中止信号是 `abort`;堆溢出或其他 worker 终止是 `worker-exit`;不是无损 JSON 的完成值是 `invalid-output`;超出上限的序列化输出是 `output-limit`——并保留能容纳的已捕获日志前缀。reject 只表示调用方误用,例如在 dispose(资源释放)后提交运行。
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## 理解实现
|
||||
|
||||
<details>
|
||||
<summary>实现细节——点击展开</summary>
|
||||
|
||||
本节解释后端背后的设计;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
||||
|
||||
### 设计理念
|
||||
|
||||
后端建立在一个分离之上:**包含,而非安全边界**。模型代码拥有与 bash 等价的信任([Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md) 的 Trust posture),因此设计追求可重建性与有界资源使用,而非硬性的多租户边界——那需要等待容器级后端。每次运行使用一个全新的 worker,程序的世界随 worker 一同终止:不存在可泄漏、也无需记录的跨运行状态,仅凭会话日志即可重建一次运行。
|
||||
|
||||
### 执行流程
|
||||
|
||||
一次运行在宿主侧剥离类型(`node:module` 的 `stripTypeScriptTypes`,保持字节位置不变),包裹为异步函数的函数体使顶层 `await`/`return` 可用,然后发送给全新的 worker,由 bootstrap 物化绑定命名空间。绑定调用以无损 JSON 跨消息端口传递,每个调用 id 至多应答一次。日志文本主动流向宿主,因此被终止的程序仍会显示已打印的内容。恰好一个结果结算运行——`done` 帧、预算到期、中止或 worker 终止——之后宿主终止 worker 并等待其退出。
|
||||
|
||||
### 把对端视为不可信
|
||||
|
||||
模型代码能够访问 `parentPort` 并伪造通信,因此任何代码读取入站消息前,系统都会逐字段验证并重建:伪造的额外字段绝不随行,非数字的 call id 绝不会被回显进 reply,绑定名称只解析为自有属性(伪造的 `constructor` 无法沿原型链访问),垃圾被静默丢弃。worker 侧命名空间使用 null-prototype,因此形似 `__proto__` 的绑定名称只是普通键。
|
||||
|
||||
### 预算
|
||||
|
||||
存在两个独立预算,因为对端不可信:`computeMs` 计量 worker 的实测忙碌时间(每 25 ms 轮询一次 `eventLoopUtilization()`),因此热循环无论是否有诱饵 dispatch 在途都会到期,而等待慢绑定的程序不累计;`maxWallMs` 为忙碌时间无法观测的情况兜底,例如永远不会 resolve 的 promise。二者最终都会调用 `worker.terminate()`。`maxWallMs` 在加载时对照 `MAX_TIMER_DELAY_MS` 做范围校验,因为 `setTimeout` 会把更长的延迟限制为 1 ms。
|
||||
|
||||
### 输出账本
|
||||
|
||||
`maxOutputBytes` 统计外层 `logs` 数组加完成值或失败消息载荷的 JSON 序列化;固定的 `CodeRunResult` 字段名与信封语法不计入这份账本。未超过上限时返回精确值;有损完成值属于 `invalid-output`,组合溢出属于 `output-limit`,不会用 inspected string 代替。失败会保留日志中能容纳的已捕获前缀。
|
||||
|
||||
### 源码地图
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`WorkerThreadCodeRuntime`、运行编排、输出账本 |
|
||||
| [`src/worker.ts`](src/worker.ts) | 源码模式 worker 入口(可擦除 TypeScript,不依赖 `lib/`) |
|
||||
| [`src/bootstrap.ts`](src/bootstrap.ts) | worker 侧 bootstrap:命名空间物化、console shim、日志捕获 |
|
||||
| [`src/protocol.ts`](src/protocol.ts) | host 与 worker 之间的端口消息词汇 |
|
||||
| [`src/worker-json.ts`](src/worker-json.ts) | worker 侧无损 JSON 编解码 |
|
||||
| [`src/output-json.ts`](src/output-json.ts) | 外层账本的字节计量与截断 |
|
||||
| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;理由见其说明) |
|
||||
|
||||
### 未构建与已构建的 worker 入口
|
||||
|
||||
源代码模式通过 Node 原生类型剥离加载只包含可擦除语法的 `src/worker.ts`;其传递运行时闭包只包含 Node 内置模块和相对源模块,因此全新 checkout 绝不需要兄弟工作区包尚未构建的 `lib/` 导出。构建模式会把兄弟文件 `lib/worker.cjs` 作为文件系统路径传入,因为 pkg 的虚拟文件系统(VFS)Worker hook 要求 CommonJS;同一路径也可在普通 Node 下使用。
|
||||
|
||||
</details>
|
||||
|
||||
-----
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## 进一步探索
|
||||
|
||||
当后端约定不够用时阅读以下内容。它们从 seam 定义进入消费方与配置面。
|
||||
|
||||
- [代码运行时 seam](../code-runtime/README.zh.md)——此后端实现的抽象约定。
|
||||
- [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md)——`dsh-tools` 如何消费 `ctx.codeRuntime` 并呈现 `run_code`。
|
||||
- [代码运行时子系统参考](../../../docs/subsystems/code-runtime.zh.md)——请求/结果词汇、绑定与失败分类体系。
|
||||
- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-code-runtime-worker-thread)——每个受支持配置字段及其源声明。
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## 模型体验
|
||||
|
||||
通过 [`dsh-tools`](../../core/tools/README.zh.md) 中的 Code Mode 间接提供;如果外层值能容纳则原样渲染,否则返回明确的 `invalid-output`/`output-limit` 失败。只有外层 `run_code` 结果进入模型上下文并使用普通落盘策略;绑定通信与中间值始终只存在于执行环境中。
|
||||
通过 `dsh-tools` 中的 Code Mode 间接提供,如果外层值能容纳则原样渲染,否则返回明确的 `invalid-output`/`output-limit` 失败,且只有外层 `run_code` 结果在其普通落盘策略下进入模型上下文,绑定通信与中间值始终只存在于执行环境中。
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
不会直接失效;由上述消费方负责请求前缀变更。
|
||||
|
||||
## 已知限制与暂缓事项
|
||||
## 已知限制与延期工作
|
||||
|
||||
- **程序派生的 OS 进程在程序终止后仍会存活**:`worker.terminate()` 只结束线程,比 bash-local 的进程组终止更弱;在容器后端出现前,孤儿进程清理属于部署职责。
|
||||
- **类型剥离依赖 Node 的实验性 `stripTypeScriptTypes` API**:如依赖的行为发生变化,amaro 或 sucrase 是已经点名的直接替代品。
|
||||
- **`computeMs` 到期最多可能超过一个轮询间隔**:系统每 25 ms 采样一次忙碌时间(内部常量,有意不做成配置)。
|
||||
- **程序获得一个含 5 个方法的 `console` shim**(`log`/`info`/`warn`/`error`/`debug`):有意不提供 Node 的完整 console 接口。
|
||||
- **中间绑定值没有字节上限**:程序可以用永远不会成为外层输出的值耗尽进程或 worker 内存。
|
||||
- **默认 64 MiB 是拒绝边界,不是可恢复存储**:外层落盘只能保存发生 `output-limit` 后返回的有界日志和诊断;在运行时上限之外被拒绝的字节永远不会到达落盘层。
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
|
||||
这些限制说明此后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
||||
|
||||
- **程序派生的 OS 进程在程序终止后仍会存活**——`worker.terminate()` 只结束线程,比 bash-local 的进程组终止更弱;在容器后端出现前,孤儿进程清理属于部署职责。
|
||||
- **类型剥离依赖 Node 的实验性 `stripTypeScriptTypes` API**——如依赖的行为发生变化,amaro 或 sucrase 是已经点名的直接替代品。
|
||||
- **`computeMs` 到期最多可能超过一个轮询间隔**——系统每 25 ms 采样一次忙碌时间(内部常量,有意不做成配置)。
|
||||
- **程序获得一个含 5 个方法的 `console` shim**(`log`/`info`/`warn`/`error`/`debug`)——有意不提供 Node 的完整 console 接口。
|
||||
- **中间绑定值没有字节上限**——程序可以用永远不会成为外层输出的值耗尽进程或 worker 内存。
|
||||
- **默认 64 MiB 上限是拒绝边界,不是可恢复存储**——外层落盘只能保存发生 `output-limit` 后返回的有界日志和诊断;在运行时上限之外被拒绝的字节永远不会到达落盘层。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
<details>
|
||||
<summary>维护者的工作上下文——点击展开</summary>
|
||||
|
||||
无。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime/README.md
|
||||
README.md: ee64f7a16295bcf31e40eb305ca35aa774dac8d2
|
||||
README.zh.md: a506f1d8b207221d508a62c0c7a7d36b971b0a8d
|
||||
README.md: 7cb07be0871bd585d40f9331a12827c92f4b87c7
|
||||
README.zh.md: 4881256012d0208707ec2ec2dbe3ba4391d587cf
|
||||
|
||||
@@ -1,27 +1,110 @@
|
||||
---
|
||||
description: "Abstract code-execution seam (`ctx.codeRuntime`) for users and maintainers composing, consuming, or building a backend that runs one model-written program against host-provided bindings."
|
||||
kind: "package-reference"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-code-runtime
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
The **`CodeRuntime`** (`ctx.codeRuntime`) defines 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.
|
||||
## Summary
|
||||
|
||||
This package owns the Service Definition role of the capability (the bash trio is the template — see [capability seams](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)): providers 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 Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md), whose first provider 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.
|
||||
`dsh-code-runtime` defines what a code runtime does: run one model-written program against a set of host-provided async functions and report `{ value, logs, error? }` — without dictating how any backend implements it. Load it in a composition with a backend and the service is available as `ctx.codeRuntime`; Code Mode in `dsh-tools` then runs model-written programs that compose tools. Every request runs once with no state carried between runs, and every program outcome — including failures — resolves as a result field rather than a rejection. The runtime knows nothing about tools or sessions: it is handed a program and named bindings, and everything tool-shaped stays with the consumer.
|
||||
|
||||
## Service API (`ctx.codeRuntime`)
|
||||
## Table of Contents
|
||||
|
||||
| Member | Semantics |
|
||||
- [Use this package](#use-this-package)
|
||||
- [Understand the implementation](#understand-the-implementation)
|
||||
- [Further Exploration](#further-exploration)
|
||||
- [Model Experience](#model-experience)
|
||||
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
Choose this package when you compose a deployment that executes model-written programs, consume `ctx.codeRuntime` directly, or build a backend that runs programs. In the shipped composition, Code Mode in `dsh-tools` is the consumer: only what the program printed and returned re-enters the conversation.
|
||||
|
||||
### Run a program
|
||||
|
||||
Give the runtime a program source and one or more binding namespaces. Each namespace becomes one global object of async functions inside the program — Code Mode passes one under `tools`. The program runs as the body of an async function, so top-level `await` and `return` work; a lossless-JSON completion becomes `result.value`, emitted text arrives in order as `result.logs`, and any failure is reported in `result.error` with a kind you can branch on. The runtime never rejects for a program failure — rejection means you misused the seam, for example by submitting a run after disposal.
|
||||
|
||||
```text
|
||||
const result = await ctx.codeRuntime.run({
|
||||
program: 'return await tools.add({ a: 1, b: 2 })',
|
||||
bindings: [{ global: 'tools', functions: { add: async (args) => args.a + args.b } }],
|
||||
})
|
||||
// result.value === 3
|
||||
```
|
||||
|
||||
### Choose a backend
|
||||
|
||||
Backends declare two descriptors you can rely on: `language` — what the program must be written in, with `'typescript'` and `'python'` as the well-known values and only TypeScript shipped — and `isolation` — the execution substrate (`'worker-thread'`, `'process'`, `'container'`), a label for deployments and diagnostics, not a security claim. The shipped backend is [`dsh-code-runtime-worker-thread`](../code-runtime-worker-thread/README.md), which executes TypeScript in a fresh Node worker thread; [`dsh-code-runtime-python`](../code-runtime-python/README.md) owns the wire protocol for the CPython backend.
|
||||
|
||||
### Name your bindings portably
|
||||
|
||||
Binding-global and error-class names are language-portable: they must match `[A-Za-z_][A-Za-z0-9_]*`, avoid every portable target language's reserved words, and avoid backend-owned slots, so one namespace list is valid against every backend. A name like `$tools`, `lambda`, or `console` fails the run before it starts; the exact exclusion sets are part of the seam contract.
|
||||
|
||||
### What can go wrong
|
||||
|
||||
Failures arrive as `result.error` with an orthogonal `kind`: the program threw or failed to parse (`exception`), a budget expired (`timeout`), the run was aborted (`abort`), the execution substrate died (`worker-exit`), the completion value was not lossless JSON (`invalid-output`), or the serialized output exceeded the cap (`output-limit`). Each kind carries a model-feedable message. `run()` rejects only for seam misuse, such as a run submitted after disposal or a binding name that fails the portable-identifier rules.
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## Understand the implementation
|
||||
|
||||
<details>
|
||||
<summary>Implementation internals — click to expand</summary>
|
||||
|
||||
This section explains the design behind the seam; observable behavior is fully covered in [Use this package](#use-this-package).
|
||||
|
||||
### Design concept
|
||||
|
||||
The package is the Service Definition role of the code-execution capability seam ([capability seams](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)): an abstract `CodeRuntime extends Service` registered as `ctx.codeRuntime`, plus the vocabulary both backends and the consumer share. Providers subclass `CodeRuntime`, implement `run`, and register the service; the consumer (Code Mode in `dsh-tools`) generates the model-facing SDK and bridges tool dispatch. The runtime stays ignorant of tools and sessions by contract: it receives a program and named async bindings and returns `{ value, logs, error? }`.
|
||||
|
||||
### Service API
|
||||
|
||||
The contract is three members a backend implements: `run(request)` executes one program against the request's bindings and resolves every program outcome — parse/transform failure, thrown exception, invalid completion, output overflow, budget expiry, abort, or substrate death — as a result `error` field, with rejection reserved for caller misuse such as a run submitted after disposal; `language` and `isolation` are read-only descriptors labeling the source language and execution substrate for deployments and diagnostics.
|
||||
|
||||
The exhaustive semantics live in the [code runtime subsystem reference](../../../docs/subsystems/code-runtime.md); the exact signatures are in [`src/index.ts`](src/index.ts).
|
||||
|
||||
### Vocabulary
|
||||
|
||||
`CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on; defaulting (time budgets, output caps) is each provider's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions` + optional `errorClass`), each exposed to the program as one global object of async callables returning `CodeJsonValue` — the seam's structural lossless-JSON type. An `errorClass` descriptor names a real program-global constructor and the own property that receives the rejected member name, so backends never learn consumer terms such as `ToolCallError`. `CodeRunResult` reports the lossless-JSON completion `value?`, ordered `logs: string[]`, and `error?` (`CodeRunFailure`: orthogonal `kind` + model-feedable `message`). See `src/types.ts` for the full contracts.
|
||||
|
||||
### Portable identifiers
|
||||
|
||||
Binding-global and error-class names are language-portable: they must match the identifier subset `[A-Za-z_][A-Za-z0-9_]*` (no JS-only `$`) and clear the seam-exported exclusion sets, so one `bindings` list is valid against every backend. The package exports the contract every backend enforces — `PORTABLE_RESERVED_WORDS` (ECMAScript ∪ Python reserved words), `RESERVED_BINDING_GLOBALS` (backend-owned globals such as `console` and `__dsh_main__`), `RESERVED_ERROR_MEMBERS` and `DUNDER_MEMBER` (error-member exclusions) — so a name like `$tools`, `lambda`, or `__dsh_main__` makes `run()` reject as seam misuse on any backend. See `src/index.ts` for the exact sets.
|
||||
|
||||
### Source map
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `run(request)` | Execute one program against the request's bindings. **Resolves with an error FIELD for every program outcome** — parse/transform failure, thrown exception, invalid completion, output overflow, budget expiry, abort, or substrate death (`CodeRunFailure`'s orthogonal `kind` taxonomy); it rejects only for caller misuse of the Service Definition contract (e.g. a run submitted after disposal). The program runs as the body of an async function: top-level `await`/`return` work, and a lossless JSON completion becomes `result.value`. |
|
||||
| `language` | Readonly descriptor: the source language `run` expects. `'typescript'` and `'python'` are the well-known values — those `dsh-tools` presents; only `'typescript'` has a published backend. 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**. |
|
||||
| [`src/index.ts`](src/index.ts) | Plugin entry: abstract `CodeRuntime` service and the portable-identifier exclusion sets |
|
||||
| [`src/types.ts`](src/types.ts) | Vocabulary: `CodeRunRequest`, `CodeBindingNamespace`, `CodeJsonValue`, `CodeRunResult`, `CodeRunFailure` |
|
||||
| [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; the seam registers no mutable data relation) |
|
||||
|
||||
Semantics every implementation must honor (contract details in the class JSDoc): binding calls bridge complete lossless-JSON arguments and resolutions with no seam-level byte cap; 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.
|
||||
</details>
|
||||
|
||||
## Vocabulary
|
||||
-----
|
||||
|
||||
`CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets and outer-output cap) is the provider's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions` + optional `errorClass`), each exposed to the program as one global object of async callables returning `CodeJsonValue`, the service-local structural equivalent of canonical `JsonValue` that keeps this Service Definition package independent of sessions. An `errorClass` descriptor names a real program-global constructor and the own property that receives the rejected member name; runtimes remain independent of Consumer terms such as `ToolCallError`. `CodeRunResult` reports the lossless JSON completion `value?`, ordered `logs: string[]`, and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts.
|
||||
<a id="further-exploration"></a>
|
||||
## Further Exploration
|
||||
|
||||
Binding-global and error-class names are **language-portable**: they must match the identifier subset `[A-Za-z_][A-Za-z0-9_]*` (no JS-only `$`) and clear the seam-exported exclusion sets, so one `bindings` list is valid against every backend regardless of its `language`. The package exports the contract every backend enforces — `PORTABLE_RESERVED_WORDS` (ECMAScript ∪ Python reserved words), `RESERVED_BINDING_GLOBALS` (backend-owned globals such as `console`), `RESERVED_ERROR_MEMBERS` and `DUNDER_MEMBER` (error-member exclusions) — so a name like `$tools`, `lambda`, or `__dsh_main__` makes `run()` reject as seam misuse on any backend, not just some. See `src/index.ts` for the exact sets and rationale.
|
||||
Read these when the package-level contract is not enough. They move from the Code Mode consumer to the shipped backends and the capability-seam model.
|
||||
|
||||
- [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) — how the tool registry consumes `ctx.codeRuntime` and presents `run_code` to the model.
|
||||
- [Worker-thread backend](../code-runtime-worker-thread/README.md) — the shipped TypeScript execution backend.
|
||||
- [Python protocol package](../code-runtime-python/README.md) — the wire protocol for the CPython backend.
|
||||
- [Code runtime subsystem reference](../../../docs/subsystems/code-runtime.md) — request/result vocabulary, bindings, and the `ctx.codeRuntime` cordis surface.
|
||||
- [Capability seams](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) — the Service Definition / Service Provider / Consumer split.
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## Model Experience
|
||||
|
||||
Indirectly, through Code Mode in `dsh-tools`, which exposes `run_code` and returns program logs, values, or failures as retained tool-result tokens.
|
||||
@@ -32,7 +115,30 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
|
||||
These limits define what the seam cannot do; they are current package constraints, not a task backlog.
|
||||
|
||||
- **`run()` is one-shot** — `logs` arrive only on the resolved `CodeRunResult`; the seam exposes no streaming-log or progress API for a live program's output.
|
||||
- **A persistent REPL-style kernel is recorded future work** — the no-state-between-runs contract stands until a persistent-kernel backend brings its own logging story ([Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)).
|
||||
- **Only the worker-thread backend ships** — `'process'`/`'container'` are declared well-known `isolation` values with no implementation; a hard security boundary awaits a container backend.
|
||||
- **Intermediate binding values have no byte cap** — implementations remain subject to structured-clone cost and process memory, while a provider or executor may already have imposed its own acquisition bound.
|
||||
- **No state survives between runs** — every request runs against a fresh world; a persistent REPL-style kernel is deferred until a backend brings its own logging story.
|
||||
- **Only the worker-thread backend ships** — `'process'` and `'container'` are declared well-known `isolation` values with no implementation; a hard security boundary awaits a container backend.
|
||||
- **Intermediate binding values have no byte cap** — implementations remain subject to structured-clone cost and process memory, while a provider may already impose its own acquisition bound.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
<details>
|
||||
<summary>Working context for maintainers — click to expand</summary>
|
||||
|
||||
This Dev Note is working context for maintainers: undecided directions and open questions. It is explicitly non-authoritative — shipped behavior and limits live in the sections above and the package code.
|
||||
|
||||
#### Future: persistent kernel backend
|
||||
|
||||
A REPL-style kernel that keeps state across `run_code` calls remains undecided; it would need its own logging story, because the no-state-between-runs contract is what keeps every request reconstructable from the session log alone.
|
||||
|
||||
#### Future: container backend
|
||||
|
||||
A container-class backend would provide a hard multi-tenant boundary for both code and shell execution; nothing is decided beyond the well-known `isolation` value.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,27 +1,110 @@
|
||||
---
|
||||
description: "抽象代码执行 seam(`ctx.codeRuntime`),供用户与维护者组合、消费或构建后端,以针对宿主提供的绑定运行一段模型编写的程序。"
|
||||
kind: "package-reference"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-code-runtime
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
**`CodeRuntime`**(`ctx.codeRuntime`)定义代码运行时做什么,即针对宿主提供的一组异步绑定运行一段模型编写的程序,并报告 `{ value, logs, error? }`,而不规定如何实现。
|
||||
## 概述
|
||||
|
||||
此包承担该能力的 Service Definition 角色(以 bash 三包结构为模板,参见[能力 seam](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)):提供方通过继承 `CodeRuntime` 并注册服务接入;Consumer 是工具注册表的 Code Mode,它生成面向模型的 SDK,并桥接工具分发。这两项职责均由 [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md) 规定,首个提供方是 Node worker 线程后端。运行时不了解工具或会话:调用方只向它提供具名异步函数与程序字符串;所有与工具有关的内容都留在 Consumer。
|
||||
`dsh-code-runtime` 定义代码运行时做什么:针对一组宿主提供的异步函数运行一段模型编写的程序,并报告 `{ value, logs, error? }`——不规定任何后端如何实现。在组合中与一个后端一起加载它,服务即可作为 `ctx.codeRuntime` 使用;随后 `dsh-tools` 中的 Code Mode 即可运行组合工具的模型程序。每次请求只运行一次,运行之间不保留状态;每个程序结果——包括失败——都以结果字段 resolve,而不是 reject。运行时不了解工具或会话:调用方只向它提供程序与具名绑定,所有与工具有关的内容都留在 Consumer。
|
||||
|
||||
## 服务 API(`ctx.codeRuntime`)
|
||||
## 目录
|
||||
|
||||
| 成员 | 语义 |
|
||||
- [使用本包](#use-this-package)
|
||||
- [理解实现](#understand-the-implementation)
|
||||
- [进一步探索](#further-exploration)
|
||||
- [模型体验](#model-experience)
|
||||
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
当你要组合一个执行模型程序的部署、直接消费 `ctx.codeRuntime`,或构建运行程序的后端时,选择本包。在已发布的组合中,`dsh-tools` 里的 Code Mode 是消费方:只有程序打印和返回的内容重新进入对话。
|
||||
|
||||
### 运行一个程序
|
||||
|
||||
向运行时提供程序源码与一个或多个绑定命名空间。每个命名空间会成为程序内的一个全局异步函数对象——Code Mode 在 `tools` 下传入一个。程序作为异步函数的函数体运行,因此顶层 `await`/`return` 可用;无损 JSON 完成值成为 `result.value`,输出的文本按顺序进入 `result.logs`,任何失败都以 `result.error` 报告并带有可分支的 kind。运行时绝不会因程序失败而 reject——reject 意味着你误用了 seam,例如在 dispose(资源释放)后提交运行。
|
||||
|
||||
```text
|
||||
const result = await ctx.codeRuntime.run({
|
||||
program: 'return await tools.add({ a: 1, b: 2 })',
|
||||
bindings: [{ global: 'tools', functions: { add: async (args) => args.a + args.b } }],
|
||||
})
|
||||
// result.value === 3
|
||||
```
|
||||
|
||||
### 选择后端
|
||||
|
||||
后端声明两个你可以依赖的描述符:`language`——程序必须使用的源语言,已知值为 `'typescript'` 与 `'python'`,目前只有 TypeScript 已发布——以及 `isolation`——执行基底(`'worker-thread'`、`'process'`、`'container'`),仅供部署与诊断使用,不构成安全声明。已发布的后端是 [`dsh-code-runtime-worker-thread`](../code-runtime-worker-thread/README.zh.md),在全新的 Node Worker 线程中执行 TypeScript;[`dsh-code-runtime-python`](../code-runtime-python/README.zh.md) 持有 CPython 后端的协议格式(wire protocol)。
|
||||
|
||||
### 可移植地命名绑定
|
||||
|
||||
binding-global 与 error-class 名称是语言可移植的:必须匹配 `[A-Za-z_][A-Za-z0-9_]*`,避开每个可移植目标语言的保留字,并避开后端拥有的槽位,因此同一份命名空间列表对每个后端都有效。`$tools`、`lambda` 或 `console` 之类的名称会在运行开始前失败;确切的排除集是 seam 约定的一部分。
|
||||
|
||||
### 可能出什么问题
|
||||
|
||||
失败以 `result.error` 返回,并带正交的 `kind`:程序抛出或解析失败(`exception`)、预算到期(`timeout`)、运行被中止(`abort`)、执行基底终止(`worker-exit`)、完成值不是无损 JSON(`invalid-output`),或序列化输出超过上限(`output-limit`)。每种 kind 都带一条可反馈给模型的消息。`run()` 只在 seam 误用时 reject,例如在 dispose 后提交运行,或绑定名称不符合可移植标识符规则。
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## 理解实现
|
||||
|
||||
<details>
|
||||
<summary>实现细节——点击展开</summary>
|
||||
|
||||
本节解释 seam 背后的设计;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
||||
|
||||
### 设计理念
|
||||
|
||||
本包是代码执行能力 seam 的 Service Definition 角色([能力 seam](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)):一个注册为 `ctx.codeRuntime` 的抽象 `CodeRuntime extends Service`,加上两个后端与消费方共享的词汇。提供方继承 `CodeRuntime`、实现 `run` 并注册服务;消费方(`dsh-tools` 中的 Code Mode)生成面向模型的 SDK 并桥接工具分发。按约定,运行时不了解工具与会话:它接收程序与具名异步绑定,返回 `{ value, logs, error? }`。
|
||||
|
||||
### 服务 API
|
||||
|
||||
约定是后端实现的三个成员:`run(request)` 针对请求的绑定执行一段程序,并把每个程序结果——解析/转换失败、抛出异常、无效完成值、输出溢出、预算到期、中止或基底终止——都作为结果 `error` 字段 resolve,reject 只留给调用方误用,例如在 dispose 后提交运行;`language` 与 `isolation` 是只读描述符,为部署与诊断标注源语言与执行基底。
|
||||
|
||||
穷尽式语义见[代码运行时子系统参考](../../../docs/subsystems/code-runtime.zh.md);确切签名见 [`src/index.ts`](src/index.ts)。
|
||||
|
||||
### 词汇
|
||||
|
||||
`CodeRunRequest`(`program`、`bindings`、`signal?`)携带运行时操作所需的全部内容;默认值(时间预算、输出上限)来自各提供方的已验证配置,绝不是 `run()` 内部隐藏的 `??`。`bindings` 是 `CodeBindingNamespace` 列表(`global` + `functions` + 可选 `errorClass`),每个命名空间作为程序内的一个全局异步可调用函数对象公开,返回 `CodeJsonValue`——seam 的结构性无损 JSON 类型。`errorClass` 描述符点名真实的程序全局构造器,以及用于接收被拒绝成员名称的自有属性,因此后端永远不会得知 `ToolCallError` 之类的 Consumer 术语。`CodeRunResult` 报告无损 JSON 完成值 `value?`、有序的 `logs: string[]` 和 `error?`(`CodeRunFailure`:正交 `kind` + 可反馈给模型的 `message`)。完整约定见 `src/types.ts`。
|
||||
|
||||
### 可移植标识符
|
||||
|
||||
binding-global 与 error-class 名称是语言可移植的:必须匹配标识符子集 `[A-Za-z_][A-Za-z0-9_]*`(不含 JS 专有的 `$`)并通过 seam 导出的排除集,因此同一份 `bindings` 列表对每个后端都有效。本包导出每个后端都执行的约定——`PORTABLE_RESERVED_WORDS`(ECMAScript ∪ Python 保留字)、`RESERVED_BINDING_GLOBALS`(如 `console`、`__dsh_main__` 等后端拥有的 global)、`RESERVED_ERROR_MEMBERS` 与 `DUNDER_MEMBER`(error-member 排除)——因此 `$tools`、`lambda` 或 `__dsh_main__` 之类的名称会让 `run()` 在任何后端上作为 seam 误用而 reject。确切集合见 `src/index.ts`。
|
||||
|
||||
### 源码地图
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `run(request)` | 针对请求的绑定执行一段程序。**所有程序失败结果都通过 resolve 结果中的 error 字段报告**:包括解析/转换失败、抛出异常、无效完成值、输出溢出、预算到期、中止或执行基底终止(由 `CodeRunFailure` 的正交 `kind` 分类表示);只有调用方误用 Service Definition 约定时才 reject(例如 dispose(资源释放)后仍提交运行)。程序作为异步函数的函数体运行,因此顶层 `await`/`return` 可用,无损 JSON 完成值会成为 `result.value`。 |
|
||||
| `language` | 只读描述符:`run` 期望的源语言。已知值为 `'typescript'` 与 `'python'`——`dsh-tools` 能呈现的那些;其中只有 `'typescript'` 有已发布的后端。仅供参考,不作门禁;生成语言专用呈现的消费方会根据该值选择分支,遇到无法呈现的语言时明确失败。 |
|
||||
| `isolation` | 只读描述符:执行基底(`'worker-thread'`、`'process'`、`'container'`)。供部署与诊断使用,**不构成安全声明**。 |
|
||||
| [`src/index.ts`](src/index.ts) | 插件入口:抽象 `CodeRuntime` 服务与可移植标识符排除集 |
|
||||
| [`src/types.ts`](src/types.ts) | 词汇:`CodeRunRequest`、`CodeBindingNamespace`、`CodeJsonValue`、`CodeRunResult`、`CodeRunFailure` |
|
||||
| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;seam 不注册任何可变数据关系) |
|
||||
|
||||
每个实现都必须遵守以下语义(完整约定见类 JSDoc):绑定调用会桥接完整的无损 JSON 参数与 resolve 值,seam 层不设字节上限;程序被视为敌对对等方(任意绑定名称都会成为自有属性,格式错误的通信绝不能使宿主崩溃);不同运行之间不保留任何状态;dispose 会终止进行中的运行,并且在完成前等待其退出。
|
||||
</details>
|
||||
|
||||
## 词汇
|
||||
-----
|
||||
|
||||
`CodeRunRequest`(`program`、`bindings`、`signal?`)携带运行时操作所需的全部内容;默认值解析(时间预算与外层输出上限)属于提供方的已验证配置,绝不能是隐藏的 `??`,更不能藏在 `run()` 内部。`bindings` 是 `CodeBindingNamespace` 列表(`global` + `functions` + 可选 `errorClass`);每个命名空间会作为一个由异步可调用函数组成的全局对象公开给程序,这些函数返回 `CodeJsonValue`。后者是服务本地、与规范 `JsonValue` 结构等价的类型,使 Service Definition 包保持独立于会话。`errorClass` 描述符点名真实的程序全局构造器,以及用于接收被拒绝成员名称的自有属性;运行时不依赖 `ToolCallError` 等 Consumer 术语。`CodeRunResult` 报告无损 JSON 完成值 `value?`、有序的 `logs: string[]` 和 `error?`(`CodeRunFailure`:`kind` + 可反馈给模型的 `message`)。完整约定见 `src/types.ts`。
|
||||
<a id="further-exploration"></a>
|
||||
## 进一步探索
|
||||
|
||||
binding-global 与 error-class 名称是**语言可移植**的:必须匹配标识符子集 `[A-Za-z_][A-Za-z0-9_]*`(不含 JS 专有的 `$`)并通过 seam 导出的排除集,因此同一份 `bindings` 列表对每个后端都有效,无论其 `language` 为何。本包导出每个后端都执行的约定——`PORTABLE_RESERVED_WORDS`(ECMAScript ∪ Python 保留字)、`RESERVED_BINDING_GLOBALS`(如 `console` 等后端拥有的 global)、`RESERVED_ERROR_MEMBERS` 与 `DUNDER_MEMBER`(error-member 排除)——因此 `$tools`、`lambda`、`__dsh_main__` 之类的名称会让 `run()` 在任何后端上作为 seam 误用而 reject,而非只在某些后端。确切集合与理由见 `src/index.ts`。
|
||||
当包级约定不够用时阅读以下内容。它们从 Code Mode 消费方进入已发布的后端与能力 seam 模型。
|
||||
|
||||
- [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md)——工具注册表如何消费 `ctx.codeRuntime` 并把 `run_code` 呈现给模型。
|
||||
- [Worker 线程后端](../code-runtime-worker-thread/README.zh.md)——已发布的 TypeScript 执行后端。
|
||||
- [Python 协议包](../code-runtime-python/README.zh.md)——CPython 后端的协议格式。
|
||||
- [代码运行时子系统参考](../../../docs/subsystems/code-runtime.zh.md)——请求/结果词汇、绑定与 `ctx.codeRuntime` 的 cordis 接口面。
|
||||
- [能力 seam](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)——Service Definition / Service Provider / Consumer 拆分。
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## 模型体验
|
||||
|
||||
通过 `dsh-tools` 中的 Code Mode 间接提供;后者公开 `run_code`,并将程序日志、值或失败作为保留的工具结果 token 返回。
|
||||
@@ -30,9 +113,32 @@ binding-global 与 error-class 名称是**语言可移植**的:必须匹配标
|
||||
|
||||
不会直接失效;由上述消费方负责请求前缀变更。
|
||||
|
||||
## 已知限制与暂缓事项
|
||||
## 已知限制与延期工作
|
||||
|
||||
- **`run()` 是一次性的**:`logs` 只有在 `CodeRunResult` resolve 后才能获得;seam 不提供正在运行的程序所产生输出的流式日志或进度接口。
|
||||
- **持久 REPL 风格内核已记录为未来工作**:在持久内核后端带来自己的日志方案前,运行之间不保留状态的约定继续有效(参见 [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md))。
|
||||
- **目前只提供 worker 线程后端**:`'process'`/`'container'` 是已经声明但没有实现的已知 `isolation` 值;强安全边界需要等待容器后端。
|
||||
- **中间绑定值没有字节上限**:实现仍受 structured-clone 成本与进程内存约束,而提供方或执行器可能已经应用自己的获取上限。
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
|
||||
这些限制说明 seam 不能做什么;它们是当前包约束,不是任务积压。
|
||||
|
||||
- **`run()` 是一次性的**——`logs` 只有在 `CodeRunResult` resolve 后才能获得;seam 不提供正在运行的程序所产生输出的流式日志或进度接口。
|
||||
- **运行之间不保留状态**——每次请求都在全新环境中运行;持久 REPL 风格内核在某个后端带来自己的日志方案之前保持延期。
|
||||
- **目前只发布 worker 线程后端**——`'process'` 与 `'container'` 是已经声明但没有实现的已知 `isolation` 值;强安全边界需要等待容器后端。
|
||||
- **中间绑定值没有字节上限**——实现仍受 structured-clone 成本与进程内存约束,而提供方可能已经应用自己的获取上限。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
<details>
|
||||
<summary>维护者的工作上下文——点击展开</summary>
|
||||
|
||||
本开发备注是维护者的工作上下文:尚未决定的方向与开放问题。它明确不具权威性——已交付的行为与限制以上文和包代码为准。
|
||||
|
||||
#### 未来:持久内核后端
|
||||
|
||||
跨 `run_code` 调用保留状态的 REPL 风格内核仍未决定;它需要自己的日志方案,因为「运行之间不保留状态」的约定正是让每次请求仅凭会话日志即可重建的原因。
|
||||
|
||||
#### 未来:容器后端
|
||||
|
||||
容器级后端将为代码与 shell 执行都提供硬性的多租户边界;除已知的 `isolation` 值外,暂无任何决定。
|
||||
|
||||
</details>
|
||||
|
||||
Reference in New Issue
Block a user