Files
deepseek-harness/packages/llm/llm-pi-ai/tests/dynamic-config.spec.ts
Yichen Jiang c0426142c5 feat(llm-pi-ai): route-keyed profiles with per-request resolution and in-place route swaps
providers becomes a dict keyed by provider route, so the composition base
and the llm-pi-ai settings section merge per provider and the route set is
structural; the pre-release array shape and per-profile provider field fail
loud with migration directions. The adapter reads a profiles thunk once per
operation and resolves the credential per stream call (literal apiKey, then
apiKeyEnv through ctx.credentials with an ambient env fallback, then pi-ai's
provider-native discovery), so key, endpoint, and knob changes reach the
next request without restarts. Route-set or captured-retry-policy changes
re-register the same adapter instance in one synchronous section; an invalid
settings snapshot keeps the last good profiles.
2026-07-29 13:36:29 +08:00

117 lines
4.7 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import LlmService from '@deepseek-ai/dsh-llm'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
import { CredentialsLocal } from '@deepseek-ai/dsh-credentials-local'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { SettingsLocal } from '@deepseek-ai/dsh-settings-local'
import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai'
import { assemble } from './assemble.ts'
import { closeMockServers, mockServer, textEvents } from './mock-server.ts'
const NS = settingsNamespace('llm-pi-ai')
const cleanups: Array<() => Promise<void>> = []
afterEach(async () => {
while (cleanups.length > 0) await cleanups.pop()!()
await closeMockServers()
vi.unstubAllEnvs()
})
async function home(): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'dsh-pi-dynamic-'))
cleanups.push(() => rm(dir, { recursive: true, force: true }))
return dir
}
/** Real dynamic composition mirroring the deepseek twin's harness. */
async function boot(dir: string, config: LlmPiAi.Config): Promise<Context> {
const ctx = new Context()
cleanups.push(async () => {
await ctx.fiber.dispose()
})
await ctx.plugin(LlmService)
await ctx.plugin(SettingsLocal, { path: join(dir, 'settings.yaml'), watch: false })
await ctx.plugin(CredentialsLocal, { path: join(dir, '.env'), watch: false })
await ctx.plugin(LlmPiAi, config)
return ctx
}
describe('request-level dynamic profiles', () => {
it('adds a provider route from settings and drops it when the user layer resets', async () => {
const dir = await home()
const server = await mockServer([{ events: textEvents }])
const ctx = await boot(dir, {
providers: { openai: { apiKey: 'k', baseURL: 'http://127.0.0.1:1/v1' } },
})
expect(ctx.llm.listProviders().map(provider => provider.id)).toEqual(['openai'])
await ctx.settings.update(NS, {
providers: { deepseek: { apiKey: 'live-key', baseURL: server.url } },
})
expect(ctx.llm.listProviders().map(provider => provider.id)).toEqual(['openai', 'deepseek'])
const result = await assemble(ctx, { provider: 'deepseek', model: 'deepseek-v4-flash', messages: [] })
expect(result.message.content).toEqual([{ type: 'text', text: 'hello' }])
expect(server.headers[0]?.authorization).toBe('Bearer live-key')
// Reset the user layer: the settings-born route unregisters, the
// composition route stays.
await ctx.settings.replace(NS, {})
expect(ctx.llm.listProviders().map(provider => provider.id)).toEqual(['openai'])
await expect(assemble(ctx, { provider: 'deepseek', model: 'deepseek-v4-flash', messages: [] }))
.rejects.toMatchObject({ code: 'NO_ADAPTER' })
})
it('rotates the per-request credential referenced by apiKeyEnv', async () => {
vi.stubEnv('PI_DYNAMIC_KEY', '')
const dir = await home()
await writeFile(join(dir, '.env'), 'PI_DYNAMIC_KEY=pk-one\n')
const server = await mockServer([{ events: textEvents }, { events: textEvents }])
const ctx = await boot(dir, {
providers: { deepseek: { apiKeyEnv: 'PI_DYNAMIC_KEY', baseURL: server.url } },
})
await assemble(ctx, { provider: 'deepseek', model: 'deepseek-v4-flash', messages: [] })
expect(server.headers[0]?.authorization).toBe('Bearer pk-one')
await ctx.credentials.set(credentialRef('PI_DYNAMIC_KEY'), 'pk-two')
await assemble(ctx, { provider: 'deepseek', model: 'deepseek-v4-flash', messages: [] })
expect(server.headers[1]?.authorization).toBe('Bearer pk-two')
})
it('re-registers routes in place when a captured retry policy changes', async () => {
const dir = await home()
const ctx = await boot(dir, { providers: { openai: {} } })
await ctx.settings.update(NS, {
providers: {
openai: {
retryPolicy: { mode: 'always', backoff: { initialDelayMs: 25, maxDelayMs: 100, jitterRatio: 0.2 } },
},
},
})
expect(ctx.llm.providerRetryPolicy('openai')).toEqual({
mode: 'always',
initialDelayMs: 25,
maxDelayMs: 100,
jitterRatio: 0.2,
})
expect(ctx.llm.listProviders().map(provider => provider.id)).toEqual(['openai'])
})
it('keeps the last good profiles when a settings snapshot names an unknown provider', async () => {
const dir = await home()
const ctx = await boot(dir, { providers: { openai: {} } })
// Schema-valid but catalog-invalid: the resolver rejects it and the
// last good route set keeps serving.
await ctx.settings.update(NS, { providers: { 'not-a-real-provider': {} } })
expect(ctx.llm.listProviders().map(provider => provider.id)).toEqual(['openai'])
})
})