@vivix-ai/ivi-frontend-sdk 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ReceiveConversationItemAddedEvent, ReceiveConversationItemDoneEvent, ReceiveResponseCreatedEvent, IviClient, ReceiveSessionCreatedEvent, ReceiveSessionEndedEvent, ReceiveSessionStageGetResponseEvent, ReceiveSessionStageUpdatedEvent, ReceiveSessionTrackCreatedEvent, ReceiveSessionTrackDeletedEvent, ReceiveSessionTrackTookEvent, ReceiveSessionTrackCuedEvent, ReceiveSessionTrackNextSetEvent, ReceiveSessionTracksListResponseEvent, ReceiveSessionSourceCreatedEvent, ReceiveSessionSourceReadyEvent, ReceiveSessionSourceFailedEvent, ReceiveSessionSourceDeletedEvent, ReceiveSessionSourcePreloadEvent, ReceiveSessionSourceClearPreloadEvent, ReceiveSessionSourcesListResponseEvent, ReceiveSessionSourcePlaybackCompletedEvent, ReceiveSessionStreamCreatedEvent, ReceiveSessionStreamStartedEvent, ReceiveSessionStreamEndedEvent, ReceiveSessionStreamFailedEvent, ReceiveSessionStreamDeletedEvent, ReceiveSessionStreamsListResponseEvent, ReceiveConversationListResponseEvent, ReceiveResponseOutputTextDeltaEvent, ReceiveResponseOutputTextDoneEvent, ReceiveResponseOutputAudioTranscriptDeltaEvent, ReceiveResponseOutputAudioTranscriptDoneEvent, ReceiveResponseDoneEvent } from '@vivix-ai/ivi-sdk-ts';
2
+ import { propagation, SpanKind, trace, SpanStatusCode, context } from '@opentelemetry/api';
3
+ import { WebSocketTransport } from '@vivix-ai/ivi-sdk-ts/transports/websocket';
2
4
  import { createContext, useState, useEffect, useMemo, useRef, useContext, useCallback, useId } from 'react';
3
5
  import { jsx, jsxs } from 'react/jsx-runtime';
4
6
 
@@ -620,7 +622,9 @@ var SourceManager = class {
620
622
  durationMs: previous?.durationMs,
621
623
  hasAudio: previous?.hasAudio,
622
624
  error: previous?.status === "failed" ? previous.error : void 0,
623
- preload: previous?.preload
625
+ preload: previous?.preload,
626
+ origin: previous?.origin,
627
+ provisional: previous?.provisional ? false : previous?.provisional
624
628
  });
625
629
  this.sources = next;
626
630
  }
@@ -635,7 +639,38 @@ var SourceManager = class {
635
639
  height: payload.height,
636
640
  durationMs: payload.durationMs,
637
641
  hasAudio: payload.hasAudio,
638
- preload: previous?.preload
642
+ preload: previous?.preload,
643
+ origin: previous?.origin,
644
+ provisional: previous?.provisional ? false : previous?.provisional
645
+ });
646
+ this.sources = next;
647
+ }
648
+ upsertBootstrapReady(payload) {
649
+ const previous = this.sources.get(payload.sourceId);
650
+ const next = new Map(this.sources);
651
+ const source = {
652
+ source_id: payload.sourceId,
653
+ kind: payload.source?.kind ?? (payload.streamId ? "generation_stream" : "stream"),
654
+ stream_id: payload.source?.stream_id ?? payload.streamId,
655
+ asset_type: payload.source?.asset_type ?? "video",
656
+ url: payload.source?.url,
657
+ generation: payload.source?.generation,
658
+ metadata: {
659
+ ...payload.source?.metadata ?? {},
660
+ label: payload.source?.metadata?.label ?? "prebuilt"
661
+ }
662
+ };
663
+ next.set(payload.sourceId, {
664
+ source,
665
+ status: "ready",
666
+ playback: previous?.playback ?? payload.playback,
667
+ width: previous?.width ?? payload.width,
668
+ height: previous?.height ?? payload.height,
669
+ durationMs: previous?.durationMs ?? payload.durationMs,
670
+ hasAudio: previous?.hasAudio ?? payload.hasAudio,
671
+ preload: previous?.preload ?? { autoclearAfterPlay: true },
672
+ origin: previous?.origin ?? "prebuilt-session-response",
673
+ provisional: previous?.provisional ?? true
639
674
  });
640
675
  this.sources = next;
641
676
  }
@@ -669,11 +704,14 @@ var SourceManager = class {
669
704
  const next = /* @__PURE__ */ new Map();
670
705
  sources.forEach((source) => {
671
706
  const previous = this.sources.get(source.source_id);
707
+ const playback = source.playback ?? previous?.playback;
672
708
  next.set(source.source_id, {
673
709
  source,
674
- status: source.playback ? "ready" : "created",
675
- playback: source.playback,
676
- preload: previous?.preload
710
+ status: playback ? "ready" : "created",
711
+ playback,
712
+ preload: previous?.preload,
713
+ origin: previous?.origin,
714
+ provisional: source.playback && previous?.provisional ? false : previous?.provisional
677
715
  });
678
716
  });
679
717
  this.sources = next;
@@ -1102,6 +1140,94 @@ var ConversationManager = class {
1102
1140
  ];
1103
1141
  }
1104
1142
  };
1143
+ var DEFAULT_TRACER_NAME = "@vivix-ai/ivi-frontend-sdk";
1144
+ function getIviTelemetryTracer(options) {
1145
+ return options?.tracer ?? trace.getTracer(DEFAULT_TRACER_NAME);
1146
+ }
1147
+ function getIviTelemetryContext(options) {
1148
+ return options?.context ?? context.active();
1149
+ }
1150
+ function startIviTelemetrySpan(name, options, attributes) {
1151
+ const tracer = getIviTelemetryTracer(options);
1152
+ const parentContext = getIviTelemetryContext(options);
1153
+ const span = tracer.startSpan(
1154
+ name,
1155
+ {
1156
+ kind: SpanKind.INTERNAL,
1157
+ attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
1158
+ },
1159
+ parentContext
1160
+ );
1161
+ return {
1162
+ span,
1163
+ context: trace.setSpan(parentContext, span)
1164
+ };
1165
+ }
1166
+ function endIviTelemetrySpan(span, attributes) {
1167
+ const cleaned = cleanIviTelemetryAttributes(attributes);
1168
+ if (cleaned) {
1169
+ span.setAttributes(cleaned);
1170
+ }
1171
+ span.setStatus({ code: SpanStatusCode.OK });
1172
+ span.end();
1173
+ }
1174
+ function failIviTelemetrySpan(span, error, attributes) {
1175
+ const cleaned = cleanIviTelemetryAttributes(attributes);
1176
+ if (cleaned) {
1177
+ span.setAttributes(cleaned);
1178
+ }
1179
+ recordIviTelemetryError(span, error);
1180
+ span.end();
1181
+ }
1182
+ function recordIviTelemetrySpan(name, options, attributes, error) {
1183
+ const { span } = startIviTelemetrySpan(name, options, attributes);
1184
+ if (error === void 0) {
1185
+ span.setStatus({ code: SpanStatusCode.OK });
1186
+ } else {
1187
+ recordIviTelemetryError(span, error);
1188
+ }
1189
+ span.end();
1190
+ }
1191
+ function withIviTelemetryContext(options, context, attributes) {
1192
+ return {
1193
+ ...options,
1194
+ context,
1195
+ attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
1196
+ };
1197
+ }
1198
+ function mergeIviTelemetryAttributes(base, next) {
1199
+ return cleanIviTelemetryAttributes({
1200
+ ...base ?? {},
1201
+ ...next ?? {}
1202
+ });
1203
+ }
1204
+ function cleanIviTelemetryAttributes(attributes) {
1205
+ const cleaned = {};
1206
+ Object.entries(attributes ?? {}).forEach(([key, value]) => {
1207
+ if (isIviTelemetryAttributeValue(value)) {
1208
+ cleaned[key] = value;
1209
+ }
1210
+ });
1211
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
1212
+ }
1213
+ function recordIviTelemetryError(span, error) {
1214
+ const message = error instanceof Error ? error.message : String(error);
1215
+ span.recordException(error instanceof Error ? error : message);
1216
+ span.setStatus({
1217
+ code: SpanStatusCode.ERROR,
1218
+ message
1219
+ });
1220
+ }
1221
+ function isIviTelemetryAttributeValue(value) {
1222
+ if (typeof value === "string" || typeof value === "boolean") return true;
1223
+ if (typeof value === "number") return Number.isFinite(value);
1224
+ if (!Array.isArray(value)) return false;
1225
+ const nonNullValues = value.filter((item) => item !== null && item !== void 0);
1226
+ if (nonNullValues.length === 0) return true;
1227
+ const firstType = typeof nonNullValues[0];
1228
+ if (firstType !== "string" && firstType !== "number" && firstType !== "boolean") return false;
1229
+ return nonNullValues.every((item) => typeof item === firstType && (typeof item !== "number" || Number.isFinite(item)));
1230
+ }
1105
1231
 
