Merge branch 'master' into feat/read-image-context
# Conflicts: # docs/module-graph.i18n.yaml # docs/module-graph.md # docs/module-graph.zh.md
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/core/tools/README.md
|
||||
README.md: f3d1b4741c7fde64669794d079c36a18e633c0c1
|
||||
README.zh.md: d3054372095ef0cfdabc6cf80e0faa41a3b12d4c
|
||||
README.md: 2c9833c3505c765283559590c8bc28b3c2077e2e
|
||||
README.zh.md: d7766b432c5a319d214da80e3df438489519be92
|
||||
|
||||
@@ -36,7 +36,7 @@ Cancellation is cooperative and quiescent. Every typed invocation supplies a cal
|
||||
|
||||
### Live events
|
||||
|
||||
The live registry pipeline has three transformable waterfalls, then the definition-owned content finalizer, then the observe-only `tools/result` boundary; registry changes are deliberately unfiltered shared-state notifications. Exact signatures, dispatch modes, scope filtering, and failure-containment contracts live in the generated region of [tools.md](../../../docs/subsystems/tools.md#cordis-surface), while the complete ordering is visualized in the generated [tool execution pipeline](../../../docs/tool-execution-pipeline.md). `tools/result` is live; the similarly named `tool/result` is the durable session event the agent loop appends afterwards.
|
||||
The live registry pipeline has three transformable waterfalls, then the definition-owned content finalizer, then the observe-only `tools/result` event; registry changes are deliberately unfiltered shared-state notifications. Exact signatures, dispatch modes, scope filtering, and failure containment contracts live in the generated region of [tools.md](../../../docs/subsystems/tools.md#cordis-surface), while the complete ordering is visualized in the generated [tool execution pipeline](../../../docs/tool-execution-pipeline.md). `tools/result` is live; the similarly named `tool/result` is the durable session event the agent loop appends afterwards.
|
||||
|
||||
### Key types
|
||||
|
||||
@@ -120,7 +120,7 @@ Under `code` or `both`, the registry exposes the reserved `run_code` transport a
|
||||
- **The SDK section** (`tools:sdk`, order 150): a lazy prompt section regenerating the language-appropriate SDK text at each assembly. In the TypeScript flavor it emits `JsonValue`, exact `ToolArgsMap` / `ToolOutputMap`, `ToolName`, the `ToolCallError` declaration, and a mapped `tools` namespace for the calling scope's visible end capabilities (exotic names via quoted keys), plus fixed usage instructions; the Python flavor (`ctx.codeRuntime.language === 'python'`) emits the equivalent named `TypedDict`s and a `tools` object with matching usage instructions. Deterministic — lexicographic tool order, byte-identical text for an unchanged tool set (prefix-cache-friendly). Both codegens are exported and never throw during prompt assembly: `jsonSchemaToTs` handles every unified schema construct and degrades unsupported raw constructs to `unknown`; `jsonSchemaToPy` does the same, degrading to `Any` (and a whole object to `dict[str, Any]` when a field name is not a legal `TypedDict` attribute, or whenever it is called outside the SDK render, which supplies the naming context a `TypedDict` declaration needs).
|
||||
- **The dispatch bridge** (`run_code`'s execute): every binding call is snapshotted as lossless JSON before dispatch (`undefined`, `BigInt`, cycles, sparse arrays, `-0`, and exotic objects reject that one call), scheduled through a per-run pool that reuses the native concurrency contract — calls start strictly in submission order, consecutive `isConcurrencySafe` calls overlap up to the validated `maxParallelSubCalls` config (default 10; `1` restores serial dispatch), and an exclusive-classified call drains the pool, runs alone, and bars later calls — given the outer execution's opaque token as `parent`, and run through the complete pre-execute → guards → execute → post-execute → result pipeline. A success returns the final canonical value after policy; a failure reaches the worker as one message and becomes `ToolCallError(toolName, message)`. Each started sub-call logs a `tool/code-dispatch-start` event (deterministic id `<parent>:code:<n>`, numbered by submission) at pipeline entry and settles with one `tool/code-dispatch` event carrying the complete model-facing `content`/`isError` outcome (the `tool/result` vocabulary, so UIs render sub-calls through the native path — the pair's `time` fields carry per-sub-call timing); a queued call abandoned by run settlement logs neither. `deriveMessages()` surfaces neither event nor persists the canonical value. Token correlation lets commit-style observers defer an inner success until the final `run_code` result without exposing the live outer execution; ordinary tool side effects are not rolled back. Every sub-call `additionalContexts` entry is deferred through the outer `ToolRunContext` in dispatch order; the loop appends those contexts only after the parent `run_code` result, preserving adjacency and retaining each source/meta even when the program later fails.
|
||||
- **Settlement discipline**: the bridge owns a run-scoped abort that follows the outer signal in and fires when the run settles for any reason, so a budget expiry aborts an in-flight sub-tool instead of orphaning it; the bridge then drains its queue BEFORE returning, so every `tool/code-dispatch` lands inside the open turn. A failed run throws `CodeRunFailedError` (`code: 'CODE_RUN_FAILED'`, message = the failure kind + captured logs), which the pipeline converts to a structured `isError` the model self-corrects from.
|
||||
- **Result boundary**: intermediate binding values cross the worker boundary whole and have no per-binding byte cap. `run_code` returns canonical `{ logs: string[], result?: JsonValue }`; strings render raw, every other present JSON root renders through a stack-safe pretty JSON traversal whose total indentation is capped at ten characters (deeper subtrees stay compact), `null` remains explicit, and absent `result` means the program returned `undefined`. The worker's configurable `maxOutputBytes` (default 64 MiB) applies only to the combined serialized outer log-array, completion-value, or failure-message payloads; fixed result-envelope syntax and presentation whitespace are outside that ledger. Invalid and over-limit completions fail explicitly, and only this outer result is eligible for ordinary spill.
|
||||
- **Result size**: intermediate binding values cross the worker process whole and have no per-binding byte cap. `run_code` returns canonical `{ logs: string[], result?: JsonValue }`; strings render raw, every other present JSON root renders through a stack-safe pretty JSON traversal whose total indentation is capped at ten characters (deeper subtrees stay compact), `null` remains explicit, and absent `result` means the program returned `undefined`. The worker's configurable `maxOutputBytes` (default 64 MiB) applies only to the combined serialized outer log-array, completion-value, or failure-message payloads; fixed result-envelope syntax and presentation whitespace are outside that limit. Invalid and over-limit completions fail explicitly, and only this outer result is eligible for ordinary spill.
|
||||
|
||||
### Parallel execution
|
||||
|
||||
@@ -146,7 +146,7 @@ Prefix-stable while visible definitions and their order are unchanged. Registrat
|
||||
|
||||
#### What the model sees
|
||||
|
||||
Code Mode exposes the generated [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tools), the SDK instructions below, and the generated exact SDK block for the loaded runtime's language (the TypeScript `declare const tools` block, or the Python `tools` declaration). `both` exposes normal schemas and this Code Mode surface. The instructions and SDK block match the loaded runtime's language; the TypeScript flavor (via [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md)) is shown below, and the Python flavor (for any runtime reporting `language: 'python'`) is the same shape with Python syntax (`await tools.name(args)`, subscript access for exotic names, `print(...)` and top-level `return`).
|
||||
Code Mode exposes the generated [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tools), the SDK instructions below, and the generated exact SDK block for the loaded runtime's language (the TypeScript `declare const tools` block, or the Python `tools` declaration). `both` exposes normal schemas and this Code Mode API. The instructions and SDK block match the loaded runtime's language; the TypeScript version (via [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md)) is shown below, and the Python version (for any runtime reporting `language: 'python'`) has the same operations and types in Python syntax (`await tools.name(args)`, subscript access for exotic names, `print(...)` and top-level `return`).
|
||||
|
||||
##### Code Mode SDK instructions
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ tools:
|
||||
|
||||
### 实时事件
|
||||
|
||||
实时注册表流水线先经过 3 个可变换的 waterfall,再经过由定义拥有的内容终结器,最后到达仅观测的 `tools/result` 边界;注册表变更有意作为不过滤的共享状态通知。确切签名、分发 mode、作用域筛选和故障收容约定位于 [tools.md](../../../docs/subsystems/tools.md#cordis-surface) 的生成区块,完整顺序则在生成的[工具执行流水线](../../../docs/tool-execution-pipeline.md)中可视化。`tools/result` 是实时事件;名称相近的 `tool/result` 是 agent loop 随后追加的持久会话事件。
|
||||
实时注册表流水线先经过 3 个可变换的 waterfall,再经过由定义拥有的内容终结器,最后发布仅供观测的 `tools/result` 事件;注册表变更有意作为不过滤的共享状态通知。确切签名、分发 mode、作用域筛选和失败隔离约定位于 [tools.md](../../../docs/subsystems/tools.md#cordis-surface) 的生成区块,完整顺序则在生成的[工具执行流水线](../../../docs/tool-execution-pipeline.md)中可视化。`tools/result` 是实时事件;名称相近的 `tool/result` 是 agent loop 随后追加的持久会话事件。
|
||||
|
||||
### 关键类型
|
||||
|
||||
@@ -120,7 +120,7 @@ ctx.tools.register(defineTool({
|
||||
- **SDK 段**(`tools:sdk`,顺序 150):一个惰性提示词段,每次组装时都会重新生成与所加载运行时语言相符的 SDK 文本。TypeScript 形态发出 `JsonValue`、精确的 `ToolArgsMap` / `ToolOutputMap`、`ToolName`、`ToolCallError` 声明、面向调用作用域可见最终能力的映射 `tools` 命名空间(特殊名称使用带引号的键),以及固定用法说明;Python 形态(`ctx.codeRuntime.language === 'python'`)发出等价的具名 `TypedDict` 与一个带相同用法说明的 `tools` 对象。其输出具有确定性:工具按字典序排列;工具集合不变时,文本逐字节相同(有利于前缀 cache)。两个代码生成器都已导出,且绝不会在提示词组装期间抛出:`jsonSchemaToTs` 处理统一 schema 的每种构造并将不受支持的原始构造降级为 `unknown`;`jsonSchemaToPy` 同理,降级为 `Any`(当某字段名不是合法的 `TypedDict` 属性时,或在 SDK 渲染之外被调用时——`TypedDict` 声明所需的命名上下文由该渲染提供——整个对象降级为 `dict[str, Any]`)。
|
||||
- **分发桥接层**(`run_code` 的 execute):每个绑定调用都会在分发前快照为无损 JSON(`undefined`、`BigInt`、循环、稀疏数组、`-0` 和特殊对象会使该次调用被拒绝),经由每次运行独有、复用原生并发约定的池调度——调用严格按提交顺序启动,连续的 `isConcurrencySafe` 调用最多可重叠经校验的 `maxParallelSubCalls` 配置个(默认 10;设为 `1` 即恢复串行分发),被分类为独占的调用先排空池、单独运行并阻挡其后的调用——以外层执行的不透明 token 作为 `parent`,并经过完整的 pre-execute → guards → execute → post-execute → result 流水线。成功会返回策略处理后的最终规范值;失败以一条消息到达 worker,并成为 `ToolCallError(toolName, message)`。每个已启动的子调用在进入流水线时记录一条 `tool/code-dispatch-start` 事件(确定性 id `<parent>:code:<n>`,按提交顺序编号),并以一条携带完整模型可见 `content`/`isError` 结果的 `tool/code-dispatch` 事件完结(采用 `tool/result` 词汇,因此 UI 会沿原生路径呈现子调用——这对事件的 `time` 字段承载每个子调用的计时);因 run 结算而被放弃的排队调用两者都不记录。`deriveMessages()` 既不公开这两个事件,也不持久化规范值。token 关联让以提交为语义的观察器能够把内部成功延迟到最终 `run_code` 结果,而无需公开实时外层执行;普通工具副作用不会回滚。每个子调用的 `additionalContexts` 条目都会按分发顺序通过外层 `ToolRunContext` 延迟;循环只在父级 `run_code` 结果之后追加这些上下文,从而保持相邻关系,并且即使程序后来失败,也会保留各自的来源/元数据。
|
||||
- **结算纪律**:桥接层拥有一个运行作用域的中止机制;该中止会跟随传入的外层信号,并在运行因任何原因结算时触发,因此预算耗尽会中止正在运行的子工具,而不会将其遗留。桥接层随后会在返回之前排空队列,使每个 `tool/code-dispatch` 都落在仍打开的轮次内。失败的运行会抛出 `CodeRunFailedError`(`code: 'CODE_RUN_FAILED'`,message = 失败类型 + 已捕获日志),流水线会将其转换为模型可据以自我修正的结构化 `isError`。
|
||||
- **结果边界**:中间绑定值会完整跨越 worker 边界,且没有逐绑定字节上限。`run_code` 返回规范的 `{ logs: string[], result?: JsonValue }`;字符串原样呈现,其他所有存在的 JSON 根都通过栈安全的美化 JSON 遍历呈现,总缩进最多为 10 个字符(更深的子树保持紧凑),`null` 保持显式,而缺少 `result` 表示程序返回 `undefined`。worker 可配置的 `maxOutputBytes`(默认 64 MiB)只应用于组合序列化后的外层日志数组、完成值或失败消息载荷;固定的结果 envelope 语法和呈现空白不计入该账本。无效和超限的完成会明确失败,只有此外层结果可以使用普通 spill。
|
||||
- **结果大小**:中间绑定值会完整传入 worker 进程,且没有逐绑定字节上限。`run_code` 返回规范的 `{ logs: string[], result?: JsonValue }`;字符串原样呈现,其他所有存在的 JSON 根都通过栈安全的美化 JSON 遍历呈现,总缩进最多为 10 个字符(更深的子树保持紧凑),`null` 保持显式,而缺少 `result` 表示程序返回 `undefined`。worker 可配置的 `maxOutputBytes`(默认 64 MiB)只应用于组合序列化后的外层日志数组、完成值或失败消息载荷;固定的结果 envelope 语法和呈现空白不计入该上限。无效和超限的完成会明确失败,只有此外层结果可以使用普通 spill。
|
||||
|
||||
### 并行执行
|
||||
|
||||
@@ -146,7 +146,7 @@ agent loop 将连续的 `parallel` 调用归入有界滚动池,并把每个 `e
|
||||
|
||||
#### 模型看到的内容
|
||||
|
||||
Code Mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tools)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块(TypeScript 的 `declare const tools` 块,或 Python 的 `tools` 声明)。`both` 会同时公开普通 schema 与此 Code Mode 接口。说明与 SDK 块随所加载运行时的语言切换;下方展示 TypeScript 风格(经 [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md)),Python 风格(用于任何报告 `language: 'python'` 的运行时)形状相同,只是换成 Python 语法(`await tools.name(args)`、特殊名称用下标访问、`print(...)` 与顶层 `return`)。
|
||||
Code Mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tools)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块(TypeScript 的 `declare const tools` 块,或 Python 的 `tools` 声明)。`both` 会同时公开普通 schema 与此 Code Mode API。说明与 SDK 块随所加载运行时的语言切换;下方展示 TypeScript 版本(经 [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md)),Python 版本(用于任何报告 `language: 'python'` 的运行时)以 Python 语法提供相同操作和类型(`await tools.name(args)`、特殊名称用下标访问、`print(...)` 与顶层 `return`)。
|
||||
|
||||
##### Code Mode SDK 说明
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export const SDK_SECTION_ORDER = 150
|
||||
* strings share one source of truth. Keyed by `CodeRuntime.language`, mirroring
|
||||
* `SDK_RENDERERS` in {@link ./index.ts}. The emitted flavor MUST match the
|
||||
* semantics the same language's SDK instructions promise, so the model never
|
||||
* receives a TypeScript-shaped schema beside a Python SDK (or vice versa).
|
||||
* receives a TypeScript schema beside a Python SDK (or vice versa).
|
||||
*/
|
||||
interface RunCodeFlavor {
|
||||
/** The tool `description` the model sees for this language. */
|
||||
@@ -338,8 +338,8 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
exec.signal.addEventListener('abort', onOuterAbort, { once: true })
|
||||
|
||||
let dispatches = 0
|
||||
// The per-run scheduler, reusing the NATIVE concurrency contract through
|
||||
// the registry's staged view (the loop scheduler's own boundary) — and the
|
||||
// The per-run scheduler uses the registry's staged interface and follows
|
||||
// the same concurrency rules as the native loop. It also follows the
|
||||
// native loop's SEQUENCING: every ordered stage (the dispatch-start
|
||||
// append, prepare = pre-execute/guards, finalize/finish = post-execute,
|
||||
// context deferral, the settle append) runs inside ONE driver lane, so
|
||||
@@ -369,7 +369,7 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
}
|
||||
const pendingQueue: PendingDispatch[] = []
|
||||
const inFlight = new Set<Promise<void>>()
|
||||
/** Tracked settle-event side work (log shaping + append), drained at run settlement. */
|
||||
/** Tracked settle-event side work (log-content listener + append), drained at run settlement. */
|
||||
const logWork = new Set<Promise<void>>()
|
||||
const commitQueue: PendingDispatch[] = []
|
||||
let exclusiveActive = false
|
||||
@@ -394,7 +394,7 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
driverRun = (async () => {
|
||||
try {
|
||||
for (;;) {
|
||||
// Arm before inspecting state so a settle or submission landing
|
||||
// Create the wakeup promise before inspecting state so a settle or submission arriving
|
||||
// between the checks and the await below cannot be lost.
|
||||
const signal = new Promise<void>((resolve) => { wake = resolve })
|
||||
const commitHead = commitQueue[0]
|
||||
@@ -449,7 +449,7 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
// entries, awaits the live pool, and drains the ordered commit lane —
|
||||
// including a commit already in progress when the program returned.
|
||||
await drive()
|
||||
// Every settle's shaped append lands inside the open run_code turn
|
||||
// Every settle event is appended inside the open run_code turn
|
||||
// (tasks self-remove on settlement).
|
||||
while (logWork.size > 0) await Promise.allSettled([...logWork])
|
||||
}
|
||||
@@ -483,10 +483,10 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
| { kind: 'post-result' | 'final-result'; exec: ToolRunContext; result: ToolExecutionResult }
|
||||
| undefined
|
||||
const settle = (result: ToolExecutionResult): void => {
|
||||
// The program gets its value NOW: log shaping (e.g. a spill
|
||||
// backend) must never delay the binding or occupy a dispatch
|
||||
// slot. The shaped append is tracked side work; the run's
|
||||
// settlement drains logWork so every settle event still lands
|
||||
// The program gets its value NOW: the log-content listener (for
|
||||
// example, a spill backend) must never delay the binding or occupy
|
||||
// a dispatch slot. The event append is tracked side work; the run's
|
||||
// settlement drains logWork so every settle event is still appended
|
||||
// inside the open turn (shapeDispatchLog is contained, so this
|
||||
// chain cannot reject).
|
||||
resolve(result.isError
|
||||
@@ -495,9 +495,9 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
const agent = exec.agent
|
||||
if (agent === undefined) return
|
||||
const task: Promise<void> = (async () => {
|
||||
// The durable copy may be reshaped (e.g. spilled to a preview +
|
||||
// locator) by the log-shaping waterfall; the program's value
|
||||
// and the model contract are untouched.
|
||||
// The listener may replace the durable copy with a preview and
|
||||
// locator; the program's value and model-visible result are
|
||||
// untouched.
|
||||
const logged = await shapeDispatchLog({
|
||||
exec, agent, subCallId, name, isError: result.isError,
|
||||
// The registry deep-froze this projection at result
|
||||
@@ -560,16 +560,16 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
for (const context of result.additionalContexts ?? []) {
|
||||
exec.deferContext(context)
|
||||
}
|
||||
// Like the context forwarding above, cross-boundary facts travel
|
||||
// on the nested result and the composite forwards them: only a
|
||||
// successful nested result can carry the terminal marker
|
||||
// The composite forwards `additionalContexts` above and
|
||||
// `concludesTurn` here from the nested result. Only a successful
|
||||
// nested result can carry the terminal marker
|
||||
// (ToolExecutionFailure types it never), so a policy-converted
|
||||
// failure cannot stop the turn through a recovering program.
|
||||
if (result.concludesTurn) exec.concludeTurn()
|
||||
settle(result)
|
||||
// Backpressure on the shaped-append side channel: pending log
|
||||
// tasks (each retaining a full result while a slow backend
|
||||
// stores it) are bounded by the pool cap — beyond it the
|
||||
// Backpressure on pending event-append tasks: each task retains
|
||||
// a full result while a slow backend stores it, so the pool cap
|
||||
// bounds their count. Beyond the cap, the
|
||||
// ordered lane waits, so later sub-calls cannot start and
|
||||
// pending I/O/memory cannot grow without bound.
|
||||
while (logWork.size > maxParallel) await Promise.race(logWork)
|
||||
@@ -578,7 +578,7 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
wakeup()
|
||||
void drive()
|
||||
})
|
||||
// A budget expiry or outer cancel that lands while this call was in
|
||||
// A budget expiry or outer cancel that occurs while this call was in
|
||||
// flight already aborted the dispatch; stop the program now rather
|
||||
// than hand it a result from a run that is over.
|
||||
if (runOver()) {
|
||||
@@ -661,7 +661,7 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
Object.defineProperty(definition, 'parameters', {
|
||||
enumerable: true,
|
||||
// Recompile through the same spec→schema projection defineTool used, so
|
||||
// the emitted shape can never drift from the validated one.
|
||||
// the emitted schema always matches the validated specification.
|
||||
get: () => parameterSchemaSpecToJsonSchema({
|
||||
code: { type: 'string', required: true, description: resolveFlavor(peekRuntime).codeDescription },
|
||||
description: { type: 'string', required: true, description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION },
|
||||
|
||||
@@ -160,13 +160,14 @@ declare module 'cordis' {
|
||||
*/
|
||||
'tools/post-execute'(this: Scoped<ToolRegistry>, exec: ToolExecution, result: Readonly<ToolExecutionResult>, next: () => Promise<PostToolDecision>): Promise<PostToolDecision>
|
||||
/**
|
||||
* Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before
|
||||
* the bridge appends its `tool/code-dispatch` event. `next()` keeps the
|
||||
* Allow a listener to replace content in the DURABLE LOG COPY of one
|
||||
* `run_code` sub-dispatch outcome before the bridge appends its
|
||||
* `tool/code-dispatch` event. `next()` keeps the
|
||||
* content unchanged; a listener may return replacement blocks (e.g. the
|
||||
* spill policy's preview + locator for an oversized text result). Only the
|
||||
* logged copy is affected — the program already received the complete
|
||||
* value, and the model sees neither. A throwing listener is contained:
|
||||
* the bridge falls back to logging the unshaped content.
|
||||
* the bridge falls back to logging the original settled content.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's dispatches.
|
||||
* @param dispatch - the parent execution, sub-call identity, and the settled content to log.
|
||||
* @mode waterfall
|
||||
@@ -1183,8 +1184,8 @@ export class ToolRegistry extends Service {
|
||||
/**
|
||||
* Run the `tools/code-dispatch-log` waterfall over one settled sub-dispatch
|
||||
* and return the content the bridge should log on `tool/code-dispatch`.
|
||||
* Contained: a throwing listener falls back to the unshaped content — log
|
||||
* shaping must never fail the dispatch or lose the settle event. Private:
|
||||
* Contained: when a listener throws, the method logs the original settled
|
||||
* content; that failure must not fail the dispatch or omit the settle event. Private:
|
||||
* the ONE consumer is the `run_code` bridge this registry constructs, which
|
||||
* receives it as a capability parameter (the `requireRuntime` idiom) — the
|
||||
* waterfall, not this invoker, is the public extension point.
|
||||
@@ -1196,7 +1197,7 @@ export class ToolRegistry extends Service {
|
||||
() => Promise.resolve(dispatch.content),
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
this.ctx.logger.warn(`tools: code-dispatch-log listener failed for ${dispatch.name}: ${errorMessage(error)}; logging the unshaped content`)
|
||||
this.ctx.logger.warn(`tools: code-dispatch-log listener failed for ${dispatch.name}: ${errorMessage(error)}; logging the original settled content`)
|
||||
return dispatch.content
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* Unsupported or misplaced keywords reject rather than being accepted without
|
||||
* enforcement. Consumers that require an object root apply
|
||||
* {@link assertObjectJsonSchema} at their own boundary.
|
||||
* {@link assertObjectJsonSchema} before accepting input.
|
||||
* @module dsh-tools/json-schema
|
||||
*/
|
||||
|
||||
@@ -25,7 +25,7 @@ type JsonSchemaScalarType = Exclude<JsonSchemaType, 'object' | 'array'>
|
||||
|
||||
/**
|
||||
* One raw JSON Schema node in the enforced subset. The optional fields express
|
||||
* the external wire shape; {@link assertSupportedJsonSchema} rejects invalid
|
||||
* the external wire schema; {@link assertSupportedJsonSchema} rejects invalid
|
||||
* combinations before a caller treats the node as trusted.
|
||||
*/
|
||||
export interface JsonSchemaNode {
|
||||
|
||||
@@ -733,7 +733,7 @@ export function jsonSchemaToPy(schema: unknown): string {
|
||||
/** The fixed model-facing usage contract rendered above the declarations. */
|
||||
const SDK_INSTRUCTIONS = `## Writing code for run_code
|
||||
|
||||
Pass \`run_code\` the body of an async Python function (top-level \`await\` and \`return\` both work). At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing shapes — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program:
|
||||
Pass \`run_code\` the body of an async Python function (top-level \`await\` and \`return\` both work). At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program:
|
||||
|
||||
- Call tools as \`await tools.name(args)\` — subscript access for exotic, reserved, or underscore-leading names: \`await tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value (each method's return type below). Tool arguments must be lossless JSON.
|
||||
- A FAILED tool call raises \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose message is human-readable — wrap in \`try/except\` to handle and continue.
|
||||
|
||||
@@ -59,7 +59,7 @@ async function setup(options: SetupOptions = {}) {
|
||||
return { ctx, tools: ctx.tools, systemPrompt: ctx.systemPrompt, runtime: runtime! }
|
||||
}
|
||||
|
||||
/** Mint one production-shaped agent scope that can register scoped tool policy. */
|
||||
/** Mint an agent scope configured like production that can register scoped tool policy. */
|
||||
async function mintAgentScope(ctx: Context, name = 'scoped'): Promise<{ scope: Scope; agent: Agent }> {
|
||||
const agent = { id: SessionId(name) } as Agent
|
||||
let scope!: Scope
|
||||
@@ -407,7 +407,7 @@ describe('mode-aware wire contribution', () => {
|
||||
})
|
||||
|
||||
it('degrades the run_code flavor to TypeScript when no runtime is mounted', async () => {
|
||||
// Any reader of the definition without a mounted runtime lands here; the
|
||||
// Any reader of the definition without a mounted runtime uses this fallback; the
|
||||
// shipped one is the tool-catalog generator, which boots the registry under
|
||||
// `mode: code` and reads run_code's schema WITHOUT a runtime. peekRuntime
|
||||
// returns undefined there, so the flavor getter degrades to the TS default
|
||||
@@ -663,7 +663,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => {
|
||||
expect(stages).toEqual(['post-enter:writer', 'post-exit:writer'])
|
||||
})
|
||||
|
||||
it('run settlement drains a commit already in progress: the settle event lands inside the turn', async () => {
|
||||
it('run settlement drains a commit already in progress: the settle event is appended inside the turn', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
const gated = registerGated(ctx, 'safe_read', true)
|
||||
const { agent, events } = fakeAgent()
|
||||
@@ -921,10 +921,10 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect(result.content[0]).toEqual({ type: 'text', text: 'caught: deliberate failure' })
|
||||
})
|
||||
|
||||
it('a throwing tools/code-dispatch-log listener is contained: the unshaped content is logged', async () => {
|
||||
it('a throwing tools/code-dispatch-log listener is contained: the original settled content is logged', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
registerEcho(ctx)
|
||||
ctx.on('tools/code-dispatch-log', () => { throw new Error('shaper exploded') })
|
||||
ctx.on('tools/code-dispatch-log', () => { throw new Error('log-content listener failed') })
|
||||
const { agent, events } = fakeAgent()
|
||||
runtime.behavior = async (request) => {
|
||||
const value = await request.bindings[0]!.functions.echo!({ value: 'x' })
|
||||
@@ -1593,7 +1593,7 @@ describe('per-agent presentation', () => {
|
||||
const { ctx, systemPrompt } = await setup({ mode: 'native' })
|
||||
registerEcho(ctx)
|
||||
// The preset's standing scope declares once; the agent only PARENTS to it
|
||||
// (the per-preset standing-mount shape — no per-agent declaration at all).
|
||||
// (the per-preset standing mount configuration has no per-agent declaration).
|
||||
const standing = await mintAgentScope(ctx, 'preset:code-like')
|
||||
standing.scope.ctx.tools.presentAs('code')
|
||||
const joined = await mintAgentScope(ctx, 'joined-agent')
|
||||
|
||||
Reference in New Issue
Block a user