mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
llm.discoverModels was reachable from any declared trusted host. The method takes a caller-supplied baseURL and makes the host issue a GET to it, then reports the status or the parsed body — so on a LAN deployment an anonymous caller had a probe for whatever the host can reach and the browser cannot, plus a path that carries a draft credential. The PRIVILEGED_METHODS doc already states the rule this broke: trustedHosts is a DNS-rebinding fence, not authentication, so the configuration plane stays loopback-same-origin. It is in that set now, asserted both against the hand-built fence and over real HTTP beside the catalog reads that deliberately stay reachable. supportsDiscovery and listModelDiscoveryNamespaces are gone. The field was required on the wire and read by nobody: its own contract said a surface should offer the action "instead of naming an adapter family it would have to hardcode", while the surface hardcodes llm-pi-ai in two places and gates the button on whether there is anything to probe. Its shape did not fit the second caller either — the create card has no row to read a per-row field from. Keeping a required field alive for a consumer that may never arrive costs every producer and fixture a value nobody consults, which is exactly how the fixtures drifted. The registry that fed it had no other production consumer, so registration and disposal are now observed through the offer itself. The Agent Note claimed the key is never logged, which the wire schema beside it already contradicts, and predated both the provider field and the catalog-answer path. The two new public types pointed at core.md without a type-equiv block or manifest entry, so the generated service catalog named documentation that did not exist.
83 lines
3.5 KiB
TypeScript
83 lines
3.5 KiB
TypeScript
/**
|
|
* llm domain contract: host-scoped provider topology for configuration
|
|
* surfaces. `llm.providers` merges the configurable-provider directory
|
|
* (which providers CAN be configured, and where their settings live) with the
|
|
* live route registry; `llm.models` is the session-independent model catalog
|
|
* (the same groups as `session.models`, without the per-session current
|
|
* target). Both invalidate on the `host/models-changed` frame.
|
|
*/
|
|
|
|
import type { RpcRequest, RpcResponse } from './rpc.ts'
|
|
import type { ModelCatalogFailure, ModelProviderGroup } from './sessions.ts'
|
|
|
|
/** Wire view of one configurable provider. */
|
|
export interface ConfigurableProviderView {
|
|
/** Provider route key (`deepseek-official`, `openai`, …). */
|
|
provider: string
|
|
/** Human-readable name for configuration surfaces. */
|
|
displayName: string
|
|
/** Settings namespace whose section configures this provider. */
|
|
settingsNs: string
|
|
/** Path from that section's root to the provider's profile object (empty = whole section). */
|
|
settingsPath: string[]
|
|
/** Whether the route is currently registered (its models are requestable). */
|
|
active: boolean
|
|
}
|
|
|
|
/** Llm-domain unary methods (the map keys llm.* of RpcMethodMap). */
|
|
export interface LlmApi {
|
|
/**
|
|
* List every configurable provider with its live/dormant state, in
|
|
* directory declaration order. Routes registered outside the directory
|
|
* (an adapter that never declared configurability) are appended with their
|
|
* registration identity and no settings address.
|
|
*/
|
|
providers(request: RpcRequest<{}>): Promise<RpcResponse<{ providers: ConfigurableProviderView[] }>>
|
|
|
|
/**
|
|
* Host-scoped model catalog over every registered provider route: the
|
|
* settings surface's models view, needing no session. Per-provider listing
|
|
* failures ride `failures` without failing the sound groups.
|
|
*/
|
|
models(request: RpcRequest<{}>): Promise<RpcResponse<{ groups: ModelProviderGroup[]; failures: ModelCatalogFailure[] }>>
|
|
|
|
/**
|
|
* Interrogate a provider endpoint the configuration surface is still
|
|
* 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 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
|
|
provider?: string
|
|
baseURL?: string
|
|
api?: string
|
|
apiKey?: string
|
|
}>,
|
|
signal?: AbortSignal,
|
|
): Promise<RpcResponse<{ models: DiscoveredModelView[] }>>
|
|
}
|
|
|
|
/** Wire view of one model an interrogated endpoint advertises. */
|
|
export interface DiscoveredModelView {
|
|
/** Model id the endpoint accepts. */
|
|
id: string
|
|
/** Human-readable name when the endpoint supplies one. */
|
|
name?: string
|
|
/** Maximum combined request and response context, when disclosed. */
|
|
contextWindow?: number
|
|
/** Maximum output tokens, when disclosed. */
|
|
maxTokens?: number
|
|
}
|