import type { SessionId, TurnId } from '../ids/index.js'; import type { LLMToolSchema } from '../tool/index.js'; /** * A conversation with no turn boundary. * * Every other seam in this kernel is turn-based by construction: a turn * has iterations, an iteration sends a complete message list and reads a * stream back, and a checkpoint is taken between two of them. That shape * is load-bearing everywhere it appears and it cannot describe a duplex * session, where input keeps arriving while output is still being * produced and "the turn" is not a thing either side can point at. * * So this is a second contract rather than a widening of the first. The * alternative — bending `chatStream` until it accepts a live input * channel — would put a half-duplex assumption inside every consumer of * the turn-based path in exchange for a duplex path that still would not * fit. * * What is here: the driver contract, the session loop that runs tools * against it, and a scripted driver so both are exercised. What is NOT * here, and deliberately: audio capture and playback, which belong to * whatever owns the microphone, and checkpoint/resume, which has no * meaning yet for a session whose state lives on the far side of a * socket. */ export type BidiInput = { readonly type: 'text'; readonly text: string; } | { readonly type: 'audio'; readonly data: string; readonly mediaType: string; }; /** * What the far side reports. * * `interrupted` is the event that makes this contract different from the * turn-based one. In a duplex session the human can speak over the model, * and everything the model was in the middle of — text it was emitting, a * tool it just asked for — is now answering a question nobody is asking. * A driver that cannot detect that never emits it and the loop behaves as * if the model always finished what it started. */ export type BidiEvent = { readonly type: 'text'; readonly text: string; } | { readonly type: 'audio'; readonly data: string; readonly mediaType: string; } | { readonly type: 'tool_call'; /** Unique for the complete session; a duplicate is a protocol failure. */ readonly id: string; readonly name: string; /** JSON, as the model produced it. */ readonly arguments: string; } /** The model finished a stretch of output and is waiting. */ | { readonly type: 'turn_complete'; } /** * The human spoke over the model; work that has not started publishing a * result is now stale. This is not run cancellation: tool code keeps its * live signal so an irreversible side effect is not stopped halfway. */ | { readonly type: 'interrupted'; } | { readonly type: 'error'; readonly message: string; } | { readonly type: 'closed'; readonly reason?: string; }; export interface BidiConnectParams { readonly model: string; readonly system?: string; readonly tools?: readonly LLMToolSchema[]; /** * Lifetime of the host-owned run. A provider uses it for connection and * ongoing transport work; the host also invokes `close()` when it aborts. */ readonly signal?: AbortSignal; } export interface BidiSession { /** Push input from the human. Never waits for the model to be idle. */ send(input: BidiInput): Promise; /** * Answer a `tool_call`. * * Separate from {@link send} because it is not input from the human: * a driver has to attach it to the call it answers, and a session that * received it as ordinary input would have to guess. * * Atomically publish one result. Entering this call is the commit point: * a later conversational interruption cannot recall it. Resolve only after * the provider accepted the result; reject when it did not. */ sendToolResult(id: string, output: string, isError?: boolean): Promise; events(): AsyncIterable; /** Stop the event stream and release provider resources. Called at most once. */ close(): Promise; } export interface BidiProvider { readonly id: string; connect(params: BidiConnectParams): Promise; } /** What the loop reports back to whoever is driving it. */ export type BidiTurnEvent = { readonly type: 'text'; readonly sessionId: SessionId; readonly turnId: TurnId; readonly text: string; } | { readonly type: 'audio'; readonly sessionId: SessionId; readonly turnId: TurnId; readonly data: string; readonly mediaType: string; } | { readonly type: 'tool_started'; readonly sessionId: SessionId; readonly turnId: TurnId; readonly toolUseId: string; readonly toolName: string; } | { readonly type: 'tool_completed'; readonly sessionId: SessionId; readonly turnId: TurnId; readonly toolUseId: string; readonly toolName: string; readonly output: string; readonly isError: boolean; } /** * A tool's answer was thrown away because the human interrupted before * publication began. The operation itself may have run to completion. */ | { readonly type: 'tool_abandoned'; readonly sessionId: SessionId; readonly turnId: TurnId; readonly toolUseId: string; readonly toolName: string; } | { readonly type: 'turn_complete'; readonly sessionId: SessionId; readonly turnId: TurnId; } | { readonly type: 'interrupted'; readonly sessionId: SessionId; readonly turnId: TurnId; } | { readonly type: 'error'; readonly sessionId: SessionId; readonly turnId: TurnId; readonly message: string; } | { readonly type: 'closed'; readonly sessionId: SessionId; readonly turnId: TurnId; readonly reason?: string; }; //# sourceMappingURL=index.d.ts.map