Merge remote-tracking branch 'origin/master' into worktree/code-diff-card

This commit is contained in:
creatixchu committed 2026-09-11 16:47:45 +08:00
commit 3c632ebf66
318 files changed
+9061 -3253

No files matched your search

+2 -2
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 packages/core/system-prompt/README.md
README.md: 7e4826c4fb51873db02fd80eca632c3d1dd8d550
README.zh.md: 30f2a43908513f5fe1e7169bff7ee95c9461ea5d
README.md: a76ae5c66f0801cb85cd8cf22d0c3ebbc68e65b8
README.zh.md: 98c6e733399c954c5bbc56b905aefcc6b080381d
+4 -2
View File
@@ -63,6 +63,8 @@ ctx.systemPrompt.section({
})
```
Set `interpolate: false` on a section to preserve its text literally, including `{{…}}` groups in generated tool documentation. Other sections interpolate variables by default.
### Contribute a prompt variable
Variables are referenced from section text as `{{name}}` and resolved at each assembly; scoped variables shadow a same-named global for that agent. The loop supplies `model` and `cwd`; any plugin can register the facts it owns.
@@ -102,7 +104,7 @@ The package is a registry plus a cooperative assembly pipeline. One `assemble()`
### Assembly and rendering
Assembly resolves and renders in two stages: `assemble()` returns sections with resolved-but-uninterpolated text, the ordered tool schemas, and every registered variable resolved against the context, while `renderPrompt()` interpolates `{{variable}}` references, drops empty sections, and joins with blank lines — strictly, an unknown reference, a registered-but-valueless reference, or a malformed complete group throws, because a malformed prompt is worse than a loud failure. `toolOrder` canonicalizes the collected tools before the waterfall (registration order is a plugin-load artifact); a waterfall listener that mutates the list owns the determinism of what it emits.
Assembly resolves and renders in two stages: `assemble()` returns sections with resolved-but-uninterpolated text, the ordered tool schemas, and every registered variable resolved against the context, while `renderPrompt()` interpolates `{{variable}}` references unless a section sets `interpolate: false`, drops empty sections, and joins with blank lines — strictly, an unknown reference, a registered-but-valueless reference, or a malformed complete group throws, because a malformed prompt is worse than a loud failure. `toolOrder` canonicalizes the collected tools before the waterfall (registration order is a plugin-load artifact); a waterfall listener that mutates the list owns the determinism of what it emits.
### Scoping
@@ -170,7 +172,7 @@ Prefix-stable while the visible schema set, rendering, and order are unchanged.
These limits define when prompt assembly needs special care. They are current package constraints, not a task backlog.
- **Deployment-authored prompt text is config/composition only** — this plugin owns the global persona prefix and suffix defaults, creator plugins may register agent-scoped shadows, and other sections come from the plugin that owns the fact; there is no end-user prompt-editing API.
- **No escape syntax for literal `{{…}}` braces** — every complete group is interpolated against registered variables; an escape is deferred until a real prompt needs one.
- **No inline escape syntax in interpolated text** — use `interpolate: false` when a whole section must preserve literal braces.
- **`toolOrder` misconfiguration surfaces at prompt assembly (the first turn), not at boot** — only shape violations throw at config load.
+4 -2
View File
@@ -63,6 +63,8 @@ ctx.systemPrompt.section({
})
```
在段上设置 `interpolate: false` 可原样保留文本,包括生成的工具文档中的 `{{…}}` 组。其他段默认执行变量插值。
### 贡献提示词变量
变量在段文本中以 `{{name}}` 引用,并在每次组装时解析;带作用域变量会为该 agent 遮蔽同名全局变量。循环提供 `model` 与 `cwd`;任何插件都可以注册自己拥有的事实。
@@ -102,7 +104,7 @@ ctx.systemPrompt.variable('cwd', ({ agent }) => agent?.session.header.cwd)
### 组装与渲染
组装分两阶段完成求值与渲染:`assemble()` 返回文本已求值但尚未插值的段、有序工具 schema,以及每个已注册变量按当前上下文求得的值;`renderPrompt()` 插值 `{{variable}}` 引用、删除空段并用空行连接——严格规则:未知引用、已注册但无值的引用或格式错误的完整组都会抛出,因为格式错误的提示词比明确失败更糟。`toolOrder` 在 waterfall 分发前规范化收集到的工具(注册顺序只是插件加载产物);修改列表的 waterfall 监听器对其输出的确定性负责。
组装分两阶段完成求值与渲染:`assemble()` 返回文本已求值但尚未插值的段、有序工具 schema,以及每个已注册变量按当前上下文求得的值;`renderPrompt()` 对未设置 `interpolate: false` 的段插值 `{{variable}}` 引用、删除空段并用空行连接——严格规则:未知引用、已注册但无值的引用或格式错误的完整组都会抛出,因为格式错误的提示词比明确失败更糟。`toolOrder` 在 waterfall 分发前规范化收集到的工具(注册顺序只是插件加载产物);修改列表的 waterfall 监听器对其输出的确定性负责。
### 作用域
@@ -170,7 +172,7 @@ schema token 在每次请求中重复。限制工具会为该 agent 移除其全
这些限制说明提示词组装何时需要特别留意。它们是当前包约束,不是待办事项清单。
- **部署方编写的提示词文本只来自配置/组合**:此插件拥有全局 persona 前缀与后缀默认值;创建方插件可以注册 agent 作用域的遮蔽项;其他段来自拥有相应事实的插件。不存在终端用户提示词编辑 API。
- **没有表示字面量 `{{…}}` 花括号的转义语法**:每个完整组都会按已注册变量插值;只有实际提示词需要转义时才会实现。
- **插值文本不支持行内转义语法**:整段需要保留字面花括号时,使用 `interpolate: false`。
- **`toolOrder` 配置错误在提示词组装(首轮)时出现,而不是启动时**:只有形状违规会在配置加载时抛出。
+9 -3
View File
@@ -61,9 +61,11 @@ export interface PromptSection {
/**
* Static text or a provider evaluated at each assembly with that assembly's
* {@link AssembleContext}. The text may reference `{{variable}}`s — they are
* interpolated later, by {@link renderPrompt}.
* interpolated later, by {@link renderPrompt}, unless `interpolate` is false.
*/
readonly text: string | ((context: AssembleContext) => string)
/** Whether to interpolate prompt variables. Defaults to true; false preserves literal text. */
readonly interpolate?: boolean
/**
* Treat this contribution as the complete system prompt. Assembly still
* runs the cooperative waterfall so tools, contexts, and variables can be
@@ -89,6 +91,8 @@ export interface AssembledSection {
name: string
/** The resolved (but not yet interpolated) section text. */
text: string
/** Whether to interpolate prompt variables. Defaults to true; false preserves literal text. */
interpolate?: boolean
}
/** One resolved dynamic context contribution. */
@@ -264,7 +268,8 @@ export interface Config {
/**
* Interpolate strict `{{variable}}` references, drop empty sections, and join
* the rest with blank lines. Malformed, unknown, or undefined references throw;
* the rest with blank lines. Sections with `interpolate: false` retain literal
* text. Malformed, unknown, or undefined references in other sections throw;
* a lone `{{` without any later `}}` is literal prose, and substituted values
* are not scanned again.
* @param assembly - the assembly whose sections and variables to render.
@@ -272,7 +277,7 @@ export interface Config {
*/
export function renderPrompt(assembly: PromptAssembly): string {
return assembly.sections
.map(section => interpolate(section, assembly.variables, 'section'))
.map(section => section.interpolate === false ? section.text : interpolate(section, assembly.variables, 'section'))
.filter(text => text.length > 0)
.join('\n\n')
}
@@ -597,6 +602,7 @@ export class SystemPrompt extends Service {
const assembled = {
name: section.name,
text: typeof section.text === 'function' ? section.text(context) : section.text,
...section.interpolate !== undefined ? { interpolate: section.interpolate } : {},
}
if (section.complete === true) completeSection = { ...assembled }
return assembled
@@ -579,6 +579,25 @@ describe('SystemPrompt', () => {
expect(renderPrompt(await ctx.systemPrompt.assemble())).toBe(`${IDENTITY}\n\nYou run on deepseek-v4 in /work.`)
})
it.each([
[false, false],
[false, true],
[true, false],
[true, true],
])('preserves literal section text with complete=%s and dynamic=%s', async (complete, dynamic) => {
const ctx = new Context()
try {
await ctx.plugin(SystemPrompt, { includeHarnessIdentity: false, personaPrefix: '{{model}}' })
ctx.systemPrompt.variable('model', () => 'actual-model')
const text = '{{item}} {{model}} {{ model }} {{nested{{item}}}}'
ctx.systemPrompt.section({ name: 'literal', order: 1, text: dynamic ? () => text : text, interpolate: false, complete })
expect(renderPrompt(await ctx.systemPrompt.assemble()))
.toBe(complete ? text : `actual-model\n\n${text}`)
} finally {
await ctx.fiber.dispose()
}
})
it('lets a waterfall listener add or override variables before render', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
+2 -2
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 packages/core/tools/README.md
README.md: a62e89c1bde0b96d5ba6b0e7c8f27d2af87f4340
README.zh.md: d10ce3913cc9eb24e417f61c5b9f081a40b50cc3
README.md: 17e2eb05bac152c75f7a906bf6e15f26517d38dc
README.zh.md: cd08e9282e2ac6e75c1694d9989a4079d65985b5
+1 -1
View File
@@ -170,7 +170,7 @@ Prefix-stable while visible definitions and their order are unchanged. Registrat
#### What the model sees
PTC 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 instructions identify generated declarations as program-only bindings. When the current `bash` parameter schema accepts the example arguments, they also show a complete `run_code` call around `tools.bash(...)`. The `tools:sdk` section uses first-party order 5000. `both` exposes normal schemas and this PTC mode API; under `ptc` the prompt also carries the `tools:ptc-only` rule earlier in the first-party order, so the model reads which tools it may call before it reads what each one is for.
PTC 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 instructions identify generated declarations as program-only bindings. When the current `bash` parameter schema accepts the example arguments, they also show a complete `run_code` call around `tools.bash(...)`. The `tools:sdk` section uses first-party order 5000 and disables prompt-variable interpolation, preserving literal `{{…}}` text in tool descriptions and schemas for both runtime languages. `both` exposes normal schemas and this PTC mode API; under `ptc` the prompt also carries the `tools:ptc-only` rule earlier in the first-party order, so the model reads which tools it may call before it reads what each one is for.
##### TypeScript PTC mode SDK instructions with bash
+1 -1
View File
@@ -170,7 +170,7 @@ ctx.tools.register(defineTool({
#### 模型看到什么
PTC mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tools)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块。TypeScript 说明会把生成声明明确标为只能在程序内使用的绑定。当当前 `bash` 参数 schema 接受示例参数时,说明还会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`tools:sdk` 段使用 first-party 顺序 5000。`both` 会同时公开普通 schema 与此 PTC mode API;在 `ptc` 下,提示词还会带上处于更早 first-party 顺序的 `tools:ptc-only` 规则,让模型先读到「可以调用哪些工具」再读「每个工具做什么」。
PTC mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tools)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块。TypeScript 说明会把生成声明明确标为只能在程序内使用的绑定。当当前 `bash` 参数 schema 接受示例参数时,说明还会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`tools:sdk` 段使用 first-party 顺序 5000,并关闭提示词变量插值,使两种运行时语言都原样保留工具描述和 schema 中的 `{{…}}` 文本。`both` 会同时公开普通 schema 与此 PTC mode API;在 `ptc` 下,提示词还会带上处于更早 first-party 顺序的 `tools:ptc-only` 规则,让模型先读到「可以调用哪些工具」再读「每个工具做什么」。
##### 带 bash 的 TypeScript PTC mode SDK 说明
+4 -3
View File
@@ -13,7 +13,7 @@ import { HarnessError } from '@deepseek-ai/dsh-llm'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { UserMessage } from '@deepseek-ai/dsh-session'
import { assertNever, deepFreeze, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
import type { ToolProviderResult } from '@deepseek-ai/dsh-system-prompt'
import type { PromptSection, ToolProviderResult } from '@deepseek-ai/dsh-system-prompt'
import type { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
// Type-only: makes `ctx.get('approval')` resolve to the ApprovalService
// augmentation. The seam stays optional at runtime — see `serviceAsk`.
@@ -844,7 +844,7 @@ export class ToolRuntime extends Service {
* `both` renders empty: native calls do execute there, so the rule is false.
* @returns the section registration.
*/
private collapseSection(): { name: string; order: number; text: (context: { scope?: ScopeKey }) => string } {
private collapseSection(): PromptSection {
return {
name: 'tools:ptc-only',
order: this.ctx.systemPrompt.getSectionOrder('PTC_ONLY'),
@@ -864,10 +864,11 @@ export class ToolRuntime extends Service {
* dropped from the rendered prompt.
* @returns the section registration.
*/
private sdkSection(): { name: string; order: number; text: (context: { scope?: ScopeKey }) => string } {
private sdkSection(): PromptSection {
return {
name: 'tools:sdk',
order: this.ctx.systemPrompt.getSectionOrder('TOOLS_SDK'),
interpolate: false,
// Regenerate from the calling scope's visible tools in stable order.
text: (context) => {
const mode = this.modeFor(context.scope)
+20
View File
@@ -0,0 +1,20 @@
/** Tool documentation containing literal template syntax for recorded PTC replay. */
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'literal-sdk'
export const inject = ['tools']
export function apply(ctx: Context): void {
ctx.tools.register(defineTool({
name: 'template_echo',
description: 'Echo template text such as {{item}}, {{model}}, or {{ item }} without substitution.',
parameters: { text: { type: 'string', required: true } },
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
execute: args => Promise.resolve(args.text),
}))
}
+33 -1
View File
@@ -3,7 +3,7 @@ import { Context } from '@deepseek-ai/cordis'
import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
import { createScope } from '@deepseek-ai/dsh-scope'
import type { Scope } from '@deepseek-ai/dsh-scope'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import ToolRuntime, { CodeRunFailedError, RUN_CODE_NAME, TOOL_ABORTED_BEFORE_DISPATCH, defineContentToolFixture, defineTool } from '@deepseek-ai/dsh-tools'
@@ -117,6 +117,38 @@ async function runCode(
}
describe('mode-aware wire contribution', () => {
it.each([
{ mode: 'ptc', language: 'typescript' },
{ mode: 'both', language: 'typescript' },
{ mode: 'ptc', language: 'python' },
{ mode: 'both', language: 'python' },
] as const)('preserves literal braces in the $language SDK under $mode', async ({ mode, language }) => {
const { ctx, systemPrompt } = await setup({ mode, runtime: { language } })
try {
systemPrompt.variable('model', () => 'actual-model')
const description = 'Expand {{item}} with {{model}} or {{ model }}.'
ctx.tools.register(defineTool({
name: 'template',
description,
parameters: { value: { type: 'string', description, enum: ['{{item}}', '{{model}}'], required: true } },
output: {
schema: { type: 'string', enum: ['{{item}}', '{{model}}'] },
render: (_args, value) => [{ type: 'text', text: value }],
},
execute: args => Promise.resolve(args.value),
}))
const assembly = await systemPrompt.assemble()
const sdk = assembly.sections.find(section => section.name === 'tools:sdk')
expect(sdk).toBeDefined()
const prompt = renderPrompt(assembly)
expect(prompt).toContain(description)
expect(prompt).toContain(sdk!.text)
expect(prompt).not.toContain('actual-model')
} finally {
await ctx.fiber.dispose()
}
})
it("mode 'native' contributes every schema, no run_code, no SDK section — and needs no runtime", async () => {
const { ctx, systemPrompt } = await setup({ mode: 'native', runtime: false })
registerEcho(ctx)