feat(llm): add per-provider retry policies

This commit is contained in:
Turtle
2026-07-25 10:18:16 +08:00
parent fe84b446b0
commit b58e33268a
60 changed files with 1606 additions and 334 deletions

View File

@@ -1366,8 +1366,9 @@ export function streamSessionEventUpdate(
return
}
case 'llm/retry': {
const retryLimit = event.data.mode === 'always' ? '∞' : String(event.data.maxRetries)
const text = '\n\n[Previous model attempt discarded; retrying '
+ `${event.data.retry}/${event.data.maxRetries} in ${event.data.delayMs}ms: `
+ `${event.data.retry}/${retryLimit} in ${event.data.delayMs}ms: `
+ `${event.data.failure.message}]\n\n`
notify({ sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } } })
return

View File

@@ -103,6 +103,8 @@ describe('streamSessionEventUpdate', () => {
expect(updatesFor(evt('llm/retry', {
turn: 1,
step: 1,
provider: 'mock',
mode: 'normal',
retry: 1,
maxRetries: 2,
delayMs: 500,
@@ -114,6 +116,21 @@ describe('streamSessionEventUpdate', () => {
text: '\n\n[Previous model attempt discarded; retrying 1/2 in 500ms: backend busy]\n\n',
},
}])
expect(updatesFor(evt('llm/retry', {
turn: 1,
step: 2,
provider: 'mock',
mode: 'always',
retry: 7,
delayMs: 1_000,
failure: { message: 'still unavailable', code: 'AUTH' },
}))).toEqual([{
sessionUpdate: 'agent_message_chunk',
content: {
type: 'text',
text: '\n\n[Previous model attempt discarded; retrying 7/∞ in 1000ms: still unavailable]\n\n',
},
}])
expect(updatesFor(evt('turn/end', {
turn: 1,
reason: { kind: 'error', step: 2, failure: { message: 'still busy', code: 'SERVER' } },

View File

@@ -2298,8 +2298,9 @@ export function createTuiChat(
}
case 'llm/retry': {
clearStreaming()
const retryLimit = event.data.mode === 'always' ? '∞' : String(event.data.maxRetries)
appendNotice(
`Retrying model request (${event.data.retry}/${event.data.maxRetries}) in ${event.data.delayMs}ms: ${event.data.failure.message}`,
`Retrying model request (${event.data.retry}/${retryLimit}) in ${event.data.delayMs}ms: ${event.data.failure.message}`,
'warning',
)
break

View File

@@ -21,7 +21,7 @@ buffer
7| "▌ "
style 0-0 fg=bright-blue
8| <blank>
9| " Retrying model request (1/2) in 1000ms: temporary transport failure "
9| " Retrying model request (1/∞) in 1000ms: temporary transport failure "
style 1-67 fg=yellow
10| <blank>
11| " Turn cancelled. "

View File

@@ -269,6 +269,8 @@ describe('TUI terminal-state snapshots', () => {
harness.session.append('llm/retry', {
turn: 1,
step: 1,
provider: 'mock',
mode: 'normal',
retry: 1,
maxRetries: 2,
delayMs: 500,
@@ -297,8 +299,9 @@ describe('TUI terminal-state snapshots', () => {
harness.session.append('llm/retry', {
turn: 1,
step: 1,
provider: 'mock',
mode: 'always',
retry: 1,
maxRetries: 2,
delayMs: 1_000,
failure: { message: 'temporary transport failure', code: 'TRANSPORT' },
})

View File

@@ -1290,6 +1290,8 @@ describe('pi-tui chat lifecycle and transcript', () => {
result.session.append('llm/retry', {
turn: 1,
step: 1,
provider: 'mock',
mode: 'normal',
retry: 1,
maxRetries: 2,
delayMs: 500,
@@ -1322,6 +1324,8 @@ describe('pi-tui chat lifecycle and transcript', () => {
result.session.append('llm/retry', {
turn: 1,
step: 1,
provider: 'mock',
mode: 'normal',
retry: 1,
maxRetries: 2,
delayMs: 500,
@@ -1330,6 +1334,8 @@ describe('pi-tui chat lifecycle and transcript', () => {
result.session.append('llm/retry', {
turn: 1,
step: 2,
provider: 'mock',
mode: 'normal',
retry: 2,
maxRetries: 2,
delayMs: 1_000,