fix(llm): capture an immutable snapshot per pi-ai operation
Review found four defects in the declared-provider work. `PiAiAdapter` reused one `Models` collection and mutated it whenever the configuration changed. `Models.streamSimple()` resolves its provider lazily — when the stream is first consumed, which is after the adapter awaits the route's credential — so a configuration change landing in that window let an in-flight request finish under a configuration it never resolved against, or fail on a provider that no longer existed. Each resolution now produces an immutable snapshot and every operation captures one before its first await, which is what makes the seam's per-step freeze (`llm.prepareCall()`) hold end to end: switching models mid-reply takes effect on the next step, never inside the one in flight. `defaultMaxTokens` was materialized from the catalog's `Model.maxTokens`. The two answer different questions: pi-ai requires that field as the model's output capability, while the seam's is a cap the deployment chose to send on requests naming none, so every request had started carrying a number nobody picked. Only an explicitly configured cap reaches the seam now. The configurable-provider directory was refreshed by disposing its registration and making a new one. A candidate set the registry refuses — a profile keyed `deepseek-official`, which llm-deepseek declares — left the whole directory withdrawn and the Models page empty, silently, because the settings callback contains the failure. The seam's registration handle now carries `replace()` with the same validate-first atomicity `registerAdapter` has. The protocol table offered every pi-ai streaming API, including four whose authentication a profile cannot express: Bedrock signs with SigV4 over AWS credentials and a region, Vertex needs a project, a location, and ADC, Azure needs provider environment plus an api-version, and Codex uses OAuth. Offering them handed back routes that cannot authenticate. Catalog routes still reach them through their own provider.
This commit is contained in:
@@ -421,8 +421,8 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
jsDoc: '/**\n * Describe provider routes with a registered adapter.\n * @returns detached provider metadata in registration order.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void',
|
||||
jsDoc: '/**\n * Declare provider routes an adapter plugin can activate through\n * configuration. Registration is all-or-nothing: an empty list, invalid\n * entry, or a provider already declared by any registration throws\n * `LlmError` without registering the rest. Disposed with the fiber.\n * @param entries - every configurable provider this plugin owns.\n * @returns the disposer that withdraws all of them.\n */',
|
||||
signature: 'registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle',
|
||||
jsDoc: '/**\n * Declare provider routes an adapter plugin can activate through\n * configuration. Registration is all-or-nothing: an empty list, invalid\n * entry, or a provider already declared by any registration throws\n * `LlmError` without registering the rest. Disposed with the fiber.\n * @param entries - every configurable provider this plugin owns.\n * @returns a handle that withdraws all of them, and can atomically replace them.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'listConfigurableProviders(): LlmConfigurableProvider[]',
|
||||
@@ -1889,6 +1889,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'DirectoryPickerNativeCapability',
|
||||
declaration: 'export interface DirectoryPickerNativeCapability {\n kind: \'native\';\n pick(signal: AbortSignal): Promise<string | null>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'DirectoryRegistrationHandle',
|
||||
declaration: 'export interface DirectoryRegistrationHandle {\n (): void;\n replace(entries: readonly LlmConfigurableProvider[]): void;\n}',
|
||||
},
|
||||
{
|
||||
name: 'Domain',
|
||||
declaration: 'export interface Domain<S extends DomainSpec> {\n readonly name: string;\n readonly global: DomainGlobalHandleOf<S>;\n table<N extends keyof S[\'tables\'] & string>(name: N): KvTable<TableKeyOf<S, N>, TableValueOf<S, N>>;\n close(): Promise<void>;\n}',
|
||||
|
||||
Reference in New Issue
Block a user