/** * TypeScript consumer SDK types for the public api-store LLM endpoint * (feature 472 — public/private LLM proxy routing). * * These types intentionally cover ONLY the surface introduced by feature 472: * the `LlmRoute` enum, the `route` / `executionRoute` / `viaNodeId` fields, and * the corresponding additions to the four LLM request/response shapes. They * are exported so applications consuming the api-store endpoint can stay * type-safe end-to-end. * * The wire form follows the OpenAPI contract at * `specs/472-public-private-llm-proxy/contracts/public_api_store_llm.yaml`: * * request: { …, "route_mode": "auto" | "public" | "private" } * response: { …, "execution_route": "public" | "private", "via_node_id": "…" } * * Existing snake_case fields are preserved; in TypeScript-land we map them to * the snake_case names used over the wire (no auto-camelCasing in the SDK). * * **Honest framing** (security finding S5 / U4, surfaced via JSDoc on * `LlmRoute`): Private mode isolates the upstream provider's API credentials * on the peer's host and routes egress through a separate device; it does NOT * mask prompt contents from the upstream provider or anonymize the user. */ /** * Consumer-expressed routing intent for an LLM call. * * - `"public"` — force direct provider execution; skip the custodial-hardware * hop even on peers that have it configured. * - `"private"` — force routing through the custodial-hardware path. Returns * a typed `PrivateUnavailableError` when no hardware path is available on * the selected executor. * - `"auto"` — default; pick the more-isolated path when available, never * hard-fail because of routing. * * **Honest framing**: Private mode isolates the upstream provider's API * credentials on the peer's host and routes egress through a separate device; * it does NOT mask prompt contents from the upstream provider or anonymize * the user. */ export type LlmRoute = "auto" | "public" | "private"; /** * Truthful echo of what actually executed the call. Never `"auto"` — the * service resolves auto-intent at the endpoint layer and reports the * concrete path used in the response. */ export type ExecutionRoute = "public" | "private"; /** * Reason the peer rejected a `route_mode=private` request. Surfaced via * `PrivateUnavailableError.reason` in `./errors.ts`. */ export type PrivateUnavailableReason = "no_esp32_endpoint" | "firmware_upgrade_required" | "peer_offline" | "platform_not_enabled"; /** * Local-vs-network preference. Composes with `route` per the Routing Truth * Table in `data-model.md §4`. */ export type ExecutionPreference = "prefer_local" | "local_only" | "network_only"; /** * Shared shape carried by every public api-store LLM call/stream request. * Applications wrap their own input shape around this and submit it via * `ApiStoreClient` — see `./ApiStoreClient.ts`. * * **JSDoc honest-framing reminder**: `route: "private"` does NOT mask prompt * contents from the upstream provider — it only changes which device's * credentials issue the egress call. Document this in your application UI. */ export interface ExecuteChatRequest { /** The prompt or chat-style messages. Application-specific shape. */ messages: Array<{ role: string; content: string; }>; /** Platform name, e.g. "OpenAI", "Anthropic". */ platform?: string; /** Model name, e.g. "gpt-4o", "claude-3-5-sonnet-20240620". */ model?: string; /** * Consumer-expressed routing intent. Omit (or set to `"auto"`) to keep the * default behaviour — the peer picks the more-isolated path when available * and falls back to public when not. * * Setting `"private"` MAY return a `PrivateUnavailableError` when the * selected executor has no custodial-hardware path; check * `.fallback_available` on the error and decide whether to retry with * `route: "auto"`. * * **Honest framing**: Private mode isolates the upstream provider's API * credentials on the peer's host and routes egress through a separate * device; it does NOT mask prompt contents from the upstream provider or * anonymize the user. */ route?: LlmRoute; /** * Local-vs-network executor preference. Composes with `route` per the * Routing Truth Table. */ execution_preference?: ExecutionPreference; /** Correlation id for logs / audit (allowlisted log field per S4). */ correlation_id?: string; /** Optional cap on output tokens. */ max_output_tokens?: number; /** Optional tool specs (OpenAI-style). Opaque to the SDK. */ tools?: unknown; /** Optional tool_choice directive. Opaque to the SDK. */ tool_choice?: unknown; /** Sampling temperature. */ temperature?: number; /** Optional response_format (e.g., `json_schema`). Opaque to the SDK. */ response_format?: unknown; } /** Streaming variant of [[ExecuteChatRequest]] — structurally identical. */ export type StreamChatRequest = ExecuteChatRequest; /** "best-model" variant — model resolution happens server-side. */ export interface ExecuteBestModelRequest extends Omit { } /** Single-message execute variant. */ export interface ExecuteRequest extends ExecuteChatRequest { } /** * Common echo fields appended to every successful public api-store LLM * response. Carries the truthful execution path so the consumer can log it, * retry under different intent, or shape downstream behaviour. */ export interface RouteExecutionEcho { /** * Truthful echo of which path actually executed the call. Always * `"public"` or `"private"`, never `"auto"`. */ execution_route: ExecutionRoute; /** Identifier of the node that executed the call (self or peer). */ via_node_id: string; } /** Unary call response. */ export interface ExecuteChatResponse extends RouteExecutionEcho { response_text: string; platform: string; model: string; prompt_tokens: number; completion_tokens: number; cost_sats: number; correlation_id?: string; } /** Streaming chunk. The final chunk MUST carry `RouteExecutionEcho`. */ export type StreamChatChunk = { type: "delta"; text: string; } | ({ type: "end"; } & RouteExecutionEcho & { prompt_tokens: number; completion_tokens: number; cost_sats: number; correlation_id?: string; }) | { type: "error"; code: string; message: string; };