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:
@@ -6,7 +6,7 @@
|
||||
import { z } from 'zod'
|
||||
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
|
||||
import type { Wire } from './rpc.schema.ts'
|
||||
import type { ConfigurableProviderView } from './llm.ts'
|
||||
import type { ConfigurableProviderView, DiscoveredModelView } from './llm.ts'
|
||||
import { modelCatalogFailureSchema, modelProviderGroupSchema } from './sessions.schema.ts'
|
||||
|
||||
/** ConfigurableProviderView row of llm.providers. */
|
||||
@@ -34,3 +34,27 @@ export const llmModelsValueSchema = z.object({
|
||||
groups: z.array(modelProviderGroupSchema),
|
||||
failures: z.array(modelCatalogFailureSchema),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'llm.models'>>>
|
||||
|
||||
/** DiscoveredModelView row of llm.discoverModels. */
|
||||
export const discoveredModelViewSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1).optional(),
|
||||
contextWindow: z.number().int().positive().optional(),
|
||||
maxTokens: z.number().int().positive().optional(),
|
||||
}) satisfies z.ZodType<Wire<DiscoveredModelView>>
|
||||
|
||||
/** llm.discoverModels request payload. */
|
||||
export const llmDiscoverModelsRequestSchema = z.object({
|
||||
settingsNs: z.string().min(1),
|
||||
baseURL: z.string().min(1),
|
||||
api: z.string().min(1).optional(),
|
||||
// Write-only: the host uses it for this one interrogation and never stores,
|
||||
// logs, or returns it. Kept out of any redacted echo for the same reason
|
||||
// `credentials.set` never reads a value back.
|
||||
apiKey: z.string().min(1).optional(),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'llm.discoverModels'>>>
|
||||
|
||||
/** llm.discoverModels response value. */
|
||||
export const llmDiscoverModelsValueSchema = z.object({
|
||||
models: z.array(discoveredModelViewSchema),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'llm.discoverModels'>>>
|
||||
|
||||
Reference in New Issue
Block a user