feat(llm): interrogate a draft provider endpoint for its models
Once a pi-ai route became a declaration rather than a catalog lookup, adding an OpenAI-compatible gateway meant knowing its model ids up front. Most such endpoints publish that list at `GET /models`, but no seam operation could ask: every one is keyed by a registered provider route, and the provider being added has no route, no stored profile, and no stored credential — the endpoint and key are values in a form. Interrogation is therefore keyed by settings namespace, which a configuration surface already holds from the configurable-provider directory. `registerModelDiscovery` offers it per namespace, `discoverModels` asks, and the request carries the draft itself. The reply is candidates, not a catalog: every field but the id is optional because most listings disclose nothing else, and adopting one is a settings write like any other. Nothing here reads or writes settings or credentials, so `settings.yaml` still decides what a route serves. `llm.discoverModels` carries the same draft over the wire. Its apiKey is the third and last payload a secret may ride, and it is never stored, logged, or echoed; every refusal folds into `model-discovery-failed`, naming the endpoint asked but never the credential offered. The pi-ai side is a plain GET for OpenAI-compatible protocols only — their listing shape is the one gateways, self-hosted servers, and the official endpoints agree on. Others say so, sending the user to hand-entry rather than reporting a guessed shape as an empty provider. The reply is read under a four-megabyte ceiling held on the bytes actually received, because the endpoint is a URL the user typed.
This commit is contained in:
@@ -2444,6 +2444,12 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
|
||||
],
|
||||
}),
|
||||
models: request => ok(request, { groups: fixtureModelGroups(), failures: [] }),
|
||||
// The fixture endpoint is imaginary, so the interrogation answers the
|
||||
// catalog it already serves — enough for a surface to exercise adopting
|
||||
// candidates without a reachable provider.
|
||||
discoverModels: request => ok(request, {
|
||||
models: fixtureModelGroups().flatMap(group => group.models.map(model => ({ id: model.id, name: model.name }))),
|
||||
}),
|
||||
},
|
||||
respond(message: ClientResponse): Promise<RpcReceipt> {
|
||||
// Same routing discipline as the host: rpcId first, then the payload's
|
||||
@@ -2561,6 +2567,7 @@ export class FixtureApiClient extends AbstractApiClient {
|
||||
case 'credentials.unset': return this.api.credentials.unset(request)
|
||||
case 'llm.providers': return this.api.llm.providers(request)
|
||||
case 'llm.models': return this.api.llm.models(request)
|
||||
case 'llm.discoverModels': return this.api.llm.discoverModels(request, signal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ export class FakeApiClient implements IApiClient {
|
||||
readonly llm: IApiClient['llm'] = {
|
||||
providers: payload => this.record('llm.providers', payload, Promise.resolve(ok({ providers: [] }))),
|
||||
models: payload => this.record('llm.models', payload, Promise.resolve(ok({ groups: [], failures: [] }))),
|
||||
discoverModels: payload => this.record('llm.discoverModels', payload, Promise.resolve(ok({ models: [] }))),
|
||||
}
|
||||
|
||||
/** When true, streams never fire onOpen (misbehaving-carrier material for the handshake timeout guard). */
|
||||
|
||||
@@ -232,6 +232,7 @@ export class FakeApiClient implements IApiClient {
|
||||
readonly llm: IApiClient['llm'] = {
|
||||
providers: payload => this.record('llm.providers', payload, Promise.resolve(ok({ providers: [] }))),
|
||||
models: payload => this.record('llm.models', payload, Promise.resolve(ok({ groups: [], failures: [] }))),
|
||||
discoverModels: payload => this.record('llm.discoverModels', payload, Promise.resolve(ok({ models: [] }))),
|
||||
}
|
||||
|
||||
/** When true, streams never fire onOpen (misbehaving-carrier material for the handshake timeout guard). */
|
||||
|
||||
Reference in New Issue
Block a user