|
|
@@ -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)
|
|
|
})
|
|
|
|