@openclaw/facetime 2026.9.6 → 2026.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{setup-DUgC9yE7.mjs → setup-CoxvlB5L.mjs} +91 -128
  3. package/dist/doctor-contract-api.js +189 -7
  4. package/dist/index.js +1 -1
  5. package/dist/runtime-api.js +197 -238
  6. package/node_modules/typebox/build/compile/code.mjs +1 -2
  7. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  8. package/node_modules/typebox/build/format/iri.mjs +16 -9
  9. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  10. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  11. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  12. package/node_modules/typebox/build/guard/index.mjs +6 -0
  13. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  14. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  15. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  16. package/node_modules/typebox/build/schema/build.mjs +2 -3
  17. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  18. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  19. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  20. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  21. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  22. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  23. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  24. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  25. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  26. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  27. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  28. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  29. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  30. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  31. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  32. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  33. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  34. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  35. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  36. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  37. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  38. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  39. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  40. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  41. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  42. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  43. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  44. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  45. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  46. package/node_modules/typebox/package.json +1 -1
  47. package/node_modules/typebox/readme.md +3 -1
  48. package/package.json +5 -5
  49. package/src/audio-pump.ts +9 -16
  50. package/src/call-events.ts +12 -26
  51. package/src/call-lifecycle.ts +3 -19
  52. package/src/runtime-call-control.ts +3 -12
  53. package/src/runtime-call-events.ts +4 -9
  54. package/src/runtime-state.ts +0 -10
  55. package/src/setup.ts +96 -173
  56. package/src/talk-driver.ts +217 -242
  57. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
@@ -1,4 +1,4 @@
1
- import { l as resolveFaceTimeConfig, n as ensureCaptureBinary, r as ensureHelperArtifacts, s as installFaceTimeDriver, t as runFaceTimeSetup, u as validateFaceTimeConfig } from "./.setup/setup-DUgC9yE7.mjs";
1
+ import { l as resolveFaceTimeConfig, n as ensureCaptureBinary, r as ensureHelperArtifacts, s as installFaceTimeDriver, t as runFaceTimeSetup, u as validateFaceTimeConfig } from "./.setup/setup-CoxvlB5L.mjs";
2
2
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
3
3
  import { REALTIME_VOICE_AGENT_CONSULT_SENDER_AUTH_VERSION, REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceAgentCancelProviderResult, buildRealtimeVoiceAgentConsultPolicyInstructions, buildRealtimeVoiceAgentConsultWorkingResponse, consultRealtimeVoiceAgent, createRealtimeVoiceBridgeSession, createTalkSessionController, getRealtimeVoiceProvider, recordRealtimeVoiceTranscript, recordTalkObservabilityEvent, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow } from "openclaw/plugin-sdk/realtime-voice";
