Merge branch 'codex/canonical-tool-output' into codex/code-mode-typed-results

This commit is contained in:
Tianyi Cui
2026-07-23 01:15:13 +08:00
18 changed files with 403 additions and 119 deletions

View File

@@ -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
2026-07-20-canonical-tool-output-contract.md: 4099568de5dcc21a89b7873d4a6d4c7e9c62f8e4
2026-07-20-canonical-tool-output-contract.zh.md: 01b50ef7493ea6548cd238f55e445a702e4d78b3
2026-07-20-canonical-tool-output-contract.md: 9cc7c97c1f0c9e0753ee826c1e20a3a425e2caf3
2026-07-20-canonical-tool-output-contract.zh.md: 183a7e6bf212e63dffca1a346aa907f66006444a

View File

@@ -24,7 +24,7 @@ output: {
`defineTool` infers the body return and both projectors from the unified `ValueSchemaSpec`. Raw and dynamic definitions provide the compiled `JsonSchemaNode` form. Registration rejects a missing declaration or unsupported raw schema; there is no content-return compatibility path.
For each successful dispatch the registry snapshots the returned value as lossless `JsonValue`, validates it against `output.schema`, deep-freezes it, then invokes the pure renderer and, for a direct surface call, the optional metadata projector. Renderer, projector, schema, or lossless-JSON failures are contained as ordinary `ToolOutputError` results. An around `tools/execute` wrapper receives and returns the canonical success/failure union; a wrapper-authored success is normalized again through the resolved tool's output declaration instead of trusting independently authored content.
For each successful dispatch the registry snapshots the returned value as lossless `JsonValue`, validates it against `output.schema`, deep-freezes it, then invokes the pure renderer and, for a direct surface call, the optional metadata projector. Renderer, projector, schema, or lossless-JSON failures are contained as ordinary `ToolOutputError` results. An around `tools/execute` wrapper receives and returns the canonical success/failure union; a wrapper-authored success is normalized again through the resolved tool's output declaration instead of trusting independently authored content. Canonical-result provenance is scoped to the immutable dispatch token, so returning a cached result from another call or tool triggers normalization under the active declaration rather than bypassing it.
```ts ignore-check
type ToolExecutionResult =

View File

@@ -24,7 +24,7 @@ output: {
`defineTool` 从统一的 `ValueSchemaSpec` 推导工具主体返回值和两个投影器的类型。原始定义和动态定义则提供编译后的 `JsonSchemaNode` 形式。注册时会拒绝缺失输出声明或采用不受支持原始 schema 的定义,不提供兼容旧式内容返回值的路径。
每次成功分发时,注册表会将返回值快照为无损 `JsonValue`,依据 `output.schema` 校验并深度冻结然后调用纯渲染器对于直接的外层调用还会调用可选的元数据投影器。渲染器、投影器、schema 或无损 JSON 处理失败都会被收敛为普通 `ToolOutputError` 结果。around `tools/execute` 包装层接收并返回规范的成功/失败联合;包装层自行产生的成功结果会再次通过已解析工具的输出声明完成归一化,而不会信任其独立编写的内容。
每次成功分发时,注册表会将返回值快照为无损 `JsonValue`,依据 `output.schema` 校验并深度冻结然后调用纯渲染器对于直接的外层调用还会调用可选的元数据投影器。渲染器、投影器、schema 或无损 JSON 处理失败都会被收敛为普通 `ToolOutputError` 结果。around `tools/execute` 包装层接收并返回规范的成功/失败联合;包装层自行产生的成功结果会再次通过已解析工具的输出声明完成归一化,而不会信任其独立编写的内容。每个规范结果只归属于一个不可变的分发 token因此如果包装层返回来自其他调用或工具的缓存结果系统会依据当前生效的输出声明重新执行归一化而不会绕过这一步。
```ts ignore-check
type ToolExecutionResult =

View File

@@ -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
2026-07-20-unified-json-value-schema-dsl.md: e735472ee0ac6a696462aa7598d57fce04aa9c3e
2026-07-20-unified-json-value-schema-dsl.zh.md: dd34817a6d047b14346b35ba6c4bce0290ad7feb
2026-07-20-unified-json-value-schema-dsl.md: 3e35bce6eb48afeb31c564e9b5d7b84ff91a7b1f
2026-07-20-unified-json-value-schema-dsl.zh.md: 77a20d17aab61e759de6e490510b14b5bc408726

View File

@@ -12,7 +12,9 @@ Tool parameters used a small author DSL while subagent/workflow structured outpu
`dsh-tools` owns one JSON-value schema vocabulary with two representations. `ValueSchemaSpec` is the author form for any JSON root; `ParameterSchemaSpec` is its implicit object-property-map form with per-property `required: true`. `JsonSchemaNode` is the raw wire form. Both support string, finite number, integer, boolean, null, array, object, type-correct scalar `enum`/`const`, and exact-one `oneOf`; `{ type: 'json' }` is author-only sugar for an annotation-only unconstrained raw node.
An explicit author object must declare `additionalProperties: true | false`. The implicit parameter root and raw JSON Schema preserve the standard open default. `InferValue<S>` and `InferArgs<P>` derive TypeScript values from the same declarations that `valueSchemaSpecToJsonSchema()` and `parameterSchemaSpecToJsonSchema()` compile. `assertSupportedJsonSchema()` rejects unsupported or misplaced keywords, and `validateJsonSchemaValue()` enforces the accepted subset against the lossless `JsonValue` boundary: no `undefined`, negative zero, non-finite numbers, sparse arrays, cycles, exotic objects, functions, symbols, or other coercive values. Intrinsic plain Object and Array containers remain plain across JavaScript realms; subclasses remain exotic. Author compilation, raw-schema assertion, value validation, schema-to-TypeScript rendering, registry detachment, and dynamic Cordis cross-realm normalization and cloning use explicit work stacks, so valid nesting is limited by available memory rather than the JavaScript call stack.
An explicit author object must declare `additionalProperties: true | false`. The implicit parameter root and raw JSON Schema preserve the standard open default. Schema records contain only own enumerable string keys, schema arrays are dense intrinsic arrays, and supported keywords are read as own properties; custom prototypes, inherited constraints, symbols, and JSON-invisible decorations therefore cannot make compilation, projection, and validation observe different declarations. Intrinsic plain Object and Array containers remain plain across JavaScript realms, while subclasses and forged constructor prototypes remain exotic.
`InferValue<S>` and `InferArgs<P>` derive TypeScript values from the same declarations that `valueSchemaSpecToJsonSchema()` and `parameterSchemaSpecToJsonSchema()` compile. Exact inference is bounded to 16 container levels and then uses `JsonValue`, preventing TypeScript's type-instantiation stack from becoming the authoring limit. `assertSupportedJsonSchema()` rejects unsupported or misplaced keywords, and `validateJsonSchemaValue()` enforces the accepted subset against the lossless `JsonValue` boundary: no `undefined`, negative zero, non-finite numbers, sparse arrays, cycles, exotic objects, functions, symbols, or other coercive values. Author compilation, raw-schema assertion, value validation, schema-to-TypeScript rendering, registry detachment, and dynamic Cordis cross-realm normalization and cloning use explicit work stacks, so runtime nesting is limited by available memory rather than the JavaScript call stack.
Object-rooting is a consumer rule rather than a vocabulary restriction. Subagent and workflow caller-defined structured outputs use `assertObjectJsonSchema()` and `ObjectJsonSchema`; tool outputs may use any root. Dynamic Cordis registrations rebuild realm-foreign schemas into host-owned JSON, preserve raw-wrapper openness, and require direct-DSL object openness before calling the same compiler.
@@ -28,5 +30,6 @@ Object-rooting is a consumer rule rather than a vocabulary restriction. Subagent
- Parameter validation, output validation, schema-to-TypeScript generation, subagent/workflow guards, and dynamic registration share one enforced vocabulary.
- Output declarations can infer object, array, scalar, or null roots; subagent/workflow structured outputs remain object-rooted at their existing seams.
- Explicit object openness and type-correct literal constraints make malformed declarations fail during authoring or registration rather than during a later model call.
- Bounded type inference retains useful exact types for ordinary declarations and degrades unusually deep tails to `JsonValue`; runtime schema enforcement remains exact at every depth.
- Raw tools may still register broader JSON Schema directly, but unified code generation treats unsupported schemas as unknown instead of pretending to enforce them.
- Runtime and compile-time tests cover every root, exact-one overlap/no-match behavior, raw open defaults, explicit openness, lossy JSON values, inference, and deep nesting across core and dynamic projections.

View File

@@ -12,7 +12,9 @@ Status: implemented
`dsh-tools` 以两种表示形式统一管理一套 JSON 值 schema 词汇。`ValueSchemaSpec` 是可描述任意 JSON 根类型的作者侧形式;`ParameterSchemaSpec` 是其隐式对象属性映射形式,每个属性可标记 `required: true``JsonSchemaNode` 是原始协议表示。两种形式都支持字符串、有限数值、整数、布尔值、null、数组、对象、类型正确的标量 `enum``const`,以及要求恰好匹配一个分支的 `oneOf``{ type: 'json' }` 仅是作者侧语法糖,会编译为仅含注解、不施加约束的原始节点。
显式的作者侧对象必须声明 `additionalProperties: true | false`。隐式参数根对象和原始 JSON Schema 保留标准的默认开放语义。`InferValue<S>``InferArgs<P>` 根据同一份声明推导 TypeScript 值,`valueSchemaSpecToJsonSchema()``parameterSchemaSpecToJsonSchema()` 也将这些声明编译为 JSON Schema。`assertSupportedJsonSchema()` 会拒绝不受支持或位置错误的关键字;`validateJsonSchemaValue()` 则以无损 `JsonValue` 边界校验受支持的子集,不允许 `undefined`、负零、非有限数、稀疏数组、循环引用、非普通对象、函数、symbol 及其他需要强制转换的值。内建的普通 Object 和 Array 容器跨 JavaScript 运行域后仍视为普通容器;其子类仍视为非普通对象。作者侧 schema 编译、原始 schema 断言、值校验、schema 到 TypeScript 的渲染、注册表数据分离,以及动态 Cordis 的跨运行域规范化与克隆均使用显式工作栈,因此合法嵌套的深度上限由可用内存决定,而非 JavaScript 调用栈
显式的作者侧对象必须声明 `additionalProperties: true | false`。隐式参数根对象和原始 JSON Schema 保留标准的默认开放语义。schema 记录只能包含自有且可枚举的字符串键schema 数组必须是稠密的内建数组系统只从自有属性读取受支持的关键字因此自定义原型、继承的约束、symbol 和 JSON 不可见的附加内容都无法让编译、投影和校验观察到不同的声明。内建的普通 Object 和 Array 容器跨 JavaScript 运行域后仍视为普通容器,而子类和伪造构造函数的原型仍视为非普通对象
`InferValue<S>``InferArgs<P>` 根据同一份声明推导 TypeScript 值,`valueSchemaSpecToJsonSchema()``parameterSchemaSpecToJsonSchema()` 也将这些声明编译为 JSON Schema。精确类型推导以 16 层容器为界,超过后使用 `JsonValue`,从而避免 TypeScript 的类型实例化栈限制作者能声明的嵌套深度。`assertSupportedJsonSchema()` 会拒绝不受支持或位置错误的关键字;`validateJsonSchemaValue()` 则以无损 `JsonValue` 边界校验受支持的子集,不允许 `undefined`、负零、非有限数、稀疏数组、循环引用、非普通对象、函数、symbol 及其他需要强制转换的值。作者侧 schema 编译、原始 schema 断言、值校验、schema 到 TypeScript 的渲染、注册表数据分离,以及动态 Cordis 的跨运行域规范化与克隆均使用显式工作栈,因此运行时嵌套只受可用内存限制,不受 JavaScript 调用栈限制。
对象根限制属于消费方规则,不属于 schema 词汇本身。subagent 和工作流中由调用方定义的结构化输出通过 `assertObjectJsonSchema()``ObjectJsonSchema` 保持对象根限制;工具输出可以使用任意根类型。动态 Cordis 注册会把跨 JavaScript 运行域传入的 schema 重建为当前运行时持有的 JSON保留原始包装层的默认开放语义并要求直接使用 DSL 声明的对象明确选择开放方式,然后再调用同一编译器。
@@ -28,5 +30,6 @@ Status: implemented
- 参数校验、输出校验、schema 到 TypeScript 的代码生成、subagent工作流门禁和动态注册共用一套强制执行的词汇。
- 输出声明可以推导对象、数组、标量或 null 根类型subagent工作流的结构化输出仍在其现有服务边界保持对象根限制。
- 显式的对象开放方式和类型正确的字面量约束会让格式错误的声明在编写或注册阶段快速失败,而不是拖到后续模型调用时才失败。
- 有界类型推导会为常规声明保留有用的精确类型,并将异常深的尾部结构退化为 `JsonValue`;运行时 schema 强制执行在任意深度仍保持精确。
- 原始工具仍可直接注册范围更广的 JSON Schema但统一代码生成会把不受支持的 schema 视为未知类型,不会假装自己能够强制执行。
- 运行时和编译期测试覆盖所有根类型、恰好匹配一个分支时的重叠/无匹配行为、原始 schema 的默认开放语义、显式开放方式、有损 JSON 值、类型推导,以及核心投影和动态投影中的深层嵌套。