refactor(llm): rename CallId to ToolCallId

This commit is contained in:
_Kerman committed 2026-08-21 16:10:24 +08:00
1 parent 8cf2445f11
commit a789637db6
186 files changed
+1013 -1013

No files matched your search

+2 -2
View File
@@ -1,7 +1,7 @@
import { fileURLToPath } from 'node:url'
import { Context } from '@deepseek-ai/cordis'
import { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
import { CallId } from '@deepseek-ai/dsh-llm'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-skill'
@@ -45,7 +45,7 @@ try {
: undefined
const summary = (await ctx.skills.list()).find(skill => skill.name === 'dsh-badge')
const result = await ctx.tools.execute({
callId: CallId('dsh-badge-snapshot'),
callId: ToolCallId('dsh-badge-snapshot'),
name: 'skill',
arguments: { name: 'dsh-badge' },
signal: new AbortController().signal,
+2 -2
View File
@@ -13,7 +13,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { resolveSessionPreset, SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-agent-presets'
import { applyChildComposition, childSessionMeta } from '@deepseek-ai/dsh-subagent'
import { CallId } from '@deepseek-ai/dsh-llm'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-compaction-basic'
import type {} from '@deepseek-ai/dsh-skill'
import type {} from '@deepseek-ai/dsh-tools'
@@ -403,7 +403,7 @@ describe('the shipped Web composition', () => {
// The preset's own loader tool resolves the global-layer skill.
const loaded = await ctx.tools.execute({
callId: CallId('preset-skills-load'),
callId: ToolCallId('preset-skills-load'),
name: 'skill',
arguments: { name: 'dsh-badge' },
signal: new AbortController().signal,
+2 -2
View File
@@ -10,7 +10,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 {
@@ -90,7 +90,7 @@ describe.skipIf(MODE === 'record')('web e2e: background job list', () => {
const started = await scaffold.ctx.tools.execute({
signal: new AbortController().signal,
callId: CallId('background-job-list-e2e'),
callId: ToolCallId('background-job-list-e2e'),
name: 'bash',
arguments: { command: COMMAND, description: 'Hold a background slot open', run_in_background: true },
agent,
@@ -9,7 +9,7 @@ import { join } from 'node:path'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
import type { ReplayEntry, ReplayOverrideDoc } from '@deepseek-ai/dsh-llm-replay'
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
import {
@@ -32,7 +32,7 @@ interface TurnSpec {
readonly firstMarker: string
readonly doneMarker: string
readonly deltas: readonly string[]
readonly callId?: ReturnType<typeof CallId>
readonly callId?: ReturnType<typeof ToolCallId>
readonly toolResultMarker?: string
}
@@ -81,7 +81,7 @@ function turnSpec(index: number): TurnSpec {
firstMarker,
doneMarker,
deltas,
callId: CallId(`continuous-chat-tool-${id}`),
callId: ToolCallId(`continuous-chat-tool-${id}`),
toolResultMarker: `CONTINUOUS_CHAT_TOOL_RESULT_${id}`,
}
}
+2 -2
View File
@@ -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'
@@ -34,7 +34,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'
+2 -2
View File
@@ -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,
+3 -3
View File
@@ -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)}`,
+4 -4
View File
@@ -3,7 +3,7 @@ import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterAll, beforeAll, describe, expect, it } 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'
@@ -68,14 +68,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,
@@ -84,7 +84,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,
+2 -2
View File
@@ -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', {
+2 -2
View File
@@ -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', {
+2 -2
View File
@@ -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,
@@ -98,7 +98,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"',
+4 -4
View File
@@ -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,
+4 -4
View File
@@ -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'
@@ -199,7 +199,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',
@@ -217,7 +217,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,
@@ -231,7 +231,7 @@ it('lets a preset producer reach the background-job registry', async () => {
// through a preset-plane control, which is the linkage the realm severed.
const collected = await ctx.tools.execute({
signal,
callId: CallId('shipped-task-output'),
callId: ToolCallId('shipped-task-output'),
name: 'job_output',
arguments: { job_id: 'bash-1', wait: true },
agent: handle.agent,
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/cordis-tutorial/07-into-the-harness.md
07-into-the-harness.md: 2d3c23f9f7f7fc6bd6cabd4e7e68ebfc46e20665
07-into-the-harness.zh.md: 0e0fe031be92c8284d2ff6331d753b9b25994ee9
07-into-the-harness.md: 1bfe05fb511b54067b913a1a2d0903a0772a22d7
07-into-the-harness.zh.md: 5ed4ff9d95d6e8b14402e525e1e9bb5328bfb678
+3 -3
View File
@@ -11,7 +11,7 @@ Create `greet-tool.ts` in `tmp/cordis-tutorial`:
```ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { CallId } from '@deepseek-ai/dsh-llm'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
export const name = 'greet-tool'
export const inject = ['tools']
@@ -33,10 +33,10 @@ export function apply(ctx: Context) {
}))
// Drive one call through the real execution pipeline, standing in for
// the model. CallId brands the correlation id a provider would issue.
// the model. ToolCallId brands the correlation id a provider would issue.
void (async () => {
const result = await ctx.tools.execute({
callId: CallId('demo-1'),
callId: ToolCallId('demo-1'),
name: 'greet',
arguments: { name: 'Cordis' },
signal: new AbortController().signal,
@@ -11,7 +11,7 @@
```ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { CallId } from '@deepseek-ai/dsh-llm'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
export const name = 'greet-tool'
export const inject = ['tools']
@@ -33,10 +33,10 @@ export function apply(ctx: Context) {
}))
// Drive one call through the real execution pipeline, standing in for
// the model. CallId brands the correlation id a provider would issue.
// the model. ToolCallId brands the correlation id a provider would issue.
void (async () => {
const result = await ctx.tools.execute({
callId: CallId('demo-1'),
callId: ToolCallId('demo-1'),
name: 'greet',
arguments: { name: 'Cordis' },
signal: new AbortController().signal,
+2 -2
View File
@@ -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: f8c10821e4b6daa8f10b0cff63f838ecb381125c
persistence-catalog.zh.md: 4cefec2eba0a3a3fa7f4c587236fa78e476ad9d7
persistence-catalog.md: 44a9f61b7b53670186c565efc0b4c587e216d6b8
persistence-catalog.zh.md: 2ea0e7c236301b786a3f5aab66ad58e802f017c7
+4 -4
View File
@@ -153,12 +153,12 @@ Source: [`packages/preset/agent-presets/src/session.ts:26`](../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/index.ts:44`](../packages/interaction/user-approval/src/index.ts)
@@ -799,10 +799,10 @@ Source: [`packages/core/session/src/types.ts:303`](../packages/core/session/src/
* 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:283`](../packages/core/session/src/types.ts)
+4 -4
View File
@@ -155,12 +155,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
'approval/asked': {
id: ApprovalRequestId
toolName: string
callId?: CallId
callId?: ToolCallId
reason?: string
}
```
类型:[CallId](subsystems/core.zh.md)
类型:[ToolCallId](subsystems/core.zh.md)
来源:[`packages/interaction/user-approval/src/index.ts:44`](../packages/interaction/user-approval/src/index.ts)
@@ -801,10 +801,10 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
* call with its `tool/result`.
*/
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
```
类型:[CallId](subsystems/core.zh.md)
类型:[ToolCallId](subsystems/core.zh.md)
来源:[`packages/core/session/src/types.ts:283`](../packages/core/session/src/types.ts)
+2 -2
View File
@@ -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: 7d3d09314f8fbb151cc8a00dbbee5e2cc14e2c9a
approval.zh.md: 22d0b0ec4242fcb2ad6fb82c29893a5914369238
approval.md: 276cac2c00e5b781e9cdbb5da461d035e61de719
approval.zh.md: 5fe63b5d1aa39ddced51af2c0eaf860e4df10e86
+1 -1
View File
@@ -70,7 +70,7 @@ interface ApprovalRequest {
* 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
/**
+1 -1
View File
@@ -70,7 +70,7 @@ interface ApprovalRequest {
* The exact tool call being decided, when the asker has one — lets a UI
* attach the prompt to the tool call it already streamed.
*/
readonly callId?: CallId
readonly callId?: ToolCallId
/** The asker's human-readable explanation of WHY it is asking. */
readonly reason?: string
/**
+2 -2
View File
@@ -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: 4b6e9dee81cb30d42bb6194776457e354102b529
compaction.zh.md: a9c57b64798be1d0361125eac68d589b3e0f1c66
compaction.md: ebd8e6e9d7530ff5b0bb9faf4a4d5d24b00e464d
compaction.zh.md: e51e8f7d627a339e3e5a9c079d5b5a57f384772d
+1 -1
View File
@@ -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. */
+1 -1
View File
@@ -99,7 +99,7 @@ interface PrunedEntry {
/** Newly appended pruned tool-result event. */
readonly replacementSeq: number
/** Tool call shared by the original and replacement. */
readonly callId: CallId
readonly callId: ToolCallId
/** Original text size in Unicode code points. */
readonly charsBefore: number
/** Replacement text size in Unicode code points. */
+2 -2
View File
@@ -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: 18057c9a9a308e439bb6a08f598f51f158243496
core.zh.md: dfb1d8ad482b2cf9c6de94bde04c4dbff83a0082
core.md: 37c185ff8c90f88aecb0b2a7e426bc8f22162578
core.zh.md: 6559e25a5cdb534d98c3fd28cd68c506bdbaf1f4
+2 -2
View File
@@ -295,7 +295,7 @@ Two large discriminated unions are the ones consumers `switch` over most: **`Str
### Branded IDs
IDs passed between packages are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `CallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings.
IDs passed between packages are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `ToolCallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings.
The `Branded<B>` primitive lives in its own type-only package, [dsh-brand](../../packages/util/brand) (no runtime code, no harness-package dependency), so any package can brand the ids it owns without depending on an unrelated capability package.
@@ -306,7 +306,7 @@ Source: [`packages/util/brand/src/index.ts`](../../packages/util/brand/src/index
type Branded<B extends string> = string & { readonly [BRAND]: B }
```
The two core IDs are `CallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `JobId` in [jobs.md](jobs.md).
The two core IDs are `ToolCallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `JobId` in [jobs.md](jobs.md).
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
+2 -2
View File
@@ -305,7 +305,7 @@ declare module '@deepseek-ai/dsh-llm' {
### 品牌化 ID
在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `CallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。
在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `ToolCallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。
`Branded<B>` 原语位于独立的纯类型包 [dsh-brand](../../packages/util/brand) 中(没有运行时代码,也不依赖 harness 包),因此任何包都能品牌化其拥有的 id,而无需依赖无关的能力包。
@@ -316,7 +316,7 @@ declare module '@deepseek-ai/dsh-llm' {
type Branded<B extends string> = string & { readonly [BRAND]: B }
```
两个核心 ID 是 `CallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [jobs.md](jobs.zh.md) 中的 `JobId`。
两个核心 ID 是 `ToolCallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [jobs.md](jobs.zh.md) 中的 `JobId`。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
+2 -2
View File
@@ -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: 4f322ca1024b9d74a4906e34f93fc6f8e4082cbf
llm-streaming.zh.md: c74cabe27c1f5fdd44711ac0aae7cd6b0a7ba7dd
llm-streaming.md: adb8fdde50c898b42f74a1562f95ab267afa6db6
llm-streaming.zh.md: 197ae86a2182b93c3fbf4005b3fc51df96dc49b3
+2 -2
View File
@@ -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.
Source: [`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts)
@@ -193,7 +193,7 @@ type StreamChunk =
| { type: 'block-start'; index: number; blockType: ContentBlockType }
| { type: 'text-delta'; index: number; text: string }
| { type: 'reasoning-delta'; index: number; text: string }
| { type: 'tool-call-delta'; index: number; id: CallId; name?: string; argumentsDelta: string }
| { type: 'tool-call-delta'; index: number; id: ToolCallId; name?: string; argumentsDelta: string }
| { type: 'block-end'; index: number; block: ContentBlock }
| { type: 'usage'; usage: TokenUsage }
| {
+2 -2
View File
@@ -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。
源码:[`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts)
@@ -193,7 +193,7 @@ type StreamChunk =
| { type: 'block-start'; index: number; blockType: ContentBlockType }
| { type: 'text-delta'; index: number; text: string }
| { type: 'reasoning-delta'; index: number; text: string }
| { type: 'tool-call-delta'; index: number; id: CallId; name?: string; argumentsDelta: string }
| { type: 'tool-call-delta'; index: number; id: ToolCallId; name?: string; argumentsDelta: string }
| { type: 'block-end'; index: number; block: ContentBlock }
| { type: 'usage'; usage: TokenUsage }
| {
+2 -2
View File
@@ -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: 96520c72b83bf713b7fe09563f7d6e44c0386bcf
session.zh.md: 28a259226b8149dbe26c7650bc574de4aa43a740
session.md: a23ed4c43befc1c39b110560d933ff2bb1bce941
session.zh.md: a564ddd34705d530267f3f5bba5afe08b7dcc0ae
+1 -1
View File
@@ -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
+1 -1
View File
@@ -71,7 +71,7 @@ interface SessionEventMap {
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
* call with its `tool/result`.
*/
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
/**
* A completed tool call's model-facing result, optional internal failure
* identity, and optional tool-private `meta` presentation payload. `meta` is
+2 -2
View File
@@ -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: 8356fc45f8581c6776238f212592c4ea2841a96a
spill.zh.md: 353c5fea63ff6284c922a9bbf7d6d07f124fb9bd
spill.md: 2a3201cf2fa0d7b034a01f64d0448536bfe8d861
spill.zh.md: d4c5b3853be46780cd93e2f826bf399bff42c405
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -50,7 +50,7 @@ interface SpillSource {
/** The tool whose result was spilled (e.g. `web_fetch`). */
toolName: string
/** The model-issued call id the result belongs to. */
callId: CallId
callId: ToolCallId
/** A short human label for the artifact (e.g. `result`). */
label: string
}
+2 -2
View File
@@ -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
+4 -4
View File
@@ -183,12 +183,12 @@ type ToolExecutionToken = symbol & { readonly [toolExecutionTokenBrand]: true }
* callers do not choose that token.
*/
interface ToolExecutionInput {
readonly callId: CallId
readonly callId: ToolCallId
/**
* Root model-requested call owning this execution tree. Callers omit it for
* a root execution; nested dispatchers propagate the enclosing value.
*/
readonly rootCallId?: CallId
readonly rootCallId?: ToolCallId
readonly name: string
/** Losslessly JSON-serializable parsed arguments (tools validate their own schema). */
readonly arguments: unknown
@@ -270,7 +270,7 @@ interface CodeDispatchLog {
/** The calling agent (the scope routing key and the spill owner), when the outer call has one. */
readonly agent?: Agent
/** Deterministic sub-call id (`<parent>:code:<n>`). */
readonly subCallId: CallId
readonly subCallId: ToolCallId
/** The dispatched sub-tool name. */
readonly name: string
/** Whether the sub-call settled as an error. */
@@ -290,7 +290,7 @@ interface CodeDispatchLog {
*/
interface ToolExecution extends ToolExecutionInput {
/** Root model-requested call, resolved for every root and nested execution. */
readonly rootCallId: CallId
readonly rootCallId: ToolCallId
/** Registry-assigned identity shared with nested calls only as their opaque `parent` token. */
readonly token: ToolExecutionToken
}
+4 -4
View File
@@ -183,12 +183,12 @@ type ToolExecutionToken = symbol & { readonly [toolExecutionTokenBrand]: true }
* callers do not choose that token.
*/
interface ToolExecutionInput {
readonly callId: CallId
readonly callId: ToolCallId
/**
* Root model-requested call owning this execution tree. Callers omit it for
* a root execution; nested dispatchers propagate the enclosing value.
*/
readonly rootCallId?: CallId
readonly rootCallId?: ToolCallId
readonly name: string
/** Losslessly JSON-serializable parsed arguments (tools validate their own schema). */
readonly arguments: unknown
@@ -270,7 +270,7 @@ interface CodeDispatchLog {
/** The calling agent (the scope routing key and the spill owner), when the outer call has one. */
readonly agent?: Agent
/** Deterministic sub-call id (`<parent>:code:<n>`). */
readonly subCallId: CallId
readonly subCallId: ToolCallId
/** The dispatched sub-tool name. */
readonly name: string
/** Whether the sub-call settled as an error. */
@@ -290,7 +290,7 @@ interface CodeDispatchLog {
*/
interface ToolExecution extends ToolExecutionInput {
/** Root model-requested call, resolved for every root and nested execution. */
readonly rootCallId: CallId
readonly rootCallId: ToolCallId
/** Registry-assigned identity shared with nested calls only as their opaque `parent` token. */
readonly token: ToolExecutionToken
}
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/user/develop/practice/llm-adapter.md
llm-adapter.md: 882e82d880ac622cf886c6c24e75a1987e1c6702
llm-adapter.zh.md: 27c480af2a19a68ac35900a2ce1b5e9dbdc85bf8
llm-adapter.md: ff5fc5657e2be0a5fe0d4e10f148865e62f92b18
llm-adapter.zh.md: b05bfad6cbc4038f54f816b212068d85e54c2100
+3 -3
View File
@@ -54,7 +54,7 @@ export function apply(ctx: Context, config: Config) {
`stream()` yields chunks using this protocol:
```ts
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
async function* exampleChunks(): AsyncIterable<StreamChunk> {
// 1. Start each content block with block-start.
@@ -76,7 +76,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
yield {
type: 'tool-call-delta',
index: 1,
id: CallId('call-123'),
id: ToolCallId('call-123'),
name: 'bash',
argumentsDelta: '{"command":"ls"}',
}
@@ -85,7 +85,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
index: 1,
block: {
type: 'tool-call',
id: CallId('call-123'),
id: ToolCallId('call-123'),
name: 'bash',
arguments: '{"command":"ls"}',
},
+3 -3
View File
@@ -54,7 +54,7 @@ export function apply(ctx: Context, config: Config) {
`stream()` 必须按以下协议生成分片:
```ts
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
import { ToolCallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
async function* exampleChunks(): AsyncIterable<StreamChunk> {
// 1. Start each content block with block-start.
@@ -76,7 +76,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
yield {
type: 'tool-call-delta',
index: 1,
id: CallId('call-123'),
id: ToolCallId('call-123'),
name: 'bash',
argumentsDelta: '{"command":"ls"}',
}
@@ -85,7 +85,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
index: 1,
block: {
type: 'tool-call',
id: CallId('call-123'),
id: ToolCallId('call-123'),
name: 'bash',
arguments: '{"command":"ls"}',
},
@@ -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 },
})
@@ -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
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import LlmRuntime, { createUserMessage, CallId, HarnessError } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, ToolCallId, HarnessError } from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
@@ -93,7 +93,7 @@ function runCode(
agent?: Agent,
): Promise<ToolExecutionResult> {
return harness.tools.execute({
callId: CallId(`keyless-code-${++keylessCall}`),
callId: ToolCallId(`keyless-code-${++keylessCall}`),
name: RUN_CODE_NAME,
arguments: { code, description: 'Run the e2e program' },
signal,
+3 -3
View File
@@ -1,6 +1,6 @@
import type { Context } from '@deepseek-ai/cordis'
import {
CallId,
ToolCallId,
LlmAdapter,
ReasoningEffortId,
type GenerateOptions,
@@ -37,8 +37,8 @@ class CliMockAdapter extends LlmAdapter {
if (toolResult === undefined) {
const args = JSON.stringify({ command: 'printf CLI_TOOL_ROUND_TRIP', description: 'Prove the CLI tool round trip.' })
yield { type: 'block-start', index: 0, blockType: 'tool-call' }
yield { type: 'tool-call-delta', index: 0, id: CallId('cli-smoke-call'), name: 'bash', argumentsDelta: args }
yield { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('cli-smoke-call'), name: 'bash', arguments: args } }
yield { type: 'tool-call-delta', index: 0, id: ToolCallId('cli-smoke-call'), name: 'bash', argumentsDelta: args }
yield { type: 'block-end', index: 0, 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
+2 -2
View File
@@ -1,6 +1,6 @@
/** Deterministic keyless Agent Teams adapter for the real headless Loader snapshot. */
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
let nextCall = 0
@@ -38,7 +38,7 @@ function latestToolText(messages) {
function toolChunks(specs) {
const chunks = []
for (const [index, spec] of specs.entries()) {
const id = CallId(`team-fixture-${++nextCall}`)
const id = ToolCallId(`team-fixture-${++nextCall}`)
const args = JSON.stringify(spec.args)
chunks.push(
{ type: 'block-start', index, blockType: 'tool-call' },
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'
import { Context } from '@deepseek-ai/cordis'
import { normalizeSessionSnapshot, type NormalizeContext } from '@deepseek-ai/dsh-acp-snapshot'
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
import { createUserMessage, CallId , createMessage } from '@deepseek-ai/dsh-llm'
import { createUserMessage, ToolCallId , createMessage } from '@deepseek-ai/dsh-llm'
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session'
import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
import { describe, expect, it } from 'vitest'
@@ -46,7 +46,7 @@ async function seedInterruptedSession(root: string, cwd: string): Promise<string
step: 1,
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: CallId('unknown-outcome-call'), name: 'write_remote', arguments: '{"value":1}' }],
content: [{ type: 'tool-call', id: ToolCallId('unknown-outcome-call'), name: 'write_remote', arguments: '{"value":1}' }],
source: {
kind: 'model',
...{ provider: 'deepseek-official', model: 'deepseek-v4-flash' },
@@ -62,7 +62,7 @@ async function seedInterruptedSession(root: string, cwd: string): Promise<string
data: {
turn: 1,
step: 1,
callId: CallId('unknown-outcome-call'),
callId: ToolCallId('unknown-outcome-call'),
name: 'write_remote',
arguments: '{"value":1}',
},
@@ -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
+3 -3
View File
@@ -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'
@@ -21,7 +21,7 @@ describe('ACP machine permission policy', () => {
const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] })
const agent = harness.ctx.agents.get(SessionId(sessionId))!
agent.session.append('turn/start', { turn: 1 })
return { agent, toolName: 'bash', callId: CallId('call-9'), ...overrides }
return { agent, toolName: 'bash', callId: ToolCallId('call-9'), ...overrides }
}
it('maps the two advertised one-shot choices', async () => {
@@ -63,7 +63,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)
})
+3 -3
View File
@@ -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'
@@ -8,8 +8,8 @@ import { makeBridgeHarness, textResponse, type BridgeHarness } from './harness.t
function toolCallResponse(): StreamChunk[] {
return [
{ type: 'block-start', index: 0, blockType: 'tool-call' },
{ type: 'tool-call-delta', index: 0, id: CallId('call-1'), name: 'echo', argumentsDelta: '{}' },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('call-1'), name: 'echo', arguments: '{}' } },
{ type: 'tool-call-delta', index: 0, id: ToolCallId('call-1'), name: 'echo', argumentsDelta: '{}' },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('call-1'), name: 'echo', arguments: '{}' } },
{ type: 'finish', reason: { kind: 'tool-calls' } },
]
}
@@ -11,7 +11,7 @@ import {
createUserMessage,
isTokenDelta,
} from '@deepseek-ai/dsh-llm/message'
import { CallId } from '@deepseek-ai/dsh-llm/brand'
import { ToolCallId } from '@deepseek-ai/dsh-llm/brand'
import type {
AssistantMessage,
ContentBlock,
@@ -62,7 +62,7 @@ function assistantMessage(content: ContentBlock[], model = 'fx-1'): AssistantMes
}
function toolResultMessage(callId: string, content: ContentBlock[], isError: boolean): ToolResultMessage {
return createToolResultMessage({ callId: CallId(callId), content, isError })
return createToolResultMessage({ callId: ToolCallId(callId), content, isError })
}
const MARKDOWN_FIXTURE = [
@@ -1,4 +1,4 @@
import { createUserMessage, createMessage, createToolResultMessage, CallId } from '@deepseek-ai/dsh-llm'
import { createUserMessage, createMessage, createToolResultMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
// Minimal SessionEvent builders for orchestration tests (shape mirrors what the
// host emits; only the fields the object layer reads).
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
@@ -45,7 +45,7 @@ export const ev = {
turn,
step,
message: createToolResultMessage({
callId: CallId(callId),
callId: ToolCallId(callId),
content: text(body),
isError: false,
}),
@@ -20,7 +20,7 @@ import type {
import type { createChatStore } from '../stores.ts'
import type { ComposerSubmitGesture, InputSubmitMode } from './composer-submission.ts'
import type { ChatNode, ChatNodeKind } from './chat-nodes.ts'
import type { CallId, SelectionTarget, ViewTab } from './views.ts'
import type { ToolCallId, SelectionTarget, ViewTab } from './views.ts'
/** Browser-owned image that has not crossed the durable host boundary. */
export interface ComposerAttachment {
@@ -344,7 +344,7 @@ export interface InputZone {
*/
export interface ConvViewOwnerProps {
/** One-shot inspect request from another view (chat's Inspect button); null when idle. */
inspect?: { callId: CallId } | null
inspect?: { callId: ToolCallId } | null
/** Acknowledge the inspect request once applied (clears the store field). */
onInspectDone?: () => void
}
@@ -415,11 +415,11 @@ export interface ChatNodeTurnDataInjected {
/** Stable owner currency delivered to one keyed Chat business renderer. */
export interface ChatNodeOwnerProps {
/** Selected Tool call, when the shared details store names one. */
selectedCallId?: CallId | undefined
selectedCallId?: ToolCallId | undefined
/** Session workspace root; Tool summaries display paths relative to it. */
cwd?: string | undefined
openFile: (path: string) => void
inspectCall: (callId: CallId) => void
inspectCall: (callId: ToolCallId) => void
forkAt: (seq: number) => void
/** Render a historical image group through the attachment slot. */
renderMessageImages: RenderMessageImages
@@ -761,7 +761,7 @@ export interface ChatViewInjected {
/** Resolve a session-authorized historical image for inline display. */
loadImage: (attachment: ImageAttachmentRef) => Promise<string>
/** Hand a call off to the trajectory view: write the one-shot inspect target and switch tabs. */
inspectCall: (callId: CallId) => void
inspectCall: (callId: ToolCallId) => void
/**
* Per-session scroll memory surviving view switches (in-memory, never
* persisted): the view saves on every scroll and restores on remount; a
@@ -1,10 +1,10 @@
/** Shared conversation view, selection, and store-state contracts. */
/** Tool call identity as carried on the wire (branded upstream in connection). */
export type CallId = string
export type ToolCallId = string
/** Selection target for the details linkage channel (toolcall is the step special case). */
export interface SelectionTarget { turnSeq: number; stepSeq?: number; callId?: CallId; toolName?: string }
export interface SelectionTarget { turnSeq: number; stepSeq?: number; callId?: ToolCallId; toolName?: string }
/**
* One conversation view tab, projected from a 'conversation.view' slot
@@ -28,5 +28,5 @@ export interface ChatStoreState {
* view consumes it and acknowledges by clearing. Read with `?? null` —
* persisted snapshots from before this field rehydrate without it.
*/
inspect: { callId: CallId } | null
inspect: { callId: ToolCallId } | null
}
@@ -20,7 +20,7 @@ export type { IConversation } from './service.ts'
export type { DraftAttachmentId } from './input/contract.ts'
export type {
CallId, ChatStoreState, SelectionTarget, ViewTab,
ToolCallId, ChatStoreState, SelectionTarget, ViewTab,
} from './contract/views.ts'
export type { ConversationKey } from './locales.ts'
export type {
@@ -3,14 +3,14 @@
* The plugin creates its handle at apply time so identity follows the fiber.
*/
import { defineStore, type EngineStoreHandle } from '@deepseek-ai/dsh-client-runtime/client'
import type { CallId, ChatStoreState, SelectionTarget } from './contract/views.ts'
import type { ToolCallId, ChatStoreState, SelectionTarget } from './contract/views.ts'
/** Declared action shape used to give the exported factory a stable return type. */
type ChatActions = {
select: (draft: ChatStoreState, target: SelectionTarget | null) => void
setDraft: (draft: ChatStoreState, text: string) => void
setView: (draft: ChatStoreState, view: string) => void
setInspect: (draft: ChatStoreState, target: { callId: CallId } | null) => void
setInspect: (draft: ChatStoreState, target: { callId: ToolCallId } | null) => void
}
/**
@@ -28,7 +28,7 @@ export function createChatStore(): EngineStoreHandle<ChatStoreState, ChatActions
select: (d, target: SelectionTarget | null) => { d.selection = target },
setDraft: (d, text: string) => { d.draft = text },
setView: (d, view: string) => { d.view = view },
setInspect: (d, target: { callId: CallId } | null) => { d.inspect = target },
setInspect: (d, target: { callId: ToolCallId } | null) => { d.inspect = target },
},
})
}
@@ -12,7 +12,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,
@@ -142,7 +142,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) }],
@@ -190,7 +190,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({
@@ -568,7 +568,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', {
@@ -734,7 +734,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', {
@@ -1183,7 +1183,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,
@@ -1199,7 +1199,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,
@@ -1417,7 +1417,7 @@ describe('default one-shot summarizer', () => {
it('rejects image summary output nested in a tool result', async () => {
const { compact } = await summarizerHarness([{
type: 'tool-result',
toolCallId: CallId('summary-tool'),
toolCallId: ToolCallId('summary-tool'),
content: [{
type: 'image',
attachment: {
@@ -3,7 +3,7 @@ import { Context } from '@deepseek-ai/cordis'
import { toolPairingBalancedAfter, toolPairingBalancedBefore } from '@deepseek-ai/dsh-compaction'
import { createUserMessage, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, resolveRetryPolicy , createMessage } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm'
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
@@ -54,7 +54,7 @@ class StepwiseToolAdapter extends LlmAdapter {
const n = this.calls
this.calls += 1
if (n < this.toolSteps) {
const id = CallId(`c${n}`)
const id = ToolCallId(`c${n}`)
const args = `{"i":${n}}`
yield { type: 'block-start', index: 0, blockType: 'text' }
yield { type: 'block-end', index: 0, block: { type: 'text', text: `step ${n}` } }
@@ -1,4 +1,4 @@
import type { CallId } from '@deepseek-ai/dsh-llm'
import type { ToolCallId } from '@deepseek-ai/dsh-llm'
/** Character-budget policy for deterministic tool-result pruning. */
export interface ToolResultPruneConfig {
@@ -24,7 +24,7 @@ export interface PrunedEntry {
/** Newly appended pruned tool-result event. */
readonly replacementSeq: number
/** Tool call shared by the original and replacement. */
readonly callId: CallId
readonly callId: ToolCallId
/** Original text size in Unicode code points. */
readonly charsBefore: number
/** Replacement text size in Unicode code points. */
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import { ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import SessionStore, {
Session,
@@ -43,7 +43,7 @@ function appendToolStep(
content: ContentBlock[],
extra: Record<string, unknown> = {},
): number {
const callId = CallId(call)
const callId = ToolCallId(call)
session.append('turn/start', {
turn,
})
@@ -126,7 +126,7 @@ describe('ToolResultPruner content transform', () => {
const reasoning: ContentBlock = { type: 'reasoning', text: 'private-rich-block' }
const call: ContentBlock = {
type: 'tool-call',
id: CallId('nested'),
id: ToolCallId('nested'),
name: 'nested',
arguments: '{}',
}
@@ -178,7 +178,7 @@ describe('ToolResultPruner session transaction', () => {
expect(result.pruned).toHaveLength(1)
expect(result.charsRemoved).toBeGreaterThan(0)
const entry = result.pruned[0]!
expect(entry).toMatchObject({ originalSeq, callId: CallId('one'), charsBefore: 100 })
expect(entry).toMatchObject({ originalSeq, callId: ToolCallId('one'), charsBefore: 100 })
expect(entry.charsAfter).toBeLessThanOrEqual(50)
const original = session.events[originalSeq]!
@@ -201,7 +201,7 @@ describe('ToolResultPruner session transaction', () => {
step: 1,
isError: true,
message: {
source: { kind: 'tool', callId: CallId('one') },
source: { kind: 'tool', callId: ToolCallId('one') },
},
error: { name: 'ExitError', code: 'EXIT_1' },
meta: { diff: ['a', 'b'] },
@@ -236,7 +236,7 @@ describe('ToolResultPruner session transaction', () => {
const prune = service()
const first = prune.pruneSession(session)
const second = prune.pruneSession(session)
expect(first.pruned.map(entry => entry.callId)).toEqual([CallId('a'), CallId('c')])
expect(first.pruned.map(entry => entry.callId)).toEqual([ToolCallId('a'), ToolCallId('c')])
expect(first.charsRemoved).toBe(
first.pruned.reduce((sum, entry) => sum + entry.charsBefore - entry.charsAfter, 0),
)
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import { createUserMessage, ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import { toolPairingBalancedAfter, toolPairingBalancedBefore } from '@deepseek-ai/dsh-compaction'
import { Session, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
@@ -35,7 +35,7 @@ function closedToolStep(): Session {
step: 1,
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: CallId('c1'), name: 'bash', arguments: '{}' }],
content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'bash', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'mock' },
@@ -46,7 +46,7 @@ function closedToolStep(): Session {
turn: 1,
step: 1,
message: createToolResultMessage({
callId: CallId('c1'),
callId: ToolCallId('c1'),
content: [{ type: 'text', text: 'done' }],
isError: false,
}),
@@ -70,7 +70,7 @@ describe('tool-pairing boundaries', () => {
step: 1,
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: CallId('open'), name: 'bash', arguments: '{}' }],
content: [{ type: 'tool-call', id: ToolCallId('open'), name: 'bash', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'mock' },
@@ -88,8 +88,8 @@ describe('tool-pairing boundaries', () => {
message: createMessage({
role: 'assistant',
content: [
{ type: 'tool-call', id: CallId('c1'), name: 'one', arguments: '{}' },
{ type: 'tool-call', id: CallId('c2'), name: 'two', arguments: '{}' },
{ type: 'tool-call', id: ToolCallId('c1'), name: 'one', arguments: '{}' },
{ type: 'tool-call', id: ToolCallId('c2'), name: 'two', arguments: '{}' },
],
source: {
kind: 'model',
@@ -100,7 +100,7 @@ describe('tool-pairing boundaries', () => {
session.append('tool/result', {
turn: 1, step: 1,
message: createToolResultMessage({
callId: CallId('c1'),
callId: ToolCallId('c1'),
content: [],
isError: false,
}),
@@ -108,7 +108,7 @@ describe('tool-pairing boundaries', () => {
session.append('tool/result', {
turn: 1, step: 1,
message: createToolResultMessage({
callId: CallId('c2'),
callId: ToolCallId('c2'),
content: [],
isError: false,
}),
@@ -125,7 +125,7 @@ describe('tool-pairing boundaries', () => {
step: 1,
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: CallId('c1'), name: 'bash', arguments: '{}' }],
content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'bash', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'mock' },
@@ -139,7 +139,7 @@ describe('tool-pairing boundaries', () => {
midStep.append('tool/result', {
turn: 1, step: 1,
message: createToolResultMessage({
callId: CallId('c1'),
callId: ToolCallId('c1'),
content: [],
isError: false,
}),
@@ -217,7 +217,7 @@ describe('tool-pairing cache refresh', () => {
step: 1,
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: CallId('c1'), name: 'one', arguments: '{}' }],
content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'one', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'mock' },
@@ -231,7 +231,7 @@ describe('tool-pairing cache refresh', () => {
data: {
turn: 1, step: 1,
message: createToolResultMessage({
callId: CallId('c1'),
callId: ToolCallId('c1'),
content: [],
isError: false,
}),
@@ -297,7 +297,7 @@ describe('tool-pairing cache refresh', () => {
step: 1,
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: CallId('c2'), name: 'two', arguments: '{}' }],
content: [{ type: 'tool-call', id: ToolCallId('c2'), name: 'two', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'mock' },
@@ -311,7 +311,7 @@ describe('tool-pairing cache refresh', () => {
data: {
turn: 2, step: 1,
message: createToolResultMessage({
callId: CallId('c2'),
callId: ToolCallId('c2'),
content: [],
isError: false,
}),
@@ -370,7 +370,7 @@ describe('tool-pairing corrupt surfaces', () => {
session.append('tool/result', {
turn: 1, step: 1,
message: createToolResultMessage({
callId: CallId('orphan'),
callId: ToolCallId('orphan'),
content: [],
isError: false,
}),
@@ -387,7 +387,7 @@ describe('tool-pairing corrupt surfaces', () => {
session.append('tool/result', {
turn: 1, step: 1,
message: createToolResultMessage({
callId: CallId('orphan'),
callId: ToolCallId('orphan'),
content: [],
isError: false,
}),
@@ -5,7 +5,7 @@ import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import * as workspaceContext from '@deepseek-ai/dsh-agent-instructions'
import LlmRuntime, { createUserMessage, CallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, ToolCallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionId, SESSION_FORMAT_VERSION, type SessionEvent, type UserMessage } from '@deepseek-ai/dsh-session'
import AgentRegistry, { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
@@ -1657,7 +1657,7 @@ describe('workspace context request injection', () => {
await write(join(root, 'AGENTS.md'), 'updated repo rule')
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-remount'),
callId: ToolCallId('read-after-remount'),
name: 'read',
arguments: { file_path: 'file.txt' },
agent,
@@ -1854,7 +1854,7 @@ describe('workspace context request injection', () => {
await write(join(root, 'AGENTS.md'), 'new root rule with more detail')
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-baseline-change'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
callId: ToolCallId('read-after-baseline-change'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -1883,7 +1883,7 @@ describe('workspace context request injection', () => {
await rm(join(root, 'AGENTS.md'))
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-baseline-remove'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
callId: ToolCallId('read-after-baseline-remove'), name: 'read', arguments: { file_path: 'file.txt' }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -2499,9 +2499,9 @@ describe('dynamic nested workspace context injection', () => {
const adapter = new MockAdapter([
[
{ type: 'block-start', index: 0, blockType: 'tool-call' },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('read-before-abort'), name: 'read', arguments: '{"file_path":"pkg/deep/file.txt"}' } },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('read-before-abort'), name: 'read', arguments: '{"file_path":"pkg/deep/file.txt"}' } },
{ type: 'block-start', index: 1, blockType: 'tool-call' },
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('abort-after-read'), name: 'abort_step', arguments: '{}' } },
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('abort-after-read'), name: 'abort_step', arguments: '{}' } },
{ type: 'finish', reason: { kind: 'tool-calls' } },
] satisfies StreamChunk[],
toolCallResponse('read-after-abort', 'read', { file_path: join('pkg', 'deep', 'file.txt') }),
@@ -2595,7 +2595,7 @@ describe('dynamic nested workspace context injection', () => {
const reason = new Error('cancel dynamic reconciliation')
controller.abort(reason)
const exec = stubToolExecution({
callId: CallId('cancelled-dynamic-read'),
callId: ToolCallId('cancelled-dynamic-read'),
name: 'read',
arguments: { file_path: join('pkg', 'file.txt') },
agent: stubAgent(root),
@@ -2632,7 +2632,7 @@ describe('dynamic nested workspace context injection', () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-nested'),
callId: ToolCallId('read-nested'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -2678,7 +2678,7 @@ describe('dynamic nested workspace context injection', () => {
ctx.emit('tools/result', stubToolExecution({
signal: controller.signal,
callId: CallId('read-before-signal-end'),
callId: ToolCallId('read-before-signal-end'),
name: 'read',
arguments: { file_path: join('pkg', 'file.txt') },
agent,
@@ -2711,7 +2711,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-configured-nested-candidate'),
callId: ToolCallId('read-configured-nested-candidate'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -2744,7 +2744,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-nested-overlay'),
callId: ToolCallId('read-nested-overlay'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -2787,7 +2787,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-nested-overlay-disabled'),
callId: ToolCallId('read-nested-overlay-disabled'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -2815,7 +2815,7 @@ describe('dynamic nested workspace context injection', () => {
const first = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-nested-1'),
callId: ToolCallId('read-nested-1'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -2823,7 +2823,7 @@ describe('dynamic nested workspace context injection', () => {
await appendAdditionalContexts(ctx, agent)
const second = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-nested-2'),
callId: ToolCallId('read-nested-2'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -2858,12 +2858,12 @@ describe('dynamic nested workspace context injection', () => {
const first = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
const second = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-with-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-with-version-fast-path'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(first.additionalContexts).toBeUndefined()
@@ -2895,18 +2895,18 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
fs.entries.set(instructionPath, { type: 'file', content: 'same package rule', version: FsVersion('revision-2') })
const afterVersionChange = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await syncWorkspaceContext(ctx, agent)
const afterRefresh = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-version-cache-refresh'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-version-cache-refresh'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(afterVersionChange.additionalContexts).toBeUndefined()
@@ -2941,11 +2941,11 @@ describe('dynamic nested workspace context injection', () => {
const secondAgent = stubAgent(root)
const first = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-from-first-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: firstAgent,
callId: ToolCallId('read-from-first-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: firstAgent,
})
const second = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-from-second-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: secondAgent,
callId: ToolCallId('read-from-second-session'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: secondAgent,
})
expect(first.additionalContexts).toBeUndefined()
@@ -2973,13 +2973,13 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
await write(join(root, 'pkg/AGENTS.md'), 'new package rule with more detail')
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -3016,7 +3016,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-both-siblings'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-both-siblings'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content)
expect(firstText).toContain('native package rule')
@@ -3025,7 +3025,7 @@ describe('dynamic nested workspace context injection', () => {
await rm(join(root, 'pkg/AGENTS.md'))
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-one-sibling-removed'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-one-sibling-removed'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
// Removing one candidate only removes its own scope; the sibling scope is untouched.
@@ -3054,7 +3054,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-nested-dup-siblings'), name: 'read', arguments: { file_path: join('pkg', 'deep', 'file.txt') }, agent,
callId: ToolCallId('read-nested-dup-siblings'), name: 'read', arguments: { file_path: join('pkg', 'deep', 'file.txt') }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -3276,7 +3276,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content)
expect(firstText).toContain('canonical nested rule')
@@ -3285,7 +3285,7 @@ describe('dynamic nested workspace context injection', () => {
await write(join(root, 'pkg/CLAUDE.md'), 'canonical nested rule')
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
const convergence = await syncedWorkspaceContext(ctx, agent)
@@ -3313,14 +3313,14 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
// Only the earlier candidate changes; the sibling stays byte-identical but now duplicates it.
await write(join(root, 'pkg/AGENTS.md'), 'secondary nested rule')
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -3351,13 +3351,13 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
await rm(join(root, 'pkg/AGENTS.md'))
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -3391,7 +3391,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content)
await appendAdditionalContexts(ctx, agent)
@@ -3405,7 +3405,7 @@ describe('dynamic nested workspace context injection', () => {
await symlink(join(root, 'pkg/elsewhere'), join(root, 'pkg/AGENTS.md'))
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -3431,20 +3431,20 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
await rm(join(root, 'pkg/AGENTS.md'))
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-to-create-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-to-create-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
await write(join(root, 'pkg/AGENTS.md'), 'restored package rule')
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-after-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({
@@ -3476,13 +3476,13 @@ describe('dynamic nested workspace context injection', () => {
const first = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-before-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await appendAdditionalContexts(ctx, agent)
fs.throwOnStat.add(join(root, 'pkg/AGENTS.md'))
const duringFailure = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-during-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-during-provider-failure'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
expect(first.additionalContexts).toBeUndefined()
@@ -3506,7 +3506,7 @@ describe('dynamic nested workspace context injection', () => {
const agent = stubAgent(root)
const first = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-resume'),
callId: ToolCallId('read-before-resume'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3516,7 +3516,7 @@ describe('dynamic nested workspace context injection', () => {
const afterResume = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-resume'),
callId: ToolCallId('read-after-resume'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent: resumed,
@@ -3542,7 +3542,7 @@ describe('dynamic nested workspace context injection', () => {
const original = stubAgent(root)
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-offline-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: original,
callId: ToolCallId('read-before-offline-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent: original,
})
await appendAdditionalContexts(ctx, original)
await write(join(root, 'pkg/AGENTS.md'), 'new nested rule after resume')
@@ -3573,7 +3573,7 @@ describe('dynamic nested workspace context injection', () => {
const agent = stubAgent(root)
const first = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-before-compact'),
callId: ToolCallId('read-before-compact'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3581,7 +3581,7 @@ describe('dynamic nested workspace context injection', () => {
const contextSeq = (await appendAdditionalContexts(ctx, agent))!
const visibleBeforeCompact = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-while-visible'),
callId: ToolCallId('read-while-visible'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3597,7 +3597,7 @@ describe('dynamic nested workspace context injection', () => {
const afterCompact = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-compact'),
callId: ToolCallId('read-after-compact'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3629,7 +3629,7 @@ describe('dynamic nested workspace context injection', () => {
const whileVisible = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-visible-baseline'),
callId: ToolCallId('read-visible-baseline'),
name: 'read',
arguments: { file_path: 'file.txt' },
agent,
@@ -3644,7 +3644,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-compacted-baseline'),
callId: ToolCallId('read-compacted-baseline'),
name: 'read',
arguments: { file_path: 'file.txt' },
agent,
@@ -3653,7 +3653,7 @@ describe('dynamic nested workspace context injection', () => {
await appendAdditionalContexts(ctx, agent)
const afterRearm = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-rearmed-baseline'),
callId: ToolCallId('read-rearmed-baseline'),
name: 'read',
arguments: { file_path: 'file.txt' },
agent,
@@ -3685,7 +3685,7 @@ describe('dynamic nested workspace context injection', () => {
const agent = stubAgent(root)
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-package'),
callId: ToolCallId('read-package'),
name: 'read',
arguments: { file_path: join('pkg', 'file.txt') },
agent,
@@ -3695,7 +3695,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-subtree'),
callId: ToolCallId('read-subtree'),
name: 'read',
arguments: { file_path: join('pkg', 'sub', 'file.txt') },
agent,
@@ -3723,7 +3723,7 @@ describe('dynamic nested workspace context injection', () => {
const agent = stubAgent(root)
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-subtree-omitting-parent'),
callId: ToolCallId('read-subtree-omitting-parent'),
name: 'read',
arguments: { file_path: join('pkg', 'sub', 'file.txt') },
agent,
@@ -3733,7 +3733,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-parent-after-omit'),
callId: ToolCallId('read-parent-after-omit'),
name: 'read',
arguments: { file_path: join('pkg', 'other.txt') },
agent,
@@ -3785,7 +3785,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-spoofed-state'),
callId: ToolCallId('read-after-spoofed-state'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3812,14 +3812,14 @@ describe('dynamic nested workspace context injection', () => {
const rootResult = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-root-file'),
callId: ToolCallId('read-root-file'),
name: 'read',
arguments: { file_path: 'root.txt' },
agent,
})
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-absolute-nested-file'),
callId: ToolCallId('read-absolute-nested-file'),
name: 'read',
arguments: { file_path: join(root, 'pkg/deep/file.txt') },
agent,
@@ -3855,7 +3855,7 @@ describe('dynamic nested workspace context injection', () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-with-unreadable-nested-instruction'),
callId: ToolCallId('read-with-unreadable-nested-instruction'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3900,7 +3900,7 @@ describe('dynamic nested workspace context injection', () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-with-downstream'),
callId: ToolCallId('read-with-downstream'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3953,7 +3953,7 @@ describe('dynamic nested workspace context injection', () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-blocked-downstream'),
callId: ToolCallId('read-blocked-downstream'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -3996,7 +3996,7 @@ describe('dynamic nested workspace context injection', () => {
const blocked = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('outer-block-first'),
callId: ToolCallId('outer-block-first'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -4007,7 +4007,7 @@ describe('dynamic nested workspace context injection', () => {
shouldBlock = false
const accepted = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('outer-block-retry'),
callId: ToolCallId('outer-block-retry'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -4043,7 +4043,7 @@ describe('dynamic nested workspace context injection', () => {
async execute(_args, exec) {
const nested = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId(`${exec.callId}:nested`),
callId: ToolCallId(`${exec.callId}:nested`),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
...exec.agent === undefined ? {} : { agent: exec.agent },
@@ -4065,7 +4065,7 @@ describe('dynamic nested workspace context injection', () => {
const blocked = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('composite-first'), name: 'composite-read', arguments: {}, agent,
callId: ToolCallId('composite-first'), name: 'composite-read', arguments: {}, agent,
})
expect(blocked.isError).toBe(true)
@@ -4099,7 +4099,7 @@ describe('dynamic nested workspace context injection', () => {
token: Symbol('nested-read') as ToolExecutionToken,
parent: outerToken,
signal: testToolSignal,
callId: CallId('nested-read'),
callId: ToolCallId('nested-read'),
name: 'read',
arguments: { file_path: join('pkg', 'file.txt') },
agent,
@@ -4108,7 +4108,7 @@ describe('dynamic nested workspace context injection', () => {
token: Symbol('nested-non-file') as ToolExecutionToken,
parent: outerToken,
signal: testToolSignal,
callId: CallId('nested-non-file'),
callId: ToolCallId('nested-non-file'),
name: 'search',
arguments: {},
agent,
@@ -4117,7 +4117,7 @@ describe('dynamic nested workspace context injection', () => {
token: Symbol('second-nested-read') as ToolExecutionToken,
parent: outerToken,
signal: testToolSignal,
callId: CallId('second-nested-read'),
callId: ToolCallId('second-nested-read'),
name: 'read',
arguments: { file_path: join('pkg', 'second.txt') },
agent,
@@ -4125,7 +4125,7 @@ describe('dynamic nested workspace context injection', () => {
ctx.emit('tools/result', stubToolExecution({
token: outerToken,
signal: testToolSignal,
callId: CallId('outer-code-run'),
callId: ToolCallId('outer-code-run'),
name: 'run_code',
arguments: {},
agent,
@@ -4163,7 +4163,7 @@ describe('dynamic nested workspace context injection', () => {
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('read-after-closed-step'),
callId: ToolCallId('read-after-closed-step'),
name: 'read',
arguments: { file_path: join('pkg', 'file.txt') },
agent,
@@ -4185,38 +4185,38 @@ describe('dynamic nested workspace context injection', () => {
await ctx.plugin(workspaceContext, { maxBytes: 65536 })
const fs = ctx.fs as RecordingFileSystem
const agent = stubAgent('/')
const plainResult = { callId: CallId('plain'), content: [], isError: false as const, value: null }
const plainResult = { callId: ToolCallId('plain'), content: [], isError: false as const, value: null }
const aborted = new AbortController()
aborted.abort(new Error('cancelled'))
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal, callId: CallId('agentless'), name: 'read', arguments: { file_path: 'file.txt' },
signal: testToolSignal, callId: ToolCallId('agentless'), name: 'read', arguments: { file_path: 'file.txt' },
}), plainResult)
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal, callId: CallId('failed'), name: 'read', arguments: { file_path: 'failed/file.txt' }, agent,
signal: testToolSignal, callId: ToolCallId('failed'), name: 'read', arguments: { file_path: 'failed/file.txt' }, agent,
}), { content: [], isError: true, error: { message: 'failed' } })
ctx.emit('tools/result', stubToolExecution({
signal: aborted.signal, callId: CallId('aborted'), name: 'read', arguments: { file_path: 'aborted/file.txt' }, agent,
signal: aborted.signal, callId: ToolCallId('aborted'), name: 'read', arguments: { file_path: 'aborted/file.txt' }, agent,
}), plainResult)
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('null-arguments'), name: 'read', arguments: null, agent,
callId: ToolCallId('null-arguments'), name: 'read', arguments: null, agent,
}), plainResult)
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('missing-path'), name: 'read', arguments: {}, agent,
callId: ToolCallId('missing-path'), name: 'read', arguments: {}, agent,
}), plainResult)
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('non-string-path'), name: 'read', arguments: { file_path: 1 }, agent,
callId: ToolCallId('non-string-path'), name: 'read', arguments: { file_path: 1 }, agent,
}), plainResult)
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('blank-path'), name: 'read', arguments: { file_path: ' ' }, agent,
callId: ToolCallId('blank-path'), name: 'read', arguments: { file_path: ' ' }, agent,
}), plainResult)
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('non-fs'), name: 'composite', arguments: {}, agent,
callId: ToolCallId('non-fs'), name: 'composite', arguments: {}, agent,
}), plainResult)
await Promise.resolve()
@@ -4243,7 +4243,7 @@ describe('dynamic nested workspace context injection', () => {
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('projection-failure'),
callId: ToolCallId('projection-failure'),
name: 'read',
arguments: { file_path: 'file.txt' },
agent,
@@ -4269,7 +4269,7 @@ describe('dynamic nested workspace context injection', () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-with-disabled-budget'),
callId: ToolCallId('read-with-disabled-budget'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent: stubAgent(root),
@@ -4302,12 +4302,12 @@ describe('dynamic nested workspace context injection', () => {
const first = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-tiny-budget-1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-tiny-budget-1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await syncWorkspaceContext(ctx, agent)
const second = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-tiny-budget-2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('read-tiny-budget-2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await syncWorkspaceContext(ctx, agent)
@@ -4335,7 +4335,7 @@ describe('dynamic nested workspace context injection', () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-missing'),
callId: ToolCallId('read-missing'),
name: 'read',
arguments: { file_path: join('pkg', 'missing.txt') },
agent: stubAgent(root),
@@ -4363,7 +4363,7 @@ describe('dynamic nested workspace context injection', () => {
const result = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('read-after-dispose'),
callId: ToolCallId('read-after-dispose'),
name: 'read',
arguments: { file_path: join('pkg', 'deep', 'file.txt') },
agent,
@@ -4423,7 +4423,7 @@ describe('workspace context inbox synchronization', () => {
const agent = stubAgent(root)
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('tiny-budget-touch'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('tiny-budget-touch'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
}), acceptedResult)
await syncWorkspaceContext(ctx, agent)
@@ -4451,7 +4451,7 @@ describe('workspace context inbox synchronization', () => {
const agent = stubAgent(root)
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('pending-v1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('pending-v1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await syncWorkspaceContext(ctx, agent)
expect(blocksText(agent.inbox.nextStep[0]?.content)).toContain('pending version one')
@@ -4464,7 +4464,7 @@ describe('workspace context inbox synchronization', () => {
await write(join(root, 'pkg/AGENTS.md'), 'pending version two with more detail')
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('pending-v2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('pending-v2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await syncWorkspaceContext(ctx, agent)
expect(agent.inbox.nextStep).toHaveLength(1)
@@ -4475,7 +4475,7 @@ describe('workspace context inbox synchronization', () => {
await rm(join(root, 'pkg/AGENTS.md'))
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('pending-delete'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
callId: ToolCallId('pending-delete'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent,
})
await syncWorkspaceContext(ctx, agent)
expect(agent.inbox.nextStep).toEqual([])
@@ -4499,7 +4499,7 @@ describe('workspace context inbox synchronization', () => {
const agent = stubAgent(root)
const first = stubToolExecution({
signal: testToolSignal,
callId: CallId('projected-before-abort'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
callId: ToolCallId('projected-before-abort'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
})
ctx.emit('tools/result', first, acceptedResult)
const controller = new AbortController()
@@ -4512,7 +4512,7 @@ describe('workspace context inbox synchronization', () => {
ctx.emit('tools/result', stubToolExecution({
signal: testToolSignal,
callId: CallId('projected-after-abort'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
callId: ToolCallId('projected-after-abort'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
}), acceptedResult)
await syncWorkspaceContext(ctx, agent)
const text = blocksText(agent.inbox.nextStep[0]?.content)
@@ -4539,11 +4539,11 @@ describe('workspace context inbox synchronization', () => {
const agent = stubAgent(root)
const first = stubToolExecution({
signal: testToolSignal,
callId: CallId('concurrent-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
callId: ToolCallId('concurrent-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent,
})
const second = stubToolExecution({
signal: testToolSignal,
callId: CallId('concurrent-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
callId: ToolCallId('concurrent-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent,
})
ctx.emit('tools/result', first, acceptedResult)
@@ -4577,14 +4577,14 @@ describe('workspace context inbox synchronization', () => {
const original = stubAgent(root)
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('recover-pending-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent: original,
callId: ToolCallId('recover-pending-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent: original,
})
await syncWorkspaceContext(ctx, original)
const resumed = stubAgent(root, [...original.session.events])
await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('recover-pending-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent: resumed,
callId: ToolCallId('recover-pending-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent: resumed,
})
await syncWorkspaceContext(ctx, resumed)
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
import { CompactionId, compactCheckpointSource } from '@deepseek-ai/dsh-compaction'
import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import { createUserMessage, ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionId } from '@deepseek-ai/dsh-session'
import SessionQueryEngine from '@deepseek-ai/dsh-session-query'
import SessionReferenceResolver, {
@@ -122,7 +122,7 @@ function appendConversation(session: Session): void {
{
turn: 2, step: 1,
message: createToolResultMessage({
callId: CallId('call'),
callId: ToolCallId('call'),
content: [{ type: 'text', text: 'tool output' }],
isError: false,
}),
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import { createUserMessage, CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import { createUserMessage, ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
import { Session, SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
import AgentRegistry, { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
@@ -113,7 +113,7 @@ function toolCallResponse(): StreamChunk[] {
{
type: 'block-end',
index: 0,
block: { type: 'tool-call', id: CallId('tick-1'), name: 'tick', arguments: '{}' },
block: { type: 'tool-call', id: ToolCallId('tick-1'), name: 'tick', arguments: '{}' },
},
{ type: 'finish', reason: { kind: 'tool-calls' } },
]
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
import { Context, type Fiber } from '@deepseek-ai/cordis'
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import LlmRuntime, { createUserMessage, CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
@@ -310,7 +310,7 @@ describe('AgentLoop initiator scope', () => {
const direct = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('direct'),
callId: ToolCallId('direct'),
name: 'agentless-probe',
arguments: {},
})
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import LlmRuntime, { createUserMessage, CallId, LlmError, MessageSource, ProviderRequestId, StreamChunk } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, ToolCallId, LlmError, MessageSource, ProviderRequestId, StreamChunk } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionEvent, SessionId, TurnEndReason, type UserMessage } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRuntime, { defineContentToolFixture, type PostToolDecision } from '@deepseek-ai/dsh-tools'
@@ -135,9 +135,9 @@ describe('abort during tool execution ends the turn', () => {
it('records post-tool context when a later call aborts the batch', async () => {
const adapter = new MockAdapter([[
{ type: 'block-start', index: 0, blockType: 'tool-call' },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('c1'), name: 'first', arguments: '{}' } },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'first', arguments: '{}' } },
{ type: 'block-start', index: 1, blockType: 'tool-call' },
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c2'), name: 'aborter', arguments: '{}' } },
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('c2'), name: 'aborter', arguments: '{}' } },
{ type: 'finish', reason: { kind: 'tool-calls' } },
] satisfies StreamChunk[]])
const ctx = await harness(adapter)
@@ -160,7 +160,7 @@ describe('abort during tool execution ends the turn', () => {
},
}))
ctx.on('tools/post-execute', async (exec, _result, next): Promise<PostToolDecision> => {
if (exec.callId !== CallId('c1')) return next()
if (exec.callId !== ToolCallId('c1')) return next()
return {
kind: 'accept',
additionalContexts: [createUserMessage({
@@ -259,9 +259,9 @@ describe('abort during tool execution ends the turn', () => {
const adapter = new MockAdapter([
[
{ type: 'block-start', index: 0, blockType: 'tool-call' },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('c1'), name: 'aborter', arguments: '{}' } },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'aborter', arguments: '{}' } },
{ type: 'block-start', index: 1, blockType: 'tool-call' },
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c2'), name: 'second', arguments: '{}' } },
{ type: 'block-end', index: 1, block: { type: 'tool-call', id: ToolCallId('c2'), name: 'second', arguments: '{}' } },
{ type: 'finish', reason: { kind: 'tool-calls' } },
] satisfies StreamChunk[],
textResponse('later turn'),
@@ -553,7 +553,7 @@ describe('discriminated SessionEvent narrows without casts', () => {
it('narrows event.data from event.type', () => {
const session = Session.create(SessionId('s'))
const appended: SessionEvent = session.append('tool/call', {
turn: 1, step: 1, callId: CallId('c1'), name: 'echo', arguments: '{}',
turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}',
})
// compile-time: this switch narrows; runtime: values flow through
switch (appended.type) {
@@ -1069,7 +1069,7 @@ describe('tool result call identity', () => {
// The loop must still record the tool/result under the model's authoritative
// call.id, which is the immutable identity carried by the execution input.
ctx.on('tools/post-execute', (exec, _result) => {
expect(exec.callId).toBe(CallId('c1')) // the loop passed the real id in
expect(exec.callId).toBe(ToolCallId('c1')) // the loop passed the real id in
return Promise.resolve({ kind: 'accept', content: [{ type: 'text', text: 'ok' }] })
}, { prepend: true })
@@ -1081,7 +1081,7 @@ describe('tool result call identity', () => {
const resultEvent = [...agent.session.events].find(e => e.type === 'tool/result')
expect(resultEvent?.type).toBe('tool/result')
if (resultEvent?.type === 'tool/result') {
expect(resultEvent.data.message.source.callId).toBe(CallId('c1'))
expect(resultEvent.data.message.source.callId).toBe(ToolCallId('c1'))
}
// And deriveMessages pairs the tool-result with the assistant tool-call:
@@ -1092,7 +1092,7 @@ describe('tool result call identity', () => {
.find(b => b.type === 'tool-result')
expect(toolResultBlock?.type).toBe('tool-result')
if (toolResultBlock?.type === 'tool-result') {
expect(toolResultBlock.toolCallId).toBe(CallId('c1'))
expect(toolResultBlock.toolCallId).toBe(ToolCallId('c1'))
}
})
})
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import LlmRuntime, { createUserMessage, CallId, LlmError, StreamChunk, errorChain } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, ToolCallId, LlmError, StreamChunk, errorChain } from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionId, TurnEndReason } from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
@@ -47,7 +47,7 @@ describe('tool JSON parse', () => {
// model emits tool-call with malformed arguments (not valid JSON)
[
{ type: 'block-start' as const, index: 0, blockType: 'tool-call' as const },
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: CallId('c1'), name: 'echo', arguments: 'not json' } },
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: ToolCallId('c1'), name: 'echo', arguments: 'not json' } },
{ type: 'finish' as const, reason: { kind: 'tool-calls' as const } },
] satisfies StreamChunk[],
textResponse('done'),
@@ -80,7 +80,7 @@ describe('tool JSON parse', () => {
const adapter = new MockAdapter([
[
{ type: 'block-start' as const, index: 0, blockType: 'tool-call' as const },
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: CallId('c1'), name: 'noarg', arguments: '' } },
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: ToolCallId('c1'), name: 'noarg', arguments: '' } },
{ type: 'finish' as const, reason: { kind: 'tool-calls' as const } },
] satisfies StreamChunk[],
textResponse('done'),
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import LlmRuntime, { createUserMessage, CallId } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
import SessionStore, {
SessionId,
type SessionEvent,
@@ -613,9 +613,9 @@ describe('tool additionalContexts buffering across a step', () => {
// One assistant step with TWO tool calls; the second model response stops.
const twoCalls = [
{ type: 'block-start' as const, index: 0, blockType: 'tool-call' as const },
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: CallId('c1'), name: 'echo', arguments: '{"text":"a"}' } },
{ type: 'block-end' as const, index: 0, block: { type: 'tool-call' as const, id: ToolCallId('c1'), name: 'echo', arguments: '{"text":"a"}' } },
{ type: 'block-start' as const, index: 1, blockType: 'tool-call' as const },
{ type: 'block-end' as const, index: 1, block: { type: 'tool-call' as const, id: CallId('c2'), name: 'echo', arguments: '{"text":"b"}' } },
{ type: 'block-end' as const, index: 1, block: { type: 'tool-call' as const, id: ToolCallId('c2'), name: 'echo', arguments: '{"text":"b"}' } },
{ type: 'usage' as const, usage: { inputTokens: 5, outputTokens: 5 } },
{ type: 'finish' as const, reason: { kind: 'tool-calls' as const } },
]
+4 -4
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import LlmRuntime, { createUserMessage, CallId, LlmError, StreamChunk } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, ToolCallId, LlmError, 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'
@@ -1052,7 +1052,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"}' },
@@ -1107,7 +1107,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"}' },
@@ -1185,7 +1185,7 @@ describe('agent loop', () => {
})
it('keeps safe max-tokens assistant content while dropping truncated tool calls', async () => {
const callId = CallId('c1')
const callId = ToolCallId('c1')
const adapter = new MockAdapter([[
{ type: 'block-start', index: 0, blockType: 'text' },
{ type: 'text-delta', index: 0, text: 'partial text' },
@@ -1,5 +1,5 @@
import type { GenerateOptions, LlmModelReasoningInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
/** Helpers to write scripted responses tersely. */
export function textResponse(text: string): StreamChunk[] {
@@ -28,7 +28,7 @@ export function maxTokensResponse(text: string): StreamChunk[] {
}
export function toolCallResponse(rawCallId: string, name: string, args: object, text?: string): StreamChunk[] {
const callId = CallId(rawCallId)
const callId = ToolCallId(rawCallId)
const argumentsJson = JSON.stringify(args)
const chunks: StreamChunk[] = []
let index = 0
@@ -5,7 +5,7 @@
import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { createUserMessage, CallId, StreamChunk } from '@deepseek-ai/dsh-llm'
import { createUserMessage, ToolCallId, StreamChunk } from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import LlmRuntime from '@deepseek-ai/dsh-llm'
@@ -49,7 +49,7 @@ function multiCall(calls: { id: string; name: string; args: object }[]): StreamC
calls.forEach((call, index) => {
chunks.push(
{ type: 'block-start', index, blockType: 'tool-call' },
{ type: 'block-end', index, block: { type: 'tool-call', id: CallId(call.id), name: call.name, arguments: JSON.stringify(call.args) } },
{ type: 'block-end', index, block: { type: 'tool-call', id: ToolCallId(call.id), name: call.name, arguments: JSON.stringify(call.args) } },
)
})
chunks.push(
@@ -196,7 +196,7 @@ describe('tool-call scheduler: grouping and barriers', () => {
const replacement = gatedExclusiveTool('x')
const disposeInitial = ctx.tools.register(initial.tool)
ctx.on('tools/result', (exec) => {
if (exec.callId !== CallId('c1')) return
if (exec.callId !== ToolCallId('c1')) return
disposeInitial()
ctx.tools.register(replacement.tool)
})
@@ -206,7 +206,7 @@ describe('tool-call scheduler: grouping and barriers', () => {
await until(() => initial.started.length === 2)
initial.release('1')
await until(() => events(agent).some(event =>
event.type === 'tool/result' && event.data.message.source.callId === CallId('c1')))
event.type === 'tool/result' && event.data.message.source.callId === ToolCallId('c1')))
await new Promise(r => setTimeout(r, 5))
expect(replacement.started).toEqual([])
initial.release('2')
@@ -238,7 +238,7 @@ describe('tool-call scheduler: model-order results despite out-of-order settleme
await waitForIdle(ctx, agent)
const results = events(agent).filter(e => e.type === 'tool/result')
expect(results.map(e => e.data.message.source.callId)).toEqual([CallId('c1'), CallId('c2')])
expect(results.map(e => e.data.message.source.callId)).toEqual([ToolCallId('c1'), ToolCallId('c2')])
})
it('derived history pairs calls in model order regardless of tool/call log interleaving', async () => {
@@ -257,7 +257,7 @@ describe('tool-call scheduler: model-order results despite out-of-order settleme
const messages = agent.session.deriveMessages()
const toolResults = messages.flatMap(m => m.content.filter(b => b.type === 'tool-result'))
expect(toolResults.map(b => b.toolCallId)).toEqual([CallId('c1'), CallId('c2')])
expect(toolResults.map(b => b.toolCallId)).toEqual([ToolCallId('c1'), ToolCallId('c2')])
})
})
@@ -316,7 +316,7 @@ describe('tool-call scheduler: rolling pool honors maxParallelToolCalls', () =>
gated.release('4')
await waitForIdle(ctx, agent)
expect(events(agent).filter(e => e.type === 'tool/result').map(e => e.data.message.source.callId))
.toEqual([CallId('c1'), CallId('c2'), CallId('c3'), CallId('c4')])
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3'), ToolCallId('c4')])
})
it('maxParallelToolCalls: 1 is fully serial (no second start before the first settles)', async () => {
@@ -386,8 +386,8 @@ describe('tool-call scheduler: ordered middleware and additional contexts', () =
gated.release('3'); gated.release('2'); gated.release('1')
await waitForIdle(ctx, agent)
expect(pre).toEqual([CallId('c1'), CallId('c2'), CallId('c3')].map(String))
expect(post).toEqual([CallId('c1'), CallId('c2'), CallId('c3')].map(String))
expect(pre).toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')].map(String))
expect(post).toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')].map(String))
})
it('injects additional contexts in model call order, not settlement order', async () => {
@@ -432,8 +432,8 @@ describe('tool-call scheduler: ordered middleware and additional contexts', () =
ctx.tools.register(gated.tool)
const post: string[] = []
ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {
if (exec.callId === CallId('c2')) return { kind: 'deny', reason: 'blocked by policy' }
if (exec.callId === CallId('c3')) throw new Error('pre exploded')
if (exec.callId === ToolCallId('c2')) return { kind: 'deny', reason: 'blocked by policy' }
if (exec.callId === ToolCallId('c3')) throw new Error('pre exploded')
return next()
})
ctx.on('tools/post-execute', async (exec, _result, next): Promise<PostToolDecision> => {
@@ -450,7 +450,7 @@ describe('tool-call scheduler: ordered middleware and additional contexts', () =
expect(gated.started).toEqual(['1'])
expect(post).toEqual(['c1', 'c2'])
const results = events(agent).filter(e => e.type === 'tool/result')
expect(results.map(e => e.data.message.source.callId)).toEqual([CallId('c1'), CallId('c2'), CallId('c3')])
expect(results.map(e => e.data.message.source.callId)).toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')])
expect((results[1]!.data.message.content[0].content[0] as { text: string }).text).toContain('blocked by policy')
expect((results[2]!.data.message.content[0].content[0] as { text: string }).text).toContain('pre exploded')
})
@@ -477,14 +477,14 @@ describe('tool-call scheduler: abort handling', () => {
expect(gated.started).toEqual([])
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
.toEqual([CallId('c1'), CallId('c2')])
.toEqual([ToolCallId('c1'), ToolCallId('c2')])
expect(events(agent).filter(e => e.type === 'tool/result').map(e => ({
callId: e.data.message.source.callId,
isError: e.data.message.content[0].isError,
error: e.data.error,
}))).toEqual([
{ callId: CallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
{ callId: CallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
{ callId: ToolCallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
{ callId: ToolCallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
])
})
@@ -498,7 +498,7 @@ describe('tool-call scheduler: abort handling', () => {
ctx.tools.register(gated.tool)
const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {
if (exec.callId === CallId('c1')) {
if (exec.callId === ToolCallId('c1')) {
agent.cancel({ kind: 'user' })
}
return next()
@@ -509,14 +509,14 @@ describe('tool-call scheduler: abort handling', () => {
expect(gated.started).toEqual([])
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
.toEqual([CallId('c1'), CallId('c2')])
.toEqual([ToolCallId('c1'), ToolCallId('c2')])
expect(events(agent).filter(e => e.type === 'tool/result').map(e => ({
callId: e.data.message.source.callId,
isError: e.data.message.content[0].isError,
error: e.data.error,
}))).toEqual([
{ callId: CallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
{ callId: CallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
{ callId: ToolCallId('c1'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
{ callId: ToolCallId('c2'), isError: true, error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } },
])
})
@@ -545,9 +545,9 @@ describe('tool-call scheduler: abort handling', () => {
expect(gated.started).toEqual(['1', '2'])
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
.toEqual([CallId('c1'), CallId('c2'), CallId('c3'), CallId('c4')])
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3'), ToolCallId('c4')])
expect(events(agent).filter(e => e.type === 'tool/result').map(e => e.data.message.source.callId))
.toEqual([CallId('c1'), CallId('c2'), CallId('c3'), CallId('c4')])
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3'), ToolCallId('c4')])
expect(events(agent).filter(e => e.type === 'tool/result').slice(-2).map(e => ({
callId: e.data.message.source.callId,
isError: e.data.message.content[0].isError,
@@ -555,12 +555,12 @@ describe('tool-call scheduler: abort handling', () => {
})))
.toEqual([
{
callId: CallId('c3'),
callId: ToolCallId('c3'),
isError: true,
error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH },
},
{
callId: CallId('c4'),
callId: ToolCallId('c4'),
isError: true,
error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH },
},
@@ -618,11 +618,11 @@ describe('tool-call scheduler: abort handling', () => {
expect(exclusive).toEqual([])
expect(events(agent).filter(e => e.type === 'tool/call').map(e => e.data.callId))
.toEqual([CallId('c1'), CallId('c2'), CallId('c3')])
.toEqual([ToolCallId('c1'), ToolCallId('c2'), ToolCallId('c3')])
expect(events(agent).filter(e => e.type === 'tool/result').at(-1)?.data)
.toMatchObject({
message: {
source: { kind: 'tool', callId: CallId('c3') },
source: { kind: 'tool', callId: ToolCallId('c3') },
content: [{ isError: true }],
},
error: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH },
@@ -651,7 +651,7 @@ describe('tool-call scheduler: failure quiescence', () => {
let thirdPrepareEntered = false
scheduler.prepare = async (exec) => {
const prepared = await prepare(exec)
if (exec.callId === CallId('c3')) {
if (exec.callId === ToolCallId('c3')) {
thirdPrepareEntered = true
await prepareGate.promise
}
@@ -660,7 +660,7 @@ describe('tool-call scheduler: failure quiescence', () => {
const schedulerError = new Error('scheduler exploded')
const drainedError = new Error('sibling failed while draining')
let rejectFirst: ((error: Error) => void) | undefined
scheduler.dispatch = exec => exec.callId === CallId('c1')
scheduler.dispatch = exec => exec.callId === ToolCallId('c1')
? new Promise((_resolve, reject) => { rejectFirst = reject })
: dispatch(exec).then(() => { throw drainedError })
const agent = ctx.agentLoop.create(SessionId('scheduler-failure'), { provider: 'mock', model: 'mock' })
+3 -3
View File
@@ -18,7 +18,7 @@
* @module @deepseek-ai/dsh-session/chunk-rows
*/
import { CallId, assertNever } from '@deepseek-ai/dsh-llm'
import { ToolCallId, assertNever } from '@deepseek-ai/dsh-llm'
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
import type { SessionEvent } from './types.ts'
@@ -50,7 +50,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[]
@@ -167,7 +167,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),
},
+3 -3
View File
@@ -7,7 +7,7 @@
import type { Context } from '@deepseek-ai/cordis'
import { assertNever } from '@deepseek-ai/dsh-llm'
import type { CallId } from '@deepseek-ai/dsh-llm'
import type { ToolCallId } from '@deepseek-ai/dsh-llm'
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import { TOOL_NOT_STARTED } from './repair.ts'
@@ -26,7 +26,7 @@ interface SessionTrace {
openStep: number | null
nextTurn: number
nextStep: number
pendingCalls: Set<CallId>
pendingCalls: Set<ToolCallId>
}
/** One accepted event's deferred mutation of a committed session trace. */
@@ -34,7 +34,7 @@ interface SessionTraceTransition {
scalars: Pick<SessionTrace, 'lastSeq' | 'openTurn' | 'openStep' | 'nextTurn' | 'nextStep'>
pendingCalls:
| { kind: 'none' }
| { kind: 'add' | 'delete'; callId: CallId }
| { kind: 'add' | 'delete'; callId: ToolCallId }
| { kind: 'clear' }
}
+2 -2
View File
@@ -5,7 +5,7 @@
* @module @deepseek-ai/dsh-session/repair
*/
import { MessageId, freezeMessage, type CallId } from '@deepseek-ai/dsh-llm'
import { MessageId, freezeMessage, type ToolCallId } from '@deepseek-ai/dsh-llm'
import type { ToolResultMessage } from '@deepseek-ai/dsh-llm'
import type { SessionEvent } from './types.ts'
@@ -29,7 +29,7 @@ export function interruptedTurnClosers(events: readonly SessionEvent[]): Session
let openStep: number | null = null
// Reset at each turn boundary so earlier calls cannot leak into tail repair.
// Assistant blocks register calls; later `tool/call` events add their seqs to `sourceEventSeqs`.
const pendingCalls = new Map<CallId, { step: number; callSeq?: number }>()
const pendingCalls = new Map<ToolCallId, { step: number; callSeq?: number }>()
for (const event of events) {
switch (event.type) {
case 'turn/start':
+2 -2
View File
@@ -1,7 +1,7 @@
import type { Branded } from '@deepseek-ai/dsh-brand'
import type {
AssistantMessage,
CallId,
ToolCallId,
LlmCallConfig,
LlmCallConfigAdapterDefaults,
LlmFailure,
@@ -280,7 +280,7 @@ export interface SessionEventMap {
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
* call with its `tool/result`.
*/
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
/**
* A completed tool call's model-facing result, optional internal failure
* identity, and optional tool-private `meta` presentation payload. `meta` is
@@ -6,7 +6,7 @@
import { describe, expect, it } from 'vitest'
import fc from 'fast-check'
import { CallId } from '@deepseek-ai/dsh-llm'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
import { decodeStorageRecord, packChunkRuns } from '@deepseek-ai/dsh-session'
import type { ChunkRow, SessionEvent, StorageRecord } from '@deepseek-ai/dsh-session'
@@ -43,7 +43,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' }
@@ -53,7 +53,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)
@@ -91,7 +91,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')]
@@ -186,13 +186,13 @@ const deltaChunkArb: fc.Arbitrary<StreamChunk> = fc.oneof(
fc.record({
type: fc.constant<'tool-call-delta'>('tool-call-delta'),
index: fc.nat(2),
id: fc.constantFrom(CallId('c1'), CallId('c2')),
id: fc.constantFrom(ToolCallId('c1'), ToolCallId('c2')),
argumentsDelta: fc.string(),
}),
fc.record({
type: fc.constant<'tool-call-delta'>('tool-call-delta'),
index: fc.nat(2),
id: fc.constantFrom(CallId('c1'), CallId('c2')),
id: fc.constantFrom(ToolCallId('c1'), ToolCallId('c2')),
name: fc.constantFrom('write', 'read'),
argumentsDelta: fc.string(),
}),
+2 -2
View File
@@ -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', {
+14 -14
View File
@@ -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,
}),
@@ -223,7 +223,7 @@ describe('session-log invariants', () => {
turn: 1,
step: 1,
message: createToolResultMessage({
callId: CallId('ghost'),
callId: ToolCallId('ghost'),
content: [],
isError: false,
}),
@@ -238,7 +238,7 @@ describe('session-log invariants', () => {
turn: 1,
step: 1,
message: createToolResultMessage({
callId: CallId('closed'),
callId: ToolCallId('closed'),
content: [],
isError: false,
}),
@@ -253,7 +253,7 @@ describe('session-log invariants', () => {
session.append('tool/call', {
turn: 1,
step: 1,
callId: CallId('rewrite'),
callId: ToolCallId('rewrite'),
name: 'echo',
arguments: '{}',
})
@@ -261,7 +261,7 @@ describe('session-log invariants', () => {
turn: 1,
step: 1,
message: createToolResultMessage({
callId: CallId('rewrite'),
callId: ToolCallId('rewrite'),
content: [{ type: 'text', text: 'original' }],
isError: false,
}),
@@ -293,7 +293,7 @@ describe('session-log invariants', () => {
session.append('tool/call', {
turn: 1,
step: 1,
callId: CallId('rewrite'),
callId: ToolCallId('rewrite'),
name: 'echo',
arguments: '{}',
})
@@ -301,7 +301,7 @@ describe('session-log invariants', () => {
turn: 1,
step: 1,
message: createToolResultMessage({
callId: CallId('rewrite'),
callId: ToolCallId('rewrite'),
content: [{ type: 'text', text: 'original' }],
isError: false,
}),
@@ -333,7 +333,7 @@ describe('session-log invariants', () => {
turn: 1,
step: 1,
message: createToolResultMessage({
callId: CallId('crashed'),
callId: ToolCallId('crashed'),
content: [],
isError: true,
}),
@@ -347,7 +347,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()
@@ -358,14 +358,14 @@ describe('session-log invariants', () => {
const session = ctx.sessions.create()
session.append('turn/start', { turn: 1 })
session.append('step/start', { turn: 1, step: 1 })
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c1'), name: 'echo', arguments: '{}' })
session.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}' })
session.append('step/end', { turn: 1, step: 1 })
session.append('step/start', { turn: 1, step: 2 })
expect(() => session.append('tool/result', {
turn: 1,
step: 2,
message: createToolResultMessage({
callId: CallId('c1'),
callId: ToolCallId('c1'),
content: [],
isError: false,
}),
@@ -9,7 +9,7 @@
import { describe, expect, it } from 'vitest'
import fc from 'fast-check'
import { createUserMessage, CallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import { createUserMessage, ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm'
import { Session, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEventMap, SessionEventType, SurfaceIntent } from '@deepseek-ai/dsh-session'
@@ -61,7 +61,7 @@ const messageEventArb: fc.Arbitrary<Appendable> = fc.oneof(
.map((r): Appendable => ({ type: 'tool/result', data: {
turn: 1, step: 1,
message: createToolResultMessage({
callId: CallId(r.id),
callId: ToolCallId(r.id),
content: r.content,
isError: r.isError,
}),
+15 -15
View File
@@ -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.
+5 -5
View File
@@ -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,
}),
+5 -5
View File
@@ -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,
}),
+2 -2
View File
@@ -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'
@@ -469,7 +469,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,
+5 -5
View File
@@ -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'
@@ -312,12 +312,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
@@ -360,7 +360,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 (`<parent>:code:<n>`). */
readonly subCallId: CallId
readonly subCallId: ToolCallId
/** The dispatched sub-tool name. */
readonly name: string
/** Whether the sub-call settled as an error. */
@@ -378,7 +378,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
}
+4 -4
View File
@@ -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
}
+11 -11
View File
@@ -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 from '@deepseek-ai/dsh-system-prompt'
@@ -107,7 +107,7 @@ async function runCode(
): Promise<ToolExecutionResult> {
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 } : {},
@@ -1624,9 +1624,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,
@@ -1666,7 +1666,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' },
})
@@ -1688,7 +1688,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' },
})
@@ -1720,7 +1720,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,
@@ -1756,14 +1756,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,
@@ -1776,7 +1776,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,
@@ -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', () => {
@@ -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.
+41 -41
View File
@@ -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<Context> {
const execution = (overrides: Partial<ToolExecution> = {}): 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: {},
})
+14 -14
View File
@@ -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<string> {
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])
+94 -94
View File
@@ -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: <unprintable thrown value>' }],
@@ -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<PreToolDecision> =>
({ 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<PreToolDecision> => ({ 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<ApprovalOutcome>('rejected'))
ctx.on('tools/pre-execute', async (_exec, _next): Promise<PreToolDecision> => ({ 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<ApprovalOutcome>('cancelled'))
ctx.on('tools/pre-execute', async (_exec, _next): Promise<PreToolDecision> => ({ 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<PreToolDecision> => ({ 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<PreToolDecision> => ({ 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<PreToolDecision> => ({ 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<PreToolDecision> => ({ 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<PostToolDecision> =>
({ 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<PostToolDecision> =>
({ 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<ToolExecutionResult>): Promise<ToolExecutionResult> =>
({ 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)
})
@@ -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 },
})
@@ -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' },
}),
@@ -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'
@@ -68,7 +68,7 @@ function execute(
signal: AbortSignal = SIGNAL,
) {
return ctx.tools.execute({
callId: CallId(`team-call-${++callNumber}`),
callId: ToolCallId(`team-call-${++callNumber}`),
name,
arguments: args,
signal,
@@ -216,7 +216,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
},
],
ownerProps: [
'/** Stable owner currency delivered to one keyed Chat business renderer. */\nexport interface ChatNodeOwnerProps {\n /** Selected Tool call, when the shared details store names one. */\n selectedCallId?: CallId | undefined\n /** Session workspace root; Tool summaries display paths relative to it. */\n cwd?: string | undefined\n openFile: (path: string) => void\n inspectCall: (callId: CallId) => void\n forkAt: (seq: number) => void\n /** Render a historical image group through the attachment slot. */\n renderMessageImages: RenderMessageImages\n fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined\n}',
'/** Stable owner currency delivered to one keyed Chat business renderer. */\nexport interface ChatNodeOwnerProps {\n /** Selected Tool call, when the shared details store names one. */\n selectedCallId?: ToolCallId | undefined\n /** Session workspace root; Tool summaries display paths relative to it. */\n cwd?: string | undefined\n openFile: (path: string) => void\n inspectCall: (callId: ToolCallId) => void\n forkAt: (seq: number) => void\n /** Render a historical image group through the attachment slot. */\n renderMessageImages: RenderMessageImages\n fileMentions: (owner: TurnTailOwnerProps) => MarkdownFileMentions | undefined\n}',
],
ownerPropsReferences: [
'MarkdownFileMentions',
@@ -1119,7 +1119,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
},
],
ownerProps: [
'/**\n * View-slot owner share: the cross-view inspect handoff (otherwise views need\n * nothing from the render site — sessionId and the snapshot hook arrive as\n * framework-standard props; tool rows go through each view\'s own declared\n * toolview hole).\n */\nexport interface ConvViewOwnerProps {\n /** One-shot inspect request from another view (chat\'s Inspect button); null when idle. */\n inspect?: { callId: CallId } | null\n /** Acknowledge the inspect request once applied (clears the store field). */\n onInspectDone?: () => void\n}',
'/**\n * View-slot owner share: the cross-view inspect handoff (otherwise views need\n * nothing from the render site — sessionId and the snapshot hook arrive as\n * framework-standard props; tool rows go through each view\'s own declared\n * toolview hole).\n */\nexport interface ConvViewOwnerProps {\n /** One-shot inspect request from another view (chat\'s Inspect button); null when idle. */\n inspect?: { callId: ToolCallId } | null\n /** Acknowledge the inspect request once applied (clears the store field). */\n onInspectDone?: () => void\n}',
],
ownerPropsReferences: [],
standardProps: [
@@ -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<ToolExecutionResult> {
return ctx.tools.execute({
signal: new AbortController().signal,
callId: CallId(`call-${++callCounter}`),
callId: ToolCallId(`call-${++callCounter}`),
name,
arguments: args,
})
@@ -2894,7 +2894,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'ApprovalRequest',
declaration: 'export interface ApprovalRequest {\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 {\n readonly agent: Agent;\n readonly toolName: string;\n readonly callId?: ToolCallId;\n readonly reason?: string;\n readonly signal?: AbortSignal;\n}',
},
{
name: 'ApprovalService',
@@ -3038,7 +3038,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',
@@ -3874,7 +3874,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',
@@ -4066,7 +4066,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 \'todo/write\': {\n todos: TodoItem[];\n };\n \'request/header\': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n };\n \'request/context\': RequestContext;\n \'session/end-seed\': Record<string, never>;\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 \'todo/write\': {\n todos: TodoItem[];\n };\n \'request/header\': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n };\n \'request/context\': RequestContext;\n \'session/end-seed\': Record<string, never>;\n}',
},
{
name: 'SessionEventMetadataFilter',
@@ -4426,7 +4426,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',
@@ -4442,7 +4442,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',
@@ -4750,7 +4750,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',
@@ -4758,7 +4758,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',
@@ -4786,7 +4786,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',
@@ -4810,7 +4810,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',
@@ -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,
Loaded 100 of 186 files, more files were not shown because too many files have changed in this diff. Show more