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.
78 lines
3.4 KiB
TypeScript
78 lines
3.4 KiB
TypeScript
/**
|
|
* RPC method registry and signature-derived generics. The map
|
|
* registers only client-request methods (respond is a client-response, so it is absent);
|
|
* map keys are the wire path segments (POST /api/session.list).
|
|
*/
|
|
|
|
import type { SessionsApi } from './sessions.ts'
|
|
import type { HostApi } from './host.ts'
|
|
import type { WorkspaceApi } from './workspace.ts'
|
|
import type { CommandsApi } from './commands.ts'
|
|
import type { SkillsApi } from './skills.ts'
|
|
import type { GoalsApi } from './goals.ts'
|
|
import type { SettingsApi } from './settings.ts'
|
|
import type { CredentialsApi } from './credentials.ts'
|
|
import type { LlmApi } from './llm.ts'
|
|
import type { SubagentsApi } from './subagents.ts'
|
|
import type { RpcResponse } from './rpc.ts'
|
|
|
|
/**
|
|
* Method name → method signature. Signatures are the single source of truth; payload/value
|
|
* types are always derived from here. A method may declare a trailing AbortSignal after the
|
|
* request (command.execute): the carrier passes its request signal, never a wire field.
|
|
*/
|
|
export interface RpcMethodMap {
|
|
'session.list': SessionsApi['list']
|
|
'session.search': SessionsApi['search']
|
|
'session.create': SessionsApi['create']
|
|
'session.history': SessionsApi['history']
|
|
'session.models': SessionsApi['models']
|
|
'session.selectModel': SessionsApi['selectModel']
|
|
'session.rename': SessionsApi['rename']
|
|
'session.fork': SessionsApi['fork']
|
|
'session.prompt': SessionsApi['prompt']
|
|
'session.updateQueue': SessionsApi['updateQueue']
|
|
'session.cancel': SessionsApi['cancel']
|
|
'subagent.list': SubagentsApi['list']
|
|
'subagent.history': SubagentsApi['history']
|
|
'subagent.prompt': SubagentsApi['prompt']
|
|
'host.describe': HostApi['describe']
|
|
'host.pickDirectory': HostApi['pickDirectory']
|
|
'host.listDirectory': HostApi['listDirectory']
|
|
'host.createDirectory': HostApi['createDirectory']
|
|
'host.openPath': HostApi['openPath']
|
|
'workspace.list': WorkspaceApi['list']
|
|
'workspace.create': WorkspaceApi['create']
|
|
'workspace.rename': WorkspaceApi['rename']
|
|
'workspace.delete': WorkspaceApi['delete']
|
|
'workspace.insertSessionBefore': WorkspaceApi['insertSessionBefore']
|
|
'workspace.archiveSession': WorkspaceApi['archiveSession']
|
|
'command.list': CommandsApi['list']
|
|
'command.execute': CommandsApi['execute']
|
|
'skill.list': SkillsApi['list']
|
|
'goal.create': GoalsApi['create']
|
|
'goal.edit': GoalsApi['edit']
|
|
'goal.pause': GoalsApi['pause']
|
|
'goal.resume': GoalsApi['resume']
|
|
'goal.complete': GoalsApi['complete']
|
|
'goal.clear': GoalsApi['clear']
|
|
'settings.describe': SettingsApi['describe']
|
|
'settings.openDocument': SettingsApi['openDocument']
|
|
'settings.update': SettingsApi['update']
|
|
'settings.replace': SettingsApi['replace']
|
|
'settings.mutate': SettingsApi['mutate']
|
|
'credentials.describe': CredentialsApi['describe']
|
|
'credentials.set': CredentialsApi['set']
|
|
'credentials.unset': CredentialsApi['unset']
|
|
'llm.providers': LlmApi['providers']
|
|
'llm.models': LlmApi['models']
|
|
'llm.discoverModels': LlmApi['discoverModels']
|
|
}
|
|
|
|
/** Business request payload of method K (reaches through the RpcRequest narrow form to payload). */
|
|
export type RequestPayload<K extends keyof RpcMethodMap> = Parameters<RpcMethodMap[K]>[0]['payload']
|
|
|
|
/** Business return value of method K (reaches through the RpcResponse narrow form to infer the ok value of result). */
|
|
export type ResponseValue<K extends keyof RpcMethodMap> =
|
|
Awaited<ReturnType<RpcMethodMap[K]>> extends RpcResponse<infer T> ? T : never
|