mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(llm): answer a catalog route's models from pi-ai's own registry
Clicking "fetch available models" on a built-in provider went to the network. That is the wrong source: pi-ai's registry is the authoritative list for its own providers, and it carries the context windows and output caps a `GET /models` listing does not disclose. Asking api.deepseek.com what DeepSeek serves is both slower and worse, and against an endpoint that answers a different shape it failed outright. Interrogation is still keyed by settings namespace — the provider being added has no route — but the request may now name the route it is editing. An adapter that already describes that route answers from what it knows, needs no endpoint at all, and never touches the network; only a route the catalog does not describe reaches the wire, and one naming no endpoint is told to set one or enter its models by hand. `ConfigurableProviderView` gained `supportsDiscovery` so a surface offers the action where a namespace can answer instead of hardcoding an adapter family. Three narrower corrections ride along. Discovery no longer claims Azure or Codex: Azure authenticates with an `api-key` header and an `api-version` query despite its OpenAI lineage, and Codex uses OAuth, so both reported an authentication failure as a provider with no models. Cancellation during the body read escaped as the raw abort reason rather than a coded ABORTED. And the schema comment claiming the probe key is never logged overstated it: the host neither stores nor returns it, but it rides the client's outgoing envelope like every other secret-bearing payload, and redacting that tap is a configuration-plane-wide change.
This commit is contained in:
@@ -16,6 +16,7 @@ export const configurableProviderViewSchema = z.object({
|
||||
settingsNs: z.string(),
|
||||
settingsPath: z.array(z.string()),
|
||||
active: z.boolean(),
|
||||
supportsDiscovery: z.boolean(),
|
||||
}) satisfies z.ZodType<Wire<ConfigurableProviderView>>
|
||||
|
||||
/** llm.providers request payload. */
|
||||
@@ -46,11 +47,14 @@ export const discoveredModelViewSchema = z.object({
|
||||
/** llm.discoverModels request payload. */
|
||||
export const llmDiscoverModelsRequestSchema = z.object({
|
||||
settingsNs: z.string().min(1),
|
||||
baseURL: z.string().min(1),
|
||||
provider: z.string().min(1).optional(),
|
||||
baseURL: z.string().min(1).optional(),
|
||||
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.
|
||||
// Write-only at the host: used for this one interrogation, never stored and
|
||||
// never returned. It does ride the client's outgoing envelope like every
|
||||
// other secret-bearing payload (`credentials.set`, `settings.update`), which
|
||||
// `subscribeEnvelopes()` observers can see — redacting that tap is a
|
||||
// configuration-plane-wide change, not this method's to make alone.
|
||||
apiKey: z.string().min(1).optional(),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'llm.discoverModels'>>>
|
||||
|
||||
|
||||
@@ -22,6 +22,12 @@ export interface ConfigurableProviderView {
|
||||
settingsPath: string[]
|
||||
/** Whether the route is currently registered (its models are requestable). */
|
||||
active: boolean
|
||||
/**
|
||||
* Whether `llm.discoverModels` can answer for this entry's namespace. A
|
||||
* surface offers the action only where it works instead of naming an adapter
|
||||
* family it would have to hardcode.
|
||||
*/
|
||||
supportsDiscovery: boolean
|
||||
}
|
||||
|
||||
/** Llm-domain unary methods (the map keys llm.* of RpcMethodMap). */
|
||||
@@ -46,17 +52,22 @@ export interface LlmApi {
|
||||
* drafting, and return the models it advertises for the user to adopt.
|
||||
*
|
||||
* The payload is the draft, not a stored route: `settingsNs` selects the
|
||||
* adapter family that knows how to read the listing, and the endpoint,
|
||||
* protocol, and key come from the form. Nothing is written — the reply is
|
||||
* candidates, and only a later `settings.mutate` decides what a route
|
||||
* serves. `apiKey` is therefore accepted here but never stored, logged, or
|
||||
* echoed back; a provider whose key is already stored omits it and the
|
||||
* endpoint answers unauthenticated or refuses.
|
||||
* adapter family that answers, and the rest comes from the form. `provider`
|
||||
* names the route being edited when there is one — an adapter that already
|
||||
* describes that route answers from its own registry, with better metadata
|
||||
* and no network call, and needs no endpoint. A route it does not describe is
|
||||
* asked over the wire, which is what `baseURL`, `api`, and `apiKey` are for.
|
||||
*
|
||||
* Nothing is written — the reply is candidates, and only a later
|
||||
* `settings.mutate` decides what a route serves. `apiKey` is accepted here
|
||||
* but never stored or returned; a provider whose key is already stored omits
|
||||
* it and the endpoint answers unauthenticated or refuses.
|
||||
*/
|
||||
discoverModels(
|
||||
request: RpcRequest<{
|
||||
settingsNs: string
|
||||
baseURL: string
|
||||
provider?: string
|
||||
baseURL?: string
|
||||
api?: string
|
||||
apiKey?: string
|
||||
}>,
|
||||
|
||||
@@ -55,7 +55,7 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
|
||||
z.object({ code: z.literal('settings-not-exposed'), message: z.string(), details: z.object({ ns: z.string() }) }),
|
||||
z.object({ code: z.literal('settings-conflict'), message: z.string(), details: z.object({ ns: z.string(), expected: z.number(), actual: z.number() }) }),
|
||||
z.object({ code: z.literal('credential-rejected'), message: z.string(), details: z.object({ ref: z.string() }) }),
|
||||
z.object({ code: z.literal('model-discovery-failed'), message: z.string(), details: z.object({ settingsNs: z.string(), baseURL: z.string() }) }),
|
||||
z.object({ code: z.literal('model-discovery-failed'), message: z.string(), details: z.object({ settingsNs: z.string(), baseURL: z.string().optional() }) }),
|
||||
z.object({ code: z.literal('title-invalid'), message: z.string(), details: z.object({ sessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('fork-unavailable'), message: z.string(), details: z.object({ sessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('subagent-parent-unavailable'), message: z.string(), details: z.object({ parentSessionId: z.string() }) }),
|
||||
|
||||
@@ -78,7 +78,7 @@ export interface RpcErrorDetailsMap {
|
||||
* it is what the form shows before falling back to hand-entry — and the
|
||||
* details name the endpoint asked, never the credential offered.
|
||||
*/
|
||||
'model-discovery-failed': { settingsNs: string; baseURL: string }
|
||||
'model-discovery-failed': { settingsNs: string; baseURL?: string }
|
||||
'title-invalid': { sessionId: SessionId }
|
||||
'fork-unavailable': { sessionId: SessionId }
|
||||
'subagent-parent-unavailable': { parentSessionId: SessionId }
|
||||
|
||||
Reference in New Issue
Block a user