realtime-avatar 0.27.0 → 0.27.2

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/react.js CHANGED
@@ -1074,6 +1074,8 @@ function RealtimeAvatarLiveKitRoom(props) {
1074
1074
  LiveKitRoom,
1075
1075
  {
1076
1076
  ...roomProps,
1077
+ // A new grant owns a new room and bridge; an old room must not report its agent into it.
1078
+ key: grant?.session_id ?? "no-session",
1077
1079
  serverUrl: grant?.livekit_url,
1078
1080
  token: grant?.participant_token,
1079
1081
  connect: shouldConnect,
@@ -2331,6 +2333,8 @@ var transcriptionOptionsSchema = z.object({
2331
2333
  languageCodes: transcriptionWireSchema.shape.language_codes,
2332
2334
  customVocabulary: transcriptionWireSchema.shape.custom_vocabulary
2333
2335
  }).strict();
2336
+ var schema02 = z.enum(["minor-sexual-content-v1"]);
2337
+ var contentPolicySchema = schema02;
2334
2338
 
2335
2339
  // ../client/src/wire.ts
2336
2340
  var DEFAULT_AVATAR_ID = "maria";
@@ -2497,7 +2501,8 @@ z.object({
2497
2501
  support_edits: sessionSupportEditsSchema.optional(),
2498
2502
  transcript_webhook: transcriptWebhookSchema.optional(),
2499
2503
  client_metadata: clientMetadataSchema.optional(),
2500
- connection_history: z.boolean().optional()
2504
+ connection_history: z.boolean().optional(),
2505
+ content_policy: contentPolicySchema.optional()
2501
2506
  }).strict().superRefine((value, ctx) => {
2502
2507
  if (value.support_edits && value.render_backend === "generative") {
2503
2508
  ctx.addIssue({
@@ -2565,7 +2570,8 @@ z.object({
2565
2570
  supportEdits: sessionSupportEditsSchema.optional(),
2566
2571
  transcriptWebhook: transcriptWebhookSchema.optional(),
2567
2572
  clientMetadata: clientMetadataSchema.optional(),
2568
- connectionHistory: z.boolean().optional()
2573
+ connectionHistory: z.boolean().optional(),
2574
+ contentPolicy: contentPolicySchema.optional()
2569
2575
  }).strict();
2570
2576
  z.object({
2571
2577
  recording: recordingArtifactSchema.optional(),
@@ -3019,7 +3025,8 @@ function lifecyclePhaseFrom(args) {
3019
3025
  kind: "reconnectable",
3020
3026
  reconnecting: true,
3021
3027
  attempt: recovery.attempt,
3022
- strategy: "fresh-grant"
3028
+ strategy: "fresh-grant",
3029
+ ...recovery.reason ? { reason: recovery.reason } : {}
3023
3030
  };
3024
3031
  }
3025
3032
  if (recovery.kind === "failed") {
@@ -3027,11 +3034,16 @@ function lifecyclePhaseFrom(args) {
3027
3034
  kind: "reconnectable",
3028
3035
  reconnecting: false,
3029
3036
  attempt: MAX_RECONNECT_ATTEMPTS,
3030
- strategy: "fresh-grant"
3037
+ strategy: "fresh-grant",
3038
+ ...recovery.reason ? { reason: recovery.reason } : {}
3031
3039
  };
3032
3040
  }
3033
3041
  if (recovery.kind === "ended") {
3034
- return recovery.reason ? { kind: "ended", reason: recovery.reason } : { kind: "ended" };
3042
+ return {
3043
+ kind: "ended",
3044
+ ...recovery.reason ? { reason: recovery.reason } : {},
3045
+ ...recovery.code ? { code: recovery.code } : {}
3046
+ };
3035
3047
  }
3036
3048
  switch (capacity.kind) {
3037
3049
  case "idle":
@@ -3044,7 +3056,7 @@ function lifecyclePhaseFrom(args) {
3044
3056
  return { kind: "ended", reason: "error" };
3045
3057
  case "active": {
3046
3058
  if (!connected || !agentPresent) {
3047
- return { kind: "connecting", grant: capacity.grant };
3059
+ return { kind: "connecting", grant: capacity.grant, waitingFor: connected ? "agent" : "transport" };
3048
3060
  }
3049
3061
  if (idle.kind === "idle-warning") {
3050
3062
  return { kind: "idle-warning", secondsRemaining: idle.secondsRemaining, deadlineAt: idle.deadlineAt };
@@ -3072,6 +3084,7 @@ function resolveIdleTimeoutMs(args) {
3072
3084
  return DEFAULT_IDLE_SECONDS * 1e3;
3073
3085
  }
3074
3086
  var DEFAULT_CONNECT_WATCHDOG_SECONDS = 12;
3087
+ var DEFAULT_READY_TIMEOUT_SECONDS = 30;
3075
3088
  function resolveWarnBeforeMs(idleTimeoutMs, override, idleWarnLeadSeconds) {
3076
3089
  if (idleTimeoutMs <= 0) return 0;
3077
3090
  const maxLeadMs = Math.max(0, idleTimeoutMs - 1e3);
@@ -3097,6 +3110,7 @@ function useSessionLifecycle(input) {
3097
3110
  maxReconnectAttempts,
3098
3111
  autoRetryBusy = true,
3099
3112
  connectWatchdogSeconds,
3113
+ readyTimeoutSeconds,
3100
3114
  requestOptions,
3101
3115
  onBehaviorChange
3102
3116
  } = input;
@@ -3107,17 +3121,27 @@ function useSessionLifecycle(input) {
3107
3121
  const reconnectPolicyRef = useRef(reconnectPolicy);
3108
3122
  reconnectPolicyRef.current = reconnectPolicy;
3109
3123
  const connectWatchdogMs = typeof connectWatchdogSeconds === "number" && Number.isFinite(connectWatchdogSeconds) ? Math.max(0, Math.floor(connectWatchdogSeconds)) * 1e3 : DEFAULT_CONNECT_WATCHDOG_SECONDS * 1e3;
3124
+ const readyTimeoutMs = typeof readyTimeoutSeconds === "number" && Number.isFinite(readyTimeoutSeconds) && readyTimeoutSeconds > 0 ? Math.max(1, Math.floor(readyTimeoutSeconds)) * 1e3 : DEFAULT_READY_TIMEOUT_SECONDS * 1e3;
3110
3125
  const [userEnded, setUserEnded] = useState(false);
3111
3126
  const userEndedRef = useRef(false);
3127
+ const [readinessFailed, setReadinessFailed] = useState(false);
3128
+ const readinessFailureRef = useRef(null);
3129
+ const readinessRef = useRef(null);
3130
+ const readyGrantRef = useRef(null);
3131
+ const agentWaitRef = useRef(null);
3112
3132
  const phaseRef = useRef({ kind: "idle" });
3113
3133
  const grantState = useLiveKitAvatarGrant({
3114
3134
  client,
3115
3135
  session,
3116
- active: active && !userEnded,
3136
+ active: active && !userEnded && !readinessFailed,
3117
3137
  autoRetryBusy,
3118
3138
  requestOptions
3119
3139
  });
3120
3140
  const capacity = grantState.capacity;
3141
+ const grantId = grantState.grant?.session_id ?? null;
3142
+ const grantIdRef = useRef(null);
3143
+ grantIdRef.current = grantId;
3144
+ const retiredGrantRef = useRef(null);
3121
3145
  const [recovery, setRecovery] = useState({ kind: "connected" });
3122
3146
  const recoveryRef = useRef(recovery);
3123
3147
  recoveryRef.current = recovery;
@@ -3140,8 +3164,19 @@ function useSessionLifecycle(input) {
3140
3164
  grantIdleSeconds: grantState.grant?.idle_timeout_seconds ?? 0
3141
3165
  });
3142
3166
  const warnAtMs = resolveWarnBeforeMs(idleTimeoutMs, warnBeforeMs, idleWarnLeadSeconds);
3143
- const [agentPresent, setAgentPresentState] = useState(false);
3144
- const [connected, setConnected] = useState(false);
3167
+ const [agentObservation, setAgentObservation] = useState({ grantId: null, present: false });
3168
+ const [connectionObservation, setConnectionObservation] = useState({ grantId: null, connected: false });
3169
+ const agentPresent = agentObservation.grantId === grantId && agentObservation.present;
3170
+ const connected = connectionObservation.grantId === grantId && connectionObservation.connected;
3171
+ connectedRef.current = connected;
3172
+ const setAgentPresentState = useCallback((present) => {
3173
+ const owner = grantIdRef.current;
3174
+ setAgentObservation((prev) => prev.grantId === owner && prev.present === present ? prev : { grantId: owner, present });
3175
+ }, []);
3176
+ const setConnected = useCallback((value) => {
3177
+ const owner = grantIdRef.current;
3178
+ setConnectionObservation((prev) => prev.grantId === owner && prev.connected === value ? prev : { grantId: owner, connected: value });
3179
+ }, []);
3145
3180
  const lastActivityRef = useRef(Date.now());
3146
3181
  const leaveRoomRef = useRef(null);
3147
3182
  const [, setClockTick] = useState(0);
@@ -3158,7 +3193,11 @@ function useSessionLifecycle(input) {
3158
3193
  refreshBaselineCapacityRef.current = null;
3159
3194
  attemptRef.current = 0;
3160
3195
  setAttempt(0);
3161
- setRecovery(userEndedRef.current ? { kind: "ended", reason: "user" } : { kind: "connected" });
3196
+ readinessRef.current = null;
3197
+ readyGrantRef.current = null;
3198
+ agentWaitRef.current = null;
3199
+ retiredGrantRef.current = null;
3200
+ setRecovery(userEndedRef.current ? { kind: "ended", reason: "user" } : readinessFailureRef.current ? { kind: "ended", reason: "error", code: readinessFailureRef.current } : { kind: "connected" });
3162
3201
  setConnected(false);
3163
3202
  setAgentPresentState(false);
3164
3203
  lastActivityRef.current = Date.now();
@@ -3167,26 +3206,33 @@ function useSessionLifecycle(input) {
3167
3206
  if (active) return;
3168
3207
  userEndedRef.current = false;
3169
3208
  setUserEnded(false);
3209
+ readinessFailureRef.current = null;
3210
+ setReadinessFailed(false);
3170
3211
  reset();
3171
3212
  }, [active, reset]);
3172
3213
  const markActivity = useCallback(() => {
3214
+ if (grantId === null || grantIdRef.current !== grantId) return;
3173
3215
  lastActivityRef.current = Date.now();
3174
3216
  setClockTick((value) => value + 1);
3175
- }, []);
3217
+ }, [grantId]);
3176
3218
  const stayConnected = useCallback(() => {
3219
+ if (grantId === null || grantIdRef.current !== grantId) return;
3177
3220
  if (!connectedRef.current) return;
3178
3221
  lastActivityRef.current = Date.now();
3179
3222
  setClockTick((value) => value + 1);
3180
- }, []);
3223
+ }, [grantId]);
3181
3224
  const setAgentPresent = useCallback((present) => {
3182
- setAgentPresentState((prev) => {
3183
- if (present && !prev) lastActivityRef.current = Date.now();
3184
- return present;
3225
+ if (grantId === null || grantIdRef.current !== grantId) return;
3226
+ if (userEndedRef.current || readinessFailureRef.current || retiredGrantRef.current !== null && retiredGrantRef.current === grantIdRef.current) return;
3227
+ setAgentObservation((prev) => {
3228
+ if (present && (prev.grantId !== grantId || !prev.present)) lastActivityRef.current = Date.now();
3229
+ return prev.grantId === grantId && prev.present === present ? prev : { grantId, present };
3185
3230
  });
3186
- }, []);
3231
+ }, [grantId]);
3187
3232
  const registerLeaveRoom = useCallback((leave) => {
3233
+ if (grantId === null || grantIdRef.current !== grantId) return;
3188
3234
  leaveRoomRef.current = leave;
3189
- }, []);
3235
+ }, [grantId]);
3190
3236
  const endIdle = useCallback(() => {
3191
3237
  if (!connectedRef.current) return;
3192
3238
  clearTimer();
@@ -3198,24 +3244,25 @@ function useSessionLifecycle(input) {
3198
3244
  leaveRoomRef.current?.();
3199
3245
  }, [clearTimer]);
3200
3246
  const onConnected = useCallback(() => {
3201
- if (userEndedRef.current) return;
3247
+ if (grantId === null || grantIdRef.current !== grantId) return;
3248
+ if (userEndedRef.current || readinessFailureRef.current || retiredGrantRef.current !== null && retiredGrantRef.current === grantIdRef.current) return;
3202
3249
  clearTimer();
3203
3250
  connectedRef.current = true;
3204
3251
  manualReconnectPendingRef.current = false;
3205
3252
  refreshBaselineCapacityRef.current = null;
3206
3253
  setConnected(true);
3207
- attemptRef.current = 0;
3208
- setAttempt(0);
3254
+ if (readinessRef.current) readinessRef.current.sawTransport = true;
3209
3255
  setRecovery({ kind: "connected" });
3210
3256
  lastActivityRef.current = Date.now();
3211
- }, [clearTimer]);
3257
+ }, [clearTimer, grantId]);
3212
3258
  const onDisconnected = useCallback(
3213
3259
  (reason) => {
3214
- if (!active || userEndedRef.current) return;
3260
+ if (grantId === null || grantIdRef.current !== grantId) return;
3261
+ if (!active || userEndedRef.current || readinessFailureRef.current) return;
3215
3262
  const action = disconnectAction(reason, connectedRef.current);
3216
3263
  if (action === "noop") return;
3217
3264
  if (action === "reset") {
3218
- if (recoveryRef.current.kind === "refreshing") return;
3265
+ if (recoveryRef.current.kind === "refreshing" || recoveryRef.current.kind === "reconnecting") return;
3219
3266
  reset();
3220
3267
  return;
3221
3268
  }
@@ -3232,11 +3279,12 @@ function useSessionLifecycle(input) {
3232
3279
  refreshBaselineCapacityRef.current = null;
3233
3280
  setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
3234
3281
  },
3235
- [active, reset]
3282
+ [active, reset, grantId]
3236
3283
  );
3237
3284
  const onConnectionError = useCallback(
3238
3285
  (error) => {
3239
- if (!active || userEndedRef.current || !isRecoverableConnectionError(error)) return;
3286
+ if (grantId === null || grantIdRef.current !== grantId) return;
3287
+ if (!active || userEndedRef.current || readinessFailureRef.current || !isRecoverableConnectionError(error)) return;
3240
3288
  connectedRef.current = false;
3241
3289
  setConnected(false);
3242
3290
  setAgentPresentState(false);
@@ -3247,7 +3295,7 @@ function useSessionLifecycle(input) {
3247
3295
  return { kind: "reconnecting", attempt: attemptRef.current };
3248
3296
  });
3249
3297
  },
3250
- [active]
3298
+ [active, grantId]
3251
3299
  );
3252
3300
  useEffect(() => {
3253
3301
  if (capacity.kind === "error" && recovery.kind === "connected") manualReconnectPendingRef.current = false;
@@ -3257,11 +3305,12 @@ function useSessionLifecycle(input) {
3257
3305
  const baseline = refreshBaselineCapacityRef.current;
3258
3306
  if (baseline?.kind === "error" && baseline.error === capacity.error) return;
3259
3307
  refreshBaselineCapacityRef.current = null;
3260
- setRecovery({ kind: "reconnecting", attempt: recovery.attempt });
3308
+ setRecovery({ kind: "reconnecting", attempt: recovery.attempt, ...recovery.reason ? { reason: recovery.reason } : {} });
3261
3309
  }, [capacity, recovery]);
3262
3310
  const onConnectionStateChange = useCallback(
3263
3311
  (state) => {
3264
- if (!active || userEndedRef.current) return;
3312
+ if (grantId === null || grantIdRef.current !== grantId) return;
3313
+ if (!active || userEndedRef.current || readinessFailureRef.current) return;
3265
3314
  if (state === "connected") {
3266
3315
  onConnected();
3267
3316
  return;
@@ -3275,14 +3324,78 @@ function useSessionLifecycle(input) {
3275
3324
  }
3276
3325
  }
3277
3326
  },
3278
- [active, onConnected]
3327
+ [active, onConnected, grantId]
3279
3328
  );
3329
+ useEffect(() => {
3330
+ if (!active || userEnded || readinessFailed || !connected || !agentPresent || !grantState.grant) return;
3331
+ readyGrantRef.current = grantState.grant.session_id;
3332
+ readinessRef.current = null;
3333
+ agentWaitRef.current = null;
3334
+ attemptRef.current = 0;
3335
+ setAttempt(0);
3336
+ }, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant]);
3337
+ const failReadiness = useCallback((code) => {
3338
+ if (userEndedRef.current || readinessFailureRef.current) return;
3339
+ readinessFailureRef.current = code;
3340
+ clearTimer();
3341
+ connectedRef.current = false;
3342
+ manualReconnectPendingRef.current = false;
3343
+ refreshBaselineCapacityRef.current = null;
3344
+ const ended = { kind: "ended", reason: "error", code };
3345
+ recoveryRef.current = ended;
3346
+ setRecovery(ended);
3347
+ setConnected(false);
3348
+ setAgentPresentState(false);
3349
+ releaseRef.current("disconnected");
3350
+ setReadinessFailed(true);
3351
+ leaveRoomRef.current?.();
3352
+ }, [clearTimer]);
3353
+ useEffect(() => {
3354
+ if (!active || userEnded || readinessFailed) return;
3355
+ if (recovery.kind === "ended" || recovery.kind === "failed") return;
3356
+ if (grantState.grant && readyGrantRef.current === grantState.grant.session_id) return;
3357
+ if (!readinessRef.current && grantState.grant) {
3358
+ readinessRef.current = { startedAt: Date.now(), sawTransport: connected };
3359
+ }
3360
+ const budget = readinessRef.current;
3361
+ if (!budget) return;
3362
+ const timer = window.setTimeout(() => {
3363
+ if (readinessRef.current !== budget) return;
3364
+ failReadiness(budget.sawTransport ? "agent_timeout" : "connection_timeout");
3365
+ }, Math.max(0, budget.startedAt + readyTimeoutMs - Date.now()));
3366
+ return () => window.clearTimeout(timer);
3367
+ }, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant, recovery.kind, readyTimeoutMs, failReadiness]);
3368
+ useEffect(() => {
3369
+ const sessionId = grantState.grant?.session_id;
3370
+ if (!active || userEnded || readinessFailed || !sessionId || readyGrantRef.current === sessionId || agentPresent) return;
3371
+ if (recovery.kind === "reconnecting" || recovery.kind === "failed" || recovery.kind === "ended") return;
3372
+ if (agentWaitRef.current?.sessionId !== sessionId) {
3373
+ if (!connected) return;
3374
+ agentWaitRef.current = { sessionId, startedAt: Date.now() };
3375
+ }
3376
+ const wait = agentWaitRef.current;
3377
+ if (connectWatchdogMs <= 0) return;
3378
+ const timer = window.setTimeout(() => {
3379
+ if (agentWaitRef.current !== wait || readinessFailureRef.current || userEndedRef.current) return;
3380
+ retiredGrantRef.current = sessionId;
3381
+ connectedRef.current = false;
3382
+ setConnected(false);
3383
+ setAgentPresentState(false);
3384
+ const next = { kind: "reconnecting", attempt: attemptRef.current, reason: "agent_timeout" };
3385
+ recoveryRef.current = next;
3386
+ setRecovery(next);
3387
+ releaseRef.current("disconnected");
3388
+ leaveRoomRef.current?.();
3389
+ }, Math.max(0, wait.startedAt + connectWatchdogMs - Date.now()));
3390
+ return () => window.clearTimeout(timer);
3391
+ }, [active, userEnded, readinessFailed, connected, agentPresent, grantState.grant, recovery.kind, connectWatchdogMs]);
3280
3392
  useEffect(() => {
3281
3393
  if (!active || connected || connectWatchdogMs <= 0) return;
3282
3394
  if (grantState.status !== "ready" || !grantState.grant) return;
3283
3395
  if (recovery.kind === "failed" || recovery.kind === "ended") return;
3284
3396
  if (recovery.kind === "in-place-reconnecting") return;
3285
3397
  const timer = window.setTimeout(() => {
3398
+ if (readinessFailureRef.current || userEndedRef.current) return;
3286
3399
  releaseRef.current("disconnected");
3287
3400
  setRecovery({ kind: "reconnecting", attempt: attemptRef.current });
3288
3401
  }, connectWatchdogMs);
@@ -3292,6 +3405,10 @@ function useSessionLifecycle(input) {
3292
3405
  if (!active || !needsFreshGrant(recovery)) return;
3293
3406
  const step2 = retryStep(attemptRef.current, reconnectPolicyRef.current);
3294
3407
  if (step2.kind === "give-up") {
3408
+ if (readinessRef.current) {
3409
+ failReadiness(readinessRef.current.sawTransport ? "agent_timeout" : "connection_timeout");
3410
+ return;
3411
+ }
3295
3412
  manualReconnectPendingRef.current = false;
3296
3413
  refreshBaselineCapacityRef.current = null;
3297
3414
  releaseRef.current("disconnected");
@@ -3300,15 +3417,16 @@ function useSessionLifecycle(input) {
3300
3417
  }
3301
3418
  timerRef.current = window.setTimeout(() => {
3302
3419
  timerRef.current = null;
3420
+ if (readinessFailureRef.current || userEndedRef.current) return;
3303
3421
  attemptRef.current = step2.attempt;
3304
3422
  setAttempt(step2.attempt);
3305
3423
  refreshBaselineCapacityRef.current = capacityRef.current;
3306
3424
  releaseRef.current("superseded");
3307
- setRecovery({ kind: "refreshing", attempt: step2.attempt });
3425
+ setRecovery({ kind: "refreshing", attempt: step2.attempt, ...recovery.reason ? { reason: recovery.reason } : {} });
3308
3426
  refreshRef.current();
3309
3427
  }, step2.delayMs);
3310
3428
  return clearTimer;
3311
- }, [recovery.kind, active, attempt, clearTimer]);
3429
+ }, [recovery.kind, active, attempt, clearTimer, failReadiness]);
3312
3430
  const end = useCallback(() => {
3313
3431
  if (!active || userEndedRef.current || phaseRef.current.kind === "ended") return;
3314
3432
  userEndedRef.current = true;
@@ -3325,6 +3443,13 @@ function useSessionLifecycle(input) {
3325
3443
  }, [active, clearTimer]);
3326
3444
  const reconnect = useCallback(() => {
3327
3445
  if (!active) return;
3446
+ if (readinessFailureRef.current) {
3447
+ readinessFailureRef.current = null;
3448
+ setReadinessFailed(false);
3449
+ reset();
3450
+ manualReconnectPendingRef.current = true;
3451
+ return;
3452
+ }
3328
3453
  if (userEndedRef.current) {
3329
3454
  userEndedRef.current = false;
3330
3455
  setUserEnded(false);
@@ -3335,6 +3460,12 @@ function useSessionLifecycle(input) {
3335
3460
  if (manualReconnectPendingRef.current || recovery.kind === "in-place-reconnecting") return;
3336
3461
  manualReconnectPendingRef.current = true;
3337
3462
  clearTimer();
3463
+ readinessRef.current = null;
3464
+ agentWaitRef.current = null;
3465
+ readyGrantRef.current = null;
3466
+ connectedRef.current = false;
3467
+ setConnected(false);
3468
+ setAgentPresentState(false);
3338
3469
  attemptRef.current = 0;
3339
3470
  setAttempt(0);
3340
3471
  refreshBaselineCapacityRef.current = capacityRef.current;
@@ -3454,6 +3585,7 @@ function SessionLifecycleRoomBridge({
3454
3585
  const assistant = useVoiceAssistant();
3455
3586
  const transcriptions = useTranscriptions();
3456
3587
  const room = useRoomContext();
3588
+ const roomMatchesGrant = lifecycle.grant === void 0 || lifecycle.grant !== null && room.name === lifecycle.grant.room_name;
3457
3589
  useCallMedia(room, {
3458
3590
  setMicrophoneFacts,
3459
3591
  setAudioPlayback,
@@ -3607,11 +3739,11 @@ function SessionLifecycleRoomBridge({
3607
3739
  detailsEnabled ? video?.publication : void 0
3608
3740
  ]);
3609
3741
  useEffect(() => {
3610
- onConnectionStateChange(connectionState);
3611
- }, [connectionState, onConnectionStateChange]);
3742
+ if (roomMatchesGrant) onConnectionStateChange(connectionState);
3743
+ }, [connectionState, onConnectionStateChange, roomMatchesGrant]);
3612
3744
  useEffect(() => {
3613
- setAgentPresent(agentPresent);
3614
- }, [agentPresent, setAgentPresent]);
3745
+ if (roomMatchesGrant) setAgentPresent(agentPresent);
3746
+ }, [agentPresent, setAgentPresent, roomMatchesGrant]);
3615
3747
  useEffect(() => {
3616
3748
  const active = isCallActivity({
3617
3749
  assistantState,
@@ -4675,4 +4807,4 @@ function RecordingPlayer({ assets, className }) {
4675
4807
  ] });
4676
4808
  }
4677
4809
 
4678
- export { AdaptivePlayoutController, AvatarCall, AvatarVideoSurface, DEFAULT_APPROACHING_END_LEAD_SECONDS, DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, DEFAULT_AVATAR_UNSTABLE_STALL_MS, DEFAULT_CREDITS_LOW_LEAD_SECONDS, DEFAULT_GOVERNOR_CONFIG, DEFAULT_GRACE_CEILING_SECONDS, DEFAULT_GRACE_WINDOW_LEAD_SECONDS, DEFAULT_IDLE_SECONDS, DEFAULT_IDLE_WARN_LEAD_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, MIC_LEASE_ENDED_TIMEOUT_MS, NETWORK_QUALITY_POLICY, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, RecordingPlayer, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarCamera, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useCharacterTools, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
4810
+ export { AdaptivePlayoutController, AvatarCall, AvatarVideoSurface, DEFAULT_APPROACHING_END_LEAD_SECONDS, DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS, DEFAULT_AVATAR_UNSTABLE_STALL_MS, DEFAULT_CREDITS_LOW_LEAD_SECONDS, DEFAULT_GOVERNOR_CONFIG, DEFAULT_GRACE_CEILING_SECONDS, DEFAULT_GRACE_WINDOW_LEAD_SECONDS, DEFAULT_IDLE_SECONDS, DEFAULT_IDLE_WARN_LEAD_SECONDS, DEFAULT_READY_TIMEOUT_SECONDS, DEFAULT_TURN_TIMEOUT_SECONDS, MAX_RECONNECT_ATTEMPTS, MAX_SESSION_INSTRUCTIONS_CHARS, MIC_LEASE_ENDED_TIMEOUT_MS, NETWORK_QUALITY_POLICY, RECONNECT_BACKOFF_MS, RealtimeAvatarCapacityError, RealtimeAvatarLiveKitRoom, RecordingPlayer, SessionLifecycleRoomBridge, capacityErrorFromBusy, capacityStateFromGrant, createProxyClient, knownBehaviorStates, mapTurnState, readInboundRtp, sessionBehaviorSchema, sessionClipSchema, shouldReplayPendingTurn, splitCallTranscript, useAvatarAdaptivePlayoutDelay, useAvatarCall, useAvatarCamera, useAvatarPlayoutDelay, useAvatarQualityGovernor, useCallTranscript, useCharacterTools, useLiveKitAvatarGrant, useMicLease, useRealtimeSession, useReleaseMicLeaseOnTrackEnded, useSessionLifecycle };
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, clipLibraryDeclarationSchema, verifyTranscript } from './index.js';
2
- export { h as Asset, g as AssetKind, A as Avatar, o as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, p as CallConnection, C as CallMode, a as CallPolicy, q as CallQueued, r as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, s as ClipLibraryPlan, e as ClipLibraryUpdate, t as ClipSource, u as ContextMessage, n as CreditBalance, E as EndCallOptions, v as EndCallReason, w as ExternalLlm, k as ListRecordingsQuery, l as ListRecordingsResponse, i as ListSessionsOptions, L as LoopRedirect, m as RecordingAccessResponse, R as RecordingArtifact, x as RecordingArtifactStatus, y as RecordingMode, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, z as isQueued, B as listRecordingsResponseSchema, D as recordingAccessResponseSchema, F as recordingArtifactSchema, G as recordingModeSchema } from './types-6MUqx800.js';
2
+ export { h as Asset, g as AssetKind, A as Avatar, o as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, p as CallConnection, C as CallMode, a as CallPolicy, q as CallQueued, r as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, s as ClipLibraryPlan, e as ClipLibraryUpdate, t as ClipSource, u as ContextMessage, n as CreditBalance, E as EndCallOptions, v as EndCallReason, w as ExternalLlm, k as ListRecordingsQuery, l as ListRecordingsResponse, i as ListSessionsOptions, L as LoopRedirect, m as RecordingAccessResponse, R as RecordingArtifact, x as RecordingArtifactStatus, y as RecordingMode, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, z as isQueued, B as listRecordingsResponseSchema, D as recordingAccessResponseSchema, F as recordingArtifactSchema, G as recordingModeSchema } from './types-D_s7ff7w.js';
3
3
  import './connection-history.js';
4
4
  import 'zod';
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-NMJQEGYW.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-YR5NJTEO.js';
2
2
  import './chunk-MCIXU6XP.js';
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-JtCwa0Ip.js';
2
- import './types-6MUqx800.js';
1
+ import { P as ProxyConfig } from './types-BH9O5leB.js';
2
+ import './types-D_s7ff7w.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-SUKZ77GL.js';
2
- import './chunk-NMJQEGYW.js';
1
+ import { createProxyHandler } from './chunk-SP5SYYSS.js';
2
+ import './chunk-YR5NJTEO.js';
3
3
  import './chunk-MCIXU6XP.js';
4
4
 
5
5
  // src/tanstack-start.ts
@@ -1,4 +1,4 @@
1
- import { C as CallMode, a as CallPolicy } from './types-6MUqx800.js';
1
+ import { C as CallMode, a as CallPolicy } from './types-D_s7ff7w.js';
2
2
 
3
3
  /** The three things a caller can ask the proxy to do. Gate on these, not on URLs. */
4
4
  type ProxyOperation = "connect" | "end" | "avatars" | "credits";
@@ -170,6 +170,11 @@ interface components {
170
170
  url: string;
171
171
  secret: string;
172
172
  };
173
+ /**
174
+ * @description Server-authored content enforcement policy. API-key integrators may request it; browser mints strip it. No default. Requires worker enforcement before emission; transport alone does not provide protection.
175
+ * @enum {string}
176
+ */
177
+ content_policy?: "minor-sexual-content-v1";
173
178
  /**
174
179
  * @description Server-owned recording policy; omitted means off. Legacy audio includes user and avatar audio. participants saves one continuous audio/video file per participant, without mixing their voices.
175
180
  * @enum {string}
@@ -1663,6 +1668,8 @@ interface ExternalLlm {
1663
1668
  }
1664
1669
  /** What YOUR SERVER decides about a call. Never accept any of this from a browser. */
1665
1670
  interface CallPolicy {
1671
+ /** Optional server-owned content safety policy. Never accept a selection or disable from the browser. */
1672
+ contentPolicy?: Wire["LiveKitSessionRequest"]["content_policy"];
1666
1673
  /** Ask the platform to grant the SDK's optional LiveKit connection history collector. */
1667
1674
  connectionHistory?: boolean;
1668
1675
  /** Optional server recording policy. Omitted means off; obtain user consent before enabling. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.27.0",
3
+ "version": "0.27.2",
4
4
  "description": "Realtime Avatar SDK. `realtime-avatar` and the route adapters hold your API key and are server-only; `realtime-avatar/react`, `/browser` and `/tools` never can — importing a server entry into a browser build throws at bundle time.",
5
5
  "license": "MIT",
6
6
  "author": "The Influence Company",