/** * Real-composition guard for the dormant pi-ai posture: LlmService, * settings-local, credentials-local, and a bare `llm-pi-ai` row boot from a * test-only cordis.yml through the actual Loader + Include path, an external * edit of settings.yaml registers the route live, and the next request * carries the credential the .env supplies. A hand-mounted `ctx.plugin` cannot * catch Loader export-shape failures, which is why the twin adapter has the * same guard. */ import { mkdtemp, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { pathToFileURL } from 'node:url' import { afterEach, describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' import Loader from '@cordisjs/plugin-loader' import Include from '@cordisjs/plugin-include' import LlmService from '@deepseek-ai/dsh-llm' import CredentialsLocal from '@deepseek-ai/dsh-credentials-local' 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' let root: string | undefined let context: Context | undefined afterEach(async () => { await context?.fiber.dispose() context = undefined if (root !== undefined) await rm(root, { recursive: true, force: true }) root = undefined await closeMockServers() vi.unstubAllEnvs() }) /** Boot the dormant composition: a bare `llm-pi-ai` row with no config at all. */ async function loadComposition(): Promise<{ ctx: Context; settingsPath: string }> { root = await mkdtemp(join(tmpdir(), 'dsh-pi-composition-')) const settingsPath = join(root, 'settings.yaml') await writeFile(settingsPath, '# personal settings\n') await writeFile(join(root, '.env'), 'PI_COMPOSITION_KEY=key-from-store\n') const configPath = join(root, 'cordis.yml') await writeFile(configPath, [ '- id: llm', " name: 'test-llm-service'", '- id: settings', " name: '@deepseek-ai/dsh-settings-local'", ' config:', ` path: ${JSON.stringify(settingsPath)}`, ' debounceMs: 10', '- id: credentials', " name: '@deepseek-ai/dsh-credentials-local'", ' config:', ` path: ${JSON.stringify(join(root, '.env'))}`, ' debounceMs: 10', '- id: llm-pi-ai', " name: '@deepseek-ai/dsh-llm-pi-ai'", '', ].join('\n')) const ctx = new Context() context = ctx ctx.baseUrl = pathToFileURL(root).href + '/' await ctx.plugin(Loader) ctx.loader.builtins.include = Include const modules = new Map([ ['test-llm-service', LlmService], ['@deepseek-ai/dsh-settings-local', SettingsLocal], ['@deepseek-ai/dsh-credentials-local', CredentialsLocal], ['@deepseek-ai/dsh-llm-pi-ai', LlmPiAi], ]) ctx.loader.internal = { version: 'v2', async import(specifier: string) { if (!modules.has(specifier)) throw new Error(`unexpected Loader import: ${specifier}`) return modules.get(specifier) }, } as unknown as NonNullable await ctx.loader.create({ name: 'cordis:include', config: { path: pathToFileURL(configPath).href }, }) await ctx.loader.await() return { ctx, settingsPath } } describe('llm-pi-ai real dormant composition', () => { it('boots with zero routes and registers one the moment settings supply a profile', async () => { vi.stubEnv('PI_COMPOSITION_KEY', '') const server = await mockServer([{ events: textEvents }]) const { ctx, settingsPath } = await loadComposition() // The shipped posture: the adapter exists, no route does. expect(ctx.llm.listProviders()).toEqual([]) // Exactly what the web Models page leaves on disk. await writeFile(settingsPath, [ 'llm-pi-ai:', ' providers:', ' deepseek:', ' apiKeyEnv: PI_COMPOSITION_KEY', ` baseURL: ${server.url}`, '', ].join('\n')) await vi.waitFor(() => { expect(ctx.llm.listProviders().map(provider => provider.id)).toEqual(['deepseek']) }, { timeout: 5000 }) 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 key-from-store') }) })