@memberjunction/realtime-widget 6.2.0-edge.5 → 6.2.0-edge.7

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.
Files changed (33) hide show
  1. package/README.md +11 -2
  2. package/dist/embed/chunks/UserViewEngine-H2CVAVG5.js +7 -0
  3. package/dist/embed/chunks/chunk-46L5OHPT.js +163629 -0
  4. package/dist/embed/chunks/chunk-4IZZL4GA.js +2513 -0
  5. package/dist/embed/chunks/chunk-FOFOZNZC.js +6165 -0
  6. package/dist/embed/chunks/chunk-MQPAK26D.js +39413 -0
  7. package/dist/embed/chunks/chunk-S3LX5KYE.js +46 -0
  8. package/dist/embed/chunks/chunk-VXA62VEQ.js +17259 -0
  9. package/dist/embed/chunks/dist-S5YIFSWR.js +66 -0
  10. package/dist/embed/chunks/guest-tool-relay-2MP5GU67.js +30 -0
  11. package/dist/embed/chunks/guest-voice-mint-MNMKTI3E.js +94 -0
  12. package/dist/embed/chunks/realtime-voice-controller-D2GLGIK2.js +30589 -0
  13. package/dist/embed/chunks/runtime-widget-transport-V7B2B3TL.js +162 -0
  14. package/dist/embed/mj-widget.js +1056 -0
  15. package/dist/embed.js +3 -2
  16. package/dist/embed.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/loader.d.ts +9 -0
  22. package/dist/loader.d.ts.map +1 -1
  23. package/dist/loader.js +56 -7
  24. package/dist/loader.js.map +1 -1
  25. package/dist/session/widget-session-client.d.ts +46 -5
  26. package/dist/session/widget-session-client.d.ts.map +1 -1
  27. package/dist/session/widget-session-client.js +118 -17
  28. package/dist/session/widget-session-client.js.map +1 -1
  29. package/dist/transport/runtime-widget-transport.d.ts +0 -6
  30. package/dist/transport/runtime-widget-transport.d.ts.map +1 -1
  31. package/dist/transport/runtime-widget-transport.js +3 -31
  32. package/dist/transport/runtime-widget-transport.js.map +1 -1
  33. package/package.json +10 -10
