@deepseek-ai/dsh-llm 0.2.0-rc.1 → 0.2.1-alpha.1

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 CHANGED
@@ -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: 6dc955a0d80f6ca0
43
- zh: f81dbcaeacd8b269
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
@@ -107,7 +107,7 @@ The service is built on one separation: **the logical contract is provider-neutr
107
107
 
108
108
  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
109
 
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/implemented/simplification/2026-09-07-file-content-scan.md) records the measured traversal cost.
110
+ 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
111
 
112
112
  ### Invariants
113
113
 
package/README.zh.md CHANGED
@@ -107,7 +107,7 @@ for await (const chunk of ctx.llm.stream({
107
107
 
108
108
  请求会对照其精确模型的能力校验,包括上下文窗口、输出默认值、推理强度、输入模态与 `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
109
 
110
- 文件检测在每次请求时读取当前内容,包括 tool-role 结果内容,不缓存消息身份或冻结状态。[文件扫描决策](../../../.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md)记录了实测遍历成本。
110
+ 文件检测在每次请求时读取当前内容,包括 tool-role 结果内容,不缓存消息身份或冻结状态。[已归档的文件扫描决策](../../../.agents/notes/archived/simplification/2026-09-07-file-content-scan.md)记录了实测遍历成本。
111
111
 
112
112
  ### 不变式
113
113
 
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`, `INVARIANT`) distinct from the
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
- * `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
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) {
@@ -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":638,"column":9},
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":550,"column":3},
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":478,"column":3},
107
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":471,"column":3},
108
108
  },
109
109
  ],
110
110
  model: {
@@ -414,7 +414,7 @@ export const TYPERT = {
414
414
  },
415
415
  {
416
416
  "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 goal: GoalMessageSource;\n schedule: { kind: 'schedule'; } & ContextFormed;\n 'compact-checkpoint': CompactionCheckpointSource;\n 'session-reference': SessionReferenceSource;\n}"
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 '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
418
  },
419
419
  {
420
420
  "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":638,"column":9},
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":550,"column":3},
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":478,"column":3},
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
- * `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
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
@@ -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
- * `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
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,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`, `INVARIANT`) distinct from the
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
  */
@@ -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`, `INVARIANT`) distinct from the
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
  */
@@ -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; INVARIANT-coded failures still surface.
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
- // cannot reach the synchronous INVARIANT rethrow below, so it is
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) {
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.0-rc.1",
4
+ "version": "0.2.1-alpha.1",
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.4"
59
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1"
65
60
  },
66
61
  "dependencies": {
67
62
  "zod": "^4.4.3",
68
- "@deepseek-ai/dsh-util-crypto": "0.2.0-rc.1",
69
- "@deepseek-ai/schemastery": "~3.18.4",
70
- "@deepseek-ai/dsh-brand": "0.2.0-rc.1",
71
- "@deepseek-ai/dsh-timeout": "0.2.0-rc.1",
72
- "@deepseek-ai/dsh-util-values": "0.2.0-rc.1",
73
- "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1"
63
+ "@deepseek-ai/dsh-util-crypto": "0.2.1-alpha.1",
64
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.1",
65
+ "@deepseek-ai/dsh-timeout": "0.2.1-alpha.1",
66
+ "@deepseek-ai/dsh-brand": "0.2.1-alpha.1",
67
+ "@deepseek-ai/dsh-util-values": "0.2.1-alpha.1",
68
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.1"
74
69
  },
75
70
  "devDependencies": {
76
- "@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
77
- "@deepseek-ai/dsh-invariants": "0.2.0-rc.1",
78
- "@deepseek-ai/cordis": "~4.0.4"
71
+ "@deepseek-ai/dsh-attachment": "0.2.1-alpha.1",
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 };
@@ -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
@@ -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