mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
Merge remote-tracking branch 'origin/master' into worktree/3116-docs-mpa-idempotence
This commit is contained in:
+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-06-11-content-block-vocabulary.md
|
||||
2026-06-11-content-block-vocabulary.md: a31df6a7d16ea7cba649702fdb474dab34533c1b
|
||||
2026-06-11-content-block-vocabulary.zh.md: da387b179816cda64791e71ca7affa1fbdfd195b
|
||||
2026-06-11-content-block-vocabulary.md: d7d3f6b43a3f65d1421f026e5b6c2cc1ba1eadd2
|
||||
2026-06-11-content-block-vocabulary.zh.md: ed4f915dff6dcb6dbc91400f9bfa5384253aea7b
|
||||
|
||||
@@ -25,4 +25,4 @@ In-session context injection (`context/message`) and mid-turn steering originall
|
||||
- Multimodal blocks return only with coordinated adapter, UI, and compaction support; see [the drop-image Agent Note](../../archived/simplification/2026-07-04-drop-image-content-block.md).
|
||||
- Cache hints and assistant prefill remain absent until a shipping adapter can honor them; see the [producer-less variants](../../archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md) and [inert request knobs](../../archived/simplification/2026-07-04-drop-inert-request-knobs.md) Agent Notes.
|
||||
- Every adapter pays a translation cost; the first real adapters have since validated the streaming protocol, and new adapters should continue proving their provider-specific mapping in adapter-local tests.
|
||||
- IDs that cross package boundaries are branded (`CallId`, the shared agent/session `SessionId`) — nominal typing at zero runtime cost.
|
||||
- IDs that cross package boundaries are branded (`ToolCallId`, the shared agent/session `SessionId`) — nominal typing at zero runtime cost.
|
||||
|
||||
@@ -25,4 +25,4 @@ harness 需要一套统一的内部消息语言,供 agent loop(智能体循
|
||||
- 多模态块只有在适配器、UI 和上下文压缩(context compaction)三方协同支持后才会回归;见 [drop-image Agent Note](../../archived/simplification/2026-07-04-drop-image-content-block.md)。
|
||||
- 缓存提示与 assistant prefill 在有实际适配器能兑现之前保持缺席;见[无生产者的词汇变体](../../archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md)与[无端到端可用路径的请求旋钮](../../archived/simplification/2026-07-04-drop-inert-request-knobs.md) Agent Note。
|
||||
- 每个适配器都需承担翻译成本;首批真实适配器已验证了流式输出协议,新适配器应继续在适配器本地测试中验证其提供方特有的映射。
|
||||
- 跨包边界的 ID 使用品牌类型(`CallId`、agent 与会话共享的 `SessionId`)——零运行时开销的名义类型。
|
||||
- 跨包边界的 ID 使用品牌类型(`ToolCallId`、agent 与会话共享的 `SessionId`)——零运行时开销的名义类型。
|
||||
|
||||
@@ -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-06-20-branded-ids.md
|
||||
2026-06-20-branded-ids.md: dda97bbf546ef99083cbe3bd2c7da39070407e04
|
||||
2026-06-20-branded-ids.zh.md: 82b79dff9d5018e2ea9f9969148eb25225f6d727
|
||||
2026-06-20-branded-ids.md: 6443608c76fe42be74a2b8fe8a27669b09951a49
|
||||
2026-06-20-branded-ids.zh.md: f13d999aadf4dba7f2c7d31bb2739deae4a0991f
|
||||
|
||||
@@ -6,13 +6,13 @@ English | [中文](2026-06-20-branded-ids.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The harness brands `CallId` (`packages/llm/llm/src/brand.ts`) and the shared agent/session `SessionId` (`packages/core/session/src/types.ts`) using the `Branded<B> = string & { readonly [BRAND]: B }` machinery (owned by the type-only `@deepseek-ai/dsh-brand` package at `packages/util/brand/` — see its [README](../../../../packages/util/brand/README.md)) and a zero-cost cast factory per type. `dsh-brand` also states the governing policy: *"Branding is for ids that cross package boundaries and could plausibly be confused; not every string needs a brand."* That policy is right; the problem is that it is only half-applied. Two gaps let a structurally-identical-but-semantically-wrong string slip through the type checker.
|
||||
The harness brands `ToolCallId` (`packages/llm/llm/src/brand.ts`) and the shared agent/session `SessionId` (`packages/core/session/src/types.ts`) using the `Branded<B> = string & { readonly [BRAND]: B }` machinery (owned by the type-only `@deepseek-ai/dsh-brand` package at `packages/util/brand/` — see its [README](../../../../packages/util/brand/README.md)) and a zero-cost cast factory per type. `dsh-brand` also states the governing policy: *"Branding is for ids that cross package boundaries and could plausibly be confused; not every string needs a brand."* That policy is right; the problem is that it is only half-applied. Two gaps let a structurally-identical-but-semantically-wrong string slip through the type checker.
|
||||
|
||||
**Gap 1 — unbranded cross-boundary IDs in the bash seam.** The background-job id is a plain `string`: `BashTask.id: string` (`packages/shell/shell/src/types.ts`), carried as `string` through the whole executor seam (`ShellExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)` in `packages/shell/shell/src/index.ts`) and validated/passed as `string` by the model-facing tools (`validateJobId`, `assertTaskAccess`, the `job_id` schema arg in `packages/shell/tool-bash/src/index.ts`). It is generated by a per-executor counter — `` `bash-${this.nextTaskId++}` `` in `packages/shell/bash-local/src/index.ts` — which gives it **exactly the same `name-N` shape as `SessionId`'s default** (`` `session-${++counter}` `` in `packages/core/session/src/index.ts`). A bash job id and a session id are trivially swappable at a call site and the compiler says nothing. It is a model-facing id (the model passes `job_id` back to `bash_output`/`bash_kill`), so a confusion here is reachable from untrusted input.
|
||||
|
||||
The bash **owner token** is the related sub-case: `ShellExecRequest.owner?: string` and `ShellExecSpec.owner: string | undefined` (`packages/shell/shell/src/types.ts`) are documented as a deliberately *opaque* isolation key, but in every live caller the value IS the owning agent's shared `Agent.id`/`SessionId` (`callerToken = (exec) => exec.agent?.id` in `packages/shell/tool-bash/src/index.ts`) wearing a different seam-local name. It is compared for access control (`owner !== callerToken(exec)`), so a mismatched-but-well-typed string here is a cross-session isolation bug the type system currently cannot catch. This is the shared id alias covered by the [unified agent/session identity decision](../simplification/2026-06-20-unify-agent-and-session-id.md).
|
||||
|
||||
**Gap 2 — brand erosion at the boundaries of the *already-branded* IDs.** Even `CallId` and `SessionId` decay back to bare `string` at exactly the places confusion is most likely: registry/store key types and public method params. Representative sites include the session store, the agent registry (both keyed by the shared `SessionId`), tool-presentation call-id maps, ACP's session records, and the persistence coordinator. A brand that is dropped at a collection key buys nothing on lookups — the value of the existing brands is partly unrealized.
|
||||
**Gap 2 — brand erosion at the boundaries of the *already-branded* IDs.** Even `ToolCallId` and `SessionId` decay back to bare `string` at exactly the places confusion is most likely: registry/store key types and public method params. Representative sites include the session store, the agent registry (both keyed by the shared `SessionId`), tool-presentation call-id maps, ACP's session records, and the persistence coordinator. A brand that is dropped at a collection key buys nothing on lookups — the value of the existing brands is partly unrealized.
|
||||
|
||||
## Decision
|
||||
|
||||
@@ -22,7 +22,7 @@ A type-only change. Brands are zero-cost casts; nothing about runtime behavior,
|
||||
|
||||
- **Mint a distinct `OwnerToken` brand.** Add `OwnerToken = Branded<'OwnerToken'>` in `packages/shell/shell/src/types.ts`; type `ShellExecRequest.owner` / `ShellExecSpec.owner` / `ShellExecutor.ownerOf` as `OwnerToken | undefined`. The `dsh-tool-bash` consumer casts the agent's shared `id` (`SessionId`) into an `OwnerToken` at the boundary — the one place the two vocabularies meet. The bash Service Definition never imports `dsh-session`. (Rationale in the next section.)
|
||||
|
||||
- **Stop the brand erosion.** Propagate the existing brands to the `Map` key types and public method params listed under Gap 2 — `Map<SessionId, Session>`, `Map<SessionId, Agent>`, `get(id: SessionId)`, `Map<CallId, …>`, ACP's `SessionId` surface, and the coordinator's `Map<SessionId, …>`. This is the larger mechanical share of the change and the part that makes the *existing* brands actually load-bearing on lookups, not just on struct fields.
|
||||
- **Stop the brand erosion.** Propagate the existing brands to the `Map` key types and public method params listed under Gap 2 — `Map<SessionId, Session>`, `Map<SessionId, Agent>`, `get(id: SessionId)`, `Map<ToolCallId, …>`, ACP's `SessionId` surface, and the coordinator's `Map<SessionId, …>`. This is the larger mechanical share of the change and the part that makes the *existing* brands actually load-bearing on lookups, not just on struct fields.
|
||||
|
||||
Illustrative shape (the factory pattern is identical to the three existing brands):
|
||||
|
||||
@@ -60,7 +60,7 @@ Kept deliberately narrow per the "not every string needs a brand" policy. Each o
|
||||
|
||||
## Verification
|
||||
|
||||
The landed invariants: `BashTaskId` and `OwnerToken` are defined in `dsh-shell` and threaded end-to-end (Service Definition, the `dsh-bash-local` generation site, the `dsh-tool-bash` model-facing tool) with no `dsh-shell` dependency on `dsh-session`; no collection keyed by an in-scope branded id (`CallId`/`SessionId`/`BashTaskId`) is keyed by bare `string`; public method params and exported signatures keep the brand; and brands are constructed via the cast factory at each boundary where a raw string enters (provider call id, ACP session id, model-supplied `job_id`), never as scattered `as` casts.
|
||||
The landed invariants: `BashTaskId` and `OwnerToken` are defined in `dsh-shell` and threaded end-to-end (Service Definition, the `dsh-bash-local` generation site, the `dsh-tool-bash` model-facing tool) with no `dsh-shell` dependency on `dsh-session`; no collection keyed by an in-scope branded id (`ToolCallId`/`SessionId`/`BashTaskId`) is keyed by bare `string`; public method params and exported signatures keep the brand; and brands are constructed via the cast factory at each boundary where a raw string enters (provider call id, ACP session id, model-supplied `job_id`), never as scattered `as` casts.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
harness 使用 `Branded<B> = string & { readonly [BRAND]: B }` 机制,为 `CallId`(`packages/llm/llm/src/brand.ts`)和 agent(智能体)/会话共享的 `SessionId`(`packages/core/session/src/types.ts`)做 brand 处理;该机制由纯类型包 `@deepseek-ai/dsh-brand` 拥有,位于 `packages/util/brand/`,见其 [README](../../../../packages/util/brand/README.zh.md),并为每个类型提供零开销的 cast 工厂。`dsh-brand` 还声明了治理策略:*「Branding 用于跨包边界且可能被混淆的 id;不是每个 string 都需要 brand。」* 这条策略是正确的;问题在于它只落实了一半。两处缺口使得结构相同但语义错误的 string 仍能通过类型检查器。
|
||||
harness 使用 `Branded<B> = string & { readonly [BRAND]: B }` 机制,为 `ToolCallId`(`packages/llm/llm/src/brand.ts`)和 agent(智能体)/会话共享的 `SessionId`(`packages/core/session/src/types.ts`)做 brand 处理;该机制由纯类型包 `@deepseek-ai/dsh-brand` 拥有,位于 `packages/util/brand/`,见其 [README](../../../../packages/util/brand/README.zh.md),并为每个类型提供零开销的 cast 工厂。`dsh-brand` 还声明了治理策略:*「Branding 用于跨包边界且可能被混淆的 id;不是每个 string 都需要 brand。」* 这条策略是正确的;问题在于它只落实了一半。两处缺口使得结构相同但语义错误的 string 仍能通过类型检查器。
|
||||
|
||||
**缺口 1:bash seam 中未 brand 的跨边界 ID。** 后台 job id 是普通 `string`:`BashTask.id: string`(`packages/shell/shell/src/types.ts`),作为 `string` 贯穿整个执行器 seam(`packages/shell/shell/src/index.ts` 中的 `ShellExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)`),再由面向模型的工具以 `string` 校验并传递(`validateJobId`、`assertTaskAccess`、`packages/shell/tool-bash/src/index.ts` 中 `job_id` 的 schema 参数)。它由每执行器计数器生成——`packages/shell/bash-local/src/index.ts` 中的 `` `bash-${this.nextTaskId++}` ``——其形状与 `SessionId` 的默认值**完全相同,都是 `name-N`**(`packages/core/session/src/index.ts` 中的 `` `session-${++counter}` ``)。bash job id 和会话 id 在调用点轻易就能互换,而编译器毫无反应。它是面向模型的 id(模型会把 `job_id` 传回 `bash_output`/`bash_kill`),所以该混淆可由不受信任的输入触达。
|
||||
|
||||
bash **owner token** 是相关的子情形:`ShellExecRequest.owner?: string` 和 `ShellExecSpec.owner: string | undefined`(`packages/shell/shell/src/types.ts`)被文档描述为刻意*不透明*的隔离键,但在所有实际调用方中,该值就是所属 agent 共享的 `Agent.id`/`SessionId`(`callerToken = (exec) => exec.agent?.id`,位于 `packages/shell/tool-bash/src/index.ts`),只是披着另一个 seam 本地名称。它被用于访问控制比较(`owner !== callerToken(exec)`),因此一个不匹配但类型正确的 string 在此处就是跨会话隔离 bug,而当前类型系统无法捕获。这正是[统一 agent/session 标识决策](../simplification/2026-06-20-unify-agent-and-session-id.zh.md)覆盖的共享 id 别名。
|
||||
|
||||
**缺口 2:*已经 brand* 的 ID 在边界处被侵蚀。** 就连 `CallId` 和 `SessionId` 也恰好在最容易混淆的地方退化为裸 `string`:注册表/store 键类型和公开方法参数。代表性位置包括会话存储、agent 注册表(二者都以共享的 `SessionId` 为键)、工具展示层的 call-id map、ACP(Agent Client Protocol)的会话记录,以及持久化协调器。在集合键处丢弃 brand,会让既有 brand 在查找时毫无价值;它们的价值只实现了一部分。
|
||||
**缺口 2:*已经 brand* 的 ID 在边界处被侵蚀。** 就连 `ToolCallId` 和 `SessionId` 也恰好在最容易混淆的地方退化为裸 `string`:注册表/store 键类型和公开方法参数。代表性位置包括会话存储、agent 注册表(二者都以共享的 `SessionId` 为键)、工具展示层的 call-id map、ACP(Agent Client Protocol)的会话记录,以及持久化协调器。在集合键处丢弃 brand,会让既有 brand 在查找时毫无价值;它们的价值只实现了一部分。
|
||||
|
||||
## 决策
|
||||
|
||||
@@ -22,7 +22,7 @@ bash **owner token** 是相关的子情形:`ShellExecRequest.owner?: string`
|
||||
|
||||
- **铸造独立的 `OwnerToken` brand。** 在 `packages/shell/shell/src/types.ts` 中添加 `OwnerToken = Branded<'OwnerToken'>`;将 `ShellExecRequest.owner` / `ShellExecSpec.owner` / `ShellExecutor.ownerOf` 的类型标注为 `OwnerToken | undefined`。`dsh-tool-bash` 消费方在边界处将 agent 共享的 `id`(`SessionId`)cast 为 `OwnerToken`——这是两套词汇唯一交汇的地方。bash Service Definition 从不导入 `dsh-session`。(理由见下一节。)
|
||||
|
||||
- **阻止 brand 侵蚀。** 将既有 brand 传播到缺口 2 列出的 `Map` 键类型和公开方法参数中:`Map<SessionId, Session>`、`Map<SessionId, Agent>`、`get(id: SessionId)`、`Map<CallId, …>`、ACP 的 `SessionId` surface、协调器的 `Map<SessionId, …>`。这是变更中机械量最大的部分,也是让*既有* brand 在查找处真正发挥作用(而不仅仅标注在结构体字段上)的关键。
|
||||
- **阻止 brand 侵蚀。** 将既有 brand 传播到缺口 2 列出的 `Map` 键类型和公开方法参数中:`Map<SessionId, Session>`、`Map<SessionId, Agent>`、`get(id: SessionId)`、`Map<ToolCallId, …>`、ACP 的 `SessionId` surface、协调器的 `Map<SessionId, …>`。这是变更中机械量最大的部分,也是让*既有* brand 在查找处真正发挥作用(而不仅仅标注在结构体字段上)的关键。
|
||||
|
||||
示意形状(工厂模式与已有的三个 brand 完全一致):
|
||||
|
||||
@@ -60,7 +60,7 @@ export function OwnerToken(id: string): OwnerToken {
|
||||
|
||||
## 验证
|
||||
|
||||
已落地的不变式如下:`BashTaskId` 和 `OwnerToken` 定义在 `dsh-shell` 中,并端到端贯穿 Service Definition、`dsh-bash-local` 生成点与 `dsh-tool-bash` 面向模型的工具,且 `dsh-shell` 未添加对 `dsh-session` 的依赖;没有任何以范围内 brand id(`CallId`/`SessionId`/`BashTaskId`)为键的集合使用裸 `string`;公开方法参数和导出签名保留 brand;每个原始 string 进入的边界(提供方 call id、ACP 会话 id、模型提供的 `job_id`)都通过 cast 工厂构造 brand,而不是散落的 `as` cast。
|
||||
已落地的不变式如下:`BashTaskId` 和 `OwnerToken` 定义在 `dsh-shell` 中,并端到端贯穿 Service Definition、`dsh-bash-local` 生成点与 `dsh-tool-bash` 面向模型的工具,且 `dsh-shell` 未添加对 `dsh-session` 的依赖;没有任何以范围内 brand id(`ToolCallId`/`SessionId`/`BashTaskId`)为键的集合使用裸 `string`;公开方法参数和导出签名保留 brand;每个原始 string 进入的边界(提供方 call id、ACP 会话 id、模型提供的 `job_id`)都通过 cast 工厂构造 brand,而不是散落的 `as` cast。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -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-07-08-tool-output-spill-files.md
|
||||
2026-07-08-tool-output-spill-files.md: e14607e388c634c4e2679c993c1b720be0a3a9f3
|
||||
2026-07-08-tool-output-spill-files.zh.md: 372c9c6cadf3cd64c3de97a8c305b8909f03caab
|
||||
2026-07-08-tool-output-spill-files.md: 915e22f1245adb6f7cfc7d358e9d5802531bab63
|
||||
2026-07-08-tool-output-spill-files.zh.md: 8d08b05483a302f4188506531da6f507931bea9c
|
||||
|
||||
@@ -35,7 +35,7 @@ interface SpillStore {
|
||||
|
||||
interface SpillSource {
|
||||
toolName: string
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
label: string
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ interface SpillStore {
|
||||
|
||||
interface SpillSource {
|
||||
toolName: string
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
label: string
|
||||
}
|
||||
|
||||
|
||||
@@ -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/feature/2026-07-06-approval-seam.md
|
||||
2026-07-06-approval-seam.md: cbb1b1cb1fef0f6ed1db8e3e0eb1ea01a5834f53
|
||||
2026-07-06-approval-seam.zh.md: 29e23f81827200cbbbe0d6a78216ff1f1d4b52ae
|
||||
2026-07-06-approval-seam.md: 6e94ed945b4c568bd3c82c8e590736d8d1c27606
|
||||
2026-07-06-approval-seam.zh.md: 26447522ecf1d93a09944c0d2eb9574f1e0f9cb2
|
||||
|
||||
@@ -55,7 +55,7 @@ After validation and a successful `approval/asked` append, the service resolves
|
||||
|
||||
Answerers are `approval/request` waterfall listeners. Zero listeners fall through to `unavailable`; a recognizing listener occupies the first-wins decision slot, while an unrecognized agent must delegate with `next()`. Listeners dispose with their fibers, so an unloaded channel fails closed. Because sibling registration order is not deterministic, a deployment composes one terminal answerer and reserves `prepend` for decide-or-delegate gates.
|
||||
|
||||
`ApprovalRequest` carries the asking `agent`, `toolName`, optional exact `callId`, human-readable `reason`, and optional `signal`. It uses the `CallId` brand without importing `dsh-tools`, which depends on this seam. Channel adapters correlate any richer call state by `callId`; the approval request does not duplicate tool arguments.
|
||||
`ApprovalRequest` carries the asking `agent`, `toolName`, optional exact `callId`, human-readable `reason`, and optional `signal`. It uses the `ToolCallId` brand without importing `dsh-tools`, which depends on this seam. Channel adapters correlate any richer call state by `callId`; the approval request does not duplicate tool arguments.
|
||||
|
||||
#### Ask routing in dsh-tools
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ tool/result "escalated" — this one call ran under the wider mode; the gra
|
||||
|
||||
应答者是 `approval/request` waterfall 监听器。零监听器会直接落到 `unavailable`;识别该 agent 的监听器占用先到先得的决策槽,而不识别的监听器必须调用 `next()` 委派。监听器会随其 fiber 一同 dispose(资源释放),因此卸载通道后,请求会在故障时默认被拒绝。由于兄弟插件的注册顺序不确定,部署应组合一个终端应答者,并保留 `prepend` 给「决策或委派」门禁。
|
||||
|
||||
`ApprovalRequest` 携带发起请求的 `agent`、`toolName`、可选的精确 `callId`、人类可读的 `reason` 和可选的 `signal`。它使用 `CallId` brand 而不导入依赖本 seam 的 `dsh-tools`。通道适配器可按 `callId` 关联任何更丰富的调用状态;审批请求本身不重复携带工具参数。
|
||||
`ApprovalRequest` 携带发起请求的 `agent`、`toolName`、可选的精确 `callId`、人类可读的 `reason` 和可选的 `signal`。它使用 `ToolCallId` brand 而不导入依赖本 seam 的 `dsh-tools`。通道适配器可按 `callId` 关联任何更丰富的调用状态;审批请求本身不重复携带工具参数。
|
||||
|
||||
#### dsh-tools 中的 Ask 路由
|
||||
|
||||
|
||||
@@ -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/proposed/feature/2026-08-04-task-surface.md
|
||||
2026-08-04-task-surface.md: 03f79dbc7d40957603885c97883a2de1bb5405d7
|
||||
2026-08-04-task-surface.zh.md: 63bb286bd8095f6eb0d3b0d366e62a838ae8bdc9
|
||||
2026-08-04-task-surface.md: dbf73976b606a4a45202c1b29b79f5cfbd77ac1c
|
||||
2026-08-04-task-surface.zh.md: ecf8764b7b1da7a4a874dd78c6f69df80fb36f36
|
||||
|
||||
@@ -142,7 +142,7 @@ type SubmitTaskSurfaceResult =
|
||||
type GetActiveTaskSurfaceResult =
|
||||
| {
|
||||
active: true
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
surfaceId: TaskSurfaceId
|
||||
model: TaskSurfaceModelV1
|
||||
pending: TaskSurfacePendingSubmission | null
|
||||
@@ -166,7 +166,7 @@ The Host resolves the exact successful `show_task_surface` occurrence, revalidat
|
||||
interface TaskSurfaceCorrelation {
|
||||
version: 1
|
||||
submissionId: TaskSurfaceSubmissionId
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
surfaceId: TaskSurfaceId
|
||||
values: Record<string, JsonValue>
|
||||
}
|
||||
@@ -210,7 +210,7 @@ The Session log is the authority. A small `taskSurface` unit in the existing [Se
|
||||
|
||||
```ts ignore-check
|
||||
interface TaskSurfaceProjection {
|
||||
active: { callId: CallId; surfaceId: TaskSurfaceId } | null
|
||||
active: { callId: ToolCallId; surfaceId: TaskSurfaceId } | null
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ type SubmitTaskSurfaceResult =
|
||||
type GetActiveTaskSurfaceResult =
|
||||
| {
|
||||
active: true
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
surfaceId: TaskSurfaceId
|
||||
model: TaskSurfaceModelV1
|
||||
pending: TaskSurfacePendingSubmission | null
|
||||
@@ -166,7 +166,7 @@ Host 解析出 `show_task_surface` 的确切成功调用实例,依据其已持
|
||||
interface TaskSurfaceCorrelation {
|
||||
version: 1
|
||||
submissionId: TaskSurfaceSubmissionId
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
surfaceId: TaskSurfaceId
|
||||
values: Record<string, JsonValue>
|
||||
}
|
||||
@@ -210,7 +210,7 @@ Task Surface 服务将已接受提交的协调状态记录为 `pending.phase: 'q
|
||||
|
||||
```ts ignore-check
|
||||
interface TaskSurfaceProjection {
|
||||
active: { callId: CallId; surfaceId: TaskSurfaceId } | null
|
||||
active: { callId: ToolCallId; surfaceId: TaskSurfaceId } | null
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-skill'
|
||||
@@ -45,7 +45,7 @@ try {
|
||||
: undefined
|
||||
const summary = (await ctx.skills.list()).find(skill => skill.name === 'dsh-badge')
|
||||
const result = await ctx.tools.execute({
|
||||
callId: CallId('dsh-badge-snapshot'),
|
||||
callId: ToolCallId('dsh-badge-snapshot'),
|
||||
name: 'skill',
|
||||
arguments: { name: 'dsh-badge' },
|
||||
signal: new AbortController().signal,
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import {
|
||||
CallId,
|
||||
ToolCallId,
|
||||
LlmAdapter,
|
||||
ReasoningEffortId,
|
||||
type GenerateOptions,
|
||||
@@ -50,7 +50,7 @@ class ControlSurfaceAdapter extends LlmAdapter {
|
||||
.join('')
|
||||
const hasToolResult = current.some(message => message.content.some(block => block.type === 'tool-result'))
|
||||
if (!hasToolResult) {
|
||||
const callId = CallId(userText.includes('cancel') ? 'control-cancel-add' : 'control-add')
|
||||
const callId = ToolCallId(userText.includes('cancel') ? 'control-cancel-add' : 'control-add')
|
||||
yield { type: 'block-start', index: 0, blockType: 'reasoning' }
|
||||
yield { type: 'reasoning-delta', index: 0, text: 'checking the attached tool' }
|
||||
yield { type: 'block-end', index: 0, block: { type: 'reasoning', text: 'checking the attached tool' } }
|
||||
|
||||
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import LlmRuntime, { createUserMessage, CallId, HarnessError } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, HarnessError } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
@@ -93,7 +93,7 @@ function runCode(
|
||||
agent?: Agent,
|
||||
): Promise<ToolExecutionResult> {
|
||||
return harness.tools.execute({
|
||||
callId: CallId(`keyless-code-${++keylessCall}`),
|
||||
callId: ToolCallId(`keyless-code-${++keylessCall}`),
|
||||
name: RUN_CODE_NAME,
|
||||
arguments: { code, description: 'Run the e2e program' },
|
||||
signal,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Deterministic keyless Agent Teams adapter shared by profile snapshot and CLI e2e. */
|
||||
|
||||
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
let nextCall = 0
|
||||
|
||||
@@ -38,7 +38,7 @@ function latestToolText(messages) {
|
||||
function toolChunks(specs) {
|
||||
const chunks = []
|
||||
for (const [index, spec] of specs.entries()) {
|
||||
const id = CallId(`team-fixture-${++nextCall}`)
|
||||
const id = ToolCallId(`team-fixture-${++nextCall}`)
|
||||
const args = JSON.stringify(spec.args)
|
||||
chunks.push(
|
||||
{ type: 'block-start', index, blockType: 'tool-call' },
|
||||
|
||||
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { normalizeSessionSnapshot, type NormalizeContext } from '@deepseek-ai/dsh-session-snapshot'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import { createUserMessage, CallId , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
@@ -46,7 +46,7 @@ async function seedInterruptedSession(root: string, cwd: string): Promise<string
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'tool-call', id: CallId('unknown-outcome-call'), name: 'write_remote', arguments: '{"value":1}' }],
|
||||
content: [{ type: 'tool-call', id: ToolCallId('unknown-outcome-call'), name: 'write_remote', arguments: '{"value":1}' }],
|
||||
source: {
|
||||
kind: 'model',
|
||||
...{ provider: 'deepseek-official', model: 'deepseek-v4-flash' },
|
||||
@@ -62,7 +62,7 @@ async function seedInterruptedSession(root: string, cwd: string): Promise<string
|
||||
data: {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
callId: CallId('unknown-outcome-call'),
|
||||
callId: ToolCallId('unknown-outcome-call'),
|
||||
name: 'write_remote',
|
||||
arguments: '{"value":1}',
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
||||
import { SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
|
||||
import { SETTINGS_NAMESPACE, SHIPPED_PRESET_ROOT } from '@deepseek-ai/dsh-agent-presets'
|
||||
import { applyChildComposition, childSessionMeta } from '@deepseek-ai/dsh-subagent'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/dsh-compaction-basic'
|
||||
import type {} from '@deepseek-ai/dsh-skill'
|
||||
import type {} from '@deepseek-ai/dsh-tools'
|
||||
@@ -429,7 +429,7 @@ describe('the shipped Web composition', () => {
|
||||
|
||||
// The preset's own loader tool resolves the global-layer skill.
|
||||
const loaded = await ctx.tools.execute({
|
||||
callId: CallId('preset-skills-load'),
|
||||
callId: ToolCallId('preset-skills-load'),
|
||||
name: 'skill',
|
||||
arguments: { name: 'dsh-badge' },
|
||||
signal: new AbortController().signal,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { JobId } from '@deepseek-ai/dsh-jobs'
|
||||
import {
|
||||
@@ -85,7 +85,7 @@ describe.skipIf(MODE === 'record')('web e2e: background job list', () => {
|
||||
|
||||
const started = await scaffold.ctx.tools.execute({
|
||||
signal: new AbortController().signal,
|
||||
callId: CallId('background-job-list-e2e'),
|
||||
callId: ToolCallId('background-job-list-e2e'),
|
||||
name: 'bash',
|
||||
arguments: { command: COMMAND, description: 'Hold a background slot open', run_in_background: true },
|
||||
agent,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { join } from 'node:path'
|
||||
import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import type { ReplayEntry, ReplayOverrideDoc } from '@deepseek-ai/dsh-llm-replay'
|
||||
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import {
|
||||
@@ -32,7 +32,7 @@ interface TurnSpec {
|
||||
readonly firstMarker: string
|
||||
readonly doneMarker: string
|
||||
readonly deltas: readonly string[]
|
||||
readonly callId?: ReturnType<typeof CallId>
|
||||
readonly callId?: ReturnType<typeof ToolCallId>
|
||||
readonly toolResultMarker?: string
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ function turnSpec(index: number): TurnSpec {
|
||||
firstMarker,
|
||||
doneMarker,
|
||||
deltas,
|
||||
callId: CallId(`continuous-chat-tool-${id}`),
|
||||
callId: ToolCallId(`continuous-chat-tool-${id}`),
|
||||
toolResultMarker: `CONTINUOUS_CHAT_TOOL_RESULT_${id}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { ReplayEntry, ReplayOverrideDoc } from '@deepseek-ai/dsh-llm-replay'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { createChatScrollFixture, type ChatScrollFixture } from './chat-scroll-fixture.ts'
|
||||
@@ -35,7 +35,7 @@ const LIVE_TEXT_PROMPT = 'CHAT_SCROLL_LIVE_USER Continue this long conversation
|
||||
const LIVE_TEXT_FIRST = 'CHAT_SCROLL_LIVE_FIRST'
|
||||
const LIVE_TEXT_DONE = 'CHAT_SCROLL_LIVE_DONE'
|
||||
const LIVE_TOOL_PROMPT = 'CHAT_SCROLL_TOOL_USER Run the requested diagnostic and then summarize it.'
|
||||
const LIVE_TOOL_CALL_ID = CallId('chat-scroll-live-tool-call')
|
||||
const LIVE_TOOL_CALL_ID = ToolCallId('chat-scroll-live-tool-call')
|
||||
const LIVE_TOOL_RESULT = 'CHAT_SCROLL_LIVE_TOOL_RESULT'
|
||||
const LIVE_TOOL_FIRST = 'CHAT_SCROLL_TOOL_STREAM_FIRST'
|
||||
const LIVE_TOOL_DONE = 'CHAT_SCROLL_TOOL_STREAM_DONE'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// persisted conversations, while unique markers identify semantic rows
|
||||
// without depending on CSS-module names or virtualizer DOM positions.
|
||||
import {
|
||||
CallId,
|
||||
ToolCallId,
|
||||
createAssistantMessage,
|
||||
createToolResultMessage,
|
||||
createUserMessage,
|
||||
@@ -105,7 +105,7 @@ function appendToolStep(
|
||||
): void {
|
||||
const calls = [1, 2].map((index) => {
|
||||
const marker = markers.tool(turn, index)
|
||||
const callId = CallId(`chat-scroll-${suffix(turn)}-${String(index)}`)
|
||||
const callId = ToolCallId(`chat-scroll-${suffix(turn)}-${String(index)}`)
|
||||
const args = JSON.stringify({
|
||||
command: `printf '${marker}\\n'`,
|
||||
description: marker,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import {
|
||||
CallId,
|
||||
ToolCallId,
|
||||
createAssistantMessage,
|
||||
createToolResultMessage,
|
||||
createUserMessage,
|
||||
@@ -233,7 +233,7 @@ function appendToolStep(
|
||||
toolCount: number,
|
||||
): void {
|
||||
const calls = Array.from({ length: toolCount }, (_, index) => {
|
||||
const callId = CallId(`perf-call-${String(turn)}-${String(index)}`)
|
||||
const callId = ToolCallId(`perf-call-${String(turn)}-${String(index)}`)
|
||||
const args = JSON.stringify({
|
||||
turn,
|
||||
index,
|
||||
@@ -467,7 +467,7 @@ function soakTurn(index: number): ConversationTurnSpec {
|
||||
}
|
||||
|
||||
function toolStream(index: number, marker: string): StreamChunk[] {
|
||||
const callId = CallId(`performance-tool-${marker.toLowerCase()}-${String(index)}`)
|
||||
const callId = ToolCallId(`performance-tool-${marker.toLowerCase()}-${String(index)}`)
|
||||
const args = JSON.stringify({
|
||||
command: `printf '${marker}\\n'`,
|
||||
description: `Emit performance marker ${String(index)}`,
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import type { AgentHandle } from '@deepseek-ai/dsh-agent'
|
||||
import { CallId, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-agent-presets'
|
||||
import type {} from '@deepseek-ai/dsh-system-prompt'
|
||||
@@ -85,14 +85,14 @@ describe('minimal agent preset', () => {
|
||||
const signal = new AbortController().signal
|
||||
await scaffold.ctx.tools.execute({
|
||||
signal,
|
||||
callId: CallId('minimal-bash-state-setup'),
|
||||
callId: ToolCallId('minimal-bash-state-setup'),
|
||||
name: 'bash',
|
||||
arguments: { command: `cd ${JSON.stringify(stateDir)} && export DSH_MINIMAL_STATE=PERSISTED` },
|
||||
agent: agentHandle.agent,
|
||||
})
|
||||
const bash = await scaffold.ctx.tools.execute({
|
||||
signal,
|
||||
callId: CallId('minimal-bash-state-read'),
|
||||
callId: ToolCallId('minimal-bash-state-read'),
|
||||
name: 'bash',
|
||||
arguments: { command: 'printf \'%s:%s\n\' "$DSH_MINIMAL_STATE" "$PWD"' },
|
||||
agent: agentHandle.agent,
|
||||
@@ -101,7 +101,7 @@ describe('minimal agent preset', () => {
|
||||
await writeFile(seedPath, 'MINIMAL_EDITOR_OK\n')
|
||||
const editor = await scaffold.ctx.tools.execute({
|
||||
signal,
|
||||
callId: CallId('minimal-editor-smoke'),
|
||||
callId: ToolCallId('minimal-editor-smoke'),
|
||||
name: 'str_replace_editor',
|
||||
arguments: { command: 'view', path: seedPath },
|
||||
agent: agentHandle.agent,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import { CallId, createAssistantMessage, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, createAssistantMessage, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { SESSION_FORMAT_VERSION, Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-session-title'
|
||||
import {
|
||||
@@ -46,7 +46,7 @@ function mentionFixture(): string {
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
const calls = WRITES.map((path, index) => ({
|
||||
path,
|
||||
callId: CallId(`file-mention-${String(index)}`),
|
||||
callId: ToolCallId(`file-mention-${String(index)}`),
|
||||
args: JSON.stringify({ file_path: path, content: `content of ${path}\n` }),
|
||||
}))
|
||||
session.append('assistant/message', {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed, vi } from 'vitest'
|
||||
import { CallId, createAssistantMessage, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, createAssistantMessage, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { SESSION_FORMAT_VERSION, Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-session-title'
|
||||
import {
|
||||
@@ -49,7 +49,7 @@ function producedFixture(): string {
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
const calls = PRODUCED.map((path, index) => ({
|
||||
path,
|
||||
callId: CallId(`produced-files-${String(index)}`),
|
||||
callId: ToolCallId(`produced-files-${String(index)}`),
|
||||
args: JSON.stringify({ file_path: path, content: `content of ${path}\n` }),
|
||||
}))
|
||||
session.append('assistant/message', {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import {
|
||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||
@@ -99,7 +99,7 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
||||
if (agent === undefined) throw new Error(`the settled Web agent ${settledSessionId} is no longer live`)
|
||||
const result = await scaffold.ctx.tools.execute({
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
callId: CallId('web-url-probe'),
|
||||
callId: ToolCallId('web-url-probe'),
|
||||
name: 'bash',
|
||||
arguments: {
|
||||
command: 'printf \'%s\\n\' "$DSH_WEB_URL"',
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import type { AgentHandle } from '@deepseek-ai/dsh-agent'
|
||||
import { CallId, createUserMessage, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, createUserMessage, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import {
|
||||
@@ -121,7 +121,7 @@ class BrowserZoneAtAdapter extends LlmAdapter {
|
||||
this.selectedAt = localAt(target, AT_BROWSER_ZONE)
|
||||
this.scheduledAt = new Date(target).toISOString()
|
||||
const argumentsJson = JSON.stringify({ prompt: AT_PROMPT, at: this.selectedAt })
|
||||
const callId = CallId('schedule-at-browser-zone')
|
||||
const callId = ToolCallId('schedule-at-browser-zone')
|
||||
yield { type: 'block-start', index: 0, blockType: 'tool-call' }
|
||||
yield {
|
||||
type: 'tool-call-delta',
|
||||
@@ -258,7 +258,7 @@ describe.skipIf(MODE === 'record')('web e2e: conversational reminders', () => {
|
||||
await workspace.attachSession(afterHandle.agent.id)
|
||||
const afterCreated = await scaffold.ctx.tools.execute({
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
callId: CallId('schedule-after-create'),
|
||||
callId: ToolCallId('schedule-after-create'),
|
||||
name: 'schedule_create',
|
||||
arguments: { prompt: AFTER_PROMPT, after_seconds: 1 },
|
||||
agent: afterHandle.agent,
|
||||
@@ -314,7 +314,7 @@ describe.skipIf(MODE === 'record')('web e2e: conversational reminders', () => {
|
||||
await workspace.attachSession(everyHandle.agent.id)
|
||||
const everyListed = await scaffold.ctx.tools.execute({
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
callId: CallId('schedule-every-list'),
|
||||
callId: ToolCallId('schedule-every-list'),
|
||||
name: 'schedule_list',
|
||||
arguments: {},
|
||||
agent: everyHandle.agent,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { readFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
||||
@@ -207,7 +207,7 @@ it('lets a preset producer reach the background-job registry', async () => {
|
||||
// fails here — with every task control still listed in the catalog above.
|
||||
const started = await ctx.tools.execute({
|
||||
signal,
|
||||
callId: CallId('shipped-bash-background'),
|
||||
callId: ToolCallId('shipped-bash-background'),
|
||||
name: 'bash',
|
||||
arguments: {
|
||||
command: 'printf SHIPPED_BACKGROUND_OK',
|
||||
@@ -225,7 +225,7 @@ it('lets a preset producer reach the background-job registry', async () => {
|
||||
// owner. A per-preset registry would list nothing here even on success.
|
||||
const listed = await ctx.tools.execute({
|
||||
signal,
|
||||
callId: CallId('shipped-task-list'),
|
||||
callId: ToolCallId('shipped-task-list'),
|
||||
name: 'job_list',
|
||||
arguments: {},
|
||||
agent: handle.agent,
|
||||
@@ -239,7 +239,7 @@ it('lets a preset producer reach the background-job registry', async () => {
|
||||
// through a preset-plane control, which is the linkage the realm severed.
|
||||
const collected = await ctx.tools.execute({
|
||||
signal,
|
||||
callId: CallId('shipped-task-output'),
|
||||
callId: ToolCallId('shipped-task-output'),
|
||||
name: 'job_output',
|
||||
arguments: { job_id: 'bash-1', wait: true },
|
||||
agent: handle.agent,
|
||||
|
||||
@@ -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/cordis-tutorial/07-into-the-harness.md
|
||||
07-into-the-harness.md: acd13d6d06a86378ee13557ea4e3e172670bf1af
|
||||
07-into-the-harness.zh.md: b6a732f6e37217ba317a0187b499dbbdcae949da
|
||||
07-into-the-harness.md: 28e6c2009eec736c92987e0da282f04db249e466
|
||||
07-into-the-harness.zh.md: 603b9cac19e05c997073ad0747622b29790bc555
|
||||
|
||||
@@ -11,7 +11,7 @@ Create `greet-tool.ts` in `tmp/cordis-tutorial`:
|
||||
```ts
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
export const name = 'greet-tool'
|
||||
export const inject = ['tools']
|
||||
@@ -33,10 +33,10 @@ export function apply(ctx: Context) {
|
||||
}))
|
||||
|
||||
// Drive one call through the real execution pipeline, standing in for
|
||||
// the model. CallId brands the correlation id a provider would issue.
|
||||
// the model. ToolCallId brands the correlation id a provider would issue.
|
||||
void (async () => {
|
||||
const result = await ctx.tools.execute({
|
||||
callId: CallId('demo-1'),
|
||||
callId: ToolCallId('demo-1'),
|
||||
name: 'greet',
|
||||
arguments: { name: 'Cordis' },
|
||||
signal: new AbortController().signal,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
```ts
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
export const name = 'greet-tool'
|
||||
export const inject = ['tools']
|
||||
@@ -33,10 +33,10 @@ export function apply(ctx: Context) {
|
||||
}))
|
||||
|
||||
// Drive one call through the real execution pipeline, standing in for
|
||||
// the model. CallId brands the correlation id a provider would issue.
|
||||
// the model. ToolCallId brands the correlation id a provider would issue.
|
||||
void (async () => {
|
||||
const result = await ctx.tools.execute({
|
||||
callId: CallId('demo-1'),
|
||||
callId: ToolCallId('demo-1'),
|
||||
name: 'greet',
|
||||
arguments: { name: 'Cordis' },
|
||||
signal: new AbortController().signal,
|
||||
|
||||
@@ -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: 593fb073022e139a13f1d642c6f12a94400b0446
|
||||
persistence-catalog.zh.md: 59acfa23c81687f23498d598cb0aead3e669ceb2
|
||||
persistence-catalog.md: b9839833308afbd0d561bc227a95b236a10238a1
|
||||
persistence-catalog.zh.md: 7b5fa938b23ea9112e370133bf7575c8d689806d
|
||||
|
||||
@@ -142,12 +142,12 @@ Source: [`packages/preset/agent-presets/src/session.ts:28`](../packages/preset/a
|
||||
'approval/asked': {
|
||||
id: ApprovalRequestId
|
||||
toolName: string
|
||||
callId?: CallId
|
||||
callId?: ToolCallId
|
||||
reason?: string
|
||||
}
|
||||
```
|
||||
|
||||
Types: [CallId](subsystems/core.md)
|
||||
Types: [ToolCallId](subsystems/core.md)
|
||||
|
||||
Source: [`packages/interaction/user-approval/src/types.ts:44`](../packages/interaction/user-approval/src/types.ts)
|
||||
|
||||
@@ -840,10 +840,10 @@ Source: [`packages/todo/tool-todo/src/types.ts:31`](../packages/todo/tool-todo/s
|
||||
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
||||
* call with its `tool/result`.
|
||||
*/
|
||||
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
|
||||
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
|
||||
```
|
||||
|
||||
Types: [CallId](subsystems/core.md)
|
||||
Types: [ToolCallId](subsystems/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:268`](../packages/core/session/src/types.ts)
|
||||
|
||||
|
||||
@@ -144,12 +144,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'approval/asked': {
|
||||
id: ApprovalRequestId
|
||||
toolName: string
|
||||
callId?: CallId
|
||||
callId?: ToolCallId
|
||||
reason?: string
|
||||
}
|
||||
```
|
||||
|
||||
类型:[CallId](subsystems/core.zh.md)
|
||||
类型:[ToolCallId](subsystems/core.zh.md)
|
||||
|
||||
来源:[`packages/interaction/user-approval/src/types.ts:44`](../packages/interaction/user-approval/src/types.ts)
|
||||
|
||||
@@ -842,10 +842,10 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
||||
* call with its `tool/result`.
|
||||
*/
|
||||
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
|
||||
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
|
||||
```
|
||||
|
||||
类型:[CallId](subsystems/core.zh.md)
|
||||
类型:[ToolCallId](subsystems/core.zh.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts)
|
||||
|
||||
|
||||
@@ -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/approval.md
|
||||
approval.md: d9f1169b52e427cd37e7bc54fa37da59d48aecce
|
||||
approval.zh.md: abc2361db3d84517c7e7497cfb39b4548160c259
|
||||
approval.md: 89130232b45da2982d0da40c8fad217b364758b2
|
||||
approval.zh.md: e8047cd5a8bf8dfa23b9e80ad97db00d0e25a061
|
||||
|
||||
@@ -70,7 +70,7 @@ interface ApprovalRequest extends ApprovalRequestEvent {
|
||||
* The exact tool call being decided, when the asker has one — lets a UI
|
||||
* attach the prompt to the tool call it already streamed.
|
||||
*/
|
||||
readonly callId?: CallId
|
||||
readonly callId?: ToolCallId
|
||||
/** The asker's human-readable explanation of WHY it is asking. */
|
||||
readonly reason?: string
|
||||
/**
|
||||
|
||||
@@ -70,7 +70,7 @@ interface ApprovalRequest extends ApprovalRequestEvent {
|
||||
* The exact tool call being decided, when the asker has one — lets a UI
|
||||
* attach the prompt to the tool call it already streamed.
|
||||
*/
|
||||
readonly callId?: CallId
|
||||
readonly callId?: ToolCallId
|
||||
/** The asker's human-readable explanation of WHY it is asking. */
|
||||
readonly reason?: string
|
||||
/**
|
||||
|
||||
@@ -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/compaction.md
|
||||
compaction.md: 019912531636848404a33d772fec3131cafa0287
|
||||
compaction.zh.md: 9acc4d8250d0274be525d826e4391dce1a606392
|
||||
compaction.md: 03642b32cc3cb3caf743d908e9c988c1a3c8fa1e
|
||||
compaction.zh.md: af37c4824cc4b01515400049525fbd7b91d018f3
|
||||
|
||||
@@ -99,7 +99,7 @@ interface PrunedEntry {
|
||||
/** Newly appended pruned tool-result event. */
|
||||
readonly replacementSeq: number
|
||||
/** Tool call shared by the original and replacement. */
|
||||
readonly callId: CallId
|
||||
readonly callId: ToolCallId
|
||||
/** Original text size in Unicode code points. */
|
||||
readonly charsBefore: number
|
||||
/** Replacement text size in Unicode code points. */
|
||||
|
||||
@@ -99,7 +99,7 @@ interface PrunedEntry {
|
||||
/** Newly appended pruned tool-result event. */
|
||||
readonly replacementSeq: number
|
||||
/** Tool call shared by the original and replacement. */
|
||||
readonly callId: CallId
|
||||
readonly callId: ToolCallId
|
||||
/** Original text size in Unicode code points. */
|
||||
readonly charsBefore: number
|
||||
/** Replacement text size in Unicode code points. */
|
||||
|
||||
@@ -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/core.md
|
||||
core.md: 6490712bc3c90686ba2cbd787a9f21f59b9f9f3d
|
||||
core.zh.md: 79a2b5d1e9090fe56ea3807d4cf42d0f87c6a48b
|
||||
core.md: dbe12dfc9bfdafe0f59d7e52eefc5695b1c8d063
|
||||
core.zh.md: 50fd2e4e76ee72653c02c2ffeca06042191aa9c4
|
||||
|
||||
@@ -301,7 +301,7 @@ Two large discriminated unions are the ones consumers `switch` over most: **`Str
|
||||
|
||||
### Branded IDs
|
||||
|
||||
IDs passed between packages are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `CallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings.
|
||||
IDs passed between packages are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `ToolCallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings.
|
||||
|
||||
The `Branded<B>` primitive lives in its own type-only package, [dsh-brand](../../packages/util/brand) (no runtime code, no harness-package dependency), so any package can brand the ids it owns without depending on an unrelated capability package.
|
||||
|
||||
@@ -312,7 +312,7 @@ Source: [`packages/util/brand/src/index.ts`](../../packages/util/brand/src/index
|
||||
type Branded<B extends string> = string & { readonly [BRAND]: B }
|
||||
```
|
||||
|
||||
The two core IDs are `CallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `JobId` in [jobs.md](jobs.md).
|
||||
The two core IDs are `ToolCallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `JobId` in [jobs.md](jobs.md).
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -311,7 +311,7 @@ declare module '@deepseek-ai/dsh-llm' {
|
||||
|
||||
### 品牌化 ID
|
||||
|
||||
在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `CallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。
|
||||
在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `ToolCallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。
|
||||
|
||||
`Branded<B>` 原语位于独立的纯类型包 [dsh-brand](../../packages/util/brand) 中(没有运行时代码,也不依赖 harness 包),因此任何包都能品牌化其拥有的 id,而无需依赖无关的能力包。
|
||||
|
||||
@@ -322,7 +322,7 @@ declare module '@deepseek-ai/dsh-llm' {
|
||||
type Branded<B extends string> = string & { readonly [BRAND]: B }
|
||||
```
|
||||
|
||||
两个核心 ID 是 `CallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [jobs.md](jobs.zh.md) 中的 `JobId`。
|
||||
两个核心 ID 是 `ToolCallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [jobs.md](jobs.zh.md) 中的 `JobId`。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -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: 8db1d95d91095d09eeb4a0399e1449688cab52cc
|
||||
llm-streaming.zh.md: fd2d483cbbecac9b1015d67e0e14aa67b67ef08e
|
||||
llm-streaming.md: 331c30e1039741462df7ae3e7a9e6497281a5df0
|
||||
llm-streaming.zh.md: f05c67bc7552a4bc5a9359416d98b62f238f1c65
|
||||
|
||||
@@ -28,7 +28,7 @@ interface ContentBlockMap {
|
||||
}
|
||||
```
|
||||
|
||||
The block interfaces (full fields in source): `TextBlock` (`text`), `ReasoningBlock` (thinking, distinct from visible text), `ImageBlock` (a durable [image attachment](attachment.md)), `ToolCallBlock` (`id: CallId`, `name`, raw-JSON `arguments`), and `ToolResultBlock` (`toolCallId`, nested `content: ContentBlock[]`, `isError?`). `ContentBlock = ContentBlockMap[ContentBlockType]`. A new modality belongs in the merge-extensible map only when its adapter, UI, compaction, and durable replay paths honor it.
|
||||
The block interfaces (full fields in source): `TextBlock` (`text`), `ReasoningBlock` (thinking, distinct from visible text), `ImageBlock` (a durable [image attachment](attachment.md)), `ToolCallBlock` (`id: ToolCallId`, `name`, raw-JSON `arguments`), and `ToolResultBlock` (`toolCallId`, nested `content: ContentBlock[]`, `isError?`). `ContentBlock = ContentBlockMap[ContentBlockType]`. A new modality belongs in the merge-extensible map only when its adapter, UI, compaction, and durable replay paths honor it.
|
||||
|
||||
Image access belongs to request serialization rather than the durable attachment or deterministic request-image version. `resolveImageAttachmentAccess()` combines the attachment provider's optional host object path with a mapping supplied by the consumer for the current tool execution filesystem. The result is available only for that request and does not participate in `variantId`.
|
||||
|
||||
@@ -205,7 +205,7 @@ type StreamChunk =
|
||||
| { type: 'block-start'; index: number; blockType: ContentBlockType }
|
||||
| { type: 'text-delta'; index: number; text: string }
|
||||
| { type: 'reasoning-delta'; index: number; text: string }
|
||||
| { type: 'tool-call-delta'; index: number; id: CallId; name?: string; argumentsDelta: string }
|
||||
| { type: 'tool-call-delta'; index: number; id: ToolCallId; name?: string; argumentsDelta: string }
|
||||
| { type: 'block-end'; index: number; block: ContentBlock }
|
||||
| { type: 'usage'; usage: TokenUsage }
|
||||
| {
|
||||
|
||||
@@ -28,7 +28,7 @@ interface ContentBlockMap {
|
||||
}
|
||||
```
|
||||
|
||||
各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ImageBlock`(一个持久的[图片附件](attachment.zh.md))、`ToolCallBlock`(`id: CallId`、`name`、原始 JSON `arguments`),以及 `ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。仅当适配器、UI、压缩(compaction)和持久回放路径均支持某种新模态时,才将其纳入可合并扩展的 map。
|
||||
各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ImageBlock`(一个持久的[图片附件](attachment.zh.md))、`ToolCallBlock`(`id: ToolCallId`、`name`、原始 JSON `arguments`),以及 `ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。仅当适配器、UI、压缩(compaction)和持久回放路径均支持某种新模态时,才将其纳入可合并扩展的 map。
|
||||
|
||||
图片访问方式属于请求序列化,不属于持久附件或确定性请求图片版本。`resolveImageAttachmentAccess()` 把附件提供方可选的宿主对象路径,与消费方为当前工具执行文件系统提供的映射组合起来。结果只适用于本次请求,不参与 `variantId`。
|
||||
|
||||
@@ -205,7 +205,7 @@ type StreamChunk =
|
||||
| { type: 'block-start'; index: number; blockType: ContentBlockType }
|
||||
| { type: 'text-delta'; index: number; text: string }
|
||||
| { type: 'reasoning-delta'; index: number; text: string }
|
||||
| { type: 'tool-call-delta'; index: number; id: CallId; name?: string; argumentsDelta: string }
|
||||
| { type: 'tool-call-delta'; index: number; id: ToolCallId; name?: string; argumentsDelta: string }
|
||||
| { type: 'block-end'; index: number; block: ContentBlock }
|
||||
| { type: 'usage'; usage: TokenUsage }
|
||||
| {
|
||||
|
||||
@@ -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/session.md
|
||||
session.md: ef24802d7ea567681f935fea86efaab2eb2b80c0
|
||||
session.zh.md: bee235657d1c1d1a6009eb23d51b8c88482c0748
|
||||
session.md: 87e8b33dec8eda6f7a716d71f5c4afa69a420d35
|
||||
session.zh.md: d30b7d24412a04521300c657c1b07daa1620ca5f
|
||||
|
||||
@@ -71,7 +71,7 @@ interface SessionEventMap {
|
||||
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
||||
* call with its `tool/result`.
|
||||
*/
|
||||
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
|
||||
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
|
||||
/**
|
||||
* A completed tool call's model-facing result, optional internal failure
|
||||
* identity, and optional tool-private `meta` presentation payload. `meta` is
|
||||
|
||||
@@ -71,7 +71,7 @@ interface SessionEventMap {
|
||||
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
||||
* call with its `tool/result`.
|
||||
*/
|
||||
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
|
||||
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
|
||||
/**
|
||||
* A completed tool call's model-facing result, optional internal failure
|
||||
* identity, and optional tool-private `meta` presentation payload. `meta` is
|
||||
|
||||
@@ -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/spill.md
|
||||
spill.md: 1ea23747ddb42c7b36d71e8125dff059ba985b3c
|
||||
spill.zh.md: a10512f0c6ff26be3dc2637cd8c19606ba4fbdc6
|
||||
spill.md: 366cacbef06e18e79d593e946536b062d8d83d50
|
||||
spill.zh.md: 82e2ad9efe418175642c3523614c2601b17e4450
|
||||
|
||||
@@ -50,7 +50,7 @@ interface SpillSource {
|
||||
/** The tool whose result was spilled (e.g. `web_fetch`). */
|
||||
toolName: string
|
||||
/** The model-issued call id the result belongs to. */
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
/** A short human label for the artifact (e.g. `result`). */
|
||||
label: string
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ interface SpillSource {
|
||||
/** The tool whose result was spilled (e.g. `web_fetch`). */
|
||||
toolName: string
|
||||
/** The model-issued call id the result belongs to. */
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
/** A short human label for the artifact (e.g. `result`). */
|
||||
label: string
|
||||
}
|
||||
|
||||
@@ -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/tools.md
|
||||
tools.md: dd89d40c1401732dd5e6d8dfb5ccb08a89c58405
|
||||
tools.zh.md: 0d3e59bc222527e66dd5bd626203f4e1bb6aad9a
|
||||
tools.md: 5ac2401d662bca52cac529c0620a39930565a7c7
|
||||
tools.zh.md: a081c58a2ba4df50b035a4dae8fa7f9ab7d9de1a
|
||||
|
||||
@@ -183,12 +183,12 @@ type ToolExecutionToken = symbol & { readonly [toolExecutionTokenBrand]: true }
|
||||
* callers do not choose that token.
|
||||
*/
|
||||
interface ToolExecutionInput {
|
||||
readonly callId: CallId
|
||||
readonly callId: ToolCallId
|
||||
/**
|
||||
* Root model-requested call owning this execution tree. Callers omit it for
|
||||
* a root execution; nested dispatchers propagate the enclosing value.
|
||||
*/
|
||||
readonly rootCallId?: CallId
|
||||
readonly rootCallId?: ToolCallId
|
||||
readonly name: string
|
||||
/** Losslessly JSON-serializable parsed arguments (tools validate their own schema). */
|
||||
readonly arguments: unknown
|
||||
@@ -270,7 +270,7 @@ interface CodeDispatchLog {
|
||||
/** The calling agent (the scope routing key and the spill owner), when the outer call has one. */
|
||||
readonly agent?: Agent
|
||||
/** Deterministic sub-call id (`<parent>:code:<n>`). */
|
||||
readonly subCallId: CallId
|
||||
readonly subCallId: ToolCallId
|
||||
/** The dispatched sub-tool name. */
|
||||
readonly name: string
|
||||
/** Whether the sub-call settled as an error. */
|
||||
@@ -290,7 +290,7 @@ interface CodeDispatchLog {
|
||||
*/
|
||||
interface ToolExecution extends ToolExecutionInput {
|
||||
/** Root model-requested call, resolved for every root and nested execution. */
|
||||
readonly rootCallId: CallId
|
||||
readonly rootCallId: ToolCallId
|
||||
/** Registry-assigned identity shared with nested calls only as their opaque `parent` token. */
|
||||
readonly token: ToolExecutionToken
|
||||
}
|
||||
|
||||
@@ -183,12 +183,12 @@ type ToolExecutionToken = symbol & { readonly [toolExecutionTokenBrand]: true }
|
||||
* callers do not choose that token.
|
||||
*/
|
||||
interface ToolExecutionInput {
|
||||
readonly callId: CallId
|
||||
readonly callId: ToolCallId
|
||||
/**
|
||||
* Root model-requested call owning this execution tree. Callers omit it for
|
||||
* a root execution; nested dispatchers propagate the enclosing value.
|
||||
*/
|
||||
readonly rootCallId?: CallId
|
||||
readonly rootCallId?: ToolCallId
|
||||
readonly name: string
|
||||
/** Losslessly JSON-serializable parsed arguments (tools validate their own schema). */
|
||||
readonly arguments: unknown
|
||||
@@ -270,7 +270,7 @@ interface CodeDispatchLog {
|
||||
/** The calling agent (the scope routing key and the spill owner), when the outer call has one. */
|
||||
readonly agent?: Agent
|
||||
/** Deterministic sub-call id (`<parent>:code:<n>`). */
|
||||
readonly subCallId: CallId
|
||||
readonly subCallId: ToolCallId
|
||||
/** The dispatched sub-tool name. */
|
||||
readonly name: string
|
||||
/** Whether the sub-call settled as an error. */
|
||||
@@ -290,7 +290,7 @@ interface CodeDispatchLog {
|
||||
*/
|
||||
interface ToolExecution extends ToolExecutionInput {
|
||||
/** Root model-requested call, resolved for every root and nested execution. */
|
||||
readonly rootCallId: CallId
|
||||
readonly rootCallId: ToolCallId
|
||||
/** Registry-assigned identity shared with nested calls only as their opaque `parent` token. */
|
||||
readonly token: ToolExecutionToken
|
||||
}
|
||||
|
||||
@@ -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/user/develop/practice/llm-adapter.md
|
||||
llm-adapter.md: 882e82d880ac622cf886c6c24e75a1987e1c6702
|
||||
llm-adapter.zh.md: 27c480af2a19a68ac35900a2ce1b5e9dbdc85bf8
|
||||
llm-adapter.md: ff5fc5657e2be0a5fe0d4e10f148865e62f92b18
|
||||
llm-adapter.zh.md: b05bfad6cbc4038f54f816b212068d85e54c2100
|
||||
|
||||
@@ -54,7 +54,7 @@ export function apply(ctx: Context, config: Config) {
|
||||
`stream()` yields chunks using this protocol:
|
||||
|
||||
```ts
|
||||
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
// 1. Start each content block with block-start.
|
||||
@@ -76,7 +76,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
yield {
|
||||
type: 'tool-call-delta',
|
||||
index: 1,
|
||||
id: CallId('call-123'),
|
||||
id: ToolCallId('call-123'),
|
||||
name: 'bash',
|
||||
argumentsDelta: '{"command":"ls"}',
|
||||
}
|
||||
@@ -85,7 +85,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
index: 1,
|
||||
block: {
|
||||
type: 'tool-call',
|
||||
id: CallId('call-123'),
|
||||
id: ToolCallId('call-123'),
|
||||
name: 'bash',
|
||||
arguments: '{"command":"ls"}',
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ export function apply(ctx: Context, config: Config) {
|
||||
`stream()` 必须按以下协议生成分片:
|
||||
|
||||
```ts
|
||||
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
// 1. Start each content block with block-start.
|
||||
@@ -76,7 +76,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
yield {
|
||||
type: 'tool-call-delta',
|
||||
index: 1,
|
||||
id: CallId('call-123'),
|
||||
id: ToolCallId('call-123'),
|
||||
name: 'bash',
|
||||
argumentsDelta: '{"command":"ls"}',
|
||||
}
|
||||
@@ -85,7 +85,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
index: 1,
|
||||
block: {
|
||||
type: 'tool-call',
|
||||
id: CallId('call-123'),
|
||||
id: ToolCallId('call-123'),
|
||||
name: 'bash',
|
||||
arguments: '{"command":"ls"}',
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import ApprovalService, { type ApprovalRequest } from '@deepseek-ai/dsh-user-approval'
|
||||
@@ -22,8 +22,8 @@ describe('ACP machine permission policy', () => {
|
||||
const agent = harness.ctx.agents.get(SessionId(sessionId))!
|
||||
agent.session.append('turn/start', { turn: 1 })
|
||||
agent.session.append('step/start', { turn: 1, step: 1 })
|
||||
agent.session.append('tool/call', { turn: 1, step: 1, callId: CallId('call-9'), name: 'bash', arguments: '{}' })
|
||||
return { agent, toolName: 'bash', callId: CallId('call-9'), ...overrides }
|
||||
agent.session.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('call-9'), name: 'bash', arguments: '{}' })
|
||||
return { agent, toolName: 'bash', callId: ToolCallId('call-9'), ...overrides }
|
||||
}
|
||||
|
||||
it('maps the two advertised one-shot choices', async () => {
|
||||
@@ -71,7 +71,7 @@ describe('ACP machine permission policy', () => {
|
||||
const foreign = {
|
||||
session: { id: request.agent.session.id, events: [{ type: 'turn/start' }], append: () => ({}) },
|
||||
} as unknown as Agent
|
||||
await expect(harness.ctx.approval.request({ agent: foreign, toolName: 'bash', callId: CallId('call') }))
|
||||
await expect(harness.ctx.approval.request({ agent: foreign, toolName: 'bash', callId: ToolCallId('call') }))
|
||||
.resolves.toBe('unavailable')
|
||||
expect(harness.permissionRequests).toHaveLength(0)
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { AttachmentError } from '@deepseek-ai/dsh-attachment'
|
||||
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
import { makeBridgeHarness, textResponse, type BridgeHarness } from './harness.ts'
|
||||
@@ -14,11 +14,11 @@ import { startHttpMcpFixture } from '../../../mcp/mcp-client/tests/http-fixture.
|
||||
function oneToolCall(): StreamChunk[] {
|
||||
return [
|
||||
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
||||
{ type: 'tool-call-delta', index: 0, id: CallId('call-switch'), name: 'switch_model', argumentsDelta: '{}' },
|
||||
{ type: 'tool-call-delta', index: 0, id: ToolCallId('call-switch'), name: 'switch_model', argumentsDelta: '{}' },
|
||||
{
|
||||
type: 'block-end',
|
||||
index: 0,
|
||||
block: { type: 'tool-call', id: CallId('call-switch'), name: 'switch_model', arguments: '{}' },
|
||||
block: { type: 'tool-call', id: ToolCallId('call-switch'), name: 'switch_model', arguments: '{}' },
|
||||
},
|
||||
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk'
|
||||
import { createUserMessage, CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
import { makeBridgeHarness, textResponse, type BridgeHarness } from './harness.ts'
|
||||
@@ -11,8 +11,8 @@ function toolCallResponse(): StreamChunk[] {
|
||||
{ type: 'reasoning-delta', index: 0, text: 'inspect first' },
|
||||
{ type: 'block-end', index: 0, block: { type: 'reasoning', text: 'inspect first' } },
|
||||
{ type: 'block-start', index: 1, blockType: 'tool-call' },
|
||||
{ type: 'tool-call-delta', index: 1, id: CallId('call-1'), name: 'echo', argumentsDelta: '{}' },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('call-1'), name: 'echo', arguments: '{}' } },
|
||||
{ type: 'tool-call-delta', index: 1, id: ToolCallId('call-1'), name: 'echo', argumentsDelta: '{}' },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('call-1'), name: 'echo', arguments: '{}' } },
|
||||
{ type: 'usage', usage: { inputTokens: 8, outputTokens: 2, reasoningTokens: 1 } },
|
||||
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { CallId, MessageId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, MessageId } from '@deepseek-ai/dsh-llm'
|
||||
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { assistantUpdates, toolCallUpdate, toolResultUpdate } from '../src/updates.ts'
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('standard ACP update projection', () => {
|
||||
const session = { requestContext: () => undefined } as unknown as Session
|
||||
const event = assistantEvent([
|
||||
{ type: 'reasoning', text: '' },
|
||||
{ type: 'tool-call', id: CallId('call-hidden'), name: 'hidden', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('call-hidden'), name: 'hidden', arguments: '{}' },
|
||||
])
|
||||
|
||||
await expect(assistantUpdates(ctx, session, event)).resolves.toEqual([])
|
||||
@@ -59,7 +59,7 @@ describe('standard ACP update projection', () => {
|
||||
type: 'tool/call',
|
||||
seq: 0,
|
||||
time: 0,
|
||||
data: { turn: 1, step: 1, callId: CallId('call-bad'), name: 'broken', arguments: '{' },
|
||||
data: { turn: 1, step: 1, callId: ToolCallId('call-bad'), name: 'broken', arguments: '{' },
|
||||
})
|
||||
const result = await toolResultUpdate({ get: () => undefined } as unknown as Context, {
|
||||
type: 'tool/result',
|
||||
@@ -71,10 +71,10 @@ describe('standard ACP update projection', () => {
|
||||
message: {
|
||||
id: MessageId('tool-message'),
|
||||
role: 'user',
|
||||
source: { kind: 'tool', callId: CallId('call-bad') },
|
||||
source: { kind: 'tool', callId: ToolCallId('call-bad') },
|
||||
content: [{
|
||||
type: 'tool-result',
|
||||
toolCallId: CallId('call-bad'),
|
||||
toolCallId: ToolCallId('call-bad'),
|
||||
isError: true,
|
||||
content: [{ type: 'reasoning', text: 'hidden' }],
|
||||
}],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
CallId, createMessage, createToolResultMessage, createUserMessage,
|
||||
ToolCallId, createMessage, createToolResultMessage, createUserMessage,
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
// Minimal SessionEvent builders for orchestration tests (shape mirrors what the
|
||||
// host emits; only the fields the object layer reads).
|
||||
@@ -52,7 +52,7 @@ export const ev = {
|
||||
turn,
|
||||
step,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId(callId),
|
||||
callId: ToolCallId(callId),
|
||||
content: text(body),
|
||||
isError: false,
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Packed history records become one event-shaped Client value per wire record. */
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import type { SessionHistoryRecord } from '../src/types.ts'
|
||||
import {
|
||||
historyEntries,
|
||||
@@ -59,7 +59,7 @@ describe('Session history record projection', () => {
|
||||
turn: 2,
|
||||
step: 4,
|
||||
index: 1,
|
||||
id: CallId('call-1'),
|
||||
id: ToolCallId('call-1'),
|
||||
dt: [2, 3],
|
||||
args: ['', '{"x":', '1}'],
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Context } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import { decodeStorageRecord, type ChunkRow } from '@deepseek-ai/dsh-session/chunk-rows'
|
||||
import { CallId, createMessage, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, createMessage, createToolResultMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type { Session, SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { SessionHistoryController } from '@deepseek-ai/dsh-api-session-controller/src/history.ts'
|
||||
import type {
|
||||
@@ -110,12 +110,12 @@ describe('Session history raw journal', () => {
|
||||
const stream = await openFollow(history, session.id, abort.signal)
|
||||
const collected = collect(stream, 2, abort)
|
||||
const call = session.append('tool/call', {
|
||||
turn: 1, step: 1, callId: CallId('raw-call'), name: 'custom', arguments: '{malformed',
|
||||
turn: 1, step: 1, callId: ToolCallId('raw-call'), name: 'custom', arguments: '{malformed',
|
||||
})
|
||||
const result = session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('raw-call'),
|
||||
callId: ToolCallId('raw-call'),
|
||||
content: [{ type: 'text', text: 'raw output' }],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -140,7 +140,7 @@ describe('Session history raw journal', () => {
|
||||
const iterator = stream[Symbol.asyncIterator]()
|
||||
|
||||
session.append('tool/call', {
|
||||
turn: 1, step: 1, callId: CallId('live-fast'), name: 'term', arguments: '{"cmd":"pwd"}',
|
||||
turn: 1, step: 1, callId: ToolCallId('live-fast'), name: 'term', arguments: '{"cmd":"pwd"}',
|
||||
})
|
||||
await expect(iterator.next()).resolves.toMatchObject({
|
||||
value: { type: 'event', event: { type: 'tool/call', data: { callId: 'live-fast' } } },
|
||||
@@ -153,7 +153,7 @@ describe('Session history raw journal', () => {
|
||||
session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('live-fast'),
|
||||
callId: ToolCallId('live-fast'),
|
||||
content: [{ type: 'text', text: 'ok' }],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -175,12 +175,12 @@ describe('Session history raw journal', () => {
|
||||
const session = ctx.sessions.create(undefined, { meta: { cwd: '/workspace' } })
|
||||
const start = session.append('turn/start', { turn: 1 })
|
||||
const call = session.append('tool/call', {
|
||||
turn: 1, step: 1, callId: CallId('history-call'), name: 'custom', arguments: '{broken',
|
||||
turn: 1, step: 1, callId: ToolCallId('history-call'), name: 'custom', arguments: '{broken',
|
||||
})
|
||||
const result = session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('history-call'),
|
||||
callId: ToolCallId('history-call'),
|
||||
content: [{ type: 'text', text: 'failed raw output' }],
|
||||
isError: true,
|
||||
}),
|
||||
@@ -298,7 +298,7 @@ describe('Session history raw journal', () => {
|
||||
step: 1,
|
||||
chunk: { type: 'reasoning-delta', index: 0, text: `r${String(index)}` },
|
||||
}))
|
||||
const callId = CallId('packed-call')
|
||||
const callId = ToolCallId('packed-call')
|
||||
const toolCall = [0, 1, 2].map(index => session.append('assistant/chunk', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
@@ -358,7 +358,7 @@ describe('Session history raw journal', () => {
|
||||
await expect(iterator.next()).resolves.toMatchObject({
|
||||
value: { type: 'event', event: { type: 'turn/start' } },
|
||||
})
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-late'), name: 'term', arguments: '{"cmd":"tail"}' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c-late'), name: 'term', arguments: '{"cmd":"tail"}' })
|
||||
await expect(iterator.next()).resolves.toMatchObject({
|
||||
value: { type: 'event', event: { type: 'tool/call' } },
|
||||
})
|
||||
@@ -373,7 +373,7 @@ describe('Session history raw journal', () => {
|
||||
const result = session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c-late'),
|
||||
callId: ToolCallId('c-late'),
|
||||
content: [{ type: 'text', text: 'ok' }],
|
||||
isError: false,
|
||||
}),
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
createToolResultMessage,
|
||||
createUserMessage,
|
||||
} from '@deepseek-ai/dsh-llm/message'
|
||||
import { CallId, type MessageId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import { ToolCallId, type MessageId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import type {
|
||||
AssistantMessage,
|
||||
ContentBlock,
|
||||
@@ -341,7 +341,7 @@ function assistantMessage(content: ContentBlock[], model = 'fx-1'): AssistantMes
|
||||
}
|
||||
|
||||
function toolResultMessage(callId: string, content: ContentBlock[], isError: boolean): ToolResultMessage {
|
||||
return createToolResultMessage({ callId: CallId(callId), content, isError })
|
||||
return createToolResultMessage({ callId: ToolCallId(callId), content, isError })
|
||||
}
|
||||
|
||||
const MARKDOWN_FIXTURE = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** Approval composer and optional correlated-detail contracts. */
|
||||
import type { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import type {
|
||||
PropsLocale, PropsRenderSlots, PropsRuntime,
|
||||
@@ -45,7 +45,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
/** Stable identity handed to an optional approval-detail renderer. */
|
||||
export interface ApprovalDetailOwnerProps {
|
||||
/** Tool call correlated with the request. */
|
||||
callId: CallId
|
||||
callId: ToolCallId
|
||||
}
|
||||
|
||||
/** Client-visible fields of an approval request projected through Remote Events. */
|
||||
@@ -53,7 +53,7 @@ export interface ApprovalPresentationRequest {
|
||||
/** Tool requesting the decision. */
|
||||
readonly toolName: string
|
||||
/** Tool call correlated with the request. */
|
||||
readonly callId?: CallId
|
||||
readonly callId?: ToolCallId
|
||||
/** Human-readable reason supplied by the requester. */
|
||||
readonly reason?: string
|
||||
/** Cancellation projected from the Host waterfall. */
|
||||
@@ -74,7 +74,7 @@ export class PendingApproval {
|
||||
/** Tool requesting the decision. */
|
||||
readonly toolName: string
|
||||
/** Correlated Tool call, when supplied by the asker. */
|
||||
readonly callId: CallId | undefined
|
||||
readonly callId: ToolCallId | undefined
|
||||
/** Human-readable reason supplied by the asker. */
|
||||
readonly reason: string | undefined
|
||||
/** Result returned by the Remote Event listener to the Host waterfall. */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createScope, scopeOf } from '@deepseek-ai/dsh-api-session-controller/client'
|
||||
import InvariantRegistry from '@deepseek-ai/dsh-invariants'
|
||||
import type { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -122,7 +122,7 @@ describe('PendingApproval', () => {
|
||||
const remove = vi.spyOn(controller.signal, 'removeEventListener')
|
||||
const pending = new PendingApproval(id('s1'), {
|
||||
toolName: 'bash',
|
||||
callId: 'call-1' as CallId,
|
||||
callId: 'call-1' as ToolCallId,
|
||||
reason: 'needs access',
|
||||
signal: controller.signal,
|
||||
})
|
||||
@@ -343,7 +343,7 @@ describe('ApprovalPanel', () => {
|
||||
it('renders correlated detail and returns allow-once', async () => {
|
||||
const pending = new PendingApproval(id('s1'), {
|
||||
toolName: 'bash',
|
||||
callId: 'call-1' as CallId,
|
||||
callId: 'call-1' as ToolCallId,
|
||||
reason: 'Run this exact command',
|
||||
})
|
||||
const renderSlot = vi.fn(() => <code>pnpm test</code>)
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
import type { MarkdownFileMentions } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
|
||||
import type { createChatStore } from '../stores.ts'
|
||||
import type { CallId, SelectionTarget } from './store.ts'
|
||||
import type { ToolCallId, SelectionTarget } from './store.ts'
|
||||
import type { ChatNode, ChatNodeKind } from './chat-nodes.ts'
|
||||
import type { ChatSnapshot, CommandNode, CompactionSummaryNode, ToolCallBlock } from './snapshot.ts'
|
||||
|
||||
@@ -59,10 +59,10 @@ export interface ChatNodeTurnDataInjected {
|
||||
|
||||
/** Stable owner currency delivered to a keyed Chat renderer. */
|
||||
export interface ChatNodeOwnerProps {
|
||||
selectedCallId?: CallId | undefined
|
||||
selectedCallId?: ToolCallId | undefined
|
||||
cwd?: string | undefined
|
||||
openFile: (path: string) => void
|
||||
inspectCall: (callId: CallId) => void
|
||||
inspectCall: (callId: ToolCallId) => void
|
||||
forkAt: (seq: number) => void
|
||||
renderMessageImages: RenderMessageImages
|
||||
fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/** Chat-owned selection state shared by the transcript and details panel. */
|
||||
|
||||
/** Tool call identity as carried by Chat nodes. */
|
||||
export type CallId = string
|
||||
export type ToolCallId = string
|
||||
|
||||
/** Selection target for the Chat details linkage channel. */
|
||||
export interface SelectionTarget {
|
||||
turnSeq: number
|
||||
stepSeq?: number
|
||||
callId?: CallId
|
||||
callId?: ToolCallId
|
||||
toolName?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export type {
|
||||
FinalAssistantChatData, ManualCompactionChatData, RetryChatData, ToolChatData,
|
||||
TurnTailChatData,
|
||||
} from './contract/chat-nodes.ts'
|
||||
export type { CallId, ChatStoreState, SelectionTarget } from './contract/store.ts'
|
||||
export type { ToolCallId, ChatStoreState, SelectionTarget } from './contract/store.ts'
|
||||
export type {
|
||||
AssistantActionOwnerProps, ChatFileMentions, ChatNodeOwnerProps, ChatNodeTurnDataInjected,
|
||||
ChatNodeViewProps, ChatScrollPosition, ChatStore, ChatViewInjected, ChatViewSlotProps,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
resolveTargetPolicy,
|
||||
} from '@deepseek-ai/dsh-compaction-basic/src/config.ts'
|
||||
import type { CompactionResult } from '@deepseek-ai/dsh-compaction'
|
||||
import LlmRuntime, { createUserMessage, CallId, CONTEXT_WINDOW_EXCEEDED_CODE, createToolResultMessage, LlmAdapter , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, createToolResultMessage, LlmAdapter , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type {
|
||||
ContentBlock,
|
||||
GenerateOptions,
|
||||
@@ -143,7 +143,7 @@ function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session {
|
||||
function toolConversation(): Session {
|
||||
const session = Session.create(SessionId('tools'))
|
||||
for (let turn = 1; turn <= 3; turn += 1) {
|
||||
const callId = CallId(`call-${turn}`)
|
||||
const callId = ToolCallId(`call-${turn}`)
|
||||
session.append('turn/start', { turn })
|
||||
session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: `request ${turn} `.repeat(300) }],
|
||||
@@ -191,7 +191,7 @@ function toolConversation(): Session {
|
||||
/** One closed routed tool step followed by an open turn for rewrite events. */
|
||||
function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Session {
|
||||
const session = Session.create(SessionId(`oversized-tool-${chars}`))
|
||||
const callId = CallId('oversized')
|
||||
const callId = ToolCallId('oversized')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
if (withCompactablePrompt) {
|
||||
session.append('user/message', createUserMessage({
|
||||
@@ -569,7 +569,7 @@ describe('pressure measurement and retention', () => {
|
||||
it('declines forced overflow when the whole surface is one indivisible tool pair', async () => {
|
||||
const compact = service(compactConfig)
|
||||
const session = Session.create(SessionId('single-tool-pair'))
|
||||
const callId = CallId('single-call')
|
||||
const callId = ToolCallId('single-call')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
session.append('request/header', {
|
||||
@@ -735,7 +735,7 @@ describe('pressure measurement and retention', () => {
|
||||
it('declines when rounding a cut would consume the only tool pair', () => {
|
||||
const ctx = createContext()
|
||||
const session = Session.create(SessionId('one-tool-pair'))
|
||||
const callId = CallId('only')
|
||||
const callId = ToolCallId('only')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
session.append('assistant/message', {
|
||||
@@ -1184,7 +1184,7 @@ describe('default one-shot summarizer', () => {
|
||||
const { adapter, compact } = await summarizerHarness([
|
||||
{ type: 'reasoning', text: 'private' },
|
||||
{ type: 'text', text: 'public summary' },
|
||||
{ type: 'tool-call', id: CallId('unexpected'), name: 'x', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('unexpected'), name: 'x', arguments: '{}' },
|
||||
], undefined, MODEL, {
|
||||
auto: false,
|
||||
summarizationProvider: MODEL,
|
||||
@@ -1200,7 +1200,7 @@ describe('default one-shot summarizer', () => {
|
||||
rawOutput: [
|
||||
{ type: 'reasoning', text: 'private' },
|
||||
{ type: 'text', text: 'public summary' },
|
||||
{ type: 'tool-call', id: CallId('unexpected'), name: 'x', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('unexpected'), name: 'x', arguments: '{}' },
|
||||
],
|
||||
llmStreamCall: true,
|
||||
provider: MODEL,
|
||||
@@ -1418,7 +1418,7 @@ describe('default one-shot summarizer', () => {
|
||||
it('rejects image summary output nested in a tool result', async () => {
|
||||
const { compact } = await summarizerHarness([{
|
||||
type: 'tool-result',
|
||||
toolCallId: CallId('summary-tool'),
|
||||
toolCallId: ToolCallId('summary-tool'),
|
||||
content: [{
|
||||
type: 'image',
|
||||
attachment: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Context } from '@deepseek-ai/cordis'
|
||||
import { toolPairingBalancedAfter, toolPairingBalancedBefore } from '@deepseek-ai/dsh-compaction'
|
||||
import { createUserMessage, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, resolveRetryPolicy , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, GenerateOptions, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
@@ -54,7 +54,7 @@ class StepwiseToolAdapter extends LlmAdapter {
|
||||
const n = this.calls
|
||||
this.calls += 1
|
||||
if (n < this.toolSteps) {
|
||||
const id = CallId(`c${n}`)
|
||||
const id = ToolCallId(`c${n}`)
|
||||
const args = `{"i":${n}}`
|
||||
yield { type: 'block-start', index: 0, blockType: 'text' }
|
||||
yield { type: 'block-end', index: 0, block: { type: 'text', text: `step ${n}` } }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/** Character-budget policy for deterministic tool-result pruning. */
|
||||
export interface ToolResultPruneConfig {
|
||||
@@ -24,7 +24,7 @@ export interface PrunedEntry {
|
||||
/** Newly appended pruned tool-result event. */
|
||||
readonly replacementSeq: number
|
||||
/** Tool call shared by the original and replacement. */
|
||||
readonly callId: CallId
|
||||
readonly callId: ToolCallId
|
||||
/** Original text size in Unicode code points. */
|
||||
readonly charsBefore: number
|
||||
/** Replacement text size in Unicode code points. */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, {
|
||||
Session,
|
||||
@@ -43,7 +43,7 @@ function appendToolStep(
|
||||
content: ContentBlock[],
|
||||
extra: Record<string, unknown> = {},
|
||||
): number {
|
||||
const callId = CallId(call)
|
||||
const callId = ToolCallId(call)
|
||||
session.append('turn/start', {
|
||||
turn,
|
||||
})
|
||||
@@ -126,7 +126,7 @@ describe('ToolResultPruner content transform', () => {
|
||||
const reasoning: ContentBlock = { type: 'reasoning', text: 'private-rich-block' }
|
||||
const call: ContentBlock = {
|
||||
type: 'tool-call',
|
||||
id: CallId('nested'),
|
||||
id: ToolCallId('nested'),
|
||||
name: 'nested',
|
||||
arguments: '{}',
|
||||
}
|
||||
@@ -178,7 +178,7 @@ describe('ToolResultPruner session transaction', () => {
|
||||
expect(result.pruned).toHaveLength(1)
|
||||
expect(result.charsRemoved).toBeGreaterThan(0)
|
||||
const entry = result.pruned[0]!
|
||||
expect(entry).toMatchObject({ originalSeq, callId: CallId('one'), charsBefore: 100 })
|
||||
expect(entry).toMatchObject({ originalSeq, callId: ToolCallId('one'), charsBefore: 100 })
|
||||
expect(entry.charsAfter).toBeLessThanOrEqual(50)
|
||||
|
||||
const original = session.events[originalSeq]!
|
||||
@@ -201,7 +201,7 @@ describe('ToolResultPruner session transaction', () => {
|
||||
step: 1,
|
||||
isError: true,
|
||||
message: {
|
||||
source: { kind: 'tool', callId: CallId('one') },
|
||||
source: { kind: 'tool', callId: ToolCallId('one') },
|
||||
},
|
||||
error: { name: 'ExitError', code: 'EXIT_1' },
|
||||
meta: { diff: ['a', 'b'] },
|
||||
@@ -236,7 +236,7 @@ describe('ToolResultPruner session transaction', () => {
|
||||
const prune = service()
|
||||
const first = prune.pruneSession(session)
|
||||
const second = prune.pruneSession(session)
|
||||
expect(first.pruned.map(entry => entry.callId)).toEqual([CallId('a'), CallId('c')])
|
||||
expect(first.pruned.map(entry => entry.callId)).toEqual([ToolCallId('a'), ToolCallId('c')])
|
||||
expect(first.charsRemoved).toBe(
|
||||
first.pruned.reduce((sum, entry) => sum + entry.charsBefore - entry.charsAfter, 0),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { toolPairingBalancedAfter, toolPairingBalancedBefore } from '@deepseek-ai/dsh-compaction'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
@@ -35,7 +35,7 @@ function closedToolStep(): Session {
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'tool-call', id: CallId('c1'), name: 'bash', arguments: '{}' }],
|
||||
content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'bash', arguments: '{}' }],
|
||||
source: {
|
||||
kind: 'model',
|
||||
...{ provider: 'mock', model: 'mock' },
|
||||
@@ -46,7 +46,7 @@ function closedToolStep(): Session {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c1'),
|
||||
callId: ToolCallId('c1'),
|
||||
content: [{ type: 'text', text: 'done' }],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -70,7 +70,7 @@ describe('tool-pairing boundaries', () => {
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'tool-call', id: CallId('open'), name: 'bash', arguments: '{}' }],
|
||||
content: [{ type: 'tool-call', id: ToolCallId('open'), name: 'bash', arguments: '{}' }],
|
||||
source: {
|
||||
kind: 'model',
|
||||
...{ provider: 'mock', model: 'mock' },
|
||||
@@ -88,8 +88,8 @@ describe('tool-pairing boundaries', () => {
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'tool-call', id: CallId('c1'), name: 'one', arguments: '{}' },
|
||||
{ type: 'tool-call', id: CallId('c2'), name: 'two', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('c1'), name: 'one', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('c2'), name: 'two', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -100,7 +100,7 @@ describe('tool-pairing boundaries', () => {
|
||||
session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c1'),
|
||||
callId: ToolCallId('c1'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -108,7 +108,7 @@ describe('tool-pairing boundaries', () => {
|
||||
session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c2'),
|
||||
callId: ToolCallId('c2'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -125,7 +125,7 @@ describe('tool-pairing boundaries', () => {
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'tool-call', id: CallId('c1'), name: 'bash', arguments: '{}' }],
|
||||
content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'bash', arguments: '{}' }],
|
||||
source: {
|
||||
kind: 'model',
|
||||
...{ provider: 'mock', model: 'mock' },
|
||||
@@ -139,7 +139,7 @@ describe('tool-pairing boundaries', () => {
|
||||
midStep.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c1'),
|
||||
callId: ToolCallId('c1'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -217,7 +217,7 @@ describe('tool-pairing cache refresh', () => {
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'tool-call', id: CallId('c1'), name: 'one', arguments: '{}' }],
|
||||
content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'one', arguments: '{}' }],
|
||||
source: {
|
||||
kind: 'model',
|
||||
...{ provider: 'mock', model: 'mock' },
|
||||
@@ -231,7 +231,7 @@ describe('tool-pairing cache refresh', () => {
|
||||
data: {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c1'),
|
||||
callId: ToolCallId('c1'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -297,7 +297,7 @@ describe('tool-pairing cache refresh', () => {
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'tool-call', id: CallId('c2'), name: 'two', arguments: '{}' }],
|
||||
content: [{ type: 'tool-call', id: ToolCallId('c2'), name: 'two', arguments: '{}' }],
|
||||
source: {
|
||||
kind: 'model',
|
||||
...{ provider: 'mock', model: 'mock' },
|
||||
@@ -311,7 +311,7 @@ describe('tool-pairing cache refresh', () => {
|
||||
data: {
|
||||
turn: 2, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c2'),
|
||||
callId: ToolCallId('c2'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -370,7 +370,7 @@ describe('tool-pairing corrupt surfaces', () => {
|
||||
session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('orphan'),
|
||||
callId: ToolCallId('orphan'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -387,7 +387,7 @@ describe('tool-pairing corrupt surfaces', () => {
|
||||
session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('orphan'),
|
||||
callId: ToolCallId('orphan'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import Loader from '@deepseek-ai/cordis-plugin-loader'
|
||||
import * as workspaceContext from '@deepseek-ai/dsh-agent-instructions'
|
||||
import LlmRuntime, { createUserMessage, CallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { Session, SessionId, SESSION_FORMAT_VERSION, type SessionEvent, type UserMessage } from '@deepseek-ai/dsh-session'
|
||||
import AgentRegistry, { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
@@ -1657,7 +1657,7 @@ describe('workspace context request injection', () => {
|
||||
await write(join(root, 'AGENTS.md'), 'updated repo rule')
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-remount'),
|
||||
callId: ToolCallId('read-after-remount'),
|
||||
name: 'read',
|
||||
arguments: { file_path: 'file.txt' },
|
||||
agent,
|
||||
@@ -1854,7 +1854,7 @@ describe('workspace context request injection', () => {
|
||||
await write(join(root, 'AGENTS.md'), 'new root rule with more detail')
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-baseline-change'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
|
||||
callId: ToolCallId('read-after-baseline-change'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -1883,7 +1883,7 @@ describe('workspace context request injection', () => {
|
||||
await rm(join(root, 'AGENTS.md'))
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-baseline-remove'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
|
||||
callId: ToolCallId('read-after-baseline-remove'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -2499,9 +2499,9 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const adapter = new MockAdapter([
|
||||
[
|
||||
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
||||
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('read-before-abort'), name: 'read', arguments: '{"file_path":"pkg/deep/file.txt"}' } },
|
||||
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('read-before-abort'), name: 'read', arguments: '{"file_path":"pkg/deep/file.txt"}' } },
|
||||
{ type: 'block-start', index: 1, blockType: 'tool-call' },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('abort-after-read'), name: 'abort_step', arguments: '{}' } },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('abort-after-read'), name: 'abort_step', arguments: '{}' } },
|
||||
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
||||
] satisfies StreamChunk[],
|
||||
toolCallResponse('read-after-abort', 'read', { file_path: join('pkg', 'deep', 'file.txt') }),
|
||||
@@ -2595,7 +2595,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const reason = new Error('cancel dynamic reconciliation')
|
||||
controller.abort(reason)
|
||||
const exec = stubToolExecution({
|
||||
callId: CallId('cancelled-dynamic-read'),
|
||||
callId: ToolCallId('cancelled-dynamic-read'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'file.txt') },
|
||||
agent: stubAgent(root),
|
||||
@@ -2632,7 +2632,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-nested'),
|
||||
callId: ToolCallId('read-nested'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -2678,7 +2678,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: controller.signal,
|
||||
callId: CallId('read-before-signal-end'),
|
||||
callId: ToolCallId('read-before-signal-end'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'file.txt') },
|
||||
agent,
|
||||
@@ -2711,7 +2711,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-configured-nested-candidate'),
|
||||
callId: ToolCallId('read-configured-nested-candidate'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -2744,7 +2744,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-nested-overlay'),
|
||||
callId: ToolCallId('read-nested-overlay'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -2787,7 +2787,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-nested-overlay-disabled'),
|
||||
callId: ToolCallId('read-nested-overlay-disabled'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -2815,7 +2815,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-nested-1'),
|
||||
callId: ToolCallId('read-nested-1'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -2823,7 +2823,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
const second = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-nested-2'),
|
||||
callId: ToolCallId('read-nested-2'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -2858,12 +2858,12 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
const second = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-with-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-with-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(first.additionalContexts).toBeUndefined()
|
||||
@@ -2895,18 +2895,18 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
fs.entries.set(instructionPath, { type: 'file', content: 'same package rule', version: FsVersion('revision-2') })
|
||||
const afterVersionChange = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
const afterRefresh = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-version-cache-refresh'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-version-cache-refresh'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(afterVersionChange.additionalContexts).toBeUndefined()
|
||||
@@ -2941,11 +2941,11 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const secondAgent = stubAgent(root)
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-from-first-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: firstAgent,
|
||||
callId: ToolCallId('read-from-first-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: firstAgent,
|
||||
})
|
||||
const second = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-from-second-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: secondAgent,
|
||||
callId: ToolCallId('read-from-second-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: secondAgent,
|
||||
})
|
||||
|
||||
expect(first.additionalContexts).toBeUndefined()
|
||||
@@ -2973,13 +2973,13 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
await write(join(root, 'pkg/AGENTS.md'), 'new package rule with more detail')
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -3016,7 +3016,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-both-siblings'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-both-siblings'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content)
|
||||
expect(firstText).toContain('native package rule')
|
||||
@@ -3025,7 +3025,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
await rm(join(root, 'pkg/AGENTS.md'))
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-one-sibling-removed'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-one-sibling-removed'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
// Removing one candidate only removes its own scope; the sibling scope is untouched.
|
||||
@@ -3054,7 +3054,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-nested-dup-siblings'), name: 'read', arguments: { file_path: join('pkg', 'deep', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-nested-dup-siblings'), name: 'read', arguments: { file_path: join('pkg', 'deep', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -3276,7 +3276,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content)
|
||||
expect(firstText).toContain('canonical nested rule')
|
||||
@@ -3285,7 +3285,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
await write(join(root, 'pkg/CLAUDE.md'), 'canonical nested rule')
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
const convergence = await syncedWorkspaceContext(ctx, agent)
|
||||
@@ -3313,14 +3313,14 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
// Only the earlier candidate changes; the sibling stays byte-identical but now duplicates it.
|
||||
await write(join(root, 'pkg/AGENTS.md'), 'secondary nested rule')
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -3351,13 +3351,13 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
await rm(join(root, 'pkg/AGENTS.md'))
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -3391,7 +3391,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content)
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
@@ -3405,7 +3405,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
await symlink(join(root, 'pkg/elsewhere'), join(root, 'pkg/AGENTS.md'))
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -3431,20 +3431,20 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
await rm(join(root, 'pkg/AGENTS.md'))
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-to-create-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-to-create-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
await write(join(root, 'pkg/AGENTS.md'), 'restored package rule')
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-after-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
|
||||
@@ -3476,13 +3476,13 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-before-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
fs.throwOnStat.add(join(root, 'pkg/AGENTS.md'))
|
||||
const duringFailure = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-during-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-during-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
expect(first.additionalContexts).toBeUndefined()
|
||||
@@ -3506,7 +3506,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const agent = stubAgent(root)
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-resume'),
|
||||
callId: ToolCallId('read-before-resume'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3516,7 +3516,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const afterResume = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-resume'),
|
||||
callId: ToolCallId('read-after-resume'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent: resumed,
|
||||
@@ -3542,7 +3542,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const original = stubAgent(root)
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-offline-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: original,
|
||||
callId: ToolCallId('read-before-offline-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: original,
|
||||
})
|
||||
await appendAdditionalContexts(ctx, original)
|
||||
await write(join(root, 'pkg/AGENTS.md'), 'new nested rule after resume')
|
||||
@@ -3573,7 +3573,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const agent = stubAgent(root)
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-before-compact'),
|
||||
callId: ToolCallId('read-before-compact'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3581,7 +3581,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const contextSeq = (await appendAdditionalContexts(ctx, agent))!
|
||||
const visibleBeforeCompact = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-while-visible'),
|
||||
callId: ToolCallId('read-while-visible'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3597,7 +3597,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const afterCompact = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-compact'),
|
||||
callId: ToolCallId('read-after-compact'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3629,7 +3629,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const whileVisible = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-visible-baseline'),
|
||||
callId: ToolCallId('read-visible-baseline'),
|
||||
name: 'read',
|
||||
arguments: { file_path: 'file.txt' },
|
||||
agent,
|
||||
@@ -3644,7 +3644,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-compacted-baseline'),
|
||||
callId: ToolCallId('read-compacted-baseline'),
|
||||
name: 'read',
|
||||
arguments: { file_path: 'file.txt' },
|
||||
agent,
|
||||
@@ -3653,7 +3653,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
await appendAdditionalContexts(ctx, agent)
|
||||
const afterRearm = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-rearmed-baseline'),
|
||||
callId: ToolCallId('read-rearmed-baseline'),
|
||||
name: 'read',
|
||||
arguments: { file_path: 'file.txt' },
|
||||
agent,
|
||||
@@ -3685,7 +3685,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const agent = stubAgent(root)
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-package'),
|
||||
callId: ToolCallId('read-package'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'file.txt') },
|
||||
agent,
|
||||
@@ -3695,7 +3695,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-subtree'),
|
||||
callId: ToolCallId('read-subtree'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'sub', 'file.txt') },
|
||||
agent,
|
||||
@@ -3723,7 +3723,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
const agent = stubAgent(root)
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-subtree-omitting-parent'),
|
||||
callId: ToolCallId('read-subtree-omitting-parent'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'sub', 'file.txt') },
|
||||
agent,
|
||||
@@ -3733,7 +3733,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-parent-after-omit'),
|
||||
callId: ToolCallId('read-parent-after-omit'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'other.txt') },
|
||||
agent,
|
||||
@@ -3785,7 +3785,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-spoofed-state'),
|
||||
callId: ToolCallId('read-after-spoofed-state'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3812,14 +3812,14 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const rootResult = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-root-file'),
|
||||
callId: ToolCallId('read-root-file'),
|
||||
name: 'read',
|
||||
arguments: { file_path: 'root.txt' },
|
||||
agent,
|
||||
})
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-absolute-nested-file'),
|
||||
callId: ToolCallId('read-absolute-nested-file'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join(root, 'pkg/deep/file.txt') },
|
||||
agent,
|
||||
@@ -3855,7 +3855,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-with-unreadable-nested-instruction'),
|
||||
callId: ToolCallId('read-with-unreadable-nested-instruction'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3900,7 +3900,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-with-downstream'),
|
||||
callId: ToolCallId('read-with-downstream'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3953,7 +3953,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-blocked-downstream'),
|
||||
callId: ToolCallId('read-blocked-downstream'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -3996,7 +3996,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const blocked = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('outer-block-first'),
|
||||
callId: ToolCallId('outer-block-first'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -4007,7 +4007,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
shouldBlock = false
|
||||
const accepted = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('outer-block-retry'),
|
||||
callId: ToolCallId('outer-block-retry'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -4043,7 +4043,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
async execute(_args, exec) {
|
||||
const nested = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId(`${exec.callId}:nested`),
|
||||
callId: ToolCallId(`${exec.callId}:nested`),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
...exec.agent === undefined ? {} : { agent: exec.agent },
|
||||
@@ -4065,7 +4065,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const blocked = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('composite-first'), name: 'composite-read', arguments: {}, agent,
|
||||
callId: ToolCallId('composite-first'), name: 'composite-read', arguments: {}, agent,
|
||||
})
|
||||
|
||||
expect(blocked.isError).toBe(true)
|
||||
@@ -4099,7 +4099,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
token: Symbol('nested-read') as ToolExecutionToken,
|
||||
parent: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('nested-read'),
|
||||
callId: ToolCallId('nested-read'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'file.txt') },
|
||||
agent,
|
||||
@@ -4108,7 +4108,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
token: Symbol('nested-non-file') as ToolExecutionToken,
|
||||
parent: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('nested-non-file'),
|
||||
callId: ToolCallId('nested-non-file'),
|
||||
name: 'search',
|
||||
arguments: {},
|
||||
agent,
|
||||
@@ -4117,7 +4117,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
token: Symbol('second-nested-read') as ToolExecutionToken,
|
||||
parent: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('second-nested-read'),
|
||||
callId: ToolCallId('second-nested-read'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'second.txt') },
|
||||
agent,
|
||||
@@ -4125,7 +4125,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
token: outerToken,
|
||||
signal: testToolSignal,
|
||||
callId: CallId('outer-code-run'),
|
||||
callId: ToolCallId('outer-code-run'),
|
||||
name: 'run_code',
|
||||
arguments: {},
|
||||
agent,
|
||||
@@ -4163,7 +4163,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-closed-step'),
|
||||
callId: ToolCallId('read-after-closed-step'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'file.txt') },
|
||||
agent,
|
||||
@@ -4185,38 +4185,38 @@ describe('dynamic nested workspace context injection', () => {
|
||||
await ctx.plugin(workspaceContext, { maxBytes: 65536 })
|
||||
const fs = ctx.fs as RecordingFileSystem
|
||||
const agent = stubAgent('/')
|
||||
const plainResult = { callId: CallId('plain'), content: [], isError: false as const, value: null }
|
||||
const plainResult = { callId: ToolCallId('plain'), content: [], isError: false as const, value: null }
|
||||
const aborted = new AbortController()
|
||||
aborted.abort(new Error('cancelled'))
|
||||
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal, callId: CallId('agentless'), name: 'read', arguments: { file_path: 'file.txt' },
|
||||
signal: testToolSignal, callId: ToolCallId('agentless'), name: 'read', arguments: { file_path: 'file.txt' },
|
||||
}), plainResult)
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal, callId: CallId('failed'), name: 'read', arguments: { file_path: 'failed/file.txt' }, agent,
|
||||
signal: testToolSignal, callId: ToolCallId('failed'), name: 'read', arguments: { file_path: 'failed/file.txt' }, agent,
|
||||
}), { content: [], isError: true, error: { message: 'failed' } })
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: aborted.signal, callId: CallId('aborted'), name: 'read', arguments: { file_path: 'aborted/file.txt' }, agent,
|
||||
signal: aborted.signal, callId: ToolCallId('aborted'), name: 'read', arguments: { file_path: 'aborted/file.txt' }, agent,
|
||||
}), plainResult)
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('null-arguments'), name: 'read', arguments: null, agent,
|
||||
callId: ToolCallId('null-arguments'), name: 'read', arguments: null, agent,
|
||||
}), plainResult)
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('missing-path'), name: 'read', arguments: {}, agent,
|
||||
callId: ToolCallId('missing-path'), name: 'read', arguments: {}, agent,
|
||||
}), plainResult)
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('non-string-path'), name: 'read', arguments: { file_path: 1 }, agent,
|
||||
callId: ToolCallId('non-string-path'), name: 'read', arguments: { file_path: 1 }, agent,
|
||||
}), plainResult)
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('blank-path'), name: 'read', arguments: { file_path: ' ' }, agent,
|
||||
callId: ToolCallId('blank-path'), name: 'read', arguments: { file_path: ' ' }, agent,
|
||||
}), plainResult)
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('non-fs'), name: 'composite', arguments: {}, agent,
|
||||
callId: ToolCallId('non-fs'), name: 'composite', arguments: {}, agent,
|
||||
}), plainResult)
|
||||
|
||||
await Promise.resolve()
|
||||
@@ -4243,7 +4243,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('projection-failure'),
|
||||
callId: ToolCallId('projection-failure'),
|
||||
name: 'read',
|
||||
arguments: { file_path: 'file.txt' },
|
||||
agent,
|
||||
@@ -4269,7 +4269,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-with-disabled-budget'),
|
||||
callId: ToolCallId('read-with-disabled-budget'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent: stubAgent(root),
|
||||
@@ -4302,12 +4302,12 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-tiny-budget-1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-tiny-budget-1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
const second = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-tiny-budget-2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('read-tiny-budget-2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
|
||||
@@ -4335,7 +4335,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-missing'),
|
||||
callId: ToolCallId('read-missing'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'missing.txt') },
|
||||
agent: stubAgent(root),
|
||||
@@ -4363,7 +4363,7 @@ describe('dynamic nested workspace context injection', () => {
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('read-after-dispose'),
|
||||
callId: ToolCallId('read-after-dispose'),
|
||||
name: 'read',
|
||||
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
|
||||
agent,
|
||||
@@ -4423,7 +4423,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
const agent = stubAgent(root)
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('tiny-budget-touch'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('tiny-budget-touch'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
}), acceptedResult)
|
||||
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
@@ -4451,7 +4451,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
const agent = stubAgent(root)
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('pending-v1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('pending-v1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
expect(blocksText(agent.inbox.nextStep[0]?.content)).toContain('pending version one')
|
||||
@@ -4464,7 +4464,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
await write(join(root, 'pkg/AGENTS.md'), 'pending version two with more detail')
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('pending-v2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('pending-v2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
expect(agent.inbox.nextStep).toHaveLength(1)
|
||||
@@ -4475,7 +4475,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
await rm(join(root, 'pkg/AGENTS.md'))
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('pending-delete'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
callId: ToolCallId('pending-delete'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
expect(agent.inbox.nextStep).toEqual([])
|
||||
@@ -4499,7 +4499,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
const agent = stubAgent(root)
|
||||
const first = stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('projected-before-abort'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
|
||||
callId: ToolCallId('projected-before-abort'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
|
||||
})
|
||||
ctx.emit('tools/result', first, acceptedResult)
|
||||
const controller = new AbortController()
|
||||
@@ -4512,7 +4512,7 @@ describe('workspace context inbox synchronization', () => {
|
||||
|
||||
ctx.emit('tools/result', stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('projected-after-abort'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
|
||||
callId: ToolCallId('projected-after-abort'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
|
||||
}), acceptedResult)
|
||||
await syncWorkspaceContext(ctx, agent)
|
||||
const text = blocksText(agent.inbox.nextStep[0]?.content)
|
||||
@@ -4539,11 +4539,11 @@ describe('workspace context inbox synchronization', () => {
|
||||
const agent = stubAgent(root)
|
||||
const first = stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('concurrent-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
|
||||
callId: ToolCallId('concurrent-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
|
||||
})
|
||||
const second = stubToolExecution({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('concurrent-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
|
||||
callId: ToolCallId('concurrent-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
|
||||
})
|
||||
|
||||
ctx.emit('tools/result', first, acceptedResult)
|
||||
@@ -4577,14 +4577,14 @@ describe('workspace context inbox synchronization', () => {
|
||||
const original = stubAgent(root)
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('recover-pending-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent: original,
|
||||
callId: ToolCallId('recover-pending-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent: original,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, original)
|
||||
const resumed = stubAgent(root, [...original.session.events])
|
||||
|
||||
await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('recover-pending-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent: resumed,
|
||||
callId: ToolCallId('recover-pending-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent: resumed,
|
||||
})
|
||||
await syncWorkspaceContext(ctx, resumed)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { CompactionId, compactCheckpointSource } from '@deepseek-ai/dsh-compaction'
|
||||
import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SessionQueryEngine from '@deepseek-ai/dsh-session-query'
|
||||
import SessionReferenceResolver, {
|
||||
@@ -122,7 +122,7 @@ function appendConversation(session: Session): void {
|
||||
{
|
||||
turn: 2, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('call'),
|
||||
callId: ToolCallId('call'),
|
||||
content: [{ type: 'text', text: 'tool output' }],
|
||||
isError: false,
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import Loader from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { createUserMessage, CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { Session, SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import AgentRegistry, { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
@@ -113,7 +113,7 @@ function toolCallResponse(): StreamChunk[] {
|
||||
{
|
||||
type: 'block-end',
|
||||
index: 0,
|
||||
block: { type: 'tool-call', id: CallId('tick-1'), name: 'tick', arguments: '{}' },
|
||||
block: { type: 'tool-call', id: ToolCallId('tick-1'), name: 'tick', arguments: '{}' },
|
||||
},
|
||||
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import { Context, type Fiber } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import LlmRuntime, { createUserMessage, CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
@@ -310,7 +310,7 @@ describe('AgentLoop initiator scope', () => {
|
||||
|
||||
const direct = await ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('direct'),
|
||||
callId: ToolCallId('direct'),
|
||||
name: 'agentless-probe',
|
||||
arguments: {},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CallId, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
/**
|
||||
* Tests for the queue-aware `Agent.cancel()` primitive. The default clears
|
||||
* queued and steering work, while `keepInbox` preserves pending input for a
|
||||
@@ -543,7 +543,7 @@ describe('Agent.cancel()', () => {
|
||||
{ type: 'text-delta', index: 0, text: 'reading the file' },
|
||||
{ type: 'block-end', index: 0, block: { type: 'text', text: 'reading the file' } },
|
||||
{ type: 'block-start', index: 1, blockType: 'tool-call' },
|
||||
{ type: 'tool-call-delta', index: 1, id: CallId('c1'), name: 'read', argumentsDelta: '{"pa' },
|
||||
{ type: 'tool-call-delta', index: 1, id: ToolCallId('c1'), name: 'read', argumentsDelta: '{"pa' },
|
||||
],
|
||||
}])
|
||||
const ctx = await harness(adapter)
|
||||
@@ -620,7 +620,7 @@ describe('Agent.cancel()', () => {
|
||||
const adapter = new MockAdapter([{
|
||||
hangAfter: [
|
||||
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
||||
{ type: 'tool-call-delta', index: 0, id: CallId('c1'), name: 'read', argumentsDelta: '{"pa' },
|
||||
{ type: 'tool-call-delta', index: 0, id: ToolCallId('c1'), name: 'read', argumentsDelta: '{"pa' },
|
||||
],
|
||||
}])
|
||||
const ctx = await harness(adapter)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import LlmRuntime, { createUserMessage, CallId, LlmError, MessageSource, ProviderRequestId, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, LlmError, MessageSource, ProviderRequestId, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { Session, SessionEvent, SessionId, TurnEndReason, type UserMessage } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRuntime, { defineContentToolFixture, type PostToolDecision } from '@deepseek-ai/dsh-tools'
|
||||
@@ -135,9 +135,9 @@ describe('abort during tool execution ends the turn', () => {
|
||||
it('records post-tool context when a later call aborts the batch', async () => {
|
||||
const adapter = new MockAdapter([[
|
||||
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
||||
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('c1'), name: 'first', arguments: '{}' } },
|
||||
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'first', arguments: '{}' } },
|
||||
{ type: 'block-start', index: 1, blockType: 'tool-call' },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c2'), name: 'aborter', arguments: '{}' } },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('c2'), name: 'aborter', arguments: '{}' } },
|
||||
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
||||
] satisfies StreamChunk[]])
|
||||
const ctx = await harness(adapter)
|
||||
@@ -160,7 +160,7 @@ describe('abort during tool execution ends the turn', () => {
|
||||
},
|
||||
}))
|
||||
ctx.on('tools/post-execute', async (exec, _result, next): Promise<PostToolDecision> => {
|
||||
if (exec.callId !== CallId('c1')) return next()
|
||||
if (exec.callId !== ToolCallId('c1')) return next()
|
||||
return {
|
||||
kind: 'accept',
|
||||
additionalContexts: [createUserMessage({
|
||||
@@ -259,9 +259,9 @@ describe('abort during tool execution ends the turn', () => {
|
||||
const adapter = new MockAdapter([
|
||||
[
|
||||
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
||||
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('c1'), name: 'aborter', arguments: '{}' } },
|
||||
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'aborter', arguments: '{}' } },
|
||||
{ type: 'block-start', index: 1, blockType: 'tool-call' },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c2'), name: 'second', arguments: '{}' } },
|
||||
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('c2'), name: 'second', arguments: '{}' } },
|
||||
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
||||
] satisfies StreamChunk[],
|
||||
textResponse('later turn'),
|
||||
@@ -553,7 +553,7 @@ describe('discriminated SessionEvent narrows without casts', () => {
|
||||
it('narrows event.data from event.type', () => {
|
||||
const session = Session.create(SessionId('s'))
|
||||
const appended: SessionEvent = session.append('tool/call', {
|
||||
turn: 1, step: 1, callId: CallId('c1'), name: 'echo', arguments: '{}',
|
||||
turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}',
|
||||
})
|
||||
// compile-time: this switch narrows; runtime: values flow through
|
||||
switch (appended.type) {
|
||||
@@ -1069,7 +1069,7 @@ describe('tool result call identity', () => {
|
||||
// The loop must still record the tool/result under the model's authoritative
|
||||
// call.id, which is the immutable identity carried by the execution input.
|
||||
ctx.on('tools/post-execute', (exec, _result) => {
|
||||
expect(exec.callId).toBe(CallId('c1')) // the loop passed the real id in
|
||||
expect(exec.callId).toBe(ToolCallId('c1')) // the loop passed the real id in
|
||||
return Promise.resolve({ kind: 'accept', content: [{ type: 'text', text: 'ok' }] })
|
||||
}, { prepend: true })
|
||||
|
||||
@@ -1081,7 +1081,7 @@ describe('tool result call identity', () => {
|
||||
const resultEvent = [...agent.session.events].find(e => e.type === 'tool/result')
|
||||
expect(resultEvent?.type).toBe('tool/result')
|
||||
if (resultEvent?.type === 'tool/result') {
|
||||
expect(resultEvent.data.message.source.callId).toBe(CallId('c1'))
|
||||
expect(resultEvent.data.message.source.callId).toBe(ToolCallId('c1'))
|
||||
}
|
||||
|
||||
// And deriveMessages pairs the tool-result with the assistant tool-call:
|
||||
@@ -1092,7 +1092,7 @@ describe('tool result call identity', () => {
|
||||
.find(b => b.type === 'tool-result')
|
||||
expect(toolResultBlock?.type).toBe('tool-result')
|
||||
if (toolResultBlock?.type === 'tool-result') {
|
||||
expect(toolResultBlock.toolCallId).toBe(CallId('c1'))
|
||||
expect(toolResultBlock.toolCallId).toBe(ToolCallId('c1'))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import LlmRuntime, { createUserMessage, CallId, LlmError, StreamChunk, errorChain } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, LlmError, StreamChunk, errorChain } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId, TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
@@ -47,7 +47,7 @@ describe('tool JSON parse', () => {
|
||||
// model emits tool-call with malformed arguments (not valid JSON)
|
||||
[
|
||||
{ type: 'block-start' as const, index: 0, blockType: 'tool-call' as const },
|
||||
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: CallId('c1'), name: 'echo', arguments: 'not json' } },
|
||||
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: ToolCallId('c1'), name: 'echo', arguments: 'not json' } },
|
||||
{ type: 'finish' as const, reason: { kind: 'tool-calls' as const } },
|
||||
] satisfies StreamChunk[],
|
||||
textResponse('done'),
|
||||
@@ -80,7 +80,7 @@ describe('tool JSON parse', () => {
|
||||
const adapter = new MockAdapter([
|
||||
[
|
||||
{ type: 'block-start' as const, index: 0, blockType: 'tool-call' as const },
|
||||
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: CallId('c1'), name: 'noarg', arguments: '' } },
|
||||
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: ToolCallId('c1'), name: 'noarg', arguments: '' } },
|
||||
{ type: 'finish' as const, reason: { kind: 'tool-calls' as const } },
|
||||
] satisfies StreamChunk[],
|
||||
textResponse('done'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import LlmRuntime, { createUserMessage, CallId } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, {
|
||||
SessionId,
|
||||
type SessionEvent,
|
||||
@@ -613,9 +613,9 @@ describe('tool additionalContexts buffering across a step', () => {
|
||||
// One assistant step with TWO tool calls; the second model response stops.
|
||||
const twoCalls = [
|
||||
{ type: 'block-start' as const, index: 0, blockType: 'tool-call' as const },
|
||||
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: CallId('c1'), name: 'echo', arguments: '{"text":"a"}' } },
|
||||
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: ToolCallId('c1'), name: 'echo', arguments: '{"text":"a"}' } },
|
||||
{ type: 'block-start' as const, index: 1, blockType: 'tool-call' as const },
|
||||
{ type: 'block-end' as const, index: 1, block: { type: 'tool-call' as const, id: CallId('c2'), name: 'echo', arguments: '{"text":"b"}' } },
|
||||
{ type: 'block-end' as const, index: 1, block: { type: 'tool-call' as const, id: ToolCallId('c2'), name: 'echo', arguments: '{"text":"b"}' } },
|
||||
{ type: 'usage' as const, usage: { inputTokens: 5, outputTokens: 5 } },
|
||||
{ type: 'finish' as const, reason: { kind: 'tool-calls' as const } },
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import LlmRuntime, { createUserMessage, CallId, LlmError, ReasoningEffortId, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import LlmRuntime, { createUserMessage, ToolCallId, LlmError, ReasoningEffortId, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId, TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRuntime, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
@@ -1081,7 +1081,7 @@ describe('agent loop', () => {
|
||||
})
|
||||
|
||||
it('does not dispatch tool calls from a max-tokens-truncated step', async () => {
|
||||
const callId = CallId('c1')
|
||||
const callId = ToolCallId('c1')
|
||||
const adapter = new MockAdapter([[
|
||||
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
||||
{ type: 'tool-call-delta', index: 0, id: callId, name: 'echo', argumentsDelta: '{"text":"x"}' },
|
||||
@@ -1136,7 +1136,7 @@ describe('agent loop', () => {
|
||||
it('appends an empty completion anchor for a max-tokens step with no usage', async () => {
|
||||
// The truncated tool call is dropped from durable content, while the
|
||||
// successful provider call still needs an exact replay anchor.
|
||||
const callId = CallId('c1')
|
||||
const callId = ToolCallId('c1')
|
||||
const adapter = new MockAdapter([[
|
||||
{ type: 'block-start', index: 0, blockType: 'tool-call' },
|
||||
{ type: 'tool-call-delta', index: 0, id: callId, name: 'echo', argumentsDelta: '{"text":"x"}' },
|
||||
@@ -1214,7 +1214,7 @@ describe('agent loop', () => {
|
||||
})
|
||||
|
||||
it('keeps safe max-tokens assistant content while dropping truncated tool calls', async () => {
|
||||
const callId = CallId('c1')
|
||||
const callId = ToolCallId('c1')
|
||||
const adapter = new MockAdapter([[
|
||||
{ type: 'block-start', index: 0, blockType: 'text' },
|
||||
{ type: 'text-delta', index: 0, text: 'partial text' },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GenerateOptions, LlmModelReasoningInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/** Helpers to write scripted responses tersely. */
|
||||
export function textResponse(text: string): StreamChunk[] {
|
||||
@@ -28,7 +28,7 @@ export function maxTokensResponse(text: string): StreamChunk[] {
|
||||
}
|
||||
|
||||
export function toolCallResponse(rawCallId: string, name: string, args: object, text?: string): StreamChunk[] {
|
||||
const callId = CallId(rawCallId)
|
||||
const callId = ToolCallId(rawCallId)
|
||||
const argumentsJson = JSON.stringify(args)
|
||||
const chunks: StreamChunk[] = []
|
||||
let index = 0
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createUserMessage, CallId, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import LlmRuntime from '@deepseek-ai/dsh-llm'
|
||||
@@ -49,7 +49,7 @@ function multiCall(calls: { id: string; name: string; args: object }[]): StreamC
|
||||
calls.forEach((call, index) => {
|
||||
chunks.push(
|
||||
{ type: 'block-start', index, blockType: 'tool-call' },
|
||||
{ type: 'block-end', index, block: { type: 'tool-call', id: CallId(call.id), name: call.name, arguments: JSON.stringify(call.args) } },
|
||||
{ type: 'block-end', index, block: { type: 'tool-call', id: ToolCallId(call.id), name: call.name, arguments: JSON.stringify(call.args) } },
|
||||
)
|
||||
})
|
||||
chunks.push(
|
||||
@@ -196,7 +196,7 @@ describe('tool-call scheduler: grouping and barriers', () => {
|
||||
const replacement = gatedExclusiveTool('x')
|
||||
const disposeInitial = ctx.tools.register(initial.tool)
|
||||
ctx.on('tools/result', (exec) => {
|
||||
if (exec.callId !== CallId('c1')) return
|
||||
if (exec.callId !== ToolCallId('c1')) return
|
||||
disposeInitial()
|
||||
ctx.tools.register(replacement.tool)
|
||||
})
|
||||
@@ -206,7 +206,7 @@ describe('tool-call scheduler: grouping and barriers', () => {
|
||||
await until(() => initial.started.length === 2)
|
||||
initial.release('1')
|
||||
await until(() => events(agent).some(event =>
|
||||
event.type === 'tool/result' && event.data.message.source.callId === CallId('c1')))
|
||||
event.type === 'tool/result' && event.data.message.source.callId === ToolCallId('c1')))
|
||||
await new Promise(r => setTimeout(r, 5))
|
||||
expect(replacement.started).toEqual([])
|
||||
initial.release('2')
|
||||
@@ -238,7 +238,7 @@ describe('tool-call scheduler: model-order results despite out-of-order settleme
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const results = events(agent).filter(e => e.type === 'tool/result')
|
||||
expect(results.map(e => e.data.message.source.callId)).toEqual([CallId('c1'), CallId('c2')])
|
||||
expect(results.map(e => e.data.message.source.callId)).toEqual([ToolCallId('c1'), ToolCallId('c2')])
|
||||
})
|
||||
|
||||
it('derived history pairs calls in model order regardless of tool/call log interleaving', async () => {
|
||||
@@ -257,7 +257,7 @@ describe('tool-call scheduler: model-order results despite out-of-order settleme
|
||||
|
||||
const messages = agent.session.deriveMessages()
|
||||
const toolResults = messages.flatMap(m => m.content.filter(b => b.type === 'tool-result'))
|
||||
expect(toolResults.map(b => b.toolCallId)).toEqual([CallId('c1'), CallId('c2')])
|
||||
expect(toolResults.map(b => b.toolCallId)).toEqual([ToolCallId('c1'), ToolCallId('c2')])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -316,7 +316,7 @@ describe('tool-call scheduler: rolling pool honors maxParallelToolCalls', () =>
|
||||
gated.release('4')
|
||||
await waitForIdle(ctx, agent)
|
||||
expect(events(agent).filter(e => e.type === 'tool/result').map(e => e.data.message.source.callId))
|
||||
.toEqual([CallId('c1'), CallId('c2'), CallId('c3'), CallId('c4')])
|
||||
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3'), ToolCallId('c4')])
|
||||
})
|
||||
|
||||
it('maxParallelToolCalls: 1 is fully serial (no second start before the first settles)', async () => {
|
||||
@@ -386,8 +386,8 @@ describe('tool-call scheduler: ordered middleware and additional contexts', () =
|
||||
gated.release('3'); gated.release('2'); gated.release('1')
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(pre).toEqual([CallId('c1'), CallId('c2'), CallId('c3')].map(String))
|
||||
expect(post).toEqual([CallId('c1'), CallId('c2'), CallId('c3')].map(String))
|
||||
expect(pre).toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')].map(String))
|
||||
expect(post).toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')].map(String))
|
||||
})
|
||||
|
||||
it('injects additional contexts in model call order, not settlement order', async () => {
|
||||
@@ -432,8 +432,8 @@ describe('tool-call scheduler: ordered middleware and additional contexts', () =
|
||||
ctx.tools.register(gated.tool)
|
||||
const post: string[] = []
|
||||
ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {
|
||||
if (exec.callId === CallId('c2')) return { kind: 'deny', reason: 'blocked by policy' }
|
||||
if (exec.callId === CallId('c3')) throw new Error('pre exploded')
|
||||
if (exec.callId === ToolCallId('c2')) return { kind: 'deny', reason: 'blocked by policy' }
|
||||
if (exec.callId === ToolCallId('c3')) throw new Error('pre exploded')
|
||||
return next()
|
||||
})
|
||||
ctx.on('tools/post-execute', async (exec, _result, next): Promise<PostToolDecision> => {
|
||||
@@ -450,7 +450,7 @@ describe('tool-call scheduler: ordered middleware and additional contexts', () =
|
||||
expect(gated.started).toEqual(['1'])
|
||||
expect(post).toEqual(['c1', 'c2'])
|
||||
const results = events(agent).filter(e => e.type === 'tool/result')
|
||||
expect(results.map(e => e.data.message.source.callId)).toEqual([CallId('c1'), CallId('c2'), CallId('c3')])
|
||||
expect(results.map(e => e.data.message.source.callId)).toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')])
|
||||
expect((results[1]!.data.message.content[0].content[0] as { text: string }).text).toContain('blocked by policy')
|
||||
expect((results[2]!.data.message.content[0].content[0] as { text: string }).text).toContain('pre exploded')
|
||||
})
|
||||
@@ -477,14 +477,14 @@ describe('tool-call scheduler: abort handling', () => {
|
||||
|
||||
expect(gated.started).toEqual([])
|
||||
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
|
||||
.toEqual([CallId('c1'), CallId('c2')])
|
||||
.toEqual([ToolCallId('c1'), ToolCallId('c2')])
|
||||
expect(events(agent).filter(e => e.type === 'tool/result').map(e => ({
|
||||
callId: e.data.message.source.callId,
|
||||
isError: e.data.message.content[0].isError,
|
||||
error: e.data.error,
|
||||
}))).toEqual([
|
||||
{ callId: CallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
{ callId: CallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
{ callId: ToolCallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
{ callId: ToolCallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
])
|
||||
})
|
||||
|
||||
@@ -498,7 +498,7 @@ describe('tool-call scheduler: abort handling', () => {
|
||||
ctx.tools.register(gated.tool)
|
||||
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
|
||||
ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {
|
||||
if (exec.callId === CallId('c1')) {
|
||||
if (exec.callId === ToolCallId('c1')) {
|
||||
agent.cancel({ kind: 'user' })
|
||||
}
|
||||
return next()
|
||||
@@ -509,14 +509,14 @@ describe('tool-call scheduler: abort handling', () => {
|
||||
|
||||
expect(gated.started).toEqual([])
|
||||
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
|
||||
.toEqual([CallId('c1'), CallId('c2')])
|
||||
.toEqual([ToolCallId('c1'), ToolCallId('c2')])
|
||||
expect(events(agent).filter(e => e.type === 'tool/result').map(e => ({
|
||||
callId: e.data.message.source.callId,
|
||||
isError: e.data.message.content[0].isError,
|
||||
error: e.data.error,
|
||||
}))).toEqual([
|
||||
{ callId: CallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
{ callId: CallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
{ callId: ToolCallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
{ callId: ToolCallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
|
||||
])
|
||||
})
|
||||
|
||||
@@ -545,9 +545,9 @@ describe('tool-call scheduler: abort handling', () => {
|
||||
|
||||
expect(gated.started).toEqual(['1', '2'])
|
||||
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
|
||||
.toEqual([CallId('c1'), CallId('c2'), CallId('c3'), CallId('c4')])
|
||||
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3'), ToolCallId('c4')])
|
||||
expect(events(agent).filter(e => e.type === 'tool/result').map(e => e.data.message.source.callId))
|
||||
.toEqual([CallId('c1'), CallId('c2'), CallId('c3'), CallId('c4')])
|
||||
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3'), ToolCallId('c4')])
|
||||
expect(events(agent).filter(e => e.type === 'tool/result').slice(-2).map(e => ({
|
||||
callId: e.data.message.source.callId,
|
||||
isError: e.data.message.content[0].isError,
|
||||
@@ -555,12 +555,12 @@ describe('tool-call scheduler: abort handling', () => {
|
||||
})))
|
||||
.toEqual([
|
||||
{
|
||||
callId: CallId('c3'),
|
||||
callId: ToolCallId('c3'),
|
||||
isError: true,
|
||||
error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH },
|
||||
},
|
||||
{
|
||||
callId: CallId('c4'),
|
||||
callId: ToolCallId('c4'),
|
||||
isError: true,
|
||||
error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH },
|
||||
},
|
||||
@@ -618,11 +618,11 @@ describe('tool-call scheduler: abort handling', () => {
|
||||
|
||||
expect(exclusive).toEqual([])
|
||||
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
|
||||
.toEqual([CallId('c1'), CallId('c2'), CallId('c3')])
|
||||
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')])
|
||||
expect(events(agent).filter(e => e.type === 'tool/result').at(-1)?.data)
|
||||
.toMatchObject({
|
||||
message: {
|
||||
source: { kind: 'tool', callId: CallId('c3') },
|
||||
source: { kind: 'tool', callId: ToolCallId('c3') },
|
||||
content: [{ isError: true }],
|
||||
},
|
||||
error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH },
|
||||
@@ -651,7 +651,7 @@ describe('tool-call scheduler: failure quiescence', () => {
|
||||
let thirdPrepareEntered = false
|
||||
scheduler.prepare = async (exec) => {
|
||||
const prepared = await prepare(exec)
|
||||
if (exec.callId === CallId('c3')) {
|
||||
if (exec.callId === ToolCallId('c3')) {
|
||||
thirdPrepareEntered = true
|
||||
await prepareGate.promise
|
||||
}
|
||||
@@ -660,7 +660,7 @@ describe('tool-call scheduler: failure quiescence', () => {
|
||||
const schedulerError = new Error('scheduler exploded')
|
||||
const drainedError = new Error('sibling failed while draining')
|
||||
let rejectFirst: ((error: Error) => void) | undefined
|
||||
scheduler.dispatch = exec => exec.callId === CallId('c1')
|
||||
scheduler.dispatch = exec => exec.callId === ToolCallId('c1')
|
||||
? new Promise((_resolve, reject) => { rejectFirst = reject })
|
||||
: dispatch(exec).then(() => { throw drainedError })
|
||||
const agent = ctx.agentLoop.create(SessionId('scheduler-failure'), { provider: 'mock', model: 'mock' })
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* @module @deepseek-ai/dsh-session/chunk-rows
|
||||
*/
|
||||
|
||||
import { CallId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionEvent } from './types.ts'
|
||||
|
||||
@@ -51,7 +51,7 @@ interface TextRunData extends RunDataBase {
|
||||
|
||||
/** Payload of a `tool-call-chunks` row: the run-constant call identity plus each member's raw arguments fragment. */
|
||||
interface ToolCallRunData extends RunDataBase {
|
||||
id: CallId
|
||||
id: ToolCallId
|
||||
/** Present iff every member carried it, with one uniform value (a mixed run never packs). */
|
||||
name?: string
|
||||
args: string[]
|
||||
@@ -188,7 +188,7 @@ function buildRow(kind: DeltaKind, run: readonly DeltaEvent[]): ChunkRow {
|
||||
...envelope,
|
||||
data: {
|
||||
...base,
|
||||
id: CallId(call.id),
|
||||
id: ToolCallId(call.id),
|
||||
...Object.hasOwn(call, 'name') ? { name: call.name as string } : {},
|
||||
args: run.map(event => (event.data.chunk as { argumentsDelta: string }).argumentsDelta),
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { assertNever } from '@deepseek-ai/dsh-llm'
|
||||
import type { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { TOOL_NOT_STARTED } from './repair.ts'
|
||||
@@ -26,7 +26,7 @@ interface SessionTrace {
|
||||
openStep: number | null
|
||||
nextTurn: number
|
||||
nextStep: number
|
||||
pendingCalls: Set<CallId>
|
||||
pendingCalls: Set<ToolCallId>
|
||||
}
|
||||
|
||||
/** One accepted event's deferred mutation of a committed session trace. */
|
||||
@@ -34,7 +34,7 @@ interface SessionTraceTransition {
|
||||
scalars: Pick<SessionTrace, 'lastSeq' | 'openTurn' | 'openStep' | 'nextTurn' | 'nextStep'>
|
||||
pendingCalls:
|
||||
| { kind: 'none' }
|
||||
| { kind: 'add' | 'delete'; callId: CallId }
|
||||
| { kind: 'add' | 'delete'; callId: ToolCallId }
|
||||
| { kind: 'clear' }
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @module @deepseek-ai/dsh-session/repair
|
||||
*/
|
||||
|
||||
import { MessageId, freezeMessage, type CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { MessageId, freezeMessage, type ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { ToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionEvent } from './types.ts'
|
||||
|
||||
@@ -29,7 +29,7 @@ export function interruptedTurnClosers(events: readonly SessionEvent[]): Session
|
||||
let openStep: number | null = null
|
||||
// Reset at each turn boundary so earlier calls cannot leak into tail repair.
|
||||
// Assistant blocks register calls; later `tool/call` events add their seqs to `sourceEventSeqs`.
|
||||
const pendingCalls = new Map<CallId, { step: number; callSeq?: number }>()
|
||||
const pendingCalls = new Map<ToolCallId, { step: number; callSeq?: number }>()
|
||||
for (const event of events) {
|
||||
switch (event.type) {
|
||||
case 'turn/start':
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
import type {
|
||||
AssistantMessage,
|
||||
CallId,
|
||||
ToolCallId,
|
||||
LlmCallConfig,
|
||||
LlmCallConfigAdapterDefaults,
|
||||
LlmFailure,
|
||||
@@ -265,7 +265,7 @@ export interface SessionEventMap {
|
||||
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
||||
* call with its `tool/result`.
|
||||
*/
|
||||
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
|
||||
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
|
||||
/**
|
||||
* A completed tool call's model-facing result, optional internal failure
|
||||
* identity, and optional tool-private `meta` presentation payload. `meta` is
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import fc from 'fast-check'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { decodeStorageRecord, packChunkRuns } from '@deepseek-ai/dsh-session'
|
||||
import { chunkRowLength, isChunkRow } from '@deepseek-ai/dsh-session/chunk-rows'
|
||||
@@ -47,7 +47,7 @@ describe('packChunkRuns', () => {
|
||||
it('packs reasoning and tool-call runs under their own tags', () => {
|
||||
const reasoning = deltaRun('reasoning-delta', 3)
|
||||
const toolCall = [4, 5, 6].map(seq =>
|
||||
chunkEvent(seq, 1000 + seq, { type: 'tool-call-delta', index: 1, id: CallId('c1'), name: 'write', argumentsDelta: `a${seq}` }))
|
||||
chunkEvent(seq, 1000 + seq, { type: 'tool-call-delta', index: 1, id: ToolCallId('c1'), name: 'write', argumentsDelta: `a${seq}` }))
|
||||
const packed = packChunkRuns([...reasoning, ...toolCall])
|
||||
expect(packed.map(r => (r as ChunkRow).type)).toStrictEqual(['reasoning-chunks', 'tool-call-chunks'])
|
||||
const row = packed[1] as ChunkRow & { type: 'tool-call-chunks' }
|
||||
@@ -58,7 +58,7 @@ describe('packChunkRuns', () => {
|
||||
|
||||
it('packs a name-less tool-call run and round-trips field absence', () => {
|
||||
const events = [0, 1, 2].map(seq =>
|
||||
chunkEvent(seq, 1000, { type: 'tool-call-delta', index: 0, id: CallId('c1'), argumentsDelta: `a${seq}` }))
|
||||
chunkEvent(seq, 1000, { type: 'tool-call-delta', index: 0, id: ToolCallId('c1'), argumentsDelta: `a${seq}` }))
|
||||
const packed = packChunkRuns(events)
|
||||
expect(packed).toHaveLength(1)
|
||||
expect(Object.hasOwn((packed[0] as ChunkRow).data, 'name')).toBe(false)
|
||||
@@ -96,7 +96,7 @@ describe('packChunkRuns', () => {
|
||||
|
||||
it('breaks a tool-call run on call-id or name change', () => {
|
||||
const call = (seq: number, id: string, name?: string): SessionEvent =>
|
||||
chunkEvent(seq, 1000, { type: 'tool-call-delta', index: 0, id: CallId(id), ...name !== undefined ? { name } : {}, argumentsDelta: 'a' })
|
||||
chunkEvent(seq, 1000, { type: 'tool-call-delta', index: 0, id: ToolCallId(id), ...name !== undefined ? { name } : {}, argumentsDelta: 'a' })
|
||||
const idSwitch = [call(0, 'c1', 'w'), call(1, 'c1', 'w'), call(2, 'c2', 'w')]
|
||||
expect(packChunkRuns(idSwitch)).toStrictEqual(idSwitch)
|
||||
const namePresence = [call(0, 'c1', 'w'), call(1, 'c1', 'w'), call(2, 'c1')]
|
||||
@@ -191,13 +191,13 @@ const deltaChunkArb: fc.Arbitrary<StreamChunk> = fc.oneof(
|
||||
fc.record({
|
||||
type: fc.constant<'tool-call-delta'>('tool-call-delta'),
|
||||
index: fc.nat(2),
|
||||
id: fc.constantFrom(CallId('c1'), CallId('c2')),
|
||||
id: fc.constantFrom(ToolCallId('c1'), ToolCallId('c2')),
|
||||
argumentsDelta: fc.string(),
|
||||
}),
|
||||
fc.record({
|
||||
type: fc.constant<'tool-call-delta'>('tool-call-delta'),
|
||||
index: fc.nat(2),
|
||||
id: fc.constantFrom(CallId('c1'), CallId('c2')),
|
||||
id: fc.constantFrom(ToolCallId('c1'), ToolCallId('c2')),
|
||||
name: fc.constantFrom('write', 'read'),
|
||||
argumentsDelta: fc.string(),
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createUserMessage, CallId , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId , createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { Session, SessionForkError, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent, TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
|
||||
@@ -268,7 +268,7 @@ describe('SessionStore.fork', () => {
|
||||
return lastSeq(session)
|
||||
}],
|
||||
['tool/call', (session) => {
|
||||
const callId = CallId('call-open')
|
||||
const callId = ToolCallId('call-open')
|
||||
session.append('turn/start', { turn: 1 })
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
session.append('assistant/message', {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createScope, scopeTarget } from '@deepseek-ai/dsh-scope'
|
||||
import { createUserMessage, CallId, createMessage, createToolResultMessage, freezeMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId, createMessage, createToolResultMessage, freezeMessage } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId, TOOL_NOT_STARTED } from '@deepseek-ai/dsh-session'
|
||||
import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant'
|
||||
import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants'
|
||||
@@ -46,18 +46,18 @@ describe('session-log invariants', () => {
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{}' }],
|
||||
content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' }],
|
||||
source: {
|
||||
kind: 'model',
|
||||
...{ provider: 'mock', model: 'mock' },
|
||||
},
|
||||
}),
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c1'), name: 'echo', arguments: '{}' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}' })
|
||||
session.append('tool/result', {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c1'),
|
||||
callId: ToolCallId('c1'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -222,7 +222,7 @@ describe('session-log invariants', () => {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('ghost'),
|
||||
callId: ToolCallId('ghost'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -237,7 +237,7 @@ describe('session-log invariants', () => {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('closed'),
|
||||
callId: ToolCallId('closed'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -252,7 +252,7 @@ describe('session-log invariants', () => {
|
||||
session.append('tool/call', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
callId: CallId('rewrite'),
|
||||
callId: ToolCallId('rewrite'),
|
||||
name: 'echo',
|
||||
arguments: '{}',
|
||||
})
|
||||
@@ -260,7 +260,7 @@ describe('session-log invariants', () => {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('rewrite'),
|
||||
callId: ToolCallId('rewrite'),
|
||||
content: [{ type: 'text', text: 'original' }],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -292,7 +292,7 @@ describe('session-log invariants', () => {
|
||||
session.append('tool/call', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
callId: CallId('rewrite'),
|
||||
callId: ToolCallId('rewrite'),
|
||||
name: 'echo',
|
||||
arguments: '{}',
|
||||
})
|
||||
@@ -300,7 +300,7 @@ describe('session-log invariants', () => {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('rewrite'),
|
||||
callId: ToolCallId('rewrite'),
|
||||
content: [{ type: 'text', text: 'original' }],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -332,7 +332,7 @@ describe('session-log invariants', () => {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('crashed'),
|
||||
callId: ToolCallId('crashed'),
|
||||
content: [],
|
||||
isError: true,
|
||||
}),
|
||||
@@ -346,7 +346,7 @@ describe('session-log invariants', () => {
|
||||
expect(() => {
|
||||
unresolved.append('turn/start', { turn: 1 })
|
||||
unresolved.append('step/start', { turn: 1, step: 1 })
|
||||
unresolved.append('tool/call', { turn: 1, step: 1, callId: CallId('c1'), name: 'echo', arguments: '{}' })
|
||||
unresolved.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}' })
|
||||
unresolved.append('step/end', { turn: 1, step: 1 })
|
||||
unresolved.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } })
|
||||
}).not.toThrow()
|
||||
@@ -357,14 +357,14 @@ describe('session-log invariants', () => {
|
||||
const session = ctx.sessions.create()
|
||||
session.append('turn/start', { turn: 1 })
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c1'), name: 'echo', arguments: '{}' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}' })
|
||||
session.append('step/end', { turn: 1, step: 1 })
|
||||
session.append('step/start', { turn: 1, step: 2 })
|
||||
expect(() => session.append('tool/result', {
|
||||
turn: 1,
|
||||
step: 2,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('c1'),
|
||||
callId: ToolCallId('c1'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import fc from 'fast-check'
|
||||
import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createUserMessage, ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEventMap, SessionEventType, SurfaceIntent } from '@deepseek-ai/dsh-session'
|
||||
|
||||
@@ -61,7 +61,7 @@ const messageEventArb: fc.Arbitrary<Appendable> = fc.oneof(
|
||||
.map((r): Appendable => ({ type: 'tool/result', data: {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId(r.id),
|
||||
callId: ToolCallId(r.id),
|
||||
content: r.content,
|
||||
isError: r.isError,
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { interruptedTurnClosers, TOOL_NOT_STARTED, TOOL_OUTCOME_UNKNOWN } from '../src/index.ts'
|
||||
import type { SessionEvent, SurfaceEvent } from '../src/index.ts'
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('interruptedTurnClosers', () => {
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'text', text: 'calling a tool' },
|
||||
{ type: 'tool-call', id: CallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -75,7 +75,7 @@ describe('interruptedTurnClosers', () => {
|
||||
turn: 2,
|
||||
step: 1,
|
||||
message: {
|
||||
source: { callId: CallId('call-1') },
|
||||
source: { callId: ToolCallId('call-1') },
|
||||
content: [{ isError: true }],
|
||||
},
|
||||
error: { code: TOOL_NOT_STARTED },
|
||||
@@ -94,7 +94,7 @@ describe('interruptedTurnClosers', () => {
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'tool-call', id: CallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -105,7 +105,7 @@ describe('interruptedTurnClosers', () => {
|
||||
{ type: 'tool/result', seq: 3, time: 3, data: {
|
||||
turn: 2, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('call-1'),
|
||||
callId: ToolCallId('call-1'),
|
||||
content: [{ type: 'text', text: 'ok' }],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -125,7 +125,7 @@ describe('interruptedTurnClosers', () => {
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'tool-call', id: CallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -152,7 +152,7 @@ describe('interruptedTurnClosers', () => {
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'tool-call', id: CallId('old-call'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('old-call'), name: 'bash', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -163,7 +163,7 @@ describe('interruptedTurnClosers', () => {
|
||||
{ type: 'tool/result', seq: 3, time: 3, data: {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('old-call'),
|
||||
callId: ToolCallId('old-call'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -177,7 +177,7 @@ describe('interruptedTurnClosers', () => {
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'tool-call', id: CallId('new-call'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('new-call'), name: 'bash', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -201,8 +201,8 @@ describe('interruptedTurnClosers', () => {
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'tool-call', id: CallId('call-a'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: CallId('call-b'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('call-a'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('call-b'), name: 'bash', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -214,7 +214,7 @@ describe('interruptedTurnClosers', () => {
|
||||
{ type: 'tool/result', seq: 3, time: 3, data: {
|
||||
turn: 1, step: 1,
|
||||
message: createToolResultMessage({
|
||||
callId: CallId('call-a'),
|
||||
callId: ToolCallId('call-a'),
|
||||
content: [],
|
||||
isError: false,
|
||||
}),
|
||||
@@ -235,7 +235,7 @@ describe('interruptedTurnClosers', () => {
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'tool-call', id: CallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
{ type: 'tool-call', id: ToolCallId('call-1'), name: 'bash', arguments: '{}' },
|
||||
],
|
||||
source: {
|
||||
kind: 'model',
|
||||
@@ -243,7 +243,7 @@ describe('interruptedTurnClosers', () => {
|
||||
},
|
||||
}),
|
||||
} },
|
||||
{ type: 'tool/call', seq: 3, time: 3, data: { turn: 1, step: 1, callId: CallId('call-1'), name: 'bash', arguments: '{}' } },
|
||||
{ type: 'tool/call', seq: 3, time: 3, data: { turn: 1, step: 1, callId: ToolCallId('call-1'), name: 'bash', arguments: '{}' } },
|
||||
]
|
||||
const closers = interruptedTurnClosers(events)
|
||||
expect(closers.map(e => e.type)).toEqual(['tool/result', 'step/end', 'turn/end'])
|
||||
@@ -266,7 +266,7 @@ describe('interruptedTurnClosers', () => {
|
||||
const events: SessionEvent[] = [
|
||||
userTurnStart(1, 0),
|
||||
{ type: 'step/start', seq: 1, time: 1, data: { turn: 1, step: 1 } },
|
||||
{ type: 'tool/call', seq: 2, time: 2, data: { turn: 1, step: 1, callId: CallId('orphan'), name: 'bash', arguments: '{}' } },
|
||||
{ type: 'tool/call', seq: 2, time: 2, data: { turn: 1, step: 1, callId: ToolCallId('orphan'), name: 'bash', arguments: '{}' } },
|
||||
]
|
||||
const closers = interruptedTurnClosers(events)
|
||||
// No pending calls → no synthetic tool/result, just step/end + turn/end.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user