Download packages/core/agent-loop/tests/request-error.spec.ts from SaylorTwift/deepseek-harness: direct link, hf CLI and curl.
- Browser
- Download file 5.68 kB
-
https://huggingface.co/SaylorTwift/deepseek-harness/resolve/main/packages/core/agent-loop/tests/request-error.spec.ts
- Command line
-
hf download hf://SaylorTwift/deepseek-harness/packages/core/agent-loop/tests/request-error.spec.ts
-
curl -L -o request-error.spec.ts https://huggingface.co/SaylorTwift/deepseek-harness/resolve/main/packages/core/agent-loop/tests/request-error.spec.ts
5.68 kB
| import { describe, expect, it } from 'vitest' | |
| import { Context } from '@deepseek-ai/cordis' | |
| import AgentRegistry from '@deepseek-ai/dsh-agent' | |
| import AgentLoop from '@deepseek-ai/dsh-agent-loop' | |
| import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' | |
| import LlmRuntime, { createUserMessage, LlmError } from '@deepseek-ai/dsh-llm' | |
| import type { LlmFailure, ResolvedRetryPolicy } from '@deepseek-ai/dsh-llm' | |
| import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' | |
| import SystemPrompt from '@deepseek-ai/dsh-system-prompt' | |
| import ToolRuntime from '@deepseek-ai/dsh-tools' | |
| import { MockAdapter, textResponse } from './mock-adapter.ts' | |
| async function harness(adapter: MockAdapter): Promise<Context> { | |
| const ctx = new Context() | |
| await ctx.plugin(LlmRuntime) | |
| await ctx.plugin(SessionStore) | |
| await ctx.plugin(SessionProjectionRegistry) | |
| await ctx.plugin(SystemPrompt) | |
| await ctx.plugin(ToolRuntime) | |
| await ctx.plugin(AgentRegistry) | |
| await ctx.plugin(AgentLoop, { agents: [] }) | |
| ctx.llm.registerAdapter(['mock'], adapter) | |
| return ctx | |
| } | |
| function fail(message: string, code: string): () => never { | |
| return () => { | |
| throw new LlmError(message, code) | |
| } | |
| } | |
| describe('agent/request-error', () => { | |
| it('does not offer middleware failures to request recovery', async () => { | |
| const adapter = new MockAdapter([textResponse('unused')]) | |
| const ctx = await harness(adapter) | |
| const agent = await ctx.agentLoop.create(SessionId('request-error-narrow'), { provider: 'mock', model: 'mock' }) | |
| let recoveries = 0 | |
| ctx.on('agent/request', () => { | |
| throw new LlmError('middleware failed', 'MIDDLEWARE') | |
| }) | |
| ctx.on('agent/request-error', async () => { | |
| recoveries += 1 | |
| }) | |
| agent.followup(createUserMessage({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })) | |
| await agent.whenIdle() | |
| expect(recoveries).toBe(0) | |
| expect(adapter.requests).toHaveLength(0) | |
| }) | |
| it('lets each failed request return a retry action before its turn closes', async () => { | |
| const adapter = new MockAdapter([ | |
| fail('busy', 'RATE_LIMIT'), | |
| fail('unavailable', 'SERVICE_UNAVAILABLE'), | |
| textResponse('ok'), | |
| ]) | |
| const ctx = await harness(adapter) | |
| const agent = await ctx.agentLoop.create(SessionId('request-error-retry'), { provider: 'mock', model: 'mock' }) | |
| const seen: { | |
| turn: number | |
| step: number | |
| failure: LlmFailure | |
| retryPolicy: ResolvedRetryPolicy | undefined | |
| }[] = [] | |
| const statuses: string[] = [] | |
| ctx.on('agent/status', ({ agent: subject, status }) => { | |
| if (subject === agent) statuses.push(status) | |
| }) | |
| ctx.on('agent/request-error', async ({ agent: subject, turn, step, failure, retryPolicy }) => { | |
| expect(subject).toBe(agent) | |
| seen.push({ turn, step, failure, retryPolicy }) | |
| return { kind: 'retry' } | |
| }) | |
| agent.followup(createUserMessage({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })) | |
| await agent.whenIdle() | |
| expect(seen.map(item => ({ | |
| turn: item.turn, | |
| step: item.step, | |
| code: item.failure.code, | |
| }))).toEqual([ | |
| { | |
| turn: 1, | |
| step: 1, | |
| code: 'RATE_LIMIT', | |
| }, | |
| { | |
| turn: 1, | |
| step: 1, | |
| code: 'SERVICE_UNAVAILABLE', | |
| }, | |
| ]) | |
| expect(agent.session.snapshotEvents().filter(event => event.type === 'turn/start')).toHaveLength(1) | |
| expect(seen.map(item => item.retryPolicy)).toEqual([ | |
| expect.objectContaining({ mode: 'normal' }), | |
| expect.objectContaining({ mode: 'normal' }), | |
| ]) | |
| expect(statuses).toEqual(['running', 'idle']) | |
| expect(agent.session.snapshotEvents().flatMap(event => | |
| event.type === 'request/header' ? [event.data.reason] : [])).toEqual(['initial']) | |
| }) | |
| it('lets cancellation win over a retry action', async () => { | |
| const adapter = new MockAdapter([fail('busy', 'RATE_LIMIT'), textResponse('unused')]) | |
| const ctx = await harness(adapter) | |
| const agent = await ctx.agentLoop.create(SessionId('request-error-cancel'), { provider: 'mock', model: 'mock' }) | |
| ctx.on('agent/request-error', async ({ agent: subject }) => { | |
| subject.cancel({ kind: 'user' }) | |
| return { kind: 'retry' } | |
| }) | |
| agent.followup(createUserMessage({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })) | |
| await agent.whenIdle() | |
| expect(adapter.requests).toHaveLength(1) | |
| expect(agent.session.snapshotEvents().filter(event => event.type === 'turn/start')).toHaveLength(1) | |
| expect(agent.session.snapshotEvents().find(event => event.type === 'turn/end')).toMatchObject({ | |
| type: 'turn/end', | |
| data: { reason: { kind: 'aborted', reason: { kind: 'user' } } }, | |
| }) | |
| }) | |
| it('does not retry when the recovery listener fails before returning its action', async () => { | |
| const adapter = new MockAdapter([fail('busy', 'RATE_LIMIT'), textResponse('unused')]) | |
| const ctx = await harness(adapter) | |
| const agent = await ctx.agentLoop.create(SessionId('request-error-recovery-failed'), { | |
| provider: 'mock', | |
| model: 'mock', | |
| }) | |
| ctx.on('agent/request-error', async () => { | |
| throw new Error('recovery failed') | |
| }) | |
| agent.followup(createUserMessage({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } })) | |
| await agent.whenIdle() | |
| expect(adapter.requests).toHaveLength(1) | |
| expect(agent.session.snapshotEvents().filter(event => event.type === 'turn/start')).toHaveLength(1) | |
| expect(agent.session.snapshotEvents().find(event => event.type === 'turn/end')).toMatchObject({ | |
| type: 'turn/end', | |
| data: { reason: { kind: 'error' } }, | |
| }) | |
| }) | |
| }) | |