diff --git a/packages/llm-pi-ai/src/adapter.ts b/packages/llm-pi-ai/src/adapter.ts index 1a05f6d65f..38b05dc007 100644 --- a/packages/llm-pi-ai/src/adapter.ts +++ b/packages/llm-pi-ai/src/adapter.ts @@ -85,6 +85,7 @@ function strictByToolName(tools: ToolSchema[] | undefined): Map { }) it.each([ + [400, 'INVALID_REQUEST'], [429, 'RATE_LIMIT'], [500, 'SERVER'], ] as const)('maps HTTP %s to stable error code %s', async (status, code) => { diff --git a/packages/session-persistence-jsonl/tests/jsonl.spec.ts b/packages/session-persistence-jsonl/tests/jsonl.spec.ts index cc463f78f7..9d22bf2dae 100644 --- a/packages/session-persistence-jsonl/tests/jsonl.spec.ts +++ b/packages/session-persistence-jsonl/tests/jsonl.spec.ts @@ -1039,6 +1039,14 @@ describe('SessionPersistenceJsonl: edge cases', () => { expect(loaded.meta.updatedAt).toBe(5) }) + it('load rejects a corrupt sidecar instead of treating it as absent', async () => { + const m = meta('bad-sidecar') + await ctx.sessionPersistence.create(m) + await ctx.sessionPersistence.append(m.id, oneTurnLog()) + await writeFile(sidecarPath(root, undefined, m.id), '{not json') + await expect(ctx.sessionPersistence.load(m.id)).rejects.toThrow() + }) + it('list returns nothing when the root directory does not exist', async () => { const ctx2 = new Context() await ctx2.plugin(SessionStore) diff --git a/packages/tools/tests/tools.spec.ts b/packages/tools/tests/tools.spec.ts index ac2fd8414c..9137631524 100644 --- a/packages/tools/tests/tools.spec.ts +++ b/packages/tools/tests/tools.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, it } from 'vitest' import { Context } from 'cordis' -import { CallId } from '@deepseek-ai/dsh-llm' +import { CallId, HarnessError } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRegistry, { defineTool, schemaSpecToJsonSchema, validateArgs, ToolArgsError, ToolNotFoundError, @@ -138,6 +138,22 @@ describe('ToolRegistry', () => { }) }) + it('preserves structured error info when a tools/execute listener throws HarnessError', async () => { + const ctx = await setup() + ctx.tools.register(echoTool) + ctx.on('tools/execute', async () => { + throw new HarnessError('denied', 'DENIED') + }) + + const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } }) + + expect(result).toMatchObject({ + callId: CallId('c1'), + isError: true, + error: { name: 'HarnessError', code: 'DENIED' }, + }) + }) + it('schemas() snapshots tool schemas instead of exposing registry objects', async () => { const ctx = await setup() ctx.tools.register(echoTool)