@pipedog/openai 0.0.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/NOTICE +9 -0
- package/dist/_constants.d.ts +4 -0
- package/dist/_constants.js +9 -0
- package/dist/base_llm.d.ts +272 -0
- package/dist/base_llm.js +619 -0
- package/dist/image.d.ts +116 -0
- package/dist/image.js +179 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/live/events.d.ts +768 -0
- package/dist/live/events.js +1020 -0
- package/dist/live/index.d.ts +2 -0
- package/dist/live/index.js +8 -0
- package/dist/live/llm.d.ts +566 -0
- package/dist/live/llm.js +1652 -0
- package/dist/llm.d.ts +30 -0
- package/dist/llm.js +50 -0
- package/dist/open_ai_live_adapter.d.ts +94 -0
- package/dist/open_ai_live_adapter.js +192 -0
- package/dist/open_ai_realtime_adapter.d.ts +95 -0
- package/dist/open_ai_realtime_adapter.js +221 -0
- package/dist/open_ai_responses_adapter.d.ts +95 -0
- package/dist/open_ai_responses_adapter.js +358 -0
- package/dist/realtime/events.d.ts +1401 -0
- package/dist/realtime/events.js +1639 -0
- package/dist/realtime/index.d.ts +2 -0
- package/dist/realtime/index.js +8 -0
- package/dist/realtime/llm.d.ts +457 -0
- package/dist/realtime/llm.js +1550 -0
- package/dist/responses/index.d.ts +2 -0
- package/dist/responses/index.js +8 -0
- package/dist/responses/llm.d.ts +448 -0
- package/dist/responses/llm.js +1517 -0
- package/dist/stt.d.ts +409 -0
- package/dist/stt.js +741 -0
- package/dist/tts.d.ts +86 -0
- package/dist/tts.js +238 -0
- package/package.json +49 -3
- package/README.md +0 -5
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/services/openai/live/__init__.py
|
|
6
|
+
// Upstream's package __init__ is empty: import the submodules directly.
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Live LLM service: full-duplex speech-to-speech over WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Porting notes:
|
|
5
|
+
* - The WebSocket is opened with `websocketConnect` from core, which sends
|
|
6
|
+
* the `Authorization` header on Bun, Node and Deno. Runtimes whose
|
|
7
|
+
* `WebSocket` can't send headers (Cloudflare Workers) pass a
|
|
8
|
+
* `webSocketFactory` (an addition to the upstream options).
|
|
9
|
+
* - `parse_server_event` raises only `ValueError`, which the receive loop
|
|
10
|
+
* skips; here any (non-cancellation) error from `parseServerEvent` is
|
|
11
|
+
* treated as that.
|
|
12
|
+
* - `OpenAIResponsesReasoningConfig` is sent as its set fields (`effort`,
|
|
13
|
+
* `summary`, `mode`), which is what upstream's `model_dump(exclude_none=True)`
|
|
14
|
+
* produces.
|
|
15
|
+
*/
|
|
16
|
+
import { Event, Lock, type Task } from "@pipedog/asyncio";
|
|
17
|
+
import type { BaseLLMAdapter } from "@pipedog/core/adapters/base_llm_adapter";
|
|
18
|
+
import { type CancelFrame, type EndFrame, type Frame, FunctionCallCancelFrame, FunctionCallResultFrame, InputAudioRawFrame, LLMServiceMetadataFrame } from "@pipedog/core/frames/frames";
|
|
19
|
+
import type { LLMContext, LLMStandardMessage } from "@pipedog/core/processors/aggregators/llm_context";
|
|
20
|
+
import { FrameDirection, type FrameProcessorSetup } from "@pipedog/core/processors/frame_processor";
|
|
21
|
+
import { LLMService, type LLMServiceOptions, type LLMWithBackendRole } from "@pipedog/core/services/llm_service";
|
|
22
|
+
import { LLMSettings, type LLMSettingsOptions } from "@pipedog/core/services/settings";
|
|
23
|
+
import { type WebsocketClientConnection, type WebsocketConnectOptions } from "@pipedog/core/services/websocket_service";
|
|
24
|
+
import { type NotGiven } from "@pipedog/core/utils/types";
|
|
25
|
+
import { _BackendSession, BackendError, BackendIdle, BackendLLMWorker, BackendOutput, BackendToolCall } from "@pipedog/core/workers/llm/backend_llm_worker";
|
|
26
|
+
import { OpenAILiveLLMAdapter, type OpenAILiveLLMInvocationParams } from "../open_ai_live_adapter.ts";
|
|
27
|
+
import { type OpenAIResponsesLLMSettings } from "../responses/llm.ts";
|
|
28
|
+
import * as events from "./events.ts";
|
|
29
|
+
export declare const DEFAULT_MODEL = "gpt-live-1";
|
|
30
|
+
export declare const TURN_GAP_SECS = 0.8;
|
|
31
|
+
export declare const SESSION_CLOSE_TIMEOUT_SECS = 10;
|
|
32
|
+
/** How long a delegation waits for the session with the backend to open. */
|
|
33
|
+
export declare const BACKEND_ATTACH_TIMEOUT_SECS = 5;
|
|
34
|
+
export declare const MAX_CONTEXT_APPEND_TOKENS = 450;
|
|
35
|
+
export interface OpenAILiveLLMSettingsOptions extends LLMSettingsOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Output voice name (for example `marin` or `cedar`). `null` leaves the
|
|
38
|
+
* choice to the API. Cannot be changed once the session has started.
|
|
39
|
+
*/
|
|
40
|
+
voice?: string | null | NotGiven;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the API keeps the session, making it available for 30 days as a
|
|
43
|
+
* stereo recording from `GET /v1/live/sessions/{id}/content` and as a
|
|
44
|
+
* source a later session can fork from. `false` by default at the API, and
|
|
45
|
+
* requires a project whose data policy permits persistence. Cannot be
|
|
46
|
+
* changed once the session has started.
|
|
47
|
+
*/
|
|
48
|
+
store?: boolean | null | NotGiven;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Settings for OpenAILiveLLMService.
|
|
52
|
+
*
|
|
53
|
+
* - `voice`: output voice name (for example `marin` or `cedar`). `null`
|
|
54
|
+
* leaves the choice to the API. Cannot be changed once the session has
|
|
55
|
+
* started.
|
|
56
|
+
* - `store`: whether the API keeps the session, making it available for 30
|
|
57
|
+
* days as a stereo recording from `GET /v1/live/sessions/{id}/content` and
|
|
58
|
+
* as a source a later session can fork from. `false` by default at the API,
|
|
59
|
+
* and requires a project whose data policy permits persistence. Cannot be
|
|
60
|
+
* changed once the session has started.
|
|
61
|
+
*/
|
|
62
|
+
export declare class OpenAILiveLLMSettings extends LLMSettings {
|
|
63
|
+
voice: string | null | NotGiven;
|
|
64
|
+
store: boolean | null | NotGiven;
|
|
65
|
+
constructor(options?: OpenAILiveLLMSettingsOptions);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* One speaker's in-progress turn, assembled from timed transcript fragments.
|
|
69
|
+
*
|
|
70
|
+
* - `gapSecs`: quiet time that ends the turn.
|
|
71
|
+
* - `text`: fragments accumulated so far.
|
|
72
|
+
* - `open`: whether a turn is currently open.
|
|
73
|
+
* - `timer`: the task that closes the turn once `gapSecs` elapses.
|
|
74
|
+
* - `lock`: held while the turn opens or closes. The timer runs in its own
|
|
75
|
+
* task, so this is what keeps one turn's end frames from landing among the
|
|
76
|
+
* next turn's start frames.
|
|
77
|
+
*/
|
|
78
|
+
export declare class _TurnGrouper {
|
|
79
|
+
gapSecs: number;
|
|
80
|
+
text: string;
|
|
81
|
+
open: boolean;
|
|
82
|
+
timer: Task<unknown> | null;
|
|
83
|
+
lock: Lock;
|
|
84
|
+
constructor(options: {
|
|
85
|
+
gapSecs: number;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A client delegation in progress: what to correlate the backend's outputs with.
|
|
90
|
+
*
|
|
91
|
+
* - `id`: the delegation's id, which the appends carry.
|
|
92
|
+
* - `answered`: whether the backend has sent a spoken output for it yet; its
|
|
93
|
+
* notes do not count.
|
|
94
|
+
* - `timeoutTask`: the task that abandons it if the backend takes too long.
|
|
95
|
+
*/
|
|
96
|
+
export declare class _LiveDelegation {
|
|
97
|
+
id: string;
|
|
98
|
+
answered: boolean;
|
|
99
|
+
timeoutTask: Task<unknown> | null;
|
|
100
|
+
constructor(options: {
|
|
101
|
+
id: string;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export interface ResponsesDelegationOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Request settings for the backend model — the same object
|
|
107
|
+
* `OpenAIResponsesLLMService` takes. `model` is required.
|
|
108
|
+
*/
|
|
109
|
+
settings: OpenAIResponsesLLMSettings;
|
|
110
|
+
/**
|
|
111
|
+
* Responses API service tier for delegated requests (`auto`, `default`,
|
|
112
|
+
* `flex` or `priority`).
|
|
113
|
+
*/
|
|
114
|
+
serviceTier?: string | null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Responses delegation: OpenAI hosts the backend model the live model delegates to.
|
|
118
|
+
*
|
|
119
|
+
* Hosted tools run server-side; function calls are executed by this service
|
|
120
|
+
* with the handlers registered for the tools in the pipeline's `LLMContext`
|
|
121
|
+
* (which is also where the backend model's `tools` and `tool_choice` come
|
|
122
|
+
* from).
|
|
123
|
+
*
|
|
124
|
+
* - `settings`: request settings for the backend model — the same object
|
|
125
|
+
* `OpenAIResponsesLLMService` takes. `model` is required.
|
|
126
|
+
* `systemInstruction` becomes the backend's `instructions`,
|
|
127
|
+
* `maxCompletionTokens` its `max_output_tokens`, `reasoning` is sent when
|
|
128
|
+
* configured (the server default applies otherwise) and `extra` is merged
|
|
129
|
+
* into the delegation configuration. Fields the Live API doesn't accept for
|
|
130
|
+
* a delegated model (`temperature`, `topP`, the penalties, `seed`, `topK`,
|
|
131
|
+
* `maxTokens`, and the user-turn-completion fields) are dropped with a
|
|
132
|
+
* warning.
|
|
133
|
+
* - `serviceTier`: Responses API service tier for delegated requests
|
|
134
|
+
* (`auto`, `default`, `flex` or `priority`).
|
|
135
|
+
*/
|
|
136
|
+
export declare class ResponsesDelegation {
|
|
137
|
+
settings: OpenAIResponsesLLMSettings;
|
|
138
|
+
serviceTier: string | null;
|
|
139
|
+
constructor(options: ResponsesDelegationOptions);
|
|
140
|
+
}
|
|
141
|
+
export interface ClientDelegationOptions {
|
|
142
|
+
/**
|
|
143
|
+
* The worker that runs delegated work — normally a `BackendLLMWorker`
|
|
144
|
+
* wrapping any LLM service, or the name of one registered elsewhere.
|
|
145
|
+
*/
|
|
146
|
+
backend: BackendLLMWorker | string;
|
|
147
|
+
/** How long a delegation may take before it is abandoned. Defaults to 120. */
|
|
148
|
+
timeoutSecs?: number;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Client delegation: a Pipecat worker is the backend the live model delegates to.
|
|
152
|
+
*
|
|
153
|
+
* A delegation names no task: the live model signals only that it is handing
|
|
154
|
+
* work over. Each one is put to the backend as a message carrying the
|
|
155
|
+
* transcript fragments since the previous delegation, and the backend works
|
|
156
|
+
* out the request from them. The service attaches to the backend for its
|
|
157
|
+
* life and takes delegations one at a time: the next waits until the backend
|
|
158
|
+
* reports it has nothing left to do, since they share one backend
|
|
159
|
+
* conversation.
|
|
160
|
+
*
|
|
161
|
+
* What comes back is appended to the live session according to each output's
|
|
162
|
+
* `prefersSpoken` flag: what it wants heard goes as commentary, for the model
|
|
163
|
+
* to relay in its own words, and the rest as thinking — silent context it can
|
|
164
|
+
* draw on if the conversation turns that way.
|
|
165
|
+
*
|
|
166
|
+
* - `backend`: the worker that runs delegated work — normally a
|
|
167
|
+
* `BackendLLMWorker` wrapping any LLM service, or the name of one
|
|
168
|
+
* registered elsewhere (in the app, or in another process on a shared
|
|
169
|
+
* bus). A worker given here is registered as a child of the pipeline worker
|
|
170
|
+
* at setup, so the pipeline must run under a `WorkerRunner`.
|
|
171
|
+
* - `timeoutSecs`: how long a delegation may take before it is abandoned.
|
|
172
|
+
*/
|
|
173
|
+
export declare class ClientDelegation {
|
|
174
|
+
backend: BackendLLMWorker | string;
|
|
175
|
+
timeoutSecs: number;
|
|
176
|
+
constructor(options: ClientDelegationOptions);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* A delegated Responses run whose function calls we are collecting.
|
|
180
|
+
*
|
|
181
|
+
* - `callIds`: calls still awaiting an output.
|
|
182
|
+
* - `hadCalls`: whether the response asked for any function call at all.
|
|
183
|
+
* - `finished`: whether the response emitted all of its output items.
|
|
184
|
+
*/
|
|
185
|
+
export declare class _PendingResponse {
|
|
186
|
+
callIds: Set<string>;
|
|
187
|
+
hadCalls: boolean;
|
|
188
|
+
finished: boolean;
|
|
189
|
+
}
|
|
190
|
+
export interface OpenAILiveLLMServiceOptions extends LLMServiceOptions {
|
|
191
|
+
/** OpenAI project API key. */
|
|
192
|
+
apiKey: string;
|
|
193
|
+
/** WebSocket base URL of the Live API. */
|
|
194
|
+
baseUrl?: string;
|
|
195
|
+
/**
|
|
196
|
+
* Runtime-updatable settings. `model`, `voice`, `store` and
|
|
197
|
+
* `systemInstruction` are fixed once the session has started.
|
|
198
|
+
*/
|
|
199
|
+
settings?: OpenAILiveLLMSettings | null;
|
|
200
|
+
/**
|
|
201
|
+
* Where the model's delegated work runs. `null` selects client delegation
|
|
202
|
+
* with no backend configured.
|
|
203
|
+
*/
|
|
204
|
+
delegation?: ResponsesDelegation | ClientDelegation | null;
|
|
205
|
+
/**
|
|
206
|
+
* Creates the underlying `WebSocket` (see `WebsocketConnectOptions`), for
|
|
207
|
+
* runtimes whose `WebSocket` can't send the `Authorization` header.
|
|
208
|
+
*/
|
|
209
|
+
webSocketFactory?: WebsocketConnectOptions["webSocketFactory"];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* OpenAI Live LLM service: full-duplex speech-to-speech over WebSocket.
|
|
213
|
+
*
|
|
214
|
+
* The live model (`gpt-live-1`) listens and speaks at the same time. It
|
|
215
|
+
* decides on its own when to answer, when to stop talking when the user
|
|
216
|
+
* speaks over it, and when to *delegate* work — search, reasoning, tool use —
|
|
217
|
+
* to a backend text model while the conversation continues. In the
|
|
218
|
+
* frontend/backend terms used throughout, the live model is the *frontend*
|
|
219
|
+
* (the conversational model) and the delegated-to model is the *backend*.
|
|
220
|
+
* There is no client-side turn detection or response triggering: the
|
|
221
|
+
* pipeline streams audio in and plays audio out.
|
|
222
|
+
*
|
|
223
|
+
* Delegation modes, selected with `delegation`:
|
|
224
|
+
*
|
|
225
|
+
* - `ResponsesDelegation` — OpenAI hosts the backend (Responses API) model.
|
|
226
|
+
* Function calls it makes are executed here with the handlers registered
|
|
227
|
+
* for the pipeline context's tools; results go back to the API as soon as
|
|
228
|
+
* they are available.
|
|
229
|
+
* - `ClientDelegation` — a `BackendLLMWorker` running any Pipecat LLM service
|
|
230
|
+
* is the backend. The model does not share its conversation with the
|
|
231
|
+
* backend, so the service ships the transcript turns since the previous
|
|
232
|
+
* delegation along with each request.
|
|
233
|
+
* - `null` — client delegation mode with no backend configured; delegated
|
|
234
|
+
* requests are declined.
|
|
235
|
+
*
|
|
236
|
+
* Turn frames: the service proposes user turn boundaries from the API's
|
|
237
|
+
* projected transcript turns, and the recommended
|
|
238
|
+
* `new ExternalUserTurnStrategies({ enableInterruptions: false })` resolve
|
|
239
|
+
* them into `UserStartedSpeakingFrame` / `UserStoppedSpeakingFrame` **without
|
|
240
|
+
* broadcasting interruptions**: the model handles being talked over itself,
|
|
241
|
+
* and a delegation keeps running when that happens (the model is expected to
|
|
242
|
+
* disregard results the conversation has moved past). As a consequence every
|
|
243
|
+
* tool behaves as `cancelOnInterruption: false`; use `cancellableByLlm: true`
|
|
244
|
+
* for tools the model should be able to cancel on request.
|
|
245
|
+
*
|
|
246
|
+
* The context aggregators record both sides of the conversation from the
|
|
247
|
+
* transcript frames. Unlike the Realtime services, this one is not flagged as
|
|
248
|
+
* a realtime service for the aggregators: the service closes a user turn
|
|
249
|
+
* itself once the speaker falls quiet and pushes its final transcript then,
|
|
250
|
+
* so the user aggregator writes each user turn to the context as it ends —
|
|
251
|
+
* before any tool calls it triggers — rather than when the assistant starts
|
|
252
|
+
* to respond.
|
|
253
|
+
*
|
|
254
|
+
* The session starts on the first `LLMContextFrame` (typically queued as an
|
|
255
|
+
* `LLMRunFrame`): the context's leading system message (or
|
|
256
|
+
* `Settings.systemInstruction`) becomes the model's instructions and the
|
|
257
|
+
* remaining text messages seed the session as prior conversation.
|
|
258
|
+
*
|
|
259
|
+
* Event handlers available:
|
|
260
|
+
*
|
|
261
|
+
* - on_session_started: called with the session resource once the session is
|
|
262
|
+
* ready for audio.
|
|
263
|
+
* - on_delegation_created: called with the delegation item when the model
|
|
264
|
+
* delegates work.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* const llm = new OpenAILiveLLMService({
|
|
268
|
+
* apiKey: process.env.OPENAI_API_KEY!,
|
|
269
|
+
* settings: new OpenAILiveLLMService.Settings({ systemInstruction: "..." }),
|
|
270
|
+
* delegation: new OpenAILiveLLMService.ResponsesDelegation({
|
|
271
|
+
* settings: new OpenAIResponsesLLMService.Settings({ model: "gpt-5.4-mini" }),
|
|
272
|
+
* }),
|
|
273
|
+
* });
|
|
274
|
+
*/
|
|
275
|
+
export declare class OpenAILiveLLMService extends LLMService<OpenAILiveLLMAdapter & BaseLLMAdapter> {
|
|
276
|
+
static Settings: typeof OpenAILiveLLMSettings;
|
|
277
|
+
static ResponsesDelegation: typeof ResponsesDelegation;
|
|
278
|
+
static ClientDelegation: typeof ClientDelegation;
|
|
279
|
+
protected _settings: OpenAILiveLLMSettings;
|
|
280
|
+
static adapterClass: typeof OpenAILiveLLMAdapter;
|
|
281
|
+
apiKey: string;
|
|
282
|
+
baseUrl: string;
|
|
283
|
+
protected _sessionModel: string;
|
|
284
|
+
protected _delegation: ResponsesDelegation | ClientDelegation | null;
|
|
285
|
+
protected _webSocketFactory: WebsocketConnectOptions["webSocketFactory"];
|
|
286
|
+
protected _websocket: WebsocketClientConnection | null;
|
|
287
|
+
protected _receiveTask: Task<unknown> | null;
|
|
288
|
+
protected _disconnecting: boolean;
|
|
289
|
+
protected _context: LLMContext | null;
|
|
290
|
+
protected _needsSessionConfig: boolean;
|
|
291
|
+
protected _sessionStarted: boolean;
|
|
292
|
+
protected _sessionClosedEvent: Event;
|
|
293
|
+
protected _sentToolsSnapshot: string | null;
|
|
294
|
+
protected _sessionStartedOnConnection: boolean;
|
|
295
|
+
protected _resampler: import("@pipedog/core/audio/resamplers/base_audio_resampler").BaseAudioResampler;
|
|
296
|
+
protected _warnedAudioDropped: boolean;
|
|
297
|
+
protected _userTurn: _TurnGrouper;
|
|
298
|
+
protected _assistantTurn: _TurnGrouper;
|
|
299
|
+
protected _openingInstruction: string | null;
|
|
300
|
+
protected _openFunctionCalls: Map<string, string>;
|
|
301
|
+
protected _pendingResponses: Map<string, _PendingResponse>;
|
|
302
|
+
protected _transcriptFragments: {
|
|
303
|
+
role: string;
|
|
304
|
+
content: string;
|
|
305
|
+
}[];
|
|
306
|
+
protected _delegatedBefore: boolean;
|
|
307
|
+
protected _backendSession: _BackendSession | null;
|
|
308
|
+
protected _backendSessionTask: Task<unknown> | null;
|
|
309
|
+
protected _backendAttached: Event;
|
|
310
|
+
protected _currentDelegation: _LiveDelegation | null;
|
|
311
|
+
protected _queuedDelegations: [events.DelegationMetadata, string][];
|
|
312
|
+
/**
|
|
313
|
+
* Initialize the OpenAI Live LLM service.
|
|
314
|
+
*
|
|
315
|
+
* @param options.apiKey OpenAI project API key.
|
|
316
|
+
* @param options.baseUrl WebSocket base URL of the Live API.
|
|
317
|
+
* @param options.settings Runtime-updatable settings. `model`, `voice`,
|
|
318
|
+
* `store` and `systemInstruction` are fixed once the session has started.
|
|
319
|
+
* @param options.delegation Where the model's delegated work runs. `null`
|
|
320
|
+
* selects client delegation with no backend configured.
|
|
321
|
+
* Other options are passed to the parent LLMService.
|
|
322
|
+
*/
|
|
323
|
+
constructor(options: OpenAILiveLLMServiceOptions);
|
|
324
|
+
/**
|
|
325
|
+
* Check if the service can generate usage metrics.
|
|
326
|
+
*
|
|
327
|
+
* @returns True if metrics generation is supported.
|
|
328
|
+
*/
|
|
329
|
+
canGenerateMetrics(): boolean;
|
|
330
|
+
/** The Live API takes one output per function call, so intermediate results are dropped. */
|
|
331
|
+
get acceptsIntermediateFunctionCallResults(): boolean;
|
|
332
|
+
/**
|
|
333
|
+
* Decline both roles: this service is a frontend with a backend of its own.
|
|
334
|
+
*
|
|
335
|
+
* @param role The role the service is being asked to take.
|
|
336
|
+
* @returns The reason, naming what to use instead.
|
|
337
|
+
*/
|
|
338
|
+
llmWithBackendRoleObjection(role: LLMWithBackendRole): string | null;
|
|
339
|
+
/**
|
|
340
|
+
* Recommend external turn strategies, resolved from the API's projected turns.
|
|
341
|
+
*
|
|
342
|
+
* `isRealtimeService` is left off on purpose: the aggregators' realtime
|
|
343
|
+
* mode defers the user-message write until the assistant responds, to
|
|
344
|
+
* absorb late transcripts, and here that would place a delegation's tool
|
|
345
|
+
* calls ahead of the user message that caused them.
|
|
346
|
+
*/
|
|
347
|
+
serviceMetadataFrame(): LLMServiceMetadataFrame;
|
|
348
|
+
/**
|
|
349
|
+
* Set up the service, register the client-delegation backend, and connect.
|
|
350
|
+
*
|
|
351
|
+
* @param setup Configuration object containing setup parameters.
|
|
352
|
+
*/
|
|
353
|
+
setup(setup: FrameProcessorSetup): Promise<void>;
|
|
354
|
+
/** Release resources at teardown. */
|
|
355
|
+
cleanup(): Promise<void>;
|
|
356
|
+
/**
|
|
357
|
+
* Close the session gracefully and disconnect.
|
|
358
|
+
*
|
|
359
|
+
* @param frame The end frame triggering service shutdown.
|
|
360
|
+
*/
|
|
361
|
+
stop(frame: EndFrame): Promise<void>;
|
|
362
|
+
/**
|
|
363
|
+
* Disconnect immediately.
|
|
364
|
+
*
|
|
365
|
+
* @param frame The cancel frame triggering service cancellation.
|
|
366
|
+
*/
|
|
367
|
+
cancel(frame: CancelFrame): Promise<void>;
|
|
368
|
+
/**
|
|
369
|
+
* Apply a settings delta.
|
|
370
|
+
*
|
|
371
|
+
* The API fixes the session's settings when it starts, so a change is
|
|
372
|
+
* stored and reaches the API on the next session (see `resetConversation`).
|
|
373
|
+
*/
|
|
374
|
+
protected _updateSettings(delta: LLMSettings): Promise<Record<string, unknown>>;
|
|
375
|
+
/**
|
|
376
|
+
* Start a new session seeded from the current context.
|
|
377
|
+
*
|
|
378
|
+
* The Live API only takes conversation history at session start, so
|
|
379
|
+
* replacing the context (for example to restore a saved conversation) means
|
|
380
|
+
* dropping the session and opening a new one configured from the context as
|
|
381
|
+
* it is now. The old session is abandoned rather than closed gracefully: a
|
|
382
|
+
* graceful close waits for its in-flight delegations to drain, and their
|
|
383
|
+
* results belong to the conversation being replaced. Must not be called
|
|
384
|
+
* from the receive task.
|
|
385
|
+
*/
|
|
386
|
+
resetConversation(): Promise<void>;
|
|
387
|
+
/**
|
|
388
|
+
* Process incoming frames from the pipeline.
|
|
389
|
+
*
|
|
390
|
+
* @param frame The frame to process.
|
|
391
|
+
* @param direction The direction of frame flow in the pipeline.
|
|
392
|
+
*/
|
|
393
|
+
processFrame(frame: Frame, direction: FrameDirection): Promise<void>;
|
|
394
|
+
/**
|
|
395
|
+
* Push a frame, delivering function call outcomes to the API on the way.
|
|
396
|
+
*
|
|
397
|
+
* Function call results and cancellations are broadcast by the base
|
|
398
|
+
* service; the downstream copy is observed here and answered to the backend
|
|
399
|
+
* model immediately. The frames continue to the assistant aggregator, which
|
|
400
|
+
* records them in the context.
|
|
401
|
+
*
|
|
402
|
+
* @param frame The frame to push.
|
|
403
|
+
* @param direction The direction of frame pushing.
|
|
404
|
+
*/
|
|
405
|
+
pushFrame(frame: Frame, direction?: FrameDirection): Promise<void>;
|
|
406
|
+
/**
|
|
407
|
+
* Take the context, configuring the session from the first one.
|
|
408
|
+
*
|
|
409
|
+
* A later context carries messages the aggregators recorded from this
|
|
410
|
+
* session's own transcripts, tool results (delivered to the API as they are
|
|
411
|
+
* produced, see `pushFrame`), or messages the app appended. None of it
|
|
412
|
+
* reaches the API: user and assistant messages can't be told apart from the
|
|
413
|
+
* transcript recordings. Newly appended system/developer messages can only
|
|
414
|
+
* come from the app (the aggregators add user, assistant and tool messages,
|
|
415
|
+
* and async-tool envelopes that `async_tool_messages.parseMessage`
|
|
416
|
+
* identifies), so a future diff could forward those as
|
|
417
|
+
* `session.context.append(channel="developer")` unambiguously.
|
|
418
|
+
*/
|
|
419
|
+
protected _handleContext(context: LLMContext): Promise<void>;
|
|
420
|
+
protected _invocationParams(): Promise<OpenAILiveLLMInvocationParams>;
|
|
421
|
+
/** Send `session.start`, the first message on the socket. */
|
|
422
|
+
protected _sendSessionConfig(): Promise<void>;
|
|
423
|
+
protected _delegationConfig(tools: Record<string, unknown>[], toolChoice: unknown): events.ClientDelegationConfig | events.ResponsesDelegationConfig;
|
|
424
|
+
/** Send a sparse `session.update` when the backend model's tools changed. */
|
|
425
|
+
protected _maybeSendToolsUpdate(): Promise<void>;
|
|
426
|
+
protected static _toolsSnapshot(params: OpenAILiveLLMInvocationParams): string;
|
|
427
|
+
/**
|
|
428
|
+
* Send a client event to the Live API.
|
|
429
|
+
*
|
|
430
|
+
* @param event The client event to send.
|
|
431
|
+
*/
|
|
432
|
+
sendClientEvent(event: events.ClientEvent): Promise<void>;
|
|
433
|
+
protected _connect(): Promise<void>;
|
|
434
|
+
protected _disconnect(): Promise<void>;
|
|
435
|
+
/** Ask the server to shut down gracefully and wait for `session.closed`. */
|
|
436
|
+
protected _closeSession(): Promise<void>;
|
|
437
|
+
protected _wsSend(message: Record<string, unknown>): Promise<void>;
|
|
438
|
+
protected _receiveTaskHandler(): Promise<void>;
|
|
439
|
+
protected _handleServerEvent(evt: events.ServerEvent): Promise<void>;
|
|
440
|
+
protected _handleEvtSessionStarted(evt: events.SessionStartedEvent): Promise<void>;
|
|
441
|
+
protected _handleEvtSessionClosed(evt: events.SessionClosedEvent): Promise<void>;
|
|
442
|
+
protected _handleEvtError(evt: events.ErrorEvent): Promise<void>;
|
|
443
|
+
protected _sendUserAudio(frame: InputAudioRawFrame): Promise<void>;
|
|
444
|
+
protected _handleEvtAudioDelta(evt: events.OutputAudioDeltaEvent): Promise<void>;
|
|
445
|
+
protected _handleEvtTranscriptDelta(evt: events.TranscriptDeltaEvent): Promise<void>;
|
|
446
|
+
protected _restartTurnTimer(role: string, turn: _TurnGrouper): Promise<void>;
|
|
447
|
+
protected _closeTurnAfterGap(role: string, turn: _TurnGrouper): Promise<void>;
|
|
448
|
+
/** Close an open turn, if any, emitting its end frames. */
|
|
449
|
+
protected _closeTurn(role: string): Promise<void>;
|
|
450
|
+
/** Emit an open turn's end frames, with the turn's lock held. */
|
|
451
|
+
protected _endTurn(role: string): Promise<void>;
|
|
452
|
+
/** Close both directions, so response frames stay balanced across sessions. */
|
|
453
|
+
protected _closeOpenTurns(): Promise<void>;
|
|
454
|
+
protected _openTurn(role: string): Promise<void>;
|
|
455
|
+
protected _appendTurn(role: string, delta: string, accumulated: string, evt: events.ServerEvent): Promise<void>;
|
|
456
|
+
/**
|
|
457
|
+
* Keep a transcript fragment for the next client delegation.
|
|
458
|
+
*
|
|
459
|
+
* Fragments land on frame boundaries, so consecutive ones from the same
|
|
460
|
+
* speaker are joined back up as they arrive, giving the backend whole
|
|
461
|
+
* utterances to read. Deltas carry their own leading spaces, so they
|
|
462
|
+
* concatenate into the original text unaltered.
|
|
463
|
+
*/
|
|
464
|
+
protected _rememberFragment(role: "user" | "assistant", delta: string): void;
|
|
465
|
+
/** Take the conversation recorded since the previous delegation. */
|
|
466
|
+
protected _takeTranscript(): LLMStandardMessage[];
|
|
467
|
+
protected _pushAssistantText(text: string): Promise<void>;
|
|
468
|
+
protected _pushInterimTranscription(transcript: string, evt: events.ServerEvent): Promise<void>;
|
|
469
|
+
protected _handleEvtDelegationCreated(evt: events.SessionDelegationCreatedEvent): Promise<void>;
|
|
470
|
+
protected _handleClientDelegation(delegation: events.DelegationMetadata): Promise<void>;
|
|
471
|
+
/** Hold the session with the backend, appending what it produces to the live session. */
|
|
472
|
+
protected _runBackendSession(): Promise<void>;
|
|
473
|
+
protected _onBackendEvent(event: BackendOutput | BackendToolCall | BackendIdle | BackendError): Promise<void>;
|
|
474
|
+
protected _startDelegation(delegation: events.DelegationMetadata, request: string): Promise<void>;
|
|
475
|
+
/** Close the delegation in progress and start the next one waiting, if any. */
|
|
476
|
+
protected _finishDelegation(): Promise<void>;
|
|
477
|
+
protected _delegationTimeout(current: _LiveDelegation, timeoutSecs: number): Promise<void>;
|
|
478
|
+
/**
|
|
479
|
+
* Drop the delegation in progress and the ones waiting: the session they belong to is over.
|
|
480
|
+
*
|
|
481
|
+
* The backend is detached, which stops its work; the next connection
|
|
482
|
+
* attaches again.
|
|
483
|
+
*/
|
|
484
|
+
protected _abandonDelegations(): Promise<void>;
|
|
485
|
+
/** Open the session with the backend, when client delegation is configured and none is open. */
|
|
486
|
+
protected _attachBackend(): Promise<void>;
|
|
487
|
+
/** Close the session with the backend, which stops its work. */
|
|
488
|
+
protected _detachBackend(): Promise<void>;
|
|
489
|
+
/**
|
|
490
|
+
* Append text to the live session, for it to speak or to keep to itself.
|
|
491
|
+
*
|
|
492
|
+
* The API's two channels are named for what the live model does with the
|
|
493
|
+
* text: commentary is paraphrased aloud, thinking joins its private
|
|
494
|
+
* reasoning. That vantage is the model's own, which is why what arrives
|
|
495
|
+
* from a backend is flagged `prefersSpoken` instead — ordinary prose can go
|
|
496
|
+
* unspoken without being anything like a thought.
|
|
497
|
+
*/
|
|
498
|
+
protected _sendContextAppend(delegationId: string | null, text: string, options: {
|
|
499
|
+
spoken: boolean;
|
|
500
|
+
}): Promise<void>;
|
|
501
|
+
/** Dispatch a wrapped Responses lifecycle event by its own type. */
|
|
502
|
+
protected _handleEvtResponse(evt: events.ResponseEventEnvelope): Promise<void>;
|
|
503
|
+
/**
|
|
504
|
+
* Remember a delegated response so its function calls can be collected.
|
|
505
|
+
*
|
|
506
|
+
* @returns The response's correlation key.
|
|
507
|
+
*/
|
|
508
|
+
protected _trackResponse(evt: events.ResponseEventEnvelope): string;
|
|
509
|
+
protected _handleResponseOutputItemDone(evt: events.ResponseEventEnvelope): Promise<void>;
|
|
510
|
+
/**
|
|
511
|
+
* Note that a response emitted all of its output items.
|
|
512
|
+
*
|
|
513
|
+
* The lifecycle snapshot's `output` is empty even here, so the calls
|
|
514
|
+
* collected from the individual item events are what has to be answered.
|
|
515
|
+
*/
|
|
516
|
+
protected _handleResponseFinished(evt: events.ResponseEventEnvelope): Promise<void>;
|
|
517
|
+
/** Continue a response once every function call it made has been answered. */
|
|
518
|
+
protected _maybeContinueResponse(key: string): Promise<void>;
|
|
519
|
+
protected _handleFunctionCallResult(frame: FunctionCallResultFrame): Promise<void>;
|
|
520
|
+
protected _handleFunctionCallCancel(frame: FunctionCallCancelFrame): Promise<void>;
|
|
521
|
+
/** Queue one function result, and continue the response once all are in. */
|
|
522
|
+
protected _sendFunctionCallOutput(callId: string, output: string): Promise<void>;
|
|
523
|
+
/**
|
|
524
|
+
* Log the session's cumulative live audio duration.
|
|
525
|
+
*
|
|
526
|
+
* The live model's usage is reported as seconds rather than tokens. Token
|
|
527
|
+
* metrics come from the backend model instead, on the nested Responses
|
|
528
|
+
* completion events.
|
|
529
|
+
*/
|
|
530
|
+
protected _reportUsage(usage: events.Usage): Promise<void>;
|
|
531
|
+
/** Report the backend model's token usage from a completed delegated response. */
|
|
532
|
+
protected _reportBackendUsage(response: Record<string, unknown>): Promise<void>;
|
|
533
|
+
}
|
|
534
|
+
/** Return the text of a trailing developer message, if the history ends with one. */
|
|
535
|
+
export declare function _trailingDeveloperText(items: readonly events.InputItem[]): string | null;
|
|
536
|
+
/**
|
|
537
|
+
* Build `delegation.responses` from the nested Responses settings.
|
|
538
|
+
*
|
|
539
|
+
* Mirrors how `OpenAIResponsesLLMService` builds a request from the same
|
|
540
|
+
* settings, minus what the Live API owns for a delegated model.
|
|
541
|
+
*/
|
|
542
|
+
export declare function _responsesDelegationConfig(delegation: ResponsesDelegation): Record<string, unknown>;
|
|
543
|
+
/**
|
|
544
|
+
* Estimate how many tokens a piece of text costs.
|
|
545
|
+
*
|
|
546
|
+
* A byte-pair encoding packs about four characters of ASCII into a token.
|
|
547
|
+
* Other scripts cost more per character — CJK runs close to one token each —
|
|
548
|
+
* so anything outside ASCII is counted whole.
|
|
549
|
+
*/
|
|
550
|
+
export declare function _estimatedTokens(text: string): number;
|
|
551
|
+
/** Return a text's cost in quarter-tokens: one for an ASCII character, four for any other. */
|
|
552
|
+
export declare function _tokenQuarters(text: string): number;
|
|
553
|
+
/**
|
|
554
|
+
* Split off the longest prefix of `text` that fits `tokenLimit`.
|
|
555
|
+
*
|
|
556
|
+
* The cut lands on the last space within the limit, so words stay whole.
|
|
557
|
+
*/
|
|
558
|
+
export declare function _splitAtTokenLimit(text: string, tokenLimit: number): [string, string];
|
|
559
|
+
/**
|
|
560
|
+
* Split text into chunks of at most `tokenLimit` estimated tokens.
|
|
561
|
+
*
|
|
562
|
+
* Chunks break on sentence boundaries; a sentence longer than the limit is
|
|
563
|
+
* split at a space.
|
|
564
|
+
*/
|
|
565
|
+
export declare function _chunkText(text: string, tokenLimit: number): string[];
|
|
566
|
+
//# sourceMappingURL=llm.d.ts.map
|