@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.
- package/assets/icon.png +0 -0
- package/dist/.setup/{setup-DUgC9yE7.mjs → setup-CoxvlB5L.mjs} +91 -128
- package/dist/doctor-contract-api.js +189 -7
- package/dist/index.js +1 -1
- package/dist/runtime-api.js +197 -238
- package/node_modules/typebox/build/compile/code.mjs +1 -2
- package/node_modules/typebox/build/compile/validator.mjs +5 -1
- package/node_modules/typebox/build/format/iri.mjs +16 -9
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +7 -5
- package/node_modules/typebox/build/guard/guard.mjs +14 -9
- package/node_modules/typebox/build/guard/index.mjs +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
- package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
- package/node_modules/typebox/build/schema/build.mjs +2 -3
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
- package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
- package/node_modules/typebox/build/schema/parse.mjs +3 -0
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
- package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
- package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
- package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
- package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
- package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
- package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +3 -1
- package/package.json +5 -5
- package/src/audio-pump.ts +9 -16
- package/src/call-events.ts +12 -26
- package/src/call-lifecycle.ts +3 -19
- package/src/runtime-call-control.ts +3 -12
- package/src/runtime-call-events.ts +4 -9
- package/src/runtime-state.ts +0 -10
- package/src/setup.ts +96 -173
- package/src/talk-driver.ts +217 -242
- /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
package/dist/runtime-api.js
CHANGED
|
@@ -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-
|
|
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 {
|
|
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
|
|
111
|
-
|
|
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:
|
|
126
|
-
conversation_resolved_audio_video_mode:
|
|
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.
|
|
275
|
+
return this.#aliases.get(normalizeCallIdentity(identity));
|
|
282
276
|
}
|
|
283
277
|
has(identity) {
|
|
284
|
-
return this.
|
|
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
|
-
|
|
1333
|
-
|
|
1334
|
-
const captureReadyPromise =
|
|
1335
|
-
|
|
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)
|
|
1351
|
-
else
|
|
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)
|
|
1361
|
-
else
|
|
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
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
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
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
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
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
params.
|
|
2496
|
-
})
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
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
|
-
|
|
2545
|
-
|
|
2493
|
+
if (!response) startResponse();
|
|
2494
|
+
talk.startOutputAudio({
|
|
2546
2495
|
turnId,
|
|
2547
|
-
payload:
|
|
2548
|
-
role,
|
|
2549
|
-
text
|
|
2550
|
-
},
|
|
2551
|
-
final
|
|
2496
|
+
payload: { callUUID: params.callUUID }
|
|
2552
2497
|
});
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
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
|
-
|
|
2569
|
-
|
|
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: "
|
|
2608
|
-
|
|
2529
|
+
type: "input.audio.committed",
|
|
2530
|
+
turnId,
|
|
2531
|
+
payload: { callUUID: params.callUUID },
|
|
2609
2532
|
final: true
|
|
2610
2533
|
});
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
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.
|
|
2619
|
-
payload: {
|
|
2620
|
-
final: true
|
|
2570
|
+
type: "session.ready",
|
|
2571
|
+
payload: { callUUID: params.callUUID }
|
|
2621
2572
|
});
|
|
2622
|
-
|
|
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
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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) {
|