@@ -0,0 +1,2513 @@
1
+ import {
2
+ IsPlainObject
3
+ } from "./chunk-VXA62VEQ.js";
4
+
5
+ // ../../AI/Core/dist/generic/baseModel.js
6
+ var BaseResult = class {
7
+ get timeElapsed() {
8
+ return this.endTime.getTime() - this.startTime.getTime();
9
+ }
10
+ constructor(success, startTime, endTime) {
11
+ this.success = success;
12
+ this.startTime = startTime;
13
+ this.endTime = endTime;
14
+ }
15
+ };
16
+ var ModelUsage = class _ModelUsage {
17
+ /**
18
+ * Creates a new ModelUsage instance.
19
+ *
20
+ * @param {number} promptTokens - Number of tokens used in the prompt/input
21
+ * @param {number} completionTokens - Number of tokens generated in the completion/output
22
+ * @param {number} [cost] - Optional cost of the execution
23
+ * @param {string} [costCurrency] - Optional currency code for the cost (e.g., 'USD', 'EUR', 'GBP')
24
+ */
25
+ constructor(promptTokens, completionTokens, cost, costCurrency) {
26
+ this.cacheReadTokens = 0;
27
+ this.cacheWriteTokens = 0;
28
+ this.promptTokens = promptTokens;
29
+ this.completionTokens = completionTokens;
30
+ if (cost !== void 0) {
31
+ this.cost = cost;
32
+ }
33
+ if (costCurrency !== void 0) {
34
+ this.costCurrency = costCurrency;
35
+ }
36
+ }
37
+ /**
38
+ * Total input tokens the provider processed: uncached ({@link promptTokens}) + cache reads +
39
+ * cache writes. This is the figure to price at the input rate (until cache-aware per-bucket
40
+ * pricing is added) and equals what the provider's native "prompt token" count was before
41
+ * normalization. Use THIS (not promptTokens) for cost so cached tokens aren't dropped.
42
+ */
43
+ get totalInputTokens() {
44
+ return this.promptTokens + (this.cacheReadTokens ?? 0) + (this.cacheWriteTokens ?? 0);
45
+ }
46
+ /**
47
+ * Total UNCACHED tokens for the call: uncached input ({@link promptTokens}) + completion.
48
+ *
49
+ * Deliberately EXCLUDES the cache buckets so the MemberJunction invariant
50
+ * `TokensUsed === TokensPrompt + TokensCompletion` (enforced by AIPromptRun's CK_AIPromptRun_Tokens
51
+ * constraint + generated validation) continues to hold once promptTokens is net-new. The full
52
+ * token count INCLUDING cache is `totalInputTokens + completionTokens` — use {@link totalInputTokens}
53
+ * for cost so cached tokens are still billed.
54
+ *
55
+ * @returns {number} promptTokens + completionTokens
56
+ */
57
+ get totalTokens() {
58
+ return this.promptTokens + this.completionTokens;
59
+ }
60
+ /**
61
+ * Builds usage for a continuous-media execution, with the token fields zeroed.
62
+ *
63
+ * @param kind The base measure being counted — never the billing measure
64
+ * @param inputUnits Quantity consumed on the input side
65
+ * @param outputUnits Quantity produced on the output side; defaults to 0
66
+ */
67
+ static ForMedia(kind, inputUnits, outputUnits = 0) {
68
+ const usage = new _ModelUsage(0, 0);
69
+ usage.unitKind = kind;
70
+ usage.inputUnits = inputUnits;
71
+ usage.outputUnits = outputUnits;
72
+ return usage;
73
+ }
74
+ };
75
+ var BaseModel = class {
76
+ /**
77
+ * Only sub-classes can access the API key
78
+ */
79
+ get apiKey() {
80
+ return this._apiKey;
81
+ }
82
+ constructor(apiKey) {
83
+ if (!apiKey || apiKey.trim().length === 0)
84
+ console.warn("BaseModel: API key is empty, this might cause issues with model execution");
85
+ this._apiKey = apiKey;
86
+ }
87
+ };
88
+
89
+ // ../../AI/Core/dist/generic/baseRealtime.js
90
+ function RealtimeDiagLog(message) {
91
+ if (typeof process === "undefined" || !process.env) {
92
+ return;
93
+ }
94
+ const v = (process.env.MJ_VERBOSE ?? "").toLowerCase();
95
+ if (v === "true" || v === "1" || v === "yes") {
96
+ const sanitized = message.replace(/[\r\n\p{Cc}\p{Zl}\p{Zp}]/gu, " ");
97
+ console.log(sanitized);
98
+ }
99
+ }
100
+ function ClampSessionSecondsValue(...candidates) {
101
+ let smallest;
102
+ for (const c of candidates) {
103
+ if (typeof c !== "number" || !Number.isInteger(c) || c <= 0)
104
+ continue;
105
+ if (smallest === void 0 || c < smallest)
106
+ smallest = c;
107
+ }
108
+ return smallest;
109
+ }
110
+ var BaseRealtimeModel = class extends BaseModel {
111
+ static {
112
+ this.DriverFacts = Object.freeze({});
113
+ }
114
+ /**
115
+ * Returns the smallest candidate that is a positive integer; see {@link ClampSessionSecondsValue}.
116
+ * The rule "the caller's bag wins only if smaller" lives there, once: pass the provider
117
+ * cap and the bag cap together.
118
+ */
119
+ ClampSessionSeconds(...candidates) {
120
+ return ClampSessionSecondsValue(...candidates);
121
+ }
122
+ static {
123
+ this.SupportsDynamicToolSet = false;
124
+ }
125
+ /**
126
+ * Whether this driver can mint an ephemeral, server-scoped client credential for a
127
+ * **client-direct** realtime session (the browser opens its own provider socket using a
128
+ * short-lived token the server minted).
129
+ *
130
+ * Defaults to `false`. Providers that support browser-direct sessions override this to `true`
131
+ * and implement {@link CreateClientSession}. The server-bridged topology (where the provider
132
+ * socket lives on the server, via {@link StartSession}) is supported by every driver regardless
133
+ * of this flag.
134
+ *
135
+ * @returns `true` if {@link CreateClientSession} is supported; `false` otherwise.
136
+ */
137
+ get SupportsClientDirect() {
138
+ return false;
139
+ }
140
+ /**
141
+ * Mints an ephemeral, server-scoped client credential plus a provider-native session config for
142
+ * a **client-direct** realtime session.
143
+ *
144
+ * In the client-direct topology the browser owns the provider socket (e.g. WebRTC), but the
145
+ * **server** still controls the prompt and tool set: it mints a short-lived token and hands back
146
+ * a {@link ClientRealtimeSessionConfig} whose `SessionConfig` the matching client driver applies
147
+ * when it opens its socket. This keeps prompt/tool authority server-side even though the media
148
+ * plane is client-direct.
149
+ *
150
+ * Not every provider supports this (some only expose a server-bridged socket), so this is a
151
+ * concrete method that throws by default rather than an abstract one — that would force every
152
+ * existing and future driver to implement it. Providers that support it override
153
+ * {@link SupportsClientDirect} to `true` and override this method.
154
+ *
155
+ * @param _params The session parameters (system prompt, tools, model, config bag).
156
+ * @returns A promise resolving to the minted {@link ClientRealtimeSessionConfig}.
157
+ * @throws Always, unless overridden by a provider that supports client-direct sessions.
158
+ */
159
+ async CreateClientSession(_params) {
160
+ throw new Error(`${this.constructor.name} does not support client-direct realtime sessions`);
161
+ }
162
+ /**
163
+ * Whether this driver's sessions carry a **video** track in addition to audio.
164
+ *
165
+ * @deprecated Nothing reads it, and no driver overrides it: it is `false` for Gemini Live, which takes camera video
166
+ * and renders avatars. What a session does with video is declared on its {@link IRealtimeSession.Capabilities}:
167
+ * {@link RealtimeSessionCapabilities.SupportedInboundTracks} for video in and
168
+ * {@link RealtimeSessionCapabilities.SupportedOutboundTracks} for video out, each track with its encoding. Whether a
169
+ * model renders a live avatar is {@link SupportsAvatarOutput}. A driver declares video there, not here.
170
+ *
171
+ * @returns `false` in every MJ driver.
172
+ */
173
+ get SupportsVideo() {
174
+ return false;
175
+ }
176
+ /**
177
+ * Whether this driver's sessions can render a live avatar for the model, on the endpoint the driver serves.
178
+ *
179
+ * Defaults to `false`: a driver whose models render no avatar says nothing more, and may ignore an avatar request. A
180
+ * driver that renders one for some models (Gemini Enterprise) overrides it. The client-session service reads it to
181
+ * tell a call or a meeting that asked for an avatar "this voice model can't show an avatar"
182
+ * ({@link RealtimeAvatarUnavailableReason} `'endpoint'`), whichever driver serves it.
183
+ *
184
+ * @param _model The provider's API name for the model.
185
+ * @returns `true` when sessions on this model can render a live avatar; `false` otherwise.
186
+ */
187
+ SupportsAvatarOutput(_model) {
188
+ return false;
189
+ }
190
+ /**
191
+ * Static fallback list of provider-native voices supported by this driver when metadata personas
192
+ * are not present or sparse.
193
+ *
194
+ * NOTE: Database metadata (`MJ: AI Personas` + `AI Model Personas`) is authoritative where configured.
195
+ * At runtime, `GetRealtimeModelVoices` unions metadata personas with this driver fallback list so that
196
+ * newly uncatalogued provider voices remain selectable while catalogued personas carry curated names/descriptions.
197
+ *
198
+ * @returns The supported voice ids (id + human label), or `[]` when none are declared.
199
+ */
200
+ get SupportedVoices() {
201
+ return [];
202
+ }
203
+ };
204
+ function ParseRealtimeClientTransport(value) {
205
+ return value === "direct" || value === "relay" ? value : void 0;
206
+ }
207
+ function ExtractToolSchedulingHint(parsed, toolName, loggerTag = "Realtime") {
208
+ const raw = parsed["__mj_scheduling"] ?? parsed["scheduling"];
209
+ if ("__mj_scheduling" in parsed) {
210
+ delete parsed["__mj_scheduling"];
211
+ }
212
+ if ("scheduling" in parsed) {
213
+ delete parsed["scheduling"];
214
+ }
215
+ if (typeof raw !== "string") {
216
+ return void 0;
217
+ }
218
+ const schedStr = raw.trim().toUpperCase();
219
+ if (schedStr.length === 0) {
220
+ return void 0;
221
+ }
222
+ if (schedStr === "SILENT") {
223
+ return "silent";
224
+ } else if (schedStr === "WHEN_IDLE") {
225
+ return "whenIdle";
226
+ } else if (schedStr === "INTERRUPT" || schedStr === "INTERRUPTED") {
227
+ return "interrupt";
228
+ } else {
229
+ console.warn(`[${loggerTag}] Unrecognized function scheduling value "${raw}" for tool "${toolName}".`);
230
+ return void 0;
231
+ }
232
+ }
233
+
234
+ // ../../AI/Core/dist/generic/chat.types.js
235
+ var ChatMessageRole = {
236
+ system: "system",
237
+ user: "user",
238
+ assistant: "assistant",
239
+ tool: "tool"
240
+ };
241
+ var ChatResult = class extends BaseResult {
242
+ };
243
+ function ValidateToolConversation(messages) {
244
+ const declaredCallIds = /* @__PURE__ */ new Set();
245
+ const orphaned = [];
246
+ for (const message of messages) {
247
+ if (message.role === ChatMessageRole.assistant) {
248
+ for (const call of message.toolCalls ?? []) {
249
+ declaredCallIds.add(call.id);
250
+ }
251
+ continue;
252
+ }
253
+ for (const block of GetToolResultBlocks(message.content)) {
254
+ if (!block.toolCallId || !declaredCallIds.has(block.toolCallId)) {
255
+ orphaned.push(block.toolCallId ?? "(missing toolCallId)");
256
+ }
257
+ }
258
+ }
259
+ if (orphaned.length > 0) {
260
+ throw new Error(`Tool result(s) with no matching tool call in the conversation: ${orphaned.join(", ")}. Every tool_result must answer a call declared by an EARLIER assistant message \u2014 set ChatMessage.toolCalls from the model's ChatCompletionMessage.toolCalls when you append its reply, before appending the results.`);
261
+ }
262
+ }
263
+ function GetToolResultBlocks(content) {
264
+ if (typeof content === "string") {
265
+ return [];
266
+ }
267
+ return content.filter((block) => block.type === "tool_result");
268
+ }
269
+
270
+ // ../../AI/Core/dist/generic/cancellation.js
271
+ var REQUEST_CANCELLED_CODE = "request_cancelled";
272
+ var REQUEST_TIMEOUT_CODE = "request_timeout";
273
+ var ABORT_ERROR_NAMES = /* @__PURE__ */ new Set(["AbortError", "APIUserAbortError", "RequestAbortedError"]);
274
+ function asErrorLike(value) {
275
+ return value !== null && typeof value === "object" ? value : void 0;
276
+ }
277
+ function IsAbortError(error) {
278
+ const e = asErrorLike(error);
279
+ if (!e) {
280
+ return false;
281
+ }
282
+ const name = typeof e.name === "string" ? e.name : "";
283
+ const className = typeof e.constructor?.name === "string" ? e.constructor.name : "";
284
+ return ABORT_ERROR_NAMES.has(name) || ABORT_ERROR_NAMES.has(className);
285
+ }
286
+ function IsTimeoutAbort(signal) {
287
+ if (!signal?.aborted) {
288
+ return false;
289
+ }
290
+ const reason = asErrorLike(signal.reason);
291
+ return reason?.name === "TimeoutError" || reason?.IsTimeout === true;
292
+ }
293
+ function BuildCancellationErrorInfo(options = {}) {
294
+ const timedOut = IsTimeoutAbort(options.signal);
295
+ const reason = options.signal?.aborted ? options.signal.reason : void 0;
296
+ return {
297
+ error: options.error,
298
+ errorType: timedOut ? "NetworkError" : "Cancelled",
299
+ severity: timedOut ? "Retriable" : "Fatal",
300
+ canFailover: timedOut,
301
+ providerErrorCode: timedOut ? REQUEST_TIMEOUT_CODE : REQUEST_CANCELLED_CODE,
302
+ context: {
303
+ provider: options.provider,
304
+ cancelled: !timedOut,
305
+ reason: typeof reason === "string" ? reason : asErrorLike(reason)?.message
306
+ }
307
+ };
308
+ }
309
+ function DescribeCancellation(signal, subject = "Request") {
310
+ const verb = IsTimeoutAbort(signal) ? "timed out" : "was cancelled";
311
+ const reason = signal?.aborted ? signal.reason : void 0;
312
+ const detail = typeof reason === "string" ? reason : asErrorLike(reason)?.message;
313
+ return typeof detail === "string" && detail.length > 0 ? `${subject} ${verb}: ${detail}` : `${subject} ${verb}`;
314
+ }
315
+
316
+ // ../../AI/Core/dist/generic/linearTextScan.js
317
+ function isLineTerminator(ch) {
318
+ return ch === "\n" || ch === "\r" || ch === "\u2028" || ch === "\u2029";
319
+ }
320
+ function lineEndFrom(text, from) {
321
+ for (let i = from; i < text.length; i++) {
322
+ if (isLineTerminator(text[i])) {
323
+ return i;
324
+ }
325
+ }
326
+ return text.length;
327
+ }
328
+ function ContainsInOrderOnOneLine(text, befores, afters) {
329
+ let lineStart = 0;
330
+ while (lineStart <= text.length) {
331
+ const lineEnd = lineEndFrom(text, lineStart);
332
+ const line = text.slice(lineStart, lineEnd);
333
+ for (const before of befores) {
334
+ const at = line.indexOf(before);
335
+ if (at < 0) {
336
+ continue;
337
+ }
338
+ for (const after of afters) {
339
+ if (line.indexOf(after, at + before.length) >= 0) {
340
+ return true;
341
+ }
342
+ }
343
+ }
344
+ lineStart = lineEnd + 1;
345
+ }
346
+ return false;
347
+ }
348
+ function FirstBraceSpanOnOneLine(text) {
349
+ let searchFrom = 0;
350
+ while (searchFrom < text.length) {
351
+ const open = text.indexOf("{", searchFrom);
352
+ if (open < 0) {
353
+ return null;
354
+ }
355
+ const lineEnd = lineEndFrom(text, open);
356
+ const line = text.slice(open, lineEnd);
357
+ const close = line.lastIndexOf("}");
358
+ if (close > 0) {
359
+ return line.slice(0, close + 1);
360
+ }
361
+ searchFrom = lineEnd + 1;
362
+ }
363
+ return null;
364
+ }
365
+
366
+ // ../../AI/Core/dist/generic/errorAnalyzer.js
367
+ var ErrorAnalyzer = class {
368
+ /**
369
+ * Analyzes an error from an AI provider and returns standardized error information.
370
+ * This method extracts relevant details from provider-specific error formats and
371
+ * maps them to a consistent structure for easier handling.
372
+ *
373
+ * @static
374
+ * @param {any} error - The error object thrown by the provider SDK
375
+ * @param {string} [providerName] - Optional name of the provider for context
376
+ * @returns {AIErrorInfo} Standardized error information
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * const errorInfo = ErrorAnalyzer.analyzeError(error, 'Anthropic');
381
+ * console.log(`Error type: ${errorInfo.errorType}`);
382
+ * console.log(`Can retry: ${errorInfo.severity !== 'Fatal'}`);
383
+ * ```
384
+ */
385
+ static AnalyzeError(error, providerName) {
386
+ const carried = this.carriedErrorInfo(error);
387
+ if (carried) {
388
+ return carried;
389
+ }
390
+ const httpStatusCode = this.extractHttpStatusCode(error);
391
+ const errorType = this.determineErrorType(error, httpStatusCode);
392
+ const severity = this.determineSeverity(errorType);
393
+ const canFailover = this.canFailoverForError(errorType);
394
+ const suggestedRetryDelaySeconds = this.extractRetryDelay(error);
395
+ const providerErrorCode = this.extractProviderErrorCode(error);
396
+ if (providerErrorCode === "context_length_exceeded") {
397
+ return {
398
+ error,
399
+ httpStatusCode,
400
+ errorType: "ContextLengthExceeded",
401
+ severity: "Fatal",
402
+ canFailover: true,
403
+ suggestedRetryDelaySeconds,
404
+ providerErrorCode,
405
+ context: {
406
+ provider: providerName,
407
+ errorName: error?.name,
408
+ errorConstructor: error?.constructor?.name
409
+ }
410
+ };
411
+ }
412
+ return {
413
+ error,
414
+ httpStatusCode,
415
+ errorType,
416
+ severity,
417
+ canFailover,
418
+ suggestedRetryDelaySeconds,
419
+ providerErrorCode,
420
+ context: {
421
+ provider: providerName,
422
+ errorName: error?.name,
423
+ errorConstructor: error?.constructor?.name
424
+ }
425
+ };
426
+ }
427
+ /** @deprecated Use {@link AnalyzeError}. */
428
+ static analyzeError(error, providerName) {
429
+ return this.AnalyzeError(error, providerName);
430
+ }
431
+ /**
432
+ * The classification a failed result already carries on `errorInfo`, if it has a complete one.
433
+ */
434
+ static carriedErrorInfo(error) {
435
+ const info = error?.errorInfo;
436
+ return info && typeof info.errorType === "string" && typeof info.severity === "string" && typeof info.canFailover === "boolean" ? info : void 0;
437
+ }
438
+ /**
439
+ * Extracts HTTP status code from various error object structures.
440
+ * Different provider SDKs store status codes in different locations.
441
+ *
442
+ * @private
443
+ * @static
444
+ * @param {any} error - The error object to extract status code from
445
+ * @returns {number | undefined} The HTTP status code if found, undefined otherwise
446
+ */
447
+ static extractHttpStatusCode(error) {
448
+ const candidates = [error?.status, error?.statusCode, error?.response?.status, error?.response?.statusCode, error?.code];
449
+ return candidates.find((c) => typeof c === "number" && c >= 100 && c <= 599);
450
+ }
451
+ static {
452
+ this.TRANSPORT_ERROR_CODES = /* @__PURE__ */ new Set([
453
+ "ECONNREFUSED",
454
+ "ECONNRESET",
455
+ "ECONNABORTED",
456
+ "EPIPE",
457
+ "ETIMEDOUT",
458
+ "ENOTFOUND",
459
+ "EAI_AGAIN",
460
+ "ENETUNREACH",
461
+ "ENETDOWN",
462
+ "EHOSTUNREACH",
463
+ "EHOSTDOWN",
464
+ "UND_ERR_SOCKET",
465
+ "UND_ERR_CLOSED",
466
+ "UND_ERR_CONNECT_TIMEOUT",
467
+ "UND_ERR_HEADERS_TIMEOUT",
468
+ "UND_ERR_BODY_TIMEOUT"
469
+ ]);
470
+ }
471
+ static {
472
+ this.MAX_CAUSE_DEPTH = 5;
473
+ }
474
+ /**
475
+ * Whether the error is a connection failure that its message does not name. Node's fetch
476
+ * reports one as `TypeError: fetch failed` (or `terminated` when the socket closes mid-response)
477
+ * and keeps the real reason — `ECONNRESET`, `ENOTFOUND` — on `cause`. SDKs built on fetch,
478
+ * such as `@google/genai`, rethrow that error unchanged, so the message alone reads as unknown.
479
+ *
480
+ * A plain `AbortError` is deliberately not a transport failure: it is also what a caller's
481
+ * cancellation looks like, and a cancellation must not fail over.
482
+ */
483
+ static isTransportFailure(error) {
484
+ return this.causeChain(error).some((link) => typeof link.code === "string" && this.TRANSPORT_ERROR_CODES.has(link.code) || link.name === "TypeError" && (link.message === "fetch failed" || link.message === "terminated"));
485
+ }
486
+ /** The error followed by each `cause` beneath it, stopping at a non-error, a cycle or the depth limit. */
487
+ static causeChain(error) {
488
+ const chain = [];
489
+ let current = error;
490
+ while (current instanceof Error && chain.length < this.MAX_CAUSE_DEPTH && !chain.includes(current)) {
491
+ chain.push(current);
492
+ current = current.cause;
493
+ }
494
+ return chain;
495
+ }
496
+ /**
497
+ * Determines the standardized error type based on status code and error properties.
498
+ * Uses a combination of HTTP status codes, error messages, and error class names.
499
+ *
500
+ * @private
501
+ * @static
502
+ * @param {any} error - The error object to analyze
503
+ * @param {number} [statusCode] - The HTTP status code if available
504
+ * @returns {AIErrorType} The categorized error type
505
+ */
506
+ static determineErrorType(error, statusCode) {
507
+ if (IsAbortError(error) || error?.code === REQUEST_CANCELLED_CODE) {
508
+ return "Cancelled";
509
+ }
510
+ const errorString = (error?.message || error?.name || "").toLowerCase();
511
+ if (errorString.includes("context_length_exceeded") || errorString.includes("context length exceeded") || errorString.includes("reduce the length of the messages") || errorString.includes("maximum context length")) {
512
+ return "ContextLengthExceeded";
513
+ }
514
+ if (errorString.includes("rate limit") || errorString.includes("too many requests")) {
515
+ return "RateLimit";
516
+ }
517
+ if (errorString.includes("credit") || // xAI "no credits" errors
518
+ errorString.includes("billing") || // Generic billing issues
519
+ errorString.includes("payment") || // Payment required errors
520
+ errorString.includes("insufficient funds") || errorString.includes("quota exceeded") || errorString.includes("usage limits") || // Anthropic spend cap — sent as a 400 invalid_request_error, which the status fallback would misread as a malformed request
521
+ errorString.includes("balance") || // Account balance issues
522
+ errorString.includes("no funds")) {
523
+ return "NoCredit";
524
+ }
525
+ if (errorString.includes("no suitable model found") || errorString.includes("no credentials found") || errorString.includes("no api keys found")) {
526
+ return "NoCredentials";
527
+ }
528
+ if (errorString.includes("unauthorized") || errorString.includes("authentication failed") || errorString.includes("invalid api key") || errorString.includes("invalid key") || errorString.includes("api key is invalid") || // Google: HTTP 400 "API key not valid. Please pass a valid API key." with reason
529
+ // API_KEY_INVALID. None of the phrases above match it, so it fell through to the
530
+ // permissive VendorValidationError and failed over to another vendor instead of failing.
531
+ errorString.includes("api key not valid") || errorString.includes("api_key_invalid") || // Google's expired key: HTTP 400 "API key expired. Please renew the API key." with reason
532
+ // API_KEY_EXPIRED. It read as InvalidRequest, which stops failover with the wrong reason.
533
+ errorString.includes("api key expired") || errorString.includes("api_key_expired")) {
534
+ return "Authentication";
535
+ }
536
+ if (errorString.includes("service unavailable") || errorString.includes("service is down") || errorString.includes("maintenance") || errorString.includes("temporarily unavailable")) {
537
+ return "ServiceUnavailable";
538
+ }
539
+ if (errorString.includes("network") || errorString.includes("timeout") || errorString.includes("econnrefused") || errorString.includes("dns") || errorString.includes("connection reset") || this.isTransportFailure(error)) {
540
+ return "NetworkError";
541
+ }
542
+ if (errorString.includes("model") && (errorString.includes("not found") || errorString.includes("does not exist") || errorString.includes("overloaded") || errorString.includes("not available"))) {
543
+ return "ModelError";
544
+ }
545
+ if (errorString.includes("required") || errorString.includes("validation") || errorString.includes("schema") || // "X is required" is already covered by includes('required') above.
546
+ // Linear scans rather than /missing.*(?:field|property)/ and its reverse, which are polynomial
547
+ // on provider-controlled text (CodeQL js/polynomial-redos).
548
+ ContainsInOrderOnOneLine(errorString, ["missing"], ["field", "property"]) || // "missing field/property X"
549
+ ContainsInOrderOnOneLine(errorString, ["field", "property"], ["missing"]) || // "field/property X missing"
550
+ /must\s+(?:be|have|contain)/.test(errorString)) {
551
+ return "VendorValidationError";
552
+ }
553
+ if (errorString.includes("malformed json") || errorString.includes("invalid json") || errorString.includes("json parse") || errorString.includes("syntax error") || errorString.includes("malformed request")) {
554
+ return "InvalidRequest";
555
+ }
556
+ if (errorString.includes("invalid request") || errorString.includes("bad request")) {
557
+ return "VendorValidationError";
558
+ }
559
+ const errorTypeName = error?.constructor?.name || error?.name || "";
560
+ if (errorTypeName.includes("RateLimit"))
561
+ return "RateLimit";
562
+ if (errorTypeName.includes("Authentication"))
563
+ return "Authentication";
564
+ if (errorTypeName.includes("APIError") && statusCode === 500)
565
+ return "InternalServerError";
566
+ if (statusCode) {
567
+ switch (statusCode) {
568
+ case 429:
569
+ return "RateLimit";
570
+ case 401:
571
+ return "Authentication";
572
+ case 403:
573
+ return "ServiceUnavailable";
574
+ case 503:
575
+ return "ServiceUnavailable";
576
+ case 500:
577
+ case 502:
578
+ case 504:
579
+ return "InternalServerError";
580
+ case 400:
581
+ case 422:
582
+ return "InvalidRequest";
583
+ }
584
+ }
585
+ return "Unknown";
586
+ }
587
+ /**
588
+ * Determines the error severity based on the error type.
589
+ * This helps decide whether to retry immediately, wait, or fail permanently.
590
+ *
591
+ * @private
592
+ * @static
593
+ * @param {AIErrorType} errorType - The categorized error type
594
+ * @returns {ErrorSeverity} The severity level of the error
595
+ */
596
+ static determineSeverity(errorType) {
597
+ switch (errorType) {
598
+ case "RateLimit":
599
+ case "NoCredit":
600
+ // Billing/credit errors are retriable with another provider
601
+ case "ServiceUnavailable":
602
+ case "NetworkError":
603
+ case "VendorValidationError":
604
+ return "Retriable";
605
+ case "InternalServerError":
606
+ case "ModelError":
607
+ return "Transient";
608
+ case "Authentication":
609
+ case "NoCredentials":
610
+ // No credentials configured - cannot resolve by retrying
611
+ case "InvalidRequest":
612
+ return "Fatal";
613
+ case "ContextLengthExceeded":
614
+ return "Fatal";
615
+ case "Cancelled":
616
+ return "Fatal";
617
+ default:
618
+ return "Transient";
619
+ }
620
+ }
621
+ /**
622
+ * Determines whether an error can potentially be resolved by switching providers.
623
+ *
624
+ * Strategy: We're permissive with failover - most errors should allow trying another
625
+ * provider/model since vendors may use different status codes and error messages.
626
+ * Only block failover for clear client-side structural errors that won't be fixed by switching.
627
+ *
628
+ * @private
629
+ * @static
630
+ * @param {AIErrorType} errorType - The categorized error type
631
+ * @returns {boolean} True if failover might help, false otherwise
632
+ */
633
+ static canFailoverForError(errorType) {
634
+ switch (errorType) {
635
+ // Provider-specific errors - ALWAYS allow failover
636
+ case "RateLimit":
637
+ // Different provider may have capacity
638
+ case "NoCredit":
639
+ // Different provider may have credits/quota
640
+ case "ServiceUnavailable":
641
+ // Different provider may be available
642
+ case "InternalServerError":
643
+ // Different provider may be stable
644
+ case "NetworkError":
645
+ // Different provider/region may be reachable
646
+ case "ModelError":
647
+ // Different provider may have working model
648
+ case "ContextLengthExceeded":
649
+ // Different model may have larger context
650
+ case "Authentication":
651
+ // Different vendor may have valid API key
652
+ case "VendorValidationError":
653
+ return true;
654
+ // Clear client-side structural/configuration errors - DO NOT failover (won't help)
655
+ case "InvalidRequest":
656
+ // Malformed JSON/syntax won't work anywhere
657
+ case "NoCredentials":
658
+ // No credentials configured for ANY provider - failover won't help
659
+ case "Cancelled":
660
+ return false;
661
+ // Unknown errors - DEFAULT to allowing failover (permissive approach)
662
+ default:
663
+ return true;
664
+ }
665
+ }
666
+ /**
667
+ * Extracts suggested retry delay from error response.
668
+ * Looks for Retry-After headers and provider-specific retry delay fields.
669
+ *
670
+ * @private
671
+ * @static
672
+ * @param {any} error - The error object to extract retry delay from
673
+ * @returns {number | undefined} Suggested retry delay in seconds, or undefined
674
+ */
675
+ static extractRetryDelay(error) {
676
+ const retryAfter = error?.response?.headers?.["retry-after"] || error?.headers?.["retry-after"];
677
+ if (retryAfter) {
678
+ const seconds = parseInt(retryAfter);
679
+ if (!isNaN(seconds)) {
680
+ return seconds;
681
+ }
682
+ return 60;
683
+ }
684
+ if (error?.retryDelay) {
685
+ return error.retryDelay;
686
+ }
687
+ const statusCode = this.extractHttpStatusCode(error);
688
+ if (statusCode === 429) {
689
+ return 30;
690
+ }
691
+ return void 0;
692
+ }
693
+ /**
694
+ * Extracts the provider-specific error code from the error object.
695
+ * Different providers store error codes in different locations.
696
+ *
697
+ * @private
698
+ * @static
699
+ * @param {any} error - The error object to extract provider code from
700
+ * @returns {string | undefined} The provider error code if found, undefined otherwise
701
+ */
702
+ static extractProviderErrorCode(error) {
703
+ const code = error?.code || error?.errorCode || error?.error?.code || error?.response?.data?.error?.code || void 0;
704
+ if (code) {
705
+ return code;
706
+ }
707
+ const errorMessage = error?.message || error?.errorMessage || "";
708
+ if (errorMessage.includes("{") && errorMessage.includes("}")) {
709
+ try {
710
+ const jsonSpan = FirstBraceSpanOnOneLine(errorMessage);
711
+ if (jsonSpan) {
712
+ const parsed = JSON.parse(jsonSpan);
713
+ return parsed?.error?.code || parsed?.code || void 0;
714
+ }
715
+ } catch (parseError) {
716
+ }
717
+ }
718
+ return void 0;
719
+ }
720
+ };
721
+
722
+ // ../../AI/Core/dist/generic/baseLLM.js
723
+ var BaseLLM = class extends BaseModel {
724
+ constructor() {
725
+ super(...arguments);
726
+ this._additionalSettings = {};
727
+ this.thinkingStreamState = null;
728
+ }
729
+ static {
730
+ this.ReplayedReasoningRequiresStablePrefix = false;
731
+ }
732
+ /**
733
+ * Get the current additional settings
734
+ */
735
+ get AdditionalSettings() {
736
+ return this._additionalSettings;
737
+ }
738
+ /**
739
+ * Set additional provider-specific settings
740
+ * Subclasses should override this method to validate required settings
741
+ *
742
+ * @param settings Provider-specific settings
743
+ */
744
+ SetAdditionalSettings(settings) {
745
+ this._additionalSettings = { ...this._additionalSettings, ...settings };
746
+ }
747
+ /**
748
+ * Clear all additional settings
749
+ * This is useful for resetting the state of the provider
750
+ * or when switching between different configurations.
751
+ */
752
+ ClearAdditionalSettings() {
753
+ this._additionalSettings = {};
754
+ }
755
+ /**
756
+ * Process a chat completion request. If streaming is enabled and supported,
757
+ * this will route to the streaming implementation.
758
+ */
759
+ async ChatCompletion(params) {
760
+ if (params.enableCaching === void 0) {
761
+ params.enableCaching = true;
762
+ }
763
+ if (this.SupportsTools) {
764
+ ValidateToolConversation(params.messages);
765
+ }
766
+ const toolsRequested = this.SupportsTools && params.tools != null && params.tools.length > 0;
767
+ const streamingSuppressedForTools = toolsRequested && params.streaming === true;
768
+ const signal = params.cancellationToken;
769
+ if (signal?.aborted) {
770
+ return this.BuildCancelledChatResult(/* @__PURE__ */ new Date(), { signal });
771
+ }
772
+ if (params.streaming && params.streamingCallbacks && this.SupportsStreaming && !streamingSuppressedForTools) {
773
+ return this.handleStreamingChatCompletion(params);
774
+ }
775
+ const startTime = /* @__PURE__ */ new Date();
776
+ let result;
777
+ try {
778
+ result = await this.nonStreamingChatCompletion(params);
779
+ } catch (error) {
780
+ if (this.IsCancellationError(error, signal)) {
781
+ return this.BuildCancelledChatResult(startTime, { error, signal });
782
+ }
783
+ throw error;
784
+ }
785
+ if (!result.success && signal?.aborted) {
786
+ return this.BuildCancelledChatResult(result.startTime ?? startTime, { error: result.exception, signal, message: result.errorMessage });
787
+ }
788
+ if (streamingSuppressedForTools) {
789
+ result.modelSpecificResponseDetails = {
790
+ ...result.modelSpecificResponseDetails,
791
+ streamingSuppressedForTools: true
792
+ };
793
+ }
794
+ return result;
795
+ }
796
+ /**
797
+ * Process multiple chat completion requests in parallel. This is useful for:
798
+ * - Generating multiple variations with different parameters (temperature, etc.)
799
+ * - Getting multiple responses to compare or select from
800
+ * - Improving reliability by sending the same request multiple times
801
+ *
802
+ * @param paramsArray Array of chat completion parameter objects
803
+ * @param callbacks Optional callbacks for progress and individual completions
804
+ * @returns Promise resolving to an array of ChatResults in the same order as the input params
805
+ */
806
+ async ChatCompletions(paramsArray, callbacks) {
807
+ if (!paramsArray || paramsArray.length === 0) {
808
+ return [];
809
+ }
810
+ const promises = paramsArray.map((params, index) => this.ChatCompletion(params).then((response) => {
811
+ if (callbacks?.OnCompletion) {
812
+ callbacks.OnCompletion(response, index);
813
+ }
814
+ return response;
815
+ }).catch((error) => {
816
+ if (callbacks?.OnError) {
817
+ callbacks.OnError(error, index);
818
+ }
819
+ throw error;
820
+ }));
821
+ const results = await Promise.allSettled(promises);
822
+ const chatResults = results.map((result, index) => {
823
+ if (result.status === "fulfilled") {
824
+ return result.value;
825
+ } else {
826
+ const startTime = /* @__PURE__ */ new Date();
827
+ const endTime = /* @__PURE__ */ new Date();
828
+ const errorResult = new ChatResult(false, startTime, endTime);
829
+ errorResult.data = {
830
+ choices: [],
831
+ usage: new ModelUsage(0, 0)
832
+ };
833
+ errorResult.statusText = "error";
834
+ errorResult.errorMessage = result.reason?.message || "Unknown error";
835
+ errorResult.exception = { exception: result.reason };
836
+ errorResult.errorInfo = ErrorAnalyzer.analyzeError(result.reason, this.constructor.name);
837
+ return errorResult;
838
+ }
839
+ });
840
+ if (callbacks?.OnAllCompleted) {
841
+ callbacks.OnAllCompleted(chatResults);
842
+ }
843
+ return chatResults;
844
+ }
845
+ /**
846
+ * Check if this provider supports streaming
847
+ * @returns true if streaming is supported, false otherwise
848
+ */
849
+ get SupportsStreaming() {
850
+ return false;
851
+ }
852
+ /**
853
+ * Whether this driver implements native tool/function calling — i.e. whether it maps
854
+ * `ChatParams.tools` onto its SDK and normalizes tool calls back into
855
+ * `ChatCompletionMessage.toolCalls`.
856
+ *
857
+ * This is a CODE-level capability ("has the mapping been written for this driver?"), distinct
858
+ * from the METADATA-level capability `ModelConfiguration.LLM.SupportsNativeToolCalling`
859
+ * ("does this model, on this vendor, support tools at all?"). Both must hold for native mode.
860
+ *
861
+ * A driver returning false ignores any `tools` passed to it and records the fact in the
862
+ * result's `modelSpecificResponseDetails` — the prompt runner's gate should keep that from
863
+ * happening, but the layer is safe standalone.
864
+ */
865
+ get SupportsTools() {
866
+ return false;
867
+ }
868
+ /**
869
+ * Whether this LLM provider supports assistant prefill (pre-seeding the start of the model's response).
870
+ * Providers that support prefill should override this to return true.
871
+ * This is used as a code-level default when database metadata (AIModelType/AIModel/AIModelVendor.SupportsPrefill)
872
+ * is null. Database values of true/false override this getter.
873
+ */
874
+ get SupportsPrefill() {
875
+ return false;
876
+ }
877
+ /**
878
+ * Whether a message is framework-authored volatile state for this request only: the loop agent's
879
+ * trailing runtime-state fragment (date/time, scratchpad, payload, a relocated specialization),
880
+ * which is rebuilt every iteration and must never sit inside a provider's cached prefix.
881
+ *
882
+ * The base test is the {@link VolatileStateMessageMetadata.volatileState} flag the agent layer
883
+ * sets. A driver may override to recognise the message by other means (Anthropic also accepts
884
+ * the fragment's tag literal for callers that pass plain text), but should call `super` first.
885
+ */
886
+ isVolatileStateMessage(message) {
887
+ const metadata = message?.metadata;
888
+ return metadata?.volatileState === true;
889
+ }
890
+ /**
891
+ * Index of the trailing volatile-state message in an outgoing request, or -1 when there is none.
892
+ * The fragment is the LAST message, or the one before it when an assistant prefill has been
893
+ * appended after it. -1 is also returned when nothing precedes the fragment, because then there
894
+ * is no stable history for a provider to cache ahead of it.
895
+ */
896
+ trailingVolatileStateIndex(messages) {
897
+ const last = messages.length - 1;
898
+ if (last >= 1 && this.isVolatileStateMessage(messages[last])) {
899
+ return last;
900
+ }
901
+ if (last >= 2 && messages[last].role === ChatMessageRole.assistant && this.isVolatileStateMessage(messages[last - 1])) {
902
+ return last - 1;
903
+ }
904
+ return -1;
905
+ }
906
+ /**
907
+ * Splits an outgoing request around its trailing volatile-state message, so a driver can place
908
+ * its cache boundary on the last message of `head` and send `tail` uncached. Returns null when the
909
+ * request has no trailing volatile state, in which case the driver formats the whole list as usual.
910
+ * Neither array is a copy of the input's messages; only the list is new.
911
+ */
912
+ splitTrailingVolatileState(messages) {
913
+ const index = this.trailingVolatileStateIndex(messages);
914
+ if (index < 1) {
915
+ return null;
916
+ }
917
+ return { head: messages.slice(0, index), tail: messages.slice(index) };
918
+ }
919
+ /**
920
+ * Returns the native file input capabilities of this LLM driver, or null if
921
+ * the driver does not support file attachments. Subclasses that accept files
922
+ * (PDFs, images, etc.) should override this method.
923
+ */
924
+ GetFileCapabilities() {
925
+ return null;
926
+ }
927
+ /**
928
+ * Template method for handling streaming chat completion
929
+ * This implements the common pattern across providers while delegating
930
+ * provider-specific logic to abstract methods.
931
+ */
932
+ async handleStreamingChatCompletion(params) {
933
+ const startTime = /* @__PURE__ */ new Date();
934
+ this.resetStreamingState();
935
+ return new Promise((resolve, reject) => {
936
+ (async () => {
937
+ try {
938
+ const stream = await this.createStreamingRequest(params);
939
+ let accumulatedContent = "";
940
+ let lastChunk = null;
941
+ let usage = {
942
+ promptTokens: 0,
943
+ completionTokens: 0,
944
+ totalTokens: 0
945
+ };
946
+ if (!stream) {
947
+ throw new Error("Stream is null or undefined");
948
+ }
949
+ try {
950
+ for await (const chunk of stream) {
951
+ if (chunk) {
952
+ const processed = this.processStreamingChunk(chunk);
953
+ if (processed?.content) {
954
+ accumulatedContent += processed.content;
955
+ if (params.streamingCallbacks?.OnContent) {
956
+ params.streamingCallbacks.OnContent(processed.content, false);
957
+ }
958
+ }
959
+ lastChunk = chunk;
960
+ if (processed?.usage) {
961
+ usage = processed.usage;
962
+ }
963
+ }
964
+ }
965
+ } catch (streamError) {
966
+ if (!params.cancellationToken?.aborted) {
967
+ throw streamError;
968
+ }
969
+ }
970
+ if (params.cancellationToken?.aborted) {
971
+ this.settleCancelledStream(params, this.BuildCancelledChatResult(startTime, { signal: params.cancellationToken }), resolve);
972
+ return;
973
+ }
974
+ const thinkingRemainder = this.flushThinkingStreamRemainder();
975
+ if (thinkingRemainder) {
976
+ accumulatedContent += thinkingRemainder;
977
+ if (params.streamingCallbacks?.OnContent) {
978
+ params.streamingCallbacks.OnContent(thinkingRemainder, false);
979
+ }
980
+ }
981
+ if (params.streamingCallbacks?.OnContent) {
982
+ params.streamingCallbacks.OnContent("", true);
983
+ }
984
+ const endTime = /* @__PURE__ */ new Date();
985
+ const result = this.finalizeStreamingResponse(accumulatedContent, lastChunk, usage);
986
+ if (!result) {
987
+ throw new Error("Failed to create result");
988
+ }
989
+ result.startTime = startTime;
990
+ result.endTime = endTime;
991
+ if (params.streamingCallbacks?.OnComplete) {
992
+ params.streamingCallbacks.OnComplete(result);
993
+ }
994
+ resolve(result);
995
+ } catch (error) {
996
+ if (this.IsCancellationError(error, params.cancellationToken)) {
997
+ this.settleCancelledStream(params, this.BuildCancelledChatResult(startTime, { error, signal: params.cancellationToken }), resolve);
998
+ return;
999
+ }
1000
+ if (params.streamingCallbacks?.OnError) {
1001
+ params.streamingCallbacks.OnError(error);
1002
+ }
1003
+ const endTime = /* @__PURE__ */ new Date();
1004
+ const errorResult = new ChatResult(false, startTime, endTime);
1005
+ errorResult.data = {
1006
+ choices: [],
1007
+ usage: new ModelUsage(0, 0)
1008
+ };
1009
+ errorResult.statusText = "error";
1010
+ errorResult.errorMessage = error?.message || "Unknown error";
1011
+ errorResult.exception = { exception: error };
1012
+ errorResult.errorInfo = ErrorAnalyzer.analyzeError(error, this.constructor.name);
1013
+ reject(errorResult);
1014
+ } finally {
1015
+ try {
1016
+ this.resetStreamingState();
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ })();
1021
+ });
1022
+ }
1023
+ /**
1024
+ * Hook invoked at the start AND end (in `finally`) of every streaming chat
1025
+ * completion to reset per-request streaming state. Default is a no-op;
1026
+ * providers that maintain instance-level streaming state (e.g., Anthropic /
1027
+ * OpenAI thinking-block accumulators) MUST override this. See audit R2-C5
1028
+ * for context — without this, state from a prior request bleeds into the
1029
+ * next one and accumulated buffers grow unbounded across the singleton's
1030
+ * lifetime.
1031
+ */
1032
+ resetStreamingState() {
1033
+ }
1034
+ /**
1035
+ * Settles a cancelled stream: resolves the request first, then tells `OnComplete`. A callback that
1036
+ * throws is logged, never allowed to leave the request unsettled.
1037
+ */
1038
+ settleCancelledStream(params, cancelled, resolve) {
1039
+ resolve(cancelled);
1040
+ try {
1041
+ params.streamingCallbacks?.OnComplete?.(cancelled);
1042
+ } catch (callbackError) {
1043
+ console.error("OnComplete threw for a cancelled stream:", callbackError);
1044
+ }
1045
+ }
1046
+ /**
1047
+ * Whether a request ended because it was aborted: its signal is aborted, the error is an abort by
1048
+ * name (`AbortError`, `APIUserAbortError`, `RequestAbortedError`), or {@link IsProviderAbortError}
1049
+ * recognizes it.
1050
+ */
1051
+ IsCancellationError(error, signal) {
1052
+ return signal?.aborted === true || IsAbortError(error) || this.IsProviderAbortError(error);
1053
+ }
1054
+ /**
1055
+ * Recognizes an abort error the shared names miss. A driver whose SDK raises its own class for an
1056
+ * aborted request overrides this. Default: false.
1057
+ */
1058
+ IsProviderAbortError(_error) {
1059
+ return false;
1060
+ }
1061
+ /**
1062
+ * The result of a cancelled request, the same for every driver: a failed result with no choices,
1063
+ * `statusText` `'cancelled'`, and an `errorInfo` that says `Cancelled` (Fatal, never failed over) —
1064
+ * or, when the signal was aborted by a timeout, a retriable `NetworkError`.
1065
+ *
1066
+ * @param options.error The error the abort raised, kept on `exception`.
1067
+ * @param options.signal The request's abort signal, read for why it was aborted.
1068
+ * @param options.message The message to report; defaults to one naming the signal's reason.
1069
+ */
1070
+ BuildCancelledChatResult(startTime, options = {}) {
1071
+ const result = new ChatResult(false, startTime, /* @__PURE__ */ new Date());
1072
+ result.data = {
1073
+ choices: [],
1074
+ usage: new ModelUsage(0, 0)
1075
+ };
1076
+ result.statusText = "cancelled";
1077
+ result.errorMessage = options.message || DescribeCancellation(options.signal);
1078
+ result.exception = options.error;
1079
+ result.errorInfo = BuildCancellationErrorInfo({ error: options.error, signal: options.signal, provider: this.constructor.name });
1080
+ return result;
1081
+ }
1082
+ /**
1083
+ * Check if the provider supports thinking models
1084
+ * Providers should override this to return true if they support thinking extraction
1085
+ */
1086
+ supportsThinkingModels() {
1087
+ return false;
1088
+ }
1089
+ /**
1090
+ * Get the thinking tag format for this provider
1091
+ * Providers can override this to customize the thinking tag format
1092
+ */
1093
+ getThinkingTagFormat() {
1094
+ return { open: "<think>", close: "</think>" };
1095
+ }
1096
+ /**
1097
+ * Extract thinking content from non-streaming content
1098
+ * This method handles case-insensitive extraction of thinking blocks
1099
+ */
1100
+ extractThinkingFromContent(content) {
1101
+ if (!content)
1102
+ return {
1103
+ content,
1104
+ thinking: void 0
1105
+ };
1106
+ let processedContent = content.trim();
1107
+ let thinkingContent = void 0;
1108
+ const tags = this.getThinkingTagFormat();
1109
+ const openTagLower = tags.open.toLowerCase();
1110
+ const closeTagLower = tags.close.toLowerCase();
1111
+ const contentLower = processedContent.toLowerCase();
1112
+ if (contentLower.startsWith(openTagLower) && contentLower.includes(closeTagLower)) {
1113
+ const thinkStart = tags.open.length;
1114
+ const thinkEndIndex = contentLower.indexOf(closeTagLower);
1115
+ if (thinkEndIndex !== -1) {
1116
+ thinkingContent = processedContent.substring(thinkStart, thinkEndIndex).trim();
1117
+ processedContent = processedContent.substring(thinkEndIndex + tags.close.length).trim();
1118
+ }
1119
+ }
1120
+ return { content: processedContent, thinking: thinkingContent };
1121
+ }
1122
+ /**
1123
+ * Initialize thinking stream state for streaming extraction
1124
+ */
1125
+ initializeThinkingStreamState() {
1126
+ this.thinkingStreamState = {
1127
+ accumulatedThinking: "",
1128
+ inThinkingBlock: false,
1129
+ pendingContent: "",
1130
+ thinkingComplete: false
1131
+ };
1132
+ }
1133
+ /**
1134
+ * Reset thinking stream state
1135
+ */
1136
+ resetThinkingStreamState() {
1137
+ this.thinkingStreamState = null;
1138
+ }
1139
+ /**
1140
+ * Returns (and clears) any user-visible content the thinking-tag stripper is still holding back at
1141
+ * the end of a stream. Mid-stream, {@link processStreamChunkWithThinking} holds back a trailing
1142
+ * fragment that could be the start of a `<think>`/`</think>` tag so a split tag never leaks as
1143
+ * partial text; once the stream ends, such a fragment is real content and must be emitted (bug A5).
1144
+ *
1145
+ * Only flushes when NOT inside a thinking block: an unterminated `<think>` block's buffered text is
1146
+ * reasoning, not answer, and is left held back (never surfaced as visible content). Returns `''`
1147
+ * when thinking extraction isn't active (no state) or there's nothing to flush.
1148
+ */
1149
+ flushThinkingStreamRemainder() {
1150
+ const state = this.thinkingStreamState;
1151
+ if (!state || state.inThinkingBlock) {
1152
+ return "";
1153
+ }
1154
+ const remainder = state.pendingContent;
1155
+ state.pendingContent = "";
1156
+ return remainder;
1157
+ }
1158
+ /**
1159
+ * Process streaming chunk with thinking extraction
1160
+ * This method handles case-insensitive extraction across chunk boundaries
1161
+ */
1162
+ processStreamChunkWithThinking(rawContent) {
1163
+ if (!this.thinkingStreamState) {
1164
+ return rawContent;
1165
+ }
1166
+ let contentToEmit = "";
1167
+ this.thinkingStreamState.pendingContent += rawContent;
1168
+ if (this.thinkingStreamState.thinkingComplete) {
1169
+ contentToEmit = this.thinkingStreamState.pendingContent;
1170
+ this.thinkingStreamState.pendingContent = "";
1171
+ return contentToEmit;
1172
+ }
1173
+ const tags = this.getThinkingTagFormat();
1174
+ const pending = this.thinkingStreamState.pendingContent;
1175
+ const pendingLower = pending.toLowerCase();
1176
+ if (!this.thinkingStreamState.inThinkingBlock && pendingLower.includes(tags.open.toLowerCase())) {
1177
+ const thinkStartIndex = pendingLower.indexOf(tags.open.toLowerCase());
1178
+ if (thinkStartIndex > 0) {
1179
+ contentToEmit = pending.substring(0, thinkStartIndex);
1180
+ this.thinkingStreamState.pendingContent = pending.substring(thinkStartIndex);
1181
+ return contentToEmit;
1182
+ }
1183
+ this.thinkingStreamState.inThinkingBlock = true;
1184
+ this.thinkingStreamState.pendingContent = pending.substring(tags.open.length);
1185
+ }
1186
+ if (this.thinkingStreamState.inThinkingBlock) {
1187
+ const pendingInBlockLower = this.thinkingStreamState.pendingContent.toLowerCase();
1188
+ const thinkEndIndex = pendingInBlockLower.indexOf(tags.close.toLowerCase());
1189
+ if (thinkEndIndex !== -1) {
1190
+ this.thinkingStreamState.accumulatedThinking += this.thinkingStreamState.pendingContent.substring(0, thinkEndIndex);
1191
+ this.thinkingStreamState.pendingContent = this.thinkingStreamState.pendingContent.substring(thinkEndIndex + tags.close.length);
1192
+ this.thinkingStreamState.inThinkingBlock = false;
1193
+ this.thinkingStreamState.thinkingComplete = true;
1194
+ return this.processStreamChunkWithThinking("");
1195
+ } else {
1196
+ const closeHoldBack = this.partialTagSuffixLength(this.thinkingStreamState.pendingContent, tags.close);
1197
+ const closeCut = this.thinkingStreamState.pendingContent.length - closeHoldBack;
1198
+ this.thinkingStreamState.accumulatedThinking += this.thinkingStreamState.pendingContent.substring(0, closeCut);
1199
+ this.thinkingStreamState.pendingContent = this.thinkingStreamState.pendingContent.substring(closeCut);
1200
+ return "";
1201
+ }
1202
+ }
1203
+ const openHoldBack = this.partialTagSuffixLength(this.thinkingStreamState.pendingContent, tags.open);
1204
+ let emitEnd = this.avoidSurrogateSplit(this.thinkingStreamState.pendingContent, this.thinkingStreamState.pendingContent.length - openHoldBack);
1205
+ contentToEmit = this.thinkingStreamState.pendingContent.substring(0, emitEnd);
1206
+ this.thinkingStreamState.pendingContent = this.thinkingStreamState.pendingContent.substring(emitEnd);
1207
+ return contentToEmit;
1208
+ }
1209
+ /**
1210
+ * Returns the length of the longest suffix of `text` that is a non-empty prefix of `tag`
1211
+ * (case-insensitive). Used to detect a tag that may be split across streaming chunk
1212
+ * boundaries so the caller can hold that partial fragment back instead of emitting it.
1213
+ * Returns 0 when no suffix of `text` is a prefix of `tag`.
1214
+ */
1215
+ partialTagSuffixLength(text, tag) {
1216
+ if (!text || !tag) {
1217
+ return 0;
1218
+ }
1219
+ const t = text.toLowerCase();
1220
+ const g = tag.toLowerCase();
1221
+ const max = Math.min(t.length, g.length - 1);
1222
+ for (let len = max; len > 0; len--) {
1223
+ if (t.endsWith(g.substring(0, len))) {
1224
+ return len;
1225
+ }
1226
+ }
1227
+ return 0;
1228
+ }
1229
+ /**
1230
+ * Defensive guard for the tag hold-back: if the emit/hold boundary would fall *between* the two
1231
+ * halves of a UTF-16 surrogate pair (which renders as U+FFFD), move the cut back by one so the
1232
+ * full pair stays together in the held-back remainder. In the common case the held-back fragment
1233
+ * is an ASCII tag prefix (e.g. "<thi"), so `cut` lands on an ASCII boundary and this is a no-op;
1234
+ * it only bites when a hold-back boundary happens to land mid-pair. Note it does NOT hold back a
1235
+ * lone trailing high surrogate when nothing else is being held back (holdBack === 0) — a pair
1236
+ * split exactly at a streaming chunk boundary is left to render and is corrected by the next chunk.
1237
+ */
1238
+ avoidSurrogateSplit(text, cut) {
1239
+ if (cut > 0 && cut < text.length) {
1240
+ const code = text.charCodeAt(cut - 1);
1241
+ if (code >= 55296 && code <= 56319) {
1242
+ return cut - 1;
1243
+ }
1244
+ }
1245
+ return cut;
1246
+ }
1247
+ /**
1248
+ * Add thinking content to a chat completion message
1249
+ */
1250
+ addThinkingToMessage(message, thinkingContent) {
1251
+ if (thinkingContent) {
1252
+ message.thinking = thinkingContent;
1253
+ }
1254
+ return message;
1255
+ }
1256
+ };
1257
+
1258
+ // ../../AI/Core/dist/generic/apiKeyDictionary.js
1259
+ var AIAPIKeys = class _AIAPIKeys {
1260
+ static {
1261
+ this._apiKeyPrefix = "AI_VENDOR_API_KEY__";
1262
+ }
1263
+ static {
1264
+ this._cachedAPIKeys = {};
1265
+ }
1266
+ GetCachedAPIKey(AIDriverName) {
1267
+ const normalizedKey = AIDriverName.toUpperCase();
1268
+ return _AIAPIKeys._cachedAPIKeys[normalizedKey];
1269
+ }
1270
+ SetCachedAPIKey(AIDriverName, value) {
1271
+ const normalizedKey = AIDriverName.toUpperCase();
1272
+ _AIAPIKeys._cachedAPIKeys[normalizedKey] = value;
1273
+ }
1274
+ GetAPIKey(AIDriverName) {
1275
+ if (!AIDriverName) {
1276
+ return void 0;
1277
+ }
1278
+ const normalizedKey = AIDriverName.toUpperCase();
1279
+ const cached = this.GetCachedAPIKey(normalizedKey);
1280
+ if (cached) {
1281
+ return cached;
1282
+ } else {
1283
+ const envKey = _AIAPIKeys._apiKeyPrefix + normalizedKey;
1284
+ const value = this.getEnvVariableCaseInsensitive(envKey);
1285
+ if (value) {
1286
+ this.SetCachedAPIKey(normalizedKey, value);
1287
+ return value;
1288
+ } else
1289
+ return void 0;
1290
+ }
1291
+ }
1292
+ getEnvVariableCaseInsensitive(name) {
1293
+ const upperName = name.toUpperCase();
1294
+ const envKey = Object.keys(process.env).find((key) => key.toUpperCase() === upperName);
1295
+ return envKey ? process.env[envKey] : void 0;
1296
+ }
1297
+ };
1298
+
1299
+ // ../../AI/Core/dist/generic/realtimeUsage.js
1300
+ function usageHasTokenCounts(usage) {
1301
+ if (usage === null || typeof usage !== "object") {
1302
+ return false;
1303
+ }
1304
+ const candidate = usage;
1305
+ return typeof candidate.input_tokens === "number" || typeof candidate.output_tokens === "number";
1306
+ }
1307
+ function ResolveResponseDoneUsage(event) {
1308
+ if (usageHasTokenCounts(event.response?.usage)) {
1309
+ return event.response?.usage;
1310
+ }
1311
+ return usageHasTokenCounts(event.usage) ? event.usage : void 0;
1312
+ }
1313
+
1314
+ // ../../AI/Core/dist/generic/modelConfiguration.js
1315
+ function ParseModelConfiguration(json) {
1316
+ return parseConfigurationBag(json, "ParseModelConfiguration", "ModelConfiguration");
1317
+ }
1318
+ function ParseVendorConfiguration(json) {
1319
+ return parseConfigurationBag(json, "ParseVendorConfiguration", "Configuration");
1320
+ }
1321
+ function parseConfigurationBag(json, caller, column) {
1322
+ if (typeof json !== "string" || json.trim().length === 0) {
1323
+ return null;
1324
+ }
1325
+ try {
1326
+ const parsed = JSON.parse(json);
1327
+ if (IsPlainObject(parsed)) {
1328
+ return parsed;
1329
+ }
1330
+ console.warn(`[${caller}] ${column} JSON is not a plain object; skipping layer.`);
1331
+ return null;
1332
+ } catch (err) {
1333
+ console.warn(`[${caller}] Failed to parse ${column} JSON; skipping malformed layer:`, err);
1334
+ return null;
1335
+ }
1336
+ }
1337
+ function ResolveEffectiveModelConfiguration(...layers) {
1338
+ const result = {};
1339
+ for (const layer of layers) {
1340
+ if (!IsPlainObject(layer)) {
1341
+ continue;
1342
+ }
1343
+ mergeInto(result, layer);
1344
+ }
1345
+ return Object.keys(result).length > 0 ? result : null;
1346
+ }
1347
+ function mergeInto(target, source) {
1348
+ for (const key of Object.keys(source)) {
1349
+ const incoming = source[key];
1350
+ if (incoming === void 0) {
1351
+ continue;
1352
+ }
1353
+ const existing = target[key];
1354
+ if (IsPlainObject(existing) && IsPlainObject(incoming)) {
1355
+ mergeInto(existing, incoming);
1356
+ } else if (IsPlainObject(incoming)) {
1357
+ const copy = {};
1358
+ mergeInto(copy, incoming);
1359
+ target[key] = copy;
1360
+ } else if (Array.isArray(incoming)) {
1361
+ target[key] = incoming.slice();
1362
+ } else {
1363
+ target[key] = incoming;
1364
+ }
1365
+ }
1366
+ }
1367
+
1368
+ // ../../AI/Core/dist/generic/transcriptContinuation.js
1369
+ function IsTranscriptContinuation(previous, next) {
1370
+ if (!previous || !next) {
1371
+ return false;
1372
+ }
1373
+ const before = NormalizeTranscriptForComparison(previous);
1374
+ const after = NormalizeTranscriptForComparison(next);
1375
+ if (!before || !after) {
1376
+ return false;
1377
+ }
1378
+ return after.length > before.length && after.startsWith(before);
1379
+ }
1380
+ function NormalizeTranscriptForComparison(value) {
1381
+ return value.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
1382
+ }
1383
+
1384
+ // ../../AI/Core/dist/generic/realtimeProxyRegistry.js
1385
+ var REALTIME_RELAY_TIME_LIMIT_CLOSE_CODE = 4e3;
1386
+
1387
+ // ../../AI/Core/dist/generic/realtimeToolBatchBarrier.js
1388
+ var RealtimeToolBatchBarrier = class {
1389
+ /**
1390
+ * @param defaultTimeoutMs Maximum time in milliseconds to wait for remaining tool results before flushing (default 15s).
1391
+ */
1392
+ constructor(defaultTimeoutMs = 15e3) {
1393
+ this._outstanding = /* @__PURE__ */ new Set();
1394
+ this._timer = null;
1395
+ this._timeoutMs = defaultTimeoutMs;
1396
+ }
1397
+ /**
1398
+ * Records an outstanding tool call ID that must be answered.
1399
+ * @param callId The tool call identifier.
1400
+ * @param onTimeoutFlush Optional callback to invoke if the batch times out waiting for remaining results.
1401
+ */
1402
+ Opened(callId, onTimeoutFlush) {
1403
+ if (!callId) {
1404
+ return;
1405
+ }
1406
+ this._outstanding.add(callId);
1407
+ if (onTimeoutFlush) {
1408
+ this._onTimeoutFlush = onTimeoutFlush;
1409
+ }
1410
+ this.resetTimeout();
1411
+ }
1412
+ /**
1413
+ * Alias for Opened.
1414
+ */
1415
+ TrackPendingCall(callId, onTimeoutFlush) {
1416
+ this.Opened(callId, onTimeoutFlush);
1417
+ }
1418
+ /**
1419
+ * Returns true when THIS result drains the batch and the caller should send response.create.
1420
+ * If the call was not in the outstanding set (unknown or duplicate), returns false.
1421
+ *
1422
+ * @param callId The tool call identifier.
1423
+ */
1424
+ Closed(callId) {
1425
+ if (!this._outstanding.delete(callId)) {
1426
+ return false;
1427
+ }
1428
+ if (this._outstanding.size === 0) {
1429
+ this.clearTimeout();
1430
+ return true;
1431
+ }
1432
+ this.resetTimeout();
1433
+ return false;
1434
+ }
1435
+ /**
1436
+ * Alias for Closed.
1437
+ */
1438
+ RecordResult(callId) {
1439
+ return this.Closed(callId);
1440
+ }
1441
+ /**
1442
+ * Returns true if there are no outstanding tool calls.
1443
+ */
1444
+ get IsEmpty() {
1445
+ return this._outstanding.size === 0;
1446
+ }
1447
+ /**
1448
+ * Number of tool calls still awaiting results in the current batch.
1449
+ */
1450
+ get PendingCount() {
1451
+ return this._outstanding.size;
1452
+ }
1453
+ /**
1454
+ * Arms or re-arms the timeout with a custom duration and callback.
1455
+ */
1456
+ ArmTimeout(ms, onTimeoutFlush) {
1457
+ this._timeoutMs = ms;
1458
+ this._onTimeoutFlush = onTimeoutFlush;
1459
+ this.resetTimeout();
1460
+ }
1461
+ /**
1462
+ * Resets the barrier state and cancels any pending timeout (alias for Clear).
1463
+ */
1464
+ Reset() {
1465
+ this.Clear();
1466
+ }
1467
+ /**
1468
+ * Clears all pending tool calls and cancels any active timeout (e.g. on user interruption,
1469
+ * error, or session close).
1470
+ */
1471
+ Clear() {
1472
+ this._outstanding.clear();
1473
+ this.clearTimeout();
1474
+ }
1475
+ resetTimeout() {
1476
+ this.clearTimeout();
1477
+ if (this._outstanding.size > 0 && this._onTimeoutFlush) {
1478
+ this._timer = setTimeout(() => {
1479
+ this._timer = null;
1480
+ if (this._outstanding.size > 0) {
1481
+ this._outstanding.clear();
1482
+ this._onTimeoutFlush?.();
1483
+ }
1484
+ }, this._timeoutMs);
1485
+ }
1486
+ }
1487
+ clearTimeout() {
1488
+ if (this._timer) {
1489
+ clearTimeout(this._timer);
1490
+ this._timer = null;
1491
+ }
1492
+ }
1493
+ };
1494
+
1495
+ // ../../AI/Core/dist/generic/realtimeSessionResumption.js
1496
+ var REALTIME_RESUMPTION_RETRY_DELAYS_MS = [0, 1e3, 3e3];
1497
+ var REALTIME_RESUMPTION_ATTEMPT_TIMEOUT_MS = 15e3;
1498
+ var REALTIME_RESUMPTION_DEADLINE_MARGIN_MS = 1e4;
1499
+ var REALTIME_RESUMPTION_MIN_DEADLINE_MARGIN_MS = 1500;
1500
+ var DEADLINE_MARGIN_SHARE_OF_NOTICE = 1 / 3;
1501
+ var UNKNOWN_TIME_LEFT_DELAY_MS = 3e4;
1502
+ function ParseDurationToMs(duration) {
1503
+ const match = duration ? /^(\d+(?:\.\d+)?)s$/.exec(duration.trim()) : null;
1504
+ return match ? Math.round(Number(match[1]) * 1e3) : void 0;
1505
+ }
1506
+ var RealtimeSessionResumption = class {
1507
+ constructor(options) {
1508
+ this._handle = null;
1509
+ this._resumableNow = false;
1510
+ this._turnOpen = false;
1511
+ this._endingAnnounced = false;
1512
+ this._reconnecting = false;
1513
+ this._exhausted = false;
1514
+ this._disposed = false;
1515
+ this._deadlineTimer = null;
1516
+ this._retryTimer = null;
1517
+ this._currentAttempt = null;
1518
+ this.options = options;
1519
+ }
1520
+ /** The latest handle the provider issued, or `null` before the first update. */
1521
+ get Handle() {
1522
+ return this._handle;
1523
+ }
1524
+ /** Whether a reconnect is in progress. */
1525
+ get IsReconnecting() {
1526
+ return this._reconnecting;
1527
+ }
1528
+ /**
1529
+ * Records the provider's latest resumption state. `resumable: false` means the session is
1530
+ * mid-generation or mid-tool-call: the previous handle stays as a fallback for a lost
1531
+ * connection, but a planned move waits for the next resumable point. A resumable handle that
1532
+ * arrives while a turn is in progress is kept as that fallback too: resuming from it would cut
1533
+ * the turn, so it is not a point to move at.
1534
+ *
1535
+ * @param handle The new handle, when the provider sent one.
1536
+ * @param resumable Whether the session can be resumed from this point without losing data.
1537
+ */
1538
+ RecordHandle(handle, resumable) {
1539
+ if (!resumable) {
1540
+ this._resumableNow = false;
1541
+ return;
1542
+ }
1543
+ if (handle) {
1544
+ this._handle = handle;
1545
+ this._resumableNow = !this._turnOpen;
1546
+ }
1547
+ if (this._endingAnnounced && this._resumableNow) {
1548
+ this.startReconnect("connection-ending");
1549
+ }
1550
+ }
1551
+ /**
1552
+ * A turn started or continued: the user's first transcribed words, a turn the client sent, or
1553
+ * the model's output. Until {@link TurnEnded}, a planned move waits, whatever the provider's
1554
+ * updates say; the deadline still bounds the wait. Call it for every sign of the turn: a repeat
1555
+ * changes nothing.
1556
+ */
1557
+ TurnStarted() {
1558
+ this._turnOpen = true;
1559
+ this._resumableNow = false;
1560
+ }
1561
+ /**
1562
+ * The turn ended (its `turnComplete`, with no tool call pending). A planned move now happens at
1563
+ * the next resumable handle, which the provider issues after the turn (Vertex AI: 20-70 ms
1564
+ * after `turnComplete`), so the new connection resumes with the finished turn.
1565
+ */
1566
+ TurnEnded() {
1567
+ this._turnOpen = false;
1568
+ }
1569
+ /**
1570
+ * The provider announced the current connection ends in `timeLeftMs`. The helper moves at once
1571
+ * when the latest handle is a resumable point (issued after the last turn ended, with no turn
1572
+ * since); otherwise it waits for one, until the deadline.
1573
+ *
1574
+ * @param timeLeftMs Time until the provider closes the connection; `undefined` when not given.
1575
+ */
1576
+ ConnectionEnding(timeLeftMs) {
1577
+ if (this._disposed || this._exhausted || this._reconnecting) {
1578
+ return;
1579
+ }
1580
+ this._endingAnnounced = true;
1581
+ if (this._resumableNow && this._handle) {
1582
+ this.startReconnect("connection-ending");
1583
+ return;
1584
+ }
1585
+ this.armDeadline(timeLeftMs);
1586
+ }
1587
+ /**
1588
+ * The connection closed without the consumer asking.
1589
+ *
1590
+ * @returns `true` when a reconnect started (or was already running), so the driver reports
1591
+ * "reconnecting" rather than an error; `false` when there is no handle to resume
1592
+ * from, so the driver keeps its fatal path.
1593
+ */
1594
+ ConnectionLost() {
1595
+ if (this._disposed || this._exhausted || !this._handle) {
1596
+ return false;
1597
+ }
1598
+ if (!this._reconnecting) {
1599
+ this.startReconnect("connection-lost");
1600
+ }
1601
+ return true;
1602
+ }
1603
+ /** Cancels timers, abandons any attempt in flight and stops retries. Call when the consumer closes the session. */
1604
+ Dispose() {
1605
+ this._disposed = true;
1606
+ this.clearDeadline();
1607
+ this.clearRetry();
1608
+ if (this._currentAttempt) {
1609
+ this._currentAttempt.Abandoned = true;
1610
+ }
1611
+ }
1612
+ startReconnect(reason) {
1613
+ if (this._reconnecting || this._disposed || this._exhausted || !this._handle) {
1614
+ return;
1615
+ }
1616
+ this._reconnecting = true;
1617
+ this.clearDeadline();
1618
+ this.options.Log?.(`[RealtimeSessionResumption] reconnecting (${reason})`);
1619
+ this.options.OnReconnecting?.(reason);
1620
+ this.scheduleAttempt(0);
1621
+ }
1622
+ scheduleAttempt(index) {
1623
+ this._retryTimer = setTimeout(() => {
1624
+ this._retryTimer = null;
1625
+ void this.attempt(index);
1626
+ }, this.retryDelays()[index]);
1627
+ }
1628
+ async attempt(index) {
1629
+ if (this._disposed || !this._handle) {
1630
+ return;
1631
+ }
1632
+ const attempt = { Abandoned: false };
1633
+ this._currentAttempt = attempt;
1634
+ try {
1635
+ await this.withTimeout(this.options.Reconnect(this._handle, attempt), attempt);
1636
+ this.onAttemptSucceeded();
1637
+ } catch (error) {
1638
+ attempt.Abandoned = true;
1639
+ this.onAttemptFailed(index, error);
1640
+ } finally {
1641
+ if (this._currentAttempt === attempt) {
1642
+ this._currentAttempt = null;
1643
+ }
1644
+ }
1645
+ }
1646
+ /** Rejects when `work` outlives the attempt timeout; the attempt is then abandoned. */
1647
+ withTimeout(work, attempt) {
1648
+ const timeoutMs = this.options.AttemptTimeoutMs ?? REALTIME_RESUMPTION_ATTEMPT_TIMEOUT_MS;
1649
+ work.catch(() => void 0);
1650
+ let timer = null;
1651
+ const timeout = new Promise((_resolve, reject) => {
1652
+ timer = setTimeout(() => {
1653
+ attempt.Abandoned = true;
1654
+ reject(new Error(`reconnect attempt timed out after ${timeoutMs} ms`));
1655
+ }, timeoutMs);
1656
+ });
1657
+ return Promise.race([work, timeout]).finally(() => {
1658
+ if (timer) {
1659
+ clearTimeout(timer);
1660
+ }
1661
+ });
1662
+ }
1663
+ onAttemptSucceeded() {
1664
+ this._reconnecting = false;
1665
+ this._endingAnnounced = false;
1666
+ this._resumableNow = false;
1667
+ this._turnOpen = false;
1668
+ if (!this._disposed) {
1669
+ this.options.OnReconnected?.();
1670
+ }
1671
+ }
1672
+ onAttemptFailed(index, error) {
1673
+ const message = error instanceof Error ? error.message : String(error);
1674
+ this.options.Log?.(`[RealtimeSessionResumption] attempt ${index + 1} failed: ${message}`);
1675
+ if (this._disposed) {
1676
+ return;
1677
+ }
1678
+ if (index + 1 < this.retryDelays().length) {
1679
+ this.scheduleAttempt(index + 1);
1680
+ return;
1681
+ }
1682
+ this._reconnecting = false;
1683
+ this._exhausted = true;
1684
+ this.options.OnReconnectFailed(error instanceof Error ? error : new Error(message));
1685
+ }
1686
+ armDeadline(timeLeftMs) {
1687
+ this.clearDeadline();
1688
+ const delay = timeLeftMs === void 0 ? UNKNOWN_TIME_LEFT_DELAY_MS : Math.max(0, timeLeftMs - this.deadlineMarginFor(timeLeftMs));
1689
+ this._deadlineTimer = setTimeout(() => {
1690
+ this._deadlineTimer = null;
1691
+ this.startReconnect("connection-ending");
1692
+ }, delay);
1693
+ }
1694
+ /**
1695
+ * The margin kept before an announced connection end: a third of the notice, so a turn in
1696
+ * progress gets the rest of it, between {@link REALTIME_RESUMPTION_MIN_DEADLINE_MARGIN_MS} and
1697
+ * {@link RealtimeSessionResumptionOptions.DeadlineMarginMs}. A notice shorter than the least
1698
+ * margin moves at once.
1699
+ */
1700
+ deadlineMarginFor(timeLeftMs) {
1701
+ const most = this.options.DeadlineMarginMs ?? REALTIME_RESUMPTION_DEADLINE_MARGIN_MS;
1702
+ const least = Math.min(most, REALTIME_RESUMPTION_MIN_DEADLINE_MARGIN_MS);
1703
+ return Math.min(most, Math.max(least, Math.round(timeLeftMs * DEADLINE_MARGIN_SHARE_OF_NOTICE)));
1704
+ }
1705
+ retryDelays() {
1706
+ const delays = this.options.RetryDelaysMs;
1707
+ return delays && delays.length > 0 ? delays : REALTIME_RESUMPTION_RETRY_DELAYS_MS;
1708
+ }
1709
+ clearDeadline() {
1710
+ if (this._deadlineTimer) {
1711
+ clearTimeout(this._deadlineTimer);
1712
+ this._deadlineTimer = null;
1713
+ }
1714
+ }
1715
+ clearRetry() {
1716
+ if (this._retryTimer) {
1717
+ clearTimeout(this._retryTimer);
1718
+ this._retryTimer = null;
1719
+ }
1720
+ }
1721
+ };
1722
+
1723
+ // ../../AI/Core/dist/generic/realtimeConnectionSetup.js
1724
+ var REALTIME_CONNECTION_SETUP_TIMEOUT_MS = 15e3;
1725
+ var RealtimeConnectionSetup = class {
1726
+ constructor() {
1727
+ this.settle = null;
1728
+ this.confirmed = false;
1729
+ this.outcome = new Promise((resolve, reject) => {
1730
+ this.settle = { Confirm: resolve, Fail: reject };
1731
+ });
1732
+ this.outcome.catch(() => void 0);
1733
+ }
1734
+ /** Whether the provider confirmed the setup. */
1735
+ get IsConfirmed() {
1736
+ return this.confirmed;
1737
+ }
1738
+ /** The provider confirmed the setup. Later calls, and a failure reported after it, change nothing. */
1739
+ Confirm() {
1740
+ if (!this.settle) {
1741
+ return;
1742
+ }
1743
+ this.confirmed = true;
1744
+ this.settle.Confirm();
1745
+ this.settle = null;
1746
+ }
1747
+ /**
1748
+ * The connection closed or failed before the provider confirmed the setup. Changes nothing once the setup was
1749
+ * confirmed or had already failed.
1750
+ *
1751
+ * @param error Why the setup failed; {@link Wait} rejects with it.
1752
+ */
1753
+ Fail(error) {
1754
+ if (!this.settle) {
1755
+ return;
1756
+ }
1757
+ this.settle.Fail(error);
1758
+ this.settle = null;
1759
+ }
1760
+ /**
1761
+ * Resolves once the provider confirms the setup. Rejects when the connection fails first, or when no confirmation
1762
+ * comes within `timeoutMs`; the setup then counts as failed.
1763
+ *
1764
+ * @param timeoutMs How long to wait for the confirmation. Defaults to {@link REALTIME_CONNECTION_SETUP_TIMEOUT_MS}.
1765
+ */
1766
+ Wait(timeoutMs = REALTIME_CONNECTION_SETUP_TIMEOUT_MS) {
1767
+ const timer = setTimeout(() => {
1768
+ this.Fail(new Error(`the provider did not confirm the session setup within ${timeoutMs} ms`));
1769
+ }, timeoutMs);
1770
+ return this.outcome.finally(() => clearTimeout(timer));
1771
+ }
1772
+ };
1773
+
1774
+ // ../../AI/Core/dist/generic/realtimeTracks.js
1775
+ function RealtimeTrackKey(descriptor) {
1776
+ const source = descriptor.SourceID && descriptor.SourceID.length > 0 ? `:${descriptor.SourceID}` : "";
1777
+ return `${descriptor.Direction}:${String(descriptor.Modality).trim().toLowerCase()}${source}`;
1778
+ }
1779
+ function IsPcmAudioMimeType(mimeType) {
1780
+ return /^audio\/pcm\s*(;.*)?$/i.test(mimeType.trim());
1781
+ }
1782
+ var CHANNEL_INBOUND_VIDEO_TRACK = Object.freeze({
1783
+ Modality: "video",
1784
+ Direction: "inbound",
1785
+ Encoding: "image/jpeg",
1786
+ Rate: 1,
1787
+ UsageBasis: ["tokens", "frames"],
1788
+ RequiresConsent: false
1789
+ });
1790
+ var DEFAULT_REALTIME_AUDIO_TRACKS = Object.freeze([
1791
+ Object.freeze({ Modality: "audio", Direction: "inbound" }),
1792
+ Object.freeze({ Modality: "audio", Direction: "outbound" })
1793
+ ]);
1794
+ function ResolveRequestedTracks(requested, supported, makeTrackID, limits) {
1795
+ if (!requested || requested.length === 0) {
1796
+ return [];
1797
+ }
1798
+ const supportedList = supported ?? [];
1799
+ let inboundVideoSeen = 0;
1800
+ return requested.map((descriptor, index) => {
1801
+ const supportedTrack = supportedList.find((s) => String(s.Modality).trim().toLowerCase() === String(descriptor.Modality).trim().toLowerCase() && s.Direction === descriptor.Direction);
1802
+ const isSupported = Boolean(supportedTrack);
1803
+ let refinedDescriptor = descriptor;
1804
+ if (supportedTrack) {
1805
+ let effectiveRate = descriptor.Rate;
1806
+ if (typeof supportedTrack.Rate === "number" && supportedTrack.Rate > 0) {
1807
+ effectiveRate = typeof descriptor.Rate === "number" && descriptor.Rate > 0 ? Math.min(descriptor.Rate, supportedTrack.Rate) : supportedTrack.Rate;
1808
+ }
1809
+ refinedDescriptor = {
1810
+ ...descriptor,
1811
+ Rate: effectiveRate
1812
+ };
1813
+ }
1814
+ const overStreamCap = isSupported && isInboundVideo(descriptor) && exceedsStreamCap(++inboundVideoSeen, limits?.MaxInboundVideoStreams);
1815
+ return {
1816
+ TrackID: makeTrackID(refinedDescriptor, index),
1817
+ Descriptor: refinedDescriptor,
1818
+ State: isSupported && !overStreamCap ? "requested" : "unsupported",
1819
+ Reason: !isSupported ? `Model does not support ${descriptor.Direction} ${String(descriptor.Modality)} tracks.` : overStreamCap ? `Model accepts at most ${limits?.MaxInboundVideoStreams} inbound video stream(s); this one is over the limit.` : void 0
1820
+ };
1821
+ });
1822
+ }
1823
+ function isInboundVideo(descriptor) {
1824
+ return descriptor.Direction === "inbound" && String(descriptor.Modality).trim().toLowerCase() === "video";
1825
+ }
1826
+ function exceedsStreamCap(ordinal, cap) {
1827
+ return typeof cap === "number" && ordinal > cap;
1828
+ }
1829
+
1830
+ // ../../AI/Core/dist/generic/realtimeVisionNotes.js
1831
+ function VideoSourceSeenNote(sourceLabels) {
1832
+ const named = typeof sourceLabels === "string" ? sourceLabels : sourceLabels.join(", ");
1833
+ return `[You can now see: ${named}]`;
1834
+ }
1835
+ function VideoSourceTurnedOffNote(sourceLabel) {
1836
+ return `[You can no longer see: ${sourceLabel} (the user turned it off)]`;
1837
+ }
1838
+ function VideoSourceTurnedOnNote(sourceLabel) {
1839
+ return `[You can now see: ${sourceLabel} (turned back on)]`;
1840
+ }
1841
+
1842
+ // ../../AI/Core/dist/generic/realtimeAvatarStatus.js
1843
+ var KNOWN_REASONS = {
1844
+ endpoint: true,
1845
+ bridged: true,
1846
+ phone: true,
1847
+ "custom-disabled": true,
1848
+ "unknown-avatar": true,
1849
+ "no-binding": true,
1850
+ host: true,
1851
+ browser: true,
1852
+ "decoder-missing": true,
1853
+ "decoder-failed": true,
1854
+ "publish-failed": true,
1855
+ "video-off": true
1856
+ };
1857
+ function isKnownReason(value) {
1858
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(KNOWN_REASONS, value);
1859
+ }
1860
+ var REALTIME_AVATAR_UNAVAILABLE_REASONS = Object.keys(KNOWN_REASONS).filter(isKnownReason);
1861
+
1862
+ // ../../AI/Core/dist/generic/realtimeVideoFrames.js
1863
+ var REALTIME_DEFAULT_INBOUND_VIDEO_RATE = 1;
1864
+ var REALTIME_VIDEO_FRAME_JITTER_HEADROOM = 0.75;
1865
+ function RealtimeVideoFrameIntervalMs(rate) {
1866
+ const effective = typeof rate === "number" && Number.isFinite(rate) && rate > 0 ? rate : REALTIME_DEFAULT_INBOUND_VIDEO_RATE;
1867
+ return Math.floor(1e3 / effective);
1868
+ }
1869
+ function RealtimeMinVideoFrameSpacingMs(rate) {
1870
+ return Math.floor(RealtimeVideoFrameIntervalMs(rate) * REALTIME_VIDEO_FRAME_JITTER_HEADROOM);
1871
+ }
1872
+ function ScaleRealtimeVideoFrame(size, maxDimension) {
1873
+ const longer = Math.max(size.Width, size.Height);
1874
+ if (!maxDimension || maxDimension <= 0 || longer <= maxDimension) {
1875
+ return { Width: size.Width, Height: size.Height };
1876
+ }
1877
+ const scale = maxDimension / longer;
1878
+ return {
1879
+ Width: Math.max(1, Math.round(size.Width * scale)),
1880
+ Height: Math.max(1, Math.round(size.Height * scale))
1881
+ };
1882
+ }
1883
+
1884
+ // ../../AI/Core/dist/generic/fmp4Reader.js
1885
+ var BOX_HEADER_BYTES = 8;
1886
+ var LARGE_BOX_HEADER_BYTES = 16;
1887
+ var FULL_BOX_HEADER_BYTES = 4;
1888
+ var VISUAL_SAMPLE_ENTRY_BYTES = 78;
1889
+ var VISUAL_SAMPLE_ENTRY_WIDTH_OFFSET = 24;
1890
+ var AUDIO_SAMPLE_ENTRY_BYTES = 28;
1891
+ var DECODER_CONFIG_BYTES = 13;
1892
+ var MPEG4_AUDIO_OBJECT_TYPE = 64;
1893
+ var ES_DESCRIPTOR_TAG = 3;
1894
+ var DECODER_CONFIG_DESCRIPTOR_TAG = 4;
1895
+ var DECODER_SPECIFIC_INFO_TAG = 5;
1896
+ var SAMPLE_IS_NON_SYNC = 65536;
1897
+ var MAX_RUN_SAMPLES = 65536;
1898
+ var AAC_SAMPLE_RATES = [96e3, 88200, 64e3, 48e3, 44100, 32e3, 24e3, 22050, 16e3, 12e3, 11025, 8e3, 7350];
1899
+ var AAC_EXPLICIT_RATE_INDEX = 15;
1900
+ var AAC_GA_OBJECT_TYPES = /* @__PURE__ */ new Set([1, 2, 3, 4, 6, 7, 17, 19, 20, 21, 22, 23]);
1901
+ var TFHD = {
1902
+ BaseDataOffset: 1,
1903
+ SampleDescriptionIndex: 2,
1904
+ DefaultDuration: 8,
1905
+ DefaultSize: 16,
1906
+ DefaultFlags: 32,
1907
+ DefaultBaseIsMoof: 131072
1908
+ };
1909
+ var TRUN = {
1910
+ DataOffset: 1,
1911
+ FirstSampleFlags: 4,
1912
+ Duration: 256,
1913
+ Size: 512,
1914
+ Flags: 1024,
1915
+ CompositionOffset: 2048
1916
+ };
1917
+ var TRUN_SAMPLE_FIELDS = [TRUN.Duration, TRUN.Size, TRUN.Flags, TRUN.CompositionOffset];
1918
+ function SniffFmp4Piece(bytes) {
1919
+ const view = viewOf(bytes);
1920
+ if (view.byteLength < BOX_HEADER_BYTES) {
1921
+ return null;
1922
+ }
1923
+ const type = fourCC(view, 4);
1924
+ if (type === "ftyp" || type === "moov") {
1925
+ return "init";
1926
+ }
1927
+ return type === "moof" || type === "styp" ? "fragment" : null;
1928
+ }
1929
+ function ReadFmp4Init(bytes) {
1930
+ const view = viewOf(bytes);
1931
+ const moov = readBoxes(view, 0, view.byteLength)?.find((box) => box.Type === "moov");
1932
+ const moovBoxes = moov ? childBoxes(view, moov) : null;
1933
+ if (!moovBoxes) {
1934
+ return null;
1935
+ }
1936
+ const defaults = readTrexDefaults(view, moovBoxes.find((box) => box.Type === "mvex"));
1937
+ const tracks = [];
1938
+ for (const trak of moovBoxes.filter((box) => box.Type === "trak")) {
1939
+ const track = readTrack(view, trak, defaults);
1940
+ if (!track) {
1941
+ return null;
1942
+ }
1943
+ tracks.push(track);
1944
+ }
1945
+ return { Tracks: tracks };
1946
+ }
1947
+ function Fmp4VideoSeconds(bytes, init) {
1948
+ return trackSeconds(bytes, init, "vide");
1949
+ }
1950
+ function ReadFmp4Fragment(bytes, init) {
1951
+ const view = viewOf(bytes);
1952
+ const moofs = readBoxes(view, 0, view.byteLength)?.filter((box) => box.Type === "moof");
1953
+ if (!moofs || moofs.length === 0) {
1954
+ return null;
1955
+ }
1956
+ const samples = [];
1957
+ for (const moof of moofs) {
1958
+ const read = readMovieFragment(view, moof, init);
1959
+ if (!read) {
1960
+ return null;
1961
+ }
1962
+ samples.push(...read);
1963
+ }
1964
+ return { Samples: samples };
1965
+ }
1966
+ function viewOf(bytes) {
1967
+ return ArrayBuffer.isView(bytes) ? new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) : new DataView(bytes);
1968
+ }
1969
+ function fourCC(view, offset) {
1970
+ return String.fromCharCode(view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2), view.getUint8(offset + 3));
1971
+ }
1972
+ function copyBytes(view, start, end) {
1973
+ return new Uint8Array(view.buffer, view.byteOffset + start, end - start).slice();
1974
+ }
1975
+ function readUint64(view, offset) {
1976
+ return view.getUint32(offset) * 4294967296 + view.getUint32(offset + 4);
1977
+ }
1978
+ function readBoxAt(view, offset, end) {
1979
+ const size32 = view.getUint32(offset);
1980
+ let headerBytes = BOX_HEADER_BYTES;
1981
+ let size = size32;
1982
+ if (size32 === 1) {
1983
+ if (end - offset < LARGE_BOX_HEADER_BYTES) {
1984
+ return null;
1985
+ }
1986
+ size = readUint64(view, offset + 8);
1987
+ headerBytes = LARGE_BOX_HEADER_BYTES;
1988
+ } else if (size32 === 0) {
1989
+ size = end - offset;
1990
+ }
1991
+ if (size < headerBytes || size > end - offset) {
1992
+ return null;
1993
+ }
1994
+ return { Type: fourCC(view, offset + 4), Offset: offset, Start: offset + headerBytes, End: offset + size };
1995
+ }
1996
+ function readBoxes(view, start, end) {
1997
+ const boxes = [];
1998
+ let offset = start;
1999
+ while (end - offset >= BOX_HEADER_BYTES) {
2000
+ const box = readBoxAt(view, offset, end);
2001
+ if (!box) {
2002
+ return null;
2003
+ }
2004
+ boxes.push(box);
2005
+ offset = box.End;
2006
+ }
2007
+ return boxes;
2008
+ }
2009
+ function childBoxes(view, parent, skip = 0) {
2010
+ return readBoxes(view, parent.Start + skip, parent.End);
2011
+ }
2012
+ function findChild(view, parent, type, skip = 0) {
2013
+ return childBoxes(view, parent, skip)?.find((box) => box.Type === type) ?? null;
2014
+ }
2015
+ function findPath(view, parent, ...types) {
2016
+ let box = parent;
2017
+ for (const type of types) {
2018
+ box = box ? findChild(view, box, type) : null;
2019
+ }
2020
+ return box;
2021
+ }
2022
+ function readTrack(view, trak, trexDefaults) {
2023
+ const tkhd = findChild(view, trak, "tkhd");
2024
+ const mdia = findChild(view, trak, "mdia");
2025
+ const hdlr = mdia ? findChild(view, mdia, "hdlr") : null;
2026
+ const trackID = tkhd ? readTrackID(view, tkhd) : null;
2027
+ const handler = hdlr ? readHandler(view, hdlr) : null;
2028
+ if (!mdia || trackID === null || handler === null) {
2029
+ return null;
2030
+ }
2031
+ const mdhd = findChild(view, mdia, "mdhd");
2032
+ const stsd = findPath(view, mdia, "minf", "stbl", "stsd");
2033
+ const entry = stsd ? readBoxes(view, stsd.Start + FULL_BOX_HEADER_BYTES + 4, stsd.End)?.[0] : void 0;
2034
+ return {
2035
+ TrackID: trackID,
2036
+ Handler: handler,
2037
+ Codec: entry ? readCodec(view, entry) : void 0,
2038
+ Timescale: mdhd ? readTimescale(view, mdhd) : void 0,
2039
+ ...trexDefaults.get(trackID),
2040
+ ...entry ? readDecoderConfig(view, entry) : {}
2041
+ };
2042
+ }
2043
+ function readTrackID(view, tkhd) {
2044
+ if (tkhd.End - tkhd.Start < FULL_BOX_HEADER_BYTES) {
2045
+ return null;
2046
+ }
2047
+ const offset = tkhd.Start + FULL_BOX_HEADER_BYTES + (view.getUint8(tkhd.Start) === 1 ? 16 : 8);
2048
+ return offset + 4 <= tkhd.End ? view.getUint32(offset) : null;
2049
+ }
2050
+ function readHandler(view, hdlr) {
2051
+ const offset = hdlr.Start + FULL_BOX_HEADER_BYTES + 4;
2052
+ return offset + 4 <= hdlr.End ? fourCC(view, offset) : null;
2053
+ }
2054
+ function readTimescale(view, mdhd) {
2055
+ if (mdhd.End - mdhd.Start < FULL_BOX_HEADER_BYTES) {
2056
+ return void 0;
2057
+ }
2058
+ const offset = mdhd.Start + FULL_BOX_HEADER_BYTES + (view.getUint8(mdhd.Start) === 1 ? 16 : 8);
2059
+ const timescale = offset + 4 <= mdhd.End ? view.getUint32(offset) : 0;
2060
+ return timescale > 0 ? timescale : void 0;
2061
+ }
2062
+ function readCodec(view, entry) {
2063
+ switch (entry.Type) {
2064
+ case "avc1":
2065
+ case "avc3":
2066
+ return avcCodec(view, entry);
2067
+ case "mp4a":
2068
+ return aacCodec(view, entry);
2069
+ default:
2070
+ return void 0;
2071
+ }
2072
+ }
2073
+ function readDecoderConfig(view, entry) {
2074
+ if (entry.Type === "avc1" || entry.Type === "avc3") {
2075
+ const avcC = findChild(view, entry, "avcC", VISUAL_SAMPLE_ENTRY_BYTES);
2076
+ const avc = avcC ? readAvcConfig(view, avcC) : null;
2077
+ return { ...readVisualSize(view, entry), ...avc ? { Avc: avc } : {} };
2078
+ }
2079
+ if (entry.Type === "mp4a") {
2080
+ const aac = readAacConfig(view, entry);
2081
+ return aac ? { Aac: aac } : {};
2082
+ }
2083
+ return {};
2084
+ }
2085
+ function readVisualSize(view, entry) {
2086
+ const offset = entry.Start + VISUAL_SAMPLE_ENTRY_WIDTH_OFFSET;
2087
+ if (offset + 4 > entry.End) {
2088
+ return {};
2089
+ }
2090
+ const width = view.getUint16(offset);
2091
+ const height = view.getUint16(offset + 2);
2092
+ return width > 0 && height > 0 ? { Width: width, Height: height } : {};
2093
+ }
2094
+ function avcCodec(view, entry) {
2095
+ const avcC = findChild(view, entry, "avcC", VISUAL_SAMPLE_ENTRY_BYTES);
2096
+ if (!avcC || avcC.End - avcC.Start < 4) {
2097
+ return void 0;
2098
+ }
2099
+ const profile = hex2(view.getUint8(avcC.Start + 1));
2100
+ const constraints = hex2(view.getUint8(avcC.Start + 2));
2101
+ const level = hex2(view.getUint8(avcC.Start + 3));
2102
+ return `${entry.Type}.${profile}${constraints}${level}`;
2103
+ }
2104
+ function readAvcConfig(view, avcC) {
2105
+ if (avcC.End - avcC.Start < 6) {
2106
+ return null;
2107
+ }
2108
+ const nalLengthSize = (view.getUint8(avcC.Start + 4) & 3) + 1;
2109
+ const sps = readParameterSets(view, avcC.Start + 6, avcC.End, view.getUint8(avcC.Start + 5) & 31);
2110
+ if (!sps || sps.End >= avcC.End) {
2111
+ return null;
2112
+ }
2113
+ const pps = readParameterSets(view, sps.End + 1, avcC.End, view.getUint8(sps.End));
2114
+ return pps && nalLengthSize !== 3 ? { NalLengthSize: nalLengthSize, Sps: sps.Sets, Pps: pps.Sets } : null;
2115
+ }
2116
+ function readParameterSets(view, start, end, count) {
2117
+ const sets = [];
2118
+ let offset = start;
2119
+ for (let i = 0; i < count; i++) {
2120
+ if (offset + 2 > end || offset + 2 + view.getUint16(offset) > end) {
2121
+ return null;
2122
+ }
2123
+ const length = view.getUint16(offset);
2124
+ sets.push(copyBytes(view, offset + 2, offset + 2 + length));
2125
+ offset += 2 + length;
2126
+ }
2127
+ return { Sets: sets, End: offset };
2128
+ }
2129
+ function aacCodec(view, entry) {
2130
+ const config = readEsdsDecoderConfig(view, entry);
2131
+ if (!config || config.ObjectType !== MPEG4_AUDIO_OBJECT_TYPE || !config.SpecificInfo) {
2132
+ return void 0;
2133
+ }
2134
+ const objectType = readAudioObjectType(view, config.SpecificInfo);
2135
+ return objectType === void 0 ? void 0 : `mp4a.40.${objectType}`;
2136
+ }
2137
+ function readAacConfig(view, entry) {
2138
+ const config = readEsdsDecoderConfig(view, entry);
2139
+ if (!config || config.ObjectType !== MPEG4_AUDIO_OBJECT_TYPE || !config.SpecificInfo) {
2140
+ return null;
2141
+ }
2142
+ return readAudioSpecificConfig(view, config.SpecificInfo);
2143
+ }
2144
+ function readEsdsDecoderConfig(view, entry) {
2145
+ const esds = findChild(view, entry, "esds", AUDIO_SAMPLE_ENTRY_BYTES);
2146
+ const es = esds ? readDescriptor(view, esds.Start + FULL_BOX_HEADER_BYTES, esds.End) : null;
2147
+ if (!es || es.Tag !== ES_DESCRIPTOR_TAG) {
2148
+ return null;
2149
+ }
2150
+ const decoderConfig = readDescriptor(view, esChildrenStart(view, es), es.End);
2151
+ if (!decoderConfig || decoderConfig.Tag !== DECODER_CONFIG_DESCRIPTOR_TAG || decoderConfig.End - decoderConfig.Start < DECODER_CONFIG_BYTES) {
2152
+ return null;
2153
+ }
2154
+ const specific = readDescriptor(view, decoderConfig.Start + DECODER_CONFIG_BYTES, decoderConfig.End);
2155
+ return {
2156
+ ObjectType: view.getUint8(decoderConfig.Start),
2157
+ SpecificInfo: specific?.Tag === DECODER_SPECIFIC_INFO_TAG ? specific : void 0
2158
+ };
2159
+ }
2160
+ function readDescriptor(view, offset, end) {
2161
+ let position = offset + 1;
2162
+ let size = 0;
2163
+ for (let i = 0; i < 4; i++) {
2164
+ if (position >= end) {
2165
+ return null;
2166
+ }
2167
+ const byte = view.getUint8(position++);
2168
+ size = size << 7 | byte & 127;
2169
+ if ((byte & 128) === 0) {
2170
+ break;
2171
+ }
2172
+ }
2173
+ return position + size <= end ? { Tag: view.getUint8(offset), Start: position, End: position + size } : null;
2174
+ }
2175
+ function esChildrenStart(view, es) {
2176
+ if (es.End - es.Start < 3) {
2177
+ return es.End;
2178
+ }
2179
+ const flags = view.getUint8(es.Start + 2);
2180
+ let offset = es.Start + 3;
2181
+ if (flags & 128) {
2182
+ offset += 2;
2183
+ }
2184
+ if (flags & 64) {
2185
+ offset = offset < es.End ? offset + 1 + view.getUint8(offset) : es.End;
2186
+ }
2187
+ if (flags & 32) {
2188
+ offset += 2;
2189
+ }
2190
+ return offset;
2191
+ }
2192
+ function readAudioObjectType(view, info) {
2193
+ const bits = new BitReader(view, info.Start, info.End);
2194
+ return readObjectTypeBits(bits) ?? void 0;
2195
+ }
2196
+ function readObjectTypeBits(bits) {
2197
+ const objectType = bits.Read(5);
2198
+ if (objectType !== 31) {
2199
+ return objectType;
2200
+ }
2201
+ const escaped = bits.Read(6);
2202
+ return escaped === null ? null : 32 + escaped;
2203
+ }
2204
+ function readAudioSpecificConfig(view, info) {
2205
+ const bits = new BitReader(view, info.Start, info.End);
2206
+ const objectType = readObjectTypeBits(bits);
2207
+ const rateIndex = bits.Read(4);
2208
+ const explicitRate = rateIndex === AAC_EXPLICIT_RATE_INDEX ? bits.Read(24) : null;
2209
+ const channels = bits.Read(4);
2210
+ const sampleRate = rateIndex === AAC_EXPLICIT_RATE_INDEX ? explicitRate : rateIndex !== null ? AAC_SAMPLE_RATES[rateIndex] : void 0;
2211
+ if (objectType === null || rateIndex === null || channels === null || !sampleRate) {
2212
+ return null;
2213
+ }
2214
+ const shortFrames = AAC_GA_OBJECT_TYPES.has(objectType) && bits.Read(1) === 1;
2215
+ return { ObjectType: objectType, SampleRateIndex: rateIndex, SampleRate: sampleRate, Channels: channels, FrameLength: shortFrames ? 960 : 1024 };
2216
+ }
2217
+ function readTrexDefaults(view, mvex) {
2218
+ const defaults = /* @__PURE__ */ new Map();
2219
+ const trexes = (mvex ? childBoxes(view, mvex) : null)?.filter((box) => box.Type === "trex") ?? [];
2220
+ for (const trex of trexes) {
2221
+ const fields = trex.Start + FULL_BOX_HEADER_BYTES;
2222
+ if (fields + 4 > trex.End) {
2223
+ continue;
2224
+ }
2225
+ const positive = (at) => fields + at + 4 <= trex.End && view.getUint32(fields + at) > 0 ? view.getUint32(fields + at) : void 0;
2226
+ defaults.set(view.getUint32(fields), { DefaultSampleDuration: positive(8), DefaultSampleSize: positive(12), DefaultSampleFlags: positive(16) });
2227
+ }
2228
+ return defaults;
2229
+ }
2230
+ function trackSeconds(bytes, init, handler) {
2231
+ const track = init.Tracks.find((candidate) => candidate.Handler === handler);
2232
+ if (!track?.Timescale) {
2233
+ return null;
2234
+ }
2235
+ const view = viewOf(bytes);
2236
+ const moofs = readBoxes(view, 0, view.byteLength)?.filter((box) => box.Type === "moof");
2237
+ const units = moofs ? sumOrNull(moofs, (moof) => moofDuration(view, moof, track)) : null;
2238
+ return units === null ? null : units / track.Timescale;
2239
+ }
2240
+ function moofDuration(view, moof, track) {
2241
+ const trafs = childBoxes(view, moof)?.filter((box) => box.Type === "traf");
2242
+ return trafs ? sumOrNull(trafs, (traf) => trafDuration(view, traf, track)) : null;
2243
+ }
2244
+ function trafDuration(view, traf, track) {
2245
+ const boxes = childBoxes(view, traf);
2246
+ const tfhd = boxes?.find((box) => box.Type === "tfhd");
2247
+ const header = tfhd ? readTfhd(view, tfhd) : null;
2248
+ if (!boxes || !header) {
2249
+ return null;
2250
+ }
2251
+ if (header.TrackID !== track.TrackID) {
2252
+ return 0;
2253
+ }
2254
+ const defaultDuration = header.DefaultDuration ?? track.DefaultSampleDuration;
2255
+ return sumOrNull(boxes.filter((box) => box.Type === "trun"), (trun) => trunDuration(view, trun, defaultDuration));
2256
+ }
2257
+ function trunDuration(view, trun, defaultDuration) {
2258
+ const layout = readTrackRunLayout(view, trun);
2259
+ if (!layout) {
2260
+ return null;
2261
+ }
2262
+ if (!(layout.Flags & TRUN.Duration)) {
2263
+ return defaultDuration === void 0 ? null : layout.Count * defaultDuration;
2264
+ }
2265
+ let units = 0;
2266
+ for (let i = 0; i < layout.Count; i++) {
2267
+ units += view.getUint32(layout.FirstRecord + i * layout.RecordBytes);
2268
+ }
2269
+ return units;
2270
+ }
2271
+ function sumOrNull(items, read) {
2272
+ let total = 0;
2273
+ for (const item of items) {
2274
+ const value = read(item);
2275
+ if (value === null) {
2276
+ return null;
2277
+ }
2278
+ total += value;
2279
+ }
2280
+ return total;
2281
+ }
2282
+ function readMovieFragment(view, moof, init) {
2283
+ const trafs = childBoxes(view, moof)?.filter((box) => box.Type === "traf");
2284
+ if (!trafs) {
2285
+ return null;
2286
+ }
2287
+ const samples = [];
2288
+ let previousDataEnd = moof.Offset;
2289
+ for (const traf of trafs) {
2290
+ const read = readTrackFragment(view, traf, moof, previousDataEnd, init);
2291
+ if (!read) {
2292
+ return null;
2293
+ }
2294
+ samples.push(...read.Samples);
2295
+ previousDataEnd = read.DataEnd;
2296
+ }
2297
+ return samples;
2298
+ }
2299
+ function readTrackFragment(view, traf, moof, previousDataEnd, init) {
2300
+ const children = childBoxes(view, traf);
2301
+ const tfhdBox = children?.find((box) => box.Type === "tfhd");
2302
+ const tfdtBox = children?.find((box) => box.Type === "tfdt");
2303
+ const header = tfhdBox ? readTfhd(view, tfhdBox) : null;
2304
+ const decodeTime = tfdtBox ? readDecodeTime(view, tfdtBox) : null;
2305
+ if (!children || !header || header.BaseDataOffset !== void 0 || decodeTime === null) {
2306
+ return null;
2307
+ }
2308
+ const baseOffset = header.DefaultBaseIsMoof ? moof.Offset : previousDataEnd;
2309
+ const track = init?.Tracks.find((t) => t.TrackID === header.TrackID);
2310
+ const context = { Header: header, Track: track, BaseOffset: baseOffset, DecodeTime: decodeTime, DataPosition: baseOffset };
2311
+ const samples = [];
2312
+ for (const trun of children.filter((box) => box.Type === "trun")) {
2313
+ const run = readTrackRun(view, trun, context);
2314
+ if (!run) {
2315
+ return null;
2316
+ }
2317
+ samples.push(...run);
2318
+ }
2319
+ return { Samples: samples, DataEnd: context.DataPosition };
2320
+ }
2321
+ function readFlags(view, box) {
2322
+ return view.getUint8(box.Start + 1) << 16 | view.getUint8(box.Start + 2) << 8 | view.getUint8(box.Start + 3);
2323
+ }
2324
+ function readTfhd(view, tfhd) {
2325
+ const flags = readFlags(view, tfhd);
2326
+ let offset = tfhd.Start + FULL_BOX_HEADER_BYTES;
2327
+ if (offset + 4 > tfhd.End) {
2328
+ return null;
2329
+ }
2330
+ const header = { TrackID: view.getUint32(offset), DefaultBaseIsMoof: (flags & TFHD.DefaultBaseIsMoof) !== 0 };
2331
+ offset += 4;
2332
+ if (flags & TFHD.BaseDataOffset) {
2333
+ if (offset + 8 > tfhd.End) {
2334
+ return null;
2335
+ }
2336
+ header.BaseDataOffset = readUint64(view, offset);
2337
+ offset += 8;
2338
+ }
2339
+ offset += flags & TFHD.SampleDescriptionIndex ? 4 : 0;
2340
+ const fields = [
2341
+ [TFHD.DefaultDuration, "DefaultDuration"],
2342
+ [TFHD.DefaultSize, "DefaultSize"],
2343
+ [TFHD.DefaultFlags, "DefaultFlags"]
2344
+ ];
2345
+ for (const [flag, field] of fields) {
2346
+ if (flags & flag) {
2347
+ if (offset + 4 > tfhd.End) {
2348
+ return null;
2349
+ }
2350
+ header[field] = view.getUint32(offset);
2351
+ offset += 4;
2352
+ }
2353
+ }
2354
+ return header;
2355
+ }
2356
+ function readDecodeTime(view, tfdt) {
2357
+ const version = view.getUint8(tfdt.Start);
2358
+ const offset = tfdt.Start + FULL_BOX_HEADER_BYTES;
2359
+ if (offset + (version === 1 ? 8 : 4) > tfdt.End) {
2360
+ return null;
2361
+ }
2362
+ return version === 1 ? readUint64(view, offset) : view.getUint32(offset);
2363
+ }
2364
+ function readTrackRunLayout(view, trun) {
2365
+ const flags = readFlags(view, trun);
2366
+ let offset = trun.Start + FULL_BOX_HEADER_BYTES;
2367
+ const fixedBytes = 4 + (flags & TRUN.DataOffset ? 4 : 0) + (flags & TRUN.FirstSampleFlags ? 4 : 0);
2368
+ const recordBytes = 4 * TRUN_SAMPLE_FIELDS.filter((field) => flags & field).length;
2369
+ if (offset + fixedBytes > trun.End) {
2370
+ return null;
2371
+ }
2372
+ const count = view.getUint32(offset);
2373
+ if (count > MAX_RUN_SAMPLES || offset + fixedBytes + count * recordBytes > trun.End) {
2374
+ return null;
2375
+ }
2376
+ offset += 4;
2377
+ const dataOffset = flags & TRUN.DataOffset ? view.getInt32(offset) : void 0;
2378
+ offset += dataOffset === void 0 ? 0 : 4;
2379
+ const firstSampleFlags = flags & TRUN.FirstSampleFlags ? view.getUint32(offset) : void 0;
2380
+ offset += firstSampleFlags === void 0 ? 0 : 4;
2381
+ return { Version: view.getUint8(trun.Start), Flags: flags, Count: count, DataOffset: dataOffset, FirstSampleFlags: firstSampleFlags, FirstRecord: offset, RecordBytes: recordBytes };
2382
+ }
2383
+ function readTrackRun(view, trun, context) {
2384
+ const layout = readTrackRunLayout(view, trun);
2385
+ if (!layout) {
2386
+ return null;
2387
+ }
2388
+ if (layout.DataOffset !== void 0) {
2389
+ context.DataPosition = context.BaseOffset + layout.DataOffset;
2390
+ }
2391
+ const samples = [];
2392
+ for (let i = 0; i < layout.Count; i++) {
2393
+ const sample = readRunSample(view, layout, i, context);
2394
+ if (!sample) {
2395
+ return null;
2396
+ }
2397
+ samples.push(sample);
2398
+ }
2399
+ return samples;
2400
+ }
2401
+ function readRunSample(view, layout, index, context) {
2402
+ const { Header: header, Track: track } = context;
2403
+ let position = layout.FirstRecord + index * layout.RecordBytes;
2404
+ const field = (flag) => {
2405
+ if (!(layout.Flags & flag)) {
2406
+ return void 0;
2407
+ }
2408
+ position += 4;
2409
+ return flag === TRUN.CompositionOffset && layout.Version === 1 ? view.getInt32(position - 4) : view.getUint32(position - 4);
2410
+ };
2411
+ const duration = field(TRUN.Duration) ?? header.DefaultDuration ?? track?.DefaultSampleDuration ?? 0;
2412
+ const size = field(TRUN.Size) ?? header.DefaultSize ?? track?.DefaultSampleSize;
2413
+ const firstSampleFlags = index === 0 ? layout.FirstSampleFlags : void 0;
2414
+ const sampleFlags = field(TRUN.Flags) ?? firstSampleFlags ?? header.DefaultFlags ?? track?.DefaultSampleFlags ?? 0;
2415
+ const compositionOffset = field(TRUN.CompositionOffset) ?? 0;
2416
+ if (size === void 0) {
2417
+ return null;
2418
+ }
2419
+ const start = context.DataPosition;
2420
+ const inPiece = start >= 0 && start + size <= view.byteLength;
2421
+ const sample = {
2422
+ TrackID: header.TrackID,
2423
+ DecodeTime: context.DecodeTime,
2424
+ CompositionOffset: compositionOffset,
2425
+ Duration: duration,
2426
+ Size: size,
2427
+ IsSync: (sampleFlags & SAMPLE_IS_NON_SYNC) === 0,
2428
+ Data: inPiece ? new Uint8Array(view.buffer, view.byteOffset + start, size) : null
2429
+ };
2430
+ context.DecodeTime += duration;
2431
+ context.DataPosition += size;
2432
+ return sample;
2433
+ }
2434
+ var BitReader = class {
2435
+ constructor(view, start, end) {
2436
+ this.view = view;
2437
+ this.end = end;
2438
+ this.position = start * 8;
2439
+ }
2440
+ /** The next `count` bits (at most 32) as a number, or `null` when fewer remain. */
2441
+ Read(count) {
2442
+ if (this.position + count > this.end * 8) {
2443
+ return null;
2444
+ }
2445
+ let value = 0;
2446
+ for (let i = 0; i < count; i++) {
2447
+ const byte = this.view.getUint8(this.position >> 3);
2448
+ value = value * 2 + (byte >> 7 - (this.position & 7) & 1);
2449
+ this.position++;
2450
+ }
2451
+ return value;
2452
+ }
2453
+ };
2454
+ function hex2(value) {
2455
+ return value.toString(16).padStart(2, "0");
2456
+ }
2457
+
2458
+ // ../../AI/Core/dist/generic/realtimeVideoOutput.js
2459
+ var FMP4_MIME_TYPE = "video/mp4";
2460
+ var FMP4_MIME_PATTERN = /^video\/mp4\s*(;|$)/i;
2461
+ function Fmp4PieceToVideoFrame(data, mimeType, init) {
2462
+ const label = mimeType?.trim() ?? "";
2463
+ const labelledMp4 = FMP4_MIME_PATTERN.test(label);
2464
+ const piece = SniffFmp4Piece(data) ?? (labelledMp4 ? "fragment" : null);
2465
+ if (!piece) {
2466
+ return null;
2467
+ }
2468
+ const frame = { Kind: "fmp4", Piece: piece, Data: data, MimeType: labelledMp4 ? label : FMP4_MIME_TYPE };
2469
+ return { ...frame, ...piece === "init" ? initPictureSize(data) : fragmentTiming(data, init) };
2470
+ }
2471
+ function initPictureSize(data) {
2472
+ const video = ReadFmp4Init(data)?.Tracks.find((track) => track.Handler === "vide");
2473
+ return video?.Width && video.Height ? { Width: video.Width, Height: video.Height } : {};
2474
+ }
2475
+ function fragmentTiming(data, init) {
2476
+ const video = init?.Tracks.find((track) => track.Handler === "vide");
2477
+ if (!video?.Timescale) {
2478
+ return {};
2479
+ }
2480
+ const first = ReadFmp4Fragment(data, init)?.Samples.find((sample) => sample.TrackID === video.TrackID);
2481
+ return first ? { PresentationTimeMs: (first.DecodeTime + first.CompositionOffset) / video.Timescale * 1e3, KeyFrame: first.IsSync } : {};
2482
+ }
2483
+
2484
+ export {
2485
+ RealtimeDiagLog,
2486
+ ParseRealtimeClientTransport,
2487
+ ExtractToolSchedulingHint,
2488
+ ResolveResponseDoneUsage,
2489
+ ParseModelConfiguration,
2490
+ ParseVendorConfiguration,
2491
+ ResolveEffectiveModelConfiguration,
2492
+ IsTranscriptContinuation,
2493
+ REALTIME_RELAY_TIME_LIMIT_CLOSE_CODE,
2494
+ RealtimeToolBatchBarrier,
2495
+ ParseDurationToMs,
2496
+ RealtimeSessionResumption,
2497
+ RealtimeConnectionSetup,
2498
+ RealtimeTrackKey,
2499
+ IsPcmAudioMimeType,
2500
+ DEFAULT_REALTIME_AUDIO_TRACKS,
2501
+ ResolveRequestedTracks,
2502
+ VideoSourceSeenNote,
2503
+ VideoSourceTurnedOffNote,
2504
+ VideoSourceTurnedOnNote,
2505
+ REALTIME_DEFAULT_INBOUND_VIDEO_RATE,
2506
+ RealtimeVideoFrameIntervalMs,
2507
+ RealtimeMinVideoFrameSpacingMs,
2508
+ ScaleRealtimeVideoFrame,
2509
+ SniffFmp4Piece,
2510
+ ReadFmp4Init,
2511
+ Fmp4VideoSeconds,
2512
+ Fmp4PieceToVideoFrame
2513
+ };