test: align consumers with owned-run semantics
This commit is contained in:
@@ -144,15 +144,8 @@ function alwaysConfig(backoff: BackoffConfig = {}): AlwaysRetryPolicyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const dispose = ctx.on('agent/status', (subject, status) => {
|
||||
if (subject === agent && status === 'idle') {
|
||||
dispose()
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
function waitForIdle(_ctx: Context, agent: Agent): Promise<void> {
|
||||
return agent.whenIdle()
|
||||
}
|
||||
|
||||
function waitForRetry(ctx: Context, agent: Agent, retryNumber: number): Promise<Extract<SessionEvent, { type: 'llm/retry' }>> {
|
||||
@@ -175,7 +168,7 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
describe('provider-routed retry policy', () => {
|
||||
it('records the scheduled delay before opening a fresh request attempt', async () => {
|
||||
it('records the scheduled delay before retrying the request', async () => {
|
||||
vi.useFakeTimers()
|
||||
const adapter = new ScriptedAdapter([
|
||||
new LlmError('busy', 'RATE_LIMIT', { status: 429 }),
|
||||
@@ -214,7 +207,7 @@ describe('provider-routed retry policy', () => {
|
||||
|
||||
expect(adapter.requests).toHaveLength(2)
|
||||
expect(agent.session.events.filter(item => item.type === 'step/start').map(item => item.data))
|
||||
.toEqual([{ turn: 1, step: 1 }, { turn: 2, step: 1 }])
|
||||
.toEqual([{ turn: 1, step: 1 }])
|
||||
expect(agent.session.deriveMessages().at(-1)).toEqual({
|
||||
id: expect.any(String) as unknown,
|
||||
role: 'assistant',
|
||||
@@ -251,7 +244,7 @@ describe('provider-routed retry policy', () => {
|
||||
expect(agent.session.events.filter(event => event.type === 'assistant/message').map(event => ({
|
||||
turn: event.data.turn,
|
||||
step: event.data.step,
|
||||
}))).toEqual([{ turn: 2, step: 1 }])
|
||||
}))).toEqual([{ turn: 1, step: 1 }])
|
||||
expect(agent.session.deriveMessages().at(-1)).toMatchObject({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: 'recovered' }],
|
||||
@@ -291,7 +284,7 @@ describe('provider-routed retry policy', () => {
|
||||
expect(agent.session.events.filter(event => event.type === 'assistant/message').map(event => ({
|
||||
turn: event.data.turn,
|
||||
step: event.data.step,
|
||||
}))).toEqual([{ turn: 2, step: 1 }])
|
||||
}))).toEqual([{ turn: 1, step: 1 }])
|
||||
expect(agent.session.events.some(event => event.type === 'tool/call')).toBe(false)
|
||||
expect(toolExecutions).toBe(0)
|
||||
expect(agent.session.deriveMessages().at(-1)).toMatchObject({
|
||||
@@ -534,9 +527,9 @@ describe('provider-routed retry policy', () => {
|
||||
backoff: { initialDelayMs: 1, maxDelayMs: 1 },
|
||||
}),
|
||||
}, (ctx) => {
|
||||
ctx.on('agent/request', async (_agent, turn, _step, _signal, next) => ({
|
||||
ctx.on('agent/request', async (_agent, _turn, _step, _signal, next) => ({
|
||||
...await next(),
|
||||
provider: turn === 1 ? 'mock' : 'other',
|
||||
provider: adapter.requests.length === 0 ? 'mock' : 'other',
|
||||
}))
|
||||
}))
|
||||
const agent = context.agentLoop.create(SessionId('retry-provider-budgets'), {
|
||||
|
||||
@@ -55,14 +55,8 @@ async function harness(
|
||||
return ctx
|
||||
}
|
||||
|
||||
function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const dispose = ctx.on('agent/status', (subject, status) => {
|
||||
if (subject !== agent || status !== 'idle') return
|
||||
dispose()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
function waitForIdle(_ctx: Context, agent: Agent): Promise<void> {
|
||||
return agent.whenIdle()
|
||||
}
|
||||
|
||||
function sendAndWait(ctx: Context, agent: Agent): Promise<void> {
|
||||
@@ -109,7 +103,7 @@ describe('bounded retry through the real DeepSeek HTTP/SSE adapter', () => {
|
||||
expect(server?.requests).toHaveLength(1)
|
||||
expect(agent.session.events.filter(event => event.type === 'step/start')
|
||||
.map(event => [event.data.turn, event.data.step]))
|
||||
.toEqual([[1, 1], [2, 1]])
|
||||
.toEqual([[1, 1]])
|
||||
expect(agent.session.events.filter(event => event.type === 'llm/retry').map(event => event.data.failure.code))
|
||||
.toEqual(['TRANSPORT'])
|
||||
expect(finalAssistantText(agent)).toBe('connected after retry')
|
||||
@@ -141,7 +135,7 @@ describe('bounded retry through the real DeepSeek HTTP/SSE adapter', () => {
|
||||
)).toHaveLength(failedChunkCount)
|
||||
expect(agent.session.events.filter(event => event.type === 'assistant/message')
|
||||
.map(event => [event.data.turn, event.data.step]))
|
||||
.toEqual([[2, 1]])
|
||||
.toEqual([[1, 1]])
|
||||
expect(agent.session.events.filter(event => event.type === 'llm/retry').map(event => event.data.failure.code))
|
||||
.toEqual(['TRANSPORT'])
|
||||
expect(finalAssistantText(agent)).toBe('recovered response')
|
||||
@@ -166,7 +160,7 @@ describe('bounded retry through the real DeepSeek HTTP/SSE adapter', () => {
|
||||
.toEqual(['EMPTY_RESPONSE'])
|
||||
expect(agent.session.events.filter(event => event.type === 'assistant/message')
|
||||
.map(event => [event.data.turn, event.data.step]))
|
||||
.toEqual([[2, 1]])
|
||||
.toEqual([[1, 1]])
|
||||
expect(agent.session.events.at(-1)).toMatchObject({
|
||||
type: 'turn/end',
|
||||
data: { reason: { kind: 'completed' } },
|
||||
@@ -234,7 +228,7 @@ describe('bounded retry through the real DeepSeek HTTP/SSE adapter', () => {
|
||||
await sendAndWait(context, agent)
|
||||
|
||||
expect(server.requests).toHaveLength(3)
|
||||
expect(agent.session.events.filter(event => event.type === 'step/start')).toHaveLength(3)
|
||||
expect(agent.session.events.filter(event => event.type === 'step/start')).toHaveLength(1)
|
||||
expect(agent.session.events.filter(event => event.type === 'llm/retry')).toHaveLength(2)
|
||||
expect(agent.session.events.at(-1)).toMatchObject({
|
||||
type: 'turn/end',
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/llm/llm/README.md
|
||||
README.md: d343449d1530bf70a3a8c57f883894e29c42d18f
|
||||
README.zh.md: 4dc4a0ca06378116d05fdb4b9b048738930511fd
|
||||
README.md: dc7499a6854fe9a45c1297aa2a1a67aea92eaf6f
|
||||
README.zh.md: 28f281908c2fde708f491194b441a32a8dcba8a5
|
||||
|
||||
@@ -262,13 +262,16 @@ describe('LlmService', () => {
|
||||
messages: [],
|
||||
}))
|
||||
|
||||
expect(chunks.at(-1)).toMatchObject({
|
||||
const finish = chunks.at(-1)
|
||||
expect(finish).toMatchObject({
|
||||
type: 'finish',
|
||||
reason: {
|
||||
kind: 'error',
|
||||
failure: { code: 'NO_ADAPTER', message: expect.stringContaining('no adapter registered') },
|
||||
failure: { code: 'NO_ADAPTER' },
|
||||
},
|
||||
})
|
||||
if (finish?.type !== 'finish' || finish.reason.kind !== 'error') throw new Error('expected error finish')
|
||||
expect(finish.reason.failure.message).toContain('no adapter registered')
|
||||
})
|
||||
|
||||
it.each(['done', 'value'] as const)('normalizes a throwing IteratorResult.%s getter', async (field) => {
|
||||
|
||||
Reference in New Issue
Block a user