Drop `GenerateOptions.prefill` and `ToolSchema.strict` — request knobs with no working end-to-end path
移除 `GenerateOptions.prefill` 与 `ToolSchema.strict`——无端到端可用路径的请求旋钮
Two request-contract knobs rode the whole request pipeline, yet neither could do anything: - **`prefill`** (`packages/llm/llm/src/types.ts`) had no production setter — the loop assembles `model`/`system`/`tools`/`messages` plus `sessionId`/`signal`, and the compaction backend adds only `maxTokens` — and BOTH adapters rejected it: `packages/llm/llm-deepseek/src/serialize.ts` and `packages/llm/llm-pi-ai/src/adapter.ts`
English
Problem
Two request-contract knobs rode the whole request pipeline, yet neither could do anything:
prefill(packages/llm/llm/src/types.ts) had no production setter — the loop assemblesmodel/system/tools/messagesplussessionId/signal, and the compaction backend adds onlymaxTokens— and BOTH adapters rejected it:packages/llm/llm-deepseek/src/serialize.tsandpackages/llm/llm-pi-ai/src/adapter.tseach threwLlmError('UNSUPPORTED')on a non-undefinedprefill. The field's entire observable behavior was two throws, each pinned by one adapter test. DeepSeek's chat-prefix completion is a Beta feature on a base URL neither adapter targets.strict(ToolSchema, same file) was threaded throughDefineToolOptions/defineTool(packages/core/tools/src/schema.ts), the registry'sschemas()allowlist (packages/core/tools/src/index.ts), the deepseek wire mapping (packages/llm/llm-deepseek/src/serialize.ts, whose wire-type note recorded that strict mode requires the/betabase URL the adapter does not use), a per-tool payload-patching pass inpackages/llm/llm-pi-ai/src/adapter.ts, and a conditionalStrict:row in the tool-catalog renderer (scripts/gen-tool-catalog.ts). No shipped tool set it —rgacross everytool-*package src andexamples/found zerostrict:producers; the only setters were dsh-tools unit tests.
Both knobs were adapter-symmetric, so removal shed them from both twins together — the twin-adapter design is untouched.
Decision
prefillis removed fromGenerateOptions, along with both adapters' UNSUPPORTED guards, the tests pinning the throws, the paste line in core.md, and the adapter README rows documenting the rejection. The cookbook's UNSUPPORTED guidance (adding-an-llm-adapter.md) states the rule generically — aGenerateOptionsfield your provider cannot honor throwsLlmError(..., 'UNSUPPORTED')— instead of using prefill as the example. The content-block vocabulary Agent Note's consequences record prefill as producer-gated rather than as having a home, per implemented/AGENTS.md.strictis removed fromToolSchema,DefineToolOptions,defineTool, theschemas()allowlist, the deepseek serializer branch and its wire-type field, and the tool-catalog renderer'sStrict:row. The pi-ai payload fixup is simplified to the unconditional scrub of pi-ai's own per-tool strict default (pi-ai stampsstrict: falseon every serialized tool; the hand-rolled twin sends no such field, so the scrub survives for wire parity, pinned by its serializer test). The setter tests and the core.md paste line are gone; bothGenerateOptionsandToolSchemakeep their rows inscripts/type-equiv.manifest.json, since each type survives minus a field.
This Agent Note deliberately does NOT touch temperature, stop, or maxTokens: those are honored end-to-end by both adapters and are the natural first targets of a request-mutating hook plugin on agent/request.
Alternatives considered
Why not keep them?
"An explicit UNSUPPORTED throw is honest contract behavior" — but a knob whose only implementation across both twins is rejection promises nothing, and deleting it upgrades the failure mode: an accidental setter becomes a compile error instead of a runtime throw. "Strict schema adherence is an officially documented provider feature with complete plumbing" — but a knob is not product surface until a shipped tool sets it AND an endpoint honors it; today neither is true. Each returns with its first real producer: prefill together with an adapter that implements chat-prefix completion (and a stated policy for adapters that do not), strict together with a tool that wants it and a beta-endpoint story.
Verification
rg prefill returns only Agent Note records (this one and the content-block vocabulary Agent Note's producer-gated consequence); a tool-schema-scoped rg strict returns only this Agent Note, the surviving pi-ai scrub, and unrelated prose such as strictEqual. Both adapters' contract tests pass without the guards, and the pi-ai fixup still scrubs the library's strict default — wire parity pinned by its serializer tests.
Consequences
The shipped hook bridges set no request fields at all, and a request-mutating plugin (an agent/request waterfall listener) reaches for temperature/stop (kept, working), not a field adapters reject. If chat-prefix completion or strict mode become product features, the re-add lands with the adapter/endpoint work, where the contract can say what actually happens rather than "everyone throws".
中文
问题
两个请求契约旋钮贯穿了整条请求流水线,却都无法产生任何效果:
prefill(packages/llm/llm/src/types.ts)没有生产级的 setter:agent loop(智能体循环)组装的是model/system/tools/messages加sessionId/signal,上下文压缩(context compaction)后端只追加maxTokens;而且两个适配器都拒绝它:packages/llm/llm-deepseek/src/serialize.ts和packages/llm/llm-pi-ai/src/adapter.ts各自在prefill非 undefined 时抛出LlmError('UNSUPPORTED')。该字段的全部可观测行为就是两个 throw,各由一条适配器测试固定。DeepSeek 的 chat-prefix completion 是一个 Beta 功能,运行在两个适配器都未指向的 base URL 上。strict(ToolSchema,同一文件)穿过了DefineToolOptions/defineTool(packages/core/tools/src/schema.ts)、注册表的schemas()允许列表(packages/core/tools/src/index.ts)、deepseek 协议格式(wire format)映射(packages/llm/llm-deepseek/src/serialize.ts,其 wire-type 注释记录了 strict 模式需要适配器未使用的/betabase URL)、packages/llm/llm-pi-ai/src/adapter.ts中的逐工具 payload 修补逻辑,以及 tool-catalog 渲染器(scripts/gen-tool-catalog.ts)中的条件Strict:行。没有任何已发布的工具设置过它——在所有tool-*包的 src 和examples/中执行rg搜索,strict:的生产者为零;唯一的 setter 出现在 dsh-tools 单元测试中。
两个旋钮在适配器间是对称的,因此移除操作将它们从两个孪生适配器中一并剥离——孪生适配器设计不受影响。
决策
- 从
GenerateOptions中移除prefill,同时移除两个适配器的 UNSUPPORTED 守卫、固定抛错行为的测试、core.md 中的粘贴行,以及记录该拒绝行为的适配器 README 表格行。实操手册中的 UNSUPPORTED 指导(adding-an-llm-adapter.md)改为通用表述规则——提供方无法遵守的GenerateOptions字段应抛出LlmError(..., 'UNSUPPORTED')——而不再以 prefill 为例。内容块词汇 Agent Note(agent 决策记录)的后果按照 implemented/AGENTS.md,将 prefill 记录为由生产者门控,而不是已有归属。 - 从
ToolSchema、DefineToolOptions、defineTool、schemas()允许列表、deepseek 序列化分支及其 wire-type 字段,以及工具目录渲染器的Strict:行中移除strict。pi-ai 的 payload 修补逻辑简化为对 pi-ai 自身逐工具 strict 默认值的无条件清除(pi-ai 在每个序列化的工具上打strict: false;手写的孪生适配器不发送此字段,因此清除逻辑为保持协议格式对等而保留,由其序列化器测试固定)。setter 测试和 core.md 粘贴行已移除;GenerateOptions与ToolSchema在scripts/type-equiv.manifest.json中保留各自的行,因为两个类型只是少了一个字段,本身仍然存在。
本 Agent Note 刻意不触及 temperature、stop 或 maxTokens:两个适配器都会端到端遵守它们,而且它们自然是 agent/request 上修改请求的钩子插件首批目标。
曾考虑的替代方案
为什么不保留?
「显式的 UNSUPPORTED throw 是诚实的契约行为」——但一个在两个孪生适配器中唯一的实现就是拒绝的旋钮,什么也没承诺;删除它反而升级了失败模式:意外的 setter 变成编译错误而非运行时 throw。「Strict schema 遵循是官方文档记载的提供方功能,且管道完整」——但一个旋钮在有已发布的工具设置它并且有端点兑现它之前,不构成产品表面;今天两者都不成立。它们各自随首个真实 producer 回归:prefill 随实现了 chat-prefix completion 的适配器(以及对不支持该功能的适配器的明确策略)一起回来;strict 随需要它的工具和 beta 端点方案一起回来。
验证
rg prefill 只返回 Agent Note 记录(本文及内容块词汇 Agent Note中由生产者门控的后果);限定在工具 schema 范围内的 rg strict 只返回本 Agent Note、保留下来的 pi-ai 清理逻辑,以及 strictEqual 等无关正文。两个适配器的契约测试都能在没有守卫的情况下通过,pi-ai 修正仍会清理库的 strict 默认值——其 serializer 测试固定了线协议一致性。
后果
已发布的钩子桥接不设置任何请求字段,而请求变更插件(agent/request waterfall(瀑布式事件)监听器)使用的是 temperature/stop(保留且可用),而非适配器拒绝的字段。如果 chat-prefix completion 或 strict 模式成为产品功能,重新添加将随适配器/端点工作一起落地,届时契约能说明实际发生了什么,而不是「所有人都 throw」。