feat(llm-pi-ai): dormant bare mount — routes live entirely in the settings plane
An empty or omitted providers dict is now the valid dormant posture: the adapter mounts with zero routes and no catalog entries, registers routes the moment the llm-pi-ai settings section supplies profiles, and drops them when it empties. The TUI demo mounts the adapter bare, so adding an openai/anthropic provider is purely a settings.yaml (or, next PR, web form) operation with per-request apiKeyEnv credential resolution.
This commit is contained in:
@@ -395,7 +395,9 @@ describe('provider profile lifecycle', () => {
|
||||
})
|
||||
|
||||
it('validates empty, unknown, legacy-shaped, and explicitly blank profiles', () => {
|
||||
expect(() => resolveProfiles({})).toThrow(/at least one/)
|
||||
// Empty and omitted dicts are the dormant zero-route posture, not errors.
|
||||
expect(resolveProfiles({}).size).toBe(0)
|
||||
expect(resolveProfiles(undefined).size).toBe(0)
|
||||
expect(() => resolveProfiles({ '': {} })).toThrow(/non-empty/)
|
||||
expect(() => resolveProfiles({ 'not-real': {} })).toThrow(/unknown/)
|
||||
// The pre-release array shape and its per-profile provider field fail
|
||||
|
||||
@@ -42,6 +42,30 @@ async function boot(dir: string, config: LlmPiAi.Config): Promise<Context> {
|
||||
}
|
||||
|
||||
describe('request-level dynamic profiles', () => {
|
||||
it('mounts bare and dormant, then registers routes the moment settings supply providers', async () => {
|
||||
vi.stubEnv('PI_DYNAMIC_KEY', '')
|
||||
const dir = await home()
|
||||
await writeFile(join(dir, '.env'), 'PI_DYNAMIC_KEY=pk-from-settings\n')
|
||||
const server = await mockServer([{ events: textEvents }])
|
||||
// The exact product posture: `- id: llm-pi-ai` with no config at all.
|
||||
const ctx = await boot(dir, {})
|
||||
|
||||
expect(ctx.llm.listProviders()).toEqual([])
|
||||
await ctx.settings.update(NS, {
|
||||
providers: { deepseek: { apiKeyEnv: 'PI_DYNAMIC_KEY', baseURL: server.url } },
|
||||
})
|
||||
expect(ctx.llm.listProviders().map(provider => provider.id)).toEqual(['deepseek'])
|
||||
await expect(ctx.llm.listModels('deepseek')).resolves.not.toHaveLength(0)
|
||||
|
||||
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 pk-from-settings')
|
||||
|
||||
// Emptying the user layer returns the adapter to its dormant state.
|
||||
await ctx.settings.replace(NS, {})
|
||||
expect(ctx.llm.listProviders()).toEqual([])
|
||||
})
|
||||
|
||||
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 }])
|
||||
|
||||
Reference in New Issue
Block a user