Merge remote-tracking branch 'origin/master' into codex/provider-retry-policy

# Conflicts:
#	.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	packages/llm/llm-retry/README.md
#	packages/llm/llm-retry/src/index.ts
#	packages/llm/llm-retry/tests/retry.spec.ts
This commit is contained in:
Turtle
2026-07-25 16:05:05 +08:00
156 changed files with 1412 additions and 1079 deletions
+7 -1
View File
@@ -23,7 +23,7 @@ Replay keys every call by its calling session id (`GenerateOptions.sessionId`, s
| `file` | string | `$DSH_SNAPSHOT_FILE` | Path to the primary (parent) `session.jsonl` fixture. Required (config or env). |
| `overrideFile` | string | `$DSH_SNAPSHOT_OVERRIDE` | Optional path to a `ReplayEntry[]` sidecar that replaces the PRIMARY session's derived script. |
| `childFiles` | string[] | `$DSH_SNAPSHOT_CHILD_FILES` (path-delimited) | Recorded subagent child-session logs for a nested scenario; empty for a single-session scenario. |
| `providers` | `ReplayProviderConfig[]` | — | Optional replay-only provider and model catalog. Each model may publish `contextWindow`; configured routes dispatch through the replay adapter and never perform provider I/O. |
| `providers` | `ReplayProviderConfig[]` | — | Optional replay-only provider and model catalog. Each provider may set `retryPolicy`, and each model may publish `contextWindow`; configured routes dispatch through the replay adapter and never perform provider I/O. |
```yaml
- id: llm-replay
@@ -32,6 +32,12 @@ Replay keys every call by its calling session id (`GenerateOptions.sessionId`, s
providers:
- id: deepseek
name: DeepSeek
retryPolicy:
mode: normal
backoff:
initialDelayMs: 1
maxDelayMs: 1
jitterRatio: 0
models:
- id: deepseek-v4-flash
contextWindow: 128000
+21 -2
View File
@@ -11,8 +11,16 @@ import { delimiter as pathDelimiter } from 'node:path'
import type { Context } from 'cordis'
import { decodeStorageRecord } from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import type { GenerateOptions, LlmModelContext, LlmModelInfo, LlmProviderInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
import { LlmAdapter, LlmError, assertNever } from '@deepseek-ai/dsh-llm'
import type {
GenerateOptions,
LlmModelContext,
LlmModelInfo,
LlmProviderInfo,
ResolvedRetryPolicy,
RetryPolicyConfig,
StreamChunk,
} from '@deepseek-ai/dsh-llm'
import { LlmAdapter, LlmError, assertNever, resolveRetryPolicy } from '@deepseek-ai/dsh-llm'
/**
* One recorded model call. `throw` may replay prefix chunks before failing;
@@ -48,6 +56,8 @@ export interface ReplayProviderConfig {
name?: string
/** Advisory models exposed to replay scenarios that exercise discovery. */
models?: ReplayModelConfig[]
/** Optional provider-owned retry policy used by assembled recovery snapshots. */
retryPolicy?: RetryPolicyConfig
}
/** Resolved plugin configuration. */
@@ -256,6 +266,15 @@ class ReplayAdapter extends LlmAdapter {
return { id: provider, name: configured.name ?? provider }
}
override providerRetryPolicy(provider: string): ResolvedRetryPolicy | undefined {
const configured = this.providers.get(provider)
/* v8 ignore next -- LlmService only asks about routes registered from this same map. */
if (configured === undefined) return super.providerRetryPolicy(provider)
return configured.retryPolicy === undefined
? undefined
: resolveRetryPolicy(configured.retryPolicy, `llm-replay: provider "${provider}" retryPolicy`)
}
override listModels(provider: string): Promise<readonly LlmModelInfo[]> {
const configured = this.providers.get(provider)
/* v8 ignore next -- LlmService only asks about routes registered from this same map. */
@@ -240,6 +240,11 @@ describe('installLlmReplay (through the real LlmService)', () => {
{
id: 'deepseek',
name: 'DeepSeek',
retryPolicy: {
mode: 'normal',
maxRetries: 2,
backoff: { initialDelayMs: 1, maxDelayMs: 1, jitterRatio: 0 },
},
models: [
{ id: 'flash', contextWindow: 128_000 },
{ id: 'pro', name: 'Pro', description: 'Larger model' },
@@ -262,12 +267,39 @@ describe('installLlmReplay (through the real LlmService)', () => {
await expect(ctx.llm.resolveModelContext('deepseek', 'pro')).resolves.toBeUndefined()
await expect(ctx.llm.resolveModelContext('deepseek', 'unlisted')).resolves.toBeUndefined()
await expect(ctx.llm.resolveModelContext('empty', 'unlisted')).resolves.toBeUndefined()
expect(ctx.llm.providerRetryPolicy('deepseek')).toMatchObject({
mode: 'normal',
maxRetries: 2,
initialDelayMs: 1,
maxDelayMs: 1,
jitterRatio: 0,
})
expect(ctx.llm.providerRetryPolicy('empty')).toMatchObject({
mode: 'normal',
maxRetries: 2,
initialDelayMs: 500,
maxDelayMs: 10_000,
jitterRatio: 0.1,
})
expect(await drain(ctx.llm.stream({ provider: 'deepseek', model: 'pro', messages: [] }))).toEqual(TEXT_CHUNKS)
dispose()
expect(ctx.llm.listProviders()).toEqual([])
})
it('rejects an invalid replay-provider retry policy during registration', async () => {
writeLog(TEXT_CHUNKS)
const ctx = new Context()
await ctx.plugin(LlmService)
expect(() => {
installLlmReplay(ctx, {
file,
providers: [{ id: 'deepseek', retryPolicy: { mode: 'normal', maxRetries: -1 } }],
})
}).toThrow(/llm-replay: provider "deepseek" retryPolicy\.maxRetries/)
})
it('serves the Nth call the Nth derived entry (positional)', async () => {
const second: StreamChunk[] = [
{ type: 'block-start', index: 0, blockType: 'text' },