Files
Yichen Jiang 50edee2dab review: sync the note this supersedes, gate the schema paths, export the modality type
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.
2026-08-12 16:18:52 +08:00

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'])
})
})