@deepseek-ai/dsh-llm 0.2.0-rc.2 → 0.2.1-alpha.2
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/README.i18n.yaml +4 -4
- package/README.md +20 -1
- package/README.zh.md +20 -1
- package/lib/index.js +23 -16
- package/lib/typert.host.js +15 -7
- package/lib/typert.remote-client.js +3 -3
- package/lib/types/attribution.d.ts +1 -1
- package/lib/types/attribution.js +1 -1
- package/lib/types/call-config.d.ts +19 -6
- package/lib/types/error.d.ts +1 -1
- package/lib/types/error.js +1 -1
- package/lib/types/index.d.ts +8 -4
- package/lib/types/index.js +21 -18
- package/lib/types/types.d.ts +3 -3
- package/package.json +10 -16
- package/lib/invariant.js +0 -84
- package/lib/types/invariant.d.ts +0 -13
- package/lib/types/invariant.js +0 -100
package/README.i18n.yaml
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
en: c3ecb5a1421f27db
|
|
22
22
|
zh: 4bbd3c0e513845e1
|
|
23
23
|
/deepseek-ai-dsh-llm/use-this-package/minimal-composition:
|
|
24
|
-
en:
|
|
25
|
-
zh:
|
|
24
|
+
en: 9d481d19dc52e404
|
|
25
|
+
zh: aa09cf4462e93ba6
|
|
26
26
|
/deepseek-ai-dsh-llm/use-this-package/what-you-can-do:
|
|
27
27
|
en: b45d7a414141734e
|
|
28
28
|
zh: a641c8ea3827cee6
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
en: ae1618577f4e8364
|
|
40
40
|
zh: a6e8ab1563c08dfb
|
|
41
41
|
/deepseek-ai-dsh-llm/understand-the-implementation/main-flow:
|
|
42
|
-
en:
|
|
43
|
-
zh:
|
|
42
|
+
en: da12553c289e1705
|
|
43
|
+
zh: 28827a81cd63ff84
|
|
44
44
|
/deepseek-ai-dsh-llm/understand-the-implementation/invariants:
|
|
45
45
|
en: 0163a050239248e5
|
|
46
46
|
zh: 885c95bedcbe2ff4
|
package/README.md
CHANGED
|
@@ -60,6 +60,25 @@ After a successful mount, `ctx.llm.listProviders()` reports the registered route
|
|
|
60
60
|
|
|
61
61
|
`GenerateOptions.messages` accepts durable `Message` values and request-only `RequestUserInput` values. Request-only inputs carry user-role content with no `id` or `source`; Session writes and Agent delivery still require durable messages. Callers keep auxiliary inputs unchanged until the stream settles. A caller that records its exact request, such as session-title generation, must use durable messages.
|
|
62
62
|
|
|
63
|
+
`prepareCall(config, signal, configure?)` accepts concrete `LlmCallConfig` values. The optional synchronous, pure `ConfigureCall` function receives detached, deeply frozen `LlmCallControls` and model metadata from the captured adapter generation. Callers can compose ordinary functions to choose concrete controls; later writes replace earlier ones. Preparation retains the captured route, applies defaults to omitted controls, validates the result, and returns a frozen configuration for recording and dispatch. Callback failures or cancellation reject before dispatch. Preparation requires a registered adapter.
|
|
64
|
+
|
|
65
|
+
Configuration functions select controls before adapter defaults are materialized. For example, combine least-effort selection with an explicit output cap, then pass `configure(maxTokens)` as the third argument:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import type { ConfigureCall } from '@deepseek-ai/dsh-llm'
|
|
69
|
+
|
|
70
|
+
const leastReasoning: ConfigureCall = (controls, model) => {
|
|
71
|
+
const first = model.reasoning?.efforts[0]
|
|
72
|
+
return first === undefined ? controls : { ...controls, reasoningEffort: first.id }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const outputLimit = (maxTokens: number): ConfigureCall =>
|
|
76
|
+
controls => ({ ...controls, maxTokens })
|
|
77
|
+
|
|
78
|
+
const configure = (maxTokens: number): ConfigureCall =>
|
|
79
|
+
(controls, model) => outputLimit(maxTokens)(leastReasoning(controls, model), model)
|
|
80
|
+
```
|
|
81
|
+
|
|
63
82
|
### What you can do
|
|
64
83
|
|
|
65
84
|
- **Stream one model call** — `ctx.llm.stream(options)` yields raw chunks (token-level deltas) for any registered provider and model; consumers assemble them with `BlockAssembler`.
|
|
@@ -107,7 +126,7 @@ The service is built on one separation: **the logical contract is provider-neutr
|
|
|
107
126
|
|
|
108
127
|
A request is validated against its exact model's capability — context window, output default, reasoning efforts, input modalities, and `systemPromptUpdate` mode — and any adapter-configured defaults are materialized. The runtime preserves freezing for already-frozen input; hand-built callers own input immutability. `prepareCall()` binds those facts, detached context, and retry policy to the exact adapter generation that performs terminal dispatch, so HMR or dynamic settings cannot combine one generation's image capability with another generation's endpoint. An image-capable adapter projects durable references into route-specific request versions; `resolveImageAttachmentAccess()` separately maps an attachment provider's optional host object into the current tool execution world without changing the request image or its `variantId`. A text-only route receives deterministic per-image placeholders, including tool-role result images, without rewriting append-only session history. Durable `FileBlock` references never reach any adapter: request assembly replaces each one, including tool-role result occurrences, with deterministic handle text naming the file and its saved read-only path, resolved through the mounted attachment and filesystem providers. `ctx.llm.fileRequestText(ref)` exposes that exact synchronous projection to request measurement. An image occurrence derived with `offloaded: true` reaches every route as placeholder text through `projectOffloadedImages()`. An image-capable route whose retained occurrences exceed its `LlmImageRequestBudget` at their exact bytes fails with `IMAGE_OFFLOAD_REQUIRED` naming the additional oldest occurrences (`requiredImageOffload()`), never with an unlogged projection; `dsh-compaction-image-offload` logs the selected occurrences in one `image/offload` event and retries. Adapters that charge visual tokens declare per-route `imageRequestPricing`, which `ctx.llm.imageRequestPricing(provider, model)` resolves synchronously for the token meter. Dispatch goes through the `llm/stream` waterfall, then chunks return as token-level deltas and every adapter outcome reaches the consumer as one terminal `finish` chunk.
|
|
109
128
|
|
|
110
|
-
File detection reads current content, including tool-role result content, on every request without caching message identities or freeze state. The [file-scan decision](../../../.agents/notes/
|
|
129
|
+
File detection reads current content, including tool-role result content, on every request without caching message identities or freeze state. The [archived file-scan decision](../../../.agents/notes/archived/simplification/2026-09-07-file-content-scan.md) records the measured traversal cost.
|
|
111
130
|
|
|
112
131
|
### Invariants
|
|
113
132
|
|
package/README.zh.md
CHANGED
|
@@ -60,6 +60,25 @@ for await (const chunk of ctx.llm.stream({
|
|
|
60
60
|
|
|
61
61
|
`GenerateOptions.messages` 接受持久 `Message` 值和仅供请求使用的 `RequestUserInput` 值。仅供请求使用的输入包含 user-role 内容,不含 `id` 或 `source`;Session 写入和 Agent 投递仍然要求持久消息。调用方必须在流结束前保持辅助输入不变。会记录完整请求的调用方(例如会话标题生成)必须使用持久消息。
|
|
62
62
|
|
|
63
|
+
`prepareCall(config, signal, configure?)` 接受具体的 `LlmCallConfig` 值。可选的同步纯函数 `ConfigureCall` 接收已分离且深度冻结的 `LlmCallControls`,以及捕获的适配器代次所提供的模型元数据。调用方可组合普通函数来选择具体控制项;后执行的写入替换先前的值。准备操作保留捕获的路由,为省略的控制项应用默认值,校验结果,再返回用于记录与分发的冻结配置。回调失败或取消会在分发前拒绝。准备操作要求注册适配器。
|
|
64
|
+
|
|
65
|
+
配置函数在适配器默认值物化之前选择控制项。例如,将最低强度选择与显式输出上限组合,再将 `configure(maxTokens)` 作为第三个参数传入:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import type { ConfigureCall } from '@deepseek-ai/dsh-llm'
|
|
69
|
+
|
|
70
|
+
const leastReasoning: ConfigureCall = (controls, model) => {
|
|
71
|
+
const first = model.reasoning?.efforts[0]
|
|
72
|
+
return first === undefined ? controls : { ...controls, reasoningEffort: first.id }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const outputLimit = (maxTokens: number): ConfigureCall =>
|
|
76
|
+
controls => ({ ...controls, maxTokens })
|
|
77
|
+
|
|
78
|
+
const configure = (maxTokens: number): ConfigureCall =>
|
|
79
|
+
(controls, model) => outputLimit(maxTokens)(leastReasoning(controls, model), model)
|
|
80
|
+
```
|
|
81
|
+
|
|
63
82
|
### 你可以做什么
|
|
64
83
|
|
|
65
84
|
- **流式发起一次模型调用**——`ctx.llm.stream(options)` 为任何已注册提供方与模型产出原始分片(token 级增量);消费方用 `BlockAssembler` 组装。
|
|
@@ -107,7 +126,7 @@ for await (const chunk of ctx.llm.stream({
|
|
|
107
126
|
|
|
108
127
|
请求会对照其精确模型的能力校验,包括上下文窗口、输出默认值、推理强度、输入模态与 `systemPromptUpdate` 模式,并填入任何适配器配置的默认值。运行时保留已冻结输入的冻结状态;手动构建请求的调用方负责保证输入不可变。`prepareCall()` 把这些事实、分离的上下文与重试策略绑定到执行最终分发的精确适配器代次,因此 HMR(热模块替换)或动态设置无法把一个代次的图片能力与另一代次的端点混用。支持图片的适配器把持久引用投影为路由专用请求版本;`resolveImageAttachmentAccess()` 会单独把附件提供方的可选宿主对象映射进当前工具执行世界,而不改变请求图片或其 `variantId`。纯文本路由接收确定性的逐图片占位符,包括 tool-role 结果图片,而不会改写仅追加会话历史。持久 `FileBlock` 引用永远不会到达任何适配器:请求组装把每个引用(包括 tool-role 结果中的出现)替换为确定性句柄文本,指出文件与其只读保存路径,路径经由挂载的附件与文件系统提供方解析。`ctx.llm.fileRequestText(ref)` 向请求计量公开相同的同步投影。派生后带有 `offloaded: true` 的图片出现位置,经 `projectOffloadedImages()` 以占位文本到达每条路由。支持图片的路由在保留的出现位置按精确字节超过其 `LlmImageRequestBudget` 时,以 `IMAGE_OFFLOAD_REQUIRED` 失败并说明还需省略多少最老的出现位置(`requiredImageOffload()`),绝不发送未记录的投影;`dsh-compaction-image-offload` 用一条 `image/offload` 事件记录所选位置并重试。对视觉 token 收费的适配器声明按路由的 `imageRequestPricing`,`ctx.llm.imageRequestPricing(provider, model)` 为 token meter 同步解析它。分发经过 `llm/stream` waterfall(瀑布式事件),随后分片以 token 级增量返回,每个适配器结果都以唯一一个终止 `finish` 分片到达消费方。
|
|
109
128
|
|
|
110
|
-
文件检测在每次请求时读取当前内容,包括 tool-role 结果内容,不缓存消息身份或冻结状态。[
|
|
129
|
+
文件检测在每次请求时读取当前内容,包括 tool-role 结果内容,不缓存消息身份或冻结状态。[已归档的文件扫描决策](../../../.agents/notes/archived/simplification/2026-09-07-file-content-scan.md)记录了实测遍历成本。
|
|
111
130
|
|
|
112
131
|
### 不变式
|
|
113
132
|
|
package/lib/index.js
CHANGED
|
@@ -119,7 +119,7 @@ function createToolResultMessage(input) {
|
|
|
119
119
|
*/
|
|
120
120
|
/**
|
|
121
121
|
* Base class for all harness errors. Carries a `code` (stable, programmatic —
|
|
122
|
-
* e.g. `NO_ADAPTER`, `INVALID_ARGS
|
|
122
|
+
* e.g. `NO_ADAPTER`, `INVALID_ARGS`) distinct from the
|
|
123
123
|
* human-readable `message`, and supports `cause` chaining via the standard
|
|
124
124
|
* `ErrorOptions`. `name` defaults to the subclass constructor name.
|
|
125
125
|
*/
|
|
@@ -840,7 +840,7 @@ function projectToolUpdates(messages, tools, toolUpdate, history) {
|
|
|
840
840
|
/**
|
|
841
841
|
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
842
842
|
* adapters from drifting. See
|
|
843
|
-
*
|
|
843
|
+
* `docs/subsystems/llm-streaming.md#appidentity--app-attribution`.
|
|
844
844
|
*
|
|
845
845
|
* App-attribution vocabulary for provider requests.
|
|
846
846
|
* @module @deepseek-ai/dsh-llm/attribution
|
|
@@ -1802,20 +1802,14 @@ let LlmRuntime = (() => {
|
|
|
1802
1802
|
}
|
|
1803
1803
|
/** Notify topology observers without letting one broken listener veto the commit. */
|
|
1804
1804
|
emitAdaptersUpdated() {
|
|
1805
|
-
let invariantFailure;
|
|
1806
1805
|
for (const listener of this.ctx.events.dispatch("emit", ["llm/adapters-updated"])) try {
|
|
1807
1806
|
const returned = listener();
|
|
1808
1807
|
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
1809
1808
|
this.warnAdaptersListenerFailure(error);
|
|
1810
1809
|
});
|
|
1811
1810
|
} catch (error) {
|
|
1812
|
-
if (error?.code === "INVARIANT") {
|
|
1813
|
-
invariantFailure ??= error;
|
|
1814
|
-
continue;
|
|
1815
|
-
}
|
|
1816
1811
|
this.warnAdaptersListenerFailure(error);
|
|
1817
1812
|
}
|
|
1818
|
-
if (invariantFailure !== void 0) throw invariantFailure;
|
|
1819
1813
|
}
|
|
1820
1814
|
/** Contained-listener diagnostic shared by the sync and async failure paths. */
|
|
1821
1815
|
warnAdaptersListenerFailure(error) {
|
|
@@ -2196,20 +2190,33 @@ let LlmRuntime = (() => {
|
|
|
2196
2190
|
* Resolve one call under its current adapter registration. The returned
|
|
2197
2191
|
* one-shot handle keeps that registration across header logging and dispatch,
|
|
2198
2192
|
* so HMR cannot combine one adapter's capability result with another adapter.
|
|
2199
|
-
*
|
|
2193
|
+
* An optional synchronous callback selects concrete controls using captured
|
|
2194
|
+
* model metadata. Defaults and validation apply to its result. Callback
|
|
2195
|
+
* failures and cancellation reject preparation before dispatch.
|
|
2196
|
+
* @param config - provider/model route and optional concrete request controls.
|
|
2200
2197
|
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
2198
|
+
* @param configure - pure control selection, called once before defaults and validation.
|
|
2201
2199
|
* @returns a prepared config and its registration-bound stream entry point.
|
|
2202
2200
|
*/
|
|
2203
|
-
async prepareCall(config, signal) {
|
|
2204
|
-
|
|
2205
|
-
const
|
|
2206
|
-
const
|
|
2207
|
-
const
|
|
2201
|
+
async prepareCall(config, signal, configure) {
|
|
2202
|
+
signal?.throwIfAborted();
|
|
2203
|
+
const { provider, model, ...controls } = config;
|
|
2204
|
+
const registration = this.registration(provider);
|
|
2205
|
+
const adapterCall = await registration.adapter.prepareCall(provider, model, signal);
|
|
2206
|
+
signal?.throwIfAborted();
|
|
2207
|
+
const modelInfo = this.normalizeModelInfo(registration, model, adapterCall.model);
|
|
2208
|
+
const proposed = configure === void 0 ? config : {
|
|
2209
|
+
...configure(deepFreeze(structuredClone(controls)), deepFreeze(modelInfo)),
|
|
2210
|
+
provider,
|
|
2211
|
+
model
|
|
2212
|
+
};
|
|
2213
|
+
signal?.throwIfAborted();
|
|
2214
|
+
const resolved = this.resolveCallWithInfo(proposed, modelInfo);
|
|
2208
2215
|
const resolvedConfig = deepFreeze(structuredClone(resolved.config));
|
|
2209
2216
|
const context = resolved.context === void 0 ? void 0 : deepFreeze(structuredClone(resolved.context));
|
|
2210
2217
|
const adapterDefaults = deepFreeze({
|
|
2211
|
-
...
|
|
2212
|
-
...
|
|
2218
|
+
...proposed.reasoningEffort === void 0 && resolvedConfig.reasoningEffort !== void 0 ? { reasoningEffort: true } : {},
|
|
2219
|
+
...proposed.maxTokens === void 0 && resolvedConfig.maxTokens !== void 0 ? { maxTokens: true } : {}
|
|
2213
2220
|
});
|
|
2214
2221
|
let dispatched = false;
|
|
2215
2222
|
return Object.freeze({
|
package/lib/typert.host.js
CHANGED
|
@@ -74,7 +74,7 @@ export const TYPERT = {
|
|
|
74
74
|
typeSymbol: '@deepseek-ai/dsh-llm#llm/discoverModels:result',
|
|
75
75
|
create: _deepseek_ai_dsh_llm_llm_discoverModels_result$schema,
|
|
76
76
|
},
|
|
77
|
-
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":
|
|
77
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":631,"column":9},
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
id: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders',
|
|
@@ -89,7 +89,7 @@ export const TYPERT = {
|
|
|
89
89
|
typeSymbol: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders:result',
|
|
90
90
|
create: _deepseek_ai_dsh_llm_llm_listConfigurableProviders_result$schema,
|
|
91
91
|
},
|
|
92
|
-
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":
|
|
92
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":543,"column":3},
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
id: '@deepseek-ai/dsh-llm#llm/listProviders',
|
|
@@ -104,7 +104,7 @@ export const TYPERT = {
|
|
|
104
104
|
typeSymbol: '@deepseek-ai/dsh-llm#llm/listProviders:result',
|
|
105
105
|
create: _deepseek_ai_dsh_llm_llm_listProviders_result$schema,
|
|
106
106
|
},
|
|
107
|
-
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":
|
|
107
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":471,"column":3},
|
|
108
108
|
},
|
|
109
109
|
],
|
|
110
110
|
model: {
|
|
@@ -211,9 +211,9 @@ export const TYPERT = {
|
|
|
211
211
|
{
|
|
212
212
|
"kind": "method",
|
|
213
213
|
"name": "prepareCall",
|
|
214
|
-
"signature": "async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise<PreparedLlmCall>",
|
|
214
|
+
"signature": "async prepareCall( config: LlmCallConfig, signal?: AbortSignal, configure?: ConfigureCall, ): Promise<PreparedLlmCall>",
|
|
215
215
|
"summary": "Resolve one call under its current adapter registration.",
|
|
216
|
-
"jsDoc": "/**\n * Resolve one call under its current adapter registration. The returned\n * one-shot handle keeps that registration across header logging and dispatch,\n * so HMR cannot combine one adapter's capability result with another adapter.\n * @param config - provider/model route and optional request controls.\n * @param signal - optional cancellation for adapter-owned capability lookup.\n * @returns a prepared config and its registration-bound stream entry point.\n */"
|
|
216
|
+
"jsDoc": "/**\n * Resolve one call under its current adapter registration. The returned\n * one-shot handle keeps that registration across header logging and dispatch,\n * so HMR cannot combine one adapter's capability result with another adapter.\n * An optional synchronous callback selects concrete controls using captured\n * model metadata. Defaults and validation apply to its result. Callback\n * failures and cancellation reject preparation before dispatch.\n * @param config - provider/model route and optional concrete request controls.\n * @param signal - optional cancellation for adapter-owned capability lookup.\n * @param configure - pure control selection, called once before defaults and validation.\n * @returns a prepared config and its registration-bound stream entry point.\n */"
|
|
217
217
|
},
|
|
218
218
|
{
|
|
219
219
|
"kind": "method",
|
|
@@ -264,6 +264,10 @@ export const TYPERT = {
|
|
|
264
264
|
"name": "CompactionId",
|
|
265
265
|
"declaration": "export type CompactionId = Branded<'CompactionId'>;"
|
|
266
266
|
},
|
|
267
|
+
{
|
|
268
|
+
"name": "ConfigureCall",
|
|
269
|
+
"declaration": "export type ConfigureCall = (controls: Readonly<LlmCallControls>, model: Readonly<LlmResolvedModelInfo>) => LlmCallControls;"
|
|
270
|
+
},
|
|
267
271
|
{
|
|
268
272
|
"name": "ContentBlock",
|
|
269
273
|
"declaration": "export type ContentBlock = ContentBlockMap[ContentBlockType];"
|
|
@@ -338,12 +342,16 @@ export const TYPERT = {
|
|
|
338
342
|
},
|
|
339
343
|
{
|
|
340
344
|
"name": "LlmCallConfig",
|
|
341
|
-
"declaration": "export interface LlmCallConfig {\n provider: string;\n model: string;\n
|
|
345
|
+
"declaration": "export interface LlmCallConfig extends LlmCallControls {\n provider: string;\n model: string;\n}"
|
|
342
346
|
},
|
|
343
347
|
{
|
|
344
348
|
"name": "LlmCallConfigAdapterDefaults",
|
|
345
349
|
"declaration": "export interface LlmCallConfigAdapterDefaults {\n reasoningEffort?: true;\n maxTokens?: true;\n}"
|
|
346
350
|
},
|
|
351
|
+
{
|
|
352
|
+
"name": "LlmCallControls",
|
|
353
|
+
"declaration": "export interface LlmCallControls {\n reasoningEffort?: ReasoningEffortId;\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n}"
|
|
354
|
+
},
|
|
347
355
|
{
|
|
348
356
|
"name": "LlmConfigurableProvider",
|
|
349
357
|
"declaration": "export interface LlmConfigurableProvider {\n provider: string;\n displayName: string;\n settingsNs: string;\n settingsPath: readonly string[];\n declared?: boolean;\n error?: string;\n}"
|
|
@@ -414,7 +422,7 @@ export const TYPERT = {
|
|
|
414
422
|
},
|
|
415
423
|
{
|
|
416
424
|
"name": "MessageSourceMap",
|
|
417
|
-
"declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n 'system-prompt': SystemPromptMessageSource;\n 'model-selection': { kind: 'model-selection'; } & ContextFormed;\n 'user-approval': { kind: 'user-approval'; } & ContextFormed;\n 'ptc-mode': { kind: 'ptc-mode'; };\n 'tool-registry': { kind: 'tool-registry'; };\n 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; };\n 'agent-message': AgentMessageSource;\n 'subagent-settled': SubagentSettledMessageSource;\n 'skill-invocation': SkillInvocationSource;\n 'cordis-host-runner': { kind: 'cordis-host-runner'; };\n
|
|
425
|
+
"declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n 'system-prompt': SystemPromptMessageSource;\n 'model-selection': { kind: 'model-selection'; } & ContextFormed;\n 'working-directory': { kind: 'working-directory'; };\n 'user-approval': { kind: 'user-approval'; } & ContextFormed;\n 'ptc-mode': { kind: 'ptc-mode'; };\n 'tool-registry': { kind: 'tool-registry'; };\n 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; };\n 'agent-message': AgentMessageSource;\n 'subagent-settled': SubagentSettledMessageSource;\n 'skill-invocation': SkillInvocationSource;\n 'cordis-host-runner': { kind: 'cordis-host-runner'; };\n 'compact-checkpoint': CompactionCheckpointSource;\n 'user-question-reply': { kind: 'user-question-reply'; callId: ToolCallId; outcome: 'answered'; };\n goal: GoalMessageSource;\n schedule: { kind: 'schedule'; } & ContextFormed;\n 'session-reference': SessionReferenceSource;\n}"
|
|
418
426
|
},
|
|
419
427
|
{
|
|
420
428
|
"name": "ModelMessageSource",
|
|
@@ -71,7 +71,7 @@ export const TYPERT_REMOTE = {
|
|
|
71
71
|
typeSymbol: '@deepseek-ai/dsh-llm#llm/discoverModels:result',
|
|
72
72
|
create: _deepseek_ai_dsh_llm_llm_discoverModels_result$schema,
|
|
73
73
|
},
|
|
74
|
-
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":
|
|
74
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":631,"column":9},
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
id: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders',
|
|
@@ -86,7 +86,7 @@ export const TYPERT_REMOTE = {
|
|
|
86
86
|
typeSymbol: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders:result',
|
|
87
87
|
create: _deepseek_ai_dsh_llm_llm_listConfigurableProviders_result$schema,
|
|
88
88
|
},
|
|
89
|
-
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":
|
|
89
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":543,"column":3},
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
id: '@deepseek-ai/dsh-llm#llm/listProviders',
|
|
@@ -101,7 +101,7 @@ export const TYPERT_REMOTE = {
|
|
|
101
101
|
typeSymbol: '@deepseek-ai/dsh-llm#llm/listProviders:result',
|
|
102
102
|
create: _deepseek_ai_dsh_llm_llm_listProviders_result$schema,
|
|
103
103
|
},
|
|
104
|
-
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":
|
|
104
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":471,"column":3},
|
|
105
105
|
},
|
|
106
106
|
],
|
|
107
107
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
3
3
|
* adapters from drifting. See
|
|
4
|
-
*
|
|
4
|
+
* `docs/subsystems/llm-streaming.md#appidentity--app-attribution`.
|
|
5
5
|
*
|
|
6
6
|
* App-attribution vocabulary for provider requests.
|
|
7
7
|
* @module @deepseek-ai/dsh-llm/attribution
|
package/lib/types/attribution.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
3
3
|
* adapters from drifting. See
|
|
4
|
-
*
|
|
4
|
+
* `docs/subsystems/llm-streaming.md#appidentity--app-attribution`.
|
|
5
5
|
*
|
|
6
6
|
* App-attribution vocabulary for provider requests.
|
|
7
7
|
* @module @deepseek-ai/dsh-llm/attribution
|
|
@@ -5,22 +5,35 @@
|
|
|
5
5
|
* changed snapshots instead of allowing silent per-call drift.
|
|
6
6
|
* @module dsh-llm/call-config
|
|
7
7
|
*/
|
|
8
|
-
import type { GenerateOptions } from './types.ts';
|
|
8
|
+
import type { GenerateOptions, LlmResolvedModelInfo } from './types.ts';
|
|
9
9
|
import type { ReasoningEffortId } from './brand.ts';
|
|
10
|
+
/** Concrete generation settings; omitted controls use the selected route's defaults. */
|
|
11
|
+
export interface LlmCallControls {
|
|
12
|
+
reasoningEffort?: ReasoningEffortId;
|
|
13
|
+
temperature?: number;
|
|
14
|
+
maxTokens?: number;
|
|
15
|
+
stop?: string[];
|
|
16
|
+
}
|
|
10
17
|
/**
|
|
11
18
|
* Provider, model, reasoning effort, and sampling scalars of one conversation's
|
|
12
19
|
* requests. Every field maps 1:1 onto the same-named `GenerateOptions` field;
|
|
13
20
|
* the loop builds requests from the logged header rather than accepting these
|
|
14
21
|
* per call.
|
|
15
22
|
*/
|
|
16
|
-
export interface LlmCallConfig {
|
|
23
|
+
export interface LlmCallConfig extends LlmCallControls {
|
|
17
24
|
provider: string;
|
|
18
25
|
model: string;
|
|
19
|
-
reasoningEffort?: ReasoningEffortId;
|
|
20
|
-
temperature?: number;
|
|
21
|
-
maxTokens?: number;
|
|
22
|
-
stop?: string[];
|
|
23
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Synchronous, pure configuration of one call before defaults and validation.
|
|
29
|
+
* Compose functions in the desired order; later writes replace earlier ones.
|
|
30
|
+
* The returned controls cannot change the captured route. Errors reject
|
|
31
|
+
* preparation before dispatch; only the resolved configuration is recordable.
|
|
32
|
+
* @param controls - detached, deeply frozen proposed controls, without defaults.
|
|
33
|
+
* @param model - detached, deeply frozen metadata from the captured adapter generation.
|
|
34
|
+
* @returns concrete controls; omitted fields receive the route's defaults.
|
|
35
|
+
*/
|
|
36
|
+
export type ConfigureCall = (controls: Readonly<LlmCallControls>, model: Readonly<LlmResolvedModelInfo>) => LlmCallControls;
|
|
24
37
|
/**
|
|
25
38
|
* Effective config fields supplied by exact-model adapter resolution rather
|
|
26
39
|
* than by the caller's request proposal.
|
package/lib/types/error.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Base class for all harness errors. Carries a `code` (stable, programmatic —
|
|
8
|
-
* e.g. `NO_ADAPTER`, `INVALID_ARGS
|
|
8
|
+
* e.g. `NO_ADAPTER`, `INVALID_ARGS`) distinct from the
|
|
9
9
|
* human-readable `message`, and supports `cause` chaining via the standard
|
|
10
10
|
* `ErrorOptions`. `name` defaults to the subclass constructor name.
|
|
11
11
|
*/
|
package/lib/types/error.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Base class for all harness errors. Carries a `code` (stable, programmatic —
|
|
8
|
-
* e.g. `NO_ADAPTER`, `INVALID_ARGS
|
|
8
|
+
* e.g. `NO_ADAPTER`, `INVALID_ARGS`) distinct from the
|
|
9
9
|
* human-readable `message`, and supports `cause` chaining via the standard
|
|
10
10
|
* `ErrorOptions`. `name` defaults to the subclass constructor name.
|
|
11
11
|
*/
|
package/lib/types/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
10
10
|
import type { GenerateOptions, LlmConfigurableProvider, LlmDiscoveredModel, LlmFailure, LlmImageRequestPricing, LlmModelContext, LlmModelDiscoveryRequest, LlmModelInfo, LlmResolvedModelInfo, LlmProviderInfo, ModelModality, StreamChunk, SystemPromptUpdate, ToolUpdate } from './types.ts';
|
|
11
11
|
import type { ResolvedRetryPolicy } from './retry-policy.ts';
|
|
12
12
|
import type { ProviderRequestId } from './brand.ts';
|
|
13
|
-
import type { LlmCallConfig, LlmCallConfigAdapterDefaults } from './call-config.ts';
|
|
13
|
+
import type { ConfigureCall, LlmCallConfig, LlmCallConfigAdapterDefaults } from './call-config.ts';
|
|
14
14
|
import { HarnessError } from './error.ts';
|
|
15
15
|
import type { FileAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
16
16
|
export * from './attribution.ts';
|
|
@@ -24,7 +24,7 @@ export * from './message.ts';
|
|
|
24
24
|
export * from './retry-policy.ts';
|
|
25
25
|
export { BlockAssembler } from './assembler.ts';
|
|
26
26
|
export { callConfigEquals, isAgentLoopRequest, markAgentLoopRequest } from './call-config.ts';
|
|
27
|
-
export type { LlmCallConfig, LlmCallConfigAdapterDefaults } from './call-config.ts';
|
|
27
|
+
export type { ConfigureCall, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmCallControls } from './call-config.ts';
|
|
28
28
|
declare module '@deepseek-ai/cordis' {
|
|
29
29
|
interface Context {
|
|
30
30
|
llm: LlmRuntime;
|
|
@@ -379,11 +379,15 @@ export declare class LlmRuntime extends TypertRemoteService {
|
|
|
379
379
|
* Resolve one call under its current adapter registration. The returned
|
|
380
380
|
* one-shot handle keeps that registration across header logging and dispatch,
|
|
381
381
|
* so HMR cannot combine one adapter's capability result with another adapter.
|
|
382
|
-
*
|
|
382
|
+
* An optional synchronous callback selects concrete controls using captured
|
|
383
|
+
* model metadata. Defaults and validation apply to its result. Callback
|
|
384
|
+
* failures and cancellation reject preparation before dispatch.
|
|
385
|
+
* @param config - provider/model route and optional concrete request controls.
|
|
383
386
|
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
387
|
+
* @param configure - pure control selection, called once before defaults and validation.
|
|
384
388
|
* @returns a prepared config and its registration-bound stream entry point.
|
|
385
389
|
*/
|
|
386
|
-
prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise<PreparedLlmCall>;
|
|
390
|
+
prepareCall(config: LlmCallConfig, signal?: AbortSignal, configure?: ConfigureCall): Promise<PreparedLlmCall>;
|
|
387
391
|
private registration;
|
|
388
392
|
/** Remove replay state whose historical route is owned by another adapter. */
|
|
389
393
|
private forAdapter;
|
package/lib/types/index.js
CHANGED
|
@@ -237,30 +237,22 @@ let LlmRuntime = (() => {
|
|
|
237
237
|
emitAdaptersUpdated() {
|
|
238
238
|
// Cordis emit uses Array.map: one synchronous throw starves later
|
|
239
239
|
// listeners. Registry notifications are non-vetoing, so contain each
|
|
240
|
-
// callback independently
|
|
241
|
-
let invariantFailure;
|
|
240
|
+
// callback independently.
|
|
242
241
|
for (const listener of this.ctx.events.dispatch('emit', ['llm/adapters-updated'])) {
|
|
243
242
|
try {
|
|
244
243
|
const returned = listener();
|
|
245
244
|
if (returned != null && typeof returned.then === 'function') {
|
|
246
245
|
// An emit listener may still be an async function; its rejection
|
|
247
|
-
//
|
|
248
|
-
// contained here instead of becoming an unhandled rejection.
|
|
246
|
+
// is contained here instead of becoming an unhandled rejection.
|
|
249
247
|
void Promise.resolve(returned).then(undefined, (error) => {
|
|
250
248
|
this.warnAdaptersListenerFailure(error);
|
|
251
249
|
});
|
|
252
250
|
}
|
|
253
251
|
}
|
|
254
252
|
catch (error) {
|
|
255
|
-
if (error?.code === 'INVARIANT') {
|
|
256
|
-
invariantFailure ??= error;
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
253
|
this.warnAdaptersListenerFailure(error);
|
|
260
254
|
}
|
|
261
255
|
}
|
|
262
|
-
if (invariantFailure !== undefined)
|
|
263
|
-
throw invariantFailure;
|
|
264
256
|
}
|
|
265
257
|
/** Contained-listener diagnostic shared by the sync and async failure paths. */
|
|
266
258
|
warnAdaptersListenerFailure(error) {
|
|
@@ -724,24 +716,35 @@ let LlmRuntime = (() => {
|
|
|
724
716
|
* Resolve one call under its current adapter registration. The returned
|
|
725
717
|
* one-shot handle keeps that registration across header logging and dispatch,
|
|
726
718
|
* so HMR cannot combine one adapter's capability result with another adapter.
|
|
727
|
-
*
|
|
719
|
+
* An optional synchronous callback selects concrete controls using captured
|
|
720
|
+
* model metadata. Defaults and validation apply to its result. Callback
|
|
721
|
+
* failures and cancellation reject preparation before dispatch.
|
|
722
|
+
* @param config - provider/model route and optional concrete request controls.
|
|
728
723
|
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
724
|
+
* @param configure - pure control selection, called once before defaults and validation.
|
|
729
725
|
* @returns a prepared config and its registration-bound stream entry point.
|
|
730
726
|
*/
|
|
731
|
-
async prepareCall(config, signal) {
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
const
|
|
727
|
+
async prepareCall(config, signal, configure) {
|
|
728
|
+
signal?.throwIfAborted();
|
|
729
|
+
const { provider, model, ...controls } = config;
|
|
730
|
+
const registration = this.registration(provider);
|
|
731
|
+
const adapterCall = await registration.adapter.prepareCall(provider, model, signal);
|
|
732
|
+
signal?.throwIfAborted();
|
|
733
|
+
const modelInfo = this.normalizeModelInfo(registration, model, adapterCall.model);
|
|
734
|
+
const proposed = configure === undefined
|
|
735
|
+
? config
|
|
736
|
+
: { ...configure(deepFreeze(structuredClone(controls)), deepFreeze(modelInfo)), provider, model };
|
|
737
|
+
signal?.throwIfAborted();
|
|
738
|
+
const resolved = this.resolveCallWithInfo(proposed, modelInfo);
|
|
736
739
|
const resolvedConfig = deepFreeze(structuredClone(resolved.config));
|
|
737
740
|
const context = resolved.context === undefined
|
|
738
741
|
? undefined
|
|
739
742
|
: deepFreeze(structuredClone(resolved.context));
|
|
740
743
|
const adapterDefaults = deepFreeze({
|
|
741
|
-
...
|
|
744
|
+
...proposed.reasoningEffort === undefined && resolvedConfig.reasoningEffort !== undefined
|
|
742
745
|
? { reasoningEffort: true }
|
|
743
746
|
: {},
|
|
744
|
-
...
|
|
747
|
+
...proposed.maxTokens === undefined && resolvedConfig.maxTokens !== undefined
|
|
745
748
|
? { maxTokens: true }
|
|
746
749
|
: {},
|
|
747
750
|
});
|
package/lib/types/types.d.ts
CHANGED
|
@@ -348,7 +348,7 @@ export interface LlmReasoningEffortInfo {
|
|
|
348
348
|
}
|
|
349
349
|
/** Selectable reasoning efforts for one exact provider/model route. */
|
|
350
350
|
export interface LlmModelReasoningInfo {
|
|
351
|
-
/** Supported efforts
|
|
351
|
+
/** Supported efforts from least to greatest selectable reasoning effort, not predicted token use or latency. */
|
|
352
352
|
efforts: readonly LlmReasoningEffortInfo[];
|
|
353
353
|
/**
|
|
354
354
|
* Adapter-configured default materialized into requests when callers omit
|
|
@@ -524,8 +524,8 @@ export interface GenerateOptions {
|
|
|
524
524
|
sessionId?: Branded<'SessionId'>;
|
|
525
525
|
/**
|
|
526
526
|
* Provider-neutral classification for an auxiliary model call. Adapters may
|
|
527
|
-
* map the purpose to model-hidden transport metadata
|
|
528
|
-
*
|
|
527
|
+
* map the purpose to model-hidden transport metadata. Ordinary conversation
|
|
528
|
+
* requests leave it unset.
|
|
529
529
|
*/
|
|
530
530
|
purpose?: 'compaction' | 'session-title';
|
|
531
531
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-llm",
|
|
3
3
|
"description": "Provider-neutral LLM service interface for the DeepSeek Harness",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -18,10 +18,6 @@
|
|
|
18
18
|
"types": "./lib/types/index.d.ts",
|
|
19
19
|
"default": "./lib/index.js"
|
|
20
20
|
},
|
|
21
|
-
"./invariant": {
|
|
22
|
-
"types": "./lib/types/invariant.d.ts",
|
|
23
|
-
"default": "./lib/invariant.js"
|
|
24
|
-
},
|
|
25
21
|
"./types": {
|
|
26
22
|
"types": "./lib/types/types.d.ts",
|
|
27
23
|
"default": "./lib/types/types.js"
|
|
@@ -51,7 +47,6 @@
|
|
|
51
47
|
},
|
|
52
48
|
"files": [
|
|
53
49
|
"lib/index.js",
|
|
54
|
-
"lib/invariant.js",
|
|
55
50
|
"lib/types/**/*.js",
|
|
56
51
|
"lib/types/**/*.d.ts",
|
|
57
52
|
"lib/typert.host.js",
|
|
@@ -61,20 +56,19 @@
|
|
|
61
56
|
],
|
|
62
57
|
"license": "MIT",
|
|
63
58
|
"peerDependencies": {
|
|
64
|
-
"@deepseek-ai/cordis": "~4.0.
|
|
59
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1"
|
|
65
60
|
},
|
|
66
61
|
"dependencies": {
|
|
62
|
+
"@deepseek-ai/dsh-util-crypto": "0.2.1-alpha.2",
|
|
63
|
+
"@deepseek-ai/schemastery": "~3.18.5-alpha.1",
|
|
67
64
|
"zod": "^4.4.3",
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/
|
|
72
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|
|
73
|
-
"@deepseek-ai/dsh-util-values": "0.2.0-rc.2"
|
|
65
|
+
"@deepseek-ai/dsh-timeout": "0.2.1-alpha.2",
|
|
66
|
+
"@deepseek-ai/dsh-brand": "0.2.1-alpha.2",
|
|
67
|
+
"@deepseek-ai/dsh-util-values": "0.2.1-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.2"
|
|
74
69
|
},
|
|
75
70
|
"devDependencies": {
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/
|
|
78
|
-
"@deepseek-ai/cordis": "~4.0.4"
|
|
71
|
+
"@deepseek-ai/dsh-attachment": "0.2.1-alpha.2",
|
|
72
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1"
|
|
79
73
|
}
|
|
80
74
|
}
|
package/lib/invariant.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
//#region lib/types/invariant.js
|
|
2
|
-
/** Package-owned LLM stream-protocol invariants. @module @deepseek-ai/dsh-llm/invariant */
|
|
3
|
-
const PACKAGE_NAME = "@deepseek-ai/dsh-llm";
|
|
4
|
-
/** Cordis companion plugin name. */
|
|
5
|
-
const name = "llm-invariant";
|
|
6
|
-
/** Service required before the companion can reserve package ownership. */
|
|
7
|
-
const inject = ["invariants"];
|
|
8
|
-
/** Require one chunk index to be a non-negative safe integer. */
|
|
9
|
-
function validateIndex(index, fail) {
|
|
10
|
-
if (!Number.isSafeInteger(index) || index < 0) fail(`LLM stream block index must be a non-negative safe integer, got ${index}`);
|
|
11
|
-
}
|
|
12
|
-
/** Require a delta to address an open block of its matching type. */
|
|
13
|
-
function validateDelta(open, index, expected, fail) {
|
|
14
|
-
validateIndex(index, fail);
|
|
15
|
-
const actual = open.get(index);
|
|
16
|
-
if (actual !== expected) fail(`${expected} delta at index ${index} requires an open ${expected} block, got ${String(actual)}`);
|
|
17
|
-
}
|
|
18
|
-
/** Wrap one provider stream and enforce its grammar as chunks are consumed. */
|
|
19
|
-
async function* validateStream(source, fail) {
|
|
20
|
-
const open = /* @__PURE__ */ new Map();
|
|
21
|
-
let usageSeen = false;
|
|
22
|
-
let finished = false;
|
|
23
|
-
for await (const chunk of source) {
|
|
24
|
-
if (finished) fail(`LLM stream emitted ${chunk.type} after terminal finish`);
|
|
25
|
-
switch (chunk.type) {
|
|
26
|
-
case "block-start":
|
|
27
|
-
validateIndex(chunk.index, fail);
|
|
28
|
-
if (open.has(chunk.index)) fail(`LLM stream repeated block-start index ${chunk.index}`);
|
|
29
|
-
open.set(chunk.index, chunk.blockType);
|
|
30
|
-
break;
|
|
31
|
-
case "text-delta":
|
|
32
|
-
validateDelta(open, chunk.index, "text", fail);
|
|
33
|
-
break;
|
|
34
|
-
case "reasoning-delta":
|
|
35
|
-
validateDelta(open, chunk.index, "reasoning", fail);
|
|
36
|
-
break;
|
|
37
|
-
case "tool-call-delta":
|
|
38
|
-
validateDelta(open, chunk.index, "tool-call", fail);
|
|
39
|
-
break;
|
|
40
|
-
case "block-end": {
|
|
41
|
-
validateIndex(chunk.index, fail);
|
|
42
|
-
const blockType = open.get(chunk.index);
|
|
43
|
-
if (blockType === void 0) fail(`LLM stream block-end index ${chunk.index} has no open block`);
|
|
44
|
-
if (chunk.block.type !== blockType) fail(`LLM stream block-end index ${chunk.index} closes ${chunk.block.type}, expected ${blockType}`);
|
|
45
|
-
open.delete(chunk.index);
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
case "usage":
|
|
49
|
-
if (usageSeen) fail("LLM stream emitted usage more than once");
|
|
50
|
-
usageSeen = true;
|
|
51
|
-
break;
|
|
52
|
-
case "finish":
|
|
53
|
-
if (open.size > 0 && chunk.reason.kind !== "error" && chunk.reason.kind !== "aborted") fail(`LLM stream finished with ${open.size} open block(s)`);
|
|
54
|
-
finished = true;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
yield chunk;
|
|
58
|
-
}
|
|
59
|
-
if (!finished) fail("LLM stream ended without a terminal finish chunk");
|
|
60
|
-
}
|
|
61
|
-
/** Install validation around every provider stream. */
|
|
62
|
-
const install = (ctx, fail) => {
|
|
63
|
-
ctx.on("llm/stream", (_options, next) => validateStream(next(), fail), {
|
|
64
|
-
global: true,
|
|
65
|
-
prepend: true
|
|
66
|
-
});
|
|
67
|
-
ctx.on("llm/adapters-updated", () => {
|
|
68
|
-
const llm = ctx.get("llm");
|
|
69
|
-
if (llm === void 0) return;
|
|
70
|
-
for (const provider of llm.listProviders()) try {
|
|
71
|
-
llm.providerRetryPolicy(provider.id);
|
|
72
|
-
} catch {
|
|
73
|
-
fail(`llm/adapters-updated fired while provider "${provider.id}" has no readable registration`);
|
|
74
|
-
}
|
|
75
|
-
}, { global: true });
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Register the LLM invariant companion.
|
|
79
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
80
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
81
|
-
*/
|
|
82
|
-
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
83
|
-
//#endregion
|
|
84
|
-
export { apply, inject, name };
|
package/lib/types/invariant.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/** Package-owned LLM stream-protocol invariants. @module @deepseek-ai/dsh-llm/invariant */
|
|
2
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
-
/** Cordis companion plugin name. */
|
|
4
|
-
export declare const name = "llm-invariant";
|
|
5
|
-
/** Service required before the companion can reserve package ownership. */
|
|
6
|
-
export declare const inject: string[];
|
|
7
|
-
/**
|
|
8
|
-
* Register the LLM invariant companion.
|
|
9
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
10
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
11
|
-
*/
|
|
12
|
-
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
13
|
-
//# sourceMappingURL=invariant.d.ts.map
|
package/lib/types/invariant.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/** Package-owned LLM stream-protocol invariants. @module @deepseek-ai/dsh-llm/invariant */
|
|
2
|
-
const PACKAGE_NAME = '@deepseek-ai/dsh-llm';
|
|
3
|
-
/** Cordis companion plugin name. */
|
|
4
|
-
export const name = 'llm-invariant';
|
|
5
|
-
/** Service required before the companion can reserve package ownership. */
|
|
6
|
-
export const inject = ['invariants'];
|
|
7
|
-
/** Require one chunk index to be a non-negative safe integer. */
|
|
8
|
-
function validateIndex(index, fail) {
|
|
9
|
-
if (!Number.isSafeInteger(index) || index < 0) {
|
|
10
|
-
fail(`LLM stream block index must be a non-negative safe integer, got ${index}`);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
/** Require a delta to address an open block of its matching type. */
|
|
14
|
-
function validateDelta(open, index, expected, fail) {
|
|
15
|
-
validateIndex(index, fail);
|
|
16
|
-
const actual = open.get(index);
|
|
17
|
-
if (actual !== expected) {
|
|
18
|
-
fail(`${expected} delta at index ${index} requires an open ${expected} block, got ${String(actual)}`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/** Wrap one provider stream and enforce its grammar as chunks are consumed. */
|
|
22
|
-
async function* validateStream(source, fail) {
|
|
23
|
-
const open = new Map();
|
|
24
|
-
let usageSeen = false;
|
|
25
|
-
let finished = false;
|
|
26
|
-
for await (const chunk of source) {
|
|
27
|
-
if (finished)
|
|
28
|
-
fail(`LLM stream emitted ${chunk.type} after terminal finish`);
|
|
29
|
-
switch (chunk.type) {
|
|
30
|
-
case 'block-start':
|
|
31
|
-
validateIndex(chunk.index, fail);
|
|
32
|
-
if (open.has(chunk.index))
|
|
33
|
-
fail(`LLM stream repeated block-start index ${chunk.index}`);
|
|
34
|
-
open.set(chunk.index, chunk.blockType);
|
|
35
|
-
break;
|
|
36
|
-
case 'text-delta':
|
|
37
|
-
validateDelta(open, chunk.index, 'text', fail);
|
|
38
|
-
break;
|
|
39
|
-
case 'reasoning-delta':
|
|
40
|
-
validateDelta(open, chunk.index, 'reasoning', fail);
|
|
41
|
-
break;
|
|
42
|
-
case 'tool-call-delta':
|
|
43
|
-
validateDelta(open, chunk.index, 'tool-call', fail);
|
|
44
|
-
break;
|
|
45
|
-
case 'block-end': {
|
|
46
|
-
validateIndex(chunk.index, fail);
|
|
47
|
-
const blockType = open.get(chunk.index);
|
|
48
|
-
if (blockType === undefined)
|
|
49
|
-
fail(`LLM stream block-end index ${chunk.index} has no open block`);
|
|
50
|
-
if (chunk.block.type !== blockType) {
|
|
51
|
-
fail(`LLM stream block-end index ${chunk.index} closes ${chunk.block.type}, expected ${blockType}`);
|
|
52
|
-
}
|
|
53
|
-
open.delete(chunk.index);
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
case 'usage':
|
|
57
|
-
if (usageSeen)
|
|
58
|
-
fail('LLM stream emitted usage more than once');
|
|
59
|
-
usageSeen = true;
|
|
60
|
-
break;
|
|
61
|
-
case 'finish':
|
|
62
|
-
if (open.size > 0 && chunk.reason.kind !== 'error' && chunk.reason.kind !== 'aborted') {
|
|
63
|
-
fail(`LLM stream finished with ${open.size} open block(s)`);
|
|
64
|
-
}
|
|
65
|
-
finished = true;
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
yield chunk;
|
|
69
|
-
}
|
|
70
|
-
if (!finished)
|
|
71
|
-
fail('LLM stream ended without a terminal finish chunk');
|
|
72
|
-
}
|
|
73
|
-
/** Install validation around every provider stream. */
|
|
74
|
-
const install = (ctx, fail) => {
|
|
75
|
-
ctx.on('llm/stream', (_options, next) => validateStream(next(), fail), { global: true, prepend: true });
|
|
76
|
-
ctx.on('llm/adapters-updated', () => {
|
|
77
|
-
// A disposer-time emit can outlive the service-store entry during whole-
|
|
78
|
-
// context teardown; only a live service promises a readable registry.
|
|
79
|
-
const llm = ctx.get('llm');
|
|
80
|
-
if (llm === undefined)
|
|
81
|
-
return;
|
|
82
|
-
for (const provider of llm.listProviders()) {
|
|
83
|
-
try {
|
|
84
|
-
llm.providerRetryPolicy(provider.id);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Reaching here IS the violation: the notification promised a readable
|
|
88
|
-
// registry, and only that broken promise can make the lookup throw.
|
|
89
|
-
fail(`llm/adapters-updated fired while provider "${provider.id}" has no readable registration`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}, { global: true });
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* Register the LLM invariant companion.
|
|
96
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
97
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
98
|
-
*/
|
|
99
|
-
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
100
|
-
//# sourceMappingURL=invariant.js.map
|