4
4
  import { asBoolean, asRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -13,7 +13,7 @@ import { spawn } from "node:child_process";
13
13
  import { resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
14
14
  import { parseAgentSessionKey, resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing";
15
15
  import { resolveConfiguredSecretInputString } from "openclaw/plugin-sdk/secret-input-runtime";
16
- import { resolveRealtimeBootstrapContextInstructions } from "openclaw/plugin-sdk/realtime-bootstrap-context";
16
+ import { resolveRealtimeVoiceAgentContextInstructions } from "openclaw/plugin-sdk/realtime-bootstrap-context";
17
17
  //#region extensions/facetime/src/call-events.ts
18
18
  const TU_CALL_STATUS = {
19
19
  outgoingCreated: 0,
@@ -25,6 +25,10 @@ function readFiniteNumber(value) {
25
25
  const number = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
26
26
  return Number.isFinite(number) ? number : void 0;
27
27
  }
28
+ function readInteger(value) {
29
+ const number = readFiniteNumber(value);
30
+ return Number.isInteger(number) ? number : void 0;
31
+ }
28
32
  function normalizeCallTransport(value) {
29
33
  const transport = asRecord(value);
30
34
  const base = {
@@ -85,8 +89,7 @@ function collectHandleCandidates(value, candidates = [], seen = /* @__PURE__ */
85
89
  if (!value || typeof value !== "object" || seen.has(value)) return candidates;
86
90
  seen.add(value);
87
91
  const record = asRecord(value);
88
- for (const [key, nested] of Object.entries(record)) if (handleValueKeys.has(key)) collectHandleCandidates(nested, candidates, seen);
89
- else if (nested && typeof nested === "object") collectHandleCandidates(nested, candidates, seen);
92
+ for (const [key, nested] of Object.entries(record)) if (handleValueKeys.has(key) || nested && typeof nested === "object") collectHandleCandidates(nested, candidates, seen);
90
93
  return candidates;
91
94
  }
92
95
  function normalizeFaceTimeHandleCandidates(value) {
@@ -107,10 +110,8 @@ function normalizeFaceTimeCallEvent(value) {
107
110
  const proxyIdentifier = normalizeOptionalString(data.proxy_identifier);
108
111
  const conversationUUID = normalizeOptionalString(data.conversation_uuid);
109
112
  const conversationGroupUUID = normalizeOptionalString(data.conversation_group_uuid);
110
- const conversationAVMode = typeof data.conversation_av_mode === "number" ? data.conversation_av_mode : typeof data.conversation_av_mode === "string" ? Number(data.conversation_av_mode) : void 0;
111
- const conversationResolvedAVMode = typeof data.conversation_resolved_audio_video_mode === "number" ? data.conversation_resolved_audio_video_mode : typeof data.conversation_resolved_audio_video_mode === "string" ? Number(data.conversation_resolved_audio_video_mode) : void 0;
112
- const status = typeof data.call_status === "number" ? data.call_status : typeof data.call_status === "string" ? Number(data.call_status) : void 0;
113
- if (!callUUID || !Number.isInteger(status)) return;
113
+ const status = readInteger(data.call_status);
114
+ if (!callUUID || status === void 0) return;
114
115
  return {
115
116
  event: "ft-call-status-changed",
116
117
  data: {
@@ -122,8 +123,8 @@ function normalizeFaceTimeCallEvent(value) {
122
123
  conversation_group_uuid: conversationGroupUUID,
123
124
  conversation_audio_enabled: data.conversation_audio_enabled === true,
124
125
  conversation_video_enabled: data.conversation_video_enabled === true,
125
- conversation_av_mode: Number.isInteger(conversationAVMode) ? conversationAVMode : void 0,
126
- conversation_resolved_audio_video_mode: Number.isInteger(conversationResolvedAVMode) ? conversationResolvedAVMode : void 0,
126
+ conversation_av_mode: readInteger(data.conversation_av_mode),
127
+ conversation_resolved_audio_video_mode: readInteger(data.conversation_resolved_audio_video_mode),
127
128
  is_outgoing: data.is_outgoing === true,
128
129
  has_ended: data.has_ended === true,
129
130
  is_sending_audio: data.is_sending_audio === true,
@@ -180,7 +181,6 @@ var FaceTimeCallInstance = class {
180
181
  #carrierClosure;
181
182
  #commandTail;
182
183
  constructor(canonicalId, phase) {
183
- this.canonicalId = canonicalId;
184
184
  this.lifecycleAbort = new AbortController();
185
185
  this.aliases = /* @__PURE__ */ new Set();
186
186
  this.generation = 1;
@@ -192,12 +192,6 @@ var FaceTimeCallInstance = class {
192
192
  this.carrierMode = phase === "ringing" ? "ringing" : "muted";
193
193
  this.aliases.add(normalizeCallIdentity(canonicalId));
194
194
  }
195
- get identity() {
196
- return {
197
- canonical: this.canonicalId,
198
- aliases: this.aliases
199
- };
200
- }
201
195
  captureGeneration() {
202
196
  return this.generation;
203
197
  }
@@ -278,19 +272,16 @@ var FaceTimeCallRegistry = class {
278
272
  return (this.#active && this.#active.phase !== "closed" ? [this.#active] : [])[Symbol.iterator]();
279
273
  }
280
274
  get(identity) {
281
- return this.resolve(identity);
275
+ return this.#aliases.get(normalizeCallIdentity(identity));
282
276
  }
283
277
  has(identity) {
284
- return this.resolve(identity) !== void 0;
278
+ return this.get(identity) !== void 0;
285
279
  }
286
280
  create(call) {
287
281
  if (this.#active && this.#active.phase !== "closed") throw new Error("another FaceTime call lifecycle is already active");
288
282
  this.#active = call;
289
283
  for (const alias of call.aliases) this.#aliases.set(alias, call);
290
284
  }
291
- resolve(identity) {
292
- return this.#aliases.get(normalizeCallIdentity(identity));
293
- }
294
285
  retainAlias(call, identity) {
295
286
  if (this.#active !== call || call.phase === "closed") throw new Error("cannot retain an alias for an inactive FaceTime call");
296
287
  const alias = normalizeCallIdentity(identity);
@@ -1329,26 +1320,18 @@ function startFaceTimeAudioPump(params) {
1329
1320
  let routeReadySettled = false;
1330
1321
  let routeReadyTimer;
1331
1322
  let captureStderr = "";
1332
- let resolveCaptureReady = () => {};
1333
- let rejectCaptureReady = (_error) => {};
1334
- const captureReadyPromise = new Promise((resolve, reject) => {
1335
- resolveCaptureReady = resolve;
1336
- rejectCaptureReady = reject;
1337
- });
1338
- let resolveRouteReady = () => {};
1339
- let rejectRouteReady = (_error) => {};
1340
- const routeReadyPromise = new Promise((resolve, reject) => {
1341
- resolveRouteReady = resolve;
1342
- rejectRouteReady = reject;
1343
- });
1323
+ const captureReady = createDeferred();
1324
+ const routeReady = createDeferred();
1325
+ const captureReadyPromise = captureReady.promise;
1326
+ const routeReadyPromise = routeReady.promise;
1344
1327
  captureReadyPromise.catch(() => {});
1345
1328
  routeReadyPromise.catch(() => {});
1346
1329
  const settleCaptureReady = (error) => {
1347
1330
  if (captureReadySettled) return;
1348
1331
  captureReadySettled = true;
1349
1332
  clearTimeout(captureReadyTimer);
1350
- if (error) rejectCaptureReady(error);
1351
- else resolveCaptureReady();
1333
+ if (error) captureReady.reject(error);
1334
+ else captureReady.resolve();
1352
1335
  };
1353
1336
  const settleRouteReady = (error) => {
1354
1337
  if (routeReadySettled) return;
@@ -1357,8 +1340,8 @@ function startFaceTimeAudioPump(params) {
1357
1340
  clearTimeout(routeReadyTimer);
1358
1341
  routeReadyTimer = void 0;
1359
1342
  }
1360
- if (error) rejectRouteReady(error);
1361
- else resolveRouteReady();
1343
+ if (error) routeReady.reject(error);
1344
+ else routeReady.resolve();
1362
1345
  };
1363
1346
  const reportFailure = (error, suppressionLost) => {
1364
1347
  if (stopped) return;
@@ -2231,7 +2214,6 @@ async function startFaceTimeTalkDriver(params) {
2231
2214
  let lastInputAudioStatusAt = 0;
2232
2215
  let callMediaTimestampMs = 0;
2233
2216
  let modelMediaGeneration = 1;
2234
- let responseGeneration = 0;
2235
2217
  let response;
2236
2218
  const settledResponseIds = /* @__PURE__ */ new Set();
2237
2219
  let suppressNextUnkeyedLegacyTerminal = false;
@@ -2240,7 +2222,6 @@ async function startFaceTimeTalkDriver(params) {
2240
2222
  let activated = false;
2241
2223
  let providerReady = false;
2242
2224
  const providerReadyDeferred = createDeferred();
2243
- let providerConnectPromise;
2244
2225
  let audioReadyPromise;
2245
2226
  let interruptProviderConnect;
2246
2227
  let mediaSuspensionError;
@@ -2317,15 +2298,11 @@ async function startFaceTimeTalkDriver(params) {
2317
2298
  return await closePromise;
2318
2299
  };
2319
2300
  const remember = (input) => talk.emit(input);
2320
- const ensureTurn = () => {
2321
- return talk.ensureTurn({ payload: { callUUID: params.callUUID } }).turnId;
2322
- };
2301
+ const ensureTurn = () => talk.ensureTurn({ payload: { callUUID: params.callUUID } }).turnId;
2323
2302
  const finishOutputAudio = (reason) => {
2324
2303
  talk.finishOutputAudio({ payload: { reason } });
2325
2304
  };
2326
- const endTurn = (reason) => {
2327
- return talk.endTurn({ payload: { reason } }).ok;
2328
- };
2305
+ const endTurn = (reason) => talk.endTurn({ payload: { reason } }).ok;
2329
2306
  const playedCurrentResponseMs = () => response === void 0 ? 0 : Math.max(0, ((pump?.playedAudioFrames() ?? 0) - response.playbackStartFrame) / 24);
2330
2307
  const resetResponsePlayback = () => {
2331
2308
  response = void 0;
@@ -2361,7 +2338,6 @@ async function startFaceTimeTalkDriver(params) {
2361
2338
  }
2362
2339
  response = {
2363
2340
  id: responseId,
2364
- generation: ++responseGeneration,
2365
2341
  startTimestampMs: callMediaTimestampMs,
2366
2342
  playbackStartFrame: pump?.playedAudioFrames() ?? 0
2367
2343
  };
@@ -2462,198 +2438,191 @@ async function startFaceTimeTalkDriver(params) {
2462
2438
  finishDrainedResponse();
2463
2439
  }
2464
2440
  });
2465
- const connectProvider = async () => {
2466
- if (providerConnectPromise) return await providerConnectPromise;
2467
- providerConnectPromise = (async () => {
2468
- let removeAbortListener;
2469
- let readinessTimer;
2470
- const interrupted = new Promise((_resolve, reject) => {
2471
- const interrupt = () => reject(/* @__PURE__ */ new Error("FaceTime talk startup aborted"));
2472
- interruptProviderConnect = interrupt;
2473
- params.signal?.addEventListener("abort", interrupt, { once: true });
2474
- removeAbortListener = () => params.signal?.removeEventListener("abort", interrupt);
2475
- if (params.signal?.aborted || stopped || mediaSuspended) interrupt();
2441
+ const providerConnect = (async () => {
2442
+ let removeAbortListener;
2443
+ let readinessTimer;
2444
+ const interrupted = new Promise((_resolve, reject) => {
2445
+ const interrupt = () => reject(/* @__PURE__ */ new Error("FaceTime talk startup aborted"));
2446
+ interruptProviderConnect = interrupt;
2447
+ params.signal?.addEventListener("abort", interrupt, { once: true });
2448
+ removeAbortListener = () => params.signal?.removeEventListener("abort", interrupt);
2449
+ if (params.signal?.aborted || stopped || mediaSuspended) interrupt();
2450
+ });
2451
+ const readinessTimedOut = new Promise((_resolve, reject) => {
2452
+ readinessTimer = setTimeout(() => {
2453
+ reject(/* @__PURE__ */ new Error("Realtime provider was not ready within 15 seconds"));
2454
+ }, REALTIME_READY_TIMEOUT_MS);
2455
+ readinessTimer.unref?.();
2456
+ });
2457
+ const prepareAndConnect = async () => {
2458
+ const providerResolution = resolveFaceTimeRealtimeProvider({
2459
+ config: params.config,
2460
+ fullConfig: params.fullConfig,
2461
+ agentId: consultAgentId
2476
2462
  });
2477
- const readinessTimedOut = new Promise((_resolve, reject) => {
2478
- readinessTimer = setTimeout(() => {
2479
- reject(/* @__PURE__ */ new Error("Realtime provider was not ready within 15 seconds"));
2480
- }, REALTIME_READY_TIMEOUT_MS);
2481
- readinessTimer.unref?.();
2463
+ const agentContextResolution = resolveRealtimeVoiceAgentContextInstructions({
2464
+ config: params.fullConfig,
2465
+ agentId: consultAgentId,
2466
+ sessionKey: requesterSessionKey,
2467
+ warn: (message) => params.logger.warn?.(`[facetime] realtime agent context: ${message}`)
2482
2468
  });
2483
- const prepareAndConnect = async () => {
2484
- const providerResolution = resolveFaceTimeRealtimeProvider({
2485
- config: params.config,
2486
- fullConfig: params.fullConfig,
2487
- agentId: consultAgentId
2488
- });
2489
- const bootstrapContextResolution = resolveRealtimeBootstrapContextInstructions({
2490
- config: params.fullConfig,
2491
- agentId: consultAgentId,
2492
- sessionKey: requesterSessionKey,
2493
- warn: (message) => params.logger.warn?.(`[facetime] realtime bootstrap context: ${message}`)
2494
- }).catch((error) => {
2495
- params.logger.warn?.(`[facetime] realtime bootstrap context unavailable: ${formatErrorMessage(error)}`);
2496
- });
2497
- const [resolved, bootstrapContext] = await Promise.all([providerResolution, bootstrapContextResolution]);
2498
- if (params.signal?.aborted || stopped || mediaSuspended) throw new Error("FaceTime talk startup aborted");
2499
- bridge = createRealtimeVoiceBridgeSession({
2500
- provider: resolved.provider,
2501
- capabilities: resolved.capabilities,
2502
- cfg: params.fullConfig,
2503
- agentId: consultAgentId,
2504
- providerConfig: resolved.providerConfig,
2505
- audioFormat: REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ,
2506
- instructions: buildRealtimeInstructions({
2507
- instructions: params.config.realtime.instructions,
2508
- bootstrapContext,
2509
- toolPolicy: params.config.realtime.toolPolicy
2510
- }),
2511
- autoRespondToAudio: true,
2512
- triggerGreetingOnReady: false,
2513
- initialGreetingInstructions: initialGreeting.instructions,
2514
- markStrategy: "ack-immediately",
2515
- tools: resolveRealtimeVoiceAgentConsultTools(params.config.realtime.toolPolicy, [FACETIME_END_CALL_TOOL]),
2516
- audioSink: {
2517
- isOpen: () => !stopped && !mediaSuspended,
2518
- sendAudio(audio, metadata) {
2519
- if (stopped || mediaSuspended) return;
2520
- const turnId = ensureTurn();
2521
- if (!response) startResponse();
2522
- talk.startOutputAudio({
2523
- turnId,
2524
- payload: { callUUID: params.callUUID }
2525
- });
2526
- remember({
2527
- type: "output.audio.delta",
2528
- turnId,
2529
- payload: { byteLength: audio.byteLength }
2530
- });
2531
- pump?.writeOutputAudio(audio, metadata);
2532
- },
2533
- getPlaybackState: () => pump.getPlaybackState(),
2534
- clearAudio() {
2535
- if (stopped || mediaSuspended) return;
2536
- pump?.clearOutputAudio();
2537
- resetResponsePlayback();
2538
- finishOutputAudio("clear");
2539
- }
2540
- },
2541
- onTranscript(role, text, final) {
2469
+ const [resolved, bootstrapContext] = await Promise.all([providerResolution, agentContextResolution]);
2470
+ if (params.signal?.aborted || stopped || mediaSuspended) throw new Error("FaceTime talk startup aborted");
2471
+ bridge = createRealtimeVoiceBridgeSession({
2472
+ provider: resolved.provider,
2473
+ capabilities: resolved.capabilities,
2474
+ cfg: params.fullConfig,
2475
+ agentId: consultAgentId,
2476
+ providerConfig: resolved.providerConfig,
2477
+ audioFormat: REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ,
2478
+ instructions: buildRealtimeInstructions({
2479
+ instructions: params.config.realtime.instructions,
2480
+ bootstrapContext,
2481
+ toolPolicy: params.config.realtime.toolPolicy
2482
+ }),
2483
+ autoRespondToAudio: true,
2484
+ triggerGreetingOnReady: false,
2485
+ initialGreetingInstructions: initialGreeting.instructions,
2486
+ markStrategy: "ack-immediately",
2487
+ tools: resolveRealtimeVoiceAgentConsultTools(params.config.realtime.toolPolicy, [FACETIME_END_CALL_TOOL]),
2488
+ audioSink: {
2489
+ isOpen: () => !stopped && !mediaSuspended,
2490
+ sendAudio(audio, metadata) {
2542
2491
  if (stopped || mediaSuspended) return;
2543
2492
  const turnId = ensureTurn();
2544
- remember({
2545
- type: role === "assistant" ? final ? "output.text.done" : "output.text.delta" : final ? "transcript.done" : "transcript.delta",
2493
+ if (!response) startResponse();
2494
+ talk.startOutputAudio({
2546
2495
  turnId,
2547
- payload: role === "assistant" ? { text } : {
2548
- role,
2549
- text
2550
- },
2551
- final
2496
+ payload: { callUUID: params.callUUID }
2552
2497
  });
2553
- if (role === "user" && final) {
2554
- if (text.trim()) initialGreeting.cancel();
2555
- else initialGreeting.schedule();
2556
- remember({
2557
- type: "input.audio.committed",
2558
- turnId,
2559
- payload: { callUUID: params.callUUID },
2560
- final: true
2561
- });
2562
- }
2563
- if (final) appendTranscript({
2564
- role,
2565
- text
2498
+ remember({
2499
+ type: "output.audio.delta",
2500
+ turnId,
2501
+ payload: { byteLength: audio.byteLength }
2566
2502
  });
2503
+ pump?.writeOutputAudio(audio, metadata);
2567
2504
  },
2568
- onEvent(event) {
2569
- if (stopped || mediaSuspended) return;
2570
- if (!(event.direction === "client" && event.type === "input_audio_buffer.append")) remember({
2571
- type: "health.changed",
2572
- payload: {
2573
- name: `${event.direction}:${event.type}`,
2574
- message: event.detail
2575
- }
2576
- });
2577
- if (event.type === "response.created") startResponse(event.responseId);
2578
- else if (event.type === "session.continuity.reset") resetProviderContinuity();
2579
- else if (event.type === "response.done") {
2580
- if (!event.responseId && suppressNextUnkeyedLegacyTerminal) suppressNextUnkeyedLegacyTerminal = false;
2581
- else finishResponseOutcome({
2582
- status: "completed",
2583
- ...event.responseId ? { responseId: event.responseId } : {}
2584
- }, false);
2585
- } else if (event.type === "error") remember({
2586
- type: "session.error",
2587
- payload: { message: event.detail ?? "Realtime provider error" },
2588
- final: true
2589
- });
2590
- },
2591
- onResponseDone(outcome) {
2592
- finishResponseOutcome(outcome, true);
2593
- },
2594
- onToolCall: consultController.handleToolCall,
2595
- onReady() {
2596
- if (!stopped && !mediaSuspended) {
2597
- remember({
2598
- type: "session.ready",
2599
- payload: { callUUID: params.callUUID }
2600
- });
2601
- providerReadyDeferred.resolve();
2602
- }
2603
- },
2604
- onError(error) {
2505
+ getPlaybackState: () => pump.getPlaybackState(),
2506
+ clearAudio() {
2605
2507
  if (stopped || mediaSuspended) return;
2508
+ pump?.clearOutputAudio();
2509
+ resetResponsePlayback();
2510
+ finishOutputAudio("clear");
2511
+ }
2512
+ },
2513
+ onTranscript(role, text, final) {
2514
+ if (stopped || mediaSuspended) return;
2515
+ const turnId = ensureTurn();
2516
+ remember({
2517
+ type: role === "assistant" ? final ? "output.text.done" : "output.text.delta" : final ? "transcript.done" : "transcript.delta",
2518
+ turnId,
2519
+ payload: role === "assistant" ? { text } : {
2520
+ role,
2521
+ text
2522
+ },
2523
+ final
2524
+ });
2525
+ if (role === "user" && final) {
2526
+ if (text.trim()) initialGreeting.cancel();
2527
+ else initialGreeting.schedule();
2606
2528
  remember({
2607
- type: "session.error",
2608
- payload: { message: formatErrorMessage(error) },
2529
+ type: "input.audio.committed",
2530
+ turnId,
2531
+ payload: { callUUID: params.callUUID },
2609
2532
  final: true
2610
2533
  });
2611
- params.logger.warn(`[facetime] realtime bridge failed: ${formatErrorMessage(error)}`);
2612
- if (!providerReady && !startupSettled) signalStartupFailure(error);
2613
- },
2614
- onClose(reason) {
2615
- if (stopped || mediaSuspended) return;
2616
- finishOutputAudio(reason);
2534
+ }
2535
+ if (final) appendTranscript({
2536
+ role,
2537
+ text
2538
+ });
2539
+ },
2540
+ onEvent(event) {
2541
+ if (stopped || mediaSuspended) return;
2542
+ if (!(event.direction === "client" && event.type === "input_audio_buffer.append")) remember({
2543
+ type: "health.changed",
2544
+ payload: {
2545
+ name: `${event.direction}:${event.type}`,
2546
+ message: event.detail
2547
+ }
2548
+ });
2549
+ if (event.type === "response.created") startResponse(event.responseId);
2550
+ else if (event.type === "session.continuity.reset") resetProviderContinuity();
2551
+ else if (event.type === "response.done") {
2552
+ if (!event.responseId && suppressNextUnkeyedLegacyTerminal) suppressNextUnkeyedLegacyTerminal = false;
2553
+ else finishResponseOutcome({
2554
+ status: "completed",
2555
+ ...event.responseId ? { responseId: event.responseId } : {}
2556
+ }, false);
2557
+ } else if (event.type === "error") remember({
2558
+ type: "session.error",
2559
+ payload: { message: event.detail ?? "Realtime provider error" },
2560
+ final: true
2561
+ });
2562
+ },
2563
+ onResponseDone(outcome) {
2564
+ finishResponseOutcome(outcome, true);
2565
+ },
2566
+ onToolCall: consultController.handleToolCall,
2567
+ onReady() {
2568
+ if (!stopped && !mediaSuspended) {
2617
2569
  remember({
2618
- type: "session.closed",
2619
- payload: { reason },
2620
- final: true
2570
+ type: "session.ready",
2571
+ payload: { callUUID: params.callUUID }
2621
2572
  });
2622
- const error = /* @__PURE__ */ new Error(`Realtime bridge closed unexpectedly: ${reason}`);
2623
- signalStartupFailure(error);
2624
- (async () => {
2625
- await suspendMedia("provider-closed");
2626
- if (await reportFailure(error)) await close("provider-closed");
2627
- })();
2573
+ providerReadyDeferred.resolve();
2628
2574
  }
2629
- });
2630
- await bridge.connect();
2631
- };
2632
- try {
2633
- await Promise.race([
2634
- Promise.all([prepareAndConnect(), providerReadyDeferred.promise]),
2635
- startupFailurePromise,
2636
- interrupted,
2637
- readinessTimedOut
2638
- ]);
2639
- if (startupFailure) throw startupFailure;
2640
- if (params.signal?.aborted || stopped || mediaSuspended) throw new Error("FaceTime talk startup aborted");
2641
- startupSettled = true;
2642
- providerReady = true;
2643
- } catch (error) {
2644
- const normalized = error instanceof Error ? error : new Error(String(error));
2645
- await suspendMedia(params.signal?.aborted || stopped ? "startup-aborted" : "connect-failed");
2646
- if (stopped || await reportFailure(normalized)) await close(params.signal?.aborted || stopped ? "startup-aborted" : "connect-failed");
2647
- throw normalized;
2648
- } finally {
2649
- if (readinessTimer) clearTimeout(readinessTimer);
2650
- interruptProviderConnect = void 0;
2651
- removeAbortListener?.();
2652
- }
2653
- })();
2654
- return await providerConnectPromise;
2655
- };
2656
- const providerConnect = connectProvider();
2575
+ },
2576
+ onError(error) {
2577
+ if (stopped || mediaSuspended) return;
2578
+ remember({
2579
+ type: "session.error",
2580
+ payload: { message: formatErrorMessage(error) },
2581
+ final: true
2582
+ });
2583
+ params.logger.warn(`[facetime] realtime bridge failed: ${formatErrorMessage(error)}`);
2584
+ if (!providerReady && !startupSettled) signalStartupFailure(error);
2585
+ },
2586
+ onClose(reason) {
2587
+ if (stopped || mediaSuspended) return;
2588
+ finishOutputAudio(reason);
2589
+ remember({
2590
+ type: "session.closed",
2591
+ payload: { reason },
2592
+ final: true
2593
+ });
2594
+ const error = /* @__PURE__ */ new Error(`Realtime bridge closed unexpectedly: ${reason}`);
2595
+ signalStartupFailure(error);
2596
+ (async () => {
2597
+ await suspendMedia("provider-closed");
2598
+ if (await reportFailure(error)) await close("provider-closed");
2599
+ })();
2600
+ }
2601
+ });
2602
+ await bridge.connect();
2603
+ };
2604
+ try {
2605
+ await Promise.race([
2606
+ Promise.all([prepareAndConnect(), providerReadyDeferred.promise]),
2607
+ startupFailurePromise,
2608
+ interrupted,
2609
+ readinessTimedOut
2610
+ ]);
2611
+ if (startupFailure) throw startupFailure;
2612
+ if (params.signal?.aborted || stopped || mediaSuspended) throw new Error("FaceTime talk startup aborted");
2613
+ startupSettled = true;
2614
+ providerReady = true;
2615
+ } catch (error) {
2616
+ const normalized = error instanceof Error ? error : new Error(String(error));
2617
+ await suspendMedia(params.signal?.aborted || stopped ? "startup-aborted" : "connect-failed");
2618
+ if (stopped || await reportFailure(normalized)) await close(params.signal?.aborted || stopped ? "startup-aborted" : "connect-failed");
2619
+ throw normalized;
2620
+ } finally {
2621
+ if (readinessTimer) clearTimeout(readinessTimer);
2622
+ interruptProviderConnect = void 0;
2623
+ removeAbortListener?.();
2624
+ }
2625
+ })();
2657
2626
  providerConnect.catch(() => {});
2658
2627
  try {
2659
2628
  await Promise.race([pump.suppressionReady(), startupFailurePromise]);
@@ -2947,14 +2916,7 @@ function createFaceTimeCallControl(params) {
2947
2916
  },
2948
2917
  async onFailure(error) {
2949
2918
  const failureReason = `talk-failed: ${formatErrorMessage(error)}`;
2950
- if (!call.talk) {
2951
- const carrierClosed = await waitForStartupCarrierHangup(call, failureReason);
2952
- if (carrierClosed) queueMicrotask(() => {
2953
- closeCall(call, failureReason);
2954
- });
2955
- return carrierClosed;
2956
- }
2957
- const carrierClosed = await attemptCarrierHangup(call, failureReason, { closeLocal: false });
2919
+ const carrierClosed = call.talk ? await attemptCarrierHangup(call, failureReason, { closeLocal: false }) : await waitForStartupCarrierHangup(call, failureReason);
2958
2920
  if (carrierClosed) queueMicrotask(() => {
2959
2921
  closeCall(call, failureReason);
2960
2922
  });
@@ -3127,9 +3089,6 @@ function updateCallStatus(call, event) {
3127
3089
  if (typeof event.data.local_meter_level === "number") call.maxLocalMeterLevel = Math.max(call.maxLocalMeterLevel ?? 0, event.data.local_meter_level);
3128
3090
  if (typeof event.data.remote_meter_level === "number") call.maxRemoteMeterLevel = Math.max(call.maxRemoteMeterLevel ?? 0, event.data.remote_meter_level);
3129
3091
  }
3130
- function createManagedCall(params) {
3131
- return new ActiveFaceTimeCall(params);
3132
- }
3133
3092
  //#endregion
3134
3093
  //#region extensions/facetime/src/runtime-call-events.ts
3135
3094
  function createFaceTimeCallEventHandler(params) {
@@ -3192,7 +3151,7 @@ function createFaceTimeCallEventHandler(params) {
3192
3151
  params.logger.warn("[facetime] ignored incoming call; another FaceTime bridge is active");
3193
3152
  return;
3194
3153
  }
3195
- const call = createManagedCall({
3154
+ const call = new ActiveFaceTimeCall({
3196
3155
  callUUID,
3197
3156
  phase: "ringing",
3198
3157
  owner,
@@ -3243,7 +3202,7 @@ function createFaceTimeCallEventHandler(params) {
3243
3202
  params.logger.warn("[facetime] ignored active call; another FaceTime bridge is active");
3244
3203
  return;
3245
3204
  }
3246
- call = createManagedCall({
3205
+ call = new ActiveFaceTimeCall({
3247
3206
  callUUID,
3248
3207
  phase: "active",
3249
3208
  owner,
@@ -3305,7 +3264,7 @@ function createFaceTimeCallEventHandler(params) {
3305
3264
  if (!owner || !canPromotePendingDial(pending)) return;
3306
3265
  let ringingCall = resolveEventCall(event);
3307
3266
  if (!ringingCall && params.calls.size === 0) {
3308
- ringingCall = createManagedCall({
3267
+ ringingCall = new ActiveFaceTimeCall({
3309
3268
  callUUID,
3310
3269
  phase: "ringing",
3311
3270
  owner,
@@ -16,9 +16,8 @@ function Separator() {
16
16
  function ImportSection(build) {
17
17
  const context = build.UseUnevaluated() ? [`import { CheckContext } from "typebox/schema"`] : [];
18
18
  const hashing = `import { Hashing } from "typebox/system"`;
19
- const format = `import { Format } from "typebox/format"`;
20
19
  const guard = `import { Guard } from "typebox/guard"`;
21
- return [...context, hashing, format, guard];
20
+ return [...context, hashing, guard];
22
21
  }
23
22
  // ------------------------------------------------------------------
24
23
  // ExternalSection
@@ -1,4 +1,5 @@
1
1
  // deno-fmt-ignore-file
2
+ import { EnableParseErrors, DisableParseErrors } from '../system/settings/internal.mjs';
2
3
  import { Settings } from '../system/settings/index.mjs';
3
4
  import { Errors, Clean, Convert, Create, Default, Decode, Encode, HasCodec, Parser, ParseError } from '../value/index.mjs';
4
5
  import { Build } from '../schema/index.mjs';
@@ -51,7 +52,10 @@ export class Validator {
51
52
  return value;
52
53
  if (Settings.Get().correctiveParse)
53
54
  return Parser(this.Context(), this.Type(), value);
54
- throw new ParseError(value, this.Errors(value));
55
+ EnableParseErrors();
56
+ const errors = this.Errors(value);
57
+ DisableParseErrors();
58
+ throw new ParseError(value, errors);
55
59
  }
56
60
  /** Returns an array of validation errors for the given value. */
57
61
  Errors(value) {