import type { ReactNode } from 'react'; import type { OpenAiToolDefinition } from './openai'; // ── Capability ── /** * A tool a capability contributes: the raw OpenAI definition plus its * client-side implementation. Tools execute directly and feed their result * back to the model; anything that must be gated behind user approval is a * proposal (see AiCapability.proposal), never a tool. */ export interface AiToolContribution { definition: OpenAiToolDefinition; /** Method signature so a capability may type `args` as its own tool's shape (bivariant parameter). */ execute(this: void, args: unknown): unknown; } /** * One AI function, whole and self-contained: what it is for, what it can see, * what it can do, and what it runs on. The component that owns the data builds * it (memoized, reading live state through refs) and hands it to the * `UdpAiAssistant` that renders its chat. Nothing is shared between * capabilities — an app-wide assistant, if ever wanted, is simply another * capability whose tools may delegate to others. * * The `id` is the unit the server will switch on and off per tenant, product * and environment. */ export interface AiCapability { /** Stable identifier, e.g. "tree-builder:editor" — the server's function id. */ id: string; /** * The system prompt: what this function is for and how to use its tools * well. Keep it stable — it is the cacheable prefix. Live state goes in * `context()`. */ instructions: string; /** * Called on every request — the live state this function wants the model * to see (selection, what is on screen, today's date). It is sent with the * incoming user message, after the history, so it may change freely * without invalidating the cached prefix. */ context?: () => string; tools?: AiToolContribution[]; /** * The function's one write path: propose → validate → Apply card. Every * write goes through it; tools never mutate anything. */ proposal?: AiProposalConfig; /** * The models this function runs on, by gateway id. Intersected with what the * gateway allows the tenant; if none remain, the surface renders nothing — * a function tuned for certain models should not quietly run on others. * Omit to accept whatever the gateway offers. */ models?: string[]; /** The model to start on when the tenant has it; otherwise the first allowed model. */ preferredModel?: string; /** Extra OpenAI request-body params merged into every completion — pass-through. */ requestParams?: Record; } // ── Tool registration ── /** * A tool the model may call. `definition` is the raw OpenAI tool JSON — * passed through to the gateway untouched. * * Exactly one behavior applies per tool: * - `execute`: run client-side; the return value is JSON-stringified back to * the model as the tool result and the loop continues (read-only lookups). * - `respond`: terminal; the returned string is shown as the assistant's * message and the turn ends (e.g. an ask_clarification tool). * * The proposal tool (see AiProposalConfig) is registered separately. */ export interface AiAssistantTool { definition: OpenAiToolDefinition; // Method signatures, not function-typed properties: their parameters stay // bivariant under strictFunctionTypes, so a consumer may type `args` as its // own tool's argument shape. execute?(this: void, args: unknown): unknown; respond?(this: void, args: unknown): string; } // ── Proposal (copilot) mode ── export interface AiProposalDetail { /** UdpIcon name shown next to the line (optional) */ icon?: string; text: string; } /** What `build` produces from validated tool arguments. */ export interface AiProposalResult { /** Short explanation shown at the top of the proposal card */ text: string; /** Human-readable change lines rendered on the card */ details: AiProposalDetail[]; /** Opaque payload handed back to `onApply` */ payload: TProposal; } export interface AiProposalConfig { /** Raw OpenAI tool definition for the propose tool — pass-through */ definition: OpenAiToolDefinition; /** * Validate the parsed tool arguments. Return error strings to trigger a * repair round (errors are sent back to the model once, then surfaced). * Empty array / undefined = valid. */ validate?(this: void, args: unknown): string[]; /** * Turn validated arguments into a user-facing proposal. Return null when * the arguments change nothing — the explanation is shown as plain text * instead of a card. Read current app state from your own closure/refs; * this is called at proposal time, not apply time. */ build(this: void, args: unknown): AiProposalResult | null; /** * Apply the proposal to the app. Optionally return an undo function — * when present, applied cards get an Undo button. */ onApply: (payload: TProposal) => void | (() => void); /** Custom card body; default renders text + detail lines */ renderDetails?: (proposal: AiProposalResult) => ReactNode; /** * Apply proposals immediately instead of waiting for the user's Apply click * — the card renders as already applied, with Undo available. Reserve for * reversible view-state changes (e.g. grid filters/sorts); * persisted or business-consequential changes must keep the explicit gate. * Proposals whose contextKey no longer matches are never auto-applied. */ autoApply?: boolean; } // ── Chat state ── export type ProposalStatus = 'pending' | 'applied' | 'dismissed' | 'undone'; export interface AiChatItem { id: number; role: 'user' | 'assistant'; text?: string; proposal?: AiProposalResult; /** Tool name of the proposal flow that produced it — routes apply/undo */ proposalToolName?: string; /** contextKey captured when the proposal was created */ proposalContextKey?: string; proposalStatus?: ProposalStatus; isError?: boolean; /** Renders as a centered divider (e.g. context-switch notice) instead of a bubble */ isNotice?: boolean; } // ── Engine limits ── export interface AiAssistantLimits { /** Repair rounds after a failed validation (default 1) */ maxRepairRounds?: number; /** Cap on model round-trips per user message (default 6) */ maxToolRounds?: number; /** * Turns kept as history, a turn being one user message plus everything the * model did in reply (default 10). Oldest turns are dropped whole; the first * time that happens a notice appears in the chat. */ maxHistoryTurns?: number; } //# sourceMappingURL=types.d.ts.map