feat(deepseek): upload incremental session logs
This commit is contained in:
1 parent
df6e581f58
commit
fe72ab42d1
59 files changed
+1345
-58
No files matched your search
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md
|
||||
2026-08-21-deepseek-llm-api-request-extensions.md: d83b53adcfbf41f9addde0b7d4ac9ab1a8572d2f
|
||||
2026-08-21-deepseek-llm-api-request-extensions.zh.md: 47d7ce574c189f8d71d28963910e948c86169198
|
||||
2026-08-21-deepseek-llm-api-request-extensions.md: 9353cfc7dcefff3156d1e0be446c6e0e85e94f73
|
||||
2026-08-21-deepseek-llm-api-request-extensions.zh.md: 77959c037b4658672639f15e0a1da69bb5a49a1f
|
||||
+25
-11
@@ -1,4 +1,4 @@
|
||||
# Agent Note: DeepSeek LLM API request extensions for plugin package metadata
|
||||
# Agent Note: DeepSeek LLM API request extensions for session logs and plugin packages
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,17 +6,25 @@ English | [中文](2026-08-21-deepseek-llm-api-request-extensions.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Provider-side diagnosis needs the exact active plugin package versions that produced an official DeepSeek request. The existing browser-facing plugin inventory reports configured Loader rows and lifecycle phases but owns neither package-manifest resolution nor the requesting agent's standing preset composition.
|
||||
The canonical Session log contains request boundaries, raw response chunks, assembled messages, tool activity, plugin events, and failure facts that the model message list does not preserve. The OTel session-telemetry path projects and batches that log independently of model requests, uses deployment-selected sharing modes, and intentionally drops most assistant chunks. DeepSeek's official API therefore cannot reconstruct the complete harness trajectory from its ordinary request messages or the telemetry feed.
|
||||
|
||||
This metadata belongs only on the official DeepSeek adapter path. Adding it to `GenerateOptions` or the provider-neutral LLM seam would expose a DeepSeek wire concept to pi-ai and every future adapter.
|
||||
Provider-side diagnosis also needs the exact active plugin package versions that produced a request. The existing browser-facing plugin inventory reports configured Loader rows and lifecycle phases but owns neither package-manifest resolution nor the requesting agent's standing preset composition.
|
||||
|
||||
The adapter also needs one plugin-owned extension point. Importing Loader, preset, and package-manifest logic directly into `llm-deepseek` would make the transport own metadata discovery and prevent independent request fields from evolving as plugins.
|
||||
Both values belong only on the official DeepSeek adapter path. Adding them to `GenerateOptions` or the provider-neutral LLM seam would expose DeepSeek wire concepts to pi-ai and every future adapter.
|
||||
|
||||
## Decision
|
||||
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` registers `ctx.deepseekLlmApiExtensions`, an additive registry of top-level fields for `deepseek-official` request bodies. A contributor claims one declaration-merged field with `register()`. The adapter invokes `prepare()` after serializing the exact wire messages, passes the request cancellation signal, rejects preparation or base-field collision before HTTP, merges the detached fields, and calls the captured `accept()` transaction after HTTP 2xx. The registry stops awaiting preparation after cancellation even if a contributor ignores the signal. Acceptance failures remain request failures under `REQUEST_EXTENSION`; transport and non-2xx failures never accept a contribution. A composition without the registry retains the reusable base adapter.
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` registers `ctx.deepseekLlmApiExtensions`, an additive registry of top-level fields for `deepseek-official` request bodies. A contributor claims one declaration-merged field with `register()`. The adapter invokes `prepare()` after serializing the exact wire messages, passes the request cancellation signal, rejects preparation or base-field collision before HTTP, merges the detached fields, and calls the captured `accept()` transaction after HTTP 2xx. The registry stops awaiting preparation after cancellation even if a contributor ignores the signal. Acceptance failures remain request failures under `REQUEST_EXTENSION`; transport and non-2xx failures never accept a contribution. A composition without the registry retains the reusable base adapter. Shipped compositions mount the registry and both contributors: package metadata is enabled by default, while Session-log upload is disabled by default and requires `session-log-deepseek.enabled: true`. Keyless `deepseek-official` replay invokes preparation with a synthetic empty base body and the same acceptance transaction before its first recorded chunk, preserving post-2xx extension side effects rather than field bytes.
|
||||
|
||||
Shipped compositions mount the registry and the default-on plugin-package contributor. Keyless `deepseek-official` replay invokes preparation with a synthetic empty base body and the same acceptance transaction before its first recorded chunk, preserving post-2xx extension side effects rather than field bytes. The provider-neutral `llm` package and `llm-pi-ai` contain no extension type, service lookup, field merge, or acceptance call.
|
||||
The provider-neutral `llm` package and `llm-pi-ai` contain no extension type, service lookup, field merge, or acceptance call.
|
||||
|
||||
## Incremental session-log field
|
||||
|
||||
`@deepseek-ai/dsh-session-log-deepseek` owns `dsh_session_log` as an explicit opt-in. When enabled, each request carrying a live Session id sends the contiguous canonical event suffix after the greatest durable `session-log-deepseek/accepted` watermark for that same Session identity. The field includes the immutable Session header and complete event envelopes. A 2xx appends a new watermark for the transmitted `throughSeq`; that event enters the following request's suffix. Forked logs retain parent watermark ids, so a child starts from sequence zero under its own identity. Concurrent acceptances may arrive out of order, and the maximum watermark remains authoritative. A process-local fold scans each Session event once and incrementally consumes later appends; a new Session object or HMR generation rebuilds the fold from durable history.
|
||||
|
||||
The failure direction is at least once. A transport or provider rejection records no watermark. A crash after remote acceptance but before the watermark persists causes replay after resume, never a skipped sequence. Existing session checkpoints persist the event; the upload plugin owns no second store.
|
||||
|
||||
Event strings are raw unless exact request-relative references reduce encoded bytes. A reference identifies one serialized DeepSeek message, a path to one parsed string value, and a half-open UTF-8 byte range. The encoder verifies that each candidate range decodes to the exact matched UTF-16 substring; surrogate-splitting and ill-formed UTF-16 candidates stay raw. The decoder reconstructs every literal and cited byte exactly and rejects invalid paths, ranges, or code-point splits. Fuzzy similarity, normalization, and lossy omission are absent.
|
||||
|
||||
## Plugin package field
|
||||
|
||||
@@ -42,13 +50,17 @@ The process-lifetime manifest-identity cache remains separate because in-process
|
||||
|
||||
## Verification
|
||||
|
||||
Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, real Loader composition inspects the default metadata field, one credentialed real-API request mounts the production contributor, and pi-ai tests retain their unchanged wire requests.
|
||||
Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin the default-off policy, explicit full-first/suffix-later delivery, incremental watermark folding, persisted restart recovery, fork identity fencing, out-of-order acceptance, exact Unicode reconstruction, invalid references, surrogate-safe raw fallback, and late invariant loading. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, real Loader composition pins default package metadata plus opt-in Session upload, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Add generic metadata to `GenerateOptions` or `ctx.llm`.** Rejected because the value and acceptance timing are DeepSeek wire semantics; a provider-neutral request would make every adapter understand or ignore a foreign field.
|
||||
**Add generic metadata to `GenerateOptions` or `ctx.llm`.** Rejected because the values and acceptance timing are DeepSeek wire semantics; a provider-neutral request would make every adapter understand or ignore foreign fields.
|
||||
|
||||
**Hard-wire package discovery into `llm-deepseek`.** Rejected because the adapter would import Loader, preset, and package-manifest logic. The registry keeps transport responsible only for field merge and HTTP acceptance.
|
||||
**Hard-wire the two producers into `llm-deepseek`.** Rejected because the adapter would import Session, Loader, preset, package-manifest, and cursor logic. The registry keeps transport responsible only for field merge and HTTP acceptance.
|
||||
|
||||
**Use fuzzy message similarity or omit overlapping event data.** Rejected because the receiver could not reconstruct the canonical log. Exact byte references with raw fallback preserve every value.
|
||||
|
||||
**Keep the upload cursor only in memory.** Rejected because a normal process restart would resend the entire Session. A canonical acceptance event makes restart recovery best-effort durable without another storage backend; the remaining crash window produces allowed duplicates.
|
||||
|
||||
**Inventory every live Cordis fiber.** Rejected because programmatic and in-memory fibers have no authoritative npm package provenance. Loader-backed host and preset entries provide exact resolvable package identity.
|
||||
|
||||
@@ -58,6 +70,8 @@ Registry tests pin duplicate ownership, effect-scoped disposal, detached field v
|
||||
|
||||
## Consequences
|
||||
|
||||
Official DeepSeek requests carry active package versions to their resolved `baseURL`, including configured gateways. The field is model-hidden and adds no prompt tokens or KV-cache changes. Manifest resolution, field collision, acceptance handling, or provider schema rejection fails the model request rather than silently dropping metadata.
|
||||
Official DeepSeek requests carry active package versions to their resolved `baseURL`, including configured gateways. An explicit Session-log opt-in also carries the complete newly unaccepted Session suffix. The fields are model-hidden and add no prompt tokens or KV-cache changes, but can substantially increase HTTP body size. Encoding, manifest resolution, field collision, acceptance logging, or provider schema rejection fails the model request rather than silently dropping metadata.
|
||||
|
||||
Direct calls without a live Agent still carry the host package inventory. The [DeepSeek request-identity decision](../feature/2026-08-11-deepseek-request-user-id-header.md) continues to own user/session headers, which remain outside the body.
|
||||
The accepted-watermark event becomes part of the canonical log and is itself delivered on a later request. Crash recovery can duplicate a suffix but does not infer acceptance from assistant output or create a second local cursor store. Direct calls without a live Session omit the session field; host package inventory remains available.
|
||||
|
||||
The [DeepSeek request-identity decision](../feature/2026-08-11-deepseek-request-user-id-header.md) continues to own user/session headers, which remain outside the body. The [session-telemetry decision](../feature/2026-07-23-session-telemetry-otel-revival.md) remains current until a separate change removes that seam and backend; this request path does not alter OTel capture or sharing modes.
|
||||
+26
-12
@@ -1,4 +1,4 @@
|
||||
# Agent Note: DeepSeek LLM API 插件包元数据请求扩展
|
||||
# Agent Note: DeepSeek LLM API 会话日志与插件包请求扩展
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,17 +6,25 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
提供方侧诊断需要产生一条 DeepSeek 官方请求的确切存活插件包版本。现有面向浏览器的插件清单会报告已配置 Loader 配置项与生命周期阶段,但既不拥有包 manifest(元数据清单)解析,也不拥有请求 Agent 的 standing preset 组合。
|
||||
权威会话日志包含请求边界、原始响应分片、组装后消息、工具活动、插件事件与失败事实,模型消息列表无法保留全部内容。OTel 会话遥测路径独立于模型请求投影和批处理该日志,使用部署方选择的共享模式,并刻意丢弃大多数 assistant 分片。因此,DeepSeek 官方 API 无法从普通请求消息或遥测流重建完整 harness 轨迹。
|
||||
|
||||
该元数据只属于 DeepSeek 官方适配器路径。把它加入 `GenerateOptions` 或提供方无关的 LLM seam,会让 pi-ai 与未来每个适配器接触 DeepSeek 协议概念。
|
||||
提供方侧诊断还需要产生当前请求的确切存活插件包版本。现有面向浏览器的插件清单会报告已配置 Loader 配置项与生命周期阶段,但既不拥有包 manifest(元数据清单)解析,也不拥有请求 Agent 的 standing preset 组合。
|
||||
|
||||
适配器还需要一个由插件拥有的扩展点。若 `llm-deepseek` 直接导入 Loader、preset 与包 manifest 逻辑,传输层就会拥有元数据发现,并阻止独立请求字段作为插件分别演进。
|
||||
两个值都只属于 DeepSeek 官方适配器路径。把它们加入 `GenerateOptions` 或提供方无关的 LLM seam,会让 pi-ai 与未来每个适配器接触 DeepSeek 协议概念。
|
||||
|
||||
## 决策
|
||||
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` 注册 `ctx.deepseekLlmApiExtensions`,即 `deepseek-official` 请求正文顶层字段的增量注册表。贡献方通过 `register()` 认领一个经声明合并的字段。适配器在序列化确切协议消息后调用 `prepare()`、传入请求取消信号,在 HTTP 前拒绝准备失败或基础字段冲突,合并分离字段,并在 HTTP 2xx 后调用捕获的 `accept()` 事务。即使贡献方忽略信号,注册表也会在取消后停止等待准备。接受失败仍以 `REQUEST_EXTENSION` 使请求失败;传输失败与非 2xx 失败绝不会接受贡献。未挂载注册表的组合会保留可复用基础适配器。
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` 注册 `ctx.deepseekLlmApiExtensions`,即 `deepseek-official` 请求正文顶层字段的增量注册表。贡献方通过 `register()` 认领一个经声明合并的字段。适配器在序列化确切协议消息后调用 `prepare()`、传入请求取消信号,在 HTTP 前拒绝准备失败或基础字段冲突,合并分离字段,并在 HTTP 2xx 后调用捕获的 `accept()` 事务。即使贡献方忽略信号,注册表也会在取消后停止等待准备。接受失败仍以 `REQUEST_EXTENSION` 使请求失败;传输失败与非 2xx 失败绝不会接受贡献。未挂载注册表的组合会保留可复用基础适配器。随附组合会挂载注册表与两个贡献方:插件包元数据默认开启,会话日志上传默认关闭,需要设置 `session-log-deepseek.enabled: true`。无密钥 `deepseek-official` 回放会使用合成的空基础正文执行准备,并在第一个已记录分片前调用同一接受事务;它保持的是 2xx 后扩展副作用,而非字段字节。
|
||||
|
||||
随附组合会挂载注册表与默认开启的插件包贡献方。无密钥 `deepseek-official` 回放会使用合成的空基础正文执行准备,并在第一个已记录分片前调用同一接受事务;它保持的是 2xx 后扩展副作用,而非字段字节。提供方无关的 `llm` 包与 `llm-pi-ai` 不包含任何扩展类型、服务查找、字段合并或接受调用。
|
||||
提供方无关的 `llm` 包与 `llm-pi-ai` 不包含任何扩展类型、服务查找、字段合并或接受调用。
|
||||
|
||||
## 增量会话日志字段
|
||||
|
||||
`@deepseek-ai/dsh-session-log-deepseek` 以显式选择启用的方式拥有 `dsh_session_log`。启用后,每个携带存活会话 id 的请求都会发送该确切会话身份最大持久 `session-log-deepseek/accepted` 水位之后的连续权威事件后缀。该字段包含不可变会话 header 与完整事件信封。2xx 会为已发送的 `throughSeq` 追加新水位;该事件会进入下一次请求的后缀。Fork 日志会保留父级水位 id,因此子会话会在自己的身份下从序列零开始。并发接受可能乱序到达,最大水位仍保持权威。进程内 fold 会让每条会话事件只被扫描一次,并增量消费后续追加;新的会话对象或 HMR generation 会从持久历史重建该 fold。
|
||||
|
||||
失败方向为至少一次。传输失败或提供方拒绝不会记录水位。远端接受后、水位持久化前发生崩溃,会在恢复后触发重放,绝不会跳过序列。现有会话检查点会持久化该事件;上传插件不拥有第二份存储。
|
||||
|
||||
只有在确切的请求相对引用能够减少编码字节时,事件字符串才不使用原始形式。一个引用会标识一条已序列化 DeepSeek 消息、通向一个解析后字符串值的路径,以及半开 UTF-8 字节范围。编码器会验证每个候选范围都能解码为完全相同的已匹配 UTF-16 子串;拆分 surrogate 或非良构 UTF-16 的候选会保持原始形式。解码器会精确重建每个字面值与引用字节,并拒绝无效路径、范围或码点切分。系统不使用模糊相似度、规范化或有损省略。
|
||||
|
||||
## 插件包字段
|
||||
|
||||
@@ -42,15 +50,19 @@ Status: implemented
|
||||
|
||||
## 验证
|
||||
|
||||
注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放固定 2xx 后扩展接受,真实 Loader 组合检查默认元数据字段,一个带凭据的真实 API 请求会挂载生产贡献方;pi-ai 测试保持其协议请求不变。
|
||||
注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。会话测试固定默认关闭策略、显式启用后的首次完整/后续后缀交付、增量水位 fold、持久化重启恢复、fork 身份围栏、乱序接受、确切 Unicode 重建、无效引用、surrogate 安全原始回退与 invariant 延迟加载。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放会固定 2xx 后扩展接受,真实 Loader 组合会固定默认包元数据与显式启用的会话上传,一个真实 API 请求会挂载两个随附扩展并证明官方端点接受它们;pi-ai 测试保持其协议请求不变。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**向 `GenerateOptions` 或 `ctx.llm` 添加通用元数据。** 已否决,因为该值与接受时点属于 DeepSeek 协议语义;提供方无关请求会迫使每个适配器理解或忽略外来字段。
|
||||
**向 `GenerateOptions` 或 `ctx.llm` 添加通用元数据。** 已否决,因为这些值与接受时点属于 DeepSeek 协议语义;提供方无关请求会迫使每个适配器理解或忽略外来字段。
|
||||
|
||||
**把包发现硬编码进 `llm-deepseek`。** 已否决,因为适配器将导入 Loader、preset 与包 manifest 逻辑。注册表让传输只负责字段合并与 HTTP 接受。
|
||||
**把两个提供方硬编码进 `llm-deepseek`。** 已否决,因为适配器将导入会话、Loader、preset、包 manifest 与游标逻辑。注册表让传输只负责字段合并与 HTTP 接受。
|
||||
|
||||
**清点每个存活 Cordis fiber。** 已否决,因为编程式与内存 fiber 没有权威 npm 包来源。Loader 支撑的宿主与 preset 配置项能提供可精确解析的包身份。
|
||||
**使用模糊消息相似度,或省略重叠事件数据。** 已否决,因为接收方无法重建权威日志。带原始回退的确切字节引用会保留每个值。
|
||||
|
||||
**只在内存中保留上传游标。** 已否决,因为普通进程重启会重发完整会话。权威接受事件让重启恢复获得尽力而为的持久性,无需另一存储后端;剩余崩溃窗口只会产生允许的重复。
|
||||
|
||||
**清点每个存活 Cordis fiber。** 已否决,因为编程式与内存 fiber 没有权威 NPM 包来源。Loader 支撑的宿主与 preset 配置项能提供可精确解析的包身份。
|
||||
|
||||
**cache 一份全进程清单,或按 TTL 使其过期。** 已否决,因为单份不可变清单无法正确反映 Loader 生命周期与逐会话 preset,TTL 则允许元数据在过期边界之间保持陈旧。暂缓的 epoch 设计会根据权威存活状态转换执行失效。
|
||||
|
||||
@@ -58,6 +70,8 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
DeepSeek 官方请求会把存活包版本发送到解析后的 `baseURL`,包括已配置 gateway。该字段对模型不可见,不增加提示词 token,也不改变 KV Cache。manifest 解析、字段冲突、接受处理或提供方 schema 拒绝会使模型请求失败,而不会静默丢弃元数据。
|
||||
DeepSeek 官方请求会把存活包版本发送到解析后的 `baseURL`,包括已配置 gateway。显式选择启用会话日志后,请求还会携带完整的未接受会话新后缀。这些字段对模型不可见,不增加提示词 token,也不改变 KV Cache,但可能显著增大 HTTP 正文。编码、manifest 解析、字段冲突、接受记录或提供方 schema 拒绝会使模型请求失败,而不会静默丢弃元数据。
|
||||
|
||||
缺少存活 Agent 的直接调用仍会携带宿主包清单。[DeepSeek 请求身份决策](../feature/2026-08-11-deepseek-request-user-id-header.zh.md)继续拥有 user/session header,且这些 header 仍位于正文之外。
|
||||
已接受水位事件会成为权威日志的一部分,并在后续请求中自行交付。崩溃恢复可能重复后缀,但不会根据 assistant 输出推断接受,也不会创建第二份本地游标存储。缺少存活会话的直接调用会省略会话字段;宿主包清单仍然可用。
|
||||
|
||||
[DeepSeek 请求身份决策](../feature/2026-08-11-deepseek-request-user-id-header.zh.md)继续拥有 user/session header,且这些 header 仍位于正文之外。[会话遥测决策](../feature/2026-07-23-session-telemetry-otel-revival.zh.md)在另一项变更删除该 seam 与后端之前仍保持当前有效;本请求路径不改变 OTel 捕获或共享模式。
|
||||
@@ -18,6 +18,8 @@ flowchart LR
|
||||
cfg --> plugin_dsh_base_deepseek_llm_api_extensions
|
||||
plugin_dsh_base_session["session<br/>@deepseek-ai/dsh-session"]
|
||||
cfg --> plugin_dsh_base_session
|
||||
plugin_dsh_base_session_log_deepseek["session-log-deepseek<br/>@deepseek-ai/dsh-session-log-deepseek"]
|
||||
cfg --> plugin_dsh_base_session_log_deepseek
|
||||
plugin_dsh_base_typert["typert<br/>@deepseek-ai/dsh-typert-registry"]
|
||||
cfg --> plugin_dsh_base_typert
|
||||
plugin_dsh_base_typert_loader["typert-loader<br/>@deepseek-ai/dsh-typert-loader"]
|
||||
@@ -177,6 +179,7 @@ flowchart LR
|
||||
| `llm` | `@deepseek-ai/dsh-llm` |
|
||||
| `deepseek-llm-api-extensions` | `@deepseek-ai/dsh-deepseek-llm-api-extensions` |
|
||||
| `session` | `@deepseek-ai/dsh-session` |
|
||||
| `session-log-deepseek` | `@deepseek-ai/dsh-session-log-deepseek` |
|
||||
| `typert` | `@deepseek-ai/dsh-typert-registry` |
|
||||
| `typert-loader` | `@deepseek-ai/dsh-typert-loader` |
|
||||
| `typert-gateway` | `@deepseek-ai/dsh-api-gateway` |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/capability-seams.md
|
||||
capability-seams.md: 44ee46a0bc6472c62154df9e4111ed6e6649bacd
|
||||
capability-seams.zh.md: 62ae7ac460ac9fc1c3df62d38a9dd64956744cec
|
||||
capability-seams.md: 86760ca4fc83d921bc489b03b5156ab692f04dff
|
||||
capability-seams.zh.md: 2a44729c0b552bf26a6f51132ceb4ca31c256814
|
||||
@@ -20,6 +20,7 @@ flowchart LR
|
||||
pkg_compaction_basic["compaction-basic"]
|
||||
pkg_deepseek_llm_api_extensions["deepseek-llm-api-extensions"]
|
||||
svc_deepseekLlmApiExtensions["ctx.deepseekLlmApiExtensions<br/>Official DeepSeek request extensions"]
|
||||
pkg_session_log_deepseek["session-log-deepseek"]
|
||||
pkg_plugin_package_inventory_deepseek["plugin-package-inventory-deepseek"]
|
||||
pkg_token_meter["token-meter"]
|
||||
svc_tokenMeter["ctx.tokenMeter<br/>Replay token measurement"]
|
||||
@@ -259,6 +260,7 @@ flowchart LR
|
||||
pkg_sandbox_local --> svc_sandbox
|
||||
pkg_sandbox_policy --> svc_sandboxPolicy
|
||||
pkg_session --> svc_sessions
|
||||
pkg_session_log_deepseek --> svc_deepseekLlmApiExtensions
|
||||
pkg_session_persistence --> svc_sessionPersistence
|
||||
pkg_session_persistence_jsonl --> svc_sessionPersistence
|
||||
pkg_session_persistence_sqlite --> svc_sessionPersistence
|
||||
@@ -433,7 +435,7 @@ flowchart LR
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | `host-runtime`, [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | The host commits accepted images before session events; provider adapters resolve authorized durable references into provider-native content. |
|
||||
| `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/test-support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compaction-basic`](../packages/compaction/compaction-basic) | - | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. |
|
||||
| `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | Plugins prepare independent top-level fields; the official adapter merges them and commits their delivery state after HTTP acceptance. |
|
||||
| `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | Plugins prepare independent top-level fields; the official adapter merges them and commits their delivery state after HTTP acceptance. |
|
||||
| `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements. |
|
||||
| `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. |
|
||||
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), `subagent-inprocess`, [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | Owns append-only Session instances and emits the durable session event feed. |
|
||||
|
||||
@@ -22,6 +22,7 @@ flowchart LR
|
||||
pkg_compaction_basic["compaction-basic"]
|
||||
pkg_deepseek_llm_api_extensions["deepseek-llm-api-extensions"]
|
||||
svc_deepseekLlmApiExtensions["ctx.deepseekLlmApiExtensions<br/>Official DeepSeek request extensions"]
|
||||
pkg_session_log_deepseek["session-log-deepseek"]
|
||||
pkg_plugin_package_inventory_deepseek["plugin-package-inventory-deepseek"]
|
||||
pkg_token_meter["token-meter"]
|
||||
svc_tokenMeter["ctx.tokenMeter<br/>Replay token measurement"]
|
||||
@@ -261,6 +262,7 @@ flowchart LR
|
||||
pkg_sandbox_local --> svc_sandbox
|
||||
pkg_sandbox_policy --> svc_sandboxPolicy
|
||||
pkg_session --> svc_sessions
|
||||
pkg_session_log_deepseek --> svc_deepseekLlmApiExtensions
|
||||
pkg_session_persistence --> svc_sessionPersistence
|
||||
pkg_session_persistence_jsonl --> svc_sessionPersistence
|
||||
pkg_session_persistence_sqlite --> svc_sessionPersistence
|
||||
@@ -435,7 +437,7 @@ flowchart LR
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `ctx.attachments` | `seam` | [`attachment`](../packages/attachment/attachment) | [`attachment-local`](../packages/attachment/attachment-local) | `host-runtime`, [`llm-pi-ai`](../packages/llm/llm-pi-ai) | - | 宿主会在会话事件之前提交已接受的图片;提供方适配器将已授权的持久引用解析为提供方原生内容。 |
|
||||
| `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/test-support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compaction-basic`](../packages/compaction/compaction-basic) | - | 适配器注册提供方实现;agent loop(智能体循环)与压缩功能调用提供方无关的流服务。 |
|
||||
| `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | 插件准备彼此独立的顶层字段;官方适配器会合并这些字段,并在 HTTP 接受后提交其交付状态。 |
|
||||
| `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | 插件准备彼此独立的顶层字段;官方适配器会合并这些字段,并在 HTTP 接受后提交其交付状态。 |
|
||||
| `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 拥有按会话隔离的回放折叠区;压力消费方共享不可变且带修订版本的测量结果。 |
|
||||
| `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 在摘要压缩前,通过可回放的单节点表层替换来改写过大的当前工具结果。 |
|
||||
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), `subagent-inprocess`, [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | 拥有仅追加的 Session 实例,并发出持久的会话事件流。 |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||
config-catalog.md: 003777a66d598edea65796ef01f19540aa212253
|
||||
config-catalog.zh.md: 16d043b540b64e9ad8ee291bb9454d046c15dadd
|
||||
config-catalog.md: 77af1ffed349e40f800d0ccd14610b2b2a609a0d
|
||||
config-catalog.zh.md: d444f337a0a1a9a7419cda8a7eb8774b0666a961
|
||||
@@ -1724,6 +1724,22 @@ Depends on: `Readable` (`node:stream`) · `Writable` (`node:stream`)
|
||||
|
||||
Source: [`packages/sdk/server/src/index.ts:25`](../packages/sdk/server/src/index.ts)
|
||||
|
||||
<a id="deepseek-aidsh-session-log-deepseek"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-session-log-deepseek`
|
||||
|
||||
Requires: `deepseekLlmApiExtensions` · `sessions`
|
||||
|
||||
```ts config-catalog
|
||||
/** Session-log request contribution configuration. */
|
||||
export interface Config {
|
||||
/** Contribute `dsh_session_log` to official DeepSeek requests. Defaults to `false`. */
|
||||
enabled?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/session/session-log-deepseek/src/index.ts:25`](../packages/session/session-log-deepseek/src/index.ts)
|
||||
|
||||
<a id="deepseek-aidsh-session-persistence-jsonl"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-session-persistence-jsonl`
|
||||
|
||||
@@ -1726,6 +1726,22 @@ export interface JsonRpcConfig {
|
||||
|
||||
来源:[`packages/sdk/server/src/index.ts:29`](../packages/sdk/server/src/index.ts)
|
||||
|
||||
<a id="deepseek-aidsh-session-log-deepseek"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-session-log-deepseek`
|
||||
|
||||
需要:`deepseekLlmApiExtensions` · `sessions`
|
||||
|
||||
```ts config-catalog
|
||||
/** Session-log request contribution configuration. */
|
||||
export interface Config {
|
||||
/** Contribute `dsh_session_log` to official DeepSeek requests. Defaults to `false`. */
|
||||
enabled?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/session/session-log-deepseek/src/index.ts:25`](../packages/session/session-log-deepseek/src/index.ts)
|
||||
|
||||
<a id="deepseek-aidsh-session-persistence-jsonl"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-session-persistence-jsonl`
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
|
||||
event-producer-consumer.md: c3d476fed6e4878e10a81858f918368c75bf7523
|
||||
event-producer-consumer.zh.md: 82a754094d9bc2daa1fe585d7da5cb33d67aa0a6
|
||||
event-producer-consumer.md: ee1ab4fe3eba4b6585e980a096a4b50467a7d287
|
||||
event-producer-consumer.zh.md: f9873bff957e6d141221ff6f5af000a585d25f69
|
||||
@@ -40,7 +40,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `apiproxy`, [`llm`](../packages/llm/llm) |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:65`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) |
|
||||
| `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:64`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, `apiproxy`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:85`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry) |
|
||||
@@ -71,7 +71,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
|
||||
| Event string | Dispatchers | Listeners |
|
||||
| --- | --- | --- |
|
||||
| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workflow`](../packages/workflow/workflow) |
|
||||
| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workflow`](../packages/workflow/workflow) |
|
||||
| `internal/plugin` | - | `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), `modules`, `webserver` |
|
||||
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
|
||||
| `internal/status` | - | [`agent`](../packages/core/agent) |
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `apiproxy`, [`llm`](../packages/llm/llm) |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:65`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) |
|
||||
| `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:64`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, `apiproxy`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:85`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry) |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/module-graph.md
|
||||
module-graph.md: 924ec71647a391172d3acb41f53351340330b501
|
||||
module-graph.zh.md: 594de1d2bd17744e5d611f8b5987d01e27cc1538
|
||||
module-graph.md: df9b8e1a1fb0a69c2e18ea23773e51a3fecd8a34
|
||||
module-graph.zh.md: 914daf784b21d14f44bf7001a160b81af1adbd48
|
||||
@@ -272,6 +272,7 @@ flowchart TD
|
||||
end
|
||||
subgraph group_session["packages/session"]
|
||||
pkg_session_checkpoint_policy["session-checkpoint-policy"]
|
||||
pkg_session_log_deepseek["session-log-deepseek"]
|
||||
pkg_session_persistence["session-persistence"]
|
||||
pkg_session_persistence_jsonl["session-persistence-jsonl"]
|
||||
pkg_session_persistence_sqlite["session-persistence-sqlite"]
|
||||
@@ -495,6 +496,9 @@ flowchart TD
|
||||
pkg_sandbox --> pkg_invariants
|
||||
pkg_sandbox --> pkg_llm
|
||||
pkg_sandbox --> pkg_session
|
||||
pkg_session_log_deepseek --> pkg_deepseek_llm_api_extensions
|
||||
pkg_session_log_deepseek --> pkg_invariants
|
||||
pkg_session_log_deepseek --> pkg_session
|
||||
pkg_session_persistence --> pkg_brand
|
||||
pkg_session_persistence --> pkg_invariants
|
||||
pkg_session_persistence --> pkg_session
|
||||
@@ -1560,6 +1564,7 @@ flowchart TD
|
||||
| [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
|
||||
| [`persona`](../packages/preset/persona) | `preset` | [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
|
||||
| [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
|
||||
| [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
|
||||
| [`acp-snapshot`](../packages/test-support/acp-snapshot) | `test-support` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
|
||||
|
||||
@@ -274,6 +274,7 @@ flowchart TD
|
||||
end
|
||||
subgraph group_session["packages/session"]
|
||||
pkg_session_checkpoint_policy["session-checkpoint-policy"]
|
||||
pkg_session_log_deepseek["session-log-deepseek"]
|
||||
pkg_session_persistence["session-persistence"]
|
||||
pkg_session_persistence_jsonl["session-persistence-jsonl"]
|
||||
pkg_session_persistence_sqlite["session-persistence-sqlite"]
|
||||
@@ -497,6 +498,9 @@ flowchart TD
|
||||
pkg_sandbox --> pkg_invariants
|
||||
pkg_sandbox --> pkg_llm
|
||||
pkg_sandbox --> pkg_session
|
||||
pkg_session_log_deepseek --> pkg_deepseek_llm_api_extensions
|
||||
pkg_session_log_deepseek --> pkg_invariants
|
||||
pkg_session_log_deepseek --> pkg_session
|
||||
pkg_session_persistence --> pkg_brand
|
||||
pkg_session_persistence --> pkg_invariants
|
||||
pkg_session_persistence --> pkg_session
|
||||
@@ -1562,6 +1566,7 @@ flowchart TD
|
||||
| [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
|
||||
| [`persona`](../packages/preset/persona) | `preset` | [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
|
||||
| [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
|
||||
| [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
|
||||
| [`acp-snapshot`](../packages/test-support/acp-snapshot) | `test-support` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
|
||||
persistence-catalog.md: b505bc4ce1c71198ffc316a4f06d3c00174f3fb7
|
||||
persistence-catalog.zh.md: 715ae6515f8e7c6df936229becc41d492425b1ef
|
||||
persistence-catalog.md: 3969ec00068a0b40eb7362a1805313ad35c84e5e
|
||||
persistence-catalog.zh.md: f0e58af4181c60e1d675f7f5b394accfee0d7cf0
|
||||
@@ -667,6 +667,24 @@ Types: [SessionTitleLlmRequestEventData](subsystems/session-title.md)
|
||||
|
||||
Source: [`packages/session/session-title-llm/src/index.ts:43`](../packages/session/session-title-llm/src/index.ts)
|
||||
|
||||
### `session-log-deepseek/*`
|
||||
|
||||
<a id="session-log-deepseekaccepted--log-only"></a>
|
||||
|
||||
#### `session-log-deepseek/accepted` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Records a confirmed HTTP acceptance watermark for restart-safe suffix selection. */
|
||||
'session-log-deepseek/accepted': {
|
||||
/** Session identity the accepted request carried; inherited fork markers retain the parent's id. */
|
||||
sessionId: import('@deepseek-ai/dsh-session/types').SessionId
|
||||
/** Last canonical event included in the accepted request. */
|
||||
throughSeq: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/session/session-log-deepseek/src/types.ts:54`](../packages/session/session-log-deepseek/src/types.ts)
|
||||
|
||||
### `step/*`
|
||||
|
||||
<a id="stepend--log-only"></a>
|
||||
|
||||
@@ -669,6 +669,24 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
来源:[`packages/session/session-title-llm/src/index.ts:43`](../packages/session/session-title-llm/src/index.ts)
|
||||
|
||||
### `session-log-deepseek/*`
|
||||
|
||||
<a id="session-log-deepseekaccepted--log-only"></a>
|
||||
|
||||
#### `session-log-deepseek/accepted` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Records a confirmed HTTP acceptance watermark for restart-safe suffix selection. */
|
||||
'session-log-deepseek/accepted': {
|
||||
/** Session identity the accepted request carried; inherited fork markers retain the parent's id. */
|
||||
sessionId: import('@deepseek-ai/dsh-session/types').SessionId
|
||||
/** Last canonical event included in the accepted request. */
|
||||
throughSeq: number
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/session/session-log-deepseek/src/types.ts:54`](../packages/session/session-log-deepseek/src/types.ts)
|
||||
|
||||
### `step/*`
|
||||
|
||||
<a id="stepend--log-only"></a>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md
|
||||
llm-streaming.md: a0d099bc43d1b6d17cd757e3c6bf9f9ac83f3d4f
|
||||
llm-streaming.zh.md: 585134620e6b378418ac158af623388a0cfda53d
|
||||
llm-streaming.md: 4ad7af5673f3894d86af72b04db365fcc0f36608
|
||||
llm-streaming.zh.md: ff62bccdac018ea57b58d5edec9b7dae448be76a
|
||||
@@ -666,7 +666,7 @@ interface LlmCallConfigAdapterDefaults {
|
||||
|
||||
`ctx.deepseekLlmApiExtensions` is the provider-specific registry for additive top-level fields on `deepseek-official` requests. Contributor plugins use `register(field, provider)` to claim one field; the adapter calls `prepare(request)` after serializing its base body and merges the returned fields before HTTP. The prepared `accept()` transaction runs after 2xx, so a contributor can commit delivery state without treating a transport or provider rejection as acceptance. Preparation, collision, and acceptance failures use `REQUEST_EXTENSION` and fail the model request.
|
||||
|
||||
The [wire reference](../deepseek-llm-api-wire-extensions.md) defines the exact request headers, extension transaction, field versions, and receiver obligations. The shipped composition registers [`dsh_plugin_packages`](../../packages/llm/plugin-package-inventory-deepseek/README.md) as the complete active Loader-backed package set. The field remains outside model messages and is absent from the pi-ai adapter path.
|
||||
The [wire reference](../deepseek-llm-api-wire-extensions.md) defines the exact request headers, extension transaction, field versions, and receiver obligations. The shipped composition registers [`dsh_session_log`](../../packages/session/session-log-deepseek/README.md) as a lossless incremental canonical-log suffix and [`dsh_plugin_packages`](../../packages/llm/plugin-package-inventory-deepseek/README.md) as the complete active Loader-backed package set. These fields remain outside model messages and are absent from the pi-ai adapter path.
|
||||
|
||||
## Service and provider contracts
|
||||
|
||||
|
||||
@@ -672,7 +672,7 @@ interface LlmCallConfigAdapterDefaults {
|
||||
|
||||
`ctx.deepseekLlmApiExtensions` 是用于向 `deepseek-official` 请求添加顶层字段的提供方特定注册表。贡献插件通过 `register(field, provider)` 认领一个字段;适配器在序列化基础正文后调用 `prepare(request)`,并在 HTTP 前合并返回字段。已准备的 `accept()` 事务会在 2xx 后运行,因此贡献方可以提交交付状态,而不会把传输失败或提供方拒绝当作接受。准备、冲突与接受失败会使用 `REQUEST_EXTENSION`,并使模型请求失败。
|
||||
|
||||
[协议参考](../deepseek-llm-api-wire-extensions.zh.md)定义确切的请求标头、扩展事务、字段版本和接收方义务。随附组合会将 [`dsh_plugin_packages`](../../packages/llm/plugin-package-inventory-deepseek/README.zh.md) 注册为完整存活 Loader 包集合。该字段仍位于模型消息之外,也不会进入 pi-ai 适配器路径。
|
||||
[协议参考](../deepseek-llm-api-wire-extensions.zh.md)定义确切的请求标头、扩展事务、字段版本和接收方义务。随附组合会将 [`dsh_session_log`](../../packages/session/session-log-deepseek/README.zh.md) 注册为无损增量权威日志后缀,并将 [`dsh_plugin_packages`](../../packages/llm/plugin-package-inventory-deepseek/README.zh.md) 注册为完整存活 Loader 包集合。这些字段仍位于模型消息之外,也不会进入 pi-ai 适配器路径。
|
||||
|
||||
## 服务与提供方约定
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ flowchart LR
|
||||
cfg["examples/acp-agent<br/>cordis.yml"]
|
||||
plugin_acp_deepseek_llm_api_extensions["deepseek-llm-api-extensions<br/>@deepseek-ai/dsh-deepseek-llm-api-extensions"]
|
||||
cfg --> plugin_acp_deepseek_llm_api_extensions
|
||||
plugin_acp_session_log_deepseek["session-log-deepseek<br/>@deepseek-ai/dsh-session-log-deepseek"]
|
||||
cfg --> plugin_acp_session_log_deepseek
|
||||
plugin_acp_plugin_package_inventory_deepseek["plugin-package-inventory-deepseek<br/>@deepseek-ai/dsh-plugin-package-inventory-deepseek"]
|
||||
cfg --> plugin_acp_plugin_package_inventory_deepseek
|
||||
plugin_acp_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
|
||||
@@ -80,6 +82,7 @@ flowchart LR
|
||||
| Plugin id | Package / module |
|
||||
| --- | --- |
|
||||
| `deepseek-llm-api-extensions` | `@deepseek-ai/dsh-deepseek-llm-api-extensions` |
|
||||
| `session-log-deepseek` | `@deepseek-ai/dsh-session-log-deepseek` |
|
||||
| `plugin-package-inventory-deepseek` | `@deepseek-ai/dsh-plugin-package-inventory-deepseek` |
|
||||
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
|
||||
| `sandbox` | `@deepseek-ai/dsh-sandbox-local` |
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
- id: deepseek-llm-api-extensions
|
||||
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
|
||||
- id: session-log-deepseek
|
||||
name: '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
- id: plugin-package-inventory-deepseek
|
||||
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ flowchart LR
|
||||
cfg --> plugin_headless_credentials
|
||||
plugin_headless_deepseek_llm_api_extensions["deepseek-llm-api-extensions<br/>@deepseek-ai/dsh-deepseek-llm-api-extensions"]
|
||||
cfg --> plugin_headless_deepseek_llm_api_extensions
|
||||
plugin_headless_session_log_deepseek["session-log-deepseek<br/>@deepseek-ai/dsh-session-log-deepseek"]
|
||||
cfg --> plugin_headless_session_log_deepseek
|
||||
plugin_headless_plugin_package_inventory_deepseek["plugin-package-inventory-deepseek<br/>@deepseek-ai/dsh-plugin-package-inventory-deepseek"]
|
||||
cfg --> plugin_headless_plugin_package_inventory_deepseek
|
||||
plugin_headless_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
|
||||
@@ -69,6 +71,7 @@ flowchart LR
|
||||
| `settings` | `@deepseek-ai/dsh-settings-file` |
|
||||
| `credentials` | `@deepseek-ai/dsh-credentials-local` |
|
||||
| `deepseek-llm-api-extensions` | `@deepseek-ai/dsh-deepseek-llm-api-extensions` |
|
||||
| `session-log-deepseek` | `@deepseek-ai/dsh-session-log-deepseek` |
|
||||
| `plugin-package-inventory-deepseek` | `@deepseek-ai/dsh-plugin-package-inventory-deepseek` |
|
||||
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
|
||||
| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
- id: deepseek-llm-api-extensions
|
||||
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
|
||||
- id: session-log-deepseek
|
||||
name: '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
- id: plugin-package-inventory-deepseek
|
||||
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
- id: deepseek-llm-api-extensions
|
||||
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
|
||||
- id: session-log-deepseek
|
||||
name: '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
- id: plugin-package-inventory-deepseek
|
||||
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
- id: deepseek-llm-api-extensions
|
||||
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
|
||||
- id: session-log-deepseek
|
||||
name: '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
- id: plugin-package-inventory-deepseek
|
||||
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@deepseek-ai/dsh-llm-deepseek": "workspace:*",
|
||||
"@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:*",
|
||||
"@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-log-deepseek": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm-pi-ai": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm-replay": "workspace:*",
|
||||
"@deepseek-ai/dsh-loader-smoke": "workspace:*",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
- id: session
|
||||
name: '@deepseek-ai/dsh-session'
|
||||
|
||||
- id: session-log-deepseek
|
||||
name: '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
- id: typert
|
||||
name: '@deepseek-ai/dsh-typert-registry'
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-checkpoint-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-log-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-query-sqlite": "workspace:^",
|
||||
|
||||
@@ -42,6 +42,7 @@ export const KNOWN_SESSION_EVENT_TYPES: ReadonlySet<string> = new Set([
|
||||
'request/header',
|
||||
'sandbox/mode',
|
||||
'schedule/change',
|
||||
'session-log-deepseek/accepted',
|
||||
'session/end-seed',
|
||||
'session/title',
|
||||
'session/title-llm-request',
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/llm/deepseek-llm-api-extensions/README.md
|
||||
README.md: fd25e1e047316acc4b92c0ea2c5cc3fde85cbe8a
|
||||
README.zh.md: 9f7d8e75bb6ff042d946a918e746f8b1871b2f08
|
||||
README.md: 5680b58ee2baff6a6ee2ba0249a7ea145ee7eb78
|
||||
README.zh.md: 9e7ae393b0cc72328101c89a245b8a3ace173503
|
||||
@@ -12,7 +12,7 @@ Provider-specific registry for additive top-level fields on official DeepSeek LL
|
||||
|
||||
Each provider sees the exact serialized base body, the request `AbortSignal`, plus optional `sessionId` and auxiliary-call `purpose`. It must stop its own work promptly after cancellation and returns `undefined` when its field does not apply to that request. A prepared operation retains the providers it captured even if HMR removes their registrations before HTTP acceptance.
|
||||
|
||||
The registry owns addition and lifecycle, not field semantics. `@deepseek-ai/dsh-plugin-package-inventory-deepseek` owns the initial `dsh_plugin_packages` field. The provider-neutral LLM seam and `llm-pi-ai` do not consume this registry.
|
||||
The registry owns addition and lifecycle, not field semantics. `@deepseek-ai/dsh-session-log-deepseek` owns `dsh_session_log`; `@deepseek-ai/dsh-plugin-package-inventory-deepseek` owns `dsh_plugin_packages`. The provider-neutral LLM seam and `llm-pi-ai` do not consume this registry.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
每个提供方都会看到确切的已序列化基础正文、请求 `AbortSignal`,以及可选的 `sessionId` 与辅助调用 `purpose`。提供方必须在取消后迅速停止自身工作;字段不适用于当前请求时返回 `undefined`。即使 HMR(热模块替换)在 HTTP 接受前移除了注册,已准备的操作仍会保留其捕获的提供方。
|
||||
|
||||
注册表拥有字段添加与生命周期,不拥有字段语义。`@deepseek-ai/dsh-plugin-package-inventory-deepseek` 拥有首个 `dsh_plugin_packages` 字段。提供方无关的 LLM seam 与 `llm-pi-ai` 都不消费该注册表。
|
||||
注册表拥有字段添加与生命周期,不拥有字段语义。`@deepseek-ai/dsh-session-log-deepseek` 拥有 `dsh_session_log`;`@deepseek-ai/dsh-plugin-package-inventory-deepseek` 拥有 `dsh_plugin_packages`。提供方无关的 LLM seam 与 `llm-pi-ai` 都不消费该注册表。
|
||||
|
||||
## 模型体验
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/llm/llm-deepseek/README.md
|
||||
README.md: f50de33dfbf7229968c9335972b47ab5a0bd1c41
|
||||
README.zh.md: 3820972a6cc1eb7a1c7b0855c718c6695b6a3c76
|
||||
README.md: 7d50a8e99863637a06abf49d26a6eeb419cf1bbd
|
||||
README.zh.md: c1571149529a2d4e54d67b10f63b60bb2aa1abfd
|
||||
@@ -88,7 +88,7 @@ The plugin also declares its route in the configurable-provider directory (`ctx.
|
||||
|
||||
When `ctx.deepseekLlmApiExtensions` is present, the adapter prepares its registered top-level fields after serializing the exact wire messages and before `fetch`. The same request signal reaches providers, and cancellation stops waiting even when a provider ignores it. Preparation failures and field collisions fail before HTTP with `REQUEST_EXTENSION`. After HTTP 2xx, the adapter awaits the prepared acceptance transaction before consuming the SSE body; an acceptance failure uses the same code, while transport and non-2xx failures do not accept the fields. Fields go to the resolved `baseURL`, including a configured gateway. A composition without the registry sends the base DeepSeek request unchanged.
|
||||
|
||||
Shipped profiles and runnable examples mount [`@deepseek-ai/dsh-plugin-package-inventory-deepseek`](../plugin-package-inventory-deepseek/README.md) for the complete active `dsh_plugin_packages` field. Package metadata defaults on and remains model-hidden. `llm-pi-ai` neither imports nor calls this provider-specific registry.
|
||||
Shipped profiles and runnable examples mount [`@deepseek-ai/dsh-session-log-deepseek`](../../session/session-log-deepseek/README.md) for the incremental `dsh_session_log` field and [`@deepseek-ai/dsh-plugin-package-inventory-deepseek`](../plugin-package-inventory-deepseek/README.md) for the complete active `dsh_plugin_packages` field. Session-log upload defaults off and requires `session-log-deepseek.enabled: true`; package metadata defaults on. Both remain model-hidden. `llm-pi-ai` neither imports nor calls this provider-specific registry.
|
||||
|
||||
## App attribution
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ harness LLM(大语言模型)seam 的 DeepSeek chat-completions 适配器:
|
||||
|
||||
存在 `ctx.deepseekLlmApiExtensions` 时,适配器会在序列化确切协议消息后、`fetch` 前准备其中已注册的顶层字段。提供方会收到同一个请求信号;即使某个提供方忽略信号,取消也会停止等待。准备失败与字段冲突会在 HTTP 前以 `REQUEST_EXTENSION` 失败。HTTP 2xx 后,适配器会先等待已准备的接受事务,再消费 SSE 正文;接受失败使用同一 code,而传输失败与非 2xx 失败不会接受字段。字段会发往解析后的 `baseURL`,包括已配置的网关。未组合该注册表的部署会发送未改变的 DeepSeek 基础请求。
|
||||
|
||||
随附 profile 与可运行示例会挂载 [`@deepseek-ai/dsh-plugin-package-inventory-deepseek`](../plugin-package-inventory-deepseek/README.zh.md) 以提供完整存活 `dsh_plugin_packages` 字段。插件包元数据默认开启且对模型不可见。`llm-pi-ai` 既不导入也不调用该提供方特定注册表。
|
||||
随附 profile 与可运行示例会挂载 [`@deepseek-ai/dsh-session-log-deepseek`](../../session/session-log-deepseek/README.zh.md) 以提供增量 `dsh_session_log` 字段,并挂载 [`@deepseek-ai/dsh-plugin-package-inventory-deepseek`](../plugin-package-inventory-deepseek/README.zh.md) 以提供完整存活 `dsh_plugin_packages` 字段。会话日志上传默认关闭,需要设置 `session-log-deepseek.enabled: true`;插件包元数据默认开启。两个字段都对模型不可见。`llm-pi-ai` 既不导入也不调用该提供方特定注册表。
|
||||
|
||||
## 应用归因
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"@deepseek-ai/dsh-home-paths": "workspace:^",
|
||||
"@deepseek-ai/dsh-settings": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-log-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-timeout": "workspace:^",
|
||||
"@deepseek-ai/dsh-anonymous-user-id": "workspace:^",
|
||||
"@deepseek-ai/cordis": "workspace:^"
|
||||
|
||||
@@ -19,8 +19,10 @@ import type {
|
||||
StoredImageAttachment,
|
||||
} from '@deepseek-ai/dsh-attachment'
|
||||
import { LocalCredentialProvider } from '@deepseek-ai/dsh-credentials-local'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import DeepSeekLlmApiExtensionRegistry from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
import * as PluginPackageInventoryDeepSeek from '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
import * as SessionLogDeepSeek from '@deepseek-ai/dsh-session-log-deepseek'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import type { Config } from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import { assemble, type AssembledResult } from './assemble.ts'
|
||||
@@ -184,23 +186,29 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('llm-deepseek e2e (real API)', ()
|
||||
}
|
||||
})
|
||||
|
||||
it('accepts the plugin-package request extension field', async () => {
|
||||
it('accepts the session-log and plugin-package request extension fields', async () => {
|
||||
const ctx = new Context()
|
||||
contexts.push(ctx)
|
||||
await ctx.plugin(Loader)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(LlmRuntime)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(DeepSeekLlmApiExtensionRegistry)
|
||||
await ctx.plugin(SessionLogDeepSeek, { enabled: true })
|
||||
await ctx.plugin(PluginPackageInventoryDeepSeek)
|
||||
await ctx.plugin(LlmDeepSeek, { thinking: 'disabled' })
|
||||
const session = ctx.sessions.create(SessionId('real-extension-fields'))
|
||||
session.append('turn/start', { turn: 1 })
|
||||
|
||||
const result = await assemble(ctx, {
|
||||
model: FLASH,
|
||||
messages: ask('Reply with exactly the word: pong'),
|
||||
maxTokens: 50,
|
||||
sessionId: session.id,
|
||||
})
|
||||
expect(result.finish.kind).toBe('stop')
|
||||
expect(textOf(result).toLowerCase()).toContain('pong')
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(0)
|
||||
})
|
||||
|
||||
it('serves a real request with the key held only by a credentials-local document', async () => {
|
||||
|
||||
@@ -18,12 +18,14 @@ import Loader from '@deepseek-ai/cordis-plugin-loader'
|
||||
import Include from '@deepseek-ai/cordis-plugin-include'
|
||||
import LlmRuntime from '@deepseek-ai/dsh-llm'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
||||
import LocalCredentialProvider from '@deepseek-ai/dsh-credentials-local'
|
||||
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
||||
import FileSettingsProvider from '@deepseek-ai/dsh-settings-file'
|
||||
import { getOrCreateAnonymousUserId } from '@deepseek-ai/dsh-anonymous-user-id'
|
||||
import DeepSeekLlmApiExtensionRegistry from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
import * as SessionLogDeepSeek from '@deepseek-ai/dsh-session-log-deepseek'
|
||||
import * as DeepSeekPluginPackageInventory from '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import { assemble } from './assemble.ts'
|
||||
@@ -45,7 +47,7 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
async function loadComposition(
|
||||
options: { withDynamic: boolean; baseURL: string; reuseRoot?: string },
|
||||
options: { withDynamic: boolean; baseURL: string; reuseRoot?: string; enableSessionLog?: boolean },
|
||||
): Promise<{ ctx: Context; settingsPath: string; credentialsPath: string }> {
|
||||
// A reused root is the restart case: the same harness home, its documents
|
||||
// exactly as the previous process left them.
|
||||
@@ -63,10 +65,17 @@ async function loadComposition(
|
||||
await writeFile(configPath, [
|
||||
'- id: llm',
|
||||
" name: '@deepseek-ai/dsh-llm'",
|
||||
'- id: session',
|
||||
" name: '@deepseek-ai/dsh-session'",
|
||||
'- id: agents',
|
||||
" name: '@deepseek-ai/dsh-agent'",
|
||||
'- id: deepseek-llm-api-extensions',
|
||||
" name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'",
|
||||
'- id: session-log-deepseek',
|
||||
" name: '@deepseek-ai/dsh-session-log-deepseek'",
|
||||
...options.enableSessionLog === true
|
||||
? [' config:', ' enabled: true']
|
||||
: [],
|
||||
'- id: plugin-package-inventory-deepseek',
|
||||
" name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'",
|
||||
...options.withDynamic
|
||||
@@ -97,8 +106,10 @@ async function loadComposition(
|
||||
ctx.loader.builtins.include = Include
|
||||
const modules = new Map<string, unknown>([
|
||||
['@deepseek-ai/dsh-llm', LlmRuntime],
|
||||
['@deepseek-ai/dsh-session', SessionStore],
|
||||
['@deepseek-ai/dsh-agent', AgentRegistry],
|
||||
['@deepseek-ai/dsh-deepseek-llm-api-extensions', DeepSeekLlmApiExtensionRegistry],
|
||||
['@deepseek-ai/dsh-session-log-deepseek', SessionLogDeepSeek],
|
||||
['@deepseek-ai/dsh-plugin-package-inventory-deepseek', DeepSeekPluginPackageInventory],
|
||||
['@deepseek-ai/dsh-settings-file', FileSettingsProvider],
|
||||
['@deepseek-ai/dsh-credentials-local', LocalCredentialProvider],
|
||||
@@ -131,19 +142,54 @@ async function loadComposition(
|
||||
}
|
||||
|
||||
describe('llm-deepseek real dynamic composition', () => {
|
||||
it('sends package inventory by default in the real Loader composition', async () => {
|
||||
it('keeps session upload off and package inventory on by default in the real Loader composition', async () => {
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', 'entry-key')
|
||||
const server = await mockServer([{ kind: 'sse', events: textEvents }])
|
||||
const { ctx } = await loadComposition({ withDynamic: false, baseURL: server.url })
|
||||
const session = ctx.sessions.create(SessionId('extension-composition'))
|
||||
session.append('turn/start', { turn: 1 })
|
||||
|
||||
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
|
||||
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [], sessionId: session.id })
|
||||
const request = server.requests[0] as { dsh_plugin_packages: { version: number; packages: unknown[] } }
|
||||
expect(request).not.toHaveProperty('dsh_session_log')
|
||||
expect(request.dsh_plugin_packages.packages).toEqual(expect.arrayContaining([
|
||||
{ name: '@deepseek-ai/dsh-deepseek-llm-api-extensions', version: '0.1.0-rc.8' },
|
||||
{ name: '@deepseek-ai/dsh-llm-deepseek', version: '0.1.0-rc.8' },
|
||||
{ name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek', version: '0.1.0-rc.8' },
|
||||
{ name: '@deepseek-ai/dsh-session-log-deepseek', version: '0.1.0-rc.8' },
|
||||
]))
|
||||
expect(request.dsh_plugin_packages.version).toBe(1)
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(-1)
|
||||
})
|
||||
|
||||
it('sends the canonical session suffix when the Loader composition explicitly enables upload', async () => {
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', 'entry-key')
|
||||
const server = await mockServer([{ kind: 'sse', events: textEvents }])
|
||||
const { ctx } = await loadComposition({
|
||||
withDynamic: false,
|
||||
baseURL: server.url,
|
||||
enableSessionLog: true,
|
||||
})
|
||||
const session = ctx.sessions.create(SessionId('extension-composition-enabled'))
|
||||
session.append('turn/start', { turn: 1 })
|
||||
|
||||
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [], sessionId: session.id })
|
||||
const request = server.requests[0] as {
|
||||
dsh_session_log?: {
|
||||
version: number
|
||||
session: { id: string }
|
||||
afterSeq: number
|
||||
throughSeq: number
|
||||
events: Array<{ event: { type: string; seq: number } }>
|
||||
}
|
||||
}
|
||||
expect(request.dsh_session_log).toMatchObject({
|
||||
version: 1,
|
||||
session: { id: 'extension-composition-enabled' },
|
||||
afterSeq: -1,
|
||||
throughSeq: 0,
|
||||
events: [{ event: { type: 'turn/start', seq: 0 } }],
|
||||
})
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(0)
|
||||
})
|
||||
|
||||
it('boots from cordis.yml and routes the next request after external settings and credential edits', async () => {
|
||||
|
||||
@@ -136,6 +136,7 @@ describe('PiAiAdapter provider routing', () => {
|
||||
thinking: { type: 'enabled' },
|
||||
reasoning_effort: 'max',
|
||||
})
|
||||
expect(server.requests[0]).not.toHaveProperty('dsh_session_log')
|
||||
expect(server.requests[0]).not.toHaveProperty('dsh_plugin_packages')
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/session/session-log-deepseek/README.md
|
||||
README.md: 5c7d077f641accf37b78cdf59ea2f4bca9cacc55
|
||||
README.zh.md: 1612441a768ea6e3bdd061fe4da07e4847f22bbe
|
||||
@@ -0,0 +1,49 @@
|
||||
# @deepseek-ai/dsh-session-log-deepseek
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Incremental canonical session-log upload for official DeepSeek LLM API requests. This function plugin injects `ctx.sessions` and `ctx.deepseekLlmApiExtensions`, then owns the `dsh_session_log` request field and the durable `session-log-deepseek/accepted` watermark event.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Key | Default | Meaning |
|
||||
|---|---:|---|
|
||||
| `enabled` | `false` | Register the `dsh_session_log` contribution. Set it to `true` to opt into Session-log upload. |
|
||||
|
||||
Shipped profiles mount the plugin so an overlay can enable it, but the default configuration registers no request field and appends no acceptance watermark.
|
||||
|
||||
## Request field
|
||||
|
||||
For a request carrying a live `sessionId`, the plugin folds the greatest accepted watermark for that exact Session identity, snapshots `Session.events`, and sends the contiguous suffix after the watermark. A process-local fold scans each event once and consumes later appends incrementally; restart and HMR rebuild it from the durable log. The version-1 field contains the immutable `SessionHeader`, `afterSeq`, `throughSeq`, and every complete event envelope in that range. Forked sessions ignore inherited parent watermarks because each watermark records the Session id sent on the accepted request.
|
||||
|
||||
Each event is sent raw unless request-relative packing reduces its JSON byte size. The codec traverses the exact serialized DeepSeek `messages`, and a packed string can replace an exact substring with `{ messageIndex, path, utf8Start, utf8End }`. References use half-open UTF-8 offsets into parsed string values; literal fragments retain everything outside the match. The encoder verifies each candidate's local UTF-8 round trip, so a surrogate-splitting or ill-formed UTF-16 match stays raw. The exported decoder rejects missing paths, non-string targets, invalid ranges, and ranges that split a UTF-8 code point. Raw fallback and byte-size comparison make every representation lossless and prevent reference overhead from expanding an event.
|
||||
|
||||
## Acceptance and retry
|
||||
|
||||
The DeepSeek adapter calls the prepared contribution's `accept()` after HTTP 2xx, before it consumes the SSE body. Acceptance appends `session-log-deepseek/accepted` with the uploaded `throughSeq`; the next request uploads that watermark as part of its new suffix. Transport and non-2xx failures append no watermark, so later requests resend the uncertain range. Concurrent accepted requests may append watermarks out of order; folding their maximum prevents cursor regression.
|
||||
|
||||
A crash after server acceptance but before the watermark reaches persistence can replay an accepted range after restart. This is the at-least-once failure direction: uncertainty creates duplicates, never a skipped sequence. The ordinary session checkpoint policy persists the watermark at the next semantic checkpoint; this plugin performs no independent I/O.
|
||||
|
||||
Direct requests without a live Session omit `dsh_session_log`. Normal agent, compaction, and session-title calls carry their live Session id.
|
||||
|
||||
## Model Experience
|
||||
|
||||
### Session-log metadata
|
||||
|
||||
#### What the model sees
|
||||
|
||||
Nothing. `dsh_session_log` is a sibling of the DeepSeek request's model-input fields and is not inserted into `messages`, the system prompt, or tool schemas.
|
||||
|
||||
#### Token effect
|
||||
|
||||
Zero model-input tokens; the field only increases HTTP request bytes.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
None; the model-visible request prefix remains unchanged.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Crash-window duplicates** — a 2xx followed by process loss before the acceptance watermark persists causes conservative replay on resume.
|
||||
- **No live Session means no field** — direct or stale-session calls have no canonical log to snapshot; explicit absence semantics remain deferred.
|
||||
- **No independent request-size cap** — complete delivery is fail-closed; provider rejection leaves the cursor unchanged instead of truncating the log.
|
||||
@@ -0,0 +1,49 @@
|
||||
# @deepseek-ai/dsh-session-log-deepseek
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
用于 DeepSeek 官方 LLM API 请求的增量权威会话日志上传。该函数插件注入 `ctx.sessions` 与 `ctx.deepseekLlmApiExtensions`,并拥有 `dsh_session_log` 请求字段和持久的 `session-log-deepseek/accepted` 水位事件。
|
||||
|
||||
## 配置
|
||||
|
||||
| 配置键 | 默认值 | 含义 |
|
||||
|---|---:|---|
|
||||
| `enabled` | `false` | 注册 `dsh_session_log` 贡献。将其设为 `true` 可选择启用会话日志上传。 |
|
||||
|
||||
随附 profile 会挂载该插件,让 overlay 可以启用它;默认配置不会注册请求字段,也不会追加接受水位。
|
||||
|
||||
## 请求字段
|
||||
|
||||
对于携带存活 `sessionId` 的请求,插件会折叠该确切会话身份的最大已接受水位,对 `Session.events` 取快照,并发送水位之后的连续后缀。进程内 fold 会让每条事件只被扫描一次并增量消费后续追加;重启与 HMR 会从持久日志重建它。版本 1 字段包含不可变的 `SessionHeader`、`afterSeq`、`throughSeq`,以及该范围内每个完整事件信封。每个水位都会记录已接受请求发送的会话 id,因此 fork 会话会忽略从父会话继承的水位。
|
||||
|
||||
只有在请求相对打包能够减少 JSON 字节数时,事件才不以原始形式发送。Codec 会遍历确切的已序列化 DeepSeek `messages`,并可在打包字符串中用 `{ messageIndex, path, utf8Start, utf8End }` 替换完全匹配的子字符串。引用使用解析后字符串值的半开 UTF-8 偏移;字面分片保留匹配范围之外的全部内容。编码器会校验每个候选的局部 UTF-8 往返,因此拆分 surrogate 或非良构 UTF-16 的匹配会保持原始形式。导出的解码器会拒绝缺失路径、非字符串目标、无效范围和切断 UTF-8 码点的范围。原始回退与字节数比较让每种表示都保持无损,并阻止引用开销扩张事件。
|
||||
|
||||
## 接受与重试
|
||||
|
||||
DeepSeek 适配器会在 HTTP 2xx 后、消费 SSE(Server-Sent Events)正文前调用已准备贡献的 `accept()`。接受操作会追加 `session-log-deepseek/accepted` 及已上传的 `throughSeq`;下一次请求再把该水位作为新后缀的一部分上传。传输失败与非 2xx 失败不会追加水位,因此后续请求会重发不确定范围。并发请求可能乱序追加已接受水位;折叠其中最大值可以防止游标回退。
|
||||
|
||||
服务端接受后、持久化水位前发生崩溃,可能让恢复后的进程重放已经接受的范围。这是至少一次交付的失败方向:不确定性会制造重复,绝不会跳过序列。普通会话检查点策略会在下一个语义检查点持久化水位;本插件不执行独立 I/O。
|
||||
|
||||
缺少存活会话的直接请求会省略 `dsh_session_log`。普通 agent(智能体)、压缩(compaction)与会话标题调用都会携带存活会话 id。
|
||||
|
||||
## 模型体验
|
||||
|
||||
### 会话日志元数据
|
||||
|
||||
#### 模型看到的内容
|
||||
|
||||
无。`dsh_session_log` 是 DeepSeek 请求中模型输入字段的同级字段,不会插入 `messages`、系统提示词或工具 schema。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
模型输入 token 为零;该字段只会增加 HTTP 请求字节数。
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
无;模型可见请求前缀保持不变。
|
||||
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- **崩溃窗口重复**——2xx 后、接受水位持久化前进程丢失,会在恢复时触发保守重放。
|
||||
- **缺少存活会话就没有字段**——直接调用或陈旧会话调用没有可供快照的权威日志;显式缺失语义仍暂缓处理。
|
||||
- **没有独立请求大小上限**——完整交付会快速失败;提供方拒绝会保持游标不变,而非截断日志。
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-session-log-deepseek",
|
||||
"description": "Incremental lossless session-log request extension for the official DeepSeek LLM API",
|
||||
"version": "0.1.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
||||
"directory": "packages/session/session-log-deepseek"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./invariant": {
|
||||
"types": "./lib/types/invariant.d.ts",
|
||||
"default": "./lib/invariant.js"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./lib/types/types.d.ts",
|
||||
"default": "./lib/types/types.js"
|
||||
},
|
||||
"./src/*": "./src/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/invariant.js",
|
||||
"lib/types/**/*.js",
|
||||
"lib/types/**/*.d.ts"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/schemastery": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/cordis": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/cordis": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/** Lossless request-relative packing for canonical session events. */
|
||||
|
||||
import { Buffer } from 'node:buffer'
|
||||
import type { DeepSeekLlmApiJson } from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
import type { JsonValue, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import type {
|
||||
DeepSeekMessageStringSlice,
|
||||
EncodedSessionEvent,
|
||||
PackedJsonStringPart,
|
||||
PackedJsonValue,
|
||||
} from './types.ts'
|
||||
|
||||
interface MessageStringSource {
|
||||
readonly messageIndex: number
|
||||
readonly path: readonly (string | number)[]
|
||||
readonly value: string
|
||||
readonly bytes: number
|
||||
}
|
||||
|
||||
interface PackedCandidate {
|
||||
readonly value: PackedJsonValue
|
||||
readonly references: boolean
|
||||
}
|
||||
|
||||
/** Return the serialized UTF-8 size used by the profitability decision. */
|
||||
function wireBytes(value: unknown): number {
|
||||
return Buffer.byteLength(JSON.stringify(value), 'utf8')
|
||||
}
|
||||
|
||||
/** Enumerate every string leaf of the exact wire messages. */
|
||||
function collectSources(
|
||||
value: DeepSeekLlmApiJson,
|
||||
messageIndex: number,
|
||||
path: readonly (string | number)[],
|
||||
output: MessageStringSource[],
|
||||
): void {
|
||||
if (typeof value === 'string') {
|
||||
if (value.length > 0) output.push({ messageIndex, path, value, bytes: Buffer.byteLength(value, 'utf8') })
|
||||
return
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((child, index) => { collectSources(child, messageIndex, [...path, index], output) })
|
||||
return
|
||||
}
|
||||
if (value === null || typeof value !== 'object') return
|
||||
for (const [key, child] of Object.entries(value)) collectSources(child, messageIndex, [...path, key], output)
|
||||
}
|
||||
|
||||
/** Build one exact slice from UTF-16 offsets, or reject a lossy UTF-8 conversion. */
|
||||
function sliceOf(source: MessageStringSource, start: number, end: number): DeepSeekMessageStringSlice | undefined {
|
||||
const candidate: DeepSeekMessageStringSlice = {
|
||||
messageIndex: source.messageIndex,
|
||||
path: source.path,
|
||||
utf8Start: Buffer.byteLength(source.value.slice(0, start), 'utf8'),
|
||||
utf8End: Buffer.byteLength(source.value.slice(0, end), 'utf8'),
|
||||
}
|
||||
const bytes = Buffer.from(source.value, 'utf8')
|
||||
const selected = bytes.subarray(candidate.utf8Start, candidate.utf8End)
|
||||
const decoded = selected.toString('utf8')
|
||||
return decoded === source.value.slice(start, end) && Buffer.from(decoded, 'utf8').equals(selected)
|
||||
? candidate
|
||||
: undefined
|
||||
}
|
||||
|
||||
/** Find a profitable exact whole-string or one-inner-string reference. */
|
||||
function packString(value: string, sources: readonly MessageStringSource[]): PackedCandidate {
|
||||
const literal: PackedJsonValue = { kind: 'literal', value }
|
||||
if (value.length === 0) return { value: literal, references: false }
|
||||
|
||||
let best: PackedJsonValue | undefined
|
||||
let bestBytes = wireBytes(literal)
|
||||
for (const source of sources) {
|
||||
const start = source.value.indexOf(value)
|
||||
if (start < 0) continue
|
||||
const slice = sliceOf(source, start, start + value.length)
|
||||
if (slice === undefined) continue
|
||||
const candidate: PackedJsonValue = {
|
||||
kind: 'string',
|
||||
parts: [{ kind: 'message-slice', value: slice }],
|
||||
}
|
||||
const bytes = wireBytes(candidate)
|
||||
if (bytes < bestBytes) {
|
||||
best = candidate
|
||||
bestBytes = bytes
|
||||
}
|
||||
}
|
||||
if (best !== undefined) return { value: best, references: true }
|
||||
|
||||
for (const source of sources) {
|
||||
const start = value.indexOf(source.value)
|
||||
if (start < 0) continue
|
||||
const slice = sliceOf(source, 0, source.value.length)
|
||||
if (slice === undefined) continue
|
||||
const parts: PackedJsonStringPart[] = []
|
||||
if (start > 0) parts.push({ kind: 'literal', value: value.slice(0, start) })
|
||||
parts.push({ kind: 'message-slice', value: slice })
|
||||
const end = start + source.value.length
|
||||
if (end < value.length) parts.push({ kind: 'literal', value: value.slice(end) })
|
||||
const candidate: PackedJsonValue = { kind: 'string', parts }
|
||||
const bytes = wireBytes(candidate)
|
||||
if (bytes < bestBytes) {
|
||||
best = candidate
|
||||
bestBytes = bytes
|
||||
}
|
||||
}
|
||||
return best === undefined
|
||||
? { value: literal, references: false }
|
||||
: { value: best, references: true }
|
||||
}
|
||||
|
||||
/** Recursively pack one JSON value, retaining references only when the complete subtree shrinks. */
|
||||
function packValue(value: JsonValue, sources: readonly MessageStringSource[]): PackedCandidate {
|
||||
const literal: PackedJsonValue = { kind: 'literal', value }
|
||||
if (typeof value === 'string') return packString(value, sources)
|
||||
if (value === null || typeof value !== 'object') return { value: literal, references: false }
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const children = value.map(child => packValue(child, sources))
|
||||
if (!children.some(child => child.references)) return { value: literal, references: false }
|
||||
const candidate: PackedJsonValue = { kind: 'array', items: children.map(child => child.value) }
|
||||
return { value: candidate, references: true }
|
||||
}
|
||||
|
||||
const children = Object.entries(value).map(([key, child]) => [key, packValue(child, sources)] as const)
|
||||
if (!children.some(([, child]) => child.references)) return { value: literal, references: false }
|
||||
const candidate: PackedJsonValue = {
|
||||
kind: 'object',
|
||||
entries: children.map(([key, child]) => [key, child.value]),
|
||||
}
|
||||
return { value: candidate, references: true }
|
||||
}
|
||||
|
||||
/** Resolve one path in a parsed wire message. */
|
||||
function valueAt(root: DeepSeekLlmApiJson, path: readonly (string | number)[]): DeepSeekLlmApiJson {
|
||||
let value = root
|
||||
for (const segment of path) {
|
||||
if (typeof segment === 'number') {
|
||||
if (!Array.isArray(value) || segment < 0 || segment >= value.length) {
|
||||
throw new Error(`session-log-deepseek: message reference has invalid array segment ${segment}`)
|
||||
}
|
||||
value = value[segment] as DeepSeekLlmApiJson
|
||||
continue
|
||||
}
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value) || !Object.hasOwn(value, segment)) {
|
||||
throw new Error(`session-log-deepseek: message reference has invalid object segment ${JSON.stringify(segment)}`)
|
||||
}
|
||||
value = value[segment] as DeepSeekLlmApiJson
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/** Decode and validate one exact UTF-8 message slice. */
|
||||
function decodeSlice(messages: readonly DeepSeekLlmApiJson[], slice: DeepSeekMessageStringSlice): string {
|
||||
const message = messages[slice.messageIndex]
|
||||
if (message === undefined) throw new Error(`session-log-deepseek: message reference index ${slice.messageIndex} is absent`)
|
||||
const source = valueAt(message, slice.path)
|
||||
if (typeof source !== 'string') throw new Error('session-log-deepseek: message reference path does not resolve to a string')
|
||||
const bytes = Buffer.from(source, 'utf8')
|
||||
if (!Number.isSafeInteger(slice.utf8Start) || !Number.isSafeInteger(slice.utf8End)
|
||||
|| slice.utf8Start < 0 || slice.utf8End <= slice.utf8Start || slice.utf8End > bytes.length) {
|
||||
throw new Error('session-log-deepseek: message reference byte range is invalid')
|
||||
}
|
||||
const selected = bytes.subarray(slice.utf8Start, slice.utf8End)
|
||||
const decoded = selected.toString('utf8')
|
||||
if (!Buffer.from(decoded, 'utf8').equals(selected)) {
|
||||
throw new Error('session-log-deepseek: message reference splits a UTF-8 code point')
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode one packed JSON value against the containing request's messages.
|
||||
* @param value - tagged literal/reference value to reconstruct.
|
||||
* @param messages - serialized messages from the same request.
|
||||
* @returns the exact reconstructed JSON value.
|
||||
*/
|
||||
export function unpackJsonValue(value: PackedJsonValue, messages: readonly DeepSeekLlmApiJson[]): JsonValue {
|
||||
switch (value.kind) {
|
||||
case 'literal':
|
||||
return structuredClone(value.value)
|
||||
case 'string':
|
||||
return value.parts.map(part => part.kind === 'literal' ? part.value : decodeSlice(messages, part.value)).join('')
|
||||
case 'array':
|
||||
return value.items.map(item => unpackJsonValue(item, messages))
|
||||
case 'object':
|
||||
return Object.fromEntries(value.entries.map(([key, child]) => [key, unpackJsonValue(child, messages)]))
|
||||
default:
|
||||
throw new Error('session-log-deepseek: unknown packed JSON value')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode canonical session events against the exact DeepSeek wire messages.
|
||||
* @param events - immutable canonical event suffix.
|
||||
* @param messages - serialized request messages that the receiver also holds.
|
||||
* @returns one raw-or-referenced representation per event.
|
||||
*/
|
||||
export function packSessionEvents(
|
||||
events: readonly SessionEvent[],
|
||||
messages: readonly DeepSeekLlmApiJson[],
|
||||
): EncodedSessionEvent[] {
|
||||
const sources: MessageStringSource[] = []
|
||||
messages.forEach((message, messageIndex) => { collectSources(message, messageIndex, [], sources) })
|
||||
sources.sort((left, right) => right.bytes - left.bytes)
|
||||
return events.map((event) => {
|
||||
const packed = packValue(event as unknown as JsonValue, sources)
|
||||
const raw: EncodedSessionEvent = { encoding: 'raw', event }
|
||||
if (!packed.references) return raw
|
||||
const referenced: EncodedSessionEvent = { encoding: 'message-references', event: packed.value }
|
||||
return wireBytes(referenced) < wireBytes(raw) ? referenced : raw
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruct canonical session events from one request-relative representation.
|
||||
* @param events - encoded event list from the extension field.
|
||||
* @param messages - serialized messages from the same request.
|
||||
* @returns reconstructed event values in order.
|
||||
*/
|
||||
export function unpackSessionEvents(
|
||||
events: readonly EncodedSessionEvent[],
|
||||
messages: readonly DeepSeekLlmApiJson[],
|
||||
): SessionEvent[] {
|
||||
return events.map(encoded => encoded.encoding === 'raw'
|
||||
? structuredClone(encoded.event)
|
||||
: unpackJsonValue(encoded.event, messages) as unknown as SessionEvent)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Incremental session-log contribution for official DeepSeek LLM API requests.
|
||||
* Accepted sequence watermarks live in the canonical log, so restart recovery
|
||||
* can conservatively resend uncertain tails without maintaining another store.
|
||||
* @module @deepseek-ai/dsh-session-log-deepseek
|
||||
*/
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import type { DeepSeekLlmApiJson } from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
import { SessionId, type Session, type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { packSessionEvents } from './codec.ts'
|
||||
import type { DeepSeekSessionLogExtension } from './types.ts'
|
||||
import type {} from './types.ts'
|
||||
|
||||
export { packSessionEvents, unpackJsonValue, unpackSessionEvents } from './codec.ts'
|
||||
export type * from './types.ts'
|
||||
|
||||
/** Cordis plugin name. */
|
||||
export const name = 'session-log-deepseek'
|
||||
/** Services required to resolve sessions and contribute the provider request field. */
|
||||
export const inject = ['deepseekLlmApiExtensions', 'sessions']
|
||||
|
||||
/** Session-log request contribution configuration. */
|
||||
export interface Config {
|
||||
/** Contribute `dsh_session_log` to official DeepSeek requests. Defaults to `false`. */
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
/** Validated Session-log request contribution configuration. */
|
||||
export const Config: z<Config> = z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
})
|
||||
|
||||
interface AcceptanceFold {
|
||||
readonly scannedEvents: number
|
||||
readonly throughSeq: number
|
||||
}
|
||||
|
||||
const acceptanceFolds = new WeakMap<Session, AcceptanceFold>()
|
||||
|
||||
/**
|
||||
* Highest confirmed sequence for this exact session identity.
|
||||
* @param session - canonical log whose matching acceptance events are folded.
|
||||
* @returns greatest accepted sequence, or `-1` before any accepted request.
|
||||
*/
|
||||
export function acceptedThrough(session: Session): number {
|
||||
const previous = acceptanceFolds.get(session)
|
||||
let throughSeq = previous?.throughSeq ?? -1
|
||||
const events = session.events
|
||||
const start = previous?.scannedEvents ?? 0
|
||||
for (let index = start; index < events.length; index++) {
|
||||
const event = events[index] as SessionEvent
|
||||
if (event.type !== 'session-log-deepseek/accepted') continue
|
||||
if (typeof event.data.sessionId !== 'string' || event.data.sessionId.length === 0
|
||||
|| !Number.isSafeInteger(event.data.throughSeq) || event.data.throughSeq < 0
|
||||
|| event.data.throughSeq >= event.seq) {
|
||||
throw new Error(`session-log-deepseek: malformed acceptance watermark at seq ${event.seq}`)
|
||||
}
|
||||
if (event.data.sessionId !== session.id) continue
|
||||
throughSeq = Math.max(throughSeq, event.data.throughSeq)
|
||||
}
|
||||
acceptanceFolds.set(session, { scannedEvents: events.length, throughSeq })
|
||||
return throughSeq
|
||||
}
|
||||
|
||||
/** Resolve the serialized message list or fail before transport. */
|
||||
function requestMessages(body: Readonly<Record<string, DeepSeekLlmApiJson>>): readonly DeepSeekLlmApiJson[] {
|
||||
const messages = body.messages
|
||||
if (!Array.isArray(messages)) throw new Error('session-log-deepseek: DeepSeek request body has no messages array')
|
||||
return messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the incremental `dsh_session_log` request contribution when enabled.
|
||||
* @param ctx - plugin context carrying Sessions and the DeepSeek request-extension registry.
|
||||
* @param config - validated opt-in configuration.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
if (config.enabled !== true) return
|
||||
ctx.deepseekLlmApiExtensions.register('dsh_session_log', {
|
||||
prepare: (request) => {
|
||||
// TODO: Define an explicit wire result for direct or stale-session calls if they become a supported product path.
|
||||
if (request.sessionId === undefined) return undefined
|
||||
const session = ctx.sessions.get(SessionId(request.sessionId))
|
||||
if (session === undefined) return undefined
|
||||
|
||||
const afterSeq = acceptedThrough(session)
|
||||
const snapshot = session.events
|
||||
const throughSeq = snapshot.length - 1
|
||||
if (throughSeq < 0) return undefined
|
||||
const suffix = snapshot.slice(afterSeq + 1)
|
||||
const value: DeepSeekSessionLogExtension = {
|
||||
version: 1,
|
||||
session: session.header,
|
||||
afterSeq,
|
||||
throughSeq,
|
||||
events: packSessionEvents(suffix, requestMessages(request.body)),
|
||||
}
|
||||
return {
|
||||
value,
|
||||
accept: () => {
|
||||
session.append('session-log-deepseek/accepted', { sessionId: session.id, throughSeq })
|
||||
// TODO: Add an immediate lightweight checkpoint if duplicate replay after a 2xx crash window becomes unacceptable.
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/** Package-owned invariants for DeepSeek session-log acceptance watermarks. */
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type {} from './types.ts'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'session-log-deepseek-invariant'
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Validate one acceptance watermark against its containing event and session. */
|
||||
function validateAccepted(session: Session, event: SessionEvent<'session-log-deepseek/accepted'>, fail: InvariantFailure): void {
|
||||
const { sessionId, throughSeq } = event.data
|
||||
const inherited = session.header.parentSession !== undefined
|
||||
&& session.header.seedLength !== undefined
|
||||
&& event.seq < session.header.seedLength
|
||||
if (sessionId !== session.id && !inherited) {
|
||||
fail('a non-inherited session-log-deepseek/accepted event must name its containing session')
|
||||
}
|
||||
if (!Number.isSafeInteger(throughSeq) || throughSeq < 0 || throughSeq >= event.seq) {
|
||||
fail(`session-log-deepseek/accepted throughSeq must identify an earlier event, got ${throughSeq} at seq ${event.seq}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Validate acceptance watermarks already present in one Session. */
|
||||
function validateSession(session: Session, fail: InvariantFailure): void {
|
||||
for (const event of session.events) {
|
||||
if (event.type === 'session-log-deepseek/accepted') validateAccepted(session, event, fail)
|
||||
}
|
||||
}
|
||||
|
||||
/** Validate one live session-event dispatch. */
|
||||
function validateDispatched(args: unknown[], fail: InvariantFailure): void {
|
||||
const [session, event] = args as [Session, SessionEvent]
|
||||
if (event.type === 'session-log-deepseek/accepted') validateAccepted(session, event, fail)
|
||||
}
|
||||
|
||||
/** Install validation for restored, newly created, and newly appended watermarks. */
|
||||
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
|
||||
const validateExisting = (session: Session): void => { validateSession(session, fail) }
|
||||
ctx.sessions.list().forEach(validateExisting)
|
||||
ctx.on('session/created', validateExisting, { global: true })
|
||||
ctx.on('internal/dispatch', (_mode, eventName, args) => {
|
||||
if (eventName === 'session/event') validateDispatched(args, fail)
|
||||
}, { global: true })
|
||||
}, { inject: ['sessions'] })
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
* @param ctx - Cordis context carrying the invariant service.
|
||||
* @returns the installed registration's disposer after setup succeeds.
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
@@ -0,0 +1,61 @@
|
||||
/** Wire types for lossless incremental DeepSeek session-log upload. */
|
||||
|
||||
import type { JsonValue, SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
|
||||
/** Path from one DeepSeek wire message root to a string value. */
|
||||
export type DeepSeekMessageStringPath = readonly (string | number)[]
|
||||
|
||||
/** Exact half-open UTF-8 slice of one string in the containing request's messages. */
|
||||
export interface DeepSeekMessageStringSlice {
|
||||
readonly messageIndex: number
|
||||
readonly path: DeepSeekMessageStringPath
|
||||
readonly utf8Start: number
|
||||
readonly utf8End: number
|
||||
}
|
||||
|
||||
/** One literal or request-relative fragment of a packed JSON string. */
|
||||
export type PackedJsonStringPart =
|
||||
| { readonly kind: 'literal'; readonly value: string }
|
||||
| { readonly kind: 'message-slice'; readonly value: DeepSeekMessageStringSlice }
|
||||
|
||||
/** Tagged JSON representation whose string leaves may cite the containing request. */
|
||||
export type PackedJsonValue =
|
||||
| { readonly kind: 'literal'; readonly value: JsonValue }
|
||||
| { readonly kind: 'string'; readonly parts: readonly PackedJsonStringPart[] }
|
||||
| { readonly kind: 'array'; readonly items: readonly PackedJsonValue[] }
|
||||
| { readonly kind: 'object'; readonly entries: readonly (readonly [string, PackedJsonValue])[] }
|
||||
|
||||
/** One canonical session event, sent raw unless request-relative references reduce its encoded bytes. */
|
||||
export type EncodedSessionEvent =
|
||||
| { readonly encoding: 'raw'; readonly event: SessionEvent }
|
||||
| { readonly encoding: 'message-references'; readonly event: PackedJsonValue }
|
||||
|
||||
/** Versioned incremental session-log field carried by an official DeepSeek request. */
|
||||
export interface DeepSeekSessionLogExtension {
|
||||
readonly version: 1
|
||||
readonly session: SessionHeader
|
||||
/** Highest sequence durably recorded as accepted before this request, or `-1`. */
|
||||
readonly afterSeq: number
|
||||
/** Highest sequence represented by {@link events}. */
|
||||
readonly throughSeq: number
|
||||
/** Contiguous canonical events from `afterSeq + 1` through `throughSeq`. */
|
||||
readonly events: readonly EncodedSessionEvent[]
|
||||
}
|
||||
|
||||
declare module '@deepseek-ai/dsh-deepseek-llm-api-extensions/types' {
|
||||
interface DeepSeekLlmApiExtensionMap {
|
||||
dsh_session_log: DeepSeekSessionLogExtension
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@deepseek-ai/dsh-session/types' {
|
||||
interface SessionEventMap {
|
||||
/** Records a confirmed HTTP acceptance watermark for restart-safe suffix selection. */
|
||||
'session-log-deepseek/accepted': {
|
||||
/** Session identity the accepted request carried; inherited fork markers retain the parent's id. */
|
||||
sessionId: import('@deepseek-ai/dsh-session/types').SessionId
|
||||
/** Last canonical event included in the accepted request. */
|
||||
throughSeq: number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { DeepSeekLlmApiJson } from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import {
|
||||
packSessionEvents,
|
||||
unpackJsonValue,
|
||||
unpackSessionEvents,
|
||||
} from '../src/codec.ts'
|
||||
import type { PackedJsonValue } from '../src/types.ts'
|
||||
|
||||
function event(data: unknown, seq = 0): SessionEvent {
|
||||
return {
|
||||
type: 'plugin/test',
|
||||
seq,
|
||||
time: 1_700_000_000_000 + seq,
|
||||
data,
|
||||
} as unknown as SessionEvent
|
||||
}
|
||||
|
||||
describe('DeepSeek session-log codec', () => {
|
||||
it('replaces a raw assistant chunk with an exact slice of its assembled wire message', () => {
|
||||
const delta = 'streamed assistant content '.repeat(30)
|
||||
const source = {
|
||||
type: 'assistant/chunk',
|
||||
seq: 0,
|
||||
time: 1,
|
||||
data: { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: delta } },
|
||||
} as SessionEvent<'assistant/chunk'>
|
||||
const messages: DeepSeekLlmApiJson[] = [{ role: 'assistant', content: `prefix:${delta}:suffix` }]
|
||||
const packed = packSessionEvents([source], messages)
|
||||
|
||||
expect(packed[0]?.encoding).toBe('message-references')
|
||||
expect(unpackSessionEvents(packed, messages)).toEqual([source])
|
||||
})
|
||||
|
||||
it('references exact whole strings and reconstructs Unicode event values', () => {
|
||||
const text = `前缀-${'shared text '.repeat(40)}-结尾`
|
||||
const messages: DeepSeekLlmApiJson[] = [{ role: 'assistant', content: text }]
|
||||
const source = event({ nested: [{ text }], untouched: 3 })
|
||||
const packed = packSessionEvents([source], messages)
|
||||
|
||||
expect(packed[0]?.encoding).toBe('message-references')
|
||||
expect(JSON.stringify(packed)).toContain('message-slice')
|
||||
expect(unpackSessionEvents(packed, messages)).toEqual([source])
|
||||
})
|
||||
|
||||
it('keeps surrogate-splitting and ill-formed UTF-16 strings raw', () => {
|
||||
const high = String.fromCharCode(0xD83D)
|
||||
const low = String.fromCharCode(0xDE00)
|
||||
const tail = 'shared-tail-'.repeat(80)
|
||||
const cases = [
|
||||
{ message: `😀${tail}`, logged: `${low}${tail}` },
|
||||
{ message: `${tail}😀`, logged: `${tail}${high}` },
|
||||
{ message: `${high}${tail}`, logged: `${high}${tail}` },
|
||||
]
|
||||
|
||||
for (const item of cases) {
|
||||
const source = event({ text: item.logged })
|
||||
const messages: DeepSeekLlmApiJson[] = [{ role: 'assistant', content: item.message }]
|
||||
const packed = packSessionEvents([source], messages)
|
||||
expect(packed).toEqual([{ encoding: 'raw', event: source }])
|
||||
expect(unpackSessionEvents(packed, messages)).toEqual([source])
|
||||
}
|
||||
})
|
||||
|
||||
it('references one large inner message string and preserves literal prefix and suffix', () => {
|
||||
const shared = 'payload '.repeat(80)
|
||||
const messages: DeepSeekLlmApiJson[] = [{ role: 'tool', content: shared }]
|
||||
const source = event({ output: `before:${shared}:after` })
|
||||
const packed = packSessionEvents([source], messages)
|
||||
|
||||
expect(packed[0]?.encoding).toBe('message-references')
|
||||
expect(unpackSessionEvents(packed, messages)).toEqual([source])
|
||||
})
|
||||
|
||||
it('keeps short or unrelated events raw when references would expand them', () => {
|
||||
const messages: DeepSeekLlmApiJson[] = [{ role: 'user', content: 'tiny', empty: '', parts: [null, true, 5, 'nested'] }]
|
||||
const source = event({ text: 'tiny', empty: '', other: ['unrelated', true, null] })
|
||||
const packed = packSessionEvents([source], messages)
|
||||
expect(packed).toEqual([{ encoding: 'raw', event: source }])
|
||||
expect(unpackSessionEvents(packed, messages)).toEqual([source])
|
||||
})
|
||||
|
||||
it('falls back to a raw event when referenced children do not reduce the complete envelope', () => {
|
||||
let found = false
|
||||
for (let length = 40; length <= 240; length += 1) {
|
||||
const text = 'x'.repeat(length)
|
||||
const source = event({ text })
|
||||
const packed = packSessionEvents([source], [{ content: text }])
|
||||
if (packed[0]?.encoding === 'raw' && length > 80) found = true
|
||||
}
|
||||
expect(found).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects missing paths, non-string targets, invalid ranges, and split UTF-8 code points', () => {
|
||||
const messages: DeepSeekLlmApiJson[] = [{ content: '😀abc', nested: [5] }]
|
||||
const packed = (value: object): PackedJsonValue => ({
|
||||
kind: 'string',
|
||||
parts: [{ kind: 'message-slice', value: value as never }],
|
||||
})
|
||||
expect(() => unpackJsonValue(packed({ messageIndex: 2, path: ['content'], utf8Start: 0, utf8End: 1 }), messages))
|
||||
.toThrow(/index 2 is absent/)
|
||||
expect(() => unpackJsonValue(packed({ messageIndex: 0, path: ['missing'], utf8Start: 0, utf8End: 1 }), messages))
|
||||
.toThrow(/invalid object segment/)
|
||||
expect(() => unpackJsonValue(packed({ messageIndex: 0, path: ['nested', 0], utf8Start: 0, utf8End: 1 }), messages))
|
||||
.toThrow(/does not resolve to a string/)
|
||||
expect(() => unpackJsonValue(packed({ messageIndex: 0, path: ['nested', 2], utf8Start: 0, utf8End: 1 }), messages))
|
||||
.toThrow(/invalid array segment/)
|
||||
expect(() => unpackJsonValue(packed({ messageIndex: 0, path: ['content'], utf8Start: -1, utf8End: 1 }), messages))
|
||||
.toThrow(/byte range is invalid/)
|
||||
expect(() => unpackJsonValue(packed({ messageIndex: 0, path: ['content'], utf8Start: 0, utf8End: 1 }), messages))
|
||||
.toThrow(/splits a UTF-8 code point/)
|
||||
})
|
||||
|
||||
it('decodes every packed JSON variant and rejects unknown tags', () => {
|
||||
const messages: DeepSeekLlmApiJson[] = [{ content: 'abcdef' }]
|
||||
const value: PackedJsonValue = {
|
||||
kind: 'object',
|
||||
entries: [[
|
||||
'items',
|
||||
{
|
||||
kind: 'array',
|
||||
items: [
|
||||
{ kind: 'literal', value: 1 },
|
||||
{
|
||||
kind: 'string',
|
||||
parts: [
|
||||
{ kind: 'literal', value: 'x' },
|
||||
{ kind: 'message-slice', value: { messageIndex: 0, path: ['content'], utf8Start: 1, utf8End: 4 } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]],
|
||||
}
|
||||
expect(unpackJsonValue(value, messages)).toEqual({ items: [1, 'xbcd'] })
|
||||
expect(() => unpackJsonValue({ kind: 'future' } as never, messages)).toThrow(/unknown packed JSON value/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import * as SessionLogInvariant from '../src/invariant.ts'
|
||||
import type {} from '../src/types.ts'
|
||||
|
||||
const contexts: Context[] = []
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose()))
|
||||
})
|
||||
|
||||
async function setup(): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
contexts.push(ctx)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(InvariantRegistry, { enabled: true })
|
||||
await ctx.plugin(SessionLogInvariant)
|
||||
return ctx
|
||||
}
|
||||
|
||||
describe('DeepSeek session-log acceptance invariant', () => {
|
||||
it('accepts a watermark naming an earlier event in its containing Session', async () => {
|
||||
const ctx = await setup()
|
||||
const session = ctx.sessions.create(SessionId('valid'))
|
||||
session.append('turn/start', { turn: 1 })
|
||||
expect(() => session.append('session-log-deepseek/accepted', { sessionId: session.id, throughSeq: 0 }))
|
||||
.not.toThrow()
|
||||
})
|
||||
|
||||
it('rejects a live watermark for another Session or a non-earlier sequence', async () => {
|
||||
const ctx = await setup()
|
||||
const wrongId = ctx.sessions.create(SessionId('wrong-id'))
|
||||
wrongId.append('turn/start', { turn: 1 })
|
||||
expect(() => wrongId.append('session-log-deepseek/accepted', {
|
||||
sessionId: SessionId('other'),
|
||||
throughSeq: 0,
|
||||
})).toThrow(expect.objectContaining<Partial<InvariantError>>({
|
||||
code: 'INVARIANT',
|
||||
packageName: '@deepseek-ai/dsh-session-log-deepseek',
|
||||
}))
|
||||
|
||||
const wrongSeq = ctx.sessions.create(SessionId('wrong-seq'))
|
||||
wrongSeq.append('turn/start', { turn: 1 })
|
||||
expect(() => wrongSeq.append('session-log-deepseek/accepted', {
|
||||
sessionId: wrongSeq.id,
|
||||
throughSeq: 1,
|
||||
})).toThrow(expect.objectContaining<Partial<InvariantError>>({
|
||||
code: 'INVARIANT',
|
||||
packageName: '@deepseek-ai/dsh-session-log-deepseek',
|
||||
}))
|
||||
})
|
||||
|
||||
it('validates existing history when the invariant loads after the Session', async () => {
|
||||
const ctx = new Context()
|
||||
contexts.push(ctx)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(InvariantRegistry, { enabled: true })
|
||||
const id = SessionId('late-invalid')
|
||||
ctx.sessions.create(id, { seed: [
|
||||
{ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } },
|
||||
{ type: 'session-log-deepseek/accepted', seq: 1, time: 2, data: { sessionId: id, throughSeq: 1 } },
|
||||
] })
|
||||
|
||||
let failure: unknown
|
||||
try {
|
||||
await ctx.plugin(SessionLogInvariant)
|
||||
} catch (error) {
|
||||
failure = error
|
||||
}
|
||||
expect(failure).toMatchObject<Partial<InvariantError>>({
|
||||
code: 'INVARIANT',
|
||||
packageName: '@deepseek-ai/dsh-session-log-deepseek',
|
||||
})
|
||||
})
|
||||
|
||||
it('allows an inherited parent watermark inside a fork seed', async () => {
|
||||
const ctx = new Context()
|
||||
contexts.push(ctx)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(InvariantRegistry, { enabled: true })
|
||||
const parentId = SessionId('fork-parent')
|
||||
const childId = SessionId('fork-child')
|
||||
ctx.sessions.create(childId, {
|
||||
seed: [
|
||||
{ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } },
|
||||
{ type: 'session-log-deepseek/accepted', seq: 1, time: 2, data: { sessionId: parentId, throughSeq: 0 } },
|
||||
],
|
||||
meta: { parentSession: parentId, seedLength: 2 },
|
||||
})
|
||||
|
||||
await expect(ctx.plugin(SessionLogInvariant)).resolves.toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,181 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import SessionStore, { Session, SessionId, type CreateSessionOptions, type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import DeepSeekLlmApiExtensionRegistry from '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
import * as SessionLogDeepSeek from '../src/index.ts'
|
||||
|
||||
const contexts: Context[] = []
|
||||
const SIGNAL = new AbortController().signal
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose()))
|
||||
})
|
||||
|
||||
async function harness(id: string, seed?: readonly SessionEvent[], meta?: CreateSessionOptions['meta']): Promise<{
|
||||
ctx: Context
|
||||
session: Session
|
||||
disposeUpload: () => Promise<void>
|
||||
}> {
|
||||
const ctx = new Context()
|
||||
contexts.push(ctx)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(DeepSeekLlmApiExtensionRegistry)
|
||||
const upload = ctx.plugin(SessionLogDeepSeek, { enabled: true })
|
||||
await upload
|
||||
const options = seed === undefined
|
||||
? undefined
|
||||
: { seed, ...meta === undefined ? {} : { meta } }
|
||||
const session = ctx.sessions.create(SessionId(id), options)
|
||||
return { ctx, session, disposeUpload: () => upload.dispose() }
|
||||
}
|
||||
|
||||
function body(text = 'x'.repeat(300)) {
|
||||
return { messages: [{ role: 'user', content: text }] }
|
||||
}
|
||||
|
||||
describe('incremental DeepSeek session-log upload', () => {
|
||||
it('does not contribute the session log under its default configuration', async () => {
|
||||
const ctx = new Context()
|
||||
contexts.push(ctx)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(DeepSeekLlmApiExtensionRegistry)
|
||||
await ctx.plugin(SessionLogDeepSeek)
|
||||
const session = ctx.sessions.create(SessionId('default-off'))
|
||||
session.append('turn/start', { turn: 1 })
|
||||
|
||||
const prepared = await ctx.deepseekLlmApiExtensions.prepare({
|
||||
body: body(), signal: SIGNAL, sessionId: session.id,
|
||||
})
|
||||
expect(prepared.fields).not.toHaveProperty('dsh_session_log')
|
||||
})
|
||||
|
||||
it('uploads the full first prefix, records acceptance, then sends only the appended suffix', async () => {
|
||||
const { ctx, session } = await harness('incremental')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
|
||||
const first = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
|
||||
const firstPayload = first.fields.dsh_session_log
|
||||
expect(firstPayload).toMatchObject({ afterSeq: -1, throughSeq: 1 })
|
||||
expect(firstPayload?.events).toHaveLength(2)
|
||||
await first.accept()
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(1)
|
||||
|
||||
session.append('step/end', { turn: 1, step: 1 })
|
||||
const second = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
|
||||
expect(second.fields.dsh_session_log).toMatchObject({ afterSeq: 1, throughSeq: 3 })
|
||||
expect(second.fields.dsh_session_log?.events).toHaveLength(2)
|
||||
expect(second.fields.dsh_session_log?.events[0]).toMatchObject({
|
||||
encoding: 'raw',
|
||||
event: { type: 'session-log-deepseek/accepted', seq: 2 },
|
||||
})
|
||||
})
|
||||
|
||||
it('reconstructs a persisted cursor and ignores an inherited parent watermark in a fork', async () => {
|
||||
const first = await harness('parent')
|
||||
first.session.append('turn/start', { turn: 1 })
|
||||
const prepared = await first.ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: first.session.id })
|
||||
await prepared.accept()
|
||||
const seed = first.session.events
|
||||
|
||||
const resumed = await harness('parent', seed)
|
||||
expect(SessionLogDeepSeek.acceptedThrough(resumed.session)).toBe(0)
|
||||
const resumedPayload = await resumed.ctx.deepseekLlmApiExtensions.prepare({
|
||||
body: body(), signal: SIGNAL, sessionId: resumed.session.id,
|
||||
})
|
||||
expect(resumedPayload.fields.dsh_session_log?.afterSeq).toBe(0)
|
||||
|
||||
const fork = await harness('child', seed, { parentSession: first.session.id, seedLength: seed.length })
|
||||
expect(SessionLogDeepSeek.acceptedThrough(fork.session)).toBe(-1)
|
||||
const forkPayload = await fork.ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: fork.session.id })
|
||||
expect(forkPayload.fields.dsh_session_log).toMatchObject({ afterSeq: -1, throughSeq: fork.session.seq - 1 })
|
||||
})
|
||||
|
||||
it('takes the maximum watermark when concurrent acceptances settle out of order', async () => {
|
||||
const { ctx, session } = await harness('concurrent')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
const earlier = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
const later = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
|
||||
|
||||
await later.accept()
|
||||
await earlier.accept()
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(1)
|
||||
})
|
||||
|
||||
it('folds only events appended after the cached acceptance scan', () => {
|
||||
const id = SessionId('incremental-fold')
|
||||
const events: SessionEvent[] = [
|
||||
{ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } },
|
||||
{ type: 'session-log-deepseek/accepted', seq: 1, time: 2, data: { sessionId: id, throughSeq: 0 } },
|
||||
]
|
||||
let reads = 0
|
||||
const observed = new Proxy(events, {
|
||||
get(target, property, receiver) {
|
||||
if (typeof property === 'string' && /^\d+$/.test(property)) reads++
|
||||
return Reflect.get(target, property, receiver) as unknown
|
||||
},
|
||||
})
|
||||
const session = { id, get events() { return observed } } as unknown as Session
|
||||
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(0)
|
||||
expect(reads).toBe(2)
|
||||
reads = 0
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(0)
|
||||
expect(reads).toBe(0)
|
||||
|
||||
events.push(
|
||||
{ type: 'step/start', seq: 2, time: 3, data: { turn: 1, step: 1 } },
|
||||
{ type: 'session-log-deepseek/accepted', seq: 3, time: 4, data: { sessionId: id, throughSeq: 2 } },
|
||||
)
|
||||
expect(SessionLogDeepSeek.acceptedThrough(session)).toBe(2)
|
||||
expect(reads).toBe(2)
|
||||
})
|
||||
|
||||
it('omits the field for direct or stale requests and uploads the prior acceptance marker next', async () => {
|
||||
const { ctx, session } = await harness('edges')
|
||||
await expect(ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL }))
|
||||
.resolves.toMatchObject({ fields: {} })
|
||||
await expect(ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: 'missing' }))
|
||||
.resolves.toMatchObject({ fields: {} })
|
||||
await expect(ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id }))
|
||||
.resolves.toMatchObject({ fields: {} })
|
||||
session.append('turn/start', { turn: 1 })
|
||||
const first = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
|
||||
await first.accept()
|
||||
const current = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
|
||||
expect(current.fields.dsh_session_log).toMatchObject({
|
||||
afterSeq: 0,
|
||||
throughSeq: 1,
|
||||
events: [{ event: { type: 'session-log-deepseek/accepted' } }],
|
||||
})
|
||||
})
|
||||
|
||||
it('fails preparation when the DeepSeek body has no messages array', async () => {
|
||||
const { ctx, session } = await harness('bad-body')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
await expect(ctx.deepseekLlmApiExtensions.prepare({ body: {}, signal: SIGNAL, sessionId: session.id }))
|
||||
.rejects.toThrow(/no messages array/)
|
||||
})
|
||||
|
||||
it('fails closed on a malformed persisted acceptance watermark', async () => {
|
||||
const malformed = [{
|
||||
type: 'session-log-deepseek/accepted',
|
||||
seq: 0,
|
||||
time: 1,
|
||||
data: { sessionId: 'malformed', throughSeq: 0 },
|
||||
}] as unknown as SessionEvent[]
|
||||
const session = Session.create(SessionId('malformed'), malformed)
|
||||
expect(() => SessionLogDeepSeek.acceptedThrough(session)).toThrow(/malformed acceptance watermark/)
|
||||
})
|
||||
|
||||
it('withdraws its request field when the contributing plugin reloads', async () => {
|
||||
const { ctx, session, disposeUpload } = await harness('hmr')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
expect((await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })).fields)
|
||||
.toHaveProperty('dsh_session_log')
|
||||
await disposeUpload()
|
||||
expect((await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })).fields)
|
||||
.not.toHaveProperty('dsh_session_log')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../vendor/cosmokit"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/schemastery"
|
||||
},
|
||||
{
|
||||
"path": "../../core/session"
|
||||
},
|
||||
{
|
||||
"path": "../../llm/deepseek-llm-api-extensions"
|
||||
},
|
||||
{
|
||||
"path": "../../runtime-diagnostics/invariants"
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+31
@@ -589,6 +589,9 @@ importers:
|
||||
'@deepseek-ai/dsh-session-checkpoint-policy':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/session/session-checkpoint-policy
|
||||
'@deepseek-ai/dsh-session-log-deepseek':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/session/session-log-deepseek
|
||||
'@deepseek-ai/dsh-session-persistence-jsonl':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/session/session-persistence-jsonl
|
||||
@@ -1132,6 +1135,9 @@ importers:
|
||||
'@deepseek-ai/dsh-session-checkpoint-policy':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-checkpoint-policy
|
||||
'@deepseek-ai/dsh-session-log-deepseek':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-log-deepseek
|
||||
'@deepseek-ai/dsh-session-persistence-jsonl':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-persistence-jsonl
|
||||
@@ -5686,6 +5692,9 @@ importers:
|
||||
'@deepseek-ai/dsh-session':
|
||||
specifier: workspace:^
|
||||
version: link:../../core/session
|
||||
'@deepseek-ai/dsh-session-log-deepseek':
|
||||
specifier: workspace:^
|
||||
version: link:../../session/session-log-deepseek
|
||||
'@deepseek-ai/dsh-settings':
|
||||
specifier: workspace:^
|
||||
version: link:../../settings/settings
|
||||
@@ -6529,6 +6538,25 @@ importers:
|
||||
specifier: workspace:^
|
||||
version: link:../../core/tools
|
||||
|
||||
packages/session/session-log-deepseek:
|
||||
dependencies:
|
||||
'@deepseek-ai/schemastery':
|
||||
specifier: link:../../../vendor/schemastery
|
||||
version: link:../../../vendor/schemastery
|
||||
devDependencies:
|
||||
'@deepseek-ai/cordis':
|
||||
specifier: workspace:^
|
||||
version: link:../../../vendor/cordis
|
||||
'@deepseek-ai/dsh-deepseek-llm-api-extensions':
|
||||
specifier: workspace:^
|
||||
version: link:../../llm/deepseek-llm-api-extensions
|
||||
'@deepseek-ai/dsh-invariants':
|
||||
specifier: workspace:^
|
||||
version: link:../../runtime-diagnostics/invariants
|
||||
'@deepseek-ai/dsh-session':
|
||||
specifier: workspace:^
|
||||
version: link:../../core/session
|
||||
|
||||
packages/session/session-persistence:
|
||||
devDependencies:
|
||||
'@deepseek-ai/cordis':
|
||||
@@ -9108,6 +9136,9 @@ importers:
|
||||
'@deepseek-ai/dsh-session-checkpoint-policy':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/session/session-checkpoint-policy
|
||||
'@deepseek-ai/dsh-session-log-deepseek':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/session/session-log-deepseek
|
||||
'@deepseek-ai/dsh-session-persistence':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/session/session-persistence
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"@deepseek-ai/dsh-llm-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:^",
|
||||
"@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-log-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-pi-ai": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-retry": "workspace:^",
|
||||
"@deepseek-ai/dsh-mcp-client": "workspace:^",
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
- id: deepseek-llm-api-extensions
|
||||
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
|
||||
- id: session-log-deepseek
|
||||
name: '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
- id: plugin-package-inventory-deepseek
|
||||
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'deepseek-llm-api-extensions',
|
||||
title: 'Official DeepSeek request extensions',
|
||||
mode: 'seam',
|
||||
implementations: ['plugin-package-inventory-deepseek'],
|
||||
implementations: ['session-log-deepseek', 'plugin-package-inventory-deepseek'],
|
||||
consumers: ['llm-deepseek'],
|
||||
note: 'Plugins prepare independent top-level fields; the official adapter merges them and commits their delivery state after HTTP acceptance.',
|
||||
},
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
{ "path": "./packages/typert/loader" },
|
||||
{ "path": "./packages/session/session-persistence" },
|
||||
{ "path": "./packages/session/session-checkpoint-policy" },
|
||||
{ "path": "./packages/session/session-log-deepseek" },
|
||||
{ "path": "./packages/session/session-persistence-jsonl" },
|
||||
{ "path": "./packages/session/session-persistence-sqlite" },
|
||||
{ "path": "./packages/session/session-projection" },
|
||||
|
||||
Reference in new issue
Block a user