The declared-provider-catalog note still recorded modalities as closed because "nothing reads them: context.ts keeps only text blocks" — the same reasoning this change disproves. Rewrite that fact in place, keep pricing closed for its own still-current reason, and cross-link both directions. The new resolver cases never crossed the config schema, so neither acceptance path this change adds had an executable gate: config.spec.ts now refuses an unknown modality at both levels, pins the empty route list to the namespace validator the settings seam actually runs, and pins the `[]` materialization the inheritance rule depends on. A catalog.spec.ts case re-asserts every rung through a written settings section and `ctx.llm`, which is what the note's Testing section claimed. Also export PiAiModality from the package entry — it types two public config fields and consumers could not name it — record the unverified declaration as a Known Limitation, and stop sharing one fallback array across a route's models.
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { assertServiceable, Config } from '../src/config.ts'
|
|
|
|
/** Validate one hand-declared route, with the caller's fields layered onto it. */
|
|
const routeWith = (profile: Record<string, unknown>): (() => unknown) =>
|
|
() => Config({
|
|
providers: {
|
|
'acme-gateway': {
|
|
api: 'openai-completions',
|
|
baseURL: 'https://acme.test',
|
|
models: [{ id: 'm' }],
|
|
...profile,
|
|
},
|
|
},
|
|
})
|
|
|
|
/** Validate that route with the caller's fields on its single model entry. */
|
|
const configWith = (model: Record<string, unknown>): (() => unknown) =>
|
|
routeWith({ models: [{ id: 'm', ...model }] })
|
|
|
|
describe('reasoning schema boundary', () => {
|
|
it('rejects a level pi-ai does not know at the write that produced it', () => {
|
|
expect(configWith({ reasoningEfforts: { ultra: 'x' } })).toThrow(/"off"/)
|
|
expect(configWith({ reasoningEfforts: { high: 42 } })).toThrow()
|
|
})
|
|
|
|
it('keeps false distinguishable from an absent declaration', () => {
|
|
type Materialized = { providers: Record<string, { models?: { reasoningEfforts?: unknown }[] }> }
|
|
const withFalse = configWith({ reasoningEfforts: false })() as Materialized
|
|
expect(withFalse.providers['acme-gateway']?.models?.[0]?.reasoningEfforts).toBe(false)
|
|
const absent = configWith({})() as Materialized
|
|
expect(absent.providers['acme-gateway']?.models?.[0]?.reasoningEfforts).toBeUndefined()
|
|
})
|
|
|
|
it('rejects a thinking format outside the offered set', () => {
|
|
expect(configWith({ compat: { thinkingFormat: 'quantum' } })).toThrow(/expected/)
|
|
})
|
|
})
|
|
|
|
describe('modality schema boundary', () => {
|
|
it('rejects a modality pi-ai does not know, at either level', () => {
|
|
expect(configWith({ input: ['audio'] })).toThrow(/expected/)
|
|
expect(routeWith({ defaultInput: ['text', 'audio'] })).toThrow(/expected/)
|
|
})
|
|
|
|
it('refuses a route whose models could accept nothing', () => {
|
|
// The pair the settings seam runs: the schema accepts the empty list as
|
|
// well-typed, and the namespace validator is what refuses it. Asserting
|
|
// only the schema would report this route as writable.
|
|
expect(routeWith({ defaultInput: [] })).not.toThrow()
|
|
expect(() => { assertServiceable(routeWith({ defaultInput: [] })() as Config) })
|
|
.toThrow(/defaultInput must name at least one modality/)
|
|
})
|
|
|
|
type Materialized = {
|
|
providers: Record<string, { defaultInput?: unknown; models?: { input?: unknown }[] }>
|
|
}
|
|
|
|
it('materializes an absent entry list as empty and an absent route list as text', () => {
|
|
// The empty-list inheritance rule exists because of exactly this: an entry
|
|
// that declares nothing reaches resolution as `[]`, not as `undefined`.
|
|
const absent = configWith({})() as Materialized
|
|
expect(absent.providers['acme-gateway']?.models?.[0]?.input).toEqual([])
|
|
expect(absent.providers['acme-gateway']?.defaultInput).toEqual(['text'])
|
|
})
|
|
})
|