@memberjunction/realtime-widget 6.2.0-edge.4 → 6.2.0-edge.6

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