1106
1232
  // src/runtime/managers/trtc-source-manager.ts
1107
1233
  var TAG = "[IVI-TRTC]";
@@ -1112,10 +1238,11 @@ var DEFAULT_DENOISER_OPTIONS = {
1112
1238
  mode: "normal"
1113
1239
  };
1114
1240
  var TrtcSourceManager = class {
1115
- constructor(onLog, onTrtcEvent, aiDenoiser) {
1241
+ constructor(onLog, onTrtcEvent, aiDenoiser, telemetry) {
1116
1242
  this.onLog = onLog;
1117
1243
  this.onTrtcEvent = onTrtcEvent;
1118
1244
  this.aiDenoiser = aiDenoiser;
1245
+ this.telemetry = telemetry;
1119
1246
  this.sessions = /* @__PURE__ */ new Map();
1120
1247
  this.sourceSessionKeys = /* @__PURE__ */ new Map();
1121
1248
  this.listeners = /* @__PURE__ */ new Map();
@@ -1277,6 +1404,7 @@ var TrtcSourceManager = class {
1277
1404
  startedVideoKeys: /* @__PURE__ */ new Set(),
1278
1405
  startingVideoKeys: /* @__PURE__ */ new Set()
1279
1406
  });
1407
+ this.log("info", `\u7ED1\u5B9A\u89C6\u56FE source=${sourceId} view=${viewId} views=${session.views.size}`);
1280
1408
  await this.ensureConnected(session);
1281
1409
  const binding = session.views.get(viewId);
1282
1410
  if (!binding) {
@@ -1292,6 +1420,7 @@ var TrtcSourceManager = class {
1292
1420
  }
1293
1421
  const binding = session.views.get(viewId);
1294
1422
  if (binding?.sourceId === sourceId) {
1423
+ this.log("info", `\u89E3\u7ED1\u89C6\u56FE source=${sourceId} view=${viewId}`);
1295
1424
  binding.container.removeAttribute(TRTC_VIEW_ATTR);
1296
1425
  session.views.delete(viewId);
1297
1426
  }
@@ -1309,6 +1438,24 @@ var TrtcSourceManager = class {
1309
1438
  binding.muted = muted;
1310
1439
  void this.applyAudioPolicy(session);
1311
1440
  }
1441
+ reassignView(fromSourceId, toSourceId, viewId) {
1442
+ if (fromSourceId === toSourceId) {
1443
+ return true;
1444
+ }
1445
+ const fromSession = this.getSession(fromSourceId);
1446
+ const toSession = this.getSession(toSourceId);
1447
+ if (!fromSession || !toSession || fromSession !== toSession) {
1448
+ return false;
1449
+ }
1450
+ const binding = fromSession.views.get(viewId);
1451
+ if (!binding || binding.sourceId !== fromSourceId) {
1452
+ return false;
1453
+ }
1454
+ this.log("info", `\u8FC1\u79FB\u89C6\u56FE source=${fromSourceId}->${toSourceId} view=${viewId}`);
1455
+ binding.sourceId = toSourceId;
1456
+ void this.applyAudioPolicy(fromSession);
1457
+ return true;
1458
+ }
1312
1459
  unlinkSourceFromSession(sourceId, session) {
1313
1460
  session.sourceIds.delete(sourceId);
1314
1461
  this.sourceSessionKeys.delete(sourceId);
@@ -1391,6 +1538,11 @@ var TrtcSourceManager = class {
1391
1538
  const client = TRTC.create();
1392
1539
  const onRemoteVideoAvailable = (event) => {
1393
1540
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
1541
+ recordIviTelemetrySpan(
1542
+ "ivi.media.trtc.remote_video_available",
1543
+ this.telemetry,
1544
+ buildTrtcMediaTelemetryAttributes(session.sourceId, event.userId, event.streamType)
1545
+ );
1394
1546
  const remoteVideoKey = buildRemoteVideoKey(event.userId, String(event.streamType));
1395
1547
  session.remoteVideoStreams.add(remoteVideoKey);
1396
1548
  if (!session.hasEverReceivedRemoteVideo) {
@@ -1483,6 +1635,7 @@ var TrtcSourceManager = class {
1483
1635
  if (!parsed) {
1484
1636
  continue;
1485
1637
  }
1638
+ this.log("info", `\u56DE\u653E\u5DF2\u77E5\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} userId=${parsed.userId} streamType=${parsed.streamType}`);
1486
1639
  await this.startRemoteVideoForBinding(
1487
1640
  client,
1488
1641
  parsed.userId,
@@ -1497,20 +1650,42 @@ var TrtcSourceManager = class {
1497
1650
  return;
1498
1651
  }
1499
1652
  binding.startingVideoKeys.add(remoteVideoKey);
1653
+ const telemetryAttributes = buildTrtcMediaTelemetryAttributes(
1654
+ binding.sourceId,
1655
+ userId,
1656
+ streamType
1657
+ );
1658
+ const telemetrySpan = startIviTelemetrySpan(
1659
+ "ivi.media.trtc.start_remote_video",
1660
+ this.telemetry,
1661
+ telemetryAttributes
1662
+ );
1500
1663
  try {
1664
+ this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
1501
1665
  await client.startRemoteVideo({
1502
1666
  userId,
1503
1667
  streamType,
1504
1668
  view: binding.container,
1505
1669
  option: { fillMode: "contain" }
1506
1670
  });
1671
+ endIviTelemetrySpan(telemetrySpan.span);
1672
+ recordIviTelemetrySpan(
1673
+ "ivi.media.trtc.remote_video_rendered",
1674
+ this.telemetry,
1675
+ telemetryAttributes
1676
+ );
1677
+ this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
1507
1678
  binding.startedVideoKeys.add(remoteVideoKey);
1508
1679
  enforceContainMedia(binding.container);
1509
1680
  } catch (error) {
1510
1681
  if (error instanceof Error && /already started|OPERATION_ABORT/i.test(error.message)) {
1511
1682
  binding.startedVideoKeys.add(remoteVideoKey);
1683
+ endIviTelemetrySpan(telemetrySpan.span, {
1684
+ "ivi.media.trtc.start_remote_video.result": "already_started"
1685
+ });
1512
1686
  return;
1513
1687
  }
1688
+ failIviTelemetrySpan(telemetrySpan.span, error);
1514
1689
  this.log("warn", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5931\u8D25 userId=${userId} streamType=${streamType}`, error);
1515
1690
  } finally {
1516
1691
  binding.startingVideoKeys.delete(remoteVideoKey);
@@ -1670,7 +1845,7 @@ function isRuntimeTrtcSource(source) {
1670
1845
  return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
1671
1846
  }
1672
1847
  function isSameTrtcConfig(a, b) {
1673
- return a.app_id === b.app_id && a.user_id === b.user_id && a.user_sig === b.user_sig && a.room_id === b.room_id;
1848
+ return a.app_id === b.app_id && a.user_id === b.user_id && a.room_id === b.room_id;
1674
1849
  }
1675
1850
  function buildTrtcSessionKey(trtc, aiDenoiser) {
1676
1851
  const denoiser = resolveAIDenoiserOptions(aiDenoiser);
@@ -1678,7 +1853,6 @@ function buildTrtcSessionKey(trtc, aiDenoiser) {
1678
1853
  appId: trtc.app_id,
1679
1854
  roomId: trtc.room_id,
1680
1855
  userId: trtc.user_id,
1681
- userSig: trtc.user_sig,
1682
1856
  aiDenoiser: {
1683
1857
  enabled: denoiser.enabled,
1684
1858
  mode: denoiser.mode,
@@ -1725,6 +1899,13 @@ function getAIDenoiserModeValue(mode) {
1725
1899
  function buildRemoteVideoKey(userId, streamType) {
1726
1900
  return `${userId}::${streamType}`;
1727
1901
  }
1902
+ function buildTrtcMediaTelemetryAttributes(sourceId, userId, streamType) {
1903
+ return {
1904
+ "ivi.source.id": sourceId,
1905
+ "ivi.trtc.user_id": userId,
1906
+ "ivi.trtc.stream_type": String(streamType)
1907
+ };
1908
+ }
1728
1909
  function parseRemoteVideoKey(key) {
1729
1910
  const separatorIndex = key.indexOf("::");
1730
1911
  if (separatorIndex < 0) {
@@ -1787,8 +1968,10 @@ function describeLivekitRoom(livekit) {
1787
1968
  // src/runtime/managers/livekit-source-manager.ts
1788
1969
  var TAG2 = "[IVI-LIVEKIT]";
1789
1970
  var LivekitSourceManager = class {
1790
- constructor(onLog) {
1971
+ constructor(onLog, onRemoteVideoAvailable, telemetry) {
1791
1972
  this.onLog = onLog;
1973
+ this.onRemoteVideoAvailable = onRemoteVideoAvailable;
1974
+ this.telemetry = telemetry;
1792
1975
  this.sessions = /* @__PURE__ */ new Map();
1793
1976
  this.listeners = /* @__PURE__ */ new Map();
1794
1977
  }
@@ -2010,11 +2193,21 @@ var LivekitSourceManager = class {
2010
2193
  kind
2011
2194
  });
2012
2195
  if (kind === "video" && !session.hasEverReceivedRemoteVideo) {
2196
+ recordIviTelemetrySpan(
2197
+ "ivi.media.livekit.remote_video_available",
2198
+ this.telemetry,
2199
+ {
2200
+ "ivi.source.id": session.sourceId,
2201
+ "ivi.livekit.participant_identity": participant.identity,
2202
+ "ivi.livekit.track_sid": track.sid
2203
+ }
2204
+ );
2013
2205
  session.hasEverReceivedRemoteVideo = true;
2014
2206
  for (const waiter of session.remoteVideoWaiters) {
2015
2207
  waiter(true);
2016
2208
  }
2017
2209
  session.remoteVideoWaiters.length = 0;
2210
+ this.onRemoteVideoAvailable?.(session.sourceId);
2018
2211
  }
2019
2212
  session.views.forEach((binding) => {
2020
2213
  this.attachTrackToView(track, kind, trackKey, binding);
@@ -2210,6 +2403,7 @@ var IviRuntimeCoordinator = class {
2210
2403
  this.userTextFlowCounter = 0;
2211
2404
  this.waitingTracksListValidation = false;
2212
2405
  this.waitingSourcesListValidation = false;
2406
+ this.bootstrapSource = null;
2213
2407
  this.state = {
2214
2408
  status: "idle",
2215
2409
  session: null,
@@ -2218,7 +2412,8 @@ var IviRuntimeCoordinator = class {
2218
2412
  sources: /* @__PURE__ */ new Map(),
2219
2413
  streams: /* @__PURE__ */ new Map(),
2220
2414
  conversationItems: /* @__PURE__ */ new Map(),
2221
- conversations: []
2415
+ conversations: [],
2416
+ bootstrap: null
2222
2417
  };
2223
2418
  this.client = client;
2224
2419
  this.config = {
@@ -2227,10 +2422,17 @@ var IviRuntimeCoordinator = class {
2227
2422
  };
2228
2423
  this.trtcSourceManager = new TrtcSourceManager(
2229
2424
  this.config.onLog,
2230
- (event) => this.emitTrtcEvent(event),
2231
- this.config.trtcAIDenoiser
2425
+ (event) => this.onTrtcManagerEvent(event),
2426
+ this.config.trtcAIDenoiser,
2427
+ this.config.telemetry
2428
+ );
2429
+ this.livekitSourceManager = new LivekitSourceManager(
2430
+ this.config.onLog,
2431
+ () => {
2432
+ this.recomposeBootstrapState();
2433
+ },
2434
+ this.config.telemetry
2232
2435
  );
2233
- this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
2234
2436
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
2235
2437
  onSessionCreated: (event) => this.onSessionCreated(event),
2236
2438
  onSessionEnded: (event) => this.onSessionEnded(event)
@@ -2267,15 +2469,27 @@ var IviRuntimeCoordinator = class {
2267
2469
  * 启动后状态会从 idle/stopped 进入 connecting,连接成功后由事件驱动进入后续状态。
2268
2470
  */
2269
2471
  async start() {
2270
- this.setState({
2271
- ...this.state,
2272
- status: "connecting"
2472
+ const telemetrySpan = startIviTelemetrySpan("ivi.runtime.start", this.config.telemetry, {
2473
+ "ivi.runtime.status": this.state.status,
2474
+ "ivi.session.id": this.state.session?.id
2273
2475
  });
2274
- this.dispatcher.start();
2275
2476
  try {
2477
+ this.setState({
2478
+ ...this.state,
2479
+ status: "connecting"
2480
+ });
2481
+ this.dispatcher.start();
2276
2482
  await this.client.connect();
2483
+ endIviTelemetrySpan(telemetrySpan.span, {
2484
+ "ivi.runtime.status": this.state.status,
2485
+ "ivi.session.id": this.state.session?.id
2486
+ });
2277
2487
  } catch (error) {
2278
2488
  this.stop();
2489
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2490
+ "ivi.runtime.status": this.state.status,
2491
+ "ivi.session.id": this.state.session?.id
2492
+ });
2279
2493
  throw error;
2280
2494
  }
2281
2495
  }
@@ -2283,13 +2497,31 @@ var IviRuntimeCoordinator = class {
2283
2497
  * 停止协调器并断开实时连接,同时重置运行态数据为 stopped。
2284
2498
  */
2285
2499
  stop() {
2286
- this.dispatcher.stop();
2287
- this.client.disconnect();
2288
- this.resetStoppedState();
2500
+ const telemetrySpan = startIviTelemetrySpan("ivi.runtime.stop", this.config.telemetry, {
2501
+ "ivi.runtime.status": this.state.status,
2502
+ "ivi.session.id": this.state.session?.id
2503
+ });
2504
+ try {
2505
+ this.dispatcher.stop();
2506
+ this.client.disconnect();
2507
+ this.resetStoppedState();
2508
+ endIviTelemetrySpan(telemetrySpan.span, {
2509
+ "ivi.runtime.status": this.state.status
2510
+ });
2511
+ } catch (error) {
2512
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2513
+ "ivi.runtime.status": this.state.status,
2514
+ "ivi.session.id": this.state.session?.id
2515
+ });
2516
+ throw error;
2517
+ }
2289
2518
  }
2290
2519
  getState() {
2291
2520
  return this.state;
2292
2521
  }
2522
+ getTelemetryOptions() {
2523
+ return this.config.telemetry;
2524
+ }
2293
2525
  onStateChange(listener) {
2294
2526
  this.stateListeners.add(listener);
2295
2527
  return () => {
@@ -2311,6 +2543,22 @@ var IviRuntimeCoordinator = class {
2311
2543
  emitLog(entry) {
2312
2544
  this.config.onLog?.(entry);
2313
2545
  }
2546
+ setBootstrapSource(source) {
2547
+ const previous = this.bootstrapSource;
2548
+ if (previous && (!source || previous.sourceId !== source.sourceId)) {
2549
+ const existing = this.sourceManager.get(previous.sourceId);
2550
+ if (existing?.origin === "prebuilt-session-response" && existing.provisional) {
2551
+ this.sourceManager.remove(previous.sourceId);
2552
+ }
2553
+ }
2554
+ this.bootstrapSource = source;
2555
+ this.ensureBootstrapSourceRegistered();
2556
+ this.syncPlaybackManagers();
2557
+ this.setState({
2558
+ ...this.state,
2559
+ sources: this.sourceManager.getAll()
2560
+ });
2561
+ }
2314
2562
  /**
2315
2563
  * 编排一次"用户文本输入 -> 触发模型回复"链路。
2316
2564
  *
@@ -2431,10 +2679,10 @@ var IviRuntimeCoordinator = class {
2431
2679
  this.trackManager.reset();
2432
2680
  this.sourceManager.reset();
2433
2681
  this.streamManager.reset();
2434
- this.trtcSourceManager.reset();
2435
- this.livekitSourceManager.reset();
2436
2682
  this.conversationManager.reset();
2437
2683
  this.reset();
2684
+ this.ensureBootstrapSourceRegistered();
2685
+ this.syncPlaybackManagers();
2438
2686
  const nextStatus = this.config.syncStageOnSessionCreated !== false ? "syncing" : "running";
2439
2687
  this.setState({
2440
2688
  status: nextStatus,
@@ -2472,8 +2720,8 @@ var IviRuntimeCoordinator = class {
2472
2720
  onTracksChanged(listRefreshed) {
2473
2721
  const nextStage = this.stageManager.getStage();
2474
2722
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2475
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2476
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2723
+ this.ensureBootstrapSourceRegistered();
2724
+ this.syncPlaybackManagers();
2477
2725
  this.setState({
2478
2726
  ...this.state,
2479
2727
  tracks: this.trackManager.getAll(),
@@ -2487,8 +2735,8 @@ var IviRuntimeCoordinator = class {
2487
2735
  applyLocalTrackTake(trackId) {
2488
2736
  this.trackManager.applyTrackTook(trackId);
2489
2737
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2490
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2491
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2738
+ this.ensureBootstrapSourceRegistered();
2739
+ this.syncPlaybackManagers();
2492
2740
  this.setState({
2493
2741
  ...this.state,
2494
2742
  tracks: this.trackManager.getAll(),
@@ -2528,8 +2776,8 @@ var IviRuntimeCoordinator = class {
2528
2776
  }
2529
2777
  onSourcesChanged(listRefreshed) {
2530
2778
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2531
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2532
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2779
+ this.ensureBootstrapSourceRegistered();
2780
+ this.syncPlaybackManagers();
2533
2781
  this.setState({
2534
2782
  ...this.state,
2535
2783
  sources: this.sourceManager.getAll()
@@ -2552,12 +2800,110 @@ var IviRuntimeCoordinator = class {
2552
2800
  });
2553
2801
  }
2554
2802
  setState(nextState) {
2555
- if (this.state.status === nextState.status && this.state.session === nextState.session && this.state.stage === nextState.stage && this.state.tracks === nextState.tracks && this.state.sources === nextState.sources && this.state.streams === nextState.streams && this.state.conversationItems === nextState.conversationItems && this.state.conversations === nextState.conversations) {
2803
+ let resolvedNextState = nextState;
2804
+ if (this.bootstrapSource && nextState.status !== "stopped" && !nextState.sources.has(this.bootstrapSource.sourceId)) {
2805
+ this.ensureBootstrapSourceRegistered();
2806
+ resolvedNextState = {
2807
+ ...nextState,
2808
+ sources: this.sourceManager.getAll()
2809
+ };
2810
+ }
2811
+ const composedNextState = this.composeBootstrapState(resolvedNextState);
2812
+ if (this.state.status === composedNextState.status && this.state.session === composedNextState.session && this.state.stage === composedNextState.stage && this.state.tracks === composedNextState.tracks && this.state.sources === composedNextState.sources && this.state.streams === composedNextState.streams && this.state.conversationItems === composedNextState.conversationItems && this.state.conversations === composedNextState.conversations && isSameBootstrapState(this.state.bootstrap, composedNextState.bootstrap)) {
2556
2813
  return;
2557
2814
  }
2558
- this.state = nextState;
2815
+ const previousState = this.state;
2816
+ this.state = composedNextState;
2817
+ if (previousState.status !== composedNextState.status) {
2818
+ recordIviTelemetrySpan("ivi.runtime.state_change", this.config.telemetry, {
2819
+ "ivi.runtime.status.previous": previousState.status,
2820
+ "ivi.runtime.status.next": composedNextState.status,
2821
+ "ivi.runtime.state_change.reason": "state_update",
2822
+ "ivi.session.id": composedNextState.session?.id ?? previousState.session?.id
2823
+ });
2824
+ }
2559
2825
  this.stateListeners.forEach((listener) => listener(this.state));
2560
2826
  }
2827
+ composeBootstrapState(nextState) {
2828
+ const bootstrap = this.bootstrapSource;
2829
+ if (!bootstrap) {
2830
+ return nextState.bootstrap === null || nextState.bootstrap === void 0 ? nextState : { ...nextState, bootstrap: null };
2831
+ }
2832
+ const shouldExpose = this.shouldExposeBootstrap(nextState, bootstrap);
2833
+ const bootstrapState = {
2834
+ active: shouldExpose,
2835
+ slot: bootstrap.slot,
2836
+ trackId: bootstrap.trackId,
2837
+ sourceId: bootstrap.sourceId,
2838
+ streamId: bootstrap.streamId
2839
+ };
2840
+ if (!shouldExpose) {
2841
+ return {
2842
+ ...nextState,
2843
+ bootstrap: bootstrapState
2844
+ };
2845
+ }
2846
+ const tracks = new Map(nextState.tracks);
2847
+ tracks.set(bootstrap.trackId, {
2848
+ track_id: bootstrap.trackId,
2849
+ active_source_id: bootstrap.sourceId
2850
+ });
2851
+ return {
2852
+ ...nextState,
2853
+ tracks,
2854
+ bootstrap: bootstrapState
2855
+ };
2856
+ }
2857
+ shouldExposeBootstrap(state, bootstrap) {
2858
+ if (state.status === "stopped") {
2859
+ return false;
2860
+ }
2861
+ const bootstrapRuntimeSource = state.sources.get(bootstrap.sourceId);
2862
+ if (!isReadyRenderableSource(bootstrapRuntimeSource)) {
2863
+ return false;
2864
+ }
2865
+ const layer = (state.stage?.composition ?? []).find((item) => item.slot === bootstrap.slot);
2866
+ if (!layer) {
2867
+ return true;
2868
+ }
2869
+ const track = state.tracks.get(layer.track_id);
2870
+ const activeSourceId = track?.active_source_id;
2871
+ if (!activeSourceId || activeSourceId === bootstrap.sourceId) {
2872
+ return true;
2873
+ }
2874
+ return !this.isReadyForBootstrapHandoff(activeSourceId, state.sources.get(activeSourceId));
2875
+ }
2876
+ isReadyForBootstrapHandoff(sourceId, source) {
2877
+ if (!isReadyRenderableSource(source)) {
2878
+ return false;
2879
+ }
2880
+ if (isTrtcPlaybackSource(source)) {
2881
+ return this.trtcSourceManager.hasRemoteVideoAvailable(sourceId);
2882
+ }
2883
+ if (isLivekitPlaybackSource(source)) {
2884
+ return this.livekitSourceManager.hasRemoteVideoAvailable(sourceId);
2885
+ }
2886
+ return true;
2887
+ }
2888
+ recomposeBootstrapState() {
2889
+ if (!this.bootstrapSource) {
2890
+ return;
2891
+ }
2892
+ this.setState({ ...this.state });
2893
+ }
2894
+ ensureBootstrapSourceRegistered() {
2895
+ if (!this.bootstrapSource) {
2896
+ return;
2897
+ }
2898
+ if (this.sourceManager.has(this.bootstrapSource.sourceId)) {
2899
+ return;
2900
+ }
2901
+ this.sourceManager.upsertBootstrapReady(this.bootstrapSource);
2902
+ }
2903
+ syncPlaybackManagers() {
2904
+ this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2905
+ this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2906
+ }
2561
2907
  emitEvent(event) {
2562
2908
  this.progressUserTextToResponseFlows(event);
2563
2909
  this.eventListeners.forEach((listener) => listener(event, this.state));
@@ -2566,6 +2912,12 @@ var IviRuntimeCoordinator = class {
2566
2912
  this.config.onTrtcEvent?.(event);
2567
2913
  this.trtcEventListeners.forEach((listener) => listener(event));
2568
2914
  }
2915
+ onTrtcManagerEvent(event) {
2916
+ this.emitTrtcEvent(event);
2917
+ if (event.type === "remote_video_available") {
2918
+ this.recomposeBootstrapState();
2919
+ }
2920
+ }
2569
2921
  resetStoppedState() {
2570
2922
  this.rejectPendingUserTextToResponseFlows(
2571
2923
  new Error("Runtime stopped before user text to response flow completed.")
@@ -2730,15 +3082,481 @@ function isLivekitPlaybackSource(source) {
2730
3082
  if (!source || !source.playback) return false;
2731
3083
  return isLivekitSourcePlayback(source.playback);
2732
3084
  }
3085
+ function isReadyRenderableSource(source) {
3086
+ return Boolean(source && source.status === "ready" && source.playback);
3087
+ }
3088
+ function isSameBootstrapState(a, b) {
3089
+ if (!a && !b) return true;
3090
+ if (!a || !b) return false;
3091
+ return a.active === b.active && a.slot === b.slot && a.trackId === b.trackId && a.sourceId === b.sourceId && a.streamId === b.streamId;
3092
+ }
3093
+ var IviCreateIVISessionError = class extends Error {
3094
+ constructor(response, body) {
3095
+ super(`CreateIVISession failed with ${response.status} ${response.statusText}`);
3096
+ this.name = "IviCreateIVISessionError";
3097
+ this.status = response.status;
3098
+ this.statusText = response.statusText;
3099
+ this.body = body;
3100
+ }
3101
+ };
3102
+ async function createIVISession(request, options) {
3103
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3104
+ if (!fetchImpl) {
3105
+ throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
3106
+ }
3107
+ const telemetrySpan = startIviTelemetrySpan(
3108
+ "ivi.session.create",
3109
+ options.telemetry,
3110
+ buildCreateSessionTelemetryAttributes(request)
3111
+ );
3112
+ try {
3113
+ const requestBody = toCpCreateIVISessionRequestBody(request);
3114
+ const headers = await buildHeaders(options.headers);
3115
+ injectIviTelemetryHeaders(headers, telemetrySpan.context);
3116
+ const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
3117
+ method: "POST",
3118
+ headers,
3119
+ body: JSON.stringify(requestBody),
3120
+ credentials: options.credentials,
3121
+ signal: options.signal
3122
+ });
3123
+ const responseBody = await readJsonResponse(response);
3124
+ if (!response.ok) {
3125
+ const error = new IviCreateIVISessionError(response, responseBody);
3126
+ failIviTelemetrySpan(telemetrySpan.span, error, {
3127
+ "http.response.status_code": response.status,
3128
+ "http.response.status_text": response.statusText
3129
+ });
3130
+ throw error;
3131
+ }
3132
+ const result = normalizeCreateIVISessionResponse(responseBody, request, requestBody);
3133
+ endIviTelemetrySpan(telemetrySpan.span, {
3134
+ "http.response.status_code": response.status,
3135
+ "ivi.session.id": result.iviSessionId,
3136
+ "ivi.prebuild.playback.kind": result.prebuiltPlayback?.kind
3137
+ });
3138
+ return result;
3139
+ } catch (error) {
3140
+ if (!(error instanceof IviCreateIVISessionError)) {
3141
+ failIviTelemetrySpan(telemetrySpan.span, error);
3142
+ }
3143
+ throw error;
3144
+ }
3145
+ }
3146
+ function createRuntimeFromSession(session, options = {}) {
3147
+ const telemetrySpan = startIviTelemetrySpan("ivi.runtime.create", options.telemetry, {
3148
+ "ivi.session.id": session.iviSessionId,
3149
+ "ivi.prebuild.playback.kind": session.prebuiltPlayback?.kind
3150
+ });
3151
+ try {
3152
+ const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
3153
+ const childTelemetry = withIviTelemetryContext(options.telemetry, telemetrySpan.context, {
3154
+ "ivi.session.id": session.iviSessionId
3155
+ });
3156
+ const clientTelemetry = mergeIviTelemetryOptions(
3157
+ options.clientConfig?.telemetry,
3158
+ childTelemetry
3159
+ );
3160
+ const runtimeTelemetry = mergeIviTelemetryOptions(
3161
+ options.runtimeConfig?.telemetry,
3162
+ childTelemetry
3163
+ );
3164
+ const client = new IviClient({
3165
+ ...options.clientConfig ?? {},
3166
+ telemetry: clientTelemetry,
3167
+ transport: new WebSocketTransport({
3168
+ url: websocketUrl,
3169
+ sessionId: session.iviSessionId,
3170
+ protocols: options.websocket?.protocols,
3171
+ socketFactory: options.websocket?.socketFactory
3172
+ })
3173
+ });
3174
+ const runtime = new IviRuntimeCoordinator(client, {
3175
+ ...options.runtimeConfig ?? {},
3176
+ telemetry: runtimeTelemetry
3177
+ });
3178
+ if (options.fastStart !== false && session.prebuiltPlayback) {
3179
+ runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3180
+ }
3181
+ endIviTelemetrySpan(telemetrySpan.span);
3182
+ return {
3183
+ session,
3184
+ client,
3185
+ runtime
3186
+ };
3187
+ } catch (error) {
3188
+ failIviTelemetrySpan(telemetrySpan.span, error);
3189
+ throw error;
3190
+ }
3191
+ }
3192
+ async function createIVISessionRuntime(request, options) {
3193
+ const telemetry = mergeIviTelemetryOptions(options.telemetry, options.http.telemetry);
3194
+ const session = await createIVISession(request, {
3195
+ ...options.http,
3196
+ telemetry
3197
+ });
3198
+ return createRuntimeFromSession(session, {
3199
+ ...options,
3200
+ telemetry
3201
+ });
3202
+ }
3203
+ function toCpCreateIVISessionRequestBody(request) {
3204
+ const body = { ...request };
3205
+ delete body.idempotencyKey;
3206
+ delete body.streamType;
3207
+ delete body.iviVersion;
3208
+ delete body.sessionParameters;
3209
+ delete body.enableDecoderPublish;
3210
+ delete body.sessionRecording;
3211
+ delete body.prebuiltCharacters;
3212
+ delete body.prebuiltStream;
3213
+ if (request.idempotencyKey !== void 0) body.idempotency_key = request.idempotencyKey;
3214
+ if (request.streamType !== void 0) body.stream_type = request.streamType;
3215
+ if (request.iviVersion !== void 0) body.ivi_version = request.iviVersion;
3216
+ if (request.sessionParameters !== void 0) body.session_parameters = request.sessionParameters;
3217
+ if (request.enableDecoderPublish !== void 0) body.enable_decoder_publish = request.enableDecoderPublish;
3218
+ if (request.sessionRecording !== void 0) {
3219
+ body.session_recording = {
3220
+ enabled: request.sessionRecording.enabled,
3221
+ aspect_ratio: request.sessionRecording.aspectRatio
3222
+ };
3223
+ }
3224
+ if (request.prebuiltCharacters !== void 0) {
3225
+ body.prebuilt_characters = request.prebuiltCharacters.map((character) => ({
3226
+ character_id: character.characterId,
3227
+ character_payload: serializePrebuiltPayload(character.characterPayload)
3228
+ }));
3229
+ }
3230
+ if (request.prebuiltStream !== void 0) {
3231
+ body.prebuilt_stream = {
3232
+ stream_id: request.prebuiltStream.streamId,
3233
+ mode: request.prebuiltStream.mode,
3234
+ stream_config: serializePrebuiltPayload(request.prebuiltStream.streamConfig)
3235
+ };
3236
+ }
3237
+ return body;
3238
+ }
3239
+ function normalizeCreateIVISessionResponse(responseBody, request = {}, requestBody = toCpCreateIVISessionRequestBody(request)) {
3240
+ const payload = unwrapResponsePayload(responseBody);
3241
+ const iviSessionId = getString(payload, "ivi_session_id") ?? getString(payload, "iviSessionId");
3242
+ const endpoint = getString(payload, "endpoint");
3243
+ if (!iviSessionId) {
3244
+ throw new Error("CreateIVISession response missing ivi_session_id.");
3245
+ }
3246
+ if (!endpoint) {
3247
+ throw new Error("CreateIVISession response missing endpoint.");
3248
+ }
3249
+ const trtcInfo = normalizeTrtcInfo(getObjectValue(payload, "trtc_info") ?? getObjectValue(payload, "trtcInfo"));
3250
+ const livekitInfo = normalizeLivekitInfo(getObjectValue(payload, "livekit_info") ?? getObjectValue(payload, "livekitInfo"));
3251
+ const prebuildInfo = getObjectValue(payload, "prebuild_trtc_info") ?? getObjectValue(payload, "prebuildTrtcInfo");
3252
+ const prebuildTrtcInfo = normalizeTrtcInfo(
3253
+ getObjectValue(prebuildInfo, "trtc_info") ?? getObjectValue(prebuildInfo, "trtcInfo")
3254
+ );
3255
+ const prebuildLivekitInfo = normalizeLivekitInfo(
3256
+ getObjectValue(prebuildInfo, "livekit_info") ?? getObjectValue(prebuildInfo, "livekitInfo")
3257
+ );
3258
+ const prebuiltPlayback = buildPrebuiltPlayback(
3259
+ request,
3260
+ requestBody,
3261
+ prebuildTrtcInfo,
3262
+ prebuildLivekitInfo
3263
+ );
3264
+ return {
3265
+ iviSessionId,
3266
+ endpoint,
3267
+ tokenInfo: getObjectValue(payload, "token_info") ?? getObjectValue(payload, "tokenInfo"),
3268
+ trtcInfo,
3269
+ livekitInfo,
3270
+ prebuildTrtcInfo: prebuildTrtcInfo || prebuildLivekitInfo ? {
3271
+ trtcInfo: prebuildTrtcInfo,
3272
+ livekitInfo: prebuildLivekitInfo
3273
+ } : void 0,
3274
+ prebuiltPlayback,
3275
+ raw: responseBody,
3276
+ requestBody
3277
+ };
3278
+ }
3279
+ function resolveCreateIVISessionUrl(options) {
3280
+ if (options.url) return options.url;
3281
+ if (!options.baseUrl) {
3282
+ throw new Error("CreateIVISession requires options.baseUrl or options.url.");
3283
+ }
3284
+ const path = options.path ?? "/v1/ivi/sessions";
3285
+ return new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
3286
+ }
3287
+ function buildCreateSessionTelemetryAttributes(request) {
3288
+ return {
3289
+ "ivi.version": request.iviVersion,
3290
+ "ivi.stream.type": request.streamType,
3291
+ "ivi.prebuild.enabled": Boolean(
3292
+ request.prebuiltStream || request.prebuiltCharacters && request.prebuiltCharacters.length > 0
3293
+ )
3294
+ };
3295
+ }
3296
+ function mergeIviTelemetryOptions(explicit, inherited) {
3297
+ if (!explicit && !inherited) {
3298
+ return void 0;
3299
+ }
3300
+ return {
3301
+ tracer: explicit?.tracer ?? inherited?.tracer,
3302
+ context: explicit?.context ?? inherited?.context,
3303
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3304
+ };
3305
+ }
3306
+ async function buildHeaders(headers) {
3307
+ const resolvedHeaders = typeof headers === "function" ? await headers() : headers;
3308
+ const next = new Headers(resolvedHeaders);
3309
+ if (!next.has("content-type")) {
3310
+ next.set("content-type", "application/json");
3311
+ }
3312
+ return next;
3313
+ }
3314
+ var headersTextMapSetter = {
3315
+ set(carrier, key, value) {
3316
+ carrier.set(key, value);
3317
+ }
3318
+ };
3319
+ function injectIviTelemetryHeaders(headers, context) {
3320
+ propagation.inject(context, headers, headersTextMapSetter);
3321
+ }
3322
+ async function readJsonResponse(response) {
3323
+ const text = await response.text();
3324
+ if (!text) {
3325
+ return null;
3326
+ }
3327
+ try {
3328
+ return JSON.parse(text);
3329
+ } catch {
3330
+ return text;
3331
+ }
3332
+ }
3333
+ function buildWebSocketUrl(endpoint, options) {
3334
+ const rawUrl = options?.url ?? endpoint;
3335
+ if (!options?.query) {
3336
+ return rawUrl;
3337
+ }
3338
+ const url = new URL(rawUrl);
3339
+ Object.entries(options.query).forEach(([key, value]) => {
3340
+ if (value === void 0 || value === null) return;
3341
+ url.searchParams.set(key, value);
3342
+ });
3343
+ return url.toString();
3344
+ }
3345
+ function toBootstrapSource(prebuiltPlayback, options) {
3346
+ return {
3347
+ sourceId: prebuiltPlayback.sourceId,
3348
+ streamId: prebuiltPlayback.streamId,
3349
+ slot: options.bootstrapSlot ?? "main",
3350
+ trackId: options.bootstrapTrackId ?? "__ivi_bootstrap_track",
3351
+ playback: prebuiltPlayback.playback,
3352
+ source: {
3353
+ source_id: prebuiltPlayback.sourceId,
3354
+ kind: prebuiltPlayback.streamId ? "generation_stream" : "stream",
3355
+ stream_id: prebuiltPlayback.streamId,
3356
+ asset_type: "video",
3357
+ metadata: {
3358
+ label: "prebuilt"
3359
+ }
3360
+ }
3361
+ };
3362
+ }
3363
+ function buildPrebuiltPlayback(request, requestBody, trtcInfo, livekitInfo) {
3364
+ if (!trtcInfo && !livekitInfo) {
3365
+ return void 0;
3366
+ }
3367
+ const streamId = resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo);
3368
+ const sourceId = request.prebuiltStream?.sourceId ?? streamId;
3369
+ if (!sourceId) {
3370
+ return void 0;
3371
+ }
3372
+ if (trtcInfo) {
3373
+ return {
3374
+ sourceId,
3375
+ streamId,
3376
+ kind: "trtc",
3377
+ playback: {
3378
+ type: "trtc",
3379
+ trtc: trtcInfo
3380
+ },
3381
+ provisional: true
3382
+ };
3383
+ }
3384
+ if (livekitInfo) {
3385
+ return {
3386
+ sourceId,
3387
+ streamId,
3388
+ kind: "livekit",
3389
+ playback: {
3390
+ type: "livekit",
3391
+ livekit: livekitInfo
3392
+ },
3393
+ provisional: true
3394
+ };
3395
+ }
3396
+ return void 0;
3397
+ }
3398
+ function resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo) {
3399
+ const prebuiltStream = getObjectValue(requestBody, "prebuilt_stream");
3400
+ return request.prebuiltStream?.streamId ?? getString(prebuiltStream, "stream_id") ?? getString(prebuiltStream, "streamId") ?? trtcInfo?.room_id ?? livekitInfo?.room;
3401
+ }
3402
+ function normalizeTrtcInfo(value) {
3403
+ if (!isRecord(value)) {
3404
+ return void 0;
3405
+ }
3406
+ const appId = getString(value, "app_id") ?? getString(value, "appId");
3407
+ const roomId = getString(value, "room_id") ?? getString(value, "roomId");
3408
+ const userId = getString(value, "user_id") ?? getString(value, "userId");
3409
+ const userSig = getString(value, "user_sig") ?? getString(value, "userSig");
3410
+ if (!appId || !roomId || !userId || !userSig) {
3411
+ return void 0;
3412
+ }
3413
+ const normalized = {
3414
+ ...value,
3415
+ app_id: appId,
3416
+ room_id: roomId,
3417
+ user_id: userId,
3418
+ user_sig: userSig
3419
+ };
3420
+ delete normalized.secret_key;
3421
+ delete normalized.secretKey;
3422
+ return normalized;
3423
+ }
3424
+ function normalizeLivekitInfo(value) {
3425
+ if (!isRecord(value)) {
3426
+ return void 0;
3427
+ }
3428
+ const wsUrl = getString(value, "ws_url") ?? getString(value, "url") ?? getString(value, "wsUrl");
3429
+ const token = getString(value, "token") ?? getString(value, "access_token") ?? getString(value, "accessToken");
3430
+ if (!wsUrl || !token) {
3431
+ return void 0;
3432
+ }
3433
+ return {
3434
+ ws_url: wsUrl,
3435
+ token,
3436
+ room: getString(value, "room") ?? getString(value, "room_name") ?? getString(value, "roomName"),
3437
+ identity: getString(value, "identity")
3438
+ };
3439
+ }
3440
+ function serializePrebuiltPayload(value) {
3441
+ if (typeof value === "string") {
3442
+ return value;
3443
+ }
3444
+ return encodeJsonBase64(value);
3445
+ }
3446
+ function encodeJsonBase64(value) {
3447
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
3448
+ let binary = "";
3449
+ bytes.forEach((byte) => {
3450
+ binary += String.fromCharCode(byte);
3451
+ });
3452
+ if (typeof btoa === "function") {
3453
+ return btoa(binary);
3454
+ }
3455
+ const maybeBuffer = globalThis.Buffer;
3456
+ if (maybeBuffer) {
3457
+ return maybeBuffer.from(bytes).toString("base64");
3458
+ }
3459
+ throw new Error("Base64 encoder is not available in this runtime.");
3460
+ }
3461
+ function unwrapResponsePayload(value) {
3462
+ if (!isRecord(value)) {
3463
+ return {};
3464
+ }
3465
+ const data = getObjectValue(value, "data");
3466
+ return data ?? value;
3467
+ }
3468
+ function getObjectValue(value, key) {
3469
+ if (!isRecord(value)) {
3470
+ return void 0;
3471
+ }
3472
+ const nested = value[key];
3473
+ return isRecord(nested) ? nested : void 0;
3474
+ }
3475
+ function getString(value, key) {
3476
+ if (!isRecord(value)) {
3477
+ return void 0;
3478
+ }
3479
+ const nested = value[key];
3480
+ return typeof nested === "string" && nested.trim().length > 0 ? nested.trim() : void 0;
3481
+ }
3482
+ function isRecord(value) {
3483
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3484
+ }
3485
+ function ensureTrailingSlash(value) {
3486
+ return value.endsWith("/") ? value : `${value}/`;
3487
+ }
3488
+
3489
+ // src/sdk.ts
2733
3490
  var IviFrontendSdk = class {
3491
+ constructor(config = {}) {
3492
+ this.config = config;
3493
+ }
2734
3494
  createClient(config) {
2735
- return new IviClient(config);
3495
+ return new IviClient({
3496
+ ...config,
3497
+ telemetry: mergeSdkTelemetryOptions(config.telemetry, this.config.telemetry)
3498
+ });
2736
3499
  }
2737
3500
  createRuntimeCoordinator(clientConfig, runtimeConfig) {
2738
3501
  const client = this.createClient(clientConfig);
2739
- return new IviRuntimeCoordinator(client, runtimeConfig);
3502
+ return new IviRuntimeCoordinator(client, {
3503
+ ...runtimeConfig ?? {},
3504
+ telemetry: mergeSdkTelemetryOptions(runtimeConfig?.telemetry, this.config.telemetry)
3505
+ });
3506
+ }
3507
+ createIVISession(request, options) {
3508
+ const telemetry = mergeSdkTelemetryChain(
3509
+ this.config.telemetry,
3510
+ this.config.http?.telemetry,
3511
+ options?.telemetry
3512
+ );
3513
+ return createIVISession(request, {
3514
+ ...this.config.http ?? {},
3515
+ ...options ?? {},
3516
+ telemetry
3517
+ });
3518
+ }
3519
+ createRuntimeFromSession(session, options) {
3520
+ return createRuntimeFromSession(session, {
3521
+ ...options ?? {},
3522
+ telemetry: mergeSdkTelemetryChain(this.config.telemetry, options?.telemetry)
3523
+ });
3524
+ }
3525
+ createIVISessionRuntime(request, options) {
3526
+ const telemetry = mergeSdkTelemetryChain(
3527
+ this.config.telemetry,
3528
+ this.config.http?.telemetry,
3529
+ options?.http?.telemetry,
3530
+ options?.telemetry
3531
+ );
3532
+ const http = {
3533
+ ...this.config.http ?? {},
3534
+ ...options?.http ?? {},
3535
+ telemetry
3536
+ };
3537
+ return createIVISessionRuntime(request, {
3538
+ ...options ?? {},
3539
+ telemetry,
3540
+ http
3541
+ });
2740
3542
  }
2741
3543
  };
3544
+ function mergeSdkTelemetryChain(...items) {
3545
+ return items.reduce(
3546
+ (merged, item) => mergeSdkTelemetryOptions(item, merged),
3547
+ void 0
3548
+ );
3549
+ }
3550
+ function mergeSdkTelemetryOptions(explicit, inherited) {
3551
+ if (!explicit && !inherited) {
3552
+ return void 0;
3553
+ }
3554
+ return {
3555
+ tracer: explicit?.tracer ?? inherited?.tracer,
3556
+ context: explicit?.context ?? inherited?.context,
3557
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3558
+ };
3559
+ }
2742
3560
  var IviStageViewContext = createContext(null);
2743
3561
  var EMPTY_RUNTIME_STATE = {
2744
3562
  status: "idle",
@@ -2748,7 +3566,8 @@ var EMPTY_RUNTIME_STATE = {
2748
3566
  sources: /* @__PURE__ */ new Map(),
2749
3567
  streams: /* @__PURE__ */ new Map(),
2750
3568
  conversationItems: /* @__PURE__ */ new Map(),
2751
- conversations: []
3569
+ conversations: [],
3570
+ bootstrap: null
2752
3571
  };
2753
3572
  function useRuntimeState(runtime) {
2754
3573
  const [state, setState] = useState(() => runtime?.getState() ?? EMPTY_RUNTIME_STATE);
@@ -2776,10 +3595,10 @@ function IVIStageView(props) {
2776
3595
  const state = useRuntimeState(runtime);
2777
3596
  const slotTrackMap = useMemo(() => {
2778
3597
  return buildSlotTrackMapFromState(state);
2779
- }, [state.stage]);
3598
+ }, [state.stage, state.bootstrap]);
2780
3599
  const slotBindings = useMemo(() => {
2781
3600
  return buildSlotBindingsFromState(state);
2782
- }, [state.stage, state.tracks, state.sources]);
3601
+ }, [state.stage, state.tracks, state.sources, state.bootstrap]);
2783
3602
  useEffect(() => {
2784
3603
  onBindingsChange?.(slotBindings);
2785
3604
  }, [slotBindings, onBindingsChange]);
@@ -2819,15 +3638,18 @@ function buildSlotTrackMapFromState(state) {
2819
3638
  (state.stage?.composition ?? []).forEach((item) => {
2820
3639
  map.set(item.slot, item.track_id);
2821
3640
  });
3641
+ if (state.bootstrap?.active) {
3642
+ map.set(state.bootstrap.slot, state.bootstrap.trackId);
3643
+ }
2822
3644
  return map;
2823
3645
  }
2824
3646
  function buildSlotBindingsFromState(state) {
2825
- const bindings = [];
3647
+ const bindingsBySlot = /* @__PURE__ */ new Map();
2826
3648
  (state.stage?.composition ?? []).forEach((item) => {
2827
3649
  const track = state.tracks.get(item.track_id);
2828
3650
  const sourceId = track?.active_source_id ?? null;
2829
3651
  const source = sourceId ? state.sources.get(sourceId) : void 0;
2830
- bindings.push({
3652
+ bindingsBySlot.set(item.slot, {
2831
3653
  slot: item.slot,
2832
3654
  trackId: item.track_id,
2833
3655
  track,
@@ -2835,7 +3657,18 @@ function buildSlotBindingsFromState(state) {
2835
3657
  source
2836
3658
  });
2837
3659
  });
2838
- return bindings;
3660
+ if (state.bootstrap?.active) {
3661
+ const track = state.tracks.get(state.bootstrap.trackId);
3662
+ const source = state.sources.get(state.bootstrap.sourceId);
3663
+ bindingsBySlot.set(state.bootstrap.slot, {
3664
+ slot: state.bootstrap.slot,
3665
+ trackId: state.bootstrap.trackId,
3666
+ track,
3667
+ sourceId: state.bootstrap.sourceId,
3668
+ source
3669
+ });
3670
+ }
3671
+ return Array.from(bindingsBySlot.values());
2839
3672
  }
2840
3673
  var VOLUME_STORAGE_KEY = "ivi-volume-preferences";
2841
3674
  var DEFAULT_VOLUME = 80;
@@ -3603,23 +4436,25 @@ function IVITrtcPlayer(props) {
3603
4436
  const [loading, setLoading] = useState(true);
3604
4437
  const [error, setError] = useState(null);
3605
4438
  const mutedRef = useRef(muted);
4439
+ const attachedSourceIdRef = useRef(null);
3606
4440
  mutedRef.current = muted;
4441
+ useEffect(() => {
4442
+ const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
4443
+ setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
4444
+ setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
4445
+ });
4446
+ return unsubscribe;
4447
+ }, [manager, resolvedSourceId]);
3607
4448
  useEffect(() => {
3608
4449
  const container = containerRef.current;
3609
4450
  if (!container) {
3610
4451
  return;
3611
4452
  }
3612
4453
  let disposed = false;
4454
+ attachedSourceIdRef.current = resolvedSourceId;
3613
4455
  if (shouldManageSourceLifecycle) {
3614
4456
  manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
3615
4457
  }
3616
- const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
3617
- if (disposed) {
3618
- return;
3619
- }
3620
- setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
3621
- setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
3622
- });
3623
4458
  void manager.attachView(resolvedSourceId, viewIdRef.current, container, mutedRef.current).catch((caughtError) => {
3624
4459
  if (disposed) {
3625
4460
  return;
@@ -3629,15 +4464,15 @@ function IVITrtcPlayer(props) {
3629
4464
  });
3630
4465
  return () => {
3631
4466
  disposed = true;
3632
- unsubscribe();
3633
- manager.detachView(resolvedSourceId, viewIdRef.current);
4467
+ const attachedSourceId = attachedSourceIdRef.current ?? resolvedSourceId;
4468
+ manager.detachView(attachedSourceId, viewIdRef.current);
3634
4469
  if (shouldManageSourceLifecycle) {
3635
- manager.removeSource(resolvedSourceId);
4470
+ manager.removeSource(attachedSourceId);
3636
4471
  }
4472
+ attachedSourceIdRef.current = null;
3637
4473
  };
3638
4474
  }, [
3639
4475
  manager,
3640
- resolvedSourceId,
3641
4476
  shouldManageSourceLifecycle,
3642
4477
  trtc.app_id,
3643
4478
  trtc.room_id,
@@ -3645,6 +4480,15 @@ function IVITrtcPlayer(props) {
3645
4480
  trtc.user_sig,
3646
4481
  trtcAIDenoiser
3647
4482
  ]);
4483
+ useEffect(() => {
4484
+ const previousSourceId = attachedSourceIdRef.current;
4485
+ if (!previousSourceId || previousSourceId === resolvedSourceId) {
4486
+ return;
4487
+ }
4488
+ if (manager.reassignView(previousSourceId, resolvedSourceId, viewIdRef.current)) {
4489
+ attachedSourceIdRef.current = resolvedSourceId;
4490
+ }
4491
+ }, [manager, resolvedSourceId]);
3648
4492
  useEffect(() => {
3649
4493
  manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);
3650
4494
  }, [manager, muted, resolvedSourceId]);
@@ -3913,7 +4757,16 @@ function makeLoadPolicy(label, onLog) {
3913
4757
  };
3914
4758
  }
3915
4759
  function IVIHlsVideo(props) {
3916
- const { url, videoProps, style, aggressivePreload = false, paused = false, onLog } = props;
4760
+ const {
4761
+ url,
4762
+ sourceId,
4763
+ videoProps,
4764
+ style,
4765
+ aggressivePreload = false,
4766
+ paused = false,
4767
+ onLog,
4768
+ telemetry
4769
+ } = props;
3917
4770
  const videoRef = useRef(null);
3918
4771
  const pausedRef = useRef(paused);
3919
4772
  useEffect(() => {
@@ -3950,6 +4803,7 @@ function IVIHlsVideo(props) {
3950
4803
  const onVideoError = () => {
3951
4804
  const el = videoRef.current;
3952
4805
  const mediaErr = el?.error;
4806
+ recordVideoTelemetry("ivi.media.video.error", telemetry, sourceId, "hls", mediaErr);
3953
4807
  emitHlsLog(onLog, "warn", "<video> \u5143\u7D20\u62A5\u9519", {
3954
4808
  code: mediaErr?.code,
3955
4809
  message: mediaErr?.message,
@@ -3959,6 +4813,13 @@ function IVIHlsVideo(props) {
3959
4813
  });
3960
4814
  };
3961
4815
  const onVideoStalled = () => {
4816
+ recordVideoTelemetry(
4817
+ "ivi.media.video.stalled",
4818
+ telemetry,
4819
+ sourceId,
4820
+ "hls",
4821
+ new Error("<video> stalled")
4822
+ );
3962
4823
  emitHlsLog(onLog, "warn", "<video> stalled\uFF08\u7F13\u51B2\u505C\u6EDE\uFF09", {
3963
4824
  currentTime: videoRef.current?.currentTime,
3964
4825
  readyState: videoRef.current?.readyState
@@ -4101,7 +4962,7 @@ function IVIHlsVideo(props) {
4101
4962
  hlsInstance?.destroy();
4102
4963
  hlsInstance = null;
4103
4964
  };
4104
- }, [url, aggressivePreload, onLog]);
4965
+ }, [url, sourceId, aggressivePreload, onLog, telemetry]);
4105
4966
  return /* @__PURE__ */ jsx(
4106
4967
  "video",
4107
4968
  {
@@ -4114,6 +4975,17 @@ function IVIHlsVideo(props) {
4114
4975
  }
4115
4976
  );
4116
4977
  }
4978
+ function recordVideoTelemetry(name, telemetry, sourceId, kind, error) {
4979
+ recordIviTelemetrySpan(
4980
+ name,
4981
+ telemetry,
4982
+ {
4983
+ "ivi.source.id": sourceId,
4984
+ "ivi.media.kind": kind
4985
+ },
4986
+ error
4987
+ );
4988
+ }
4117
4989
  function isM3u8Url(url) {
4118
4990
  return /\.m3u8(?:$|[?#])/i.test(url);
4119
4991
  }
@@ -4152,13 +5024,7 @@ function getSourceRenderKey(sourceId, source) {
4152
5024
  if (!trtc) {
4153
5025
  return sourceId;
4154
5026
  }
4155
- return [
4156
- "trtc",
4157
- trtc.app_id ?? "",
4158
- trtc.room_id ?? "",
4159
- trtc.user_id ?? "",
4160
- trtc.user_sig ?? ""
4161
- ].join(":");
5027
+ return ["trtc", trtc.app_id ?? "", trtc.room_id ?? ""].join(":");
4162
5028
  }
4163
5029
  function TrackSlotMediaContent(props) {
4164
5030
  const {
@@ -4185,6 +5051,7 @@ function TrackSlotMediaContent(props) {
4185
5051
  source,
4186
5052
  isPreloading: !isActive
4187
5053
  };
5054
+ const telemetry = runtime?.getTelemetryOptions();
4188
5055
  const mediaStyle = buildAdaptiveMediaStyle(adaptToSourceSize, fitStrategy);
4189
5056
  const shouldMute = !isActive;
4190
5057
  if (renderMedia) return renderMedia(renderContext);
@@ -4256,9 +5123,11 @@ function TrackSlotMediaContent(props) {
4256
5123
  IVIHlsVideo,
4257
5124
  {
4258
5125
  url: playbackUrl,
5126
+ sourceId: slotSourceId,
4259
5127
  videoProps: mergedVideoProps,
4260
5128
  style: videoStyle,
4261
- paused: shouldPause
5129
+ paused: shouldPause,
5130
+ telemetry
4262
5131
  }
4263
5132
  );
4264
5133
  }
@@ -4280,6 +5149,7 @@ function TrackSlotMediaContent(props) {
4280
5149
  SlotVideo,
4281
5150
  {
4282
5151
  src: playbackUrl,
5152
+ sourceId: slotSourceId,
4283
5153
  paused: shouldPause,
4284
5154
  videoProps: {
4285
5155
  ...mergedVideoProps,
@@ -4287,7 +5157,8 @@ function TrackSlotMediaContent(props) {
4287
5157
  playsInline: videoProps?.playsInline ?? true,
4288
5158
  controls: isActive ? videoProps?.controls ?? true : false
4289
5159
  },
4290
- style: videoStyle
5160
+ style: videoStyle,
5161
+ telemetry
4291
5162
  }
4292
5163
  );
4293
5164
  }
@@ -4337,17 +5208,48 @@ function SlotVideo(props) {
4337
5208
  video.play().catch(() => void 0);
4338
5209
  }
4339
5210
  }, [props.paused]);
5211
+ const onError = (event) => {
5212
+ recordPlainVideoTelemetry(
5213
+ "ivi.media.video.error",
5214
+ props.telemetry,
5215
+ props.sourceId,
5216
+ event.currentTarget.error
5217
+ );
5218
+ props.videoProps.onError?.(event);
5219
+ };
5220
+ const onStalled = (event) => {
5221
+ recordPlainVideoTelemetry(
5222
+ "ivi.media.video.stalled",
5223
+ props.telemetry,
5224
+ props.sourceId,
5225
+ new Error("<video> stalled")
5226
+ );
5227
+ props.videoProps.onStalled?.(event);
5228
+ };
4340
5229
  return /* @__PURE__ */ jsx(
4341
5230
  "video",
4342
5231
  {
4343
5232
  ref: videoRef,
4344
5233
  src: props.src,
4345
5234
  ...props.videoProps,
5235
+ onError,
5236
+ onStalled,
4346
5237
  autoPlay: props.paused ? false : props.videoProps.autoPlay ?? true,
4347
5238
  style: props.style
4348
5239
  }
4349
5240
  );
4350
5241
  }
5242
+ function recordPlainVideoTelemetry(name, telemetry, sourceId, error) {
5243
+ recordIviTelemetrySpan(
5244
+ name,
5245
+ telemetry,
5246
+ {
5247
+ "ivi.source.id": sourceId,
5248
+ "ivi.media.kind": "video"
5249
+ },
5250
+ error
5251
+ );
5252
+ }
4351
5253
 
4352
5254
  // src/react/internal/use-multi-preload-sources.ts
4353
5255
  function useMultiPreloadSources(sources, activeSourceId) {
@@ -4614,7 +5516,83 @@ function getClientLogTag(category) {
4614
5516
  if (category === "reconnect") return "[IVI-RECONNECT]";
4615
5517
  return "[IVI-CLIENT]";
4616
5518
  }
5519
+ function useIviSessionRuntime(config) {
5520
+ const [status, setStatus] = useState("idle");
5521
+ const [session, setSession] = useState(null);
5522
+ const [runtime, setRuntime] = useState(null);
5523
+ const [client, setClient] = useState(null);
5524
+ const [error, setError] = useState(null);
5525
+ useEffect(() => {
5526
+ const {
5527
+ request,
5528
+ enabled = true,
5529
+ autoStart = true,
5530
+ onCreated,
5531
+ onRuntimeReady,
5532
+ onError,
5533
+ ...options
5534
+ } = config;
5535
+ if (!enabled || !request) {
5536
+ setStatus("idle");
5537
+ setSession(null);
5538
+ setRuntime(null);
5539
+ setClient(null);
5540
+ setError(null);
5541
+ return;
5542
+ }
5543
+ let disposed = false;
5544
+ let activeRuntime = null;
5545
+ setStatus("creating");
5546
+ setSession(null);
5547
+ setRuntime(null);
5548
+ setClient(null);
5549
+ setError(null);
5550
+ void createIVISessionRuntime(request, options).then(async (created) => {
5551
+ if (disposed) {
5552
+ created.runtime.stop();
5553
+ return;
5554
+ }
5555
+ activeRuntime = created.runtime;
5556
+ setSession(created.session);
5557
+ setRuntime(created.runtime);
5558
+ setClient(created.client);
5559
+ onCreated?.(created.session);
5560
+ onRuntimeReady?.(created.runtime, created.client);
5561
+ if (!autoStart) {
5562
+ setStatus("idle");
5563
+ return;
5564
+ }
5565
+ setStatus("connecting");
5566
+ await created.runtime.start();
5567
+ if (disposed) {
5568
+ created.runtime.stop();
5569
+ return;
5570
+ }
5571
+ setStatus("running");
5572
+ }).catch((caughtError) => {
5573
+ if (disposed) {
5574
+ return;
5575
+ }
5576
+ const normalizedError = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
5577
+ setError(normalizedError);
5578
+ setStatus("error");
5579
+ onError?.(caughtError);
5580
+ });
5581
+ return () => {
5582
+ disposed = true;
5583
+ activeRuntime?.stop();
5584
+ setStatus("stopped");
5585
+ };
5586
+ }, [config]);
5587
+ return {
5588
+ status,
5589
+ session,
5590
+ runtime,
5591
+ client,
5592
+ error
5593
+ };
5594
+ }
4617
5595
 
4618
- export { DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS, EMPTY_RUNTIME_STATE, IVILivekitPlayer, IVIStageView, IVISubtitleOverlay, IVITrackSlot, IVITrtcPlayer, IviFrontendSdk, IviRuntimeCoordinator, IviRuntimeDispatcher, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
5596
+ export { DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS, EMPTY_RUNTIME_STATE, IVILivekitPlayer, IVIStageView, IVISubtitleOverlay, IVITrackSlot, IVITrtcPlayer, IviCreateIVISessionError, IviFrontendSdk, IviRuntimeCoordinator, IviRuntimeDispatcher, LivekitSourceManager, TrtcSourceManager, createIVISession, createIVISessionRuntime, createRuntimeFromSession, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, normalizeCreateIVISessionResponse, toCpCreateIVISessionRequestBody, useIviSessionRuntime, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
4619
5597
  //# sourceMappingURL=index.js.map
4620
5598
  //# sourceMappingURL=index.js.map