fix(llm-replay): publish recorded model capacity

This commit is contained in:
Tianyi Cui
2026-07-21 18:57:25 +08:00
parent 1cee25fdea
commit 39c9bf3ef2
5 changed files with 24 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
import type { Context } from 'cordis' import type { Context } from 'cordis'
import type { GenerateOptions, LlmModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, LlmModelContext, LlmModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm' import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
const CONTROL_PROBE = '\u001b]2;MODEL_CONTROLLED\u0007\u001b[999CMODEL_CURSOR\u009b31mMODEL_C1' const CONTROL_PROBE = '\u001b]2;MODEL_CONTROLLED\u0007\u001b[999CMODEL_CURSOR\u009b31mMODEL_C1'
@@ -25,6 +25,10 @@ class ScriptedTuiAdapter extends LlmAdapter {
]) ])
} }
override resolveModelContext(_provider: string, _model: string): Promise<LlmModelContext> {
return Promise.resolve({ contextWindow: 128_000 })
}
override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> { override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
if (options.model !== 'tui-scripted-model-pro' || !options.system?.includes('tui-scripted-model-pro')) { if (options.model !== 'tui-scripted-model-pro' || !options.system?.includes('tui-scripted-model-pro')) {
throw new Error('the scripted TUI request did not apply the selected model to routing and prompt variables') throw new Error('the scripted TUI request did not apply the selected model to routing and prompt variables')

View File

@@ -33,7 +33,7 @@ import { HeadlessTerminal } from '../../../packages/ui/tui/tests/headless-termin
const SNAPSHOTS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'snapshots') const SNAPSHOTS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'snapshots')
// Keep pre-normalization layout widths identical across macOS and Linux. // Keep pre-normalization layout widths identical across macOS and Linux.
const SNAPSHOT_TMP_ROOT = process.platform === 'win32' ? tmpdir() : '/tmp' const SNAPSHOT_TMP_ROOT = process.platform === 'win32' ? tmpdir() : '/tmp'
const PROVIDERS = [{ id: 'deepseek', models: [{ id: 'deepseek-v4-flash' }] }] const PROVIDERS = [{ id: 'deepseek', models: [{ id: 'deepseek-v4-flash', contextWindow: 128_000 }] }]
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi
type SnapshotMode = 'replay' | 'record' | 'refresh' type SnapshotMode = 'replay' | 'record' | 'refresh'

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). | | `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. | | `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. | | `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. Configured routes dispatch through the replay adapter and never perform provider I/O. | | `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. |
```yaml ```yaml
- id: llm-replay - id: llm-replay
@@ -34,6 +34,7 @@ Replay keys every call by its calling session id (`GenerateOptions.sessionId`, s
name: DeepSeek name: DeepSeek
models: models:
- id: deepseek-v4-flash - id: deepseek-v4-flash
contextWindow: 128000
- id: deepseek-v4-pro - id: deepseek-v4-pro
# file/overrideFile/childFiles default to $DSH_SNAPSHOT_FILE / # file/overrideFile/childFiles default to $DSH_SNAPSHOT_FILE /
# $DSH_SNAPSHOT_OVERRIDE / $DSH_SNAPSHOT_CHILD_FILES, set by the snapshot # $DSH_SNAPSHOT_OVERRIDE / $DSH_SNAPSHOT_CHILD_FILES, set by the snapshot

View File

@@ -10,7 +10,7 @@ import { existsSync, readFileSync } from 'node:fs'
import { delimiter as pathDelimiter } from 'node:path' import { delimiter as pathDelimiter } from 'node:path'
import type { Context } from 'cordis' import type { Context } from 'cordis'
import type { SessionEvent } from '@deepseek-ai/dsh-session' import type { SessionEvent } from '@deepseek-ai/dsh-session'
import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, StreamChunk } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, LlmModelContext, LlmModelInfo, LlmProviderInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
import { LlmAdapter, LlmError, assertNever } from '@deepseek-ai/dsh-llm' import { LlmAdapter, LlmError, assertNever } from '@deepseek-ai/dsh-llm'
/** /**
@@ -31,6 +31,8 @@ export interface ReplayModelConfig {
name?: string name?: string
/** Optional selector description. */ /** Optional selector description. */
description?: string description?: string
/** Optional positive integer context capacity published by the replay adapter. */
contextWindow?: number
} }
/** One provider route exposed by the replay adapter. */ /** One provider route exposed by the replay adapter. */
@@ -260,6 +262,14 @@ class ReplayAdapter extends LlmAdapter {
}))) })))
} }
override resolveModelContext(provider: string, model: string): Promise<LlmModelContext | undefined> {
const configured = this.providers.get(provider)
/* v8 ignore next -- LlmService only asks about routes registered from this same map. */
if (configured === undefined) return Promise.resolve(undefined)
const contextWindow = configured.models?.find(candidate => candidate.id === model)?.contextWindow
return Promise.resolve(contextWindow === undefined ? undefined : { contextWindow })
}
override stream(options: GenerateOptions): AsyncIterable<StreamChunk> { override stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
return this.replay(options) return this.replay(options)
} }

View File

@@ -228,7 +228,7 @@ describe('installLlmReplay (through the real LlmService)', () => {
id: 'deepseek', id: 'deepseek',
name: 'DeepSeek', name: 'DeepSeek',
models: [ models: [
{ id: 'flash' }, { id: 'flash', contextWindow: 128_000 },
{ id: 'pro', name: 'Pro', description: 'Larger model' }, { id: 'pro', name: 'Pro', description: 'Larger model' },
], ],
}, },
@@ -245,6 +245,10 @@ describe('installLlmReplay (through the real LlmService)', () => {
{ provider: 'deepseek', id: 'pro', name: 'Pro', description: 'Larger model' }, { provider: 'deepseek', id: 'pro', name: 'Pro', description: 'Larger model' },
]) ])
await expect(ctx.llm.listModels('empty')).resolves.toEqual([]) await expect(ctx.llm.listModels('empty')).resolves.toEqual([])
await expect(ctx.llm.resolveModelContext('deepseek', 'flash')).resolves.toEqual({ contextWindow: 128_000 })
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(await drain(ctx.llm.stream({ provider: 'deepseek', model: 'pro', messages: [] }))).toEqual(TEXT_CHUNKS) expect(await drain(ctx.llm.stream({ provider: 'deepseek', model: 'pro', messages: [] }))).toEqual(TEXT_CHUNKS)
dispose() dispose()