diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml index dac9ce6b5c..98850aed4c 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md index a31df6a7d1..d7d3f6b43a 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md index da387b1798..ed4f915dff 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md @@ -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`)——零运行时开销的名义类型。 diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml index d929b209e3..1e1545bb12 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md index dda97bbf54..6443608c76 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md @@ -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 = 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 = 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`, `Map`, `get(id: SessionId)`, `Map`, ACP's `SessionId` surface, and the coordinator's `Map`. 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`, `Map`, `get(id: SessionId)`, `Map`, ACP's `SessionId` surface, and the coordinator's `Map`. 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 diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md index 82b79dff9d..f13d999aad 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md @@ -6,13 +6,13 @@ Status: implemented ## 问题 -harness 使用 `Branded = 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 = 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`、`Map`、`get(id: SessionId)`、`Map`、ACP 的 `SessionId` surface、协调器的 `Map`。这是变更中机械量最大的部分,也是让*既有* brand 在查找处真正发挥作用(而不仅仅标注在结构体字段上)的关键。 +- **阻止 brand 侵蚀。** 将既有 brand 传播到缺口 2 列出的 `Map` 键类型和公开方法参数中:`Map`、`Map`、`get(id: SessionId)`、`Map`、ACP 的 `SessionId` surface、协调器的 `Map`。这是变更中机械量最大的部分,也是让*既有* 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。 ## 后果 diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml index 7b145bd906..f1135aa2b3 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md index e14607e388..915e22f124 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md @@ -35,7 +35,7 @@ interface SpillStore { interface SpillSource { toolName: string - callId: CallId + callId: ToolCallId label: string } diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md index 372c9c6cad..8d08b05483 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md @@ -35,7 +35,7 @@ interface SpillStore { interface SpillSource { toolName: string - callId: CallId + callId: ToolCallId label: string } diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml index b49045dd58..5075fdd7a2 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.md b/.agents/notes/implemented/feature/2026-07-06-approval-seam.md index cbb1b1cb1f..6e94ed945b 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.md +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.md @@ -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 diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md b/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md index 29e23f8182..26447522ec 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md @@ -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 路由 diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml b/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml index 4affd8145c..5c561d9188 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml @@ -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 diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.md b/.agents/notes/proposed/feature/2026-08-04-task-surface.md index 03f79dbc7d..dbf73976b6 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.md +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.md @@ -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 } @@ -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 } ``` diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md b/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md index 63bb286bd8..ecf8764b7b 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md @@ -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 } @@ -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 } ``` diff --git a/apps/cli/tests/fixtures/dsh-badge/snapshot.ts b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts index cdc4dbb6d3..d579e8fe89 100644 --- a/apps/cli/tests/fixtures/dsh-badge/snapshot.ts +++ b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts @@ -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, diff --git a/apps/cli/tests/profiles/acp/tests/fixtures/control-surface/control-surface-llm.ts b/apps/cli/tests/profiles/acp/tests/fixtures/control-surface/control-surface-llm.ts index b0858998ee..b438799e38 100644 --- a/apps/cli/tests/profiles/acp/tests/fixtures/control-surface/control-surface-llm.ts +++ b/apps/cli/tests/profiles/acp/tests/fixtures/control-surface/control-surface-llm.ts @@ -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' } } diff --git a/apps/cli/tests/profiles/headless/tests/code-mode.e2e.ts b/apps/cli/tests/profiles/headless/tests/code-mode.e2e.ts index cca7641575..ca34a38628 100644 --- a/apps/cli/tests/profiles/headless/tests/code-mode.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/code-mode.e2e.ts @@ -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 { 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, diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs index 0ec1b9703a..57f0fe3dd2 100644 --- a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs +++ b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs @@ -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' }, diff --git a/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts index 4e7118b4ec..031ec836ee 100644 --- a/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/semantic-checkpoint.expected.e2e.ts @@ -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 { // 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, diff --git a/apps/web/tests/background-job-list.e2e.ts b/apps/web/tests/background-job-list.e2e.ts index cad3d05aa0..1030369886 100644 --- a/apps/web/tests/background-job-list.e2e.ts +++ b/apps/web/tests/background-job-list.e2e.ts @@ -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, diff --git a/apps/web/tests/chat-continuous-conversation.e2e.ts b/apps/web/tests/chat-continuous-conversation.e2e.ts index 5c2362252e..fc01177397 100644 --- a/apps/web/tests/chat-continuous-conversation.e2e.ts +++ b/apps/web/tests/chat-continuous-conversation.e2e.ts @@ -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 + readonly callId?: ReturnType 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}`, } } diff --git a/apps/web/tests/chat-scroll-contract.e2e.ts b/apps/web/tests/chat-scroll-contract.e2e.ts index bb115a9298..e7b1fedfb9 100644 --- a/apps/web/tests/chat-scroll-contract.e2e.ts +++ b/apps/web/tests/chat-scroll-contract.e2e.ts @@ -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' diff --git a/apps/web/tests/chat-scroll-fixture.ts b/apps/web/tests/chat-scroll-fixture.ts index caf9cf13d9..ed6c80da68 100644 --- a/apps/web/tests/chat-scroll-fixture.ts +++ b/apps/web/tests/chat-scroll-fixture.ts @@ -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, diff --git a/apps/web/tests/complex-history.perf.ts b/apps/web/tests/complex-history.perf.ts index b36f569360..c43b919e5d 100644 --- a/apps/web/tests/complex-history.perf.ts +++ b/apps/web/tests/complex-history.perf.ts @@ -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)}`, diff --git a/apps/web/tests/minimal-preset.snapshot.ts b/apps/web/tests/minimal-preset.snapshot.ts index 75aec1c7e3..ececcbd5bf 100644 --- a/apps/web/tests/minimal-preset.snapshot.ts +++ b/apps/web/tests/minimal-preset.snapshot.ts @@ -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, diff --git a/apps/web/tests/produced-file-mentions.e2e.ts b/apps/web/tests/produced-file-mentions.e2e.ts index f89cfe53b3..9e1772cdce 100644 --- a/apps/web/tests/produced-file-mentions.e2e.ts +++ b/apps/web/tests/produced-file-mentions.e2e.ts @@ -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', { diff --git a/apps/web/tests/produced-files.e2e.ts b/apps/web/tests/produced-files.e2e.ts index 3c6ebbf526..81259cf365 100644 --- a/apps/web/tests/produced-files.e2e.ts +++ b/apps/web/tests/produced-files.e2e.ts @@ -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', { diff --git a/apps/web/tests/replay-round-trip.e2e.ts b/apps/web/tests/replay-round-trip.e2e.ts index f428502b92..7413576885 100644 --- a/apps/web/tests/replay-round-trip.e2e.ts +++ b/apps/web/tests/replay-round-trip.e2e.ts @@ -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"', diff --git a/apps/web/tests/schedule-after.e2e.ts b/apps/web/tests/schedule-after.e2e.ts index 448ca8c893..5a15831128 100644 --- a/apps/web/tests/schedule-after.e2e.ts +++ b/apps/web/tests/schedule-after.e2e.ts @@ -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, diff --git a/apps/web/tests/shipped-composition.e2e.ts b/apps/web/tests/shipped-composition.e2e.ts index 41ebdb823b..f100e045c3 100644 --- a/apps/web/tests/shipped-composition.e2e.ts +++ b/apps/web/tests/shipped-composition.e2e.ts @@ -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, diff --git a/docs/cordis-tutorial/07-into-the-harness.i18n.yaml b/docs/cordis-tutorial/07-into-the-harness.i18n.yaml index 7dabe7b92f..2e83a2042b 100644 --- a/docs/cordis-tutorial/07-into-the-harness.i18n.yaml +++ b/docs/cordis-tutorial/07-into-the-harness.i18n.yaml @@ -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 diff --git a/docs/cordis-tutorial/07-into-the-harness.md b/docs/cordis-tutorial/07-into-the-harness.md index acd13d6d06..28e6c2009e 100644 --- a/docs/cordis-tutorial/07-into-the-harness.md +++ b/docs/cordis-tutorial/07-into-the-harness.md @@ -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, diff --git a/docs/cordis-tutorial/07-into-the-harness.zh.md b/docs/cordis-tutorial/07-into-the-harness.zh.md index b6a732f6e3..603b9cac19 100644 --- a/docs/cordis-tutorial/07-into-the-harness.zh.md +++ b/docs/cordis-tutorial/07-into-the-harness.zh.md @@ -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, diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index c42d6c4b13..13c2a19a3c 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -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 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 593fb07302..b983983330 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -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) diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 59acfa23c8..7b5fa938b2 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -144,12 +144,12 @@ export type SessionEvent = { '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 = { * 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) diff --git a/docs/subsystems/approval.i18n.yaml b/docs/subsystems/approval.i18n.yaml index e70b9747e5..565f5010cc 100644 --- a/docs/subsystems/approval.i18n.yaml +++ b/docs/subsystems/approval.i18n.yaml @@ -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 diff --git a/docs/subsystems/approval.md b/docs/subsystems/approval.md index d9f1169b52..89130232b4 100644 --- a/docs/subsystems/approval.md +++ b/docs/subsystems/approval.md @@ -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 /** diff --git a/docs/subsystems/approval.zh.md b/docs/subsystems/approval.zh.md index abc2361db3..e8047cd5a8 100644 --- a/docs/subsystems/approval.zh.md +++ b/docs/subsystems/approval.zh.md @@ -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 /** diff --git a/docs/subsystems/compaction.i18n.yaml b/docs/subsystems/compaction.i18n.yaml index b8937331d1..4a2a20d79a 100644 --- a/docs/subsystems/compaction.i18n.yaml +++ b/docs/subsystems/compaction.i18n.yaml @@ -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 diff --git a/docs/subsystems/compaction.md b/docs/subsystems/compaction.md index 0199125316..03642b32cc 100644 --- a/docs/subsystems/compaction.md +++ b/docs/subsystems/compaction.md @@ -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. */ diff --git a/docs/subsystems/compaction.zh.md b/docs/subsystems/compaction.zh.md index 9acc4d8250..af37c4824c 100644 --- a/docs/subsystems/compaction.zh.md +++ b/docs/subsystems/compaction.zh.md @@ -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. */ diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index f5d61a2ed8..06bfe0a937 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -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 diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 6490712bc3..dbe12dfc9b 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -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` 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 = 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). diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 79a2b5d1e9..50fd2e4e76 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -311,7 +311,7 @@ declare module '@deepseek-ai/dsh-llm' { ### 品牌化 ID -在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `CallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。 +在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `ToolCallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。 `Branded` 原语位于独立的纯类型包 [dsh-brand](../../packages/util/brand) 中(没有运行时代码,也不依赖 harness 包),因此任何包都能品牌化其拥有的 id,而无需依赖无关的能力包。 @@ -322,7 +322,7 @@ declare module '@deepseek-ai/dsh-llm' { type Branded = 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`。 diff --git a/docs/subsystems/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml index 2f11df1c7a..7b91ec4729 100644 --- a/docs/subsystems/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -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 diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index 8db1d95d91..331c30e103 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -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 } | { diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index fd2d483cbb..f05c67bc75 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -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 } | { diff --git a/docs/subsystems/session.i18n.yaml b/docs/subsystems/session.i18n.yaml index e5b62d616e..80666a3c88 100644 --- a/docs/subsystems/session.i18n.yaml +++ b/docs/subsystems/session.i18n.yaml @@ -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 diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index ef24802d7e..87e8b33dec 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -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 diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index bee235657d..d30b7d2441 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -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 diff --git a/docs/subsystems/spill.i18n.yaml b/docs/subsystems/spill.i18n.yaml index b8d92153b3..365d8f0535 100644 --- a/docs/subsystems/spill.i18n.yaml +++ b/docs/subsystems/spill.i18n.yaml @@ -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 diff --git a/docs/subsystems/spill.md b/docs/subsystems/spill.md index 1ea23747dd..366cacbef0 100644 --- a/docs/subsystems/spill.md +++ b/docs/subsystems/spill.md @@ -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 } diff --git a/docs/subsystems/spill.zh.md b/docs/subsystems/spill.zh.md index a10512f0c6..82e2ad9efe 100644 --- a/docs/subsystems/spill.zh.md +++ b/docs/subsystems/spill.zh.md @@ -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 } diff --git a/docs/subsystems/tools.i18n.yaml b/docs/subsystems/tools.i18n.yaml index ed327e53ce..3de50c917a 100644 --- a/docs/subsystems/tools.i18n.yaml +++ b/docs/subsystems/tools.i18n.yaml @@ -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 diff --git a/docs/subsystems/tools.md b/docs/subsystems/tools.md index dd89d40c14..5ac2401d66 100644 --- a/docs/subsystems/tools.md +++ b/docs/subsystems/tools.md @@ -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 (`:code:`). */ - 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 } diff --git a/docs/subsystems/tools.zh.md b/docs/subsystems/tools.zh.md index 0d3e59bc22..a081c58a2b 100644 --- a/docs/subsystems/tools.zh.md +++ b/docs/subsystems/tools.zh.md @@ -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 (`:code:`). */ - 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 } diff --git a/docs/user/develop/practice/llm-adapter.i18n.yaml b/docs/user/develop/practice/llm-adapter.i18n.yaml index d7d431bd5e..57e3e0cd7e 100644 --- a/docs/user/develop/practice/llm-adapter.i18n.yaml +++ b/docs/user/develop/practice/llm-adapter.i18n.yaml @@ -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 diff --git a/docs/user/develop/practice/llm-adapter.md b/docs/user/develop/practice/llm-adapter.md index 882e82d880..ff5fc5657e 100644 --- a/docs/user/develop/practice/llm-adapter.md +++ b/docs/user/develop/practice/llm-adapter.md @@ -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 { // 1. Start each content block with block-start. @@ -76,7 +76,7 @@ async function* exampleChunks(): AsyncIterable { 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 { index: 1, block: { type: 'tool-call', - id: CallId('call-123'), + id: ToolCallId('call-123'), name: 'bash', arguments: '{"command":"ls"}', }, diff --git a/docs/user/develop/practice/llm-adapter.zh.md b/docs/user/develop/practice/llm-adapter.zh.md index 27c480af2a..b05bfad6cb 100644 --- a/docs/user/develop/practice/llm-adapter.zh.md +++ b/docs/user/develop/practice/llm-adapter.zh.md @@ -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 { // 1. Start each content block with block-start. @@ -76,7 +76,7 @@ async function* exampleChunks(): AsyncIterable { 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 { index: 1, block: { type: 'tool-call', - id: CallId('call-123'), + id: ToolCallId('call-123'), name: 'bash', arguments: '{"command":"ls"}', }, diff --git a/packages/acp/acp/tests/approval.spec.ts b/packages/acp/acp/tests/approval.spec.ts index 9cd522bb92..22f1973b77 100644 --- a/packages/acp/acp/tests/approval.spec.ts +++ b/packages/acp/acp/tests/approval.spec.ts @@ -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) }) diff --git a/packages/acp/acp/tests/bridge.spec.ts b/packages/acp/acp/tests/bridge.spec.ts index a9aa1b3a0a..85125b3661 100644 --- a/packages/acp/acp/tests/bridge.spec.ts +++ b/packages/acp/acp/tests/bridge.spec.ts @@ -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' } }, ] diff --git a/packages/acp/acp/tests/edges.spec.ts b/packages/acp/acp/tests/edges.spec.ts index 8cd4599e94..073a5893c9 100644 --- a/packages/acp/acp/tests/edges.spec.ts +++ b/packages/acp/acp/tests/edges.spec.ts @@ -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' } }, ] diff --git a/packages/acp/acp/tests/updates.spec.ts b/packages/acp/acp/tests/updates.spec.ts index 9af5e31d52..832403c6db 100644 --- a/packages/acp/acp/tests/updates.spec.ts +++ b/packages/acp/acp/tests/updates.spec.ts @@ -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' }], }], diff --git a/packages/api/session-controller/tests/event-script.client.ts b/packages/api/session-controller/tests/event-script.client.ts index 4722192353..0d8deb5e27 100644 --- a/packages/api/session-controller/tests/event-script.client.ts +++ b/packages/api/session-controller/tests/event-script.client.ts @@ -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, }), diff --git a/packages/api/session-controller/tests/history-records.client.spec.ts b/packages/api/session-controller/tests/history-records.client.spec.ts index 4a91fb04a8..e8adbfcb1d 100644 --- a/packages/api/session-controller/tests/history-records.client.spec.ts +++ b/packages/api/session-controller/tests/history-records.client.spec.ts @@ -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}'], }, diff --git a/packages/api/session-controller/tests/session-history-journal.host.spec.ts b/packages/api/session-controller/tests/session-history-journal.host.spec.ts index c52dda5b92..d42639922e 100644 --- a/packages/api/session-controller/tests/session-history-journal.host.spec.ts +++ b/packages/api/session-controller/tests/session-history-journal.host.spec.ts @@ -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, }), diff --git a/packages/client/connection/src/client/fixture.ts b/packages/client/connection/src/client/fixture.ts index c02f0e11bc..756edaface 100644 --- a/packages/client/connection/src/client/fixture.ts +++ b/packages/client/connection/src/client/fixture.ts @@ -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 = [ diff --git a/packages/client/ui-approval/src/client/contract/slots.ts b/packages/client/ui-approval/src/client/contract/slots.ts index f71a33008b..545260b9ae 100644 --- a/packages/client/ui-approval/src/client/contract/slots.ts +++ b/packages/client/ui-approval/src/client/contract/slots.ts @@ -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. */ diff --git a/packages/client/ui-approval/tests/ui-approval.client.spec.tsx b/packages/client/ui-approval/tests/ui-approval.client.spec.tsx index 665ae928a4..2a550ef514 100644 --- a/packages/client/ui-approval/tests/ui-approval.client.spec.tsx +++ b/packages/client/ui-approval/tests/ui-approval.client.spec.tsx @@ -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(() => pnpm test) diff --git a/packages/client/ui-chat/src/client/contract/slots.ts b/packages/client/ui-chat/src/client/contract/slots.ts index cff42849eb..49c2cb841b 100644 --- a/packages/client/ui-chat/src/client/contract/slots.ts +++ b/packages/client/ui-chat/src/client/contract/slots.ts @@ -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 diff --git a/packages/client/ui-chat/src/client/contract/store.ts b/packages/client/ui-chat/src/client/contract/store.ts index 41a3e58052..94d2d914a7 100644 --- a/packages/client/ui-chat/src/client/contract/store.ts +++ b/packages/client/ui-chat/src/client/contract/store.ts @@ -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 } diff --git a/packages/client/ui-chat/src/client/index.ts b/packages/client/ui-chat/src/client/index.ts index e0d2b22b1c..62893382b6 100644 --- a/packages/client/ui-chat/src/client/index.ts +++ b/packages/client/ui-chat/src/client/index.ts @@ -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, diff --git a/packages/compaction/compaction-basic/tests/compaction-basic.spec.ts b/packages/compaction/compaction-basic/tests/compaction-basic.spec.ts index be4fd10a65..1afe6be023 100644 --- a/packages/compaction/compaction-basic/tests/compaction-basic.spec.ts +++ b/packages/compaction/compaction-basic/tests/compaction-basic.spec.ts @@ -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: { diff --git a/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts b/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts index 867d11c2a6..46d0384803 100644 --- a/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts +++ b/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts @@ -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}` } } diff --git a/packages/compaction/compaction-tool-result-pruner/src/types.ts b/packages/compaction/compaction-tool-result-pruner/src/types.ts index 13544b163e..d8228765a3 100644 --- a/packages/compaction/compaction-tool-result-pruner/src/types.ts +++ b/packages/compaction/compaction-tool-result-pruner/src/types.ts @@ -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. */ diff --git a/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts b/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts index 3216a6e774..c634e11bdb 100644 --- a/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts +++ b/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts @@ -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 = {}, ): 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), ) diff --git a/packages/compaction/compaction/tests/tool-pairing.spec.ts b/packages/compaction/compaction/tests/tool-pairing.spec.ts index 226542a038..cd4e2a874f 100644 --- a/packages/compaction/compaction/tests/tool-pairing.spec.ts +++ b/packages/compaction/compaction/tests/tool-pairing.spec.ts @@ -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, }), diff --git a/packages/context/agent-instructions/tests/agent-instructions.spec.ts b/packages/context/agent-instructions/tests/agent-instructions.spec.ts index 4fc60bc78d..a1101a390c 100644 --- a/packages/context/agent-instructions/tests/agent-instructions.spec.ts +++ b/packages/context/agent-instructions/tests/agent-instructions.spec.ts @@ -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) diff --git a/packages/context/session-reference/tests/session-reference.spec.ts b/packages/context/session-reference/tests/session-reference.spec.ts index 921e1703c4..ab0211ce52 100644 --- a/packages/context/session-reference/tests/session-reference.spec.ts +++ b/packages/context/session-reference/tests/session-reference.spec.ts @@ -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, }), diff --git a/packages/context/time-context/tests/time-context.spec.ts b/packages/context/time-context/tests/time-context.spec.ts index 833f7e4b9c..7952b95131 100644 --- a/packages/context/time-context/tests/time-context.spec.ts +++ b/packages/context/time-context/tests/time-context.spec.ts @@ -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' } }, ] diff --git a/packages/core/agent-loop/tests/agent-initiator.spec.ts b/packages/core/agent-loop/tests/agent-initiator.spec.ts index 0cd3ac39e4..70e3d26842 100644 --- a/packages/core/agent-loop/tests/agent-initiator.spec.ts +++ b/packages/core/agent-loop/tests/agent-initiator.spec.ts @@ -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: {}, }) diff --git a/packages/core/agent-loop/tests/cancel.spec.ts b/packages/core/agent-loop/tests/cancel.spec.ts index 992f4b6284..4a9c6c93d1 100644 --- a/packages/core/agent-loop/tests/cancel.spec.ts +++ b/packages/core/agent-loop/tests/cancel.spec.ts @@ -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) diff --git a/packages/core/agent-loop/tests/contract-regressions.spec.ts b/packages/core/agent-loop/tests/contract-regressions.spec.ts index c085dc7de0..25a0c7a85a 100644 --- a/packages/core/agent-loop/tests/contract-regressions.spec.ts +++ b/packages/core/agent-loop/tests/contract-regressions.spec.ts @@ -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 => { - 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')) } }) }) diff --git a/packages/core/agent-loop/tests/coverage-edges.spec.ts b/packages/core/agent-loop/tests/coverage-edges.spec.ts index 21506c9a0b..b42b3789ec 100644 --- a/packages/core/agent-loop/tests/coverage-edges.spec.ts +++ b/packages/core/agent-loop/tests/coverage-edges.spec.ts @@ -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'), diff --git a/packages/core/agent-loop/tests/interception.spec.ts b/packages/core/agent-loop/tests/interception.spec.ts index 72e3165f78..bc2c381c2a 100644 --- a/packages/core/agent-loop/tests/interception.spec.ts +++ b/packages/core/agent-loop/tests/interception.spec.ts @@ -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 } }, ] diff --git a/packages/core/agent-loop/tests/loop.spec.ts b/packages/core/agent-loop/tests/loop.spec.ts index a8beb82622..487fc60181 100644 --- a/packages/core/agent-loop/tests/loop.spec.ts +++ b/packages/core/agent-loop/tests/loop.spec.ts @@ -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' }, diff --git a/packages/core/agent-loop/tests/mock-adapter.ts b/packages/core/agent-loop/tests/mock-adapter.ts index c5341fb7ed..7543fd21c1 100644 --- a/packages/core/agent-loop/tests/mock-adapter.ts +++ b/packages/core/agent-loop/tests/mock-adapter.ts @@ -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 diff --git a/packages/core/agent-loop/tests/tool-calls.spec.ts b/packages/core/agent-loop/tests/tool-calls.spec.ts index 87f69cc7d0..f65454949c 100644 --- a/packages/core/agent-loop/tests/tool-calls.spec.ts +++ b/packages/core/agent-loop/tests/tool-calls.spec.ts @@ -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 => { - 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 => { @@ -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 => { - 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' }) diff --git a/packages/core/session/src/chunk-rows.ts b/packages/core/session/src/chunk-rows.ts index c000d8d3d8..b50a763a12 100644 --- a/packages/core/session/src/chunk-rows.ts +++ b/packages/core/session/src/chunk-rows.ts @@ -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), }, diff --git a/packages/core/session/src/invariant.ts b/packages/core/session/src/invariant.ts index ea038c5344..5512cd0b97 100644 --- a/packages/core/session/src/invariant.ts +++ b/packages/core/session/src/invariant.ts @@ -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 + pendingCalls: Set } /** One accepted event's deferred mutation of a committed session trace. */ @@ -34,7 +34,7 @@ interface SessionTraceTransition { scalars: Pick pendingCalls: | { kind: 'none' } - | { kind: 'add' | 'delete'; callId: CallId } + | { kind: 'add' | 'delete'; callId: ToolCallId } | { kind: 'clear' } } diff --git a/packages/core/session/src/repair.ts b/packages/core/session/src/repair.ts index d91cb72494..18e7a18f0c 100644 --- a/packages/core/session/src/repair.ts +++ b/packages/core/session/src/repair.ts @@ -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() + const pendingCalls = new Map() for (const event of events) { switch (event.type) { case 'turn/start': diff --git a/packages/core/session/src/types.ts b/packages/core/session/src/types.ts index 4458ea5b3e..bc27c996d0 100644 --- a/packages/core/session/src/types.ts +++ b/packages/core/session/src/types.ts @@ -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 diff --git a/packages/core/session/tests/chunk-rows.spec.ts b/packages/core/session/tests/chunk-rows.spec.ts index 0fbe7c1631..500355a4fe 100644 --- a/packages/core/session/tests/chunk-rows.spec.ts +++ b/packages/core/session/tests/chunk-rows.spec.ts @@ -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 = 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(), }), diff --git a/packages/core/session/tests/fork.spec.ts b/packages/core/session/tests/fork.spec.ts index 1cd9cb7287..6a7770b4b6 100644 --- a/packages/core/session/tests/fork.spec.ts +++ b/packages/core/session/tests/fork.spec.ts @@ -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', { diff --git a/packages/core/session/tests/invariant.spec.ts b/packages/core/session/tests/invariant.spec.ts index 46da3d21ca..0b9a282d45 100644 --- a/packages/core/session/tests/invariant.spec.ts +++ b/packages/core/session/tests/invariant.spec.ts @@ -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, }), diff --git a/packages/core/session/tests/properties.spec.ts b/packages/core/session/tests/properties.spec.ts index 32af9bbe48..e27c0c8d44 100644 --- a/packages/core/session/tests/properties.spec.ts +++ b/packages/core/session/tests/properties.spec.ts @@ -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 = 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, }), diff --git a/packages/core/session/tests/repair.spec.ts b/packages/core/session/tests/repair.spec.ts index 941819c0af..6d474956fa 100644 --- a/packages/core/session/tests/repair.spec.ts +++ b/packages/core/session/tests/repair.spec.ts @@ -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. diff --git a/packages/core/session/tests/session.spec.ts b/packages/core/session/tests/session.spec.ts index 232f3b3e0e..d74112c833 100644 --- a/packages/core/session/tests/session.spec.ts +++ b/packages/core/session/tests/session.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId, createMessage, createToolResultMessage, MessageId, ReasoningEffortId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, createMessage, createToolResultMessage, MessageId, ReasoningEffortId } from '@deepseek-ai/dsh-llm' import SessionStore, { adoptSessionEvent, SESSION_FORMAT_VERSION, @@ -33,7 +33,7 @@ describe('Session', () => { role: 'assistant', content: [ { type: 'text', text: 'let me check' }, - { type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{}' }, + { type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' }, ], source: { kind: 'model', @@ -44,7 +44,7 @@ describe('Session', () => { session.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ - callId: CallId('c1'), + callId: ToolCallId('c1'), content: [{ type: 'text', text: 'ok' }], isError: false, }), @@ -55,7 +55,7 @@ describe('Session', () => { expect(messages.map(m => m.role)).toEqual(['user', 'assistant', 'user']) // raw chunks must NOT appear in derived history expect(messages[1]!.content).toHaveLength(2) - expect(messages[2]!.content[0]).toMatchObject({ type: 'tool-result', toolCallId: CallId('c1') }) + expect(messages[2]!.content[0]).toMatchObject({ type: 'tool-result', toolCallId: ToolCallId('c1') }) }) it('accepts and round-trips a max-tokens turn/end reason', () => { @@ -442,7 +442,7 @@ describe('Session', () => { session.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ - callId: CallId('c1'), + callId: ToolCallId('c1'), content: [{ type: 'text', text: 'tool out' }], isError: false, }), diff --git a/packages/core/session/tests/surface.spec.ts b/packages/core/session/tests/surface.spec.ts index 7c7ca160b6..4be69465c3 100644 --- a/packages/core/session/tests/surface.spec.ts +++ b/packages/core/session/tests/surface.spec.ts @@ -15,7 +15,7 @@ import { createToolResultMessage, createUserMessage, freezeMessage, - CallId, + ToolCallId, MessageId, } from '@deepseek-ai/dsh-llm' @@ -68,7 +68,7 @@ function toolResultEvent( turn: 1, step: 1, message: createToolResultMessage({ - callId: CallId(callId), + callId: ToolCallId(callId), content: [{ type: 'text', text: `result ${seq}` }], isError: false, }), @@ -175,7 +175,7 @@ describe('foldSurface tool-result rewrites', () => { }) it.each([ - ['toolCallId', { toolCallId: CallId('changed') }], + ['toolCallId', { toolCallId: ToolCallId('changed') }], ['isError', { isError: true }], ] as const)('rejects a replacement that changes the result block %s', (_field, patch) => { const original = toolResultEvent(0, 'original') @@ -460,7 +460,7 @@ describe('SurfaceManager', () => { s.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ - callId: CallId('c1'), + callId: ToolCallId('c1'), content: [{ type: 'text', text: 'ok' }], isError: false, }), @@ -474,7 +474,7 @@ describe('SurfaceManager', () => { original.append('tool/result', { turn: 1, step: 1, message: createToolResultMessage({ - callId: CallId('c1'), + callId: ToolCallId('c1'), content: [{ type: 'text', text: 'ok' }], isError: false, }), diff --git a/packages/core/tools/src/code-mode.ts b/packages/core/tools/src/code-mode.ts index 729163b95c..0d97797154 100644 --- a/packages/core/tools/src/code-mode.ts +++ b/packages/core/tools/src/code-mode.ts @@ -6,7 +6,7 @@ * @module @deepseek-ai/dsh-tools/src/code-mode */ -import { CallId, createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm' +import { ToolCallId, createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm' import type { ContentBlock } from '@deepseek-ai/dsh-llm' import type { CodeBindingFunction, CodeRunResult, CodeRuntime } from '@deepseek-ai/dsh-code-runtime' import { snapshotJsonValue } from '@deepseek-ai/dsh-session' @@ -470,7 +470,7 @@ export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeO } const normalized = jsonNormalizeArgs(rawArgs) const n = ++dispatches - const subCallId = CallId(`${String(exec.callId)}:code:${n}`) + const subCallId = ToolCallId(`${String(exec.callId)}:code:${n}`) const input = { callId: subCallId, rootCallId: exec.rootCallId, diff --git a/packages/core/tools/src/index.ts b/packages/core/tools/src/index.ts index 7c6aaf146d..8cabf75d10 100644 --- a/packages/core/tools/src/index.ts +++ b/packages/core/tools/src/index.ts @@ -8,7 +8,7 @@ import { Context, Service } from '@deepseek-ai/cordis' import z from '@deepseek-ai/schemastery' import { AnonymousEntries, NamedEntries, ScopedLayers, scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope' import type { ScopeKey, ScopeLayer, Scoped } from '@deepseek-ai/dsh-scope' -import type { CallId, ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm' +import type { ToolCallId, ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm' import { assertNever, deepFreeze, HarnessError } from '@deepseek-ai/dsh-llm' import type { Agent } from '@deepseek-ai/dsh-agent' import { snapshotJsonValue } from '@deepseek-ai/dsh-session' @@ -313,12 +313,12 @@ export type ToolExecutionToken = symbol & { readonly [toolExecutionTokenBrand]: * callers do not choose that token. */ export 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 @@ -361,7 +361,7 @@ export 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 (`:code:`). */ - readonly subCallId: CallId + readonly subCallId: ToolCallId /** The dispatched sub-tool name. */ readonly name: string /** Whether the sub-call settled as an error. */ @@ -379,7 +379,7 @@ export interface CodeDispatchLog { */ export 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 } diff --git a/packages/core/tools/src/types.ts b/packages/core/tools/src/types.ts index 4dd6f7a9a8..6de9c10714 100644 --- a/packages/core/tools/src/types.ts +++ b/packages/core/tools/src/types.ts @@ -4,14 +4,14 @@ * @module @deepseek-ai/dsh-tools/types */ -import type { CallId } from '@deepseek-ai/dsh-llm/brand' +import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand' import type { ContentBlock } from '@deepseek-ai/dsh-llm/types' /** Payload recorded when one nested Code Mode Tool dispatch starts. */ export interface CodeDispatchStartEventData { - rootCallId: CallId - parentCallId: CallId - subCallId: CallId + rootCallId: ToolCallId + parentCallId: ToolCallId + subCallId: ToolCallId name: string arguments: unknown } diff --git a/packages/core/tools/tests/code-mode.spec.ts b/packages/core/tools/tests/code-mode.spec.ts index 563dffbf9f..b6149d88fd 100644 --- a/packages/core/tools/tests/code-mode.spec.ts +++ b/packages/core/tools/tests/code-mode.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import { createScope } from '@deepseek-ai/dsh-scope' import type { Scope } from '@deepseek-ai/dsh-scope' import SystemPrompt, { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt' @@ -107,7 +107,7 @@ async function runCode( ): Promise { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId('call-1'), + callId: ToolCallId('call-1'), name: RUN_CODE_NAME, arguments: { code, description: extras.description ?? 'Run the test program' }, ...extras.agent ? { agent: extras.agent } : {}, @@ -1636,9 +1636,9 @@ describe('the run_code dispatch bridge', () => { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) session.append('tool/code-dispatch', { - rootCallId: CallId('p1'), - parentCallId: CallId('p1'), - subCallId: CallId('p1:code:1'), + rootCallId: ToolCallId('p1'), + parentCallId: ToolCallId('p1'), + subCallId: ToolCallId('p1:code:1'), name: 'echo', arguments: { value: 'x' }, isError: false, @@ -1678,7 +1678,7 @@ describe('the run_code dispatch bridge', () => { registerEcho(ctx, 'write') const result = await registry.execute({ signal: testToolSignal, - callId: CallId('call-1'), + callId: ToolCallId('call-1'), name: 'write', arguments: { text: 'hello' }, }) @@ -1700,7 +1700,7 @@ describe('the run_code dispatch bridge', () => { aborted.abort() const result = await registry.execute({ signal: aborted.signal, - callId: CallId('call-1'), + callId: ToolCallId('call-1'), name: 'write', arguments: { text: 'hello' }, }) @@ -1732,7 +1732,7 @@ describe('per-agent presentation', () => { // mode is its own rather than the deployment's. const denied = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('coded-direct'), + callId: ToolCallId('coded-direct'), name: 'echo', arguments: { value: 'coded' }, agent, @@ -1768,14 +1768,14 @@ describe('per-agent presentation', () => { // `dsh-agent-tool-presentation` produces. expect(ctx.tools.executionMode({ signal: testToolSignal, - callId: CallId('preset-coded-schedule'), + callId: ToolCallId('preset-coded-schedule'), name: 'echo', arguments: { value: 'joined' }, agent: joined.agent, })).toEqual({ kind: 'exclusive' }) const denied = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('preset-coded-direct'), + callId: ToolCallId('preset-coded-direct'), name: 'echo', arguments: { value: 'joined' }, agent: joined.agent, @@ -1788,7 +1788,7 @@ describe('per-agent presentation', () => { expect(native.tools.map(tool => tool.name)).toEqual(['echo']) const allowed = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('native-sibling-direct'), + callId: ToolCallId('native-sibling-direct'), name: 'echo', arguments: { value: 'loner' }, agent: loner.agent, diff --git a/packages/core/tools/tests/execution-mode.spec.ts b/packages/core/tools/tests/execution-mode.spec.ts index a8db75169c..333909c24a 100644 --- a/packages/core/tools/tests/execution-mode.spec.ts +++ b/packages/core/tools/tests/execution-mode.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, expectTypeOf, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { defineContentToolFixture, @@ -21,7 +21,7 @@ async function setup() { } function exec(name: string, args: unknown): ToolExecutionInput { - return { signal: testToolSignal, callId: CallId('c1'), name, arguments: args } + return { signal: testToolSignal, callId: ToolCallId('c1'), name, arguments: args } } describe('ToolRuntime.executionMode', () => { diff --git a/packages/core/tools/tests/execution-signal-types.spec.ts b/packages/core/tools/tests/execution-signal-types.spec.ts index 0ac4ac85c0..95bd182e58 100644 --- a/packages/core/tools/tests/execution-signal-types.spec.ts +++ b/packages/core/tools/tests/execution-signal-types.spec.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, it } from 'vitest' import type { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { defineTool } from '@deepseek-ai/dsh-tools' import type { ToolDispatchExecution, @@ -15,7 +15,7 @@ function inputAndExecutionContracts( run: ToolRunContext, ): void { // @ts-expect-error -- every typed invocation must supply a caller-owned signal. - const missingSignal: ToolExecutionInput = { callId: CallId('missing'), name: 'probe', arguments: {} } + const missingSignal: ToolExecutionInput = { callId: ToolCallId('missing'), name: 'probe', arguments: {} } void missingSignal // @ts-expect-error -- caller input is readonly after construction. diff --git a/packages/core/tools/tests/invariant.spec.ts b/packages/core/tools/tests/invariant.spec.ts index 3ca9191308..24c3c443f4 100644 --- a/packages/core/tools/tests/invariant.spec.ts +++ b/packages/core/tools/tests/invariant.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { scopeTarget } from '@deepseek-ai/dsh-scope' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SessionStore, { Session, SessionId } from '@deepseek-ai/dsh-session' import type { ToolExecution, ToolExecutionResult, ToolExecutionToken } from '@deepseek-ai/dsh-tools' import * as ToolsInvariant from '@deepseek-ai/dsh-tools/invariant' @@ -19,12 +19,12 @@ async function setup(): Promise { const execution = (overrides: Partial = {}): ToolExecution => ({ token: Symbol('tool') as ToolExecutionToken, - callId: CallId('call-1'), + callId: ToolCallId('call-1'), name: 'echo', arguments: Object.freeze({ text: 'hi' }), ...overrides, signal: overrides.signal ?? testToolSignal, - rootCallId: overrides.rootCallId ?? overrides.callId ?? CallId('call-1'), + rootCallId: overrides.rootCallId ?? overrides.callId ?? ToolCallId('call-1'), }) const outcome = (): ToolExecutionResult => Object.freeze({ @@ -55,7 +55,7 @@ describe('tool-pipeline invariants', () => { Object.freeze(dispatched) emitResult(ctx, dispatched, outcome()) - const denied = execution({ callId: CallId('call-2') }) + const denied = execution({ callId: ToolCallId('call-2') }) await stage(ctx, 'tools/pre-execute', denied) await ctx.waterfall(ctx as never, 'tools/post-execute', denied, outcome(), () => Promise.resolve({ kind: 'accept' as const })) Object.freeze(denied) @@ -69,7 +69,7 @@ describe('tool-pipeline invariants', () => { await stage(ctx, 'tools/pre-execute', exec) await expect(stage(ctx, 'tools/pre-execute', exec)).rejects.toThrow(/repeated/) - const noPre = execution({ callId: CallId('call-2') }) + const noPre = execution({ callId: ToolCallId('call-2') }) await expect(stage(ctx, 'tools/execute', noPre)).rejects.toThrow(/must follow tools\/pre-execute/) expect(() => ctx.waterfall( ctx as never, 'tools/post-execute', noPre, outcome(), @@ -93,9 +93,9 @@ describe('tool-pipeline invariants', () => { const ctx = await setup() const session = ctx.sessions.create() const data = { - rootCallId: CallId('parent'), - parentCallId: CallId('parent'), - subCallId: CallId('child'), + rootCallId: ToolCallId('parent'), + parentCallId: ToolCallId('parent'), + subCallId: ToolCallId('child'), name: 'echo', arguments: {}, } @@ -109,18 +109,18 @@ describe('tool-pipeline invariants', () => { const ctx = await setup() const session = ctx.sessions.create() expect(() => session.append('tool/code-dispatch-start', { - rootCallId: CallId('rejected-root'), - parentCallId: CallId('rejected-root'), - subCallId: CallId('reused-child'), + rootCallId: ToolCallId('rejected-root'), + parentCallId: ToolCallId('rejected-root'), + subCallId: ToolCallId('reused-child'), name: 'echo', arguments: {}, })).toThrow(/outside any open turn/) session.append('turn/start', { turn: 1 }) expect(() => session.append('tool/code-dispatch-start', { - rootCallId: CallId('accepted-root'), - parentCallId: CallId('accepted-root'), - subCallId: CallId('reused-child'), + rootCallId: ToolCallId('accepted-root'), + parentCallId: ToolCallId('accepted-root'), + subCallId: ToolCallId('reused-child'), name: 'echo', arguments: {}, })).not.toThrow() @@ -131,24 +131,24 @@ describe('tool-pipeline invariants', () => { const session = ctx.sessions.create() session.append('turn/start', { turn: 1 }) session.append('tool/code-dispatch-start', { - rootCallId: CallId('root'), - parentCallId: CallId('root'), - subCallId: CallId('child'), + rootCallId: ToolCallId('root'), + parentCallId: ToolCallId('root'), + subCallId: ToolCallId('child'), name: 'run_code', arguments: {}, }) session.append('tool/code-dispatch-start', { - rootCallId: CallId('root'), - parentCallId: CallId('child'), - subCallId: CallId('grandchild'), + rootCallId: ToolCallId('root'), + parentCallId: ToolCallId('child'), + subCallId: ToolCallId('grandchild'), name: 'echo', arguments: {}, }) expect(() => session.append('tool/code-dispatch-start', { - rootCallId: CallId('another-root'), - parentCallId: CallId('child'), - subCallId: CallId('invalid-grandchild'), + rootCallId: ToolCallId('another-root'), + parentCallId: ToolCallId('child'), + subCallId: ToolCallId('invalid-grandchild'), name: 'echo', arguments: {}, })).toThrow(/parentCallId child does not belong to rootCallId another-root/) @@ -161,24 +161,24 @@ describe('tool-pipeline invariants', () => { const session = ctx.sessions.create() session.append('turn/start', { turn: 1 }) expect(() => session.append('tool/code-dispatch-start', { - rootCallId: CallId(''), - parentCallId: CallId('root'), - subCallId: CallId('child'), + rootCallId: ToolCallId(''), + parentCallId: ToolCallId('root'), + subCallId: ToolCallId('child'), name: 'echo', arguments: {}, })).toThrow(/must carry non-empty rootCallId/) session.append('tool/code-dispatch-start', { - rootCallId: CallId('root'), - parentCallId: CallId('root'), - subCallId: CallId('child'), + rootCallId: ToolCallId('root'), + parentCallId: ToolCallId('root'), + subCallId: ToolCallId('child'), name: 'echo', arguments: {}, }) expect(() => session.append('tool/code-dispatch-start', { - rootCallId: CallId('other-root'), - parentCallId: CallId('other-root'), - subCallId: CallId('child'), + rootCallId: ToolCallId('other-root'), + parentCallId: ToolCallId('other-root'), + subCallId: ToolCallId('child'), name: 'echo', arguments: {}, })).toThrow(/changed rootCallId for subCallId child/) @@ -194,9 +194,9 @@ describe('tool-pipeline invariants', () => { seq: 1, time: 1, data: { - rootCallId: CallId('root'), - parentCallId: CallId('root'), - subCallId: CallId('child'), + rootCallId: ToolCallId('root'), + parentCallId: ToolCallId('root'), + subCallId: ToolCallId('child'), name: 'echo', arguments: {}, }, @@ -210,9 +210,9 @@ describe('tool-pipeline invariants', () => { const session = ctx.sessions.create() session.append('turn/start', { turn: 1 }) session.append('tool/code-dispatch', { - rootCallId: CallId('parent'), - parentCallId: CallId('parent'), - subCallId: CallId('child'), + rootCallId: ToolCallId('parent'), + parentCallId: ToolCallId('parent'), + subCallId: ToolCallId('child'), name: 'echo', arguments: {}, isError: false, @@ -227,9 +227,9 @@ describe('tool-pipeline invariants', () => { const ctx = new Context() await ctx.plugin(SessionStore) ctx.sessions.create().append('tool/code-dispatch-start', { - rootCallId: CallId('parent'), - parentCallId: CallId('parent'), - subCallId: CallId('child'), + rootCallId: ToolCallId('parent'), + parentCallId: ToolCallId('parent'), + subCallId: ToolCallId('child'), name: 'echo', arguments: {}, }) diff --git a/packages/core/tools/tests/scoped.spec.ts b/packages/core/tools/tests/scoped.spec.ts index 353b408fa8..7eac6489f7 100644 --- a/packages/core/tools/tests/scoped.spec.ts +++ b/packages/core/tools/tests/scoped.spec.ts @@ -8,7 +8,7 @@ import ToolRuntime from '@deepseek-ai/dsh-tools' import type { PreToolDecision, ToolDefinition, ToolExecution, ToolExecutionInput, ToolExecutionToken } from '@deepseek-ai/dsh-tools' import type { Agent } from '@deepseek-ai/dsh-agent' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import type { SessionId } from '@deepseek-ai/dsh-session' const testToolSignal = new AbortController().signal @@ -49,7 +49,7 @@ function tool(name: string, reply = `ran:${name}`): ToolDefinition { async function run(ctx: Context, name: string, agent?: Agent): Promise { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('c1'), + callId: ToolCallId('c1'), name, arguments: {}, ...agent ? { agent } : {}, @@ -418,7 +418,7 @@ describe('scoped execution dispatch', () => { const callerArguments = { source: true } const safeResult = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('safe-call'), + callId: ToolCallId('safe-call'), name: 'safe', arguments: callerArguments, agent: key, @@ -461,7 +461,7 @@ describe('scoped execution dispatch', () => { if (exec.name === 'parent') parent = exec.token return next() }) - await ctx.tools.execute({ signal: testToolSignal, callId: CallId('parent'), name: 'parent', arguments: {} }) + await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('parent'), name: 'parent', arguments: {} }) stopCapture() policyCalls = 0 const signal = new AbortController().signal @@ -477,7 +477,7 @@ describe('scoped execution dispatch', () => { const callerArguments = { invalid: () => undefined } const scopedResult = await ctx.tools.execute({ - callId: CallId('non-cloneable'), + callId: ToolCallId('non-cloneable'), name: 't', arguments: callerArguments, agent: key, @@ -486,7 +486,7 @@ describe('scoped execution dispatch', () => { }) const subjectlessResult = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('non-cloneable-subjectless'), + callId: ToolCallId('non-cloneable-subjectless'), name: 't', arguments: { invalid: () => undefined }, }) @@ -525,7 +525,7 @@ describe('scoped execution dispatch', () => { const forged = { fake: true } as unknown as ToolExecutionToken let parentReads = 0 const input = { - callId: CallId('stateful-parent'), + callId: ToolCallId('stateful-parent'), name: 't', arguments: {}, signal: testToolSignal, @@ -553,14 +553,14 @@ describe('scoped execution dispatch', () => { if (exec.name === 'parent') parent = exec.token return next() }) - await ctx.tools.execute({ signal: testToolSignal, callId: CallId('parent'), name: 'parent', arguments: {} }) + await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('parent'), name: 'parent', arguments: {} }) stopCapture() const acceptedSignal = new AbortController().signal const driftSignal = new AbortController().signal const forged = { fake: true } as unknown as ToolExecutionToken const reads = { callId: 0, name: 0, arguments: 0, agent: 0, parent: 0, signal: 0 } const input = { - get callId() { reads.callId += 1; return CallId('unstable-error') }, + get callId() { reads.callId += 1; return ToolCallId('unstable-error') }, get name() { reads.name += 1; return 't' }, get arguments(): unknown { reads.arguments += 1; return { invalid: () => undefined } }, get agent() { reads.agent += 1; return reads.agent === 1 ? key : driftAgent }, @@ -578,7 +578,7 @@ describe('scoped execution dispatch', () => { expect(reads).toEqual({ callId: 1, name: 1, arguments: 1, agent: 1, parent: 1, signal: 1 }) expect(scopedObserved).toBe(1) expect(observed).toMatchObject({ - callId: CallId('unstable-error'), + callId: ToolCallId('unstable-error'), name: 't', agent: key, parent, @@ -598,7 +598,7 @@ describe('scoped execution dispatch', () => { expect(result.isError).toBe(true) }) const input = { - callId: CallId('throwing-arguments'), + callId: ToolCallId('throwing-arguments'), name: 't', signal: testToolSignal, get arguments(): unknown { @@ -642,7 +642,7 @@ describe('scoped execution dispatch', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('bad-arguments'), name: 't', arguments: argumentsValue, + callId: ToolCallId('bad-arguments'), name: 't', arguments: argumentsValue, }) expect(result.isError).toBe(true) @@ -663,7 +663,7 @@ describe('scoped execution dispatch', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('unstable-arguments'), name: 't', arguments: argumentsValue, + callId: ToolCallId('unstable-arguments'), name: 't', arguments: argumentsValue, }) expect(reads).toBe(1) @@ -705,7 +705,7 @@ describe('scoped execution dispatch', () => { ctx.on('tools/result', () => Promise.reject(new Error('async observer failure')) as never) ctx.on('tools/result', (_exec, result) => { seen.push(result.isError) }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('final'), name: 't', arguments: {}, agent: key }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('final'), name: 't', arguments: {}, agent: key }) await Promise.resolve() expect(result).toMatchObject({ isError: true, content: [{ type: 'text', text: 'outer failure' }] }) expect(seen).toEqual([true, true]) diff --git a/packages/core/tools/tests/tools.spec.ts b/packages/core/tools/tests/tools.spec.ts index ec770592a6..15f7203cfa 100644 --- a/packages/core/tools/tests/tools.spec.ts +++ b/packages/core/tools/tests/tools.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId, HarnessError, type ContentBlock } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, HarnessError, type ContentBlock } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import type { Agent } from '@deepseek-ai/dsh-agent' import ApprovalService, { type ApprovalOutcome, type ApprovalRequest } from '@deepseek-ai/dsh-user-approval' @@ -88,7 +88,7 @@ describe('ToolRuntime', () => { ctx.tools.register(echoTool) let observed: ToolExecutionResult | undefined ctx.on('tools/result', (_exec, result) => { observed = result }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result).toEqual({ content: [{ type: 'text', text: 'hi' }], isError: false, value: 'hi' }) expect(observed).toEqual(result) }) @@ -106,7 +106,7 @@ describe('ToolRuntime', () => { return 'ok' }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'meta-tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'meta-tool', arguments: {} }) expect(result).toEqual({ content: [{ type: 'text', text: 'ok' }], isError: false, @@ -124,7 +124,7 @@ describe('ToolRuntime', () => { return 'ok' }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'no-meta-tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'no-meta-tool', arguments: {} }) expect(result).toEqual({ content: [{ type: 'text', text: 'ok' }], isError: false, value: 'ok' }) expect('meta' in result).toBe(false) }) @@ -147,7 +147,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('bad-meta'), name: 'bad-meta', arguments: {}, + callId: ToolCallId('bad-meta'), name: 'bad-meta', arguments: {}, }) expect(result.isError).toBe(true) expect(result.content[0]?.type === 'text' && result.content[0].text).toContain('Error:') @@ -185,7 +185,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('throwing-meta'), name: 'throwing-meta', arguments: {}, + callId: ToolCallId('throwing-meta'), name: 'throwing-meta', arguments: {}, }) expect(result.isError).toBe(true) @@ -210,7 +210,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('throwing-finalizer'), name: 'throwing-finalizer', arguments: {}, + callId: ToolCallId('throwing-finalizer'), name: 'throwing-finalizer', arguments: {}, }) expect(result).toEqual({ @@ -251,8 +251,8 @@ describe('ToolRuntime', () => { execute: async () => 42 as unknown as string, })) - const lossy = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('lossy'), name: 'lossy-output', arguments: {} }) - const mismatch = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('mismatch'), name: 'wrong-output', arguments: {} }) + const lossy = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('lossy'), name: 'lossy-output', arguments: {} }) + const mismatch = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('mismatch'), name: 'wrong-output', arguments: {} }) expect(lossy.error).toMatchObject({ info: { name: 'ToolOutputError', code: 'INVALID_TOOL_OUTPUT' } }) expect(lossy.content[0]?.type === 'text' ? lossy.content[0].text : '').toContain('not lossless JSON') expect(mismatch.error).toMatchObject({ info: { name: 'ToolOutputError', code: 'INVALID_TOOL_OUTPUT' } }) @@ -275,7 +275,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('hostile-body'), name: 'hostile-body', arguments: {}, + callId: ToolCallId('hostile-body'), name: 'hostile-body', arguments: {}, }) expect(result.error?.message).toContain('value snapshot failed: body snapshot getter exploded') expect(result.error?.info).toEqual({ name: 'ToolOutputError', code: 'INVALID_TOOL_OUTPUT' }) @@ -301,7 +301,7 @@ describe('ToolRuntime', () => { execute: async () => 'ok', })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId(projector), name: `throwing-${projector}`, arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId(projector), name: `throwing-${projector}`, arguments: {} }) expect(result.isError).toBe(true) expect(result.error?.message) .toContain(projector === 'render' ? 'renderer exploded' : 'metadata exploded') @@ -333,7 +333,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`hostile-${projector}`), name: `hostile-${projector}`, arguments: {}, + callId: ToolCallId(`hostile-${projector}`), name: `hostile-${projector}`, arguments: {}, }) expect(result.error?.message).toContain('snapshot getter exploded') expect(result.error?.info).toEqual({ name: 'ToolOutputError', code: 'INVALID_TOOL_OUTPUT' }) @@ -370,9 +370,9 @@ describe('ToolRuntime', () => { } }) - const content = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('content'), name: 'projected', arguments: {} }) + const content = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('content'), name: 'projected', arguments: {} }) replacement = 'value' - const value = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('value'), name: 'projected', arguments: {} }) + const value = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('value'), name: 'projected', arguments: {} }) expect(content).toEqual({ isError: false, @@ -402,7 +402,7 @@ describe('ToolRuntime', () => { value: 'replacement', content: [{ type: 'text', text: 'also replacement' }], } as unknown as PostToolDecision)) - const bothResult = await both.tools.execute({ signal: testToolSignal, callId: CallId('both'), name: 'echo', arguments: {} }) + const bothResult = await both.tools.execute({ signal: testToolSignal, callId: ToolCallId('both'), name: 'echo', arguments: {} }) expect(bothResult).toMatchObject({ isError: true, error: { message: 'tools/post-execute accept decision cannot replace both value and content' }, @@ -411,7 +411,7 @@ describe('ToolRuntime', () => { const invalid = await setup() invalid.tools.register(echoTool) invalid.on('tools/post-execute', async () => ({ kind: 'accept', value: 1 })) - const invalidResult = await invalid.tools.execute({ signal: testToolSignal, callId: CallId('invalid'), name: 'echo', arguments: {} }) + const invalidResult = await invalid.tools.execute({ signal: testToolSignal, callId: ToolCallId('invalid'), name: 'echo', arguments: {} }) expect(invalidResult.error).toMatchObject({ info: { code: 'INVALID_TOOL_OUTPUT' } }) expect('value' in invalidResult).toBe(false) }) @@ -424,7 +424,7 @@ describe('ToolRuntime', () => { feedback: [{ type: 'text', text: 'blocked by policy' }], })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('block'), name: 'echo', arguments: { text: 'secret' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('block'), name: 'echo', arguments: { text: 'secret' } }) expect(result).toEqual({ isError: true, error: { message: 'blocked by policy' }, @@ -438,7 +438,7 @@ describe('ToolRuntime', () => { ctx.tools.register(echoTool) ctx.on('tools/post-execute', async () => ({ kind: 'accept', value: 'replacement' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('replace-value'), name: 'echo', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('replace-value'), name: 'echo', arguments: {} }) expect(result).toEqual({ isError: false, value: 'replacement', @@ -454,7 +454,7 @@ describe('ToolRuntime', () => { ctx.tools.register(echoTool) ctx.on('tools/post-execute', async () => ({ kind: 'block', feedback: [...feedback] })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('block-message'), name: 'echo', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('block-message'), name: 'echo', arguments: {} }) expect(result.error?.message).toBe(message) }) @@ -466,7 +466,7 @@ describe('ToolRuntime', () => { feedback: [{ type: 'text', text: 'blocked', invalid: () => undefined } as never], })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('invalid-block'), name: 'echo', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('invalid-block'), name: 'echo', arguments: {} }) expect(result).toMatchObject({ isError: true, error: { message: 'tool result must be losslessly JSON-serializable' }, @@ -484,7 +484,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('failed-replace'), name: 'throw-before-replace', arguments: {}, + callId: ToolCallId('failed-replace'), name: 'throw-before-replace', arguments: {}, }) expect(result.error?.message).toBe('tools/post-execute cannot replace the value of a failed result') }) @@ -497,7 +497,7 @@ describe('ToolRuntime', () => { return { kind: 'accept', value: 'replacement' } }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('post-disposed'), name: 'echo', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('post-disposed'), name: 'echo', arguments: {} }) expect(result.error).toEqual({ message: 'unknown tool "echo"', info: { name: 'ToolNotFoundError', code: 'UNKNOWN_TOOL' }, @@ -517,7 +517,7 @@ describe('ToolRuntime', () => { })], })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('wrapper-failure'), name: 'echo', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('wrapper-failure'), name: 'echo', arguments: {} }) expect(result).toEqual({ isError: true, error: { message: 'wrapped failure' }, @@ -540,7 +540,7 @@ describe('ToolRuntime', () => { return { isError: false, value: 'replacement', content: [] } }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('wrapper-disposed'), name: 'echo', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('wrapper-disposed'), name: 'echo', arguments: {} }) expect(result.error).toEqual({ message: 'unknown tool "echo"', info: { name: 'ToolNotFoundError', code: 'UNKNOWN_TOOL' }, @@ -554,10 +554,10 @@ describe('ToolRuntime', () => { name: 'meta-suppression', output: { ...echoTool.output, presentationMeta: () => ({ card: true }) }, }) - const direct = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('direct'), name: 'meta-suppression', arguments: {} }) + const direct = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('direct'), name: 'meta-suppression', arguments: {} }) const nested = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('nested'), + callId: ToolCallId('nested'), name: 'meta-suppression', arguments: {}, parent: Symbol('outer') as ToolExecutionToken, @@ -588,7 +588,7 @@ describe('ToolRuntime', () => { async execute(_args, exec) { call += 1 const nested = await ctx.tools.execute({ - signal: exec.signal, callId: CallId(`nested-${call}`), name: 'terminal-nested', arguments: {}, parent: exec.token, + signal: exec.signal, callId: ToolCallId(`nested-${call}`), name: 'terminal-nested', arguments: {}, parent: exec.token, }) if (nested.concludesTurn) exec.concludeTurn() return nested.isError ? 'nested failed, composite recovered' : 'nested succeeded' @@ -602,7 +602,7 @@ describe('ToolRuntime', () => { return { kind: 'block', feedback: [{ type: 'text', text: 'nested success rejected' }] } }) const recovered = await ctx.tools.execute({ - signal: testToolSignal, callId: CallId('composite-vetoed'), name: 'composite', arguments: {}, + signal: testToolSignal, callId: ToolCallId('composite-vetoed'), name: 'composite', arguments: {}, }) expect(recovered.isError).toBe(false) expect(recovered.concludesTurn).toBeUndefined() @@ -611,7 +611,7 @@ describe('ToolRuntime', () => { // The same nested call succeeding carries the marker; the composite // forwards it onto its own successful result. const concluded = await ctx.tools.execute({ - signal: testToolSignal, callId: CallId('composite-ok'), name: 'composite', arguments: {}, + signal: testToolSignal, callId: ToolCallId('composite-ok'), name: 'composite', arguments: {}, }) expect(concluded.isError).toBe(false) expect(concluded.concludesTurn).toBe(true) @@ -627,7 +627,7 @@ describe('ToolRuntime', () => { }, }) - const unknown = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'nope', arguments: {} }) + const unknown = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'nope', arguments: {} }) expect(unknown.isError).toBe(true) expect(unknown.content[0]).toMatchObject({ text: 'Error: unknown tool "nope"' }) // An unknown tool is a routable failure class, same as a tool-thrown one. @@ -636,7 +636,7 @@ describe('ToolRuntime', () => { info: { name: 'ToolNotFoundError', code: 'UNKNOWN_TOOL' }, }) - const thrown = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c2'), name: 'boom', arguments: {} }) + const thrown = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c2'), name: 'boom', arguments: {} }) expect(thrown.isError).toBe(true) expect(thrown.content[0]).toMatchObject({ text: 'Error: exploded' }) }) @@ -657,7 +657,7 @@ describe('ToolRuntime', () => { await expect(ctx.tools.execute({ signal: testToolSignal, - callId: CallId('hostile'), name: 'hostile-throw', arguments: {}, + callId: ToolCallId('hostile'), name: 'hostile-throw', arguments: {}, })).resolves.toMatchObject({ isError: true, content: [{ type: 'text', text: 'Error: ' }], @@ -688,7 +688,7 @@ describe('ToolRuntime', () => { return next() }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: denied by policy' }) expect(postSawFrozen).toBe(true) @@ -701,7 +701,7 @@ describe('ToolRuntime', () => { ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask', reason: 'needs approval' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: needs approval' }) }) @@ -712,7 +712,7 @@ describe('ToolRuntime', () => { ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: tool "echo" requires approval (not yet supported)' }) }) @@ -749,7 +749,7 @@ describe('ToolRuntime', () => { ({ kind: 'ask', reason: 'hook wants a human' })) const result = await ctx.tools.execute({ - callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' }, agent, signal: controller.signal, + callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' }, agent, signal: controller.signal, }) expect(result).toMatchObject({ isError: false, content: [{ type: 'text', text: 'hi' }] }) @@ -763,7 +763,7 @@ describe('ToolRuntime', () => { ctx.on('approval/request', () => Promise.resolve('rejected')) ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: the user rejected tool "echo"' }) }) @@ -773,7 +773,7 @@ describe('ToolRuntime', () => { ctx.on('approval/request', () => Promise.resolve('cancelled')) ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: approval for tool "echo" was cancelled' }) }) @@ -795,7 +795,7 @@ describe('ToolRuntime', () => { ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask' })) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('approval-cancelled'), + callId: ToolCallId('approval-cancelled'), name: 'approval-probe', arguments: {}, agent: fakeAgent(), @@ -817,7 +817,7 @@ describe('ToolRuntime', () => { const ctx = await approvalSetup() ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: tool "echo" requires approval, but no approval channel is available' }) }) @@ -831,7 +831,7 @@ describe('ToolRuntime', () => { }) ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: {} }) expect(asked).toBe(false) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: tool "echo" requires approval, but the call has no agent to route it through' }) @@ -846,7 +846,7 @@ describe('ToolRuntime', () => { ctx.provide('approval', { request: () => Promise.resolve('yolo') } as unknown as ApprovalService) ctx.on('tools/pre-execute', async (_exec, _next): Promise => ({ kind: 'ask' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: {}, agent: fakeAgent() }) expect(result.isError).toBe(true) const text = result.content[0]?.type === 'text' ? result.content[0].text : '' expect(text).toContain('unreachable') @@ -860,7 +860,7 @@ describe('ToolRuntime', () => { ctx.on('tools/post-execute', async (_exec, _result, _next): Promise => ({ kind: 'accept', content: [{ type: 'text', text: 'rewritten' }] })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.isError).toBe(false) expect(result.content[0]).toMatchObject({ text: 'rewritten' }) }) @@ -872,7 +872,7 @@ describe('ToolRuntime', () => { ctx.on('tools/post-execute', async (_exec, _result, _next): Promise => ({ kind: 'block', feedback: [{ type: 'text', text: 'output rejected: try again' }] })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'output rejected: try again' }) }) @@ -895,7 +895,7 @@ describe('ToolRuntime', () => { throw new HarnessError('policy failed', 'POLICY_FAILED') }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('bounded'), name: 'bounded', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('bounded'), name: 'bounded', arguments: {} }) expect(result).toEqual({ content: [{ type: 'text', text: 'bounded failure' }], @@ -919,7 +919,7 @@ describe('ToolRuntime', () => { }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('identity-finalizer'), name: 'identity-finalizer', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('identity-finalizer'), name: 'identity-finalizer', arguments: {} }) expect(result.isError).toBe(false) expect(result.content).toEqual([{ type: 'text', text: '' }]) @@ -939,7 +939,7 @@ describe('ToolRuntime', () => { })], })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'rejected' }) expect(result.additionalContexts).toMatchObject([{ content: [{ text: 'why it was rejected' }], source: { kind: 'plugin', plugin: 'test' } }]) @@ -954,7 +954,7 @@ describe('ToolRuntime', () => { content: [{ type: 'text', text: 'fyi' }], source: { kind: 'plugin', plugin: 'test' }, })] })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.additionalContexts).toMatchObject([{ content: [{ text: 'fyi' }], source: { kind: 'plugin', plugin: 'test' } }]) }) @@ -999,7 +999,7 @@ describe('ToolRuntime', () => { } }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('composite'), name: 'composite', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('composite'), name: 'composite', arguments: {} }) expect(result.additionalContexts?.map(context => context.source)).toEqual([ { kind: 'plugin', plugin: 'nested-1' }, @@ -1023,7 +1023,7 @@ describe('ToolRuntime', () => { }, })) - const failed = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('failed'), name: 'failing-composite', arguments: {} }) + const failed = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('failed'), name: 'failing-composite', arguments: {} }) expect(failed.isError).toBe(true) expect(failed.additionalContexts?.map(context => context.source)).toEqual([{ kind: 'plugin', plugin: 'nested' }]) @@ -1034,7 +1034,7 @@ describe('ToolRuntime', () => { content: [{ type: 'text', text: 'block-only' }], source: { kind: 'plugin', plugin: 'blocker' }, })], })) - const blocked = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('blocked'), name: 'failing-composite', arguments: {} }) + const blocked = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('blocked'), name: 'failing-composite', arguments: {} }) expect(blocked.isError).toBe(true) expect(blocked.additionalContexts?.map(context => context.source)).toEqual([{ kind: 'plugin', plugin: 'blocker' }]) }) @@ -1057,7 +1057,7 @@ describe('ToolRuntime', () => { return decision }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'x' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'x' } }) expect(result.isError).toBe(false) // pre runs fully (gate) before dispatch, then post runs over the result. expect(order).toEqual(['pre:before', 'pre:after', 'post:before', 'post:after']) @@ -1085,7 +1085,7 @@ describe('ToolRuntime', () => { }) ctx.on('tools/post-execute', async (_exec, _result, next) => { order.push('post'); return next() }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'traced', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'traced', arguments: { text: 'hi' } }) expect(result).toEqual({ content: [{ type: 'text', text: 'hi' }], isError: false, value: [{ type: 'text', text: 'hi' }] }) // The around-dispatch extension point wraps dispatch; pre gates before it, post runs over its result. expect(order).toEqual(['pre', 'execute:before', 'dispatch', 'execute:after', 'post']) @@ -1109,7 +1109,7 @@ describe('ToolRuntime', () => { const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('cancelled-in-pre'), name: 'must-not-run', arguments: {}, signal: controller.signal, + callId: ToolCallId('cancelled-in-pre'), name: 'must-not-run', arguments: {}, signal: controller.signal, }) await entered.promise controller.abort('cancelled in policy') @@ -1140,7 +1140,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('denied-after-cancel'), name: 'denied-after-cancel', arguments: {}, signal: controller.signal, + callId: ToolCallId('denied-after-cancel'), name: 'denied-after-cancel', arguments: {}, signal: controller.signal, }) await entered.promise @@ -1173,7 +1173,7 @@ describe('ToolRuntime', () => { const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('cancelled-pre-error'), name: 'must-not-run', arguments: {}, signal: controller.signal, + callId: ToolCallId('cancelled-pre-error'), name: 'must-not-run', arguments: {}, signal: controller.signal, }) await entered.promise controller.abort('cancelled in policy') @@ -1212,7 +1212,7 @@ describe('ToolRuntime', () => { const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('cancelled-in-around'), name: 'must-not-run', arguments: {}, signal: controller.signal, + callId: ToolCallId('cancelled-in-around'), name: 'must-not-run', arguments: {}, signal: controller.signal, }) await entered.promise controller.abort('cancelled in wrapper') @@ -1246,7 +1246,7 @@ describe('ToolRuntime', () => { const controller = new AbortController() const result = await ctx.tools.execute({ - callId: CallId('cancelled-wrapper'), name: 'must-not-run', arguments: {}, signal: controller.signal, + callId: ToolCallId('cancelled-wrapper'), name: 'must-not-run', arguments: {}, signal: controller.signal, }) expect(result.error).toEqual({ @@ -1281,7 +1281,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('cancelled-short-circuit'), + callId: ToolCallId('cancelled-short-circuit'), name: 'short-circuited', arguments: {}, signal: controller.signal, @@ -1323,7 +1323,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('cancelled-after-body'), name: 'completed-before-wrapper', arguments: {}, signal: controller.signal, + callId: ToolCallId('cancelled-after-body'), name: 'completed-before-wrapper', arguments: {}, signal: controller.signal, }) await entered.promise controller.abort('cancelled while wrapper settled') @@ -1366,7 +1366,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('cancelled-in-post'), name: 'completed-before-post', arguments: {}, signal: controller.signal, + callId: ToolCallId('cancelled-in-post'), name: 'completed-before-post', arguments: {}, signal: controller.signal, }) await entered.promise controller.abort('cancelled while post policy waits') @@ -1400,7 +1400,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('wrapper-failure'), name: 'wrapper-failure', arguments: {}, signal: controller.signal, + callId: ToolCallId('wrapper-failure'), name: 'wrapper-failure', arguments: {}, signal: controller.signal, }) await entered.promise @@ -1432,7 +1432,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('tool-failure'), name: 'tool-failure', arguments: {}, signal: controller.signal, + callId: ToolCallId('tool-failure'), name: 'tool-failure', arguments: {}, signal: controller.signal, }) await entered.promise @@ -1457,7 +1457,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('post-failure'), name: 'echo', arguments: {}, signal: controller.signal, + callId: ToolCallId('post-failure'), name: 'echo', arguments: {}, signal: controller.signal, }) await entered.promise @@ -1500,7 +1500,7 @@ describe('ToolRuntime', () => { const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('cancelled-body'), name: 'cooperative', arguments: {}, signal: controller.signal, + callId: ToolCallId('cancelled-body'), name: 'cooperative', arguments: {}, signal: controller.signal, }) await entered.promise expect(bodySignal).not.toBe(controller.signal) @@ -1534,7 +1534,7 @@ describe('ToolRuntime', () => { const controller = new AbortController() await ctx.tools.execute({ - callId: CallId('restored-signal'), name: 'echo', arguments: {}, signal: controller.signal, + callId: ToolCallId('restored-signal'), name: 'echo', arguments: {}, signal: controller.signal, }) expect(postSignal).toBe(controller.signal) @@ -1558,7 +1558,7 @@ describe('ToolRuntime', () => { }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('drain-body'), name: 'uncooperative', arguments: {}, signal: controller.signal, + callId: ToolCallId('drain-body'), name: 'uncooperative', arguments: {}, signal: controller.signal, }) await entered.promise controller.abort('must still drain') @@ -1605,7 +1605,7 @@ describe('ToolRuntime', () => { }) const result = await ctx.tools.execute({ - callId: CallId('pre-aborted'), + callId: ToolCallId('pre-aborted'), name: 'domain-abort', get arguments() { argumentReads += 1; return callerArguments }, signal: callerSignal, @@ -1637,7 +1637,7 @@ describe('ToolRuntime', () => { ctx.on('tools/result', () => { observed += 1 }) const result = await ctx.tools.execute({ - callId: CallId('invalid-pre-aborted'), + callId: ToolCallId('invalid-pre-aborted'), name: 'missing', arguments: { invalid: () => undefined }, signal: AbortSignal.abort('already cancelled'), @@ -1662,7 +1662,7 @@ describe('ToolRuntime', () => { return next() }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: nope' }) expect(entered).toBe(false) // A denied call never enters the around-dispatch extension point. @@ -1685,7 +1685,7 @@ describe('ToolRuntime', () => { return result }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'boom', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'boom', arguments: {} }) expect(seen).toEqual({ isError: true, error: { message: 'kaboom', info: { name: 'HarnessError', code: 'BOOM' } }, @@ -1710,7 +1710,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('frozen-canonical'), name: 'echo', arguments: { text: 'original' }, + callId: ToolCallId('frozen-canonical'), name: 'echo', arguments: { text: 'original' }, }) expect(mutationAttempts).toEqual([false, false]) expect(result.isError ? undefined : result.value).toBe('original') @@ -1731,7 +1731,7 @@ describe('ToolRuntime', () => { return next() }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'boom', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'boom', arguments: {} }) expect(postSaw).toBe(true) // the normalized isError still flows through post-execute expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: exploded' }) @@ -1759,7 +1759,7 @@ describe('ToolRuntime', () => { return next() }) - await ctx.tools.execute({ callId: CallId('c1'), name: 'signal-probe', arguments: {}, signal: upstream }) + await ctx.tools.execute({ callId: ToolCallId('c1'), name: 'signal-probe', arguments: {}, signal: upstream }) expect(seenSignal).toBeDefined() expect(seenSignal).not.toBe(upstream) expect(seenSignal).not.toBe(replacement) @@ -1777,7 +1777,7 @@ describe('ToolRuntime', () => { ctx.on('tools/execute', async (_exec: ToolDispatchExecution, _next: () => Promise): Promise => ({ content: [{ type: 'text', text: 'ignored authored content' }], isError: false, value: 'short-circuited' })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'never-runs', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'never-runs', arguments: {} }) expect(dispatched).toBe(false) // returning without next() skips core dispatch expect(result.content[0]).toMatchObject({ text: 'short-circuited' }) }) @@ -1817,10 +1817,10 @@ describe('ToolRuntime', () => { }) const first = await ctx.tools.execute({ - signal: testToolSignal, callId: CallId('cached-first'), name: 'string-output', arguments: {}, + signal: testToolSignal, callId: ToolCallId('cached-first'), name: 'string-output', arguments: {}, }) const second = await ctx.tools.execute({ - signal: testToolSignal, callId: CallId('cached-second'), name: 'object-output', arguments: {}, + signal: testToolSignal, callId: ToolCallId('cached-second'), name: 'object-output', arguments: {}, }) expect(first.isError ? undefined : first.value).toBe('cached') @@ -1846,7 +1846,7 @@ describe('ToolRuntime', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('around-context'), name: 'echo', arguments: {}, + callId: ToolCallId('around-context'), name: 'echo', arguments: {}, }) expect(result.additionalContexts).toEqual([{ id: expect.any(String) as unknown, @@ -1861,7 +1861,7 @@ describe('ToolRuntime', () => { ctx.tools.register(echoTool) ctx.on('tools/execute', async () => { throw new Error('wrapper broke') }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result).toEqual({ content: [{ type: 'text', text: 'Error: wrapper broke' }], error: { message: 'wrapper broke' }, @@ -1876,7 +1876,7 @@ describe('ToolRuntime', () => { throw new Error('permission hook broke') }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result).toEqual({ content: [{ type: 'text', text: 'Error: permission hook broke' }], @@ -1892,7 +1892,7 @@ describe('ToolRuntime', () => { throw new Error('post hook broke') }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result).toEqual({ content: [{ type: 'text', text: 'Error: post hook broke' }], @@ -1908,7 +1908,7 @@ describe('ToolRuntime', () => { throw new HarnessError('denied', 'DENIED') }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: { text: 'hi' } }) expect(result).toMatchObject({ isError: true, @@ -2136,7 +2136,7 @@ describe('defineTool / schema DSL', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('c1'), + callId: ToolCallId('c1'), name: 'typed-echo', arguments: { text: 'hello', uppercase: true }, }) @@ -2195,7 +2195,7 @@ describe('defineTool / schema DSL', () => { // Execution round-trip const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('c1'), + callId: ToolCallId('c1'), name: 'roundtrip', arguments: { req: 'hello' }, }) @@ -2232,7 +2232,7 @@ describe('defineTool / schema DSL', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('c1'), + callId: ToolCallId('c1'), name: 'raw-tool', arguments: { path: '/tmp' }, }) @@ -2409,7 +2409,7 @@ describe('schema DSL optional and nested contracts', () => { throw { message: 'denied by object' } }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'object-thrower', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'object-thrower', arguments: {} }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: denied by object' }) }) @@ -2424,7 +2424,7 @@ describe('schema DSL optional and nested contracts', () => { throw 'kaboom' }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'string-thrower', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'string-thrower', arguments: {} }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: kaboom' }) }) @@ -2439,7 +2439,7 @@ describe('schema DSL optional and nested contracts', () => { throw { code: 500 } }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'object-no-message', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'object-no-message', arguments: {} }) expect(result.isError).toBe(true) const firstContent = result.content[0]! expect(firstContent.type).toBe('text') @@ -2578,7 +2578,7 @@ describe('defineTool validation (the runtime-validation Agent Note, part 1)', () }, })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'reader', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'reader', arguments: {} }) expect(result.isError).toBe(true) expect(result.content[0]).toMatchObject({ text: 'Error: invalid arguments: missing required property "path"', @@ -2595,7 +2595,7 @@ describe('defineTool validation (the runtime-validation Agent Note, part 1)', () return [{ type: 'text', text: `read ${args.path}` }] }, })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'reader', arguments: { path: '/x' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'reader', arguments: { path: '/x' } }) expect(result).toEqual({ content: [{ type: 'text', text: 'read /x' }], isError: false, @@ -2622,7 +2622,7 @@ describe('defineTool validation (the runtime-validation Agent Note, part 1)', () return [{ type: 'text', text: args.path }] }, })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'reader', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'reader', arguments: {} }) expect(result.isError).toBe(true) expect(result.error).toEqual({ message: 'invalid arguments: missing required property "path"', @@ -2640,7 +2640,7 @@ describe('defineTool validation (the runtime-validation Agent Note, part 1)', () throw new HarnessError('disk full', 'ENOSPC') }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'coded', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'coded', arguments: {} }) expect(result.isError).toBe(true) expect(result.error).toEqual({ message: 'disk full', info: { name: 'HarnessError', code: 'ENOSPC' } }) expect(result.content[0]).toMatchObject({ text: 'Error: disk full' }) @@ -2655,7 +2655,7 @@ describe('defineTool validation (the runtime-validation Agent Note, part 1)', () throw new Error('just a message') }, }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'plain', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'plain', arguments: {} }) expect(result.isError).toBe(true) expect(result.error).toEqual({ message: 'just a message' }) expect(result.content[0]).toMatchObject({ text: 'Error: just a message' }) @@ -2678,7 +2678,7 @@ describe('defineTool validation (the runtime-validation Agent Note, part 1)', () }) // Missing the "required" path — but raw tools validate their own input, so // this reaches execute rather than being rejected by the harness. - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'raw', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'raw', arguments: {} }) expect(result.isError).toBe(false) }) diff --git a/packages/examples/agent-spine-demo/tests/agent-core.spec.ts b/packages/examples/agent-spine-demo/tests/agent-core.spec.ts index fb43d5cea9..1b4a541901 100644 --- a/packages/examples/agent-spine-demo/tests/agent-core.spec.ts +++ b/packages/examples/agent-spine-demo/tests/agent-core.spec.ts @@ -14,7 +14,7 @@ import * as ToolFs from '@deepseek-ai/dsh-tool-fs' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import { createUserMessage, - CallId, + ToolCallId, LlmAdapter, LlmError, resolveRetryPolicy, @@ -605,8 +605,8 @@ describe('dsh-agent-spine-demo bundle', () => { const execution: ToolExecution = { signal: testToolSignal, token: Symbol('agent-core-dsh-home-test') as ToolExecution['token'], - callId: CallId('agent-core-dsh-home'), - rootCallId: CallId('agent-core-dsh-home'), + callId: ToolCallId('agent-core-dsh-home'), + rootCallId: ToolCallId('agent-core-dsh-home'), name: 'bash', arguments: { command: 'true' }, } @@ -685,7 +685,7 @@ describe('dsh-agent-spine-demo bundle', () => { const wait = vi.spyOn(ctx.jobs, 'wait') await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('task-config-forwarding'), + callId: ToolCallId('task-config-forwarding'), name: 'job_output', arguments: { job_id: id, wait: true }, }) diff --git a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts index f06fe24924..8bc735f3f6 100644 --- a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts +++ b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts @@ -8,7 +8,7 @@ import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox' import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local' import * as FsPolicy from '@deepseek-ai/dsh-fs-observation-policy' import SandboxedFileSystem from '@deepseek-ai/dsh-fs-sandbox' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' import { bwrapProfileArgs, seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts' import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy' @@ -85,22 +85,22 @@ describe('one-context multi-project sandbox', () => { const { active, agentA, agentB } = await agents() const [aOwn, bOwn, aCross, bCross] = await Promise.all([ active.tools.execute({ - callId: CallId('bash-a-own'), name: 'bash', agent: agentA, + callId: ToolCallId('bash-a-own'), name: 'bash', agent: agentA, signal: new AbortController().signal, arguments: { command: 'printf a > a-owned.txt', description: 'Write project A marker' }, }), active.tools.execute({ - callId: CallId('bash-b-own'), name: 'bash', agent: agentB, + callId: ToolCallId('bash-b-own'), name: 'bash', agent: agentB, signal: new AbortController().signal, arguments: { command: 'printf b > b-owned.txt', description: 'Write project B marker' }, }), active.tools.execute({ - callId: CallId('bash-a-cross'), name: 'bash', agent: agentA, + callId: ToolCallId('bash-a-cross'), name: 'bash', agent: agentA, signal: new AbortController().signal, arguments: { command: `printf cross > ../${basename(projectB)}/from-a.txt`, description: 'Attempt project B write' }, }), active.tools.execute({ - callId: CallId('bash-b-cross'), name: 'bash', agent: agentB, + callId: ToolCallId('bash-b-cross'), name: 'bash', agent: agentB, signal: new AbortController().signal, arguments: { command: `printf cross > ../${basename(projectA)}/from-b.txt`, description: 'Attempt project A write' }, }), @@ -122,22 +122,22 @@ describe('one-context multi-project sandbox', () => { const { active, agentA, agentB } = await agents() const [aOwn, bOwn, aCross, bCross] = await Promise.all([ active.tools.execute({ - callId: CallId('fs-a-own'), name: 'write', agent: agentA, + callId: ToolCallId('fs-a-own'), name: 'write', agent: agentA, signal: new AbortController().signal, arguments: { file_path: 'a-owned.txt', content: 'a' }, }), active.tools.execute({ - callId: CallId('fs-b-own'), name: 'write', agent: agentB, + callId: ToolCallId('fs-b-own'), name: 'write', agent: agentB, signal: new AbortController().signal, arguments: { file_path: 'b-owned.txt', content: 'b' }, }), active.tools.execute({ - callId: CallId('fs-a-cross'), name: 'write', agent: agentA, + callId: ToolCallId('fs-a-cross'), name: 'write', agent: agentA, signal: new AbortController().signal, arguments: { file_path: join(projectB, 'from-a.txt'), content: 'cross' }, }), active.tools.execute({ - callId: CallId('fs-b-cross'), name: 'write', agent: agentB, + callId: ToolCallId('fs-b-cross'), name: 'write', agent: agentB, signal: new AbortController().signal, arguments: { file_path: join(projectA, 'from-b.txt'), content: 'cross' }, }), @@ -171,22 +171,22 @@ describe('one-context multi-project sandbox', () => { const [bashOwn, bashLexical, fsOwn, fsLexical] = await Promise.all([ active.tools.execute({ - callId: CallId('bash-symlink-own'), name: 'bash', agent: handle.agent, + callId: ToolCallId('bash-symlink-own'), name: 'bash', agent: handle.agent, signal: new AbortController().signal, arguments: { command: 'printf bash > bash-owned.txt', description: 'Write physical workspace marker' }, }), active.tools.execute({ - callId: CallId('bash-symlink-lexical'), name: 'bash', agent: handle.agent, + callId: ToolCallId('bash-symlink-lexical'), name: 'bash', agent: handle.agent, signal: new AbortController().signal, arguments: { command: `printf escaped > ${join(lexicalRoot, 'bash-escaped.txt')}`, description: 'Attempt lexical workspace write' }, }), active.tools.execute({ - callId: CallId('fs-symlink-own'), name: 'write', agent: handle.agent, + callId: ToolCallId('fs-symlink-own'), name: 'write', agent: handle.agent, signal: new AbortController().signal, arguments: { file_path: 'fs-owned.txt', content: 'fs' }, }), active.tools.execute({ - callId: CallId('fs-symlink-lexical'), name: 'write', agent: handle.agent, + callId: ToolCallId('fs-symlink-lexical'), name: 'write', agent: handle.agent, signal: new AbortController().signal, arguments: { file_path: join(lexicalRoot, 'fs-escaped.txt'), content: 'escaped' }, }), @@ -222,12 +222,12 @@ describe('one-context multi-project sandbox', () => { const [bashRead, fsRead] = await Promise.all([ active.tools.execute({ - callId: CallId('bash-symlink-parent-read'), name: 'bash', agent: handle.agent, + callId: ToolCallId('bash-symlink-parent-read'), name: 'bash', agent: handle.agent, signal: new AbortController().signal, arguments: { command: 'cat ../shared.txt', description: 'Read through the physical parent' }, }), active.tools.execute({ - callId: CallId('fs-symlink-parent-read'), name: 'read', agent: handle.agent, + callId: ToolCallId('fs-symlink-parent-read'), name: 'read', agent: handle.agent, signal: new AbortController().signal, arguments: { file_path: '../shared.txt' }, }), diff --git a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts index 19765b9980..1efc795932 100644 --- a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts +++ b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts @@ -6,7 +6,7 @@ import { Context } from '@deepseek-ai/cordis' import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { scopeOf } from '@deepseek-ai/dsh-scope' import { SessionId } from '@deepseek-ai/dsh-session' import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' @@ -81,7 +81,7 @@ function execute( signal: AbortSignal = SIGNAL, ) { return ctx.tools.execute({ - callId: CallId(`team-call-${++callNumber}`), + callId: ToolCallId(`team-call-${++callNumber}`), name, arguments: args, signal, diff --git a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts index f3571dd0e3..c599a0eb3c 100644 --- a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts +++ b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts @@ -121,7 +121,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [ doc: 'Optional detail for the Tool call correlated with an approval request.', registerOptions: [], ownerProps: [ - '/** Stable identity handed to an optional approval-detail renderer. */\nexport interface ApprovalDetailOwnerProps {\n /** Tool call correlated with the request. */\n callId: CallId\n}', + '/** Stable identity handed to an optional approval-detail renderer. */\nexport interface ApprovalDetailOwnerProps {\n /** Tool call correlated with the request. */\n callId: ToolCallId\n}', ], ownerPropsReferences: [], standardProps: [ @@ -266,7 +266,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [ }, ], ownerProps: [ - '/** Stable owner currency delivered to a keyed Chat renderer. */\nexport interface ChatNodeOwnerProps {\n selectedCallId?: CallId | undefined\n cwd?: string | undefined\n openFile: (path: string) => void\n inspectCall: (callId: CallId) => void\n forkAt: (seq: number) => void\n renderMessageImages: RenderMessageImages\n fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined\n}', + '/** Stable owner currency delivered to a keyed Chat renderer. */\nexport interface ChatNodeOwnerProps {\n selectedCallId?: ToolCallId | undefined\n cwd?: string | undefined\n openFile: (path: string) => void\n inspectCall: (callId: ToolCallId) => void\n forkAt: (seq: number) => void\n renderMessageImages: RenderMessageImages\n fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined\n}', ], ownerPropsReferences: [ 'MarkdownFileMentions', diff --git a/packages/extensions/cordis-host-runner/tests/helpers.ts b/packages/extensions/cordis-host-runner/tests/helpers.ts index 894e72fe6d..f89a85f268 100644 --- a/packages/extensions/cordis-host-runner/tests/helpers.ts +++ b/packages/extensions/cordis-host-runner/tests/helpers.ts @@ -1,6 +1,6 @@ import { Context } from '@deepseek-ai/cordis' import Timer from '@deepseek-ai/cordis-plugin-timer' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRegistry from '@deepseek-ai/dsh-tools' import type { ToolDefinition, ToolExecutionResult } from '@deepseek-ai/dsh-tools' @@ -147,7 +147,7 @@ let callCounter = 0 export function call(ctx: Context, name: string, args: unknown): Promise { return ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, }) diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index c18983dfbf..7582815719 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -3277,11 +3277,11 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ApprovalRequest', - declaration: 'export interface ApprovalRequest extends ApprovalRequestEvent {\n readonly agent: Agent;\n readonly toolName: string;\n readonly callId?: CallId;\n readonly reason?: string;\n readonly signal?: AbortSignal;\n}', + declaration: 'export interface ApprovalRequest extends ApprovalRequestEvent {\n readonly agent: Agent;\n readonly toolName: string;\n readonly callId?: ToolCallId;\n readonly reason?: string;\n readonly signal?: AbortSignal;\n}', }, { name: 'ApprovalRequestEvent', - declaration: 'export interface ApprovalRequestEvent {\n readonly agent: Agent;\n readonly toolName: string;\n readonly callId?: CallId;\n readonly reason?: string;\n readonly signal?: AbortSignal;\n}', + declaration: 'export interface ApprovalRequestEvent {\n readonly agent: Agent;\n readonly toolName: string;\n readonly callId?: ToolCallId;\n readonly reason?: string;\n readonly signal?: AbortSignal;\n}', }, { name: 'AskUserQuestionAnswer', @@ -3429,7 +3429,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'CodeDispatchLog', - declaration: 'export interface CodeDispatchLog {\n readonly exec: ToolExecution;\n readonly agent?: Agent;\n readonly subCallId: CallId;\n readonly name: string;\n readonly isError: boolean;\n readonly content: ContentBlock[];\n}', + declaration: 'export interface CodeDispatchLog {\n readonly exec: ToolExecution;\n readonly agent?: Agent;\n readonly subCallId: ToolCallId;\n readonly name: string;\n readonly isError: boolean;\n readonly content: ContentBlock[];\n}', }, { name: 'CodeJsonValue', @@ -4309,7 +4309,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'PrunedEntry', - declaration: 'export interface PrunedEntry {\n readonly originalSeq: number;\n readonly replacementSeq: number;\n readonly callId: CallId;\n readonly charsBefore: number;\n readonly charsAfter: number;\n}', + declaration: 'export interface PrunedEntry {\n readonly originalSeq: number;\n readonly replacementSeq: number;\n readonly callId: ToolCallId;\n readonly charsBefore: number;\n readonly charsAfter: number;\n}', }, { name: 'PruneResult', @@ -4557,7 +4557,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'SessionEventMap', - declaration: 'export interface SessionEventMap {\n \'turn/start\': {\n turn: number;\n };\n \'turn/end\': {\n turn: number;\n reason: TurnEndReason;\n };\n \'step/start\': {\n turn: number;\n step: number;\n };\n \'step/end\': {\n turn: number;\n step: number;\n };\n \'user/message\': UserMessage;\n \'assistant/chunk\': {\n turn: number;\n step: number;\n chunk: StreamChunk;\n };\n \'assistant/message\': {\n turn: number;\n step: number;\n message: AssistantMessage;\n usage?: TokenUsage;\n interrupted?: true;\n };\n \'tool/call\': {\n turn: number;\n step: number;\n callId: CallId;\n name: string;\n arguments: string;\n };\n \'tool/result\': {\n turn: number;\n step: number;\n message: ToolResultMessage;\n error?: {\n name: string;\n code: string;\n };\n meta?: JsonValue;\n };\n \'request/header\': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n startsSeries?: true;\n };\n \'request/context\': RequestContext;\n \'session/end-seed\': Record;\n}', + declaration: 'export interface SessionEventMap {\n \'turn/start\': {\n turn: number;\n };\n \'turn/end\': {\n turn: number;\n reason: TurnEndReason;\n };\n \'step/start\': {\n turn: number;\n step: number;\n };\n \'step/end\': {\n turn: number;\n step: number;\n };\n \'user/message\': UserMessage;\n \'assistant/chunk\': {\n turn: number;\n step: number;\n chunk: StreamChunk;\n };\n \'assistant/message\': {\n turn: number;\n step: number;\n message: AssistantMessage;\n usage?: TokenUsage;\n interrupted?: true;\n };\n \'tool/call\': {\n turn: number;\n step: number;\n callId: ToolCallId;\n name: string;\n arguments: string;\n };\n \'tool/result\': {\n turn: number;\n step: number;\n message: ToolResultMessage;\n error?: {\n name: string;\n code: string;\n };\n meta?: JsonValue;\n };\n \'request/header\': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n startsSeries?: true;\n };\n \'request/context\': RequestContext;\n \'session/end-seed\': Record;\n}', }, { name: 'SessionEventMetadataFilter', @@ -5041,7 +5041,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'SpillSource', - declaration: 'export interface SpillSource {\n toolName: string;\n callId: CallId;\n label: string;\n}', + declaration: 'export interface SpillSource {\n toolName: string;\n callId: ToolCallId;\n label: string;\n}', }, { name: 'StorageBackend', @@ -5057,7 +5057,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'StreamChunk', - declaration: 'export type StreamChunk = {\n type: \'block-start\';\n index: number;\n blockType: ContentBlockType;\n} | {\n type: \'text-delta\';\n index: number;\n text: string;\n} | {\n type: \'reasoning-delta\';\n index: number;\n text: string;\n} | {\n type: \'tool-call-delta\';\n index: number;\n id: CallId;\n name?: string;\n argumentsDelta: string;\n} | {\n type: \'block-end\';\n index: number;\n block: ContentBlock;\n} | {\n type: \'usage\';\n usage: TokenUsage;\n} | {\n type: \'finish\';\n reason: FinishReason;\n replayState?: ReplayEnvelope;\n};', + declaration: 'export type StreamChunk = {\n type: \'block-start\';\n index: number;\n blockType: ContentBlockType;\n} | {\n type: \'text-delta\';\n index: number;\n text: string;\n} | {\n type: \'reasoning-delta\';\n index: number;\n text: string;\n} | {\n type: \'tool-call-delta\';\n index: number;\n id: ToolCallId;\n name?: string;\n argumentsDelta: string;\n} | {\n type: \'block-end\';\n index: number;\n block: ContentBlock;\n} | {\n type: \'usage\';\n usage: TokenUsage;\n} | {\n type: \'finish\';\n reason: FinishReason;\n replayState?: ReplayEnvelope;\n};', }, { name: 'SubagentCapabilities', @@ -5373,7 +5373,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ToolExecution', - declaration: 'export interface ToolExecution extends ToolExecutionInput {\n readonly rootCallId: CallId;\n readonly token: ToolExecutionToken;\n}', + declaration: 'export interface ToolExecution extends ToolExecutionInput {\n readonly rootCallId: ToolCallId;\n readonly token: ToolExecutionToken;\n}', }, { name: 'ToolExecutionFailure', @@ -5381,7 +5381,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ToolExecutionInput', - declaration: 'export interface ToolExecutionInput {\n readonly callId: CallId;\n readonly rootCallId?: CallId;\n readonly name: string;\n readonly arguments: unknown;\n readonly agent?: Agent;\n readonly parent?: ToolExecutionToken;\n readonly signal: AbortSignal;\n}', + declaration: 'export interface ToolExecutionInput {\n readonly callId: ToolCallId;\n readonly rootCallId?: ToolCallId;\n readonly name: string;\n readonly arguments: unknown;\n readonly agent?: Agent;\n readonly parent?: ToolExecutionToken;\n readonly signal: AbortSignal;\n}', }, { name: 'ToolExecutionMode', @@ -5409,7 +5409,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ToolMessageSource', - declaration: 'export interface ToolMessageSource {\n kind: \'tool\';\n callId: CallId;\n}', + declaration: 'export interface ToolMessageSource {\n kind: \'tool\';\n callId: ToolCallId;\n}', }, { name: 'ToolOutputDefinition', @@ -5433,7 +5433,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ToolResultBlock', - declaration: 'export interface ToolResultBlock {\n type: \'tool-result\';\n toolCallId: CallId;\n content: ContentBlock[];\n isError?: boolean;\n}', + declaration: 'export interface ToolResultBlock {\n type: \'tool-result\';\n toolCallId: ToolCallId;\n content: ContentBlock[];\n isError?: boolean;\n}', }, { name: 'ToolResultMessage', diff --git a/packages/fs/tool-fs-search/tests/integration.spec.ts b/packages/fs/tool-fs-search/tests/integration.spec.ts index be1becbf25..94be0e1088 100644 --- a/packages/fs/tool-fs-search/tests/integration.spec.ts +++ b/packages/fs/tool-fs-search/tests/integration.spec.ts @@ -15,7 +15,7 @@ import { mkdir, mkdtemp, rm, utimes, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools' import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local' @@ -30,7 +30,7 @@ let callCounter = 0 function call(name: string, args: unknown, agentObj?: object) { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`it-${++callCounter}`), + callId: ToolCallId(`it-${++callCounter}`), name, arguments: args, ...agentObj ? { agent: agentObj as never } : {}, @@ -180,7 +180,7 @@ describe('search tools over the real subprocess service + the packaged rg', () = const controller = new AbortController() controller.abort() const result = await ctx.tools.execute({ - callId: CallId(`it-${++callCounter}`), + callId: ToolCallId(`it-${++callCounter}`), name: 'grep', arguments: { pattern: 'x' }, signal: controller.signal, diff --git a/packages/fs/tool-fs-search/tests/rg-path.spec.ts b/packages/fs/tool-fs-search/tests/rg-path.spec.ts index 3ad75edaaf..7369ef521c 100644 --- a/packages/fs/tool-fs-search/tests/rg-path.spec.ts +++ b/packages/fs/tool-fs-search/tests/rg-path.spec.ts @@ -8,7 +8,7 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import type { ToolExecution } from '@deepseek-ai/dsh-tools' import { resolveRgPath, runRipgrep } from '@deepseek-ai/dsh-tool-fs-search' @@ -24,7 +24,7 @@ describe('lazy packaged-ripgrep resolution', () => { it('fails the first search call with SEARCH_FAILED instead of failing module load', async () => { // The resolution rejects before any spawn, so no subprocess service is needed. const controller = new AbortController() - const exec = { signal: controller.signal, name: 'glob', callId: CallId('missing-platform-package') } as unknown as ToolExecution + const exec = { signal: controller.signal, name: 'glob', callId: ToolCallId('missing-platform-package') } as unknown as ToolExecution await expect(runRipgrep(new Context(), exec, 'glob', ['--files'], 1_000_000, 3_000, 64 * 1024)) .rejects.toMatchObject({ name: 'SearchError', code: 'SEARCH_FAILED' }) diff --git a/packages/fs/tool-fs-search/tests/tools.spec.ts b/packages/fs/tool-fs-search/tests/tools.spec.ts index abb7e75773..465000ba37 100644 --- a/packages/fs/tool-fs-search/tests/tools.spec.ts +++ b/packages/fs/tool-fs-search/tests/tools.spec.ts @@ -13,7 +13,7 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { join, sep } from 'node:path' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED_BEFORE_DISPATCH, type ToolExecution, type ToolExecutionToken } from '@deepseek-ai/dsh-tools' import { SubprocessRuntime } from '@deepseek-ai/dsh-subprocess' @@ -213,7 +213,7 @@ function call( ) { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, ...options.agent ? { agent: options.agent as never } : {}, @@ -467,7 +467,7 @@ describe('workdir derivation and signal forwarding', () => { const { ctx } = await setup() const controller = new AbortController() controller.abort() - const exec = { signal: controller.signal, name: 'glob', callId: CallId('direct-pre-abort') } as unknown as ToolExecution + const exec = { signal: controller.signal, name: 'glob', callId: ToolCallId('direct-pre-abort') } as unknown as ToolExecution await expect(runRipgrep(ctx, exec, 'glob', ['--files'], 1_000_000, 3_000, 64 * 1024)).rejects .toMatchObject({ name: 'SearchError', code: 'SEARCH_ABORTED' }) }) diff --git a/packages/fs/tool-fs/tests/integration.spec.ts b/packages/fs/tool-fs/tests/integration.spec.ts index 0d185399d4..28315545c1 100644 --- a/packages/fs/tool-fs/tests/integration.spec.ts +++ b/packages/fs/tool-fs/tests/integration.spec.ts @@ -10,7 +10,7 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools' import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local' @@ -29,7 +29,7 @@ let callCounter = 0 function call(name: string, args: unknown) { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, agent: { session } as never, @@ -393,7 +393,7 @@ describe('per-session cwd', () => { const callIn = (sessionObj: object, name: string, args: unknown) => ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, agent: { session: sessionObj } as never, @@ -436,9 +436,9 @@ describe('signal, concurrency, and the fs/observed contract', () => { const session = { header: {} } const callSig = (signal: AbortSignal, name: string, args: unknown) => - ctx.tools.execute({ callId: CallId(`c-${++callCounter}`), name, arguments: args, agent: { session } as never, signal }) + ctx.tools.execute({ callId: ToolCallId(`c-${++callCounter}`), name, arguments: args, agent: { session } as never, signal }) const callOwned = (name: string, args: unknown) => - ctx.tools.execute({ signal: testToolSignal, callId: CallId(`c-${++callCounter}`), name, arguments: args, agent: { session } as never }) + ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId(`c-${++callCounter}`), name, arguments: args, agent: { session } as never }) it('a pre-aborted registry call skips read/write/edit with ABORTED_BEFORE_DISPATCH', async () => { await writeFile(join(dir, 'a.txt'), 'hello') diff --git a/packages/fs/tool-fs/tests/read-image.spec.ts b/packages/fs/tool-fs/tests/read-image.spec.ts index 6b33b3dcb3..583685a699 100644 --- a/packages/fs/tool-fs/tests/read-image.spec.ts +++ b/packages/fs/tool-fs/tests/read-image.spec.ts @@ -12,7 +12,7 @@ import { join } from 'node:path' import { Context } from '@deepseek-ai/cordis' import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime' import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime' -import { CallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm' +import { ToolCallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, LlmModelInfo, LlmResolvedModelInfo, Message, StreamChunk } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools' @@ -138,7 +138,7 @@ let callCounter = 0 function call(ctx: Context, name: string, args: unknown, agent?: object) { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`img-call-${++callCounter}`), + callId: ToolCallId(`img-call-${++callCounter}`), name, arguments: args, ...agent ? { agent: agent as never } : {}, @@ -587,7 +587,7 @@ describe('registration surface', () => { it('declares read_image parallel-safe and presents a read-family card', async () => { const ctx = await setup() expect(ctx.tools.executionMode({ - signal: testToolSignal, callId: CallId('img-parallel'), name: 'read_image', arguments: { file_path: 'a.png' }, + signal: testToolSignal, callId: ToolCallId('img-parallel'), name: 'read_image', arguments: { file_path: 'a.png' }, })).toEqual({ kind: 'parallel' }) expect(ctx.tools.get('read_image')?.presentCall?.({ file_path: 'shot.png' })).toEqual({ card: 'generic', diff --git a/packages/fs/tool-fs/tests/tools.spec.ts b/packages/fs/tool-fs/tests/tools.spec.ts index cad6531766..6a1fc07bba 100644 --- a/packages/fs/tool-fs/tests/tools.spec.ts +++ b/packages/fs/tool-fs/tests/tools.spec.ts @@ -8,7 +8,7 @@ import { Context } from '@deepseek-ai/cordis' import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from 'node:fs' import { tmpdir } from 'node:os' import { join, resolve, sep } from 'node:path' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { type ToolResult } from '@deepseek-ai/dsh-tools' import { FileSystem, FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs' @@ -113,7 +113,7 @@ let callCounter = 0 function call(ctx: Context, name: string, args: unknown, agent?: object) { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, ...agent ? { agent: agent as never } : {}, @@ -158,11 +158,11 @@ describe('registration', () => { it('declares read parallel-safe while write/edit remain exclusive', async () => { const { ctx } = await setup() - expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } })) + expect(ctx.tools.executionMode({ signal: testToolSignal, callId: ToolCallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } })) .toEqual({ kind: 'parallel' }) - expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } })) + expect(ctx.tools.executionMode({ signal: testToolSignal, callId: ToolCallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } })) .toEqual({ kind: 'exclusive' }) - expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } })) + expect(ctx.tools.executionMode({ signal: testToolSignal, callId: ToolCallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } })) .toEqual({ kind: 'exclusive' }) }) @@ -881,7 +881,7 @@ describe('sandbox escalation API (write/edit)', () => { // Pass a signal so the escalation ask forwards it to the approval request // (the request rides the tool-execution abort signal). await ctx.tools.execute({ - callId: CallId('call-fs-esc-grant'), + callId: ToolCallId('call-fs-esc-grant'), name: 'write', arguments: { file_path: 'a.txt', content: 'x', sandbox_permissions: 'danger-full-access', justification: 'the test needs it' }, agent: escalationAgent() as never, diff --git a/packages/fs/tool-str-replace-editor/tests/tools.spec.ts b/packages/fs/tool-str-replace-editor/tests/tools.spec.ts index fce0c37950..1523de3d12 100644 --- a/packages/fs/tool-str-replace-editor/tests/tools.spec.ts +++ b/packages/fs/tool-str-replace-editor/tests/tools.spec.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { FsVersion } from '@deepseek-ai/dsh-fs' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -55,7 +55,7 @@ function text(result: { content: { type: string; text?: string }[] }): string { function call(ctx: Context, owner: Agent | undefined, args: unknown) { return ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId(`str-replace-editor-${++callNumber}`), + callId: ToolCallId(`str-replace-editor-${++callNumber}`), name: 'str_replace_editor', arguments: args, ...owner === undefined ? {} : { agent: owner }, diff --git a/packages/goal/tool-goal/tests/tool-goal.spec.ts b/packages/goal/tool-goal/tests/tool-goal.spec.ts index cb5a2a9874..5fc0215c0b 100644 --- a/packages/goal/tool-goal/tests/tool-goal.spec.ts +++ b/packages/goal/tool-goal/tests/tool-goal.spec.ts @@ -5,7 +5,7 @@ import AgentRegistry, { agentEvents, Inbox } from '@deepseek-ai/dsh-agent' import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent' import GoalService, { GoalId } from '@deepseek-ai/dsh-goal' import type { GoalRef } from '@deepseek-ai/dsh-goal' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import type { MessageSource } from '@deepseek-ai/dsh-llm' import { SESSION_FORMAT_VERSION, Session, SessionId } from '@deepseek-ai/dsh-session' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' @@ -91,7 +91,7 @@ async function execute( ): Promise { const run = () => ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${Math.random()}`), + callId: ToolCallId(`call-${Math.random()}`), name, arguments: args, ...agent === undefined ? {} : { agent }, @@ -123,7 +123,7 @@ describe('goal tool registration and presentation', () => { expect(['create_goal', 'get_goal', 'update_goal'].map(name => ctx.tools.get(name)?.name)) .toEqual(['create_goal', 'get_goal', 'update_goal']) for (const name of ['create_goal', 'get_goal', 'update_goal']) { - expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId(name), name, arguments: {} })) + expect(ctx.tools.executionMode({ signal: testToolSignal, callId: ToolCallId(name), name, arguments: {} })) .toEqual({ kind: 'exclusive' }) } const section = (await ctx.systemPrompt.assemble()).sections.find(item => item.name === 'tool:goal') @@ -217,7 +217,7 @@ describe('goal tool execution authority', () => { openTurn(root, { kind: 'user' }) const driverless = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('call-driverless'), + callId: ToolCallId('call-driverless'), name: 'get_goal', arguments: {}, agent: root.agent, diff --git a/packages/guard/repeat-tool-reminder/tests/repeat-tool-reminder.spec.ts b/packages/guard/repeat-tool-reminder/tests/repeat-tool-reminder.spec.ts index c2dda01558..ea0f0ceef8 100644 --- a/packages/guard/repeat-tool-reminder/tests/repeat-tool-reminder.spec.ts +++ b/packages/guard/repeat-tool-reminder/tests/repeat-tool-reminder.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session' import { defineContentToolFixture } from '@deepseek-ai/dsh-tools' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -293,7 +293,7 @@ describe('chain semantics', () => { it('ignores direct executes with no agent (they neither crash nor advance any chain)', async () => { const ctx = await harness({ thresholds: [2] }) - const direct = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('d1'), name: 'probe', arguments: { q: 1 } }) + const direct = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('d1'), name: 'probe', arguments: { q: 1 } }) expect(direct.isError).toBe(false) ctx.llm.registerAdapter(['mock'], new MockAdapter([ diff --git a/packages/guard/timeout-policy/tests/timeout-policy.spec.ts b/packages/guard/timeout-policy/tests/timeout-policy.spec.ts index 73c4783b69..a987abfa64 100644 --- a/packages/guard/timeout-policy/tests/timeout-policy.spec.ts +++ b/packages/guard/timeout-policy/tests/timeout-policy.spec.ts @@ -9,7 +9,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 { CallId, HarnessError } from '@deepseek-ai/dsh-llm' +import { ToolCallId, HarnessError } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { defineContentToolFixture, TOOL_ABORTED, type ToolExecutionInput, type PostToolDecision } from '@deepseek-ai/dsh-tools' import * as timeoutPolicy from '@deepseek-ai/dsh-tool-call-timeout-policy' @@ -52,7 +52,7 @@ describe('timeout-policy delegation (unconfigured / fast)', () => { ctx.tools.register(defineContentToolFixture({ name: 'probe', description: 'd', parameters: {}, async execute(_a, exec) { seenSignal = exec.signal; return [{ type: 'text' as const, text: 'ok' }] } })) const upstream = new AbortController().signal - const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'probe', arguments: {}, signal: upstream }) + const result = await ctx.tools.execute({ callId: ToolCallId('c1'), name: 'probe', arguments: {}, signal: upstream }) expect(result.isError).toBe(false) expect(seenSignal).toBe(upstream) }) @@ -61,7 +61,7 @@ describe('timeout-policy delegation (unconfigured / fast)', () => { const ctx = await setup() ctx.tools.register(defineContentToolFixture({ name: 'fast', description: 'd', parameters: {}, timeoutMs: 10_000, async execute() { return [{ type: 'text' as const, text: 'ok' }] } })) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'fast', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'fast', arguments: {} }) expect(result).toEqual({ content: [{ type: 'text', text: 'ok' }], isError: false, @@ -75,7 +75,7 @@ describe('timeout-policy delegation (unconfigured / fast)', () => { ctx.tools.register(defineContentToolFixture({ name: 'probe', description: 'd', parameters: {}, timeoutMs: 10_000, async execute(_a, exec) { seenSignal = exec.signal; return [{ type: 'text' as const, text: 'ok' }] } })) const upstream = new AbortController().signal - await ctx.tools.execute({ callId: CallId('c1'), name: 'probe', arguments: {}, signal: upstream }) + await ctx.tools.execute({ callId: ToolCallId('c1'), name: 'probe', arguments: {}, signal: upstream }) expect(seenSignal).toBeDefined() expect(seenSignal).not.toBe(upstream) }) @@ -89,7 +89,7 @@ describe('timeout-policy signal restoration', () => { let postSignal: AbortSignal | undefined | 'unset' = 'unset' ctx.on('tools/post-execute', async (exec, _result, next): Promise => { postSignal = exec.signal; return next() }) const upstream = new AbortController().signal - await ctx.tools.execute({ callId: CallId('c1'), name: 'fast', arguments: {}, signal: upstream }) + await ctx.tools.execute({ callId: ToolCallId('c1'), name: 'fast', arguments: {}, signal: upstream }) expect(postSignal).toBe(upstream) }) }) @@ -101,7 +101,7 @@ describe('timeout-policy TOOL_TIMEOUT replacement (deadline wins)', () => { it('replaces a cooperative tool result with TOOL_TIMEOUT when its own deadline fires', async () => { const ctx = await setup() ctx.tools.register(cooperativeTool) - const pending = ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'slow', arguments: {} }) + const pending = ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'slow', arguments: {} }) await vi.advanceTimersByTimeAsync(150) const result = await pending expect(result).toEqual({ @@ -117,7 +117,7 @@ describe('timeout-policy TOOL_TIMEOUT replacement (deadline wins)', () => { it('replaces a provider-owned abort ERROR result with TOOL_TIMEOUT when the signal was ours', async () => { const ctx = await setup() ctx.tools.register(abortThrowingTool) - const pending = ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'aborter', arguments: {} }) + const pending = ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'aborter', arguments: {} }) await vi.advanceTimersByTimeAsync(150) const result = await pending expect(result.isError).toBe(true) @@ -143,7 +143,7 @@ describe('timeout-policy TOOL_TIMEOUT replacement (deadline wins)', () => { }, })) const upstream = new AbortController() - const pending = ctx.tools.execute({ callId: CallId('c1'), name: 'slow', arguments: {}, signal: upstream.signal }) + const pending = ctx.tools.execute({ callId: ToolCallId('c1'), name: 'slow', arguments: {}, signal: upstream.signal }) await entered.promise upstream.abort('user cancelled') await vi.advanceTimersByTimeAsync(0) @@ -175,7 +175,7 @@ describe('timeout-policy TOOL_TIMEOUT replacement (deadline wins)', () => { })) const upstream = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('timeout-first'), name: 'slow-cleanup', arguments: {}, signal: upstream.signal, + callId: ToolCallId('timeout-first'), name: 'slow-cleanup', arguments: {}, signal: upstream.signal, }) await vi.advanceTimersByTimeAsync(100) @@ -209,10 +209,10 @@ describe('timeout-policy disposal (HMR safety)', () => { async execute(_a, exec) { seenSignal = exec.signal; return [{ type: 'text' as const, text: 'ok' }] } })) const fiber = await ctx.plugin(timeoutPolicy) const upstream = new AbortController().signal - await ctx.tools.execute({ callId: CallId('c1'), name: 'probe', arguments: {}, signal: upstream }) + await ctx.tools.execute({ callId: ToolCallId('c1'), name: 'probe', arguments: {}, signal: upstream }) expect(seenSignal).not.toBe(upstream) await fiber.dispose() - await ctx.tools.execute({ callId: CallId('c2'), name: 'probe', arguments: {}, signal: upstream }) + await ctx.tools.execute({ callId: ToolCallId('c2'), name: 'probe', arguments: {}, signal: upstream }) expect(seenSignal).toBe(upstream) }) }) @@ -237,7 +237,7 @@ describe('dsh-tool-call-timeout-policy real-load-path guard', () => { const loader = Object.create(Loader.prototype) as Loader const unwrapped = loader.unwrapExports(timeoutPolicy) as Parameters[0] const fiber = await ctx.plugin(unwrapped) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'fast', arguments: {} } satisfies ToolExecutionInput) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'fast', arguments: {} } satisfies ToolExecutionInput) expect(result.isError).toBe(false) await fiber.dispose() }) diff --git a/packages/hooks/hooks-claude-code/tests/coverage-cases.ts b/packages/hooks/hooks-claude-code/tests/coverage-cases.ts index 08e20e3e49..416010d69f 100644 --- a/packages/hooks/hooks-claude-code/tests/coverage-cases.ts +++ b/packages/hooks/hooks-claude-code/tests/coverage-cases.ts @@ -153,8 +153,8 @@ export function defineCoverageCases(group: CoverageGroup): void { const ctx = await harness(path, new MockAdapter([])) let ran = false ctx.tools.register(defineContentToolFixture({ name: 'echo', description: 'e', parameters: {}, async execute() { ran = true; return [{ type: 'text', text: 'x' }] } })) - const { CallId } = await import('@deepseek-ai/dsh-llm') - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: {} }) + const { ToolCallId } = await import('@deepseek-ai/dsh-llm') + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'echo', arguments: {} }) expect(ran).toBe(false) expect(result.isError).toBe(true) }) diff --git a/packages/hooks/hooks-codex/tests/coverage-cases.ts b/packages/hooks/hooks-codex/tests/coverage-cases.ts index 53742e2e96..dcb5cd4443 100644 --- a/packages/hooks/hooks-codex/tests/coverage-cases.ts +++ b/packages/hooks/hooks-codex/tests/coverage-cases.ts @@ -458,8 +458,8 @@ export function defineCoverageCases(groups: CoverageGroup | readonly CoverageGro const ctx = await harness(join(d, 'hooks.json'), new MockAdapter([])) let ran = false ctx.tools.register(defineContentToolFixture({ name: 'Bash', description: 'b', parameters: { command: { type: 'string' } }, async execute() { ran = true; return [{ type: 'text', text: 'x' }] } })) - const { CallId } = await import('@deepseek-ai/dsh-llm') - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'Bash', arguments: { command: 'x' } }) + const { ToolCallId } = await import('@deepseek-ai/dsh-llm') + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'Bash', arguments: { command: 'x' } }) expect(ran).toBe(false) // denied expect(result.isError).toBe(true) }) @@ -469,8 +469,8 @@ export function defineCoverageCases(groups: CoverageGroup | readonly CoverageGro hooks(d, { PostToolUse: [{ hooks: [{ type: 'command', command: sh(d, 'pc.sh', '#!/usr/bin/env bash\necho \'{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"x"}}\'\n') }] }] }) const ctx = await harness(join(d, 'hooks.json'), new MockAdapter([])) ctx.tools.register(defineContentToolFixture({ name: 'Bash', description: 'b', parameters: { command: { type: 'string' } }, async execute() { return [{ type: 'text', text: 'ok' }] } })) - const { CallId } = await import('@deepseek-ai/dsh-llm') - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'Bash', arguments: { command: 'x' } }) + const { ToolCallId } = await import('@deepseek-ai/dsh-llm') + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'Bash', arguments: { command: 'x' } }) expect(result.isError).toBeFalsy() expect(result.additionalContexts?.[0]?.content.some(b => b.type === 'text' && b.text === 'x')).toBe(true) }) diff --git a/packages/interaction/tool-ask-user/tests/tool-ask-user.spec.ts b/packages/interaction/tool-ask-user/tests/tool-ask-user.spec.ts index 2b63067475..071a9a2048 100644 --- a/packages/interaction/tool-ask-user/tests/tool-ask-user.spec.ts +++ b/packages/interaction/tool-ask-user/tests/tool-ask-user.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime from '@deepseek-ai/dsh-tools' @@ -98,7 +98,7 @@ describe('ask_user_question tool', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ask-1'), + callId: ToolCallId('ask-1'), name: 'ask_user_question', arguments: { questions: [{ @@ -134,7 +134,7 @@ describe('ask_user_question tool', () => { await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ask-recommended'), + callId: ToolCallId('ask-recommended'), name: 'ask_user_question', arguments: { questions: [{ @@ -170,7 +170,7 @@ describe('ask_user_question tool', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ask-multi'), + callId: ToolCallId('ask-multi'), name: 'ask_user_question', arguments: { questions: [ @@ -218,7 +218,7 @@ describe('ask_user_question tool', () => { const controller = new AbortController() await ctx.tools.execute({ - callId: CallId('ask-2'), + callId: ToolCallId('ask-2'), name: 'ask_user_question', arguments: { questions: [{ id: 'continue', question: 'Continue?' }] }, signal: controller.signal, @@ -241,7 +241,7 @@ describe('ask_user_question tool', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ask-3'), + callId: ToolCallId('ask-3'), name: 'ask_user_question', arguments: { questions: [{ id: 'continue', header: 'Confirm', question: 'Continue?' }] }, agent, @@ -256,7 +256,7 @@ describe('ask_user_question tool', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ask-no-provider'), + callId: ToolCallId('ask-no-provider'), name: 'ask_user_question', arguments: { questions: [{ id: 'continue', question: 'Continue?' }] }, }) @@ -283,7 +283,7 @@ describe('ask_user_question tool', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ask-delegated'), + callId: ToolCallId('ask-delegated'), name: 'ask_user_question', arguments: { questions: [{ id: 'continue', question: 'Continue?' }] }, agent: child, @@ -305,7 +305,7 @@ describe('ask_user_question tool', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ask-empty'), + callId: ToolCallId('ask-empty'), name: 'ask_user_question', arguments: { questions: [] }, }) diff --git a/packages/interaction/user-approval/src/index.ts b/packages/interaction/user-approval/src/index.ts index 5d03b3186c..2608643ca0 100644 --- a/packages/interaction/user-approval/src/index.ts +++ b/packages/interaction/user-approval/src/index.ts @@ -8,7 +8,7 @@ import { randomUUID } from 'node:crypto' import { Context, Service } from '@deepseek-ai/cordis' import z from '@deepseek-ai/schemastery' import type { Agent } from '@deepseek-ai/dsh-agent' -import { createUserMessage, type CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, type ToolCallId } from '@deepseek-ai/dsh-llm' import { scopeTarget } from '@deepseek-ai/dsh-scope' import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' import type {} from '@deepseek-ai/dsh-system-prompt' @@ -128,7 +128,7 @@ export 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 /** diff --git a/packages/interaction/user-approval/src/types.ts b/packages/interaction/user-approval/src/types.ts index 9a4185601d..862d1739f0 100644 --- a/packages/interaction/user-approval/src/types.ts +++ b/packages/interaction/user-approval/src/types.ts @@ -8,7 +8,7 @@ import type { Branded } from '@deepseek-ai/dsh-brand' import type { Scoped } from '@deepseek-ai/dsh-scope' import type { Agent } from '@deepseek-ai/dsh-agent/types' -import type { CallId } from '@deepseek-ai/dsh-llm/brand' +import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand' /** * Pairs one `approval/asked` audit event with its `approval/decided`. @@ -44,7 +44,7 @@ declare module '@deepseek-ai/dsh-session/types' { 'approval/asked': { id: ApprovalRequestId toolName: string - callId?: CallId + callId?: ToolCallId reason?: string } /** @@ -66,7 +66,7 @@ export interface ApprovalRequestEvent { /** Tool whose operation requires a decision. */ readonly toolName: string /** Exact tool call being decided, when available. */ - readonly callId?: CallId + readonly callId?: ToolCallId /** Human-readable reason supplied by the asker. */ readonly reason?: string /** Cancellation lifetime of the pending request. */ diff --git a/packages/interaction/user-approval/tests/approval.spec.ts b/packages/interaction/user-approval/tests/approval.spec.ts index 3271ecc7a5..eb0484a1db 100644 --- a/packages/interaction/user-approval/tests/approval.spec.ts +++ b/packages/interaction/user-approval/tests/approval.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import type { Agent } from '@deepseek-ai/dsh-agent' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { carrierKeyOf, createScope } from '@deepseek-ai/dsh-scope' import type { Scope } from '@deepseek-ai/dsh-scope' import SessionStore, { Session, SessionId } from '@deepseek-ai/dsh-session' @@ -60,7 +60,7 @@ describe('ApprovalService.request', () => { const ctx = await mounted() const { agent, appended } = fakeAgent() - const outcome = await ctx.approval.request(requestOf(agent, { callId: CallId('call-1'), reason: 'hook says ask' })) + const outcome = await ctx.approval.request(requestOf(agent, { callId: ToolCallId('call-1'), reason: 'hook says ask' })) expect(outcome).toBe('unavailable') expect(appended.map(e => e.type)).toEqual(['approval/asked', 'approval/decided']) @@ -95,7 +95,7 @@ describe('ApprovalService.request', () => { }) const request = requestOf(agent, { toolName: 'scoped-tool', - callId: CallId('scoped-call'), + callId: ToolCallId('scoped-call'), reason: 'scoped reason', }) diff --git a/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts b/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts index 29fc44a11b..4c6a81e48a 100644 --- a/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts +++ b/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime from '@deepseek-ai/dsh-tools' import AgentRegistry, { emitAgentEvent } from '@deepseek-ai/dsh-agent' @@ -92,7 +92,7 @@ function producer(overrides: Partial & JobHooks> = {}) { let callCounter = 0 function call(ctx: Context, name: string, args: unknown, agent?: Agent) { - return ctx.tools.execute({ signal: testToolSignal, callId: CallId(`call-${++callCounter}`), name, arguments: args, ...agent ? { agent } : {} }) + return ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, ...agent ? { agent } : {} }) } function text(result: { content: { type: string; text?: string }[] }): string { diff --git a/packages/llm/llm-deepseek/src/translate.ts b/packages/llm/llm-deepseek/src/translate.ts index 7b5022e31a..732422b502 100644 --- a/packages/llm/llm-deepseek/src/translate.ts +++ b/packages/llm/llm-deepseek/src/translate.ts @@ -8,7 +8,7 @@ * @module dsh-llm-deepseek/translate */ -import { CallId, EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm' +import { ToolCallId, EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm' import type { ContentBlock, FinishReason, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm' import { DONE } from './sse.ts' import type { WireChunk, WireUsage } from './types.ts' @@ -77,7 +77,7 @@ function closeBlock(block: OpenBlock): ContentBlock { case 'reasoning': return { type: 'reasoning', text: block.text } case 'tool-call': return { type: 'tool-call', - id: CallId(block.callId ?? ''), + id: ToolCallId(block.callId ?? ''), name: block.name ?? '', arguments: block.text, } @@ -172,7 +172,7 @@ export async function* translate(payloads: AsyncIterable): AsyncGenerato yield { type: 'tool-call-delta', index: block.index, - id: CallId(block.callId ?? ''), + id: ToolCallId(block.callId ?? ''), ...block.name !== undefined ? { name: block.name } : {}, argumentsDelta: fragment, } diff --git a/packages/llm/llm-deepseek/tests/adapter.e2e.ts b/packages/llm/llm-deepseek/tests/adapter.e2e.ts index 6e7c3307f7..cf3d511063 100644 --- a/packages/llm/llm-deepseek/tests/adapter.e2e.ts +++ b/packages/llm/llm-deepseek/tests/adapter.e2e.ts @@ -7,7 +7,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 AgentRegistry from '@deepseek-ai/dsh-agent' -import LlmRuntime, { createUserMessage, CallId, ReasoningEffortId, createMessage } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { createUserMessage, ToolCallId, ReasoningEffortId, createMessage } from '@deepseek-ai/dsh-llm' import type { Message, ToolSchema } from '@deepseek-ai/dsh-llm' import AttachmentStore, { AttachmentId, ImageVariantId } from '@deepseek-ai/dsh-attachment' import type { @@ -299,7 +299,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('llm-deepseek e2e (real API)', () createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId(call!.id), + toolCallId: ToolCallId(call!.id), content: [{ type: 'text', text: 'Sunny, 22°C' }], }], source: { kind: 'plugin', plugin: 'test' }, diff --git a/packages/llm/llm-deepseek/tests/adapter.spec.ts b/packages/llm/llm-deepseek/tests/adapter.spec.ts index 50145d0d32..46d51efc43 100644 --- a/packages/llm/llm-deepseek/tests/adapter.spec.ts +++ b/packages/llm/llm-deepseek/tests/adapter.spec.ts @@ -6,7 +6,7 @@ import { Context } from '@deepseek-ai/cordis' import { AttachmentId, ImageVariantId } from '@deepseek-ai/dsh-attachment' import type { AttachmentStore, ImageAttachmentRef, RequestImageAttachment } from '@deepseek-ai/dsh-attachment' import { createLaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment' -import LlmRuntime, { CallId, createUserMessage, +import LlmRuntime, { ToolCallId, createUserMessage, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, ProviderRequestId, @@ -660,7 +660,7 @@ describe('DeepSeekAdapter against a mock server', () => { const nested = createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId('image-result'), + toolCallId: ToolCallId('image-result'), content: [{ type: 'image', attachment: imageRef }], }], source: { kind: 'plugin', plugin: 'test' }, diff --git a/packages/llm/llm-deepseek/tests/serialize.spec.ts b/packages/llm/llm-deepseek/tests/serialize.spec.ts index 0240a6c22a..97b4b62127 100644 --- a/packages/llm/llm-deepseek/tests/serialize.spec.ts +++ b/packages/llm/llm-deepseek/tests/serialize.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { AttachmentId, ImageVariantId } from '@deepseek-ai/dsh-attachment' import type { ImageAttachmentRef, ImageMediaType, RequestImageAttachment } from '@deepseek-ai/dsh-attachment' -import { createUserMessage, CallId, ReasoningEffortId, createMessage } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, ReasoningEffortId, createMessage } from '@deepseek-ai/dsh-llm' import type { ContentBlock, GenerateOptions, Message } from '@deepseek-ai/dsh-llm' import { serializeMessages, @@ -122,7 +122,7 @@ describe('serializeMessages', () => { role: 'assistant', content: [ { type: 'reasoning', text: 'I should check the weather.' }, - { type: 'tool-call', id: CallId('call-1'), name: 'get_weather', arguments: '{"city":"Paris"}' }, + { type: 'tool-call', id: ToolCallId('call-1'), name: 'get_weather', arguments: '{"city":"Paris"}' }, ], source: { kind: 'plugin', plugin: 'test' }, }), @@ -142,8 +142,8 @@ describe('serializeMessages', () => { createMessage({ role: 'assistant', content: [ - { type: 'tool-call', id: CallId('a'), name: 'one', arguments: '{}' }, - { type: 'tool-call', id: CallId('b'), name: 'two', arguments: '{}' }, + { type: 'tool-call', id: ToolCallId('a'), name: 'one', arguments: '{}' }, + { type: 'tool-call', id: ToolCallId('b'), name: 'two', arguments: '{}' }, ], source: { kind: 'plugin', plugin: 'test' }, }), @@ -157,7 +157,7 @@ describe('serializeMessages', () => { createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId('call-1'), + toolCallId: ToolCallId('call-1'), content: [{ type: 'text', text: 'Sunny 22C' }], }], source: { kind: 'plugin', plugin: 'test' }, @@ -169,7 +169,7 @@ describe('serializeMessages', () => { it('sends a sentinel for empty tool-result content', () => { const wire = serializeMessages([ createUserMessage({ - content: [{ type: 'tool-result', toolCallId: CallId('call-1'), content: [] }], + content: [{ type: 'tool-result', toolCallId: ToolCallId('call-1'), content: [] }], source: { kind: 'plugin', plugin: 'test' }, }), ]) @@ -181,7 +181,7 @@ describe('serializeMessages', () => { createUserMessage({ content: [ { type: 'text', text: 'context note' }, - { type: 'tool-result', toolCallId: CallId('call-1'), content: [{ type: 'text', text: 'ok' }] }, + { type: 'tool-result', toolCallId: ToolCallId('call-1'), content: [{ type: 'text', text: 'ok' }] }, ], source: { kind: 'plugin', plugin: 'test' }, }), @@ -459,7 +459,7 @@ describe('image serialization', () => { createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId('first'), + toolCallId: ToolCallId('first'), content: [{ type: 'image', attachment: imageRef() }], }], source: { kind: 'plugin', plugin: 'test' }, @@ -467,7 +467,7 @@ describe('image serialization', () => { createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId('second'), + toolCallId: ToolCallId('second'), content: [ { type: 'text', text: 'caption' }, { type: 'image', attachment: imageRef('image/jpeg') }, @@ -511,7 +511,7 @@ describe('image serialization', () => { { type: 'chart', data: 'ignored' } as unknown as ContentBlock, { type: 'tool-result', - toolCallId: CallId('result'), + toolCallId: ToolCallId('result'), content: [{ type: 'text', text: 'ok' }], }, ], @@ -528,14 +528,14 @@ describe('image serialization', () => { content: [ { type: 'tool-result', - toolCallId: CallId('nested'), + toolCallId: ToolCallId('nested'), content: [{ type: 'tool-result', - toolCallId: CallId('inner'), + toolCallId: ToolCallId('inner'), content: [{ type: 'text', text: 'inside' }], }], }, - { type: 'tool-result', toolCallId: CallId('empty'), content: [] }, + { type: 'tool-result', toolCallId: ToolCallId('empty'), content: [] }, ], source: { kind: 'plugin', plugin: 'test' }, })] @@ -550,7 +550,7 @@ describe('image serialization', () => { const imageResult = (id: string) => createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId(id), + toolCallId: ToolCallId(id), content: [{ type: 'image', attachment: imageRef() }], }], source: { kind: 'plugin' as const, plugin: 'test' }, @@ -734,7 +734,7 @@ describe('review fixes: assistant content shapes', () => { it('serializes tool-call turns with empty string content, not null', () => { const wire = serializeMessages([createMessage({ role: 'assistant', - content: [{ type: 'tool-call', id: CallId('c'), name: 'f', arguments: '{}' }], + content: [{ type: 'tool-call', id: ToolCallId('c'), name: 'f', arguments: '{}' }], source: { kind: 'plugin', plugin: 'test' }, })]) expect(wire[0]).toMatchObject({ content: '' }) diff --git a/packages/llm/llm-pi-ai/src/context.ts b/packages/llm/llm-pi-ai/src/context.ts index 4315cc690e..133883867e 100644 --- a/packages/llm/llm-pi-ai/src/context.ts +++ b/packages/llm/llm-pi-ai/src/context.ts @@ -4,7 +4,7 @@ * @module dsh-llm-pi-ai/context */ -import { CallId, contentHasImage, LlmError, offloadedImageText, offloadRequestImagesWithPolicy, requestImageHandleText } from '@deepseek-ai/dsh-llm' +import { ToolCallId, contentHasImage, LlmError, offloadedImageText, offloadRequestImagesWithPolicy, requestImageHandleText } from '@deepseek-ai/dsh-llm' import type { ContentBlock, GenerateOptions, ImageAttachmentAccessResolver, Message } from '@deepseek-ai/dsh-llm' import type { AttachmentId, @@ -138,7 +138,7 @@ function piContext(options: GenerateOptions, messages: PiMessage[]): PiContext { } function textOnlyContext(options: GenerateOptions, onReplayDegrade?: (reason: string) => void): PiContext { - const toolNames = new Map() + const toolNames = new Map() const messages: PiMessage[] = [] for (const message of options.messages) { if (contentHasImage(message.content)) { @@ -150,7 +150,7 @@ function textOnlyContext(options: GenerateOptions, onReplayDegrade?: (reason: st } if (message.role === 'assistant') { const assistant = toPiAssistant(message, onReplayDegrade) - for (const block of assistant.content) if (block.type === 'toolCall') toolNames.set(CallId(block.id), block.name) + for (const block of assistant.content) if (block.type === 'toolCall') toolNames.set(ToolCallId(block.id), block.name) messages.push(assistant) continue } @@ -251,7 +251,7 @@ async function toPiContextWithImages( byteLength: ref => (requestImages.get(ref.attachmentId) as RequestImageAttachment).bytes, placeholder: ref => offloadedImageText(ref, resolveImageAccess(ref)), }) - const toolNames = new Map() + const toolNames = new Map() const messages: PiMessage[] = [] for (const message of exactMessages) { @@ -265,7 +265,7 @@ async function toPiContextWithImages( if (message.role === 'assistant') { const assistant = toPiAssistant(message, onReplayDegrade) for (const block of assistant.content) { - if (block.type === 'toolCall') toolNames.set(CallId(block.id), block.name) + if (block.type === 'toolCall') toolNames.set(ToolCallId(block.id), block.name) } messages.push(assistant) continue diff --git a/packages/llm/llm-pi-ai/src/stream.ts b/packages/llm/llm-pi-ai/src/stream.ts index 4aa7584f35..43c4ff9ec6 100644 --- a/packages/llm/llm-pi-ai/src/stream.ts +++ b/packages/llm/llm-pi-ai/src/stream.ts @@ -8,7 +8,7 @@ * @module dsh-llm-pi-ai/stream */ -import { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm' +import { ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm' import type { FinishReason, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm' import { isContextOverflow } from '@earendil-works/pi-ai' import type { AssistantMessage, AssistantMessageEvent, Usage as PiUsage } from '@earendil-works/pi-ai' @@ -182,7 +182,7 @@ export async function* toStreamChunks( yield { type: 'tool-call-delta', index: event.contentIndex, - id: CallId(known?.id ?? ''), + id: ToolCallId(known?.id ?? ''), ...known?.name !== undefined && known.name.length > 0 ? { name: known.name } : {}, argumentsDelta: event.delta, } @@ -194,7 +194,7 @@ export async function* toStreamChunks( index: event.contentIndex, block: { type: 'tool-call', - id: CallId(event.toolCall.id), + id: ToolCallId(event.toolCall.id), name: event.toolCall.name, // pi-ai hands back the PARSED arguments; the harness vocabulary // keeps the raw string. diff --git a/packages/llm/llm-pi-ai/tests/adapter.e2e.ts b/packages/llm/llm-pi-ai/tests/adapter.e2e.ts index 0a7a589e07..99b2be8721 100644 --- a/packages/llm/llm-pi-ai/tests/adapter.e2e.ts +++ b/packages/llm/llm-pi-ai/tests/adapter.e2e.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import LlmRuntime, { createUserMessage, CallId, ReasoningEffortId } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { createUserMessage, ToolCallId, ReasoningEffortId } from '@deepseek-ai/dsh-llm' import type { Message, ToolSchema } from '@deepseek-ai/dsh-llm' import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai' import type { PiAiProviderProfile } from '@deepseek-ai/dsh-llm-pi-ai' @@ -129,7 +129,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('llm-pi-ai e2e (real API)', () => createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId(call!.id), + toolCallId: ToolCallId(call!.id), content: [{ type: 'text', text: 'Sunny, 22°C' }], }], source: { kind: 'plugin', plugin: 'test' }, diff --git a/packages/llm/llm-pi-ai/tests/context.spec.ts b/packages/llm/llm-pi-ai/tests/context.spec.ts index 4e6e29c118..902a288caa 100644 --- a/packages/llm/llm-pi-ai/tests/context.spec.ts +++ b/packages/llm/llm-pi-ai/tests/context.spec.ts @@ -6,7 +6,7 @@ import type { ImageRequestPolicy, RequestImageAttachment, } from '@deepseek-ai/dsh-attachment' -import { CallId, createMessage, createUserMessage, offloadedImageText } from '@deepseek-ai/dsh-llm' +import { ToolCallId, createMessage, createUserMessage, offloadedImageText } from '@deepseek-ai/dsh-llm' import type { ContentBlock, GenerateOptions, Message } from '@deepseek-ai/dsh-llm' import { toPiContext } from '../src/context.ts' import type { PiImageRequestContext } from '../src/context.ts' @@ -82,7 +82,7 @@ describe('pi-ai request context conversion', () => { }) it('converts complete text-only history and rejects nested images without storage', () => { - const callId = CallId('call-1') + const callId = ToolCallId('call-1') expect(toPiContext(request([ history('system', [{ type: 'text', text: 'history system' }]), history('assistant', [{ type: 'tool-call', id: callId, name: 'lookup', arguments: '{}' }]), @@ -119,8 +119,8 @@ describe('pi-ai request context conversion', () => { }) it('resolves user and tool-result images while preserving explicit fallbacks', async () => { - const callId = CallId('missing-call') - const knownCallId = CallId('known-call') + const callId = ToolCallId('missing-call') + const knownCallId = ToolCallId('known-call') const context = await toPiContext(request([ user([{ type: 'text', text: '' }]), history('assistant', [ @@ -204,7 +204,7 @@ describe('pi-ai request context conversion', () => { }) it('recursively converts nested tool-result text and images', async () => { - const callId = CallId('nested-call') + const callId = ToolCallId('nested-call') const context = await toPiContext(request([user([{ type: 'tool-result', toolCallId: callId, @@ -237,7 +237,7 @@ describe('pi-ai request context conversion', () => { }) it('flattens nested text-only tool results and ignores other block types without storage', () => { - const callId = CallId('nested-text') + const callId = ToolCallId('nested-text') expect(toPiContext(request([user([{ type: 'tool-result', toolCallId: callId, @@ -263,7 +263,7 @@ describe('pi-ai request context conversion', () => { )) const store = projectionStore(readImageRequest) const sized: ImageAttachmentRef = { ...ref, bytes: 3 } - const callId = CallId('shot-call') + const callId = ToolCallId('shot-call') // Three 3-byte images cost 4 base64 characters each (12 total); a bound of // 8 forces exactly the oldest one out, including one nested in a tool result. const context = await toPiContext(request([ @@ -417,12 +417,12 @@ describe('pi-ai request context conversion', () => { }) it('keeps empty text-only users while separating result-only messages', () => { - const callId = CallId('unknown-call') + const callId = ToolCallId('unknown-call') expect(toPiContext(request([ user([]), history('assistant', [ { type: 'text', text: 'answer' }, - { type: 'tool-call', id: CallId('other-call'), name: 'lookup', arguments: '{}' }, + { type: 'tool-call', id: ToolCallId('other-call'), name: 'lookup', arguments: '{}' }, ]), user([{ type: 'tool-result', diff --git a/packages/llm/llm-pi-ai/tests/convert.spec.ts b/packages/llm/llm-pi-ai/tests/convert.spec.ts index 8c1d940676..c80df43df1 100644 --- a/packages/llm/llm-pi-ai/tests/convert.spec.ts +++ b/packages/llm/llm-pi-ai/tests/convert.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { AttachmentId, ImageVariantId } from '@deepseek-ai/dsh-attachment' import type { AttachmentStore, ImageAttachmentRef, ImageRequestPolicy, RequestImageAttachment } from '@deepseek-ai/dsh-attachment' -import { createUserMessage, CallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, createMessage } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, createMessage } from '@deepseek-ai/dsh-llm' import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm' import type { AssistantMessage, AssistantMessageEvent, Usage } from '@earendil-works/pi-ai' import { toPiContext } from '../src/context.ts' @@ -148,14 +148,14 @@ describe('toPiContext', () => { messages: [createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId('outer'), + toolCallId: ToolCallId('outer'), content: [ - { type: 'tool-result', toolCallId: CallId('empty'), content: [] }, + { type: 'tool-result', toolCallId: ToolCallId('empty'), content: [] }, { type: 'text', text: 'before' }, - { type: 'tool-result', toolCallId: CallId('text'), content: [{ type: 'text', text: 'middle' }] }, + { type: 'tool-result', toolCallId: ToolCallId('text'), content: [{ type: 'text', text: 'middle' }] }, { type: 'tool-result', - toolCallId: CallId('inner'), + toolCallId: ToolCallId('inner'), content: [ { type: 'image', attachment }, { type: 'text', text: 'after' }, @@ -208,7 +208,7 @@ describe('toPiContext', () => { content: [ { type: 'reasoning', text: 'hmm' }, { type: 'text', text: 'calling' }, - { type: 'tool-call', id: CallId('c1'), name: 'f', arguments: '{"a":1}' }, + { type: 'tool-call', id: ToolCallId('c1'), name: 'f', arguments: '{"a":1}' }, ], source: { kind: 'plugin', plugin: 'test' }, })], @@ -259,7 +259,7 @@ describe('toPiContext', () => { model: 'm', messages: [createMessage({ role: 'assistant', - content: [{ type: 'tool-call', id: CallId('c1'), name: 'f', arguments: '{broken' }], + content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'f', arguments: '{broken' }], source: { kind: 'plugin', plugin: 'test' }, })], }) @@ -273,7 +273,7 @@ describe('toPiContext', () => { model: 'm', messages: [createMessage({ role: 'assistant', - content: [{ type: 'tool-call', id: CallId('c1'), name: 'f', arguments: '[1,2]' }], + content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'f', arguments: '[1,2]' }], source: { kind: 'plugin', plugin: 'test' }, })], }) @@ -287,16 +287,16 @@ describe('toPiContext', () => { messages: [ createMessage({ role: 'assistant', - content: [{ type: 'tool-call', id: CallId('c1'), name: 'get_weather', arguments: '{}' }], + content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'get_weather', arguments: '{}' }], source: { kind: 'plugin', plugin: 'test' }, }), createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId('c1'), + toolCallId: ToolCallId('c1'), content: [ { type: 'text', text: 'Sunny' }, - { type: 'tool-result', toolCallId: CallId('nested'), content: [{ type: 'text', text: '!' }] }, + { type: 'tool-result', toolCallId: ToolCallId('nested'), content: [{ type: 'text', text: '!' }] }, { type: 'chart', data: 'ignored' } as unknown as ContentBlock, ], }], @@ -319,7 +319,7 @@ describe('toPiContext', () => { provider: 'deepseek', model: 'm', messages: [createUserMessage({ - content: [{ type: 'tool-result', toolCallId: CallId('zz'), content: [], isError: true }], + content: [{ type: 'tool-result', toolCallId: ToolCallId('zz'), content: [], isError: true }], source: { kind: 'plugin', plugin: 'test' }, })], }) @@ -343,7 +343,7 @@ describe('toPiContext', () => { createUserMessage({ content: [ { type: 'text', text: 'note' }, - { type: 'tool-result', toolCallId: CallId('c1'), content: [{ type: 'text', text: 'ok' }] }, + { type: 'tool-result', toolCallId: ToolCallId('c1'), content: [{ type: 'text', text: 'ok' }] }, ], source: { kind: 'plugin', plugin: 'test' }, }), @@ -390,7 +390,7 @@ describe('toPiContext', () => { content: [ { type: 'reasoning', text: 'private reasoning' }, { type: 'text', text: 'calling' }, - { type: 'tool-call', id: CallId('c1'), name: 'f', arguments: '{"a":1}' }, + { type: 'tool-call', id: ToolCallId('c1'), name: 'f', arguments: '{"a":1}' }, ], source: { kind: 'model', @@ -431,7 +431,7 @@ describe('toPiContext', () => { content: [ { type: 'reasoning', text: 'private reasoning' }, { type: 'text', text: 'calling' }, - { type: 'tool-call', id: CallId('c1'), name: 'f', arguments: '{"a":1}' }, + { type: 'tool-call', id: ToolCallId('c1'), name: 'f', arguments: '{"a":1}' }, ], source: { kind: 'model', diff --git a/packages/llm/llm-pi-ai/tests/provider-apis.e2e.ts b/packages/llm/llm-pi-ai/tests/provider-apis.e2e.ts index f651aa1f9a..c6fd5d6db3 100644 --- a/packages/llm/llm-pi-ai/tests/provider-apis.e2e.ts +++ b/packages/llm/llm-pi-ai/tests/provider-apis.e2e.ts @@ -10,7 +10,7 @@ import type { SaveImageAttachment, StoredImageAttachment, } from '@deepseek-ai/dsh-attachment' -import LlmRuntime, { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import type { Message, ToolSchema } from '@deepseek-ai/dsh-llm' import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai' import type { PiAiReplayResponse } from '../src/replay.ts' @@ -212,7 +212,7 @@ for (const profile of providerCases) { createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId(call!.id), + toolCallId: ToolCallId(call!.id), content: [{ type: 'text', text: 'The code blue means ocean.' }], }], source: { kind: 'plugin', plugin: 'test' }, diff --git a/packages/llm/llm-retry/tests/retry.spec.ts b/packages/llm/llm-retry/tests/retry.spec.ts index c2643a6d45..bc1ed6bab6 100644 --- a/packages/llm/llm-retry/tests/retry.spec.ts +++ b/packages/llm/llm-retry/tests/retry.spec.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, expectTypeOf, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import type { Fiber } from '@deepseek-ai/cordis' -import LlmRuntime, { createUserMessage, CallId, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, resolveRetryPolicy } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { createUserMessage, ToolCallId, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, resolveRetryPolicy } from '@deepseek-ai/dsh-llm' import type { AlwaysRetryPolicyConfig, BackoffConfig, @@ -60,7 +60,7 @@ class ScriptedAdapter extends LlmAdapter { } async function* partialToolFailure(error: Error): AsyncGenerator { - const id = CallId('discarded-call') + const id = ToolCallId('discarded-call') yield { type: 'block-start', index: 0, blockType: 'text' } yield { type: 'text-delta', index: 0, text: 'discarded partial output' } yield { type: 'block-end', index: 0, block: { type: 'text', text: 'discarded partial output' } } diff --git a/packages/llm/llm/src/assembler.ts b/packages/llm/llm/src/assembler.ts index 7dc5221c9b..96472d09e9 100644 --- a/packages/llm/llm/src/assembler.ts +++ b/packages/llm/llm/src/assembler.ts @@ -6,7 +6,7 @@ * @module @deepseek-ai/dsh-llm/assembler */ -import { CallId } from './brand.ts' +import { ToolCallId } from './brand.ts' import { assertNever } from './never.ts' import { createMessage } from './message.ts' import type { Message, MessageSource } from './message.ts' @@ -15,7 +15,7 @@ import type { ContentBlock, FinishReason, ReplayEnvelope, StreamChunk, TokenUsag interface PartialBlock { blockType: string text: string - toolCallId?: CallId + toolCallId?: ToolCallId toolCallName?: string toolCallArguments: string /** Set by `block-end` — authoritative, and freezes the partial. */ @@ -111,7 +111,7 @@ export class BlockAssembler { case 'reasoning': return { type: 'reasoning', text: partial.text } case 'tool-call': return { type: 'tool-call', - id: partial.toolCallId ?? CallId(`call-${index}`), + id: partial.toolCallId ?? ToolCallId(`call-${index}`), name: partial.toolCallName ?? '', arguments: partial.toolCallArguments, } diff --git a/packages/llm/llm/src/brand.ts b/packages/llm/llm/src/brand.ts index e0b3a38c8e..42410829fa 100644 --- a/packages/llm/llm/src/brand.ts +++ b/packages/llm/llm/src/brand.ts @@ -28,15 +28,15 @@ export function MessageId(id: string): MessageId { * Correlates a model-issued tool call with its result. Provider-issued for * real adapters; synthesized by mocks/assembler fallbacks. */ -export type CallId = Branded<'CallId'> +export type ToolCallId = Branded<'ToolCallId'> /** - * Brand a string as a {@link CallId}. + * Brand a string as a {@link ToolCallId}. * @param id - the provider-issued (or synthesized) call id. * @returns the same string, branded; no validation is performed. */ -export function CallId(id: string): CallId { - return id as CallId +export function ToolCallId(id: string): ToolCallId { + return id as ToolCallId } /** Provider-issued request identifier retained for diagnostics across package boundaries. */ diff --git a/packages/llm/llm/src/message.ts b/packages/llm/llm/src/message.ts index f4e47ebc62..b4aadf83e4 100644 --- a/packages/llm/llm/src/message.ts +++ b/packages/llm/llm/src/message.ts @@ -1,7 +1,7 @@ /** Message value types, identity, and immutable construction helpers. */ import { randomUUID } from '@deepseek-ai/dsh-util-crypto' -import { MessageId, type CallId } from './brand.ts' +import { MessageId, type ToolCallId } from './brand.ts' import { deepFreeze } from './call-config.ts' import type { ContentBlock, ToolResultBlock } from './types.ts' @@ -27,7 +27,7 @@ export interface ModelMessageSource extends AssistantProvenance { /** Required source of a user-role message carrying one tool result. */ export interface ToolMessageSource { kind: 'tool' - callId: CallId + callId: ToolCallId } /** @@ -219,7 +219,7 @@ export function createAssistantMessage( /** Input whose acceptance creates one tool-result message. */ export interface ToolResultMessageInput { - readonly callId: CallId + readonly callId: ToolCallId readonly content: ContentBlock[] readonly isError: boolean } diff --git a/packages/llm/llm/src/types.ts b/packages/llm/llm/src/types.ts index c9ee739f8d..e7dadc6b20 100644 --- a/packages/llm/llm/src/types.ts +++ b/packages/llm/llm/src/types.ts @@ -6,7 +6,7 @@ import type { Branded } from '@deepseek-ai/dsh-brand' import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment' -import type { CallId, ProviderRequestId, ReasoningEffortId } from './brand.ts' +import type { ToolCallId, ProviderRequestId, ReasoningEffortId } from './brand.ts' import type { Message } from './message.ts' declare module '@deepseek-ai/cordis' { @@ -78,7 +78,7 @@ export interface ImageBlock { export interface ToolCallBlock { type: 'tool-call' /** Provider-issued call id; correlates with the matching tool result. */ - id: CallId + id: ToolCallId name: string /** Raw JSON string as produced by the model. */ arguments: string @@ -87,7 +87,7 @@ export interface ToolCallBlock { /** The result of a tool invocation, sent back to the model. */ export interface ToolResultBlock { type: 'tool-result' - toolCallId: CallId + toolCallId: ToolCallId content: ContentBlock[] isError?: boolean } @@ -351,7 +351,7 @@ export 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 } | { diff --git a/packages/llm/llm/tests/assembler.spec.ts b/packages/llm/llm/tests/assembler.spec.ts index 2ecfcfd860..4a4a73324e 100644 --- a/packages/llm/llm/tests/assembler.spec.ts +++ b/packages/llm/llm/tests/assembler.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { BlockAssembler, CallId, type StreamChunk } from '@deepseek-ai/dsh-llm' +import { BlockAssembler, ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm' describe('BlockAssembler', () => { it('assembles interleaved text, reasoning, and tool-call deltas', () => { @@ -11,8 +11,8 @@ describe('BlockAssembler', () => { { type: 'text-delta', index: 1, text: 'Hello' }, { type: 'text-delta', index: 1, text: ' world' }, { type: 'block-start', index: 2, blockType: 'tool-call' }, - { type: 'tool-call-delta', index: 2, id: CallId('call-1'), name: 'echo', argumentsDelta: '{"text":' }, - { type: 'tool-call-delta', index: 2, id: CallId('call-1'), argumentsDelta: '"hi"}' }, + { type: 'tool-call-delta', index: 2, id: ToolCallId('call-1'), name: 'echo', argumentsDelta: '{"text":' }, + { type: 'tool-call-delta', index: 2, id: ToolCallId('call-1'), argumentsDelta: '"hi"}' }, { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } }, { type: 'finish', reason: { kind: 'tool-calls' } }, ] @@ -22,7 +22,7 @@ describe('BlockAssembler', () => { expect(assembler.blocks()).toEqual([ { type: 'reasoning', text: 'thinking…' }, { type: 'text', text: 'Hello world' }, - { type: 'tool-call', id: CallId('call-1'), name: 'echo', arguments: '{"text":"hi"}' }, + { type: 'tool-call', id: ToolCallId('call-1'), name: 'echo', arguments: '{"text":"hi"}' }, ]) expect(assembler.usage).toEqual({ inputTokens: 10, outputTokens: 5 }) expect(assembler.finish).toEqual({ kind: 'tool-calls' }) @@ -93,11 +93,11 @@ describe('BlockAssembler', () => { it('ignores tool-call-delta stragglers after block-end', () => { const assembler = new BlockAssembler() assembler.push({ type: 'block-start', index: 0, blockType: 'tool-call' }) - assembler.push({ type: 'tool-call-delta', index: 0, id: CallId('c1'), name: 'echo', argumentsDelta: '{}' }) - assembler.push({ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{}' } }) + assembler.push({ type: 'tool-call-delta', index: 0, id: ToolCallId('c1'), name: 'echo', argumentsDelta: '{}' }) + assembler.push({ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' } }) // straggler after block-end — partial.block is set, so early return - assembler.push({ type: 'tool-call-delta', index: 0, id: CallId('c1'), name: 'evil', argumentsDelta: 'oops' }) - expect(assembler.blocks()).toEqual([{ type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{}' }]) + assembler.push({ type: 'tool-call-delta', index: 0, id: ToolCallId('c1'), name: 'evil', argumentsDelta: 'oops' }) + expect(assembler.blocks()).toEqual([{ type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' }]) }) it('assembles tool-call with generated id fallback when no id provided', () => { @@ -106,7 +106,7 @@ describe('BlockAssembler', () => { // No id and no name provided — uses fallback id `call-{index}` and empty name const blocks = assembler.blocks() expect(blocks).toEqual([ - { type: 'tool-call', id: CallId('call-0'), name: '', arguments: '{}' }, + { type: 'tool-call', id: ToolCallId('call-0'), name: '', arguments: '{}' }, ]) }) @@ -127,7 +127,7 @@ describe('BlockAssembler replay metadata', () => { assembler.push({ type: 'block-end', index: 1, - block: { type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{"text":' }, + block: { type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{"text":' }, }) assembler.push({ type: 'block-end', index: 2, block: { type: 'reasoning', text: 'tail' } }) assembler.push({ @@ -164,7 +164,7 @@ describe('BlockAssembler replay metadata', () => { assembler.push({ type: 'block-end', index: 1, - block: { type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{}' }, + block: { type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' }, }) assembler.push({ type: 'finish', reason: { kind: 'tool-calls' }, replayState }) @@ -178,7 +178,7 @@ describe('BlockAssembler replay metadata', () => { assembler.push({ type: 'block-end', index: 1, - block: { type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{"text":' }, + block: { type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{"text":' }, }) assembler.push({ type: 'finish', reason: { kind: 'max-tokens' }, replayState }) @@ -245,10 +245,10 @@ describe('BlockAssembler.interruptedBlocks', () => { assembler.push({ type: 'text-delta', index: 0, text: 'calling' }) assembler.push({ type: 'block-end', index: 0, block: { type: 'text', text: 'calling' } }) assembler.push({ type: 'block-start', index: 1, blockType: 'tool-call' }) - assembler.push({ type: 'tool-call-delta', index: 1, id: CallId('c1'), name: 'read', argumentsDelta: '{"a":1}' }) - assembler.push({ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c1'), name: 'read', arguments: '{"a":1}' } }) + assembler.push({ type: 'tool-call-delta', index: 1, id: ToolCallId('c1'), name: 'read', argumentsDelta: '{"a":1}' }) + assembler.push({ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'read', arguments: '{"a":1}' } }) assembler.push({ type: 'block-start', index: 2, blockType: 'tool-call' }) - assembler.push({ type: 'tool-call-delta', index: 2, id: CallId('c2'), name: 'read', argumentsDelta: '{"pa' }) + assembler.push({ type: 'tool-call-delta', index: 2, id: ToolCallId('c2'), name: 'read', argumentsDelta: '{"pa' }) expect(assembler.interruptedBlocks()).toEqual([{ type: 'text', text: 'calling' }]) }) diff --git a/packages/llm/llm/tests/content.spec.ts b/packages/llm/llm/tests/content.spec.ts index b17499b0f6..748e20f412 100644 --- a/packages/llm/llm/tests/content.spec.ts +++ b/packages/llm/llm/tests/content.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' import { AttachmentId, ImageVariantId } from '@deepseek-ai/dsh-attachment' import type { AttachmentStore, ImageMediaType } from '@deepseek-ai/dsh-attachment' import { - CallId, + ToolCallId, createUserMessage, offloadedImageText, offloadedImagePrefixCount, @@ -75,7 +75,7 @@ describe('base64 request-image offload', () => { createUserMessage({ content: [{ type: 'tool-result', - toolCallId: CallId('shot'), + toolCallId: ToolCallId('shot'), content: [shared], }], source, @@ -87,7 +87,7 @@ describe('base64 request-image offload', () => { expect(fitted).not.toBe(messages) expect(fitted[0]?.content).toEqual([{ type: 'tool-result', - toolCallId: CallId('shot'), + toolCallId: ToolCallId('shot'), content: [{ type: 'text', text: OMITTED }], }]) expect(fitted[1]?.content).toEqual([shared, image(3)]) @@ -103,7 +103,7 @@ describe('base64 request-image offload', () => { it('keeps unchanged nested content while replacing a later image', () => { const nested = { type: 'tool-result' as const, - toolCallId: CallId('text-only'), + toolCallId: ToolCallId('text-only'), content: [{ type: 'text' as const, text: 'kept' }], } const messages = [createUserMessage({ content: [nested, image(3)], source })] @@ -330,12 +330,12 @@ describe('projectImagesForTextModel', () => { const plain = createUserMessage({ content: [{ type: 'text', text: 'plain' }], source }) const nested = { type: 'tool-result' as const, - toolCallId: CallId('nested-image'), + toolCallId: ToolCallId('nested-image'), content: [{ type: 'text' as const, text: 'before' }, image(3), { type: 'text' as const, text: 'after' }], } const unchangedNested = { type: 'tool-result' as const, - toolCallId: CallId('text-only'), + toolCallId: ToolCallId('text-only'), content: [{ type: 'text' as const, text: 'unchanged' }], } const visual = createUserMessage({ diff --git a/packages/llm/llm/tests/invariant.spec.ts b/packages/llm/llm/tests/invariant.spec.ts index 2d5744d22f..fdb538f5dc 100644 --- a/packages/llm/llm/tests/invariant.spec.ts +++ b/packages/llm/llm/tests/invariant.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import LlmRuntime, { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import * as LlmInvariant from '@deepseek-ai/dsh-llm/invariant' import InvariantRegistry from '@deepseek-ai/dsh-invariants' @@ -38,8 +38,8 @@ describe('LLM stream invariants', () => { { type: 'block-end', index: 1, block: { type: 'reasoning', text: 'b' } }, { type: 'block-end', index: 0, block: { type: 'text', text: 'a' } }, { type: 'block-start', index: 2, blockType: 'tool-call' }, - { type: 'tool-call-delta', index: 2, id: CallId('c1'), name: 'echo', argumentsDelta: '{}' }, - { type: 'block-end', index: 2, block: { type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{}' } }, + { type: 'tool-call-delta', index: 2, id: ToolCallId('c1'), name: 'echo', argumentsDelta: '{}' }, + { type: 'block-end', index: 2, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' } }, { type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } }, finish, ] diff --git a/packages/llm/llm/tests/message.spec.ts b/packages/llm/llm/tests/message.spec.ts index 846d37c9df..3d458611d2 100644 --- a/packages/llm/llm/tests/message.spec.ts +++ b/packages/llm/llm/tests/message.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { - CallId, + ToolCallId, createAssistantMessage, createToolResultMessage, createUserMessage, @@ -75,7 +75,7 @@ describe('message construction', () => { }) it('couples tool-result content and its cited call seq to one call identity', () => { - const callId = CallId('call-1') + const callId = ToolCallId('call-1') const message = createToolResultMessage({ callId, content: [{ type: 'text', text: 'result' }], diff --git a/packages/llm/llm/tests/properties.spec.ts b/packages/llm/llm/tests/properties.spec.ts index 31d07c1a47..22f854b378 100644 --- a/packages/llm/llm/tests/properties.spec.ts +++ b/packages/llm/llm/tests/properties.spec.ts @@ -11,7 +11,7 @@ import { describe, expect, it } from 'vitest' import fc from 'fast-check' import { BlockAssembler } from '@deepseek-ai/dsh-llm' import type { StreamChunk } from '@deepseek-ai/dsh-llm' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' // A small pool of indices so collisions (duplicate-index bugs) are common. const indexArb = fc.integer({ min: 0, max: 4 }) @@ -24,7 +24,7 @@ const blockEndArb = (index: number): fc.Arbitrary => fc.oneof( { type: 'block-end', index, block: { type: 'reasoning', text: r.text } } )), fc.record({ id: fc.string({ minLength: 1 }), name: fc.string(), args: fc.string() }).map((r): StreamChunk => ( - { type: 'block-end', index, block: { type: 'tool-call', id: CallId(r.id), name: r.name, arguments: r.args } } + { type: 'block-end', index, block: { type: 'tool-call', id: ToolCallId(r.id), name: r.name, arguments: r.args } } )), ) @@ -36,7 +36,7 @@ const chunkArb: fc.Arbitrary = indexArb.chain(index => fc.oneof( fc.string().map((text): StreamChunk => ({ type: 'text-delta', index, text })), fc.string().map((text): StreamChunk => ({ type: 'reasoning-delta', index, text })), fc.record({ id: fc.string({ minLength: 1 }), argumentsDelta: fc.string() }) - .map((r): StreamChunk => ({ type: 'tool-call-delta', index, id: CallId(r.id), argumentsDelta: r.argumentsDelta })), + .map((r): StreamChunk => ({ type: 'tool-call-delta', index, id: ToolCallId(r.id), argumentsDelta: r.argumentsDelta })), blockEndArb(index), fc.constant({ type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } }), fc.constant({ type: 'finish', reason: { kind: 'stop' } }), diff --git a/packages/llm/token-meter/tests/token-meter.spec.ts b/packages/llm/token-meter/tests/token-meter.spec.ts index 0175bdf9de..84f1dd1788 100644 --- a/packages/llm/token-meter/tests/token-meter.spec.ts +++ b/packages/llm/token-meter/tests/token-meter.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, 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 type { ContentBlock, Message, TokenUsage } from '@deepseek-ai/dsh-llm' import SessionStore, { Session, SessionId, canonicalHeader } from '@deepseek-ai/dsh-session' import type { EpochHeader, SessionEvent } from '@deepseek-ai/dsh-session' @@ -134,10 +134,10 @@ describe('TokenMeter pricing', () => { const blocks: ContentBlock[] = [ { type: 'text', text: 'abcd' }, { type: 'reasoning', text: 'ab' }, - { type: 'tool-call', id: CallId('c'), name: 'read', arguments: '{"x":1}' }, + { type: 'tool-call', id: ToolCallId('c'), name: 'read', arguments: '{"x":1}' }, { type: 'tool-result', - toolCallId: CallId('c'), + toolCallId: ToolCallId('c'), content: [{ type: 'text', text: 'xy' }], isError: false, }, diff --git a/packages/mcp/mcp-client/tests/mcp-client.e2e.ts b/packages/mcp/mcp-client/tests/mcp-client.e2e.ts index cec7451db2..ee3db70a77 100644 --- a/packages/mcp/mcp-client/tests/mcp-client.e2e.ts +++ b/packages/mcp/mcp-client/tests/mcp-client.e2e.ts @@ -22,7 +22,7 @@ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' import LocalAttachmentStore from '@deepseek-ai/dsh-attachment-local' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime from '@deepseek-ai/dsh-tools' -import { CallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm' +import { ToolCallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm' import { apply } from '@deepseek-ai/dsh-mcp-client/src/index.ts' import { publicToolName } from '@deepseek-ai/dsh-mcp-client/src/tools.ts' @@ -87,8 +87,8 @@ function textOf(block: unknown): string { } let callSeq = 0 -function nextCallId(): CallId { - return CallId(`e2e-${++callSeq}`) +function nextCallId(): ToolCallId { + return ToolCallId(`e2e-${++callSeq}`) } // ---- Fixture server tests ---- diff --git a/packages/mcp/mcp-client/tests/mcp-client.spec.ts b/packages/mcp/mcp-client/tests/mcp-client.spec.ts index b72b8faad4..fef1f48d0c 100644 --- a/packages/mcp/mcp-client/tests/mcp-client.spec.ts +++ b/packages/mcp/mcp-client/tests/mcp-client.spec.ts @@ -4,7 +4,7 @@ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js' import { Context } from '@deepseek-ai/cordis' import AttachmentStore, { AttachmentError, AttachmentId } from '@deepseek-ai/dsh-attachment' import type { ImageAttachmentLimits, ImageAttachmentRef, SaveImageAttachment, StoredImageAttachment } from '@deepseek-ai/dsh-attachment' -import { CallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm' +import { ToolCallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm' import type { ContentBlock } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' @@ -228,7 +228,7 @@ describe('syncTools', () => { expect(ctx.tools.get('search')).toBeDefined() expect(ctx.tools.get('mcp__srv__search')).toBeDefined() - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'search', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'search', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: 'native' }) }) @@ -365,14 +365,14 @@ describe('syncTools', () => { const missing = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('missing'), name: 'mcp__srv__supported', arguments: {}, + callId: ToolCallId('missing'), name: 'mcp__srv__supported', arguments: {}, }) expect(missing.error).toMatchObject({ info: { code: 'INVALID_TOOL_OUTPUT' } }) expect(missing.error?.message).toContain('structuredContent') const fallback = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('fallback'), name: 'mcp__srv__future-schema', arguments: {}, + callId: ToolCallId('fallback'), name: 'mcp__srv__future-schema', arguments: {}, }) if (fallback.isError) throw new Error('unsupported schema must use the bridge fallback') expect(fallback.value).toEqual({ @@ -399,7 +399,7 @@ describe('tool execution', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__echo', arguments: { msg: 'hi' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__echo', arguments: { msg: 'hi' } }) expect(result.isError).toBe(false) expect(result.content).toEqual([{ type: 'text', text: 'hello world' }]) @@ -421,7 +421,7 @@ describe('tool execution', () => { await syncTools(client as never, ctx, defaultOpts, new Map()) const publicName = publicToolName('srv', 'admin.reset') - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: publicName, arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: publicName, arguments: {} }) expect(result.isError).toBe(false) expect(client.callTool).toHaveBeenCalledWith( @@ -438,7 +438,7 @@ describe('tool execution', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__multi', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__multi', arguments: {} }) expect(result.content).toEqual([{ type: 'text', text: 'line1\nline2' }]) }) @@ -460,7 +460,7 @@ describe('tool execution', () => { await syncTools(client as never, rich.ctx, defaultOpts, new Map()) const result = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('c1'), + callId: ToolCallId('c1'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -494,7 +494,7 @@ describe('tool execution', () => { await syncTools(client as never, ctx, defaultOpts, new Map()) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('no-store'), + callId: ToolCallId('no-store'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -522,7 +522,7 @@ describe('tool execution', () => { await syncTools(client as never, rich.ctx, defaultOpts, new Map()) const result = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('bad-batch'), + callId: ToolCallId('bad-batch'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -548,7 +548,7 @@ describe('tool execution', () => { await syncTools(client as never, rich.ctx, defaultOpts, new Map()) const result = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('strict-batch'), + callId: ToolCallId('strict-batch'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -571,7 +571,7 @@ describe('tool execution', () => { await syncTools(client as never, rich.ctx, defaultOpts, new Map()) const result = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('text-route'), + callId: ToolCallId('text-route'), name: 'mcp__srv__img', arguments: {}, agent: agentOn('text') as never, @@ -591,7 +591,7 @@ describe('tool execution', () => { const noProvider = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('no-provider'), + callId: ToolCallId('no-provider'), name: 'mcp__srv__img', arguments: {}, agent: { options: { model: 'vision' }, session: { requestHeader: () => undefined } } as never, @@ -600,7 +600,7 @@ describe('tool execution', () => { const noModel = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('no-model'), + callId: ToolCallId('no-model'), name: 'mcp__srv__img', arguments: {}, agent: { options: { provider: 'visual' }, session: { requestHeader: () => undefined } } as never, @@ -612,7 +612,7 @@ describe('tool execution', () => { await syncTools(client as never, noLlmCtx, defaultOpts, new Map()) const noLlm = await noLlmCtx.tools.execute({ signal: testToolSignal, - callId: CallId('no-llm'), + callId: ToolCallId('no-llm'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -622,7 +622,7 @@ describe('tool execution', () => { vi.spyOn(rich.ctx.llm, 'resolveModelInfo').mockRejectedValueOnce(new Error('catalog down')) const unverified = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('unverified'), + callId: ToolCallId('unverified'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -634,7 +634,7 @@ describe('tool execution', () => { }) const unknown = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('unknown-modalities'), + callId: ToolCallId('unknown-modalities'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -648,7 +648,7 @@ describe('tool execution', () => { }) const canceled = await rich.ctx.tools.execute({ signal: controller.signal, - callId: CallId('canceled'), + callId: ToolCallId('canceled'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -669,7 +669,7 @@ describe('tool execution', () => { await syncTools(client as never, rich.ctx, defaultOpts, new Map()) const result = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('store-rejected'), + callId: ToolCallId('store-rejected'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -691,7 +691,7 @@ describe('tool execution', () => { await syncTools(client as never, rich.ctx, defaultOpts, new Map()) const result = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('policy-rejected'), + callId: ToolCallId('policy-rejected'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -715,7 +715,7 @@ describe('tool execution', () => { await syncTools(client as never, rich.ctx, defaultOpts, new Map()) const result = await rich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('replaced'), + callId: ToolCallId('replaced'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -738,7 +738,7 @@ describe('tool execution', () => { await syncTools(valueClient as never, valueRich.ctx, defaultOpts, new Map()) const replaced = await valueRich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('value-replaced'), + callId: ToolCallId('value-replaced'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -757,7 +757,7 @@ describe('tool execution', () => { await syncTools(blockedClient as never, blockedRich.ctx, defaultOpts, new Map()) const blocked = await blockedRich.ctx.tools.execute({ signal: testToolSignal, - callId: CallId('blocked'), + callId: ToolCallId('blocked'), name: 'mcp__srv__img', arguments: {}, agent: agentOn() as never, @@ -776,7 +776,7 @@ describe('tool execution', () => { await syncTools(client as never, ctx, defaultOpts, new Map()) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('primitive'), name: 'mcp__srv__primitive-blocks', arguments: {}, + callId: ToolCallId('primitive'), name: 'mcp__srv__primitive-blocks', arguments: {}, }) expect(result.content[0]).toEqual({ @@ -799,7 +799,7 @@ describe('tool execution', () => { { content: [{ type: 'text', text: '42' }], structuredContent: { answer: 42 } }, ) await syncTools(valid as never, ctx, defaultOpts, new Map()) - const success = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('valid'), name: 'mcp__srv__structured', arguments: {} }) + const success = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('valid'), name: 'mcp__srv__structured', arguments: {} }) if (success.isError) throw new Error('expected supported structuredContent to validate') expect(success.value).toEqual({ content: [{ type: 'text', text: '42' }], structuredContent: { answer: 42 } }) @@ -809,7 +809,7 @@ describe('tool execution', () => { { content: [{ type: 'text', text: 'wrong' }], structuredContent: { answer: 'forty-two' } }, ) await syncTools(invalid as never, invalidCtx, defaultOpts, new Map()) - const failure = await invalidCtx.tools.execute({ signal: testToolSignal, callId: CallId('invalid'), name: 'mcp__srv__structured', arguments: {} }) + const failure = await invalidCtx.tools.execute({ signal: testToolSignal, callId: ToolCallId('invalid'), name: 'mcp__srv__structured', arguments: {} }) expect(failure.error).toMatchObject({ info: { code: 'INVALID_TOOL_OUTPUT' } }) expect(failure.content[0]?.type === 'text' ? failure.content[0].text : '') .toContain('value.structuredContent.answer') @@ -825,7 +825,7 @@ describe('tool execution', () => { { content: [], structuredContent: ['kept', { nested: true }] }, ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('fallback'), name: 'mcp__srv__future-schema', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('fallback'), name: 'mcp__srv__future-schema', arguments: {} }) if (result.isError) throw new Error('unsupported MCP output schemas must fall back') expect(result.value).toEqual({ content: [], structuredContent: ['kept', { nested: true }] }) }) @@ -837,7 +837,7 @@ describe('tool execution', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__fail', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__fail', arguments: {} }) expect(result.isError).toBe(true) expect(result.content[0]).toEqual({ type: 'text', text: 'Error: something went wrong' }) @@ -852,7 +852,7 @@ describe('tool execution', () => { await syncTools(client as never, ctx, defaultOpts, new Map()) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('task-only'), name: 'mcp__srv__task-only', arguments: {}, + callId: ToolCallId('task-only'), name: 'mcp__srv__task-only', arguments: {}, }) expect(result.isError).toBe(true) @@ -868,7 +868,7 @@ describe('tool execution', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - await ctx.tools.execute({ callId: CallId('c1'), name: 'mcp__srv__slow', arguments: {}, signal: controller.signal }) + await ctx.tools.execute({ callId: ToolCallId('c1'), name: 'mcp__srv__slow', arguments: {}, signal: controller.signal }) expect(client.callTool).toHaveBeenCalledWith( expect.anything(), @@ -884,7 +884,7 @@ describe('tool execution', () => { client.callTool.mockResolvedValue({ toolResult: { key: 'value' } }) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__legacy', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__legacy', arguments: {} }) expect(result.isError).toBe(false) expect(result.content[0]).toEqual({ type: 'text', text: '{"key":"value"}' }) @@ -900,7 +900,7 @@ describe('tool execution', () => { await syncTools(client as never, ctx, defaultOpts, new Map()) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('legacy-structured'), name: 'mcp__srv__legacy-structured', arguments: {}, + callId: ToolCallId('legacy-structured'), name: 'mcp__srv__legacy-structured', arguments: {}, }) if (result.isError) throw new Error('expected legacy structured result success') @@ -917,7 +917,7 @@ describe('tool execution', () => { await syncTools(client as never, ctx, defaultOpts, new Map()) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('legacy-error'), name: 'mcp__srv__legacy-error', arguments: {}, + callId: ToolCallId('legacy-error'), name: 'mcp__srv__legacy-error', arguments: {}, }) expect(result.isError).toBe(true) @@ -939,7 +939,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__audio_tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__audio_tool', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', @@ -954,7 +954,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__res_tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__res_tool', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', @@ -969,7 +969,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__link_tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__link_tool', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: 'Resource link: Design (https://example.test/design)' }) }) @@ -981,7 +981,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('missing-link'), name: 'mcp__srv__link_tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('missing-link'), name: 'mcp__srv__link_tool', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: '[resource link unavailable: the MCP block is missing its name or URI]', @@ -995,7 +995,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__unknown_tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__unknown_tool', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: '[unsupported MCP content type: video]' }) }) @@ -1007,7 +1007,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__img2', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__img2', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', @@ -1022,7 +1022,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__audio_no_mime', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__audio_no_mime', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', @@ -1037,7 +1037,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__notext', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__notext', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: '(notext returned no model-visible content)' }) }) @@ -1049,7 +1049,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__empty_tool', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__empty_tool', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: '(empty_tool returned no model-visible content)' }) }) @@ -1062,7 +1062,7 @@ describe('tool execution edge cases', () => { client.callTool.mockResolvedValue({ toolResult: undefined, structuredContent: undefined }) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__legacy2', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__legacy2', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: '(no output)' }) }) @@ -1074,7 +1074,7 @@ describe('tool execution edge cases', () => { client.callTool.mockResolvedValue({}) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('legacy-empty'), name: 'mcp__srv__legacy-empty', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('legacy-empty'), name: 'mcp__srv__legacy-empty', arguments: {} }) expect(result.content[0]).toEqual({ type: 'text', text: '(no output)' }) }) @@ -1086,7 +1086,7 @@ describe('tool execution edge cases', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__err_notext', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__err_notext', arguments: {} }) expect(result.isError).toBe(true) expect(result.content[0]).toEqual({ @@ -1228,7 +1228,7 @@ describe('tool execution — non-object args fallback', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__coerce', arguments: null }) + await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__coerce', arguments: null }) expect(client.callTool).toHaveBeenCalledWith( { name: 'coerce', arguments: {} }, @@ -1244,7 +1244,7 @@ describe('tool execution — non-object args fallback', () => { ) await syncTools(client as never, ctx, defaultOpts, new Map()) - await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__coerce2', arguments: 'bad' }) + await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'mcp__srv__coerce2', arguments: 'bad' }) expect(client.callTool).toHaveBeenCalledWith( { name: 'coerce2', arguments: {} }, diff --git a/packages/mcp/mcp-client/tests/reconnect.spec.ts b/packages/mcp/mcp-client/tests/reconnect.spec.ts index b7277ccb60..186d4dc345 100644 --- a/packages/mcp/mcp-client/tests/reconnect.spec.ts +++ b/packages/mcp/mcp-client/tests/reconnect.spec.ts @@ -8,7 +8,7 @@ import { describe, expect, it, vi, beforeEach } from 'vitest' import { Context } from '@deepseek-ai/cordis' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime from '@deepseek-ai/dsh-tools' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import type { Config } from '@deepseek-ai/dsh-mcp-client' // ---- Mock MCP SDK ---- @@ -115,8 +115,8 @@ function listing(...names: string[]): { tools: { name: string; inputSchema: { ty } let callSeq = 0 -function nextCallId(): CallId { - return CallId(`reconnect-${++callSeq}`) +function nextCallId(): ToolCallId { + return ToolCallId(`reconnect-${++callSeq}`) } // ---- Tests ---- diff --git a/packages/plan/plan-mode/tests/plan-mode.spec.ts b/packages/plan/plan-mode/tests/plan-mode.spec.ts index ec6eec8167..f08769c155 100644 --- a/packages/plan/plan-mode/tests/plan-mode.spec.ts +++ b/packages/plan/plan-mode/tests/plan-mode.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { RUN_CODE_NAME, defineContentToolFixture } from '@deepseek-ai/dsh-tools' import { Session, SessionId, type UserMessage } from '@deepseek-ai/dsh-session' @@ -151,7 +151,7 @@ function expectPlanCodeSdkBindings(sdk: string): void { let callCounter = 0 function execute(ctx: Context, name: string, agent?: Agent) { return ctx.tools.execute({ - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name, arguments: {}, signal: new AbortController().signal, @@ -754,7 +754,7 @@ describe('exit_plan_mode', () => { function callExit(ctx: Context, agent: Agent | undefined, plan = '# The plan\n\ndo things') { return ctx.tools.execute({ - callId: CallId(`call-exit-${++callCounter}`), + callId: ToolCallId(`call-exit-${++callCounter}`), name: EXIT_PLAN_MODE, arguments: { plan }, signal: new AbortController().signal, @@ -882,7 +882,7 @@ describe('exit_plan_mode', () => { const agent = await agentWithSession(ctx, 'code-mode-exit', { active: true }) const result = await ctx.tools.execute({ - callId: CallId(`call-exit-${++callCounter}`), + callId: ToolCallId(`call-exit-${++callCounter}`), name: RUN_CODE_NAME, arguments: { code: `return await tools.${EXIT_PLAN_MODE}({ plan: ${JSON.stringify(plan)} })`, description: 'Submit the plan for review' }, signal: new AbortController().signal, @@ -1032,7 +1032,7 @@ describe('exit_plan_mode', () => { const { ctx, agent, asked } = await setupWithReview({ selected: ['Approve'] }) const controller = new AbortController() const result = await ctx.tools.execute({ - callId: CallId(`call-exit-${++callCounter}`), + callId: ToolCallId(`call-exit-${++callCounter}`), name: EXIT_PLAN_MODE, arguments: { plan: '# P' }, agent, diff --git a/packages/sandbox/sandbox/src/escalation.ts b/packages/sandbox/sandbox/src/escalation.ts index e0b9a2ce63..e60b18daf6 100644 --- a/packages/sandbox/sandbox/src/escalation.ts +++ b/packages/sandbox/sandbox/src/escalation.ts @@ -97,7 +97,7 @@ export type EscalationOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'una * structurally the approval seam's `ApprovalService`, generic over the agent * type `A` and call-id type `C` so this package resolves escalations through * `ctx.approval` without importing the approval or agent packages (the tool - * layer infers `A`/`C` as its own `Agent`/`CallId`). + * layer infers `A`/`C` as its own `Agent`/`ToolCallId`). */ export interface EscalationApprover { /** diff --git a/packages/schedule/schedule/tests/plugin.spec.ts b/packages/schedule/schedule/tests/plugin.spec.ts index 2932ffa657..d4fa7e236a 100644 --- a/packages/schedule/schedule/tests/plugin.spec.ts +++ b/packages/schedule/schedule/tests/plugin.spec.ts @@ -4,7 +4,7 @@ import Loader from '@deepseek-ai/cordis-plugin-loader' import { agentEvents } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import * as toolSchedule from '../src/index.ts' @@ -51,7 +51,7 @@ describe('Schedule plugin composition', () => { const created = await ctx.agents.withInitiator(root.agent, () => ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId('schedule-plugin-create'), + callId: ToolCallId('schedule-plugin-create'), name: 'schedule_create', arguments: { prompt: 'future reminder', after_seconds: 3_600 }, agent: root.agent, diff --git a/packages/schedule/schedule/tests/tools.spec.ts b/packages/schedule/schedule/tests/tools.spec.ts index 63f080a678..b8ef0be6f8 100644 --- a/packages/schedule/schedule/tests/tools.spec.ts +++ b/packages/schedule/schedule/tests/tools.spec.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent, AgentCancelCause, InboxTarget } from '@deepseek-ai/dsh-agent' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import type { UserMessage } from '@deepseek-ai/dsh-llm' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' @@ -73,7 +73,7 @@ async function execute( ): Promise { return test.ctx.agents.withInitiator(agent, () => test.ctx.tools.execute({ signal: executionSignal, - callId: CallId(`call-${Math.random()}`), + callId: ToolCallId(`call-${Math.random()}`), name, arguments: args, agent, @@ -111,7 +111,7 @@ describe('Schedule tool protocol', () => { schema.properties?.code?.const === 'persistence_uncertain') expect(persistenceError?.properties?.operation?.enum).toEqual(['create', 'list', 'delete']) for (const name of ['schedule_create', 'schedule_list', 'schedule_delete']) { - expect(test.ctx.tools.executionMode({ signal, callId: CallId(name), name, arguments: {}, agent: test.agent })) + expect(test.ctx.tools.executionMode({ signal, callId: ToolCallId(name), name, arguments: {}, agent: test.agent })) .toEqual({ kind: 'exclusive' }) } expect(test.ctx.tools.get('schedule_create')?.presentCall?.({ prompt: 'x', after_seconds: 1 })) diff --git a/packages/session-query/session-query/tests/search-helpers.spec.ts b/packages/session-query/session-query/tests/search-helpers.spec.ts index eb82890646..bcfef07739 100644 --- a/packages/session-query/session-query/tests/search-helpers.spec.ts +++ b/packages/session-query/session-query/tests/search-helpers.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm' import SessionStore, { SESSION_FORMAT_VERSION, SessionId, @@ -31,7 +31,7 @@ function expectCode(code: SessionQueryErrorCode): Error { describe('session-query semantic extraction', () => { it('extracts first-party message, tool, todo, and failure detail', () => { - const callId = CallId('call') + const callId = ToolCallId('call') const messageContent: SessionEvent<'user/message'>['data']['content'] = [ { type: 'text', text: ' visible ' }, { type: 'reasoning', text: 'thought' }, diff --git a/packages/session-query/tool-session-query/tests/sqlite-integration.spec.ts b/packages/session-query/tool-session-query/tests/sqlite-integration.spec.ts index 500d7de3a6..eab3ff50db 100644 --- a/packages/session-query/tool-session-query/tests/sqlite-integration.spec.ts +++ b/packages/session-query/tool-session-query/tests/sqlite-integration.spec.ts @@ -4,7 +4,7 @@ import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import type { Agent } from '@deepseek-ai/dsh-agent' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import SessionStore, { SESSION_FORMAT_VERSION, SessionId, @@ -78,7 +78,7 @@ describe('tool-session-query with the real SQLite provider', () => { const execute = (name: string, args: unknown) => ctx.tools.execute({ name, arguments: args, - callId: CallId(`integration-${++call}`), + callId: ToolCallId(`integration-${++call}`), signal: new AbortController().signal, agent: fakeAgent(caller), }) @@ -170,7 +170,7 @@ describe('tool-session-query with the real SQLite provider', () => { const execute = (args: unknown) => ctx.tools.execute({ name: 'session_event_search', arguments: args, - callId: CallId(`fractional-integration-${++call}`), + callId: ToolCallId(`fractional-integration-${++call}`), signal: new AbortController().signal, agent: fakeAgent(caller), }) diff --git a/packages/session-query/tool-session-query/tests/tool-session-query.spec.ts b/packages/session-query/tool-session-query/tests/tool-session-query.spec.ts index 7d213f9591..07969d19cf 100644 --- a/packages/session-query/tool-session-query/tests/tool-session-query.spec.ts +++ b/packages/session-query/tool-session-query/tests/tool-session-query.spec.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { Context, type Fiber } from '@deepseek-ai/cordis' import type { Agent } from '@deepseek-ai/dsh-agent' -import { createUserMessage, CallId, HarnessError , createMessage } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, HarnessError , createMessage } from '@deepseek-ai/dsh-llm' import { MAX_TIMER_DELAY_MS, TimeoutReason } from '@deepseek-ai/dsh-timeout' import * as TimeoutPolicy from '@deepseek-ai/dsh-tool-call-timeout-policy' import SessionStore, { @@ -218,7 +218,7 @@ async function mount( call: (toolName, args, options = {}) => ctx.tools.execute({ name: toolName, arguments: args, - callId: CallId(`call-${++calls}`), + callId: ToolCallId(`call-${++calls}`), signal: options.signal ?? new AbortController().signal, ...options.agent === undefined ? { agent: fakeAgent(caller) } : { agent: options.agent }, }), @@ -301,7 +301,7 @@ describe('registration and schemas', () => { expect(mounted.ctx.tools.executionMode({ name, arguments: args, - callId: CallId(`mode-${name}`), + callId: ToolCallId(`mode-${name}`), signal: new AbortController().signal, agent: fakeAgent(mounted.caller), })).toEqual({ kind }) @@ -579,7 +579,7 @@ describe('workspace authority and lineage redaction', () => { const missing = await mounted.ctx.tools.execute({ name: 'session_trace', arguments: {}, - callId: CallId('missing-agent'), + callId: ToolCallId('missing-agent'), signal: new AbortController().signal, }) expect(errorCode(missing)).toBe('SESSION_QUERY_TOOL_MISSING_AGENT') diff --git a/packages/session/session-checkpoint-policy/tests/fixtures/crash-child.ts b/packages/session/session-checkpoint-policy/tests/fixtures/crash-child.ts index 9557746db9..83826090dd 100644 --- a/packages/session/session-checkpoint-policy/tests/fixtures/crash-child.ts +++ b/packages/session/session-checkpoint-policy/tests/fixtures/crash-child.ts @@ -2,7 +2,7 @@ import { writeFile } from 'node:fs/promises' import { Context } from '@deepseek-ai/cordis' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import { createUserMessage, CallId, type GenerateOptions, LlmAdapter, type StreamChunk } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, type GenerateOptions, LlmAdapter, type StreamChunk } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' import * as checkpointPolicy from '../../src/index.ts' @@ -29,7 +29,7 @@ class CrashAdapter extends LlmAdapter { yield { type: 'block-end', index: 0, - block: { type: 'tool-call', id: CallId('crash-call'), name: 'crash_tool', arguments: '{}' }, + block: { type: 'tool-call', id: ToolCallId('crash-call'), name: 'crash_tool', arguments: '{}' }, } yield { type: 'finish', reason: { kind: 'tool-calls' } } } diff --git a/packages/session/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts b/packages/session/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts index 851271aa9c..64b1c3cf3c 100644 --- a/packages/session/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts +++ b/packages/session/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' -import LlmRuntime, { CallId, type GenerateOptions, LlmAdapter, type StreamChunk } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { ToolCallId, type GenerateOptions, LlmAdapter, type StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session' import SessionPersistence from '@deepseek-ai/dsh-session-persistence' @@ -138,7 +138,7 @@ describe('session-checkpoint-policy tool and step boundaries', () => { }) const pending = ctx.tools.execute({ - callId: CallId('write-1'), name: 'write', arguments: {}, agent, + callId: ToolCallId('write-1'), name: 'write', arguments: {}, agent, signal: new AbortController().signal, }) await Promise.resolve() @@ -167,7 +167,7 @@ describe('session-checkpoint-policy tool and step boundaries', () => { }) const pending = ctx.tools.execute({ - callId: CallId('write-cancelled'), name: 'write', arguments: {}, agent, + callId: ToolCallId('write-cancelled'), name: 'write', arguments: {}, agent, signal: controller.signal, }) await Promise.resolve() @@ -198,7 +198,7 @@ describe('session-checkpoint-policy tool and step boundaries', () => { execute: async () => { ran = true; return null }, }) const result = await ctx.tools.execute({ - callId: CallId('write-2'), name: 'write', arguments: {}, agent, + callId: ToolCallId('write-2'), name: 'write', arguments: {}, agent, signal: new AbortController().signal, }) expect(result.isError).toBe(true) @@ -218,7 +218,7 @@ describe('session-checkpoint-policy tool and step boundaries', () => { execute: async () => null, }) await ctx.tools.execute({ - callId: CallId('nested-1'), name: 'nested', arguments: {}, agent, + callId: ToolCallId('nested-1'), name: 'nested', arguments: {}, agent, parent: Symbol('outer') as never, signal: new AbortController().signal, }) diff --git a/packages/session/session-persistence-sqlite/tests/compression.spec.ts b/packages/session/session-persistence-sqlite/tests/compression.spec.ts index 361b4bc788..237e5c08b9 100644 --- a/packages/session/session-persistence-sqlite/tests/compression.spec.ts +++ b/packages/session/session-persistence-sqlite/tests/compression.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { zstdCompressSync } from 'node:zlib' import type { SessionEvent } from '@deepseek-ai/dsh-session' -import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm' +import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm' import { decodeStorageRecord, MAX_PACKED_DATA_BYTES, @@ -85,10 +85,10 @@ describe('SQLite compression', () => { const events = [ ...[0, 1, 2].map(seq => event(seq, seq, { type: 'reasoning-delta', index: 1, text: `${seq}` })), ...[3, 4, 5].map(seq => event(seq, seq, { - type: 'tool-call-delta', index: 2, id: CallId('named'), name: 'write', argumentsDelta: `${seq}`, + type: 'tool-call-delta', index: 2, id: ToolCallId('named'), name: 'write', argumentsDelta: `${seq}`, })), ...[6, 7, 8].map(seq => event(seq, seq, { - type: 'tool-call-delta', index: 3, id: CallId('unnamed'), argumentsDelta: `${seq}`, + type: 'tool-call-delta', index: 3, id: ToolCallId('unnamed'), argumentsDelta: `${seq}`, })), ] const records = packChunkRuns(events) @@ -128,7 +128,7 @@ describe('SQLite compression', () => { event(2, Number.MAX_SAFE_INTEGER, { type: 'text-delta', index: 0, text: 'c' }), ] const toolName = [0, 1, 2].map(seq => event(seq, seq, { - type: 'tool-call-delta', index: 0, id: CallId('id'), + type: 'tool-call-delta', index: 0, id: ToolCallId('id'), ...seq === 2 ? {} : { name: 'write' }, argumentsDelta: 'x', })) for (const events of [gap, step, block, unsafeTime, toolName]) { diff --git a/packages/session/session-persistence-sqlite/tests/differential.spec.ts b/packages/session/session-persistence-sqlite/tests/differential.spec.ts index d819ca2130..e299d7c9a2 100644 --- a/packages/session/session-persistence-sqlite/tests/differential.spec.ts +++ b/packages/session/session-persistence-sqlite/tests/differential.spec.ts @@ -5,7 +5,7 @@ import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { DatabaseSync } from 'node:sqlite' -import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm' +import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore, { type SessionEvent } from '@deepseek-ai/dsh-session' import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence' import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl' @@ -85,7 +85,7 @@ function packingMatrixLog(): SessionEvent[] { chunk: { type: 'tool-call-delta' as const, index: 2, - id: CallId('named-call'), + id: ToolCallId('named-call'), name: 'write', argumentsDelta: `{${index}`, }, @@ -95,7 +95,7 @@ function packingMatrixLog(): SessionEvent[] { chunk: { type: 'tool-call-delta' as const, index: 3, - id: CallId('unnamed-call'), + id: ToolCallId('unnamed-call'), argumentsDelta: `${index}}`, }, time: 3_000 + index, @@ -161,13 +161,13 @@ const streamChunkArbitrary: fc.Arbitrary = fc.oneof( fc.record({ type: fc.constant<'tool-call-delta'>('tool-call-delta'), index: fc.nat(2), - id: fc.constantFrom(CallId('call-1'), CallId('call-2')), + id: fc.constantFrom(ToolCallId('call-1'), ToolCallId('call-2')), argumentsDelta: fc.string(), }), fc.record({ type: fc.constant<'tool-call-delta'>('tool-call-delta'), index: fc.nat(2), - id: fc.constantFrom(CallId('call-1'), CallId('call-2')), + id: fc.constantFrom(ToolCallId('call-1'), ToolCallId('call-2')), name: fc.constantFrom('read', 'write'), argumentsDelta: fc.string(), }), diff --git a/packages/session/session-persistence/tests/contract.ts b/packages/session/session-persistence/tests/contract.ts index a672884e46..5be7076bd7 100644 --- a/packages/session/session-persistence/tests/contract.ts +++ b/packages/session/session-persistence/tests/contract.ts @@ -11,7 +11,7 @@ import { describe, expect, it } from 'vitest' import { SESSION_FORMAT_VERSION, Session, SessionId, TOOL_NOT_STARTED, TOOL_OUTCOME_UNKNOWN } from '@deepseek-ai/dsh-session' import type { SessionEvent, SessionHeader, SurfaceEventType, SurfaceIntent } from '@deepseek-ai/dsh-session' -import { CallId, MessageId, createMessage, freezeMessage } from '@deepseek-ai/dsh-llm' +import { ToolCallId, MessageId, createMessage, freezeMessage } from '@deepseek-ai/dsh-llm' import type { SessionPersistence } from '../src/index.ts' /** A backend under test plus its teardown. */ @@ -184,7 +184,7 @@ export function runPersistenceContract(name: string, make: () => Promise Promise e.type === 'tool/result') expect(synthetic?.type === 'tool/result' && synthetic.data).toMatchObject({ message: { - source: { kind: 'tool', callId: CallId('call-x') }, - content: [{ type: 'tool-result', toolCallId: CallId('call-x'), isError: true }], + source: { kind: 'tool', callId: ToolCallId('call-x') }, + content: [{ type: 'tool-result', toolCallId: ToolCallId('call-x'), isError: true }], }, error: { code: TOOL_NOT_STARTED }, }) @@ -215,7 +215,7 @@ export function runPersistenceContract(name: string, make: () => Promise e.type === 'assistant/message') const callId = call?.type === 'assistant/message' && call.data.message.content.find(b => b.type === 'tool-call') - expect(callId && callId.type === 'tool-call' && callId.id).toBe(CallId('call-x')) + expect(callId && callId.type === 'tool-call' && callId.id).toBe(ToolCallId('call-x')) } finally { await dispose() } @@ -234,7 +234,7 @@ export function runPersistenceContract(name: string, make: () => Promise Promise Promise message.content.some(block => block.type === 'tool-result')) expect(resumedResult?.content[0]).toMatchObject({ - type: 'tool-result', toolCallId: CallId('call-risk'), isError: true, + type: 'tool-result', toolCallId: ToolCallId('call-risk'), isError: true, }) } finally { await dispose() diff --git a/packages/session/session-title-llm/tests/llm.spec.ts b/packages/session/session-title-llm/tests/llm.spec.ts index 7b9bade991..f635c921e8 100644 --- a/packages/session/session-title-llm/tests/llm.spec.ts +++ b/packages/session/session-title-llm/tests/llm.spec.ts @@ -1,6 +1,6 @@ import { Context } from '@deepseek-ai/cordis' import { describe, expect, it, vi } from 'vitest' -import LlmRuntime, { createUserMessage, CallId, isAgentLoopRequest, LlmAdapter } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { createUserMessage, ToolCallId, isAgentLoopRequest, LlmAdapter } from '@deepseek-ai/dsh-llm' import type { FinishReason, GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import { SessionTitleProviderId } from '@deepseek-ai/dsh-session-title' @@ -283,7 +283,7 @@ describe('generateSessionTitleWithLlm', () => { it('rejects tool-call blocks and a successful response with no text', async () => { const toolScript: StreamChunk[] = [ { type: 'block-start', index: 0, blockType: 'tool-call' }, - { type: 'tool-call-delta', index: 0, id: CallId('title-tool'), name: 'unexpected', argumentsDelta: '{}' }, + { type: 'tool-call-delta', index: 0, id: ToolCallId('title-tool'), name: 'unexpected', argumentsDelta: '{}' }, { type: 'finish', reason: { kind: 'stop' } }, ] const tool = await withScript(toolScript) diff --git a/packages/shell/shell-env/tests/shell-env.spec.ts b/packages/shell/shell-env/tests/shell-env.spec.ts index d923e7d236..4df99a41a4 100644 --- a/packages/shell/shell-env/tests/shell-env.spec.ts +++ b/packages/shell/shell-env/tests/shell-env.spec.ts @@ -8,7 +8,7 @@ import { homedir } from 'node:os' import { join, resolve } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import type { Agent } from '@deepseek-ai/dsh-agent' import type { ToolExecution } from '@deepseek-ai/dsh-tools' import { ShellEnvRegistry } from '@deepseek-ai/dsh-shell-env' @@ -22,8 +22,8 @@ function execution(sessionId?: string): ToolExecution { return { signal: testToolSignal, token: Symbol('bash-env-test') as ToolExecution['token'], - callId: CallId('bash-env-call'), - rootCallId: CallId('bash-env-call'), + callId: ToolCallId('bash-env-call'), + rootCallId: ToolCallId('bash-env-call'), name: 'bash', arguments: { command: 'true' }, ...(sessionId === undefined diff --git a/packages/shell/tool-bash-persistent/tests/loader-composition.spec.ts b/packages/shell/tool-bash-persistent/tests/loader-composition.spec.ts index 6909d9eea8..17d0f674eb 100644 --- a/packages/shell/tool-bash-persistent/tests/loader-composition.spec.ts +++ b/packages/shell/tool-bash-persistent/tests/loader-composition.spec.ts @@ -6,7 +6,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' import Include from '@deepseek-ai/cordis-plugin-include' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -127,7 +127,7 @@ suite('persistent Bash through a real cordis.yml Loader composition', () => { const signal = new AbortController().signal const execute = (id: string, command: string) => context!.tools.execute({ signal, - callId: CallId(id), + callId: ToolCallId(id), name: 'bash', arguments: { command }, agent: owner, diff --git a/packages/shell/tool-bash-persistent/tests/tools.spec.ts b/packages/shell/tool-bash-persistent/tests/tools.spec.ts index 4cdebbbdce..921ff1f265 100644 --- a/packages/shell/tool-bash-persistent/tests/tools.spec.ts +++ b/packages/shell/tool-bash-persistent/tests/tools.spec.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -66,7 +66,7 @@ function call( ) { return ctx.tools.execute({ signal, - callId: CallId(`persistent-bash-${++callNumber}`), + callId: ToolCallId(`persistent-bash-${++callNumber}`), name: 'bash', arguments: { command }, ...owner === undefined ? {} : { agent: owner }, diff --git a/packages/shell/tool-bash/tests/tools.spec.ts b/packages/shell/tool-bash/tests/tools.spec.ts index 40314ef5fb..c67c26f2d4 100644 --- a/packages/shell/tool-bash/tests/tools.spec.ts +++ b/packages/shell/tool-bash/tests/tools.spec.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { ShellExecutor } from '@deepseek-ai/dsh-shell' import type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellProcessRead, ShellRunResult } from '@deepseek-ai/dsh-shell' import SystemPrompt, { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt' @@ -76,7 +76,7 @@ function registerFakeAgent(ctx: Context, sessionId: string, inject: (...args: un } let callCounter = 0 function call(ctx: Context, name: string, args: unknown, agent?: Agent) { - return ctx.tools.execute({ signal: testToolSignal, callId: CallId(`call-${++callCounter}`), name, arguments: args, ...agent ? { agent } : {} }) + return ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, ...agent ? { agent } : {} }) } function text(result: { content: { type: string; text?: string }[] }): string { @@ -309,7 +309,7 @@ describe('bash tool', () => { const ctx = await setup() const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('call-abort'), + callId: ToolCallId('call-abort'), name: 'bash', arguments: { command: 'sleep 60', description: 'test command' }, signal: controller.signal, @@ -517,7 +517,7 @@ describe('background execution through the job runtime', () => { const controller = new AbortController() controller.abort() const result = await ctx.tools.execute({ - callId: CallId('call-pre-aborted'), + callId: ToolCallId('call-pre-aborted'), name: 'bash', arguments: { command: 'sleep 60', description: 'test command', run_in_background: true }, signal: controller.signal, @@ -654,7 +654,7 @@ describe('sandbox escalation through the generic task producer', () => { const agent = sandboxAgent(undefined, ctx) ctx.agents.register(agent) const foreground = await ctx.tools.execute({ - callId: CallId('sandbox-signal'), + callId: ToolCallId('sandbox-signal'), name: 'bash', arguments: escalate, agent, @@ -677,7 +677,7 @@ describe('sandbox escalation through the generic task producer', () => { const start = vi.spyOn(bash, 'start') const result = await ctx.tools.execute({ - callId: CallId('cancelled-escalation-background'), + callId: ToolCallId('cancelled-escalation-background'), name: 'bash', arguments: { ...escalate, run_in_background: true }, agent, @@ -841,7 +841,7 @@ describe('session-cwd routing (per-session workdir)', () => { it('falls back to the executor default when the agent has no session cwd', async () => { const ctx = await setup() // No exec.agent at all → executor uses its config/process.cwd() default. - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('cwd-noagent'), name: 'bash', arguments: { command: 'pwd', description: 'pwd' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('cwd-noagent'), name: 'bash', arguments: { command: 'pwd', description: 'pwd' } }) expect(result.isError).toBe(false) expect(text(result).trim().length).toBeGreaterThan(0) }) @@ -1139,7 +1139,7 @@ describe('the model-facing bash tool builds its request from named args only (no await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('session-env-fg'), + callId: ToolCallId('session-env-fg'), name: 'bash', arguments: { command: 'true', description: 'run command' }, agent, @@ -1160,7 +1160,7 @@ describe('the model-facing bash tool builds its request from named args only (no await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('session-env-bg'), + callId: ToolCallId('session-env-bg'), name: 'bash', arguments: { command: 'sleep 1', @@ -1187,7 +1187,7 @@ describe('the model-facing bash tool builds its request from named args only (no await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('session-env-id-only'), + callId: ToolCallId('session-env-id-only'), name: 'bash', arguments: { command: 'true', description: 'run command' }, agent, @@ -1209,7 +1209,7 @@ describe('the model-facing bash tool builds its request from named args only (no for (const [callId, agent] of [['parent', parent], ['child', child]] as const) { await ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`session-env-${callId}`), + callId: ToolCallId(`session-env-${callId}`), name: 'bash', arguments: { command: 'true', description: 'run command' }, agent, @@ -1240,7 +1240,7 @@ describe('the model-facing bash tool builds its request from named args only (no // already set environment variables or feed stdin. await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('no-forward-1'), + callId: ToolCallId('no-forward-1'), name: 'bash', arguments: { command: 'echo hi', @@ -1262,7 +1262,7 @@ describe('the model-facing bash tool builds its request from named args only (no const { ctx, bash } = await setupRecording() const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('no-forward-2'), + callId: ToolCallId('no-forward-2'), name: 'bash', arguments: { command: 'sleep 1', diff --git a/packages/shell/tool-pwsh-persistent/tests/loader-composition.spec.ts b/packages/shell/tool-pwsh-persistent/tests/loader-composition.spec.ts index 1bf73ad50a..b06feaba59 100644 --- a/packages/shell/tool-pwsh-persistent/tests/loader-composition.spec.ts +++ b/packages/shell/tool-pwsh-persistent/tests/loader-composition.spec.ts @@ -7,7 +7,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' import Include from '@deepseek-ai/cordis-plugin-include' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -130,7 +130,7 @@ describe.skipIf(!hasPwsh)('persistent pwsh through a real cordis.yml Loader comp const signal = new AbortController().signal const execute = (id: string, command: string) => context!.tools.execute({ signal, - callId: CallId(id), + callId: ToolCallId(id), name: 'pwsh', arguments: { command }, agent: owner, diff --git a/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts b/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts index 8d76abd3a6..8d9c55e39c 100644 --- a/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts +++ b/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -66,7 +66,7 @@ function call( ) { return ctx.tools.execute({ signal, - callId: CallId(`persistent-pwsh-${++callNumber}`), + callId: ToolCallId(`persistent-pwsh-${++callNumber}`), name: 'pwsh', arguments: { command }, ...owner === undefined ? {} : { agent: owner }, diff --git a/packages/shell/tool-pwsh/tests/fixtures/loader/driver.ts b/packages/shell/tool-pwsh/tests/fixtures/loader/driver.ts index bd6f63cb34..6b68d72023 100644 --- a/packages/shell/tool-pwsh/tests/fixtures/loader/driver.ts +++ b/packages/shell/tool-pwsh/tests/fixtures/loader/driver.ts @@ -8,7 +8,7 @@ import { writeFile } from 'node:fs/promises' import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' const configPath = process.argv[2] if (configPath === undefined) throw new Error('tool-pwsh driver requires a config path') @@ -21,7 +21,7 @@ try { const foreground = await ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId('loader-fg'), + callId: ToolCallId('loader-fg'), name: 'pwsh', arguments: { command: 'Write-Output loader-ok', description: 'loader foreground' }, }) @@ -29,7 +29,7 @@ try { const background = await ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId('loader-bg'), + callId: ToolCallId('loader-bg'), name: 'pwsh', arguments: { command: 'Start-Sleep -Milliseconds 200; Write-Output loader-bg-ok', @@ -46,7 +46,7 @@ try { while (Date.now() < deadline) { const read = await ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId('loader-bg-read'), + callId: ToolCallId('loader-bg-read'), name: 'job_output', arguments: { job_id: jobId }, }) diff --git a/packages/shell/tool-pwsh/tests/integration.spec.ts b/packages/shell/tool-pwsh/tests/integration.spec.ts index 022223a393..84d308e1b3 100644 --- a/packages/shell/tool-pwsh/tests/integration.spec.ts +++ b/packages/shell/tool-pwsh/tests/integration.spec.ts @@ -15,7 +15,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { spawnSync } from 'node:child_process' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED } from '@deepseek-ai/dsh-tools' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' @@ -41,7 +41,7 @@ let callCounter = 0 function call(name: string, args: unknown, agentObj?: object, signal?: AbortSignal) { return ctx.tools.execute({ signal: signal ?? testToolSignal, - callId: CallId(`it-${++callCounter}`), + callId: ToolCallId(`it-${++callCounter}`), name, arguments: args, ...agentObj ? { agent: agentObj as never } : {}, diff --git a/packages/shell/tool-pwsh/tests/tools.spec.ts b/packages/shell/tool-pwsh/tests/tools.spec.ts index ecd9f5bde5..69ecafe2d4 100644 --- a/packages/shell/tool-pwsh/tests/tools.spec.ts +++ b/packages/shell/tool-pwsh/tests/tools.spec.ts @@ -15,7 +15,7 @@ import { Context } from '@deepseek-ai/cordis' import { mkdtempSync, realpathSync } from 'node:fs' import { tmpdir } from 'node:os' import { join, resolve as resolvePath } from 'node:path' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED, TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' @@ -271,7 +271,7 @@ let callCounter = 0 function call(ctx: Context, name: string, args: unknown, agent?: Agent) { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name, arguments: args, ...agent ? { agent } : {}, @@ -403,7 +403,7 @@ describe('execution through the bash seam', () => { bash.handler = () => runResult('ok\n') await ctx.tools.execute({ signal: controller.signal, - callId: CallId('call-signal'), + callId: ToolCallId('call-signal'), name: 'pwsh', arguments: { command: 'Write-Output ok', description: 'ok' }, }) @@ -626,7 +626,7 @@ describe('sandbox escalation through ctx.approval', () => { const agent = sandboxAgent(undefined, ctx) ctx.agents.register(agent) const foreground = await ctx.tools.execute({ - callId: CallId('sandbox-signal'), + callId: ToolCallId('sandbox-signal'), name: 'pwsh', arguments: escalate, agent, @@ -649,7 +649,7 @@ describe('sandbox escalation through ctx.approval', () => { const start = vi.spyOn(bash, 'start') const result = await ctx.tools.execute({ - callId: CallId('cancelled-escalation-background'), + callId: ToolCallId('cancelled-escalation-background'), name: 'pwsh', arguments: { ...escalate, run_in_background: true }, agent, @@ -752,7 +752,7 @@ describe('background execution through the job runtime', () => { const controller = new AbortController() controller.abort() const result = await ctx.tools.execute({ - callId: CallId('call-pre-aborted'), + callId: ToolCallId('call-pre-aborted'), name: 'pwsh', arguments: { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true }, signal: controller.signal, diff --git a/packages/skill/tool-skill/tests/tool-skill.spec.ts b/packages/skill/tool-skill/tests/tool-skill.spec.ts index d8dd9df2f4..134fc85a62 100644 --- a/packages/skill/tool-skill/tests/tool-skill.spec.ts +++ b/packages/skill/tool-skill/tests/tool-skill.spec.ts @@ -3,7 +3,7 @@ import { mkdir, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { tmpdir } from 'node:os' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId, type Message } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, type Message } from '@deepseek-ai/dsh-llm' import { createScope, type Scope } from '@deepseek-ai/dsh-scope' import { Session, SessionId, type SessionEvent, type UserMessage } from '@deepseek-ai/dsh-session' import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt' @@ -630,7 +630,7 @@ describe('dsh-tool-skill', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('body-refresh'), + callId: ToolCallId('body-refresh'), name: 'skill', arguments: { name: 'body-skill' }, agent, @@ -658,7 +658,7 @@ describe('dsh-tool-skill', () => { const scoped = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('scoped-load'), + callId: ToolCallId('scoped-load'), name: 'skill', arguments: { name: 'preset-only-skill' }, agent, @@ -668,7 +668,7 @@ describe('dsh-tool-skill', () => { const foreign = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('foreign-load'), + callId: ToolCallId('foreign-load'), name: 'skill', arguments: { name: 'preset-only-skill' }, agent: agentForCwd('/workspace/other'), @@ -766,7 +766,7 @@ describe('dsh-tool-skill', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('c1'), + callId: ToolCallId('c1'), name: 'skill', arguments: { name: 'project-skill' }, agent: { session: { header: { cwd: project } } } as never, @@ -825,9 +825,9 @@ describe('dsh-tool-skill', () => { content: 'Provider instructions.', }) - const opaque = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c2'), name: 'skill', arguments: { name: 'opaque-skill' } }) - const url = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c3'), name: 'skill', arguments: { name: 'url-skill' } }) - const provider = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c4'), name: 'skill', arguments: { name: 'provider-skill' } }) + const opaque = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c2'), name: 'skill', arguments: { name: 'opaque-skill' } }) + const url = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c3'), name: 'skill', arguments: { name: 'url-skill' } }) + const provider = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c4'), name: 'skill', arguments: { name: 'provider-skill' } }) if (opaque.content[0]?.type !== 'text' || url.content[0]?.type !== 'text' || provider.content[0]?.type !== 'text') { throw new Error('expected text tool results') @@ -849,7 +849,7 @@ describe('dsh-tool-skill', () => { content: 'Rogue instructions.', }) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c5'), name: 'skill', arguments: { name: 'rogue-resource-skill' } }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c5'), name: 'skill', arguments: { name: 'rogue-resource-skill' } }) expect(result.isError).toBe(true) expect(result.error?.info?.code).toBe('INVALID_TOOL_OUTPUT') @@ -871,10 +871,10 @@ describe('dsh-tool-skill', () => { content: 'Model-only instructions.', }) - const unknown = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'skill', arguments: { name: 'missing' } }) - const invalid = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c2'), name: 'skill', arguments: { name: 'Bad_Name' } }) - const disabled = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c3'), name: 'skill', arguments: { name: 'hidden-skill' } }) - const modelOnly = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c4'), name: 'skill', arguments: { name: 'model-only-skill' } }) + const unknown = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c1'), name: 'skill', arguments: { name: 'missing' } }) + const invalid = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c2'), name: 'skill', arguments: { name: 'Bad_Name' } }) + const disabled = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c3'), name: 'skill', arguments: { name: 'hidden-skill' } }) + const modelOnly = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c4'), name: 'skill', arguments: { name: 'model-only-skill' } }) expect(unknown.isError).toBe(true) expect(invalid.isError).toBe(true) @@ -933,9 +933,9 @@ describe('dsh-tool-skill', () => { }, })) - const denied = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c6'), name: 'skill', arguments: { name: 'denied-skill' } }) - const raced = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c7'), name: 'skill', arguments: { name: 'policy-race-skill' } }) - const vanished = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c8'), name: 'skill', arguments: { name: 'vanishing-skill' } }) + const denied = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c6'), name: 'skill', arguments: { name: 'denied-skill' } }) + const raced = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c7'), name: 'skill', arguments: { name: 'policy-race-skill' } }) + const vanished = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c8'), name: 'skill', arguments: { name: 'vanishing-skill' } }) expect(denied.isError).toBe(true) expect(raced.isError).toBe(true) diff --git a/packages/spill/spill-local/tests/spill-local.spec.ts b/packages/spill/spill-local/tests/spill-local.spec.ts index 1948731bff..94bcaa02c4 100644 --- a/packages/spill/spill-local/tests/spill-local.spec.ts +++ b/packages/spill/spill-local/tests/spill-local.spec.ts @@ -15,7 +15,7 @@ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, st import { realpath } from 'node:fs/promises' import { tmpdir } from 'node:os' import { basename, dirname, isAbsolute, join, normalize } from 'node:path' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import type { SaveTextSpill } from '@deepseek-ai/dsh-spill' import LocalSpillStore, { @@ -52,7 +52,7 @@ function writeAged(path: string, content: string, ageDays: number): void { function request(overrides: Partial = {}): SaveTextSpill { return { owner: { sessionId: SessionId('sess-1') }, - source: { toolName: 'web_fetch', callId: CallId('call-1'), label: 'result' }, + source: { toolName: 'web_fetch', callId: ToolCallId('call-1'), label: 'result' }, suggestedName: 'web_fetch.txt', content: 'the full body', ...overrides, diff --git a/packages/spill/spill-policy/src/index.ts b/packages/spill/spill-policy/src/index.ts index d3cef074eb..d6ebb1b664 100644 --- a/packages/spill/spill-policy/src/index.ts +++ b/packages/spill/spill-policy/src/index.ts @@ -50,7 +50,7 @@ import { TextRetainer, describeOmitted } from '@deepseek-ai/dsh-output-retention import type { Omitted } from '@deepseek-ai/dsh-output-retention' import type { SaveTextSpill, SpillRef } from '@deepseek-ai/dsh-spill' import type { SessionId } from '@deepseek-ai/dsh-session' -import type { CallId } from '@deepseek-ai/dsh-llm' +import type { ToolCallId } from '@deepseek-ai/dsh-llm' import type { PostToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools' import type { SpillPolicyExec } from './types.ts' @@ -132,7 +132,7 @@ export function apply(ctx: Context, config: Config): void { totalBytes: number, sessionId: SessionId | undefined, toolName: string, - callId: CallId, + callId: ToolCallId, label: 'result' | 'dispatch', ): Promise { if (sessionId === undefined) { diff --git a/packages/spill/spill-policy/tests/spill-policy.spec.ts b/packages/spill/spill-policy/tests/spill-policy.spec.ts index e28ec1522d..433d2c1797 100644 --- a/packages/spill/spill-policy/tests/spill-policy.spec.ts +++ b/packages/spill/spill-policy/tests/spill-policy.spec.ts @@ -11,7 +11,7 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import type { ContentBlock } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' @@ -58,7 +58,7 @@ function textTool(name: string, text: string) { function exec(name: string, session = 's1'): ToolExecution { // Only agent.session.header.id is read by the policy; a structural stub suffices. const agent = { session: { header: { id: SessionId(session) } } } - return { callId: CallId(`call-${name}`), name, arguments: {}, agent, signal: testToolSignal } as unknown as ToolExecution + return { callId: ToolCallId(`call-${name}`), name, arguments: {}, agent, signal: testToolSignal } as unknown as ToolExecution } /** @@ -204,7 +204,7 @@ describe('outer Code Mode failure capture', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('code-output-limit'), + callId: ToolCallId('code-output-limit'), name: 'run_code', arguments: { code: 'console.log("HEAD-" + "x".repeat(300)); console.log("TAIL-" + "y".repeat(300)); return "unreachable";', @@ -255,7 +255,7 @@ describe('the durable dispatch-log arm', () => { for (const tool of extraTools) ctx.tools.register(tool) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('parent-1'), + callId: ToolCallId('parent-1'), name: 'run_code', arguments: { code: program, description: 'Drive dispatch-log spilling' }, agent: agent as never, @@ -338,7 +338,7 @@ describe('the durable dispatch-log arm', () => { let smallAfterHuge = false const runPromise = ctx.tools.execute({ signal: testToolSignal, - callId: CallId('parent-3'), + callId: ToolCallId('parent-3'), name: 'run_code', arguments: { // The program takes BOTH values while the spill backend hangs: the @@ -396,7 +396,7 @@ describe('the durable dispatch-log arm', () => { && (event.data as { subCallId: string }).subCallId.endsWith(`:code:${n}`)) const runPromise = ctx.tools.execute({ signal: testToolSignal, - callId: CallId('parent-bound'), + callId: ToolCallId('parent-bound'), name: 'run_code', arguments: { code: 'await tools.huge_read({}); await tools.huge_read({}); await tools.huge_read({}); return "done"', @@ -446,7 +446,7 @@ describe('the durable dispatch-log arm', () => { ctx.tools.register(textTool('huge_read', 'H'.repeat(2_000))) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('parent-2'), + callId: ToolCallId('parent-2'), name: 'run_code', arguments: { code: 'return (await tools.huge_read({}))[0].text.length', description: 'Fail the spill backend' }, agent: agent as never, @@ -498,7 +498,7 @@ describe('best-effort fallback', () => { const { ctx, spill } = await setup({ maxInlineBytes: 10 }) const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {}) ctx.tools.register(textTool('big', 'x'.repeat(1000))) - const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c'), name: 'big', arguments: {} }) + const result = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c'), name: 'big', arguments: {} }) expect(textOf(result.content)).toBe('x'.repeat(1000)) expect(spill?.saves).toHaveLength(0) expect(warn).toHaveBeenCalled() diff --git a/packages/spill/spill/src/types.ts b/packages/spill/spill/src/types.ts index f07e13f4ef..8091fffce9 100644 --- a/packages/spill/spill/src/types.ts +++ b/packages/spill/spill/src/types.ts @@ -7,7 +7,7 @@ */ import type { Branded } from '@deepseek-ai/dsh-brand' -import type { CallId } from '@deepseek-ai/dsh-llm' +import type { ToolCallId } from '@deepseek-ai/dsh-llm' import type { SessionId } from '@deepseek-ai/dsh-session' /** @@ -47,7 +47,7 @@ export 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 } diff --git a/packages/spill/spill/tests/service.spec.ts b/packages/spill/spill/tests/service.spec.ts index c17a77b273..b812b3e4e2 100644 --- a/packages/spill/spill/tests/service.spec.ts +++ b/packages/spill/spill/tests/service.spec.ts @@ -7,7 +7,7 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import { SpillLocator, SpillStore } from '@deepseek-ai/dsh-spill' import type { SaveTextSpill, SpillRef } from '@deepseek-ai/dsh-spill' @@ -29,7 +29,7 @@ class StubStore extends SpillStore { function request(content: string): SaveTextSpill { return { owner: { sessionId: SessionId('s1') }, - source: { toolName: 'web_fetch', callId: CallId('c1'), label: 'result' }, + source: { toolName: 'web_fetch', callId: ToolCallId('c1'), label: 'result' }, suggestedName: 'web_fetch.txt', content, } diff --git a/packages/subagent/subagent-acp/tests/fixtures/loader/mock-delegating-llm.ts b/packages/subagent/subagent-acp/tests/fixtures/loader/mock-delegating-llm.ts index f98c007125..f318424153 100644 --- a/packages/subagent/subagent-acp/tests/fixtures/loader/mock-delegating-llm.ts +++ b/packages/subagent/subagent-acp/tests/fixtures/loader/mock-delegating-llm.ts @@ -1,6 +1,6 @@ import type { Context } from '@deepseek-ai/cordis' import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' -import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm' +import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm' /** * Test adapter for the `mock-delegate` model: the first request calls the @@ -20,8 +20,8 @@ class MockDelegatingAdapter extends LlmAdapter { if (toolResultText.length === 0) { const args = JSON.stringify({ description: 'cwd probe', prompt: 'report your workspace' }) yield { type: 'block-start', index: 0, blockType: 'tool-call' } - yield { type: 'tool-call-delta', index: 0, id: CallId('call-delegate'), name: 'subagent', argumentsDelta: args } - yield { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('call-delegate'), name: 'subagent', arguments: args } } + yield { type: 'tool-call-delta', index: 0, id: ToolCallId('call-delegate'), name: 'subagent', argumentsDelta: args } + yield { type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('call-delegate'), name: 'subagent', arguments: args } } yield { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } } yield { type: 'finish', reason: { kind: 'tool-calls' } } return diff --git a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/mock-delegating-llm.ts b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/mock-delegating-llm.ts index 178decf4b3..f07a50379c 100644 --- a/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/mock-delegating-llm.ts +++ b/packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/mock-delegating-llm.ts @@ -1,7 +1,7 @@ import type { Context } from '@deepseek-ai/cordis' import { appendFileSync } from 'node:fs' import type { GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm' -import { CallId, LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm' +import { ToolCallId, LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm' /** * Test adapter for the `mock-delegate` model: the first request calls the @@ -42,8 +42,8 @@ class MockDelegatingAdapter extends LlmAdapter { ...selectedRoute, }) yield { type: 'block-start', index: 0, blockType: 'tool-call' } - yield { type: 'tool-call-delta', index: 0, id: CallId('call-delegate'), name: 'subagent', argumentsDelta: args } - yield { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('call-delegate'), name: 'subagent', arguments: args } } + yield { type: 'tool-call-delta', index: 0, id: ToolCallId('call-delegate'), name: 'subagent', argumentsDelta: args } + yield { type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('call-delegate'), name: 'subagent', arguments: args } } yield { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } } yield { type: 'finish', reason: { kind: 'tool-calls' } } return diff --git a/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts b/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts index 78a4068d95..8f21ee4483 100644 --- a/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts +++ b/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { createUserMessage, CallId, type ContentBlock, type GenerateOptions } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, type ContentBlock, type GenerateOptions } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' @@ -133,7 +133,7 @@ describe('in-process structured output', () => { const response = [ ...toolCallResponse('c1', STRUCTURED_OUTPUT_TOOL, { answer: 5 }).slice(0, -2), { type: 'block-start', index: 1, blockType: 'tool-call' }, - { type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c2'), name: 'side_effect', arguments: '{}' } }, + { type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('c2'), name: 'side_effect', arguments: '{}' } }, { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } }, { type: 'finish', reason: { kind: 'tool-calls' } }, ] as Script[number] @@ -161,7 +161,7 @@ describe('in-process structured output', () => { const response = [ ...toolCallResponse('c1', STRUCTURED_OUTPUT_TOOL, { answer: 5 }).slice(0, -2), { type: 'block-start', index: 1, blockType: 'tool-call' }, - { type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c2'), name: 'side_effect', arguments: '{}' } }, + { type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('c2'), name: 'side_effect', arguments: '{}' } }, { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } }, { type: 'finish', reason: { kind: 'tool-calls' } }, ] as Script[number] @@ -198,7 +198,7 @@ describe('in-process structured output', () => { it('leaves tool calls that PRECEDE the capture in the same response untouched', async () => { const response = [ { type: 'block-start', index: 0, blockType: 'tool-call' }, - { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('c1'), name: 'side_effect', arguments: '{}' } }, + { type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'side_effect', arguments: '{}' } }, ...toolCallResponse('c2', STRUCTURED_OUTPUT_TOOL, { answer: 6 }).map(chunk => 'index' in chunk ? { ...chunk, index: 1 } : chunk), ] as Script[number] @@ -433,7 +433,7 @@ describe('in-process structured output', () => { expect(adapter.requests).toHaveLength(2) const child = ctx.agents.get(run.id)! const outer = child.session.events.find(event => - event.type === 'tool/result' && event.data.message.source.callId === CallId('c1')) + event.type === 'tool/result' && event.data.message.source.callId === ToolCallId('c1')) expect(outer?.type === 'tool/result' && outer.data.message.content[0].isError).toBe(true) await run.dispose() }) diff --git a/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts b/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts index 823890d934..da43c58fb1 100644 --- a/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts +++ b/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts @@ -1,4 +1,4 @@ -import { CallId, createUserMessage } from '@deepseek-ai/dsh-llm' +import { ToolCallId, createUserMessage } from '@deepseek-ai/dsh-llm' import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { type Agent, type AgentOptions } from '@deepseek-ai/dsh-agent' @@ -177,8 +177,8 @@ describe('startInProcessRun', () => { toolCallResponse('t1', 'noop', {}, 'partial one'), [ { type: 'block-start', index: 0, blockType: 'tool-call' }, - { type: 'tool-call-delta', index: 0, id: CallId('t2'), name: 'noop', argumentsDelta: '{}' }, - { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('t2'), name: 'noop', arguments: '{}' } }, + { type: 'tool-call-delta', index: 0, id: ToolCallId('t2'), name: 'noop', argumentsDelta: '{}' }, + { type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('t2'), name: 'noop', arguments: '{}' } }, { type: 'usage', usage: { inputTokens: 20, outputTokens: 5 } }, { type: 'finish', reason: { kind: 'max-tokens' } }, ], diff --git a/packages/subagent/subagent/tests/continuation.spec.ts b/packages/subagent/subagent/tests/continuation.spec.ts index 8f38df3f79..9508065197 100644 --- a/packages/subagent/subagent/tests/continuation.spec.ts +++ b/packages/subagent/subagent/tests/continuation.spec.ts @@ -12,7 +12,7 @@ import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn-in-process' import * as SubagentFork from '@deepseek-ai/dsh-subagent-fork-in-process' import type { GenerateOptions, MessageId, StreamChunk } from '@deepseek-ai/dsh-llm' -import { CallId, createUserMessage, LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm' +import { ToolCallId, createUserMessage, LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm' import { defineTool } from '@deepseek-ai/dsh-tools' import InvariantRegistry from '@deepseek-ai/dsh-invariants' import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -1401,8 +1401,8 @@ describe('continuable review regressions', () => { toolCallResponse('t1', 'noop', {}, 'partial one'), [ { type: 'block-start', index: 0, blockType: 'tool-call' }, - { type: 'tool-call-delta', index: 0, id: CallId('t2'), name: 'noop', argumentsDelta: '{}' }, - { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('t2'), name: 'noop', arguments: '{}' } }, + { type: 'tool-call-delta', index: 0, id: ToolCallId('t2'), name: 'noop', argumentsDelta: '{}' }, + { type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('t2'), name: 'noop', arguments: '{}' } }, { type: 'usage', usage: { inputTokens: 20, outputTokens: 5 } }, { type: 'finish', reason: { kind: 'max-tokens' } }, ], diff --git a/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts b/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts index cbf3f7761f..120cc987b4 100644 --- a/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts +++ b/packages/subagent/tool-subagent-control/tests/list-agents.spec.ts @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { SessionId } from '@deepseek-ai/dsh-session' @@ -88,7 +88,7 @@ function callTool( ) { return ctx.tools.execute({ signal, - callId: CallId(`call-${++calls}`), + callId: ToolCallId(`call-${++calls}`), name, arguments: args, ...agent !== undefined ? { agent: agent as never } : {}, diff --git a/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts b/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts index d31bbdbd46..f0b835fe1c 100644 --- a/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts +++ b/packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { SessionId } from '@deepseek-ai/dsh-session' @@ -87,7 +87,7 @@ function callTool( ) { return ctx.tools.execute({ signal, - callId: CallId(`call-${++calls}`), + callId: ToolCallId(`call-${++calls}`), name, arguments: args, ...agent !== undefined ? { agent: agent as never } : {}, diff --git a/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts b/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts index 28d6068757..e8bb53925d 100644 --- a/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts +++ b/packages/subagent/tool-subagent-report/tests/tool-subagent-report.spec.ts @@ -7,7 +7,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent' import { assembleContextFor } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import { CallId, LlmAdapter, createUserMessage } from '@deepseek-ai/dsh-llm' +import { ToolCallId, LlmAdapter, createUserMessage } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import type { SessionEvent } from '@deepseek-ai/dsh-session' @@ -118,7 +118,7 @@ let calls = 0 function callReport(ctx: Context, child: Agent, output: string, signal = testSignal) { return ctx.tools.execute({ signal, - callId: CallId(`report-${++calls}`), + callId: ToolCallId(`report-${++calls}`), name: 'report', arguments: { output }, agent: child, diff --git a/packages/subagent/tool-subagent/tests/harness.ts b/packages/subagent/tool-subagent/tests/harness.ts index 36ac602c89..918e3a38f6 100644 --- a/packages/subagent/tool-subagent/tests/harness.ts +++ b/packages/subagent/tool-subagent/tests/harness.ts @@ -1,5 +1,5 @@ import { Context } from '@deepseek-ai/cordis' -import LlmRuntime, { CallId } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime from '@deepseek-ai/dsh-tools' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -43,7 +43,7 @@ export function callSubagent( const agent = 'agent' in over ? over.agent : fakeAgent() return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name: 'subagent', arguments: args, ...agent ? { agent } : {}, diff --git a/packages/subagent/tool-subagent/tests/list-models.spec.ts b/packages/subagent/tool-subagent/tests/list-models.spec.ts index de3643fd97..893f11a9ff 100644 --- a/packages/subagent/tool-subagent/tests/list-models.spec.ts +++ b/packages/subagent/tool-subagent/tests/list-models.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import LlmRuntime, { - CallId, + ToolCallId, LlmAdapter, ReasoningEffortId, } from '@deepseek-ai/dsh-llm' @@ -71,7 +71,7 @@ let counter = 0 function call(ctx: Context, args: unknown) { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`list-models-${++counter}`), + callId: ToolCallId(`list-models-${++counter}`), name: 'list_subagent_models', arguments: args, }) diff --git a/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts b/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts index ae9220e567..4243581e23 100644 --- a/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts +++ b/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os' import path from 'node:path' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' -import LlmRuntime, { CallId, ReasoningEffortId } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { ToolCallId, ReasoningEffortId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools' import { assembleContextFor, type Agent } from '@deepseek-ai/dsh-agent' @@ -108,13 +108,13 @@ describe('dsh-tool-subagent', () => { const ctx = await setup({ provider: 'mock' }) expect(ctx.tools.executionMode({ signal: testToolSignal, - callId: CallId('subagent-foreground'), + callId: ToolCallId('subagent-foreground'), name: 'subagent', arguments: { description: 'do work', prompt: 'Reply OK' }, })).toEqual({ kind: 'parallel' }) expect(ctx.tools.executionMode({ signal: testToolSignal, - callId: CallId('subagent-background'), + callId: ToolCallId('subagent-background'), name: 'subagent', arguments: { description: 'do work', prompt: 'Reply OK', run_in_background: true }, })).toEqual({ kind: 'parallel' }) @@ -189,8 +189,8 @@ describe('dsh-tool-subagent', () => { const names = ctx.tools.schemas().map(s => s.name).filter(n => n.startsWith('subagent')).sort() expect(names).toEqual(['subagent', 'subagent_acp']) - const viaSpawn = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c-spawn'), name: 'subagent', arguments: { description: 'd', prompt: 'p' }, agent: fakeAgent() }) - const viaAcp = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c-acp'), name: 'subagent_acp', arguments: { description: 'd', prompt: 'p' }, agent: fakeAgent() }) + const viaSpawn = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c-spawn'), name: 'subagent', arguments: { description: 'd', prompt: 'p' }, agent: fakeAgent() }) + const viaAcp = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('c-acp'), name: 'subagent_acp', arguments: { description: 'd', prompt: 'p' }, agent: fakeAgent() }) expect(text(viaSpawn)).toBe('from spawn') expect(text(viaAcp)).toBe('from acp') }) @@ -872,7 +872,7 @@ describe('dsh-tool-subagent background mode', () => { const started = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('resumable-one-shot'), + callId: ToolCallId('resumable-one-shot'), name: 'subagent_resumable', arguments: { description: 'work', prompt: 'go', run_in_background: true }, agent: parent, @@ -894,7 +894,7 @@ describe('dsh-tool-subagent background mode', () => { const collected = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('collect-1'), + callId: ToolCallId('collect-1'), name: 'job_output', arguments: { job_id: 'subagent-1', wait: true }, agent: parent, @@ -904,7 +904,7 @@ describe('dsh-tool-subagent background mode', () => { // Final-output reads are idempotent (not consumed). const again = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('collect-2'), + callId: ToolCallId('collect-2'), name: 'job_output', arguments: { job_id: 'subagent-1' }, agent: parent, @@ -922,7 +922,7 @@ describe('dsh-tool-subagent background mode', () => { const started = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('diagnostic-background-start'), + callId: ToolCallId('diagnostic-background-start'), name: 'subagent', arguments: { description: 'd', prompt: 'p', run_in_background: true }, agent: parent, @@ -931,7 +931,7 @@ describe('dsh-tool-subagent background mode', () => { const output = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('diagnostic-background-output'), + callId: ToolCallId('diagnostic-background-output'), name: 'job_output', arguments: { job_id: 'subagent-1', wait: true }, agent: parent, @@ -1058,7 +1058,7 @@ describe('dsh-tool-subagent background mode', () => { const started = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('broken-start'), + callId: ToolCallId('broken-start'), name: 'subagent_broken', arguments: { description: 'broken', prompt: 'p', run_in_background: true }, agent: parent, @@ -1066,7 +1066,7 @@ describe('dsh-tool-subagent background mode', () => { expect(text(started)).toBe('started background subagent job subagent-1') const output = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('broken-output'), + callId: ToolCallId('broken-output'), name: 'job_output', arguments: { job_id: 'subagent-1', wait: true }, agent: parent, @@ -1089,21 +1089,21 @@ describe('dsh-tool-subagent background mode', () => { await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('pending-start'), + callId: ToolCallId('pending-start'), name: 'subagent_pending', arguments: { description: 'pending', prompt: 'p', run_in_background: true }, agent: parent, }) await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('pending-kill'), + callId: ToolCallId('pending-kill'), name: 'job_kill', arguments: { job_id: 'subagent-1', reason: 'no longer needed' }, agent: parent, }) const output = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('pending-output'), + callId: ToolCallId('pending-output'), name: 'job_output', arguments: { job_id: 'subagent-1', wait: true }, agent: parent, @@ -1131,21 +1131,21 @@ describe('dsh-tool-subagent background mode', () => { await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('broken-rollback-start'), + callId: ToolCallId('broken-rollback-start'), name: 'subagent_broken_rollback', arguments: { description: 'broken rollback', prompt: 'p', run_in_background: true }, agent: parent, }) await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('broken-rollback-kill'), + callId: ToolCallId('broken-rollback-kill'), name: 'job_kill', arguments: { job_id: 'subagent-1' }, agent: parent, }) const output = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('broken-rollback-output'), + callId: ToolCallId('broken-rollback-output'), name: 'job_output', arguments: { job_id: 'subagent-1', wait: true }, agent: parent, @@ -1182,19 +1182,19 @@ describe('dsh-tool-subagent background mode', () => { // Direct apply preserves omitted agentOptions instead of applying schema defaults. tool.apply(ctx, { provider: 'hanging', toolName: 'subagent_hang' }) - const startOne = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('h1'), name: 'subagent_hang', arguments: { description: 'one', prompt: 'p', run_in_background: true }, agent: parent }) - const startTwo = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('h2'), name: 'subagent_hang', arguments: { description: 'two', prompt: 'p', run_in_background: true }, agent: parent }) + const startOne = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('h1'), name: 'subagent_hang', arguments: { description: 'one', prompt: 'p', run_in_background: true }, agent: parent }) + const startTwo = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('h2'), name: 'subagent_hang', arguments: { description: 'two', prompt: 'p', run_in_background: true }, agent: parent }) expect(text(startOne)).toBe('started background subagent job subagent-1') expect(text(startTwo)).toBe('started background subagent job subagent-2') - const withReason = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('k1'), name: 'job_kill', arguments: { job_id: 'subagent-1', reason: 'superseded' }, agent: parent }) - const withoutReason = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('k2'), name: 'job_kill', arguments: { job_id: 'subagent-2' }, agent: parent }) + const withReason = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('k1'), name: 'job_kill', arguments: { job_id: 'subagent-1', reason: 'superseded' }, agent: parent }) + const withoutReason = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('k2'), name: 'job_kill', arguments: { job_id: 'subagent-2' }, agent: parent }) expect(text(withReason)).toBe('requested cancellation of job subagent-1') expect(text(withoutReason)).toBe('requested cancellation of job subagent-2') expect(cancels).toEqual(['superseded', 'background subagent task killed']) // The aborted children settle as killed tasks. - const killed = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('w1'), name: 'job_output', arguments: { job_id: 'subagent-1', wait: true }, agent: parent }) + const killed = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('w1'), name: 'job_output', arguments: { job_id: 'subagent-1', wait: true }, agent: parent }) expect(text(killed)).toBe('(no new output)\n[status: killed]') }) @@ -1230,7 +1230,7 @@ describe('dsh-tool-subagent continuable background mode', () => { const { ctx } = await continuableSetup() expect(ctx.tools.executionMode({ signal: testToolSignal, - callId: CallId('subagent-continuable'), + callId: ToolCallId('subagent-continuable'), name: 'subagent', arguments: { description: 'do work', prompt: 'Reply OK' }, })).toEqual({ kind: 'parallel' }) @@ -1329,7 +1329,7 @@ describe('dsh-tool-subagent continuable background mode', () => { const execute = (callId: string, description: string, signal: AbortSignal) => ctx.tools.execute({ signal, - callId: CallId(callId), + callId: ToolCallId(callId), name: 'subagent_gated', arguments: { description, prompt: 'work', run_in_background: true }, agent: parent, @@ -1399,7 +1399,7 @@ describe('background preflight failure (no orphaned child, by construction)', () const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('probe-1'), + callId: ToolCallId('probe-1'), name: 'subagent_probe', arguments: { description: 'd', prompt: 'p', run_in_background: true }, agent: parent, diff --git a/packages/terminal/tool-terminal/tests/loader-composition.spec.ts b/packages/terminal/tool-terminal/tests/loader-composition.spec.ts index ada46ef78f..577850407c 100644 --- a/packages/terminal/tool-terminal/tests/loader-composition.spec.ts +++ b/packages/terminal/tool-terminal/tests/loader-composition.spec.ts @@ -6,7 +6,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' import Include from '@deepseek-ai/cordis-plugin-include' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -114,15 +114,15 @@ suite('terminal real Loader composition through cordis.yml', () => { const owner = agent(context) const signal = new AbortController().signal const spawn = await context.tools.execute({ - signal, callId: CallId('spawn'), name: 'terminal_open', arguments: { type: 'shell', name: 'main', cwd: root }, agent: owner, + signal, callId: ToolCallId('spawn'), name: 'terminal_open', arguments: { type: 'shell', name: 'main', cwd: root }, agent: owner, }) expect(resultText(spawn)).toContain('started terminal session pty-1 (main)') await context.tools.execute({ - signal, callId: CallId('state'), name: 'terminal_send', arguments: { sessionId: 'pty-1', text: 'export KEEP=loader; cd /' }, agent: owner, + signal, callId: ToolCallId('state'), name: 'terminal_send', arguments: { sessionId: 'pty-1', text: 'export KEEP=loader; cd /' }, agent: owner, }) const read = await context.tools.execute({ - signal, callId: CallId('read'), name: 'terminal_send', arguments: { sessionId: 'pty-1', text: 'printf "cwd=%s keep=%s\\n" "$PWD" "$KEEP"' }, agent: owner, + signal, callId: ToolCallId('read'), name: 'terminal_send', arguments: { sessionId: 'pty-1', text: 'printf "cwd=%s keep=%s\\n" "$PWD" "$KEEP"' }, agent: owner, }) expect(resultText(read)).toContain('cwd=/ keep=loader') expect(context.terminals.list(owner)).toHaveLength(1) diff --git a/packages/terminal/tool-terminal/tests/tools.spec.ts b/packages/terminal/tool-terminal/tests/tools.spec.ts index 9feaf1e7b0..406ae59778 100644 --- a/packages/terminal/tool-terminal/tests/tools.spec.ts +++ b/packages/terminal/tool-terminal/tests/tools.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -122,11 +122,11 @@ let callNumber = 0 const TOOL_NAMES = ['terminal_open', 'terminal_send', 'terminal_read', 'terminal_signal', 'terminal_close', 'terminal_list'] as const const testToolSignal = new AbortController().signal function call(ctx: Context, name: string, args: unknown, agent?: Agent) { - return ctx.tools.execute({ signal: testToolSignal, callId: CallId(`pty-call-${++callNumber}`), name, arguments: args, ...agent ? { agent } : {} }) + return ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId(`pty-call-${++callNumber}`), name, arguments: args, ...agent ? { agent } : {} }) } function callWithSignal(ctx: Context, name: string, args: unknown, agent: Agent, signal: AbortSignal) { - return ctx.tools.execute({ callId: CallId(`pty-call-${++callNumber}`), name, arguments: args, agent, signal }) + return ctx.tools.execute({ callId: ToolCallId(`pty-call-${++callNumber}`), name, arguments: args, agent, signal }) } function text(result: { content: { type: string; text?: string }[] }): string { diff --git a/packages/test-support/llm-replay/tests/llm-replay.spec.ts b/packages/test-support/llm-replay/tests/llm-replay.spec.ts index e9ff768cc4..c06ac28add 100644 --- a/packages/test-support/llm-replay/tests/llm-replay.spec.ts +++ b/packages/test-support/llm-replay/tests/llm-replay.spec.ts @@ -6,7 +6,7 @@ import { Context } from '@deepseek-ai/cordis' import type { SessionEvent } from '@deepseek-ai/dsh-session' import { CompactionId } from '@deepseek-ai/dsh-compaction' import DeepSeekLlmApiExtensionRegistry from '@deepseek-ai/dsh-deepseek-llm-api-extensions' -import LlmRuntime, { CallId, createUserMessage, GenerateOptions, LlmAdapter, StreamChunk } from '@deepseek-ai/dsh-llm' +import LlmRuntime, { ToolCallId, createUserMessage, GenerateOptions, LlmAdapter, StreamChunk } from '@deepseek-ai/dsh-llm' import { type Config, type ReplayEntry, @@ -539,8 +539,8 @@ describe('installLlmReplay (through the real LlmRuntime)', () => { function scriptedCall(argumentsDelta: string): StreamChunk[] { return [ { type: 'block-start', index: 0, blockType: 'tool-call' }, - { type: 'tool-call-delta', index: 0, id: CallId('c1'), name: 'update_goal', argumentsDelta }, - { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('c1'), name: 'update_goal', arguments: argumentsDelta } }, + { type: 'tool-call-delta', index: 0, id: ToolCallId('c1'), name: 'update_goal', argumentsDelta }, + { type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'update_goal', arguments: argumentsDelta } }, { type: 'finish', reason: { kind: 'tool-calls' } }, ] } @@ -1189,7 +1189,7 @@ describe('installLlmReplay (per-session keying)', () => { index: 0, block: { type: 'tool-call', - id: CallId('send-child'), + id: ToolCallId('send-child'), name: 'send_message', arguments: '{"subagent_id":"{{session:2}}"}', }, @@ -1210,7 +1210,7 @@ describe('installLlmReplay (per-session keying)', () => { index: 0, block: { type: 'tool-call', - id: CallId('send-child'), + id: ToolCallId('send-child'), name: 'send_message', arguments: '{"subagent_id":"live-child"}', }, diff --git a/packages/test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts b/packages/test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts index e12faba64d..58d86280f2 100644 --- a/packages/test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts +++ b/packages/test-support/loader-smoke/tests/fixtures/cli-mock-llm.ts @@ -1,6 +1,6 @@ import type { Context } from '@deepseek-ai/cordis' import { - CallId, + ToolCallId, LlmAdapter, ReasoningEffortId, type GenerateOptions, @@ -41,8 +41,8 @@ class CliMockAdapter extends LlmAdapter { yield { type: 'reasoning-delta', index: 0, text: reasoning } yield { type: 'block-end', index: 0, block: { type: 'reasoning', text: reasoning } } yield { type: 'block-start', index: 1, blockType: 'tool-call' } - yield { type: 'tool-call-delta', index: 1, id: CallId('cli-smoke-call'), name: 'bash', argumentsDelta: args } - yield { type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('cli-smoke-call'), name: 'bash', arguments: args } } + yield { type: 'tool-call-delta', index: 1, id: ToolCallId('cli-smoke-call'), name: 'bash', argumentsDelta: args } + yield { type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('cli-smoke-call'), name: 'bash', arguments: args } } yield { type: 'usage', usage: { inputTokens: 11, outputTokens: 3, cacheReadTokens: 2 } } yield { type: 'finish', reason: { kind: 'tool-calls' } } return diff --git a/packages/todo/tool-todo/tests/loader-composition.spec.ts b/packages/todo/tool-todo/tests/loader-composition.spec.ts index 945b2e84e5..a3f5bd68fb 100644 --- a/packages/todo/tool-todo/tests/loader-composition.spec.ts +++ b/packages/todo/tool-todo/tests/loader-composition.spec.ts @@ -9,7 +9,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' import Include from '@deepseek-ai/cordis-plugin-include' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' @@ -101,7 +101,7 @@ describe('tool-todo real Loader composition through cordis.yml', () => { const owner = agent(ctx) const result = await ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId('parallel'), + callId: ToolCallId('parallel'), name: 'todo_write', arguments: { todos: PARALLEL_TODOS }, agent: owner, @@ -119,7 +119,7 @@ describe('tool-todo real Loader composition through cordis.yml', () => { const owner = agent(ctx) const result = await ctx.tools.execute({ signal: new AbortController().signal, - callId: CallId('parallel-enabled'), + callId: ToolCallId('parallel-enabled'), name: 'todo_write', arguments: { todos: PARALLEL_TODOS }, agent: owner, diff --git a/packages/todo/tool-todo/tests/tool-todo.spec.ts b/packages/todo/tool-todo/tests/tool-todo.spec.ts index 5d44a94679..3d67c993bb 100644 --- a/packages/todo/tool-todo/tests/tool-todo.spec.ts +++ b/packages/todo/tool-todo/tests/tool-todo.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime from '@deepseek-ai/dsh-tools' import { Session, SessionId } from '@deepseek-ai/dsh-session' @@ -39,7 +39,7 @@ function callTodo(ctx: Context, args: unknown, over: { agent?: Agent | undefined const agent = 'agent' in over ? over.agent : agentWithSession() return ctx.tools.execute({ signal: testToolSignal, - callId: CallId(`call-${++callCounter}`), + callId: ToolCallId(`call-${++callCounter}`), name: 'todo_write', arguments: args, ...agent ? { agent } : {}, diff --git a/packages/util/brand/README.i18n.yaml b/packages/util/brand/README.i18n.yaml index ad35ff7bb5..66209cb86b 100644 --- a/packages/util/brand/README.i18n.yaml +++ b/packages/util/brand/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/util/brand/README.md -README.md: 05226688943273d2adc743a56e72caa1f24ccc41 -README.zh.md: 83b18999352130f9ab36906ca59aef9bf77d51f4 +README.md: 879d10107929539b0973e4b0ad3f7f19da7be0ca +README.zh.md: ba1c7e31129d1e58162484ee135210b1176d7499 diff --git a/packages/util/brand/README.md b/packages/util/brand/README.md index 0522668894..879d101079 100644 --- a/packages/util/brand/README.md +++ b/packages/util/brand/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-brand` makes structurally identical strings non-interchangeable at the type level with its `Branded` primitive: a `SessionId` cannot be passed where a `CallId` is expected even though both are plain `string`s at runtime. Comparison, logging, JSON serialization, and the wire format all behave exactly as for ordinary strings because the brand is erased at compile time. It is a type-only package with no runtime code and no dependency on other harness packages, so any package can brand the ids it owns without importing an unrelated capability package. Packages that own a cross-package id — `CallId` in `dsh-llm`, the shared agent/session `SessionId`, `JobId` in `dsh-jobs` — brand that id and construct it through a per-id factory. +`dsh-brand` makes structurally identical strings non-interchangeable at the type level with its `Branded` primitive: a `SessionId` cannot be passed where a `ToolCallId` is expected even though both are plain `string`s at runtime. Comparison, logging, JSON serialization, and the wire format all behave exactly as for ordinary strings because the brand is erased at compile time. It is a type-only package with no runtime code and no dependency on other harness packages, so any package can brand the ids it owns without importing an unrelated capability package. Packages that own a cross-package id — `ToolCallId` in `dsh-llm`, the shared agent/session `SessionId`, `JobId` in `dsh-jobs` — brand that id and construct it through a per-id factory. ## Table of Contents @@ -44,7 +44,7 @@ The factory is a plain cast with zero runtime cost. Once branded, the id flows t ### When to brand -Brand ids that cross package boundaries and could plausibly be confused — `CallId` in `dsh-llm`, the shared agent/session `SessionId` in `dsh-session`, `JobId` in `dsh-jobs`, `LspProviderId` in `dsh-lsp`. Do not brand every string: the cost is a factory at every construction site and a type import in every consumer, so ids that never leave their owning package do not earn it. +Brand ids that cross package boundaries and could plausibly be confused — `ToolCallId` in `dsh-llm`, the shared agent/session `SessionId` in `dsh-session`, `JobId` in `dsh-jobs`, `LspProviderId` in `dsh-lsp`. Do not brand every string: the cost is a factory at every construction site and a type import in every consumer, so ids that never leave their owning package do not earn it. ----- diff --git a/packages/util/brand/README.zh.md b/packages/util/brand/README.zh.md index 83b1899935..ba1c7e3112 100644 --- a/packages/util/brand/README.zh.md +++ b/packages/util/brand/README.zh.md @@ -9,7 +9,7 @@ kind: "package-library" ## 概述 -`dsh-brand` 借助其 `Branded` 原语,让结构相同的字符串在类型层面不可互换:即使 `SessionId` 与 `CallId` 在运行时都是普通 `string`,前者也无法传给期望后者的位置。由于品牌在编译期被擦除,比较、日志记录、JSON 序列化与协议格式(wire format)的行为都与普通字符串完全相同。它是纯类型包,没有运行时代码,也不依赖其他 harness 包,因此任何包都可以为自己拥有的 id 添加品牌,而无需导入不相关的能力包。拥有跨包 id 的包——`dsh-llm` 中的 `CallId`、共享的 agent/会话 `SessionId`、`dsh-jobs` 中的 `JobId`——为该 id 添加品牌,并通过各 id 专用工厂构造。 +`dsh-brand` 借助其 `Branded` 原语,让结构相同的字符串在类型层面不可互换:即使 `SessionId` 与 `ToolCallId` 在运行时都是普通 `string`,前者也无法传给期望后者的位置。由于品牌在编译期被擦除,比较、日志记录、JSON 序列化与协议格式(wire format)的行为都与普通字符串完全相同。它是纯类型包,没有运行时代码,也不依赖其他 harness 包,因此任何包都可以为自己拥有的 id 添加品牌,而无需导入不相关的能力包。拥有跨包 id 的包——`dsh-llm` 中的 `ToolCallId`、共享的 agent/会话 `SessionId`、`dsh-jobs` 中的 `JobId`——为该 id 添加品牌,并通过各 id 专用工厂构造。 ## 目录 @@ -44,7 +44,7 @@ export function SessionId(id: string): SessionId { ### 何时添加品牌 -为跨包边界且可能被混淆的 id 添加品牌——`dsh-llm` 中的 `CallId`、`dsh-session` 中共享的 agent/会话 `SessionId`、`dsh-jobs` 中的 `JobId`、`dsh-lsp` 中的 `LspProviderId`。不要为每个字符串都添加品牌:代价是每个构造点一个工厂、每个消费方一次类型导入,因此从不离开所属包的 id 不值得这样做。 +为跨包边界且可能被混淆的 id 添加品牌——`dsh-llm` 中的 `ToolCallId`、`dsh-session` 中共享的 agent/会话 `SessionId`、`dsh-jobs` 中的 `JobId`、`dsh-lsp` 中的 `LspProviderId`。不要为每个字符串都添加品牌:代价是每个构造点一个工厂、每个消费方一次类型导入,因此从不离开所属包的 id 不值得这样做。 ----- diff --git a/packages/util/brand/src/index.ts b/packages/util/brand/src/index.ts index 379a769e14..942f6dc9d2 100644 --- a/packages/util/brand/src/index.ts +++ b/packages/util/brand/src/index.ts @@ -4,12 +4,12 @@ * cross-boundary id. * * A brand makes structurally-identical strings non-interchangeable at the type - * level: a `SessionId` cannot be passed where a `CallId` is expected, even + * level: a `SessionId` cannot be passed where a `ToolCallId` is expected, even * though both are plain strings at runtime. Construction goes through a per-id * factory in the OWNING package (a plain cast inside — zero runtime cost); * comparison, logging, and serialization all behave as ordinary strings. * - * Policy: a package brands the ids it owns — `CallId` in dsh-llm (tool-call + * Policy: a package brands the ids it owns — `ToolCallId` in dsh-llm (tool-call * correlation), the shared agent/session `SessionId` in dsh-session, and * `JobId` in dsh-jobs. Branding is for ids that cross package boundaries and * could plausibly be confused; not every string needs a brand. diff --git a/packages/web/tool-web/tests/integration.spec.ts b/packages/web/tool-web/tests/integration.spec.ts index 4ba1a845a4..4b7c843f9e 100644 --- a/packages/web/tool-web/tests/integration.spec.ts +++ b/packages/web/tool-web/tests/integration.spec.ts @@ -11,7 +11,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http' import { AddressInfo } from 'node:net' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { type ToolExecutionResult } from '@deepseek-ai/dsh-tools' import WebRuntime from '@deepseek-ai/dsh-web' @@ -61,7 +61,7 @@ afterEach(async () => { let counter = 0 function call(name: string, args: unknown): Promise { - return ctx.tools.execute({ signal: testToolSignal, callId: CallId(`call-${++counter}`), name, arguments: args }) + return ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId(`call-${++counter}`), name, arguments: args }) } describe('web_fetch integration over the real backend', () => { @@ -153,7 +153,7 @@ describe('tool-call timeout returns TOOL_TIMEOUT (deadline wins over a slow fetc }) it('returns a structured TOOL_TIMEOUT (not the provider WEB_FETCH_TIMEOUT) when the tool-call budget wins', async () => { - const out = await tctx.tools.execute({ signal: testToolSignal, callId: CallId('slow-1'), name: 'web_fetch', arguments: { url: slowBase } }) + const out = await tctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('slow-1'), name: 'web_fetch', arguments: { url: slowBase } }) expect(out.isError).toBe(true) // The outer tool-call deadline won: TOOL_TIMEOUT, owned by dsh-tool-call-timeout-policy, // NOT the provider's own WEB_FETCH_TIMEOUT (its 30s backstop never fired). diff --git a/packages/web/tool-web/tests/spill.spec.ts b/packages/web/tool-web/tests/spill.spec.ts index e45d32ac94..4df47e4799 100644 --- a/packages/web/tool-web/tests/spill.spec.ts +++ b/packages/web/tool-web/tests/spill.spec.ts @@ -14,7 +14,7 @@ import { mkdtempSync, readFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from '@deepseek-ai/cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime from '@deepseek-ai/dsh-tools' @@ -68,7 +68,7 @@ afterEach(async () => { /** A web_fetch call carrying a session owner (so the policy can scope the spill). */ function fetchCall(): Promise<{ isError: boolean; content: { type: string; text?: string }[] }> { const agent = { session: { header: { id: SessionId('web-sess') } } } - const exec = { callId: CallId('call-1'), name: 'web_fetch', arguments: { url: base }, agent, signal: testToolSignal } as unknown as ToolExecution + const exec = { callId: ToolCallId('call-1'), name: 'web_fetch', arguments: { url: base }, agent, signal: testToolSignal } as unknown as ToolExecution return ctx.tools.execute(exec) } diff --git a/packages/web/tool-web/tests/tool-web.spec.ts b/packages/web/tool-web/tests/tool-web.spec.ts index cefb675e62..b4bf82dad9 100644 --- a/packages/web/tool-web/tests/tool-web.spec.ts +++ b/packages/web/tool-web/tests/tool-web.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import TurndownService from 'turndown' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { type ToolExecutionResult } from '@deepseek-ai/dsh-tools' import WebRuntime from '@deepseek-ai/dsh-web' @@ -49,7 +49,7 @@ async function mountTools(opts: { if (opts.fetchProvider) ctx.web.registerFetchProvider(opts.fetchProvider) const fiber = await ctx.plugin(ToolWeb, opts.config ?? {}) let counter = 0 - const call = (name: string, args: unknown) => ctx.tools.execute({ signal: testToolSignal, callId: CallId(`call-${++counter}`), name, arguments: args }) + const call = (name: string, args: unknown) => ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId(`call-${++counter}`), name, arguments: args }) return { ctx, fiber, call } } @@ -452,9 +452,9 @@ describe('tool-web registration', () => { const names = ctx.tools.schemas().map(s => s.name) expect(names).toContain('web_search') expect(names).toContain('web_fetch') - expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('search-safe'), name: 'web_search', arguments: { queries: ['q'] } })) + expect(ctx.tools.executionMode({ signal: testToolSignal, callId: ToolCallId('search-safe'), name: 'web_search', arguments: { queries: ['q'] } })) .toEqual({ kind: 'parallel' }) - expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('fetch-safe'), name: 'web_fetch', arguments: { url: 'https://a.test' } })) + expect(ctx.tools.executionMode({ signal: testToolSignal, callId: ToolCallId('fetch-safe'), name: 'web_fetch', arguments: { url: 'https://a.test' } })) .toEqual({ kind: 'parallel' }) await fiber.dispose() expect(ctx.tools.schemas().map(s => s.name)).not.toContain('web_search') @@ -724,7 +724,7 @@ describe('tool-web execution through the real registry', () => { } const { ctx, fiber } = await mountTools({ webConfig: { fetchProvider: 'stub-fetch' }, fetchProvider }) const controller = new AbortController() - const out = await ctx.tools.execute({ callId: CallId('fetch-1'), name: 'web_fetch', arguments: { url: 'https://a.test' }, signal: controller.signal }) + const out = await ctx.tools.execute({ callId: ToolCallId('fetch-1'), name: 'web_fetch', arguments: { url: 'https://a.test' }, signal: controller.signal }) expect(out.isError).toBe(false) expect(out.value).toEqual({ url: 'https://a.test', @@ -751,7 +751,7 @@ describe('tool-web execution through the real registry', () => { }, } const { ctx, fiber } = await mountTools({ webConfig: { fetchProvider: 'stub-fetch' }, fetchProvider }) - const out = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('fetch-2'), name: 'web_fetch', arguments: { url: 'https://a.test' } }) + const out = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('fetch-2'), name: 'web_fetch', arguments: { url: 'https://a.test' } }) expect(out.isError).toBe(false) expect(out.value).toEqual({ url: 'https://a.test', @@ -773,7 +773,7 @@ describe('tool-web execution through the real registry', () => { } const { ctx, fiber } = await mountTools({ webConfig: { searchProvider: 'stub-search' }, search: provider }) const controller = new AbortController() - await ctx.tools.execute({ callId: CallId('search-1'), name: 'web_search', arguments: { queries: ['q'] }, signal: controller.signal }) + await ctx.tools.execute({ callId: ToolCallId('search-1'), name: 'web_search', arguments: { queries: ['q'] }, signal: controller.signal }) expect(seen.signal).toBe(controller.signal) await fiber.dispose() }) @@ -792,7 +792,7 @@ describe('tool-web execution through the real registry', () => { } const { ctx, fiber } = await mountTools({ webConfig: { searchProvider: 'stub-search' }, search: provider }) const controller = new AbortController() - const pending = ctx.tools.execute({ callId: CallId('search-multi-1'), name: 'web_search', arguments: { queries: ['one', 'two'] }, signal: controller.signal }) + const pending = ctx.tools.execute({ callId: ToolCallId('search-multi-1'), name: 'web_search', arguments: { queries: ['one', 'two'] }, signal: controller.signal }) await vi.waitFor(() => { expect(signals).toHaveLength(2) }) expect(signals[0]).toBe(signals[1]) expect(signals[0]).not.toBe(controller.signal) diff --git a/packages/workflow/tool-ralph/tests/integration.spec.ts b/packages/workflow/tool-ralph/tests/integration.spec.ts index 8b3629ffe7..124364e7b4 100644 --- a/packages/workflow/tool-ralph/tests/integration.spec.ts +++ b/packages/workflow/tool-ralph/tests/integration.spec.ts @@ -3,7 +3,7 @@ import { Context } from '@deepseek-ai/cordis' import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import SubagentRuntime from '@deepseek-ai/dsh-subagent' import { STRUCTURED_OUTPUT_TOOL } from '@deepseek-ai/dsh-subagent-in-process-driver' @@ -83,7 +83,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => { }) const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ralph-integration'), + callId: ToolCallId('ralph-integration'), name: 'ralph', arguments: { objective: 'Complete both migration slices.', maxRounds: 2 }, agent: parent, @@ -135,7 +135,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ralph-child-failure'), + callId: ToolCallId('ralph-child-failure'), name: 'ralph', arguments: { objective: 'Complete both migration slices.', maxRounds: 2 }, agent: parent, @@ -224,7 +224,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => { const result = await ctx.tools.execute({ signal: testToolSignal, - callId: CallId('ralph-script-enforcement'), + callId: ToolCallId('ralph-script-enforcement'), name: 'ralph', arguments: { objective: 'Complete the scoped work.', maxRounds: config.maxRounds }, agent: parent, @@ -251,7 +251,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => { ctx.on('workflow/agent-end', (_run, child) => { outcomes.push(child.outcome) }) const controller = new AbortController() const pending = ctx.tools.execute({ - callId: CallId('ralph-real-cancel'), + callId: ToolCallId('ralph-real-cancel'), name: 'ralph', arguments: { objective: 'Keep working until cancelled.', maxRounds: 2 }, agent: parent, diff --git a/packages/workflow/tool-ralph/tests/tool-ralph.spec.ts b/packages/workflow/tool-ralph/tests/tool-ralph.spec.ts index 0ccbbd16c6..39e22e381b 100644 --- a/packages/workflow/tool-ralph/tests/tool-ralph.spec.ts +++ b/packages/workflow/tool-ralph/tests/tool-ralph.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' 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 SubagentRuntime from '@deepseek-ai/dsh-subagent' import type { SubagentCapabilities, SubagentProvider, SubagentRun, SubagentStartRequest } from '@deepseek-ai/dsh-subagent' @@ -100,7 +100,7 @@ function execute( ): Promise { return ctx.tools.execute({ signal: extra?.signal ?? testToolSignal, - callId: CallId('ralph-call'), + callId: ToolCallId('ralph-call'), name: 'ralph', arguments: args, ...extra?.agent === undefined ? {} : { agent: extra.agent }, diff --git a/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts b/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts index f89083faba..959a3a3c8c 100644 --- a/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts +++ b/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts @@ -10,7 +10,7 @@ import type { WorkflowAgentEndInfo, WorkflowAgentInfo, WorkflowResult, WorkflowRun, WorkflowRunId as WorkflowRunIdType, WorkflowStartRequest, } from '@deepseek-ai/dsh-workflow' -import { CallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId } from '@deepseek-ai/dsh-llm' import SubagentRuntime from '@deepseek-ai/dsh-subagent' import WorkerThreadWorkflowEngine from '@deepseek-ai/dsh-workflow-worker-thread' import * as toolWorkflow from '../src/index.ts' @@ -96,7 +96,7 @@ function execute(ctx: Context, args: unknown, extra?: { }): Promise { return ctx.tools.execute({ signal: testToolSignal, - callId: CallId('call-1'), + callId: ToolCallId('call-1'), name: 'workflow', arguments: args, ...extra?.agent ? { agent: extra.agent } : {}, diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index fbc552dbc1..5939a4f9a6 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -38,7 +38,7 @@ type EventEnvelopeTypeName = typeof EVENT_ENVELOPE_TYPE_NAMES[number] /** Documentation target, relative to `docs/`, for linked payload types. */ const LINK_MAP: Record = { - CallId: 'subsystems/core.md', + ToolCallId: 'subsystems/core.md', ContentBlock: 'subsystems/core.md', MessageSource: 'subsystems/core.md', ScheduleChange: 'subsystems/schedule.md', diff --git a/snapshots/session/cordis-inspect-jsdoc/session.jsonl b/snapshots/session/cordis-inspect-jsdoc/session.jsonl index dd4355c65d..0791125afe 100644 --- a/snapshots/session/cordis-inspect-jsdoc/session.jsonl +++ b/snapshots/session/cordis-inspect-jsdoc/session.jsonl @@ -18,7 +18,7 @@ {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[12,13,14,15,16],"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"presentAs(mode: ToolPresentationMode): () => void\",\n \"description\": \"Present the calling scope's tools in `mode` instead of the deployment default. Nearest scope on the chain wins, so a preset's standing declaration covers every agent joined under it.\\n\\nScoped only, and one declaration per scope: this is how an agent preset composes Code Mode agents beside native ones in the same process, and a process-global override would be the `mode` config field instead.\",\n \"parameters\": [\n {\n \"name\": \"mode\",\n \"description\": \"the presentation the covered agents' models see.\"\n }\n ],\n \"returns\": \"the exact disposer that restores the deployment default.\"\n },\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"Restrict global tools for the calling agent scope. Empty filters, unknown names, scope-local names, and reserved transport names fail. Restrictions intersect; scoped registrations remain visible.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic guard after the extensible `tools/pre-execute` waterfall. A plain-context guard applies globally; one registered through `agent.ctx` applies only to that agent. Any matching guard may deny by returning a reason, while no guard can force-allow a call another guard denied. The exact effect disposer is returned for ordered ownership and HMR cleanup.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"synchronous check; a returned string denies the execution.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the guard.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"executionMode(exec: ToolExecutionInput): ToolExecutionMode\",\n \"description\": \"Classify a pending call through the caller's visible tool definition. Only an exact `true` is parallel; unknown, hidden, undeclared, invalid, or throwing classifiers are exclusive.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"call name, parsed arguments, and optional agent scope.\"\n }\n ],\n \"returns\": \"the fail-closed scheduling mode.\"\n },\n {\n \"signature\": \"async execute(exec: ToolExecutionInput): Promise\",\n \"description\": \"Execute through pre-policy, guards, around-dispatch, post-policy, definition-owned content finalization, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a successful started outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"the typed same-process call input. The registry assigns its correlation token before policy begins.\"\n }\n ],\n \"returns\": \"the materialized final result.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssistantProvenance\",\n \"declaration\": \"export interface AssistantProvenance {\\n provider: string;\\n model: string;\\n replayState?: unknown;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"ContextFormed\",\n \"declaration\": \"export type ContextFormed = {\\n readonly form?: never;\\n} | {\\n readonly form: 'instructions';\\n} | {\\n readonly form: 'catalog';\\n} | {\\n readonly form: 'snapshot';\\n readonly sections: readonly ContextSnapshotSection[];\\n} | {\\n readonly form: 'notice';\\n readonly summary: string;\\n} | {\\n readonly form: 'relay';\\n} | {\\n readonly form: 'recall';\\n};\"\n },\n {\n \"name\": \"ContextSnapshotSection\",\n \"declaration\": \"export interface ContextSnapshotSection {\\n readonly name: string;\\n readonly text: string;\\n}\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"Message\",\n \"declaration\": \"export interface Message {\\n readonly id: MessageId;\\n readonly role: 'system' | 'user' | 'assistant';\\n readonly content: ContentBlock[];\\n readonly source: MessageSource;\\n}\"\n },\n {\n \"name\": \"MessageId\",\n \"declaration\": \"export type MessageId = Branded<'MessageId'>;\"\n },\n {\n \"name\": \"MessageSource\",\n \"declaration\": \"export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\"\n },\n {\n \"name\": \"MessageSourceMap\",\n \"declaration\": \"export interface MessageSourceMap {\\n user: {\\n kind: 'user';\\n };\\n plugin: {\\n kind: 'plugin';\\n plugin: string;\\n } & ContextFormed;\\n model: ModelMessageSource;\\n tool: ToolMessageSource;\\n}\"\n },\n {\n \"name\": \"ModelMessageSource\",\n \"declaration\": \"export interface ModelMessageSource extends AssistantProvenance {\\n kind: 'model';\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: CallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: CallId;\\n readonly rootCallId?: CallId;\\n readonly name: string;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionMode\",\n \"declaration\": \"export type ToolExecutionMode = {\\n kind: 'parallel';\\n} | {\\n kind: 'exclusive';\\n};\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolMessageSource\",\n \"declaration\": \"export interface ToolMessageSource {\\n kind: 'tool';\\n callId: CallId;\\n}\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPresentationMode\",\n \"declaration\": \"export type ToolPresentationMode = 'native' | 'code' | 'both';\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends Message {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"{{message:4}}"}},"sourceEventSeqs":[18],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"presentAs(mode: ToolPresentationMode): () => void\",\n \"description\": \"Present the calling scope's tools in `mode` instead of the deployment default. Nearest scope on the chain wins, so a preset's standing declaration covers every agent joined under it.\\n\\nScoped only, and one declaration per scope: this is how an agent preset composes Code Mode agents beside native ones in the same process, and a process-global override would be the `mode` config field instead.\",\n \"parameters\": [\n {\n \"name\": \"mode\",\n \"description\": \"the presentation the covered agents' models see.\"\n }\n ],\n \"returns\": \"the exact disposer that restores the deployment default.\"\n },\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"Restrict global tools for the calling agent scope. Empty filters, unknown names, scope-local names, and reserved transport names fail. Restrictions intersect; scoped registrations remain visible.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic guard after the extensible `tools/pre-execute` waterfall. A plain-context guard applies globally; one registered through `agent.ctx` applies only to that agent. Any matching guard may deny by returning a reason, while no guard can force-allow a call another guard denied. The exact effect disposer is returned for ordered ownership and HMR cleanup.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"synchronous check; a returned string denies the execution.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the guard.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"executionMode(exec: ToolExecutionInput): ToolExecutionMode\",\n \"description\": \"Classify a pending call through the caller's visible tool definition. Only an exact `true` is parallel; unknown, hidden, undeclared, invalid, or throwing classifiers are exclusive.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"call name, parsed arguments, and optional agent scope.\"\n }\n ],\n \"returns\": \"the fail-closed scheduling mode.\"\n },\n {\n \"signature\": \"async execute(exec: ToolExecutionInput): Promise\",\n \"description\": \"Execute through pre-policy, guards, around-dispatch, post-policy, definition-owned content finalization, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a successful started outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"the typed same-process call input. The registry assigns its correlation token before policy begins.\"\n }\n ],\n \"returns\": \"the materialized final result.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssistantProvenance\",\n \"declaration\": \"export interface AssistantProvenance {\\n provider: string;\\n model: string;\\n replayState?: unknown;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"ContextFormed\",\n \"declaration\": \"export type ContextFormed = {\\n readonly form?: never;\\n} | {\\n readonly form: 'instructions';\\n} | {\\n readonly form: 'catalog';\\n} | {\\n readonly form: 'snapshot';\\n readonly sections: readonly ContextSnapshotSection[];\\n} | {\\n readonly form: 'notice';\\n readonly summary: string;\\n} | {\\n readonly form: 'relay';\\n} | {\\n readonly form: 'recall';\\n};\"\n },\n {\n \"name\": \"ContextSnapshotSection\",\n \"declaration\": \"export interface ContextSnapshotSection {\\n readonly name: string;\\n readonly text: string;\\n}\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"Message\",\n \"declaration\": \"export interface Message {\\n readonly id: MessageId;\\n readonly role: 'system' | 'user' | 'assistant';\\n readonly content: ContentBlock[];\\n readonly source: MessageSource;\\n}\"\n },\n {\n \"name\": \"MessageId\",\n \"declaration\": \"export type MessageId = Branded<'MessageId'>;\"\n },\n {\n \"name\": \"MessageSource\",\n \"declaration\": \"export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\"\n },\n {\n \"name\": \"MessageSourceMap\",\n \"declaration\": \"export interface MessageSourceMap {\\n user: {\\n kind: 'user';\\n };\\n plugin: {\\n kind: 'plugin';\\n plugin: string;\\n } & ContextFormed;\\n model: ModelMessageSource;\\n tool: ToolMessageSource;\\n}\"\n },\n {\n \"name\": \"ModelMessageSource\",\n \"declaration\": \"export interface ModelMessageSource extends AssistantProvenance {\\n kind: 'model';\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: ToolCallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: ToolCallId;\\n readonly rootCallId?: ToolCallId;\\n readonly name: string;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionMode\",\n \"declaration\": \"export type ToolExecutionMode = {\\n kind: 'parallel';\\n} | {\\n kind: 'exclusive';\\n};\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolMessageSource\",\n \"declaration\": \"export interface ToolMessageSource {\\n kind: 'tool';\\n callId: ToolCallId;\\n}\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPresentationMode\",\n \"declaration\": \"export type ToolPresentationMode = 'native' | 'code' | 'both';\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends Message {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"{{message:4}}"}},"sourceEventSeqs":[18],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}