/** * ConversationController — the bridge between the widget UI and the * `@smooai/smooth-operator` protocol client. * * This is the piece that was rewired: the original smooai widget spoke to * `@smooai/realtime`; here every protocol action goes through {@link SmoothAgentClient}. * The wire shapes are identical (the protocol was lifted from `@smooai/realtime`), * so the swap is purely at the client-library boundary. * * Flow: * 1. `connect()` → opens the WebSocket transport and `create_conversation_session`. * 2. `send(text)` → `send_message`, streaming `stream_token` deltas into the * in-progress assistant message, then the terminal * `eventual_response`. * * The controller is UI-agnostic: it emits typed events and the view renders them. */ import { type SmoothAgentClientOptions } from '../client.js'; import type { Citation } from '../types.js'; import type { ChatWidgetConfig } from './config.js'; export type { Citation }; export type Role = 'user' | 'assistant'; /** * A chat-native interactive prompt rendered as buttons inside an assistant * bubble. Backs SEP `ui/confirm` and `ui/select`: the agent (or an extension it * hosts) pauses the turn asking for a choice, and the click flows back as an * action frame that resumes the same turn. * * `confirm` projects the protocol's `write_confirmation_required` event (Yes/No * → `confirm_tool_action`); `select` projects a `ui/select` (one option → its * value). Each option's `value` is what the resume action carries. */ export interface ChatPrompt { /** The turn's `requestId` — the resume action must echo it. */ requestId: string; kind: 'confirm' | 'select'; /** Human-readable prompt text (the confirmation description / select prompt). */ text: string; /** The buttons. For `confirm`: Yes (`true`) / No (`false`). */ options: Array<{ label: string; value: string | boolean; }>; /** Set once the user picks — disables the buttons and records the choice. */ answered?: string; } export interface ChatMessage { id: string; role: Role; /** Accumulated text (assistant messages grow as tokens stream in). */ text: string; /** True while an assistant message is still streaming. */ streaming: boolean; /** * Sources that grounded an assistant answer, when the terminal * `eventual_response` carried any. Optional + back-compatible: absent when * the turn used no knowledge sources (or for user messages). Read * defensively off the terminal event — see {@link extractCitations}. */ citations?: Citation[]; /** * A pending (or answered) chat-native button prompt. Present only on the * bubble that carries a `write_confirmation_required` / `ui/select` request. */ prompt?: ChatPrompt; } export type ConnectionStatus = 'idle' | 'connecting' | 'ready' | 'error' | 'closed'; export interface ConversationEvents { /** Fired whenever the message list changes (append, token delta, finalize). */ onMessages: (messages: ChatMessage[]) => void; /** Fired on connection-status transitions. */ onStatus: (status: ConnectionStatus, detail?: string) => void; } export declare class ConversationController { private readonly config; private readonly events; private client; private sessionId; private readonly messages; private status; private seq; /** * requestId → resolver for a pending button prompt. The turn loop parks on * the promise; {@link answerPrompt} (called by the view on a button click) * resolves it and the loop resumes consuming the (now un-paused) stream. */ private readonly pendingPrompts; /** Extra client options — a test seam for injecting a mock transport. */ private readonly clientOptions?; constructor(config: ChatWidgetConfig, events: ConversationEvents, clientOptions?: Partial); get connectionStatus(): ConnectionStatus; private nextId; private setStatus; private emitMessages; /** Open the transport and create a conversation session. Idempotent. */ connect(): Promise; /** * Submit a user message. Appends the user bubble immediately, then streams the * assistant reply token-by-token, finalizing on `eventual_response`. */ send(text: string): Promise; /** * Attach a pending `prompt` to the current assistant bubble and return a * promise that resolves when the user clicks a button (via * {@link answerPrompt}). The bubble carrying the prompt is the last * assistant message. */ private awaitPromptChoice; /** * Resolve a pending button prompt — called by the view when the user clicks * an option. Marks the prompt answered (buttons disable, choice sticks) and * unblocks the turn loop. No-op if the prompt was already answered. */ answerPrompt(requestId: string, value: string | boolean): void; /** Tear down the underlying client. */ disconnect(): void; } //# sourceMappingURL=conversation.d.ts.map