dsh-plugin-dev-kb 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/kb/INDEX.md +21 -5
- package/kb/README.md +11 -10
- package/kb/extra/AGENTS.md +4 -4
- package/kb/extra/cookbook/adding-a-remote-api.md +197 -0
- package/kb/extra/cookbook/adding-a-remote-api.zh.md +197 -0
- package/kb/extra/cookbook/adding-a-vendored-package.md +2 -2
- package/kb/extra/cookbook/adding-a-vendored-package.zh.md +2 -2
- package/kb/extra/deepseek-llm-api-wire-extensions.md +163 -0
- package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +163 -0
- package/kb/extra/development.md +8 -14
- package/kb/extra/development.zh.md +8 -14
- package/kb/extra/event-producer-consumer.md +55 -48
- package/kb/extra/event-producer-consumer.zh.md +58 -51
- package/kb/extra/glossary.md +1 -1
- package/kb/extra/glossary.zh.md +1 -1
- package/kb/extra/graph-atlas.md +0 -2
- package/kb/extra/graph-atlas.zh.md +0 -2
- package/kb/extra/i18n/README.md +4 -4
- package/kb/extra/i18n/README.zh.md +4 -4
- package/kb/extra/i18n/style-samples.md +2 -2
- package/kb/extra/module-graph.md +646 -926
- package/kb/extra/module-graph.zh.md +648 -928
- package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +2 -2
- package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +2 -2
- package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +2 -2
- package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +2 -2
- package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +2 -2
- package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +2 -2
- package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +1 -1
- package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +1 -1
- package/kb/extra/rescope.md +2 -2
- package/kb/extra/rescope.zh.md +2 -2
- package/kb/extra/subsystems/agent-team.md +28 -4
- package/kb/extra/subsystems/agent-team.zh.md +28 -4
- package/kb/extra/subsystems/attachment.md +168 -7
- package/kb/extra/subsystems/attachment.zh.md +168 -7
- package/kb/extra/subsystems/extensions.md +18 -0
- package/kb/extra/subsystems/extensions.zh.md +18 -0
- package/kb/extra/subsystems/feedback.md +4 -4
- package/kb/extra/subsystems/feedback.zh.md +4 -4
- package/kb/extra/subsystems/todo.md +32 -0
- package/kb/extra/subsystems/todo.zh.md +32 -0
- package/kb/extra/subsystems/webhook.md +70 -0
- package/kb/extra/subsystems/webhook.zh.md +70 -0
- package/kb/extra/testing.md +15 -10
- package/kb/extra/testing.zh.md +13 -8
- package/kb/extra/web-styling.md +4 -0
- package/kb/extra/web-styling.zh.md +4 -0
- package/kb/meta/search-index.json +309 -177
- package/kb/meta/site-pages.txt +183 -167
- package/kb/meta/source.json +5 -5
- package/kb/meta/topics.md +14 -6
- package/kb/site/develop/basic/publish.md +2 -2
- package/kb/site/develop/basic/tool.md +1 -1
- package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +5 -4
- package/kb/site/develop/framework/events.md +1 -1
- package/kb/site/develop/practice/dynamic-cordis.md +17 -0
- package/kb/site/develop/practice/llm-adapter.md +4 -3
- package/kb/site/en/develop/basic/publish.md +2 -2
- package/kb/site/en/develop/basic/tool.md +1 -1
- package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +5 -4
- package/kb/site/en/develop/framework/events.md +1 -1
- package/kb/site/en/develop/practice/dynamic-cordis.md +17 -0
- package/kb/site/en/develop/practice/llm-adapter.md +4 -3
- package/kb/site/en/guide/github-review.md +104 -0
- package/kb/site/en/guide/mcp-memory.md +103 -0
- package/kb/site/en/guide/network-proxy.md +87 -0
- package/kb/site/en/guide/providers.md +70 -17
- package/kb/site/en/guide/python-sdk.md +80 -34
- package/kb/site/en/guide/schedule.md +23 -0
- package/kb/site/en/reference/agent-lifecycle.md +6 -4
- package/kb/{extra → site/en/reference}/api-gateway.md +12 -10
- package/kb/site/en/reference/capability-seams.md +128 -73
- package/kb/site/en/reference/config-catalog.md +481 -360
- package/kb/site/en/reference/cookbook/adding-a-package.md +3 -4
- package/kb/site/en/reference/cookbook/adding-a-settings-card.md +12 -10
- package/kb/site/en/reference/cookbook/adding-a-tool.md +11 -4
- package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +1 -1
- package/kb/site/en/reference/cookbook/extension-cookbook.md +20 -17
- package/kb/site/en/reference/cordis-api/inherited.md +1 -1
- package/kb/site/en/reference/cordis-primer.md +2 -1
- package/kb/site/en/reference/index.md +30 -11
- package/kb/site/en/reference/persistence-catalog.md +148 -80
- package/kb/site/en/reference/subsystems/approval.md +10 -10
- package/kb/site/en/reference/subsystems/client-modules.md +58 -16
- package/kb/site/en/reference/subsystems/code-runtime.md +10 -6
- package/kb/site/en/reference/subsystems/commands.md +25 -16
- package/kb/site/en/reference/subsystems/compaction.md +11 -11
- package/kb/site/en/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +50 -24
- package/kb/site/en/reference/subsystems/core.md +156 -17
- package/kb/site/en/reference/subsystems/credentials.md +44 -3
- package/kb/site/en/reference/subsystems/filesystem.md +12 -2
- package/kb/site/en/reference/subsystems/goal.md +1 -1
- package/kb/site/en/reference/subsystems/index.md +7 -2
- package/kb/site/en/reference/subsystems/jobs.md +1 -1
- package/kb/site/en/reference/subsystems/llm-streaming.md +154 -12
- package/kb/site/en/reference/subsystems/permission-presets.md +6 -6
- package/kb/site/en/reference/subsystems/persistence.md +185 -175
- package/kb/site/en/reference/subsystems/plan.md +2 -2
- package/kb/site/en/reference/subsystems/sandbox.md +2 -0
- package/kb/site/en/reference/subsystems/schedule.md +9 -3
- package/kb/site/en/reference/subsystems/session-projection.md +115 -48
- package/kb/site/en/reference/subsystems/session-query.md +28 -14
- package/kb/site/en/reference/subsystems/session-reference.md +53 -8
- package/kb/site/en/reference/subsystems/session-telemetry.md +8 -8
- package/kb/site/en/reference/subsystems/session-title.md +6 -6
- package/kb/site/en/reference/subsystems/session.md +401 -99
- package/kb/site/en/reference/subsystems/settings.md +101 -6
- package/kb/site/en/reference/subsystems/skills.md +23 -0
- package/kb/site/en/reference/subsystems/slots.md +178 -0
- package/kb/site/en/reference/subsystems/spill.md +2 -2
- package/kb/site/en/reference/subsystems/storage.md +34 -3
- package/kb/site/en/reference/subsystems/subagent.md +122 -109
- package/kb/site/en/reference/subsystems/system-prompt.md +17 -4
- package/kb/site/en/reference/subsystems/token-meter.md +27 -12
- package/kb/site/en/reference/subsystems/tools.md +39 -39
- package/kb/site/en/reference/subsystems/typert.md +62 -55
- package/kb/site/en/reference/subsystems/user-questions.md +33 -33
- package/kb/site/en/reference/subsystems/web-client.md +98 -0
- package/kb/site/en/reference/subsystems/web-server.md +11 -5
- package/kb/site/en/reference/subsystems/web.md +7 -1
- package/kb/site/en/reference/subsystems/workspace.md +102 -9
- package/kb/site/en/reference/tool-catalog.md +86 -82
- package/kb/site/en/reference/tool-execution-pipeline.md +1 -1
- package/kb/site/guide/github-review.md +104 -0
- package/kb/site/guide/mcp-memory.md +103 -0
- package/kb/site/guide/network-proxy.md +87 -0
- package/kb/site/guide/providers.md +70 -17
- package/kb/site/guide/python-sdk.md +87 -41
- package/kb/site/guide/schedule.md +23 -0
- package/kb/site/reference/agent-lifecycle.md +6 -4
- package/kb/{extra/api-gateway.zh.md → site/reference/api-gateway.md} +12 -10
- package/kb/site/reference/capability-seams.md +128 -73
- package/kb/site/reference/config-catalog.md +481 -360
- package/kb/site/reference/cookbook/adding-a-package.md +3 -4
- package/kb/site/reference/cookbook/adding-a-settings-card.md +12 -10
- package/kb/site/reference/cookbook/adding-a-tool.md +11 -4
- package/kb/site/reference/cookbook/adding-an-llm-adapter.md +1 -1
- package/kb/site/reference/cookbook/extension-cookbook.md +20 -17
- package/kb/site/reference/cordis-api/inherited.md +1 -1
- package/kb/site/reference/cordis-primer.md +2 -1
- package/kb/site/reference/index.md +30 -11
- package/kb/site/reference/persistence-catalog.md +148 -80
- package/kb/site/reference/subsystems/approval.md +10 -10
- package/kb/site/reference/subsystems/client-modules.md +58 -16
- package/kb/site/reference/subsystems/code-runtime.md +10 -6
- package/kb/site/reference/subsystems/commands.md +25 -16
- package/kb/site/reference/subsystems/compaction.md +11 -11
- package/kb/site/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +50 -24
- package/kb/site/reference/subsystems/core.md +156 -17
- package/kb/site/reference/subsystems/credentials.md +44 -3
- package/kb/site/reference/subsystems/filesystem.md +12 -2
- package/kb/site/reference/subsystems/goal.md +1 -1
- package/kb/site/reference/subsystems/index.md +7 -2
- package/kb/site/reference/subsystems/jobs.md +1 -1
- package/kb/site/reference/subsystems/llm-streaming.md +154 -12
- package/kb/site/reference/subsystems/permission-presets.md +5 -5
- package/kb/site/reference/subsystems/persistence.md +184 -174
- package/kb/site/reference/subsystems/plan.md +2 -2
- package/kb/site/reference/subsystems/schedule.md +9 -3
- package/kb/site/reference/subsystems/session-projection.md +115 -48
- package/kb/site/reference/subsystems/session-query.md +28 -14
- package/kb/site/reference/subsystems/session-reference.md +53 -8
- package/kb/site/reference/subsystems/session-telemetry.md +8 -8
- package/kb/site/reference/subsystems/session-title.md +6 -6
- package/kb/site/reference/subsystems/session.md +401 -99
- package/kb/site/reference/subsystems/settings.md +101 -6
- package/kb/site/reference/subsystems/skills.md +23 -0
- package/kb/site/reference/subsystems/slots.md +178 -0
- package/kb/site/reference/subsystems/spill.md +2 -2
- package/kb/site/reference/subsystems/storage.md +34 -3
- package/kb/site/reference/subsystems/subagent.md +122 -109
- package/kb/site/reference/subsystems/system-prompt.md +17 -4
- package/kb/site/reference/subsystems/token-meter.md +27 -12
- package/kb/site/reference/subsystems/tools.md +39 -39
- package/kb/site/reference/subsystems/typert.md +62 -55
- package/kb/site/reference/subsystems/user-questions.md +33 -33
- package/kb/site/reference/subsystems/web-client.md +98 -0
- package/kb/site/reference/subsystems/web-server.md +11 -5
- package/kb/site/reference/subsystems/web.md +7 -1
- package/kb/site/reference/subsystems/workspace.md +102 -9
- package/kb/site/reference/tool-catalog.md +85 -81
- package/kb/site/reference/tool-execution-pipeline.md +1 -1
- package/package.json +2 -2
- package/skills/dsh-plugin-dev-kb.md +8 -6
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# 实操手册:新增一个 Remote API
|
|
2
|
+
|
|
3
|
+
[English](adding-a-remote-api.md) | 中文
|
|
4
|
+
|
|
5
|
+
新增或改动一个 `ctx.remote` 端点按本页五步走:声明方法、声明失败、在包上注册、在 Client 消费、写测试。decorator 语义、lookup 解析、生成管线与 `/api` 路由属于机制,由 [API Gateway 参考](../api-gateway.zh.md)负责;本页给的是每一步的动作与必须遵守的约定。为什么是这套编程面,见 [Typert Remote 方法调用 Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md);为什么失败面是单个 `RemoteError` 加一张码表,见[失败词汇 Agent Note](../../.agents/notes/implemented/architecture/2026-08-28-ctx-remote-failure-vocabulary.zh.md)。
|
|
6
|
+
|
|
7
|
+
## 1. 声明 API
|
|
8
|
+
|
|
9
|
+
owner 是一个 Host 侧 Cordis 服务:继承 `TypertRemoteService` 把 service 键与 wire namespace 一起绑定,再用 `@Remote` 标注对外暴露的方法。业务方法的签名若已符合 wire 约定就直接标注它本身;只有形态需要调整(补 `signal`、换参数顺序、换导出名)才写一个 `remoteExport*` adapter,由它调用不改名的业务方法。lookup 对象(`Agent`、`Session`)只能占顶层参数位,支持协作式取消的方法把 `signal: AbortSignal` 放在最后一位。
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
13
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
14
|
+
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
|
|
15
|
+
|
|
16
|
+
/** One stored note as a Client reads it. */
|
|
17
|
+
export interface NoteRow {
|
|
18
|
+
readonly noteId: string
|
|
19
|
+
readonly title: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module '@deepseek-ai/cordis' {
|
|
23
|
+
interface Context {
|
|
24
|
+
notesController: NotesController
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class NotesController extends TypertRemoteService {
|
|
29
|
+
constructor(ctx: Context) {
|
|
30
|
+
super(ctx, 'notesController', { namespace: 'notes' })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param agent - lookup parameter the Gateway resolves from its wire identity.
|
|
35
|
+
* @param signal - carrier cancellation, always the final parameter.
|
|
36
|
+
* @returns the notes this Agent's session owns.
|
|
37
|
+
*/
|
|
38
|
+
@Remote('list')
|
|
39
|
+
async remoteExportList(agent: Agent, signal: AbortSignal): Promise<NoteRow[]> {
|
|
40
|
+
return await this.list(agent, signal)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The in-process API the adapter above delegates to, unchanged by it. */
|
|
44
|
+
async list(agent: Agent, signal: AbortSignal): Promise<NoteRow[]> {
|
|
45
|
+
signal.throwIfAborted()
|
|
46
|
+
return await Promise.resolve([{ noteId: `${agent.id}-1`, title: 'draft' }])
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 2. 声明失败
|
|
52
|
+
|
|
53
|
+
Remote 失败只有一个类 `RemoteError`:域码经 declaration merging 进 `RemoteErrorDetailsMap`,失败点直接 `throw new RemoteError(code, message, details)`。不要建域异常类家族,也不要写出口映射函数;与本端点无关的异常不预先归类,Gateway 会兜底折成 `gateway/internal`。只有"把任意 provider 异常归为一个域码"这一种场景才写 `catch`,并把原始异常挂在 `cause` 上。
|
|
54
|
+
|
|
55
|
+
码名是 `<域>/<理由>`,声明落点四条:
|
|
56
|
+
|
|
57
|
+
- 只有一个生产者:声明落生产者包,紧挨抛出点。
|
|
58
|
+
- 多个包共同生产:落双方共同依赖的最低层域包(`session/not-found` 在 `core/session`,`workspace/not-found` 在 `dsh-workspace`)。
|
|
59
|
+
- 载体码 `gateway/bad-request`、`gateway/cancelled`、`gateway/internal` 已在 protocol 声明,Gateway 基础设施码已在 gateway 声明——直接用,不要复制。
|
|
60
|
+
- 不上 wire 的本地失败不进码表,用调用方自己的类型表达。
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
|
|
64
|
+
|
|
65
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
66
|
+
interface RemoteErrorDetailsMap {
|
|
67
|
+
/** No stored note carries that id. */
|
|
68
|
+
'note/not-found': { readonly noteId: string }
|
|
69
|
+
/** The store refused an otherwise valid write. */
|
|
70
|
+
'note/rejected': { readonly noteId: string }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare const stored: ReadonlyMap<string, string>
|
|
75
|
+
declare function persist(noteId: string, title: string): Promise<void>
|
|
76
|
+
|
|
77
|
+
export async function rename(noteId: string, title: string): Promise<void> {
|
|
78
|
+
if (!stored.has(noteId)) {
|
|
79
|
+
throw new RemoteError('note/not-found', `no note "${noteId}"`, { noteId })
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await persist(noteId, title)
|
|
83
|
+
} catch (error: unknown) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
85
|
+
throw new RemoteError('note/rejected', message, { noteId }, { cause: error })
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 3. 在包上注册
|
|
91
|
+
|
|
92
|
+
`@Remote` 必须落在一个 Loader entry 插件包里;owner 是抽象 seam 时把控制器放进 `packages/api/` 下的对应包。包清单要补两个生成入口与 protocol 的 peer 依赖,Client 侧则由 `@deepseek-ai/dsh-api-remotes` 的 assembly 挂载该贡献并按需转口类型词汇。两个入口分别指向哪个生成产物、生成管线如何排序,见 [API Gateway 参考](../api-gateway.zh.md)。
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"exports": {
|
|
97
|
+
"./typert": { "types": "./lib/typert.host.d.ts", "default": "./lib/typert.host.js" },
|
|
98
|
+
"./remote": { "types": "./lib/typert.remote-client.d.ts", "default": "./lib/typert.remote-client.js" }
|
|
99
|
+
},
|
|
100
|
+
"peerDependencies": { "@deepseek-ai/dsh-typert-protocol": "workspace:^" },
|
|
101
|
+
"devDependencies": { "@deepseek-ai/dsh-typert-protocol": "workspace:^" }
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
改动了签名、码表、namespace 或导出名之后重跑 `pnpm run build:lib`,Client 才拿得到新的声明与 codec;只改实现体不需要重新生成。
|
|
106
|
+
|
|
107
|
+
## 4. 在 Client 消费
|
|
108
|
+
|
|
109
|
+
调用插件在 `inject` 里同时声明 `remote` 与 `remote.<namespace>`,调用点直写 `ctx.remote.<namespace>.<method>(...)`:不要用 `Pick<ClientRemote, …>` 窄化、不要手写方法签名、不要造 wire 中转对象。结果是 `RemoteResult<T>`,就地 `if (!result.ok)` 分支,判 `code` 而不是 `instanceof`——code 分支会自动窄化 `details`。异常流的站点写 `throw result.error`(它是真 Error);接住它的上层用 `isRemoteFailure` 区分 Remote 失败与本地缺陷,本地缺陷继续往上抛。不要写防御性 catch:Remote 调用不 reject,装配错误就该炸。
|
|
110
|
+
|
|
111
|
+
Host 的固定事实读 `ctx.remote.$host`:`home` 与 `isLoopback` 是普通值读取,没有订阅也没有 generation 计数器,`home` 在第一帧 ready 之前是 `undefined`;重连后的刷新走 `ctx.on('connection/reset')` 或各域自己的 remote 事件。调用方 abort 掉一次一元调用时,结果落在错误分支上的 `gateway/cancelled`,而不是抛出。
|
|
112
|
+
|
|
113
|
+
```ts ignore-check
|
|
114
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
115
|
+
import { isRemoteFailure } from '@deepseek-ai/dsh-api-gateway/client'
|
|
116
|
+
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
|
117
|
+
|
|
118
|
+
export const inject = ['remote', 'remote.notes']
|
|
119
|
+
|
|
120
|
+
declare const ctx: Context
|
|
121
|
+
|
|
122
|
+
/** Store-side read: the error branch is handled where the code is meaningful. */
|
|
123
|
+
export async function noteTitles(): Promise<readonly string[]> {
|
|
124
|
+
const result = await ctx.remote.notes.list()
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
if (result.error.code === 'note/not-found') return []
|
|
127
|
+
throw result.error
|
|
128
|
+
}
|
|
129
|
+
return result.value.map(row => row.title)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Action-side: a Remote failure becomes copy; a local fault keeps crashing. */
|
|
133
|
+
export async function renderTitles(): Promise<string> {
|
|
134
|
+
try {
|
|
135
|
+
return (await noteTitles()).join(', ')
|
|
136
|
+
} catch (error: unknown) {
|
|
137
|
+
if (!isRemoteFailure(error)) throw error
|
|
138
|
+
return `unavailable (${error.code})`
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Fixed Host facts as plain reads. */
|
|
143
|
+
export function hostLabel(): string {
|
|
144
|
+
const { home, isLoopback } = ctx.remote.$host
|
|
145
|
+
return home ?? (isLoopback ? 'local host' : 'remote host')
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 5. 测试
|
|
150
|
+
|
|
151
|
+
owner 侧断言抛出的码:捕获后用 `remoteErrorOf` 取出失败,再用 `toMatchObject` 比对 `code` 与需要的 `details` 字段——不要用 `toEqual` 深比对错误对象,也不要断言 `instanceof`。
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol'
|
|
155
|
+
import { expect, it } from 'vitest'
|
|
156
|
+
|
|
157
|
+
declare function rename(noteId: string, title: string): Promise<void>
|
|
158
|
+
|
|
159
|
+
it('refuses an unknown note before writing', async () => {
|
|
160
|
+
const failure = await rename('n-404', 'fresh title').catch((error: unknown) => error)
|
|
161
|
+
|
|
162
|
+
expect(remoteErrorOf(failure)).toMatchObject({
|
|
163
|
+
code: 'note/not-found',
|
|
164
|
+
details: { noteId: 'n-404' },
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Client 侧的替身返回真实例:`RemoteError` 与 `TestRemote` 的值 import 一律取自 `@deepseek-ai/dsh-client-test-runtime`,因为从 `api-remotes` facade 值 import 会拉起尚未构建的装配链。`TestRemote.$host` 是普通字段,spec 直接赋值即可。
|
|
170
|
+
|
|
171
|
+
```ts ignore-check
|
|
172
|
+
import { Context } from '@deepseek-ai/cordis'
|
|
173
|
+
import { RemoteError, TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
|
|
174
|
+
import { expect, it } from 'vitest'
|
|
175
|
+
|
|
176
|
+
it('renders the failure code the Host reported', async () => {
|
|
177
|
+
const ctx = new Context()
|
|
178
|
+
const remote = new TestRemote(ctx, {
|
|
179
|
+
notes: {
|
|
180
|
+
list: () => Promise.resolve({
|
|
181
|
+
ok: false as const,
|
|
182
|
+
error: new RemoteError('note/not-found', 'no note "n-404"', { noteId: 'n-404' }),
|
|
183
|
+
}),
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
remote.$host = { home: '/home/fixture', isLoopback: true }
|
|
187
|
+
|
|
188
|
+
await expect(ctx.remote.notes.list()).resolves.toMatchObject({ error: { code: 'note/not-found' } })
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 验证
|
|
193
|
+
|
|
194
|
+
1. `pnpm run build:lib`:签名、码表、namespace 或导出名变过就必须重跑,Client 声明与 codec 由它产出。
|
|
195
|
+
2. `pnpm run typecheck`:Host 与 Client 两个 program 都过一遍,码表的 merge 落点错了会在这里红。
|
|
196
|
+
3. 点名跑两侧 spec:`npx vitest run <owner spec> <client spec>`。
|
|
197
|
+
4. 端点属于产品可见面时补一条录制会话快照,规则见[测试策略](../testing.zh.md)。
|
|
@@ -8,7 +8,7 @@ When the harness needs another upstream Cordis package (e.g. `@cordisjs/plugin-h
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
vendor/<dir>/
|
|
11
|
-
package.json # from upstream;
|
|
11
|
+
package.json # from upstream; rescope the name, keep exports/type (publishable release member, no private flag)
|
|
12
12
|
tsconfig.json # extends ../../tsconfig.base.json (see configuration below)
|
|
13
13
|
src/ # the upstream src/ verbatim
|
|
14
14
|
README.md LICENSE # if upstream ships them
|
|
@@ -29,7 +29,7 @@ vendor/<dir>/
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
`package.json` invariants:
|
|
32
|
+
`package.json` invariants: rescope the `name` ([mapping](../rescope.md)) while keeping upstream's `exports`/`type`, point declaration metadata at `lib/types`, publish `.d.ts` and `.d.ts.map` declaration outputs, and list its cordis deps in `peerDependencies` (matching the upstream manifest). Vendored packages are publishable release members, so they must NOT set `private: true` and must set `publishConfig.access: public`; the `version` field follows the harness release sequence (see [vendor/README.md](../../vendor/README.md)). Transitive upstream deps must themselves be vendored or already present — vendoring one package often means vendoring its dependency tree (e.g. `@cordisjs/plugin-http` pulls `@cordisjs/fetch-file`).
|
|
33
33
|
|
|
34
34
|
Local relative imports/exports in vendored TypeScript source use explicit `.ts` specifiers after copying. This is a repo-local build difference from upstream: `rewriteRelativeImportExtensions` emits `.js` runtime imports while declarations keep explicit `.ts` specifiers that NodeNext/Node16 TypeScript consumers can resolve.
|
|
35
35
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
vendor/<dir>/
|
|
11
|
-
package.json # from upstream;
|
|
11
|
+
package.json # from upstream; rescope the name, keep exports/type (publishable release member, no private flag)
|
|
12
12
|
tsconfig.json # extends ../../tsconfig.base.json (see configuration below)
|
|
13
13
|
src/ # the upstream src/ verbatim
|
|
14
14
|
README.md LICENSE # if upstream ships them
|
|
@@ -29,7 +29,7 @@ vendor/<dir>/
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
`package.json`
|
|
32
|
+
`package.json` 的不变式:改写 `name` 的 scope([映射](../rescope.zh.md)),保留上游的 `exports`/`type`;声明元数据指向 `lib/types`;发布 `.d.ts` 与 `.d.ts.map` 声明输出;在 `peerDependencies` 中列出其 Cordis 依赖(与上游 manifest(元数据清单)一致)。vendored 包是可发布的 release member,因此不得设置 `private: true`,且必须设置 `publishConfig.access: public`;`version` 字段跟随 harness 发布序列(见 [vendor/README.md](../../vendor/README.md))。传递性上游依赖本身也必须被 vendor 或已存在于仓库中——vendor 一个包往往意味着 vendor 其整条依赖树(如 `@cordisjs/plugin-http` 会拉入 `@cordisjs/fetch-file`)。
|
|
33
33
|
|
|
34
34
|
vendored TypeScript 源码中的本地相对导入/导出在复制后使用显式 `.ts` 后缀。这是仓库本地构建与上游的差异:`rewriteRelativeImportExtensions` 输出 `.js` 运行时导入,而声明文件保留显式 `.ts` 后缀,使 NodeNext/Node16 的 TypeScript 消费方能够解析。
|
|
35
35
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Official DeepSeek LLM API wire extensions
|
|
2
|
+
|
|
3
|
+
English | [中文](deepseek-llm-api-wire-extensions.zh.md)
|
|
4
|
+
|
|
5
|
+
This reference defines every DeepSeek Harness-specific HTTP header and additive JSON field sent by [`@deepseek-ai/dsh-llm-deepseek`](../packages/llm/llm-deepseek/README.md) on `deepseek-official` chat-completion requests. It does not redefine fields owned by the upstream DeepSeek API. The provider-neutral LLM interface and `llm-pi-ai` do not implement these additions.
|
|
6
|
+
|
|
7
|
+
The adapter sends the additions to its resolved `baseURL`, including a configured gateway. They remain outside `messages`, system prompts, and tool schemas, so they do not add model-input tokens or alter the model-visible prefix.
|
|
8
|
+
|
|
9
|
+
## Wire namespaces and versioning
|
|
10
|
+
|
|
11
|
+
| Location | Naming | Examples |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| HTTP field names | Lowercase kebab-case; HTTP matching remains case-insensitive | `user-agent`, `x-deepseek-harness-session-id` |
|
|
14
|
+
| DeepSeek request-body extension fields | Snake case with the reserved `dsh_` prefix | `dsh_plugin_packages`, `dsh_session_log` |
|
|
15
|
+
| DSH-owned nested JSON members | Camel case | `afterSeq`, `throughSeq`, `sessionId` |
|
|
16
|
+
| Tagged values | Kebab-case strings; durable events use `domain/action` | `session-log-deepseek/delivery-accepted` |
|
|
17
|
+
|
|
18
|
+
Each body extension owns its `version` independently. A version applies only to the object that contains it; no compatibility or ordering relationship exists between versions of different fields. JSON member order is not part of the protocol.
|
|
19
|
+
|
|
20
|
+
The [`DeepSeekLlmApiExtensionRegistry`](../packages/llm/deepseek-llm-api-extensions/README.md) reserves one provider per top-level extension name. Empty or whitespace-padded names, duplicate registrations, and collisions with the base DeepSeek request fail before HTTP dispatch.
|
|
21
|
+
|
|
22
|
+
## Request headers
|
|
23
|
+
|
|
24
|
+
| Header | Presence | Value |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `user-agent` | Every provider HTTP request, including Files API operations | Application identity in `product/version (+url)` form; the default product is `deepseek-harness` |
|
|
27
|
+
| `x-deepseek-harness-user-id` | Every authorized chat-completion request | The stable anonymous UUID for the resolved Harness home |
|
|
28
|
+
| `x-deepseek-harness-session-id` | Chat-completion requests carrying a Session id | The exact request `sessionId` string |
|
|
29
|
+
| `x-deepseek-harness-compact` | Chat-completion requests whose purpose is `compaction` | The literal string `1` |
|
|
30
|
+
|
|
31
|
+
Credential failure happens before anonymous-user-id resolution, so an unauthorized request neither sends these headers nor creates the identity file. A direct request without a Session omits `x-deepseek-harness-session-id`. Session-title requests have no additional purpose header; the ordinary Session-id rule still applies when one carries a `sessionId`.
|
|
32
|
+
|
|
33
|
+
## Body-extension transaction
|
|
34
|
+
|
|
35
|
+
The adapter serializes the complete base body, including the exact `messages`, before it asks registered providers to prepare fields. A provider receives that immutable body, the request cancellation signal, and optional `sessionId` and auxiliary-call `purpose`. Returning `undefined` omits that provider's field for the request.
|
|
36
|
+
|
|
37
|
+
Prepared JSON values are detached from provider-owned state, merged as top-level siblings of the base fields, and serialized in the same HTTP body. Preparation or collision failure prevents the request. A composition without the registry sends the unextended base body.
|
|
38
|
+
|
|
39
|
+
After the configured endpoint returns HTTP 2xx, the adapter runs the prepared `accept()` transaction before reading the SSE response body. Transport failures and non-2xx responses do not accept any contribution. An acceptance failure fails the model request even though the endpoint returned 2xx. Acceptance records endpoint-level HTTP success; it does not assert that an SSE stream completed or that the endpoint persisted an extension.
|
|
40
|
+
|
|
41
|
+
## `dsh_plugin_packages`
|
|
42
|
+
|
|
43
|
+
[`@deepseek-ai/dsh-plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek/README.md) contributes the complete active Loader-backed plugin package inventory. The field is enabled by default.
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"dsh_plugin_packages": {
|
|
48
|
+
"version": 1,
|
|
49
|
+
"packages": [
|
|
50
|
+
{
|
|
51
|
+
"name": "@deepseek-ai/dsh-example",
|
|
52
|
+
"version": "0.1.1-rc.2"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Member | Type | Meaning |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `version` | `1` | Schema version for `dsh_plugin_packages` |
|
|
62
|
+
| `packages` | array | Complete active set for this request |
|
|
63
|
+
| `packages[].name` | string | Exact non-empty npm package name from the owning manifest |
|
|
64
|
+
| `packages[].version` | string | Exact non-empty package version from the same manifest |
|
|
65
|
+
|
|
66
|
+
Every request re-reads active non-group Loader entries from the host tree and, when available for the request Session, its standing agent-preset tree. Relative and absolute modules use their nearest owning manifest; bare package entries follow the Loader resolution base that activated them. A named manifest without a non-empty version fails request preparation.
|
|
67
|
+
|
|
68
|
+
The sender deduplicates exact `(name, version)` pairs and sorts first by `name`, then by `version`, with a locale-independent text comparison. Simultaneously active versions of one package remain separate entries. Receivers must not collapse the array by package name or infer package activation from array order.
|
|
69
|
+
|
|
70
|
+
Disabled, pending, failed, unloading, disposed, and structural Loader entries are absent. Ordinary dependencies, loose modules without a named owning package, programmatically mounted child fibers, and in-memory dynamic plugins are also absent because they have no authoritative Loader package provenance.
|
|
71
|
+
|
|
72
|
+
An enabled inventory with no qualifying entries sends `packages: []`; disabling the contributor omits the entire `dsh_plugin_packages` field. Package identities are provider metadata and never enter model input.
|
|
73
|
+
|
|
74
|
+
## `dsh_session_log`
|
|
75
|
+
|
|
76
|
+
[`@deepseek-ai/dsh-session-log-deepseek`](../packages/session/session-log-deepseek/README.md) contributes one contiguous suffix of the canonical Session log. The field is disabled by default. When enabled, it applies to a request with a live Session and at least one event; a direct request, a stale Session id, or an empty log omits the field.
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"dsh_session_log": {
|
|
81
|
+
"version": 2,
|
|
82
|
+
"sessionFormatVersion": 2,
|
|
83
|
+
"session": {
|
|
84
|
+
"version": 2,
|
|
85
|
+
"id": "session-id",
|
|
86
|
+
"createdAt": 1780000000000,
|
|
87
|
+
"isSeeded": false
|
|
88
|
+
},
|
|
89
|
+
"afterSeq": -1,
|
|
90
|
+
"throughSeq": 0,
|
|
91
|
+
"events": [
|
|
92
|
+
{
|
|
93
|
+
"type": "turn/start",
|
|
94
|
+
"seq": 0,
|
|
95
|
+
"time": 1780000000001,
|
|
96
|
+
"data": {
|
|
97
|
+
"turn": 1
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
| Member | Type | Meaning |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `version` | `2` | Schema version for `dsh_session_log` |
|
|
108
|
+
| `sessionFormatVersion` | non-negative integer | Session format generation represented by this suffix |
|
|
109
|
+
| `session` | object | Immutable wire projection of the current Session header |
|
|
110
|
+
| `afterSeq` | integer | Greatest sequence recorded as accepted before this request, or `-1` |
|
|
111
|
+
| `throughSeq` | non-negative integer | Greatest sequence represented by this request |
|
|
112
|
+
| `events` | array | Contiguous events from `afterSeq + 1` through `throughSeq` |
|
|
113
|
+
|
|
114
|
+
The first upload uses `afterSeq: -1` and carries the complete current log. Each later upload starts after the greatest accepted watermark for the same Session id. The sender snapshots the event array once per request; appends after that snapshot belong to a later request.
|
|
115
|
+
|
|
116
|
+
### Wire Session header
|
|
117
|
+
|
|
118
|
+
The `session` member projects `Session.header`, not a complete runtime Session or the header object itself. It copies the current header facts, including the required `isSeeded` lineage bit; the exact `Session.inheritedEventCount` is not part of this request field. The outer `dsh_session_log.version` selects this extension schema, while `session.version` selects the logical Session format. Changing the Session header projection requires an extension-schema bump even when the embedded logical format also changes.
|
|
119
|
+
|
|
120
|
+
| Member | Presence | Meaning |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `version` | required | Logical Session format version; currently `2` |
|
|
123
|
+
| `id` | required | Exact Session id |
|
|
124
|
+
| `createdAt` | required | Non-negative safe-integer Unix epoch milliseconds |
|
|
125
|
+
| `cwd` | optional | Absolute working directory recorded at Session creation |
|
|
126
|
+
| `parentSession` | optional | Parent Session id for a fork |
|
|
127
|
+
| `isSeeded` | required | Whether the Session contains a fork-inherited event prefix |
|
|
128
|
+
| `origin` | optional | Literal `subagent` for a subagent child |
|
|
129
|
+
| `delegationDepth` | optional | Non-negative persisted subagent delegation depth |
|
|
130
|
+
| `agentPreset` | optional | Agent preset id used to compose this Session |
|
|
131
|
+
|
|
132
|
+
### Canonical event envelopes
|
|
133
|
+
|
|
134
|
+
Each `events` item is a complete canonical `SessionEvent`, independent of every other request field. An event always carries `type`, `seq`, `time`, and `data`; it may carry `ignorable: true`, and surface events may additionally carry `sourceEventSeqs` and `surfaceOp`. The sender copies every present member without projection, redaction, or reconstruction.
|
|
135
|
+
|
|
136
|
+
### Acceptance watermark and at-least-once delivery
|
|
137
|
+
|
|
138
|
+
After the endpoint returns HTTP 2xx, the contribution appends this canonical event to the same Session:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"type": "session-log-deepseek/delivery-accepted",
|
|
143
|
+
"seq": 8,
|
|
144
|
+
"time": 1780000000002,
|
|
145
|
+
"data": {
|
|
146
|
+
"sessionId": "session-id",
|
|
147
|
+
"sessionFormatVersion": 2,
|
|
148
|
+
"throughSeq": 7
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`delivery-accepted` means that the configured endpoint returned HTTP 2xx for the containing LLM request. It does not assert SSE completion or remote persistence. The event's `throughSeq` must identify an earlier event, its `sessionId` identifies the Session whose suffix was sent, and `sessionFormatVersion` binds the watermark to that exact logical generation. Absence means historical format v0.
|
|
154
|
+
|
|
155
|
+
The sender folds the greatest matching `throughSeq` for the current Session id and format generation, so concurrent accepted requests cannot move the cursor backward and a watermark from another generation cannot authorize the current suffix. A resumed process rebuilds the cursor from the durable log. A fork ignores inherited watermarks that name another Session, and therefore sends its own complete inherited prefix before advancing under the child id. The watermark event itself belongs to the next unsent suffix.
|
|
156
|
+
|
|
157
|
+
Transport and non-2xx failures append no watermark. A crash after endpoint acceptance but before local persistence may resend an already accepted range; uncertainty produces duplicates, never a sequence gap. There is no independent upload store, size cap, or truncation path.
|
|
158
|
+
|
|
159
|
+
## Exposure and receiver requirements
|
|
160
|
+
|
|
161
|
+
The request headers expose the Harness application version, one anonymous Harness-home identity, and an optional Session identity. `dsh_plugin_packages` exposes active npm package names and versions. When enabled, `dsh_session_log` may expose the Session working directory, system-prompt snapshots, user and Assistant content, embedded Assistant streams, failed-attempt output, tool arguments and results, compaction summaries, feedback, and plugin-owned events. Adapter API keys are not Session events and therefore do not enter the field. A gateway selected through `baseURL` receives the same values as the official endpoint.
|
|
162
|
+
|
|
163
|
+
Receivers address extension fields by name, dispatch each field by its own `version`, preserve distinct package versions, and ignore JSON member ordering. A session-log receiver validates the contiguous sequence range before interpreting event types. An unrecognized canonical event without `ignorable: true` prevents lossless reconstruction. The base request remains usable without either the registry or a particular contribution; field absence means that contribution did not apply to that request.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# DeepSeek 官方 LLM API 协议扩展
|
|
2
|
+
|
|
3
|
+
[English](deepseek-llm-api-wire-extensions.md) | 中文
|
|
4
|
+
|
|
5
|
+
本参考文档定义 [`@deepseek-ai/dsh-llm-deepseek`](../packages/llm/llm-deepseek/README.zh.md) 在 `deepseek-official` 聊天补全请求中发送的全部 DeepSeek Harness 特有 HTTP 标头和附加 JSON 字段。本文不重复定义 DeepSeek 上游 API 持有的字段。提供方无关的 LLM(大语言模型)接口与 `llm-pi-ai` 均不实现这些扩展。
|
|
6
|
+
|
|
7
|
+
适配器将这些扩展发送至已解析的 `baseURL`,包括已配置的网关。扩展位于 `messages`、系统提示词和工具 schema 之外,因此不会增加模型输入 token,也不会改变模型可见前缀。
|
|
8
|
+
|
|
9
|
+
## 协议命名空间与版本
|
|
10
|
+
|
|
11
|
+
| 位置 | 命名方式 | 示例 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| HTTP 字段名 | 小写 kebab-case;HTTP 匹配仍不区分大小写 | `user-agent`, `x-deepseek-harness-session-id` |
|
|
14
|
+
| DeepSeek 请求正文扩展字段 | 使用保留 `dsh_` 前缀的 snake case | `dsh_plugin_packages`, `dsh_session_log` |
|
|
15
|
+
| DSH 持有的嵌套 JSON 成员 | Camel case | `afterSeq`, `throughSeq`, `sessionId` |
|
|
16
|
+
| 带标签的值 | 使用 kebab-case 字符串;持久事件采用 `domain/action` | `session-log-deepseek/delivery-accepted` |
|
|
17
|
+
|
|
18
|
+
每个正文扩展独立持有自身的 `version`。版本仅适用于包含该字段的对象;不同字段的版本之间不存在兼容或排序关系。JSON 成员顺序不属于协议。
|
|
19
|
+
|
|
20
|
+
[`DeepSeekLlmApiExtensionRegistry`](../packages/llm/deepseek-llm-api-extensions/README.zh.md) 为每个顶层扩展名保留一个提供方。空名称、两端带空白的名称、重复注册以及与 DeepSeek 基础请求冲突的名称都会在 HTTP 分派前失败。
|
|
21
|
+
|
|
22
|
+
## 请求标头
|
|
23
|
+
|
|
24
|
+
| 标头 | 出现条件 | 值 |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `user-agent` | 每个提供方 HTTP 请求,包括 Files API 操作 | 采用 `product/version (+url)` 形式的应用身份;默认产品为 `deepseek-harness` |
|
|
27
|
+
| `x-deepseek-harness-user-id` | 每个已授权的聊天补全请求 | 已解析 Harness home 的稳定匿名 UUID |
|
|
28
|
+
| `x-deepseek-harness-session-id` | 携带会话 id 的聊天补全请求 | 确切的请求 `sessionId` 字符串 |
|
|
29
|
+
| `x-deepseek-harness-compact` | 用途为 `compaction` 的聊天补全请求 | 字面字符串 `1` |
|
|
30
|
+
|
|
31
|
+
凭据失败发生在解析匿名用户 id 之前,因此未授权请求既不会发送这些标头,也不会创建身份文件。没有会话的直接请求会省略 `x-deepseek-harness-session-id`。会话标题请求没有额外的用途标头;请求携带 `sessionId` 时,仍然适用普通的会话 id 规则。
|
|
32
|
+
|
|
33
|
+
## 正文扩展事务
|
|
34
|
+
|
|
35
|
+
适配器先序列化包括确切 `messages` 在内的完整基础正文,再让已注册提供方准备字段。提供方会收到该不可变正文、请求取消信号,以及可选的 `sessionId` 和辅助调用 `purpose`。提供方返回 `undefined` 时,本次请求会省略其字段。
|
|
36
|
+
|
|
37
|
+
系统将已准备的 JSON 值与提供方持有的状态分离,再将其作为基础字段的顶层同级成员合并,并序列化到同一个 HTTP 正文中。准备失败或冲突会阻止请求。组合未挂载注册表时,适配器发送未经扩展的基础正文。
|
|
38
|
+
|
|
39
|
+
已配置端点返回 HTTP 2xx 后,适配器会在读取 SSE 正文之前运行已准备的 `accept()` 事务。传输失败和非 2xx 响应不会接受任何贡献。即使端点返回 2xx,接受失败仍会使模型请求失败。接受仅记录端点级 HTTP 成功,不表示 SSE 流已完整结束,也不表示端点已持久化扩展。
|
|
40
|
+
|
|
41
|
+
## `dsh_plugin_packages`
|
|
42
|
+
|
|
43
|
+
[`@deepseek-ai/dsh-plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek/README.zh.md) 贡献完整存活的 Loader-backed 插件包清单。该字段默认启用。
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"dsh_plugin_packages": {
|
|
48
|
+
"version": 1,
|
|
49
|
+
"packages": [
|
|
50
|
+
{
|
|
51
|
+
"name": "@deepseek-ai/dsh-example",
|
|
52
|
+
"version": "0.1.1-rc.2"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| 成员 | 类型 | 含义 |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `version` | `1` | `dsh_plugin_packages` 的 schema 版本 |
|
|
62
|
+
| `packages` | 数组 | 本次请求的完整存活集合 |
|
|
63
|
+
| `packages[].name` | 字符串 | 来自所属 manifest(元数据清单)的确切非空 npm 包名 |
|
|
64
|
+
| `packages[].version` | 字符串 | 来自同一 manifest 的确切非空包版本 |
|
|
65
|
+
|
|
66
|
+
每个请求都会重新读取宿主树中的存活非分组 Loader 配置项;请求会话存在 standing agent-preset 树时,也会读取该树。相对与绝对模块使用距离自身最近的所属 manifest;裸包配置项使用激活自身的 Loader 解析基准。具名 manifest 未提供非空版本时,请求准备会失败。
|
|
67
|
+
|
|
68
|
+
发送方会对确切 `(name, version)` 组合去重,并使用与 locale 无关的文本比较,先按 `name`、再按 `version` 排序。同一包的多个同时存活版本会保留为独立配置项。接收方不得按包名折叠该数组,也不得根据数组顺序推断包的激活关系。
|
|
69
|
+
|
|
70
|
+
该清单不包含已禁用、pending、failed、unloading、disposed 和结构性 Loader 配置项。普通依赖、没有具名所属包的松散模块、以编程方式挂载的子 fiber,以及内存动态插件也不在其中,因为它们没有权威的 Loader 包来源信息。
|
|
71
|
+
|
|
72
|
+
清单已启用但没有符合条件的配置项时,系统发送 `packages: []`;禁用贡献插件时,系统省略整个 `dsh_plugin_packages` 字段。包身份属于提供方元数据,绝不进入模型输入。
|
|
73
|
+
|
|
74
|
+
## `dsh_session_log`
|
|
75
|
+
|
|
76
|
+
[`@deepseek-ai/dsh-session-log-deepseek`](../packages/session/session-log-deepseek/README.zh.md) 贡献权威会话日志的一段连续后缀。该字段默认禁用。启用后,它适用于携带存活会话且至少存在一个事件的请求;直接请求、陈旧会话 id 或空日志会省略该字段。
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"dsh_session_log": {
|
|
81
|
+
"version": 2,
|
|
82
|
+
"sessionFormatVersion": 2,
|
|
83
|
+
"session": {
|
|
84
|
+
"version": 2,
|
|
85
|
+
"id": "session-id",
|
|
86
|
+
"createdAt": 1780000000000,
|
|
87
|
+
"isSeeded": false
|
|
88
|
+
},
|
|
89
|
+
"afterSeq": -1,
|
|
90
|
+
"throughSeq": 0,
|
|
91
|
+
"events": [
|
|
92
|
+
{
|
|
93
|
+
"type": "turn/start",
|
|
94
|
+
"seq": 0,
|
|
95
|
+
"time": 1780000000001,
|
|
96
|
+
"data": {
|
|
97
|
+
"turn": 1
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
| 成员 | 类型 | 含义 |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `version` | `2` | `dsh_session_log` 的 schema 版本 |
|
|
108
|
+
| `sessionFormatVersion` | 非负整数 | 该后缀所表示的 Session 格式 generation |
|
|
109
|
+
| `session` | 对象 | 当前 Session header 的不可变协议投影 |
|
|
110
|
+
| `afterSeq` | 整数 | 本次请求前记录为已接受的最大序号,或 `-1` |
|
|
111
|
+
| `throughSeq` | 非负整数 | 本次请求所表示的最大序号 |
|
|
112
|
+
| `events` | 数组 | 从 `afterSeq + 1` 到 `throughSeq` 的连续事件 |
|
|
113
|
+
|
|
114
|
+
首次上传使用 `afterSeq: -1`,并携带当前的完整日志。此后每次上传都从同一会话 id 的最大已接受水位(watermark)之后开始。发送方为每次请求仅快照一次事件数组;快照后的追加内容属于后续请求。
|
|
115
|
+
|
|
116
|
+
### Session 协议 header
|
|
117
|
+
|
|
118
|
+
`session` 成员投影 `Session.header`,既不是完整的运行时 Session,也不是 header 对象本身。它复制当前 header 事实,包括必需的 `isSeeded` 谱系位;精确的 `Session.inheritedEventCount` 不属于该请求字段。外层 `dsh_session_log.version` 选择本扩展 schema,`session.version` 则选择逻辑 Session 格式。即使嵌入的逻辑格式同时变化,只要 Session header 投影变化,扩展 schema 也必须升版。
|
|
119
|
+
|
|
120
|
+
| 成员 | 出现条件 | 含义 |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `version` | 必需 | 逻辑 Session 格式版本;当前为 `2` |
|
|
123
|
+
| `id` | 必需 | 确切的会话 id |
|
|
124
|
+
| `createdAt` | 必需 | 非负安全整数 Unix epoch 毫秒数 |
|
|
125
|
+
| `cwd` | 可选 | 创建会话时记录的绝对工作目录 |
|
|
126
|
+
| `parentSession` | 可选 | fork 的父会话 id |
|
|
127
|
+
| `isSeeded` | 必需 | Session 是否包含 fork 继承的事件前缀 |
|
|
128
|
+
| `origin` | 可选 | subagent 子项使用的字面值 `subagent` |
|
|
129
|
+
| `delegationDepth` | 可选 | 持久化的非负 subagent 委派深度 |
|
|
130
|
+
| `agentPreset` | 可选 | 用于组合该会话的 agent preset id |
|
|
131
|
+
|
|
132
|
+
### 权威事件信封
|
|
133
|
+
|
|
134
|
+
每个 `events` 元素都是完整的权威 `SessionEvent`,不依赖任何其他请求字段。事件始终携带 `type`、`seq`、`time` 与 `data`;它可以携带 `ignorable: true`,展示事件还可携带 `sourceEventSeqs` 与 `surfaceOp`。发送方会复制每个已有成员,不执行投影、脱敏或重建。
|
|
135
|
+
|
|
136
|
+
### 接受水位与至少一次交付
|
|
137
|
+
|
|
138
|
+
端点返回 HTTP 2xx 后,该贡献会向同一会话追加以下权威事件:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"type": "session-log-deepseek/delivery-accepted",
|
|
143
|
+
"seq": 8,
|
|
144
|
+
"time": 1780000000002,
|
|
145
|
+
"data": {
|
|
146
|
+
"sessionId": "session-id",
|
|
147
|
+
"sessionFormatVersion": 2,
|
|
148
|
+
"throughSeq": 7
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`delivery-accepted` 表示已配置端点为包含该字段的 LLM 请求返回 HTTP 2xx。它不表示 SSE 已完整结束,也不表示远端已经持久化。该事件的 `throughSeq` 必须标识一项更早的事件,`sessionId` 标识已发送后缀所属的 Session,`sessionFormatVersion` 则把水位绑定到该逻辑 generation。缺少该字段表示历史格式 v0。
|
|
154
|
+
|
|
155
|
+
发送方只会为当前 Session id 与格式 generation 折叠最大的匹配 `throughSeq`,因此并发已接受请求无法使游标倒退,其他 generation 的水位也不能授权当前后缀。恢复后的进程会从持久日志重建游标。fork 会忽略命名其他 Session 的继承水位,因此先发送自身完整的继承前缀,再以子会话 id 推进。水位事件自身属于下一段未发送后缀。
|
|
156
|
+
|
|
157
|
+
传输失败和非 2xx 响应不会追加水位。端点接受后、本地持久化前发生崩溃时,系统可能重新发送已接受范围;不确定性只会产生重复,绝不会产生序号缺口。系统没有独立上传存储、大小上限或截断路径。
|
|
158
|
+
|
|
159
|
+
## 暴露内容与接收方要求
|
|
160
|
+
|
|
161
|
+
请求标头会暴露 Harness 应用版本、一个匿名 Harness-home 身份和可选的会话身份。`dsh_plugin_packages` 会暴露存活 npm 包的名称与版本。启用后,`dsh_session_log` 可能暴露会话工作目录、系统提示词快照、用户与 Assistant 内容、嵌入式 Assistant stream、失败 attempt 输出、工具参数与结果、压缩摘要、反馈和插件持有的事件。适配器 API key 不是会话事件,因此不会进入该字段。通过 `baseURL` 选择的网关会收到与官方端点相同的值。
|
|
162
|
+
|
|
163
|
+
接收方按名称定位扩展字段,按各字段自己的 `version` 分派,保留不同的包版本,并忽略 JSON 成员顺序。会话日志接收方必须先校验连续序号范围,再解释事件类型。遇到不带 `ignorable: true` 的未知权威事件时,接收方无法进行无损重建。即使缺少注册表或某项贡献,基础请求仍然可用;字段缺失表示该项贡献不适用于本次请求。
|
package/kb/extra/development.md
CHANGED
|
@@ -43,11 +43,11 @@ Setup is complete when `pnpm run typecheck` exits successfully.
|
|
|
43
43
|
|
|
44
44
|
### TypeScript project layout
|
|
45
45
|
|
|
46
|
-
The repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json
|
|
46
|
+
The repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`; three packages (`host/webserver`, `compaction/compaction`, `typert/registry`) are referenced by both aggregates as shared leaves so each side type-checks the same source.
|
|
47
47
|
|
|
48
48
|
| File | Role | Forms a program? |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `
|
|
50
|
+
| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `scripts/`. | No |
|
|
51
51
|
| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |
|
|
52
52
|
| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |
|
|
53
53
|
| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |
|
|
@@ -57,9 +57,9 @@ Host and Client stay two aggregate programs because both sides declaration-merge
|
|
|
57
57
|
|
|
58
58
|
- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.
|
|
59
59
|
- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.
|
|
60
|
-
- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.
|
|
60
|
+
- A new package is registered in exactly one aggregate; only the split packages above carry both leaf configs, and the shared leaves are registered in both aggregates because each side must type-check the same source. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.
|
|
61
61
|
|
|
62
|
-
`api/remotes`
|
|
62
|
+
Six packages split Host and Client tsconfigs: `api/remotes`, `api/gateway`, `api/session-controller`, `api/workspace-controller`, `client/connection`, and `session-query/session-log-export`. `api/remotes`' Host entry participates in the Host Typert graph while its Client entry imports generated `/remote` declarations; `session-log-export` keeps Node archive production out of its browser controller. Each split package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf; it discovers split packages from the presence of both leaf configs, so a new split joins the gate automatically. The [`api-remotes` README](../packages/api/remotes/README.md) and [`session-log-export` README](../packages/session-query/session-log-export/README.md) explain their splits.
|
|
63
63
|
|
|
64
64
|
The root build follows the generated dependency order:
|
|
65
65
|
|
|
@@ -75,7 +75,7 @@ Both tsdown passes use the same complete workspace match. They neither scan buil
|
|
|
75
75
|
|
|
76
76
|
Typert runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start Typert. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. The [API Remotes generated-contract build note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md) records this ordering decision.
|
|
77
77
|
|
|
78
|
-
`pnpm run build` embeds the
|
|
78
|
+
`pnpm run build` embeds the root package version, the seven-character source commit, and a dirty marker when Git reports local changes; it also inherits other caller-supplied `DSH_CLIENT_*` values. `pnpm run build:official` is the cross-platform local equivalent of the CI and release artifact build and omits the local dirty marker. Each successful complete build writes a gitignored record that binds the exact public values to the Vite output and dynamic client bundles; release packing and built Web tests reject a missing record or artifacts changed by a later partial build. `pnpm run dev:web` still requires the artifact tree from a prior complete build, but it samples the current version and Git state once at startup and shares that environment across every watcher stage for the session; it does not validate the complete-build record because the watcher stages rewrite its recorded artifacts.
|
|
79
79
|
|
|
80
80
|
Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already depends on the Typert contract-generation pass or the complete build. See the [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md) for the two-aggregate setup, the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md) for tsc-first emit ownership, and the [Typert Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract.
|
|
81
81
|
|
|
@@ -126,7 +126,7 @@ The keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates
|
|
|
126
126
|
|
|
127
127
|
The root [contributor instructions](../AGENTS.md#commands) summarize common commands, while [`package.json`](../package.json) and [scripts/run-gates.ts](../scripts/run-gates.ts) own the current script and gate inventories. Select the smallest checks that cover the changed surface. Documentation changes use `pnpm run doc-sync`; package-public behavior changes also update the owning README or JSDoc, and built-artifact checks require `pnpm run build` first.
|
|
128
128
|
|
|
129
|
-
###
|
|
129
|
+
### Profile runs
|
|
130
130
|
|
|
131
131
|
Run the repository build separately before using these source-checkout demos:
|
|
132
132
|
|
|
@@ -140,16 +140,10 @@ The one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment o
|
|
|
140
140
|
pnpm dsh --profile headless "summarize this workspace"
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
The
|
|
143
|
+
The PTC mode demo runs the same headless profile with code presentation enabled:
|
|
144
144
|
|
|
145
145
|
```sh
|
|
146
|
-
pnpm run demo:
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
The ACP automation server exposes fresh agent sessions over JSON-RPC stdio and also needs `DEEPSEEK_API_KEY`:
|
|
150
|
-
|
|
151
|
-
```sh
|
|
152
|
-
pnpm run demo:acp
|
|
146
|
+
pnpm run demo:ptc -- "summarize this workspace"
|
|
153
147
|
```
|
|
154
148
|
|
|
155
149
|
### TODO markers
|