@omote/core 0.10.5 → 0.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -34
- package/dist/chunk-3FILA2CD.mjs +785 -0
- package/dist/chunk-3FILA2CD.mjs.map +1 -0
- package/dist/chunk-5WIOGMJA.mjs +785 -0
- package/dist/chunk-5WIOGMJA.mjs.map +1 -0
- package/dist/chunk-NWZMIQK4.mjs +782 -0
- package/dist/chunk-NWZMIQK4.mjs.map +1 -0
- package/dist/chunk-WW4XAUJ3.mjs +208 -0
- package/dist/chunk-WW4XAUJ3.mjs.map +1 -0
- package/dist/index.d.mts +84 -79
- package/dist/index.d.ts +84 -79
- package/dist/index.js +514 -406
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +233 -199
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.js +5 -0
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs +1 -1
- package/dist/otlp-2BML6FIK.mjs +7 -0
- package/dist/otlp-2BML6FIK.mjs.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,6 +30,220 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/telemetry/exporters/otlp.ts
|
|
34
|
+
var otlp_exports = {};
|
|
35
|
+
__export(otlp_exports, {
|
|
36
|
+
OTLPExporter: () => OTLPExporter
|
|
37
|
+
});
|
|
38
|
+
function toOTLPAttributeValue(value) {
|
|
39
|
+
if (typeof value === "string") return { stringValue: value };
|
|
40
|
+
if (typeof value === "number") return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };
|
|
41
|
+
return { boolValue: value };
|
|
42
|
+
}
|
|
43
|
+
function toOTLPAttributes(attrs) {
|
|
44
|
+
return Object.entries(attrs).filter(([, v]) => v !== void 0).map(([key, value]) => ({ key, value: toOTLPAttributeValue(value) }));
|
|
45
|
+
}
|
|
46
|
+
function spanToOTLPSpan(span) {
|
|
47
|
+
return {
|
|
48
|
+
traceId: span.traceId,
|
|
49
|
+
spanId: span.spanId,
|
|
50
|
+
parentSpanId: span.parentSpanId || "",
|
|
51
|
+
name: span.name,
|
|
52
|
+
kind: 1,
|
|
53
|
+
// INTERNAL
|
|
54
|
+
startTimeUnixNano: String(span.epochMs * 1e6),
|
|
55
|
+
endTimeUnixNano: String(span.endEpochMs * 1e6),
|
|
56
|
+
attributes: toOTLPAttributes(span.attributes),
|
|
57
|
+
status: {
|
|
58
|
+
code: span.status === "ok" ? StatusCode.OK : StatusCode.ERROR,
|
|
59
|
+
message: span.error?.message || ""
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function metricToOTLPMetric(metric) {
|
|
64
|
+
const attributes = toOTLPAttributes(metric.attributes);
|
|
65
|
+
const timeUnixNano = String(metric.timestamp * 1e6);
|
|
66
|
+
if (metric.type === "counter") {
|
|
67
|
+
return {
|
|
68
|
+
name: metric.name,
|
|
69
|
+
sum: {
|
|
70
|
+
dataPoints: [{
|
|
71
|
+
attributes,
|
|
72
|
+
timeUnixNano,
|
|
73
|
+
asInt: metric.value
|
|
74
|
+
}],
|
|
75
|
+
aggregationTemporality: 1,
|
|
76
|
+
// DELTA
|
|
77
|
+
isMonotonic: true
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (metric.histogramData) {
|
|
82
|
+
return {
|
|
83
|
+
name: metric.name,
|
|
84
|
+
histogram: {
|
|
85
|
+
dataPoints: [{
|
|
86
|
+
attributes,
|
|
87
|
+
timeUnixNano,
|
|
88
|
+
count: metric.histogramData.count,
|
|
89
|
+
sum: metric.histogramData.sum,
|
|
90
|
+
min: metric.histogramData.min,
|
|
91
|
+
max: metric.histogramData.max,
|
|
92
|
+
explicitBounds: metric.histogramData.bucketBoundaries,
|
|
93
|
+
bucketCounts: metric.histogramData.bucketCounts
|
|
94
|
+
}],
|
|
95
|
+
aggregationTemporality: 1
|
|
96
|
+
// DELTA
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
name: metric.name,
|
|
102
|
+
gauge: {
|
|
103
|
+
dataPoints: [{
|
|
104
|
+
attributes,
|
|
105
|
+
timeUnixNano,
|
|
106
|
+
asDouble: metric.value
|
|
107
|
+
}]
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
var StatusCode, OTLPExporter;
|
|
112
|
+
var init_otlp = __esm({
|
|
113
|
+
"src/telemetry/exporters/otlp.ts"() {
|
|
114
|
+
"use strict";
|
|
115
|
+
StatusCode = {
|
|
116
|
+
UNSET: 0,
|
|
117
|
+
OK: 1,
|
|
118
|
+
ERROR: 2
|
|
119
|
+
};
|
|
120
|
+
OTLPExporter = class {
|
|
121
|
+
constructor(config, serviceName = "omote-sdk", serviceVersion = "0.1.0") {
|
|
122
|
+
this.spanBuffer = [];
|
|
123
|
+
this.metricBuffer = [];
|
|
124
|
+
this.flushIntervalId = null;
|
|
125
|
+
this.BUFFER_SIZE = 100;
|
|
126
|
+
this.FLUSH_INTERVAL_MS = 5e3;
|
|
127
|
+
this.isShutdown = false;
|
|
128
|
+
const parsed = new URL(config.endpoint);
|
|
129
|
+
if (parsed.protocol !== "https:") {
|
|
130
|
+
const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
|
|
131
|
+
if (!isLocalhost) {
|
|
132
|
+
throw new Error("OTLP endpoint must use HTTPS (or localhost for development)");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
this.config = {
|
|
136
|
+
endpoint: config.endpoint,
|
|
137
|
+
timeoutMs: config.timeoutMs ?? 1e4,
|
|
138
|
+
headers: config.headers ? { ...config.headers } : {}
|
|
139
|
+
};
|
|
140
|
+
this.serviceName = serviceName;
|
|
141
|
+
this.serviceVersion = serviceVersion;
|
|
142
|
+
this.flushIntervalId = setInterval(() => {
|
|
143
|
+
this.flush().catch(console.error);
|
|
144
|
+
}, this.FLUSH_INTERVAL_MS);
|
|
145
|
+
}
|
|
146
|
+
exportSpan(span) {
|
|
147
|
+
if (this.isShutdown) return;
|
|
148
|
+
this.spanBuffer.push(span);
|
|
149
|
+
if (this.spanBuffer.length >= this.BUFFER_SIZE) {
|
|
150
|
+
this.flush().catch(console.error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exportMetric(metric) {
|
|
154
|
+
if (this.isShutdown) return;
|
|
155
|
+
this.metricBuffer.push(metric);
|
|
156
|
+
if (this.metricBuffer.length >= this.BUFFER_SIZE) {
|
|
157
|
+
this.flush().catch(console.error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async flush() {
|
|
161
|
+
if (this.isShutdown) return;
|
|
162
|
+
const spans = this.spanBuffer.splice(0);
|
|
163
|
+
const metrics = this.metricBuffer.splice(0);
|
|
164
|
+
const promises = [];
|
|
165
|
+
if (spans.length > 0) {
|
|
166
|
+
promises.push(this.exportSpans(spans));
|
|
167
|
+
}
|
|
168
|
+
if (metrics.length > 0) {
|
|
169
|
+
promises.push(this.exportMetrics(metrics));
|
|
170
|
+
}
|
|
171
|
+
await Promise.all(promises);
|
|
172
|
+
}
|
|
173
|
+
async shutdown() {
|
|
174
|
+
if (this.flushIntervalId) {
|
|
175
|
+
clearInterval(this.flushIntervalId);
|
|
176
|
+
this.flushIntervalId = null;
|
|
177
|
+
}
|
|
178
|
+
await this.flush();
|
|
179
|
+
this.isShutdown = true;
|
|
180
|
+
}
|
|
181
|
+
async exportSpans(spans) {
|
|
182
|
+
const resourceAttrs = [
|
|
183
|
+
{ key: "service.name", value: { stringValue: this.serviceName } },
|
|
184
|
+
{ key: "service.version", value: { stringValue: this.serviceVersion } },
|
|
185
|
+
{ key: "telemetry.sdk.name", value: { stringValue: "omote-sdk" } },
|
|
186
|
+
{ key: "telemetry.sdk.language", value: { stringValue: "javascript" } }
|
|
187
|
+
];
|
|
188
|
+
const body = {
|
|
189
|
+
resourceSpans: [{
|
|
190
|
+
resource: { attributes: resourceAttrs },
|
|
191
|
+
scopeSpans: [{
|
|
192
|
+
scope: { name: "omote-sdk", version: this.serviceVersion },
|
|
193
|
+
spans: spans.map(spanToOTLPSpan)
|
|
194
|
+
}]
|
|
195
|
+
}]
|
|
196
|
+
};
|
|
197
|
+
const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/traces";
|
|
198
|
+
await this.sendRequest(endpoint, body);
|
|
199
|
+
}
|
|
200
|
+
async exportMetrics(metrics) {
|
|
201
|
+
const resourceAttrs = [
|
|
202
|
+
{ key: "service.name", value: { stringValue: this.serviceName } },
|
|
203
|
+
{ key: "service.version", value: { stringValue: this.serviceVersion } }
|
|
204
|
+
];
|
|
205
|
+
const body = {
|
|
206
|
+
resourceMetrics: [{
|
|
207
|
+
resource: { attributes: resourceAttrs },
|
|
208
|
+
scopeMetrics: [{
|
|
209
|
+
scope: { name: "omote-sdk", version: this.serviceVersion },
|
|
210
|
+
metrics: metrics.map(metricToOTLPMetric)
|
|
211
|
+
}]
|
|
212
|
+
}]
|
|
213
|
+
};
|
|
214
|
+
const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/metrics";
|
|
215
|
+
await this.sendRequest(endpoint, body);
|
|
216
|
+
}
|
|
217
|
+
async sendRequest(endpoint, body) {
|
|
218
|
+
const controller = new AbortController();
|
|
219
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
220
|
+
try {
|
|
221
|
+
const response = await fetch(endpoint, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
...this.config.headers
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify(body),
|
|
228
|
+
signal: controller.signal
|
|
229
|
+
});
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
console.warn(`[OTLP] Export failed: ${response.status} ${response.statusText}`);
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error.name === "AbortError") {
|
|
235
|
+
console.warn("[OTLP] Export timed out");
|
|
236
|
+
} else {
|
|
237
|
+
console.warn("[OTLP] Export error:", error);
|
|
238
|
+
}
|
|
239
|
+
} finally {
|
|
240
|
+
clearTimeout(timeoutId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
30
247
|
// src/index.ts
|
|
31
248
|
var index_exports = {};
|
|
32
249
|
__export(index_exports, {
|
|
@@ -57,7 +274,6 @@ __export(index_exports, {
|
|
|
57
274
|
EmotionResolver: () => EmotionResolver,
|
|
58
275
|
EmphasisDetector: () => EmphasisDetector,
|
|
59
276
|
ErrorCodes: () => ErrorCodes,
|
|
60
|
-
ErrorTypes: () => ErrorTypes,
|
|
61
277
|
EventEmitter: () => EventEmitter,
|
|
62
278
|
FaceCompositor: () => FaceCompositor,
|
|
63
279
|
HF_CDN_URLS: () => HF_CDN_URLS,
|
|
@@ -329,6 +545,8 @@ var jsonFormatter = (entry) => {
|
|
|
329
545
|
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
330
546
|
output.data = entry.data;
|
|
331
547
|
}
|
|
548
|
+
if (entry.traceId) output.traceId = entry.traceId;
|
|
549
|
+
if (entry.spanId) output.spanId = entry.spanId;
|
|
332
550
|
if (entry.error) {
|
|
333
551
|
output.error = {
|
|
334
552
|
name: entry.error.name,
|
|
@@ -350,6 +568,9 @@ var prettyFormatter = (entry) => {
|
|
|
350
568
|
const color = LEVEL_COLORS[entry.level];
|
|
351
569
|
output = `${COLORS.gray}${time}${COLORS.reset} ${color}${level}${COLORS.reset} ${COLORS.cyan}[${module2}]${COLORS.reset} ${message}`;
|
|
352
570
|
}
|
|
571
|
+
if (entry.traceId) {
|
|
572
|
+
output += ` [trace:${entry.traceId.slice(0, 8)}]`;
|
|
573
|
+
}
|
|
353
574
|
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
354
575
|
const dataStr = safeStringify(entry.data);
|
|
355
576
|
if (dataStr.length > 80) {
|
|
@@ -454,198 +675,6 @@ var ConsoleExporter = class {
|
|
|
454
675
|
}
|
|
455
676
|
};
|
|
456
677
|
|
|
457
|
-
// src/telemetry/exporters/otlp.ts
|
|
458
|
-
var StatusCode = {
|
|
459
|
-
UNSET: 0,
|
|
460
|
-
OK: 1,
|
|
461
|
-
ERROR: 2
|
|
462
|
-
};
|
|
463
|
-
function spanToOTLP(span, serviceName, serviceVersion) {
|
|
464
|
-
const attributes = Object.entries(span.attributes).filter(([, v]) => v !== void 0).map(([key, value]) => ({
|
|
465
|
-
key,
|
|
466
|
-
value: typeof value === "string" ? { stringValue: value } : typeof value === "number" ? Number.isInteger(value) ? { intValue: value } : { doubleValue: value } : { boolValue: value }
|
|
467
|
-
}));
|
|
468
|
-
return {
|
|
469
|
-
resourceSpans: [{
|
|
470
|
-
resource: {
|
|
471
|
-
attributes: [
|
|
472
|
-
{ key: "service.name", value: { stringValue: serviceName } },
|
|
473
|
-
{ key: "service.version", value: { stringValue: serviceVersion } },
|
|
474
|
-
{ key: "telemetry.sdk.name", value: { stringValue: "omote-sdk" } },
|
|
475
|
-
{ key: "telemetry.sdk.language", value: { stringValue: "javascript" } }
|
|
476
|
-
]
|
|
477
|
-
},
|
|
478
|
-
scopeSpans: [{
|
|
479
|
-
scope: {
|
|
480
|
-
name: "omote-sdk",
|
|
481
|
-
version: serviceVersion
|
|
482
|
-
},
|
|
483
|
-
spans: [{
|
|
484
|
-
traceId: span.traceId,
|
|
485
|
-
spanId: span.spanId,
|
|
486
|
-
parentSpanId: span.parentSpanId || "",
|
|
487
|
-
name: span.name,
|
|
488
|
-
kind: 1,
|
|
489
|
-
// INTERNAL
|
|
490
|
-
startTimeUnixNano: String(span.startTime * 1e6),
|
|
491
|
-
endTimeUnixNano: String(span.endTime * 1e6),
|
|
492
|
-
attributes,
|
|
493
|
-
status: {
|
|
494
|
-
code: span.status === "ok" ? StatusCode.OK : StatusCode.ERROR,
|
|
495
|
-
message: span.error?.message || ""
|
|
496
|
-
}
|
|
497
|
-
}]
|
|
498
|
-
}]
|
|
499
|
-
}]
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
function metricToOTLP(metric, serviceName, serviceVersion) {
|
|
503
|
-
const attributes = Object.entries(metric.attributes).filter(([, v]) => v !== void 0).map(([key, value]) => ({
|
|
504
|
-
key,
|
|
505
|
-
value: typeof value === "string" ? { stringValue: value } : typeof value === "number" ? Number.isInteger(value) ? { intValue: value } : { doubleValue: value } : { boolValue: value }
|
|
506
|
-
}));
|
|
507
|
-
const dataPoint = {
|
|
508
|
-
attributes,
|
|
509
|
-
timeUnixNano: String(metric.timestamp * 1e6),
|
|
510
|
-
...metric.type === "counter" ? { asInt: metric.value } : { asDouble: metric.value }
|
|
511
|
-
};
|
|
512
|
-
return {
|
|
513
|
-
resourceMetrics: [{
|
|
514
|
-
resource: {
|
|
515
|
-
attributes: [
|
|
516
|
-
{ key: "service.name", value: { stringValue: serviceName } },
|
|
517
|
-
{ key: "service.version", value: { stringValue: serviceVersion } }
|
|
518
|
-
]
|
|
519
|
-
},
|
|
520
|
-
scopeMetrics: [{
|
|
521
|
-
scope: {
|
|
522
|
-
name: "omote-sdk",
|
|
523
|
-
version: serviceVersion
|
|
524
|
-
},
|
|
525
|
-
metrics: [{
|
|
526
|
-
name: metric.name,
|
|
527
|
-
...metric.type === "counter" ? {
|
|
528
|
-
sum: {
|
|
529
|
-
dataPoints: [dataPoint],
|
|
530
|
-
aggregationTemporality: 2,
|
|
531
|
-
// CUMULATIVE
|
|
532
|
-
isMonotonic: true
|
|
533
|
-
}
|
|
534
|
-
} : {
|
|
535
|
-
gauge: {
|
|
536
|
-
dataPoints: [dataPoint]
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}]
|
|
540
|
-
}]
|
|
541
|
-
}]
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
var OTLPExporter = class {
|
|
545
|
-
constructor(config, serviceName = "omote-sdk", serviceVersion = "0.1.0") {
|
|
546
|
-
this.spanBuffer = [];
|
|
547
|
-
this.metricBuffer = [];
|
|
548
|
-
this.flushIntervalId = null;
|
|
549
|
-
this.BUFFER_SIZE = 100;
|
|
550
|
-
this.FLUSH_INTERVAL_MS = 5e3;
|
|
551
|
-
this.isShutdown = false;
|
|
552
|
-
const parsed = new URL(config.endpoint);
|
|
553
|
-
if (parsed.protocol !== "https:") {
|
|
554
|
-
const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
|
|
555
|
-
if (!isLocalhost) {
|
|
556
|
-
throw new Error("OTLP endpoint must use HTTPS (or localhost for development)");
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
this.config = {
|
|
560
|
-
endpoint: config.endpoint,
|
|
561
|
-
timeoutMs: config.timeoutMs ?? 1e4,
|
|
562
|
-
headers: config.headers ? { ...config.headers } : {}
|
|
563
|
-
};
|
|
564
|
-
this.serviceName = serviceName;
|
|
565
|
-
this.serviceVersion = serviceVersion;
|
|
566
|
-
this.flushIntervalId = setInterval(() => {
|
|
567
|
-
this.flush().catch(console.error);
|
|
568
|
-
}, this.FLUSH_INTERVAL_MS);
|
|
569
|
-
}
|
|
570
|
-
exportSpan(span) {
|
|
571
|
-
if (this.isShutdown) return;
|
|
572
|
-
this.spanBuffer.push(span);
|
|
573
|
-
if (this.spanBuffer.length >= this.BUFFER_SIZE) {
|
|
574
|
-
this.flush().catch(console.error);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
exportMetric(metric) {
|
|
578
|
-
if (this.isShutdown) return;
|
|
579
|
-
this.metricBuffer.push(metric);
|
|
580
|
-
if (this.metricBuffer.length >= this.BUFFER_SIZE) {
|
|
581
|
-
this.flush().catch(console.error);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
async flush() {
|
|
585
|
-
if (this.isShutdown) return;
|
|
586
|
-
const spans = this.spanBuffer.splice(0);
|
|
587
|
-
const metrics = this.metricBuffer.splice(0);
|
|
588
|
-
const promises = [];
|
|
589
|
-
if (spans.length > 0) {
|
|
590
|
-
promises.push(this.exportSpans(spans));
|
|
591
|
-
}
|
|
592
|
-
if (metrics.length > 0) {
|
|
593
|
-
promises.push(this.exportMetrics(metrics));
|
|
594
|
-
}
|
|
595
|
-
await Promise.all(promises);
|
|
596
|
-
}
|
|
597
|
-
async shutdown() {
|
|
598
|
-
if (this.flushIntervalId) {
|
|
599
|
-
clearInterval(this.flushIntervalId);
|
|
600
|
-
this.flushIntervalId = null;
|
|
601
|
-
}
|
|
602
|
-
await this.flush();
|
|
603
|
-
this.isShutdown = true;
|
|
604
|
-
}
|
|
605
|
-
async exportSpans(spans) {
|
|
606
|
-
const resourceSpans = spans.map(
|
|
607
|
-
(span) => spanToOTLP(span, this.serviceName, this.serviceVersion).resourceSpans[0]
|
|
608
|
-
);
|
|
609
|
-
const body = { resourceSpans };
|
|
610
|
-
const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/traces";
|
|
611
|
-
await this.sendRequest(endpoint, body);
|
|
612
|
-
}
|
|
613
|
-
async exportMetrics(metrics) {
|
|
614
|
-
const resourceMetrics = metrics.map(
|
|
615
|
-
(metric) => metricToOTLP(metric, this.serviceName, this.serviceVersion).resourceMetrics[0]
|
|
616
|
-
);
|
|
617
|
-
const body = { resourceMetrics };
|
|
618
|
-
const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/metrics";
|
|
619
|
-
await this.sendRequest(endpoint, body);
|
|
620
|
-
}
|
|
621
|
-
async sendRequest(endpoint, body) {
|
|
622
|
-
const controller = new AbortController();
|
|
623
|
-
const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
624
|
-
try {
|
|
625
|
-
const response = await fetch(endpoint, {
|
|
626
|
-
method: "POST",
|
|
627
|
-
headers: {
|
|
628
|
-
"Content-Type": "application/json",
|
|
629
|
-
...this.config.headers
|
|
630
|
-
},
|
|
631
|
-
body: JSON.stringify(body),
|
|
632
|
-
signal: controller.signal
|
|
633
|
-
});
|
|
634
|
-
if (!response.ok) {
|
|
635
|
-
console.warn(`[OTLP] Export failed: ${response.status} ${response.statusText}`);
|
|
636
|
-
}
|
|
637
|
-
} catch (error) {
|
|
638
|
-
if (error.name === "AbortError") {
|
|
639
|
-
console.warn("[OTLP] Export timed out");
|
|
640
|
-
} else {
|
|
641
|
-
console.warn("[OTLP] Export error:", error);
|
|
642
|
-
}
|
|
643
|
-
} finally {
|
|
644
|
-
clearTimeout(timeoutId);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
|
|
649
678
|
// src/logging/Clock.ts
|
|
650
679
|
var defaultClock = {
|
|
651
680
|
now: () => performance.now(),
|
|
@@ -660,6 +689,7 @@ function getClock() {
|
|
|
660
689
|
}
|
|
661
690
|
|
|
662
691
|
// src/telemetry/OmoteTelemetry.ts
|
|
692
|
+
var DEFAULT_HISTOGRAM_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4, 3e4, 6e4];
|
|
663
693
|
function generateId(length = 16) {
|
|
664
694
|
const bytes = new Uint8Array(length);
|
|
665
695
|
crypto.getRandomValues(bytes);
|
|
@@ -679,6 +709,7 @@ function getTelemetry() {
|
|
|
679
709
|
var OmoteTelemetry = class {
|
|
680
710
|
constructor(config) {
|
|
681
711
|
this.exporter = null;
|
|
712
|
+
this.exporterReady = null;
|
|
682
713
|
this.activeTraceId = null;
|
|
683
714
|
this.metricsIntervalId = null;
|
|
684
715
|
// Span stack for log-to-span correlation
|
|
@@ -697,14 +728,18 @@ var OmoteTelemetry = class {
|
|
|
697
728
|
metricsIntervalMs: config.metricsIntervalMs ?? 6e4
|
|
698
729
|
};
|
|
699
730
|
if (this.config.enabled) {
|
|
700
|
-
|
|
701
|
-
|
|
731
|
+
if (config.customExporter) {
|
|
732
|
+
this.exporter = config.customExporter;
|
|
733
|
+
this.startMetricsCollection();
|
|
734
|
+
} else {
|
|
735
|
+
this.exporterReady = this.initExporter();
|
|
736
|
+
}
|
|
702
737
|
}
|
|
703
738
|
}
|
|
704
739
|
/**
|
|
705
740
|
* Initialize the configured exporter
|
|
706
741
|
*/
|
|
707
|
-
initExporter() {
|
|
742
|
+
async initExporter() {
|
|
708
743
|
switch (this.config.exporter) {
|
|
709
744
|
case "console":
|
|
710
745
|
this.exporter = new ConsoleExporter({ enabled: true });
|
|
@@ -714,16 +749,20 @@ var OmoteTelemetry = class {
|
|
|
714
749
|
console.warn("[Telemetry] OTLP exporter requires exporterConfig with endpoint");
|
|
715
750
|
return;
|
|
716
751
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
this.
|
|
720
|
-
|
|
721
|
-
|
|
752
|
+
{
|
|
753
|
+
const { OTLPExporter: OTLPExporter2 } = await Promise.resolve().then(() => (init_otlp(), otlp_exports));
|
|
754
|
+
this.exporter = new OTLPExporter2(
|
|
755
|
+
this.config.exporterConfig,
|
|
756
|
+
this.config.serviceName,
|
|
757
|
+
this.config.serviceVersion
|
|
758
|
+
);
|
|
759
|
+
}
|
|
722
760
|
break;
|
|
723
761
|
case "none":
|
|
724
762
|
default:
|
|
725
763
|
this.exporter = null;
|
|
726
764
|
}
|
|
765
|
+
this.startMetricsCollection();
|
|
727
766
|
}
|
|
728
767
|
/**
|
|
729
768
|
* Start periodic metrics collection
|
|
@@ -768,6 +807,7 @@ var OmoteTelemetry = class {
|
|
|
768
807
|
const spanId = generateId(8);
|
|
769
808
|
const parentSpanId = parentContext?.spanId;
|
|
770
809
|
const startTime = getClock().now();
|
|
810
|
+
const epochMs = Date.now();
|
|
771
811
|
if (!parentContext && !this.activeTraceId) {
|
|
772
812
|
this.activeTraceId = traceId;
|
|
773
813
|
}
|
|
@@ -783,6 +823,7 @@ var OmoteTelemetry = class {
|
|
|
783
823
|
if (idx !== -1) this.spanStack.splice(idx, 1);
|
|
784
824
|
const endTime = getClock().now();
|
|
785
825
|
const durationMs = endTime - startTime;
|
|
826
|
+
const endEpochMs = epochMs + (endTime - startTime);
|
|
786
827
|
if (status === "error" && !sampled) {
|
|
787
828
|
sampled = this.shouldSample(true);
|
|
788
829
|
}
|
|
@@ -795,6 +836,8 @@ var OmoteTelemetry = class {
|
|
|
795
836
|
startTime,
|
|
796
837
|
endTime,
|
|
797
838
|
durationMs,
|
|
839
|
+
epochMs,
|
|
840
|
+
endEpochMs,
|
|
798
841
|
status,
|
|
799
842
|
attributes: spanAttributes,
|
|
800
843
|
error
|
|
@@ -869,7 +912,7 @@ var OmoteTelemetry = class {
|
|
|
869
912
|
* });
|
|
870
913
|
* ```
|
|
871
914
|
*/
|
|
872
|
-
recordHistogram(name, value, attributes = {}) {
|
|
915
|
+
recordHistogram(name, value, attributes = {}, bucketBoundaries = DEFAULT_HISTOGRAM_BUCKETS) {
|
|
873
916
|
if (!this.config.enabled || !this.config.metricsEnabled) return;
|
|
874
917
|
const key = this.getMetricKey(name, attributes);
|
|
875
918
|
const existing = this.histograms.get(key);
|
|
@@ -878,8 +921,27 @@ var OmoteTelemetry = class {
|
|
|
878
921
|
existing.sum += value;
|
|
879
922
|
if (value < existing.min) existing.min = value;
|
|
880
923
|
if (value > existing.max) existing.max = value;
|
|
924
|
+
let placed = false;
|
|
925
|
+
for (let i = 0; i < existing.bucketBoundaries.length; i++) {
|
|
926
|
+
if (value <= existing.bucketBoundaries[i]) {
|
|
927
|
+
existing.bucketCounts[i]++;
|
|
928
|
+
placed = true;
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (!placed) existing.bucketCounts[existing.bucketCounts.length - 1]++;
|
|
881
933
|
} else {
|
|
882
|
-
|
|
934
|
+
const bucketCounts = new Array(bucketBoundaries.length + 1).fill(0);
|
|
935
|
+
let placed = false;
|
|
936
|
+
for (let i = 0; i < bucketBoundaries.length; i++) {
|
|
937
|
+
if (value <= bucketBoundaries[i]) {
|
|
938
|
+
bucketCounts[i]++;
|
|
939
|
+
placed = true;
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (!placed) bucketCounts[bucketCounts.length - 1]++;
|
|
944
|
+
this.histograms.set(key, { count: 1, sum: value, min: value, max: value, bucketBoundaries, bucketCounts, attributes });
|
|
883
945
|
}
|
|
884
946
|
}
|
|
885
947
|
/**
|
|
@@ -896,7 +958,7 @@ var OmoteTelemetry = class {
|
|
|
896
958
|
*/
|
|
897
959
|
flushMetrics() {
|
|
898
960
|
if (!this.exporter) return;
|
|
899
|
-
const timestamp =
|
|
961
|
+
const timestamp = Date.now();
|
|
900
962
|
for (const [key, data] of this.counters) {
|
|
901
963
|
if (data.value === 0) continue;
|
|
902
964
|
const name = key.split("|")[0];
|
|
@@ -925,19 +987,29 @@ var OmoteTelemetry = class {
|
|
|
925
987
|
min: data.min,
|
|
926
988
|
max: data.max
|
|
927
989
|
},
|
|
928
|
-
timestamp
|
|
990
|
+
timestamp,
|
|
991
|
+
histogramData: {
|
|
992
|
+
count: data.count,
|
|
993
|
+
sum: data.sum,
|
|
994
|
+
min: data.min,
|
|
995
|
+
max: data.max,
|
|
996
|
+
bucketBoundaries: [...data.bucketBoundaries],
|
|
997
|
+
bucketCounts: [...data.bucketCounts]
|
|
998
|
+
}
|
|
929
999
|
};
|
|
930
1000
|
this.exporter.exportMetric(metric);
|
|
931
1001
|
data.count = 0;
|
|
932
1002
|
data.sum = 0;
|
|
933
1003
|
data.min = Infinity;
|
|
934
1004
|
data.max = -Infinity;
|
|
1005
|
+
data.bucketCounts.fill(0);
|
|
935
1006
|
}
|
|
936
1007
|
}
|
|
937
1008
|
/**
|
|
938
1009
|
* Force flush all pending data
|
|
939
1010
|
*/
|
|
940
1011
|
async flush() {
|
|
1012
|
+
if (this.exporterReady) await this.exporterReady;
|
|
941
1013
|
this.flushMetrics();
|
|
942
1014
|
await this.exporter?.flush();
|
|
943
1015
|
}
|
|
@@ -1059,12 +1131,12 @@ var Logger = class _Logger {
|
|
|
1059
1131
|
};
|
|
1060
1132
|
var loggerCache = /* @__PURE__ */ new Map();
|
|
1061
1133
|
function createLogger(module2) {
|
|
1062
|
-
let
|
|
1063
|
-
if (!
|
|
1064
|
-
|
|
1065
|
-
loggerCache.set(module2,
|
|
1134
|
+
let logger37 = loggerCache.get(module2);
|
|
1135
|
+
if (!logger37) {
|
|
1136
|
+
logger37 = new Logger(module2);
|
|
1137
|
+
loggerCache.set(module2, logger37);
|
|
1066
1138
|
}
|
|
1067
|
-
return
|
|
1139
|
+
return logger37;
|
|
1068
1140
|
}
|
|
1069
1141
|
var noopLogger = {
|
|
1070
1142
|
module: "noop",
|
|
@@ -1140,6 +1212,67 @@ var ErrorCodes = {
|
|
|
1140
1212
|
NET_WEBSOCKET_ERROR: "OMOTE_NET_003"
|
|
1141
1213
|
};
|
|
1142
1214
|
|
|
1215
|
+
// src/telemetry/types.ts
|
|
1216
|
+
var MetricNames = {
|
|
1217
|
+
// --- Inference ---
|
|
1218
|
+
/** Histogram: Inference latency in ms */
|
|
1219
|
+
INFERENCE_LATENCY: "omote.inference.latency",
|
|
1220
|
+
/** Histogram: Model load time in ms */
|
|
1221
|
+
MODEL_LOAD_TIME: "omote.model.load_time",
|
|
1222
|
+
/** Counter: Total inference operations */
|
|
1223
|
+
INFERENCE_TOTAL: "omote.inference.total",
|
|
1224
|
+
/** Counter: Total errors */
|
|
1225
|
+
ERRORS_TOTAL: "omote.errors.total",
|
|
1226
|
+
/** Counter: Cache hits */
|
|
1227
|
+
CACHE_HITS: "omote.cache.hits",
|
|
1228
|
+
/** Counter: Cache misses */
|
|
1229
|
+
CACHE_MISSES: "omote.cache.misses",
|
|
1230
|
+
/** Counter: Cache stale (version/etag mismatch) */
|
|
1231
|
+
CACHE_STALE: "omote.cache.stale",
|
|
1232
|
+
/** Counter: Cache quota warning (>90% used) */
|
|
1233
|
+
CACHE_QUOTA_WARNING: "omote.cache.quota_warning",
|
|
1234
|
+
/** Counter: Cache eviction (LRU) */
|
|
1235
|
+
CACHE_EVICTION: "omote.cache.eviction",
|
|
1236
|
+
// --- Pipeline ---
|
|
1237
|
+
/** Histogram: Voice turn latency (speech end → transcript ready, excludes playback) */
|
|
1238
|
+
VOICE_TURN_LATENCY: "omote.voice.turn.latency",
|
|
1239
|
+
/** Histogram: ASR transcription latency in ms */
|
|
1240
|
+
VOICE_TRANSCRIPTION_LATENCY: "omote.voice.transcription.latency",
|
|
1241
|
+
/** Histogram: Response handler latency in ms */
|
|
1242
|
+
VOICE_RESPONSE_LATENCY: "omote.voice.response.latency",
|
|
1243
|
+
/** Counter: Total transcriptions */
|
|
1244
|
+
VOICE_TRANSCRIPTIONS: "omote.voice.transcriptions",
|
|
1245
|
+
/** Counter: Total interruptions */
|
|
1246
|
+
VOICE_INTERRUPTIONS: "omote.voice.interruptions",
|
|
1247
|
+
// --- Playback ---
|
|
1248
|
+
/** Histogram: PlaybackPipeline session duration in ms */
|
|
1249
|
+
PLAYBACK_SESSION_DURATION: "omote.playback.session.duration",
|
|
1250
|
+
/** Histogram: Audio chunk processing latency in ms */
|
|
1251
|
+
PLAYBACK_CHUNK_LATENCY: "omote.playback.chunk.latency",
|
|
1252
|
+
// --- TTS ---
|
|
1253
|
+
/** Histogram: TTSSpeaker.connect() latency in ms */
|
|
1254
|
+
TTS_CONNECT_LATENCY: "omote.tts.connect.latency",
|
|
1255
|
+
/** Histogram: TTSSpeaker.speak() latency in ms */
|
|
1256
|
+
TTS_SPEAK_LATENCY: "omote.tts.speak.latency",
|
|
1257
|
+
/** Counter: TTSSpeaker.stop() aborted speak calls */
|
|
1258
|
+
TTS_SPEAK_ABORTED: "omote.tts.speak.aborted",
|
|
1259
|
+
// --- Mic ---
|
|
1260
|
+
/** Counter: MicLipSync sessions started */
|
|
1261
|
+
MIC_SESSIONS: "omote.mic.sessions",
|
|
1262
|
+
// --- Frame budget ---
|
|
1263
|
+
/** Histogram: CharacterController.update() latency in µs */
|
|
1264
|
+
AVATAR_FRAME_LATENCY: "omote.avatar.frame.latency_us",
|
|
1265
|
+
/** Histogram: FaceCompositor.compose() latency in µs */
|
|
1266
|
+
COMPOSITOR_COMPOSE_LATENCY: "omote.compositor.compose.latency_us",
|
|
1267
|
+
/** Counter: Frames exceeding budget threshold */
|
|
1268
|
+
AVATAR_FRAME_DROPS: "omote.avatar.frame.drops",
|
|
1269
|
+
// --- Audio scheduling ---
|
|
1270
|
+
/** Counter: Audio scheduling gaps (playback fell behind) */
|
|
1271
|
+
AUDIO_SCHEDULE_GAP: "omote.audio.schedule_gap"
|
|
1272
|
+
};
|
|
1273
|
+
var INFERENCE_LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3];
|
|
1274
|
+
var MODEL_LOAD_TIME_BUCKETS = [100, 500, 1e3, 2500, 5e3, 1e4, 3e4, 6e4];
|
|
1275
|
+
|
|
1143
1276
|
// src/audio/MicrophoneCapture.ts
|
|
1144
1277
|
var logger = createLogger("MicrophoneCapture");
|
|
1145
1278
|
var MicrophoneCapture = class {
|
|
@@ -1176,6 +1309,7 @@ var MicrophoneCapture = class {
|
|
|
1176
1309
|
return;
|
|
1177
1310
|
}
|
|
1178
1311
|
if (this._isRecording) return;
|
|
1312
|
+
const span = getTelemetry()?.startSpan("MicrophoneCapture.start");
|
|
1179
1313
|
try {
|
|
1180
1314
|
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
1181
1315
|
audio: {
|
|
@@ -1249,6 +1383,8 @@ var MicrophoneCapture = class {
|
|
|
1249
1383
|
source.connect(this.processor);
|
|
1250
1384
|
this.processor.connect(this.context.destination);
|
|
1251
1385
|
this._isRecording = true;
|
|
1386
|
+
getTelemetry()?.incrementCounter(MetricNames.MIC_SESSIONS);
|
|
1387
|
+
span?.end();
|
|
1252
1388
|
logger.info("Started recording", {
|
|
1253
1389
|
contextState: this.context.state,
|
|
1254
1390
|
sampleRate: this.config.sampleRate,
|
|
@@ -1269,6 +1405,7 @@ var MicrophoneCapture = class {
|
|
|
1269
1405
|
message: err.message,
|
|
1270
1406
|
details: err
|
|
1271
1407
|
});
|
|
1408
|
+
span?.endWithError(err instanceof Error ? err : new Error(String(err)));
|
|
1272
1409
|
}
|
|
1273
1410
|
}
|
|
1274
1411
|
stop() {
|
|
@@ -1470,6 +1607,7 @@ var AudioScheduler = class {
|
|
|
1470
1607
|
if (scheduleTime < ctx.currentTime) {
|
|
1471
1608
|
const gap = ctx.currentTime - scheduleTime;
|
|
1472
1609
|
const gapMs = gap * 1e3;
|
|
1610
|
+
getTelemetry()?.incrementCounter(MetricNames.AUDIO_SCHEDULE_GAP, 1, { gap_ms: Math.round(gapMs) });
|
|
1473
1611
|
if (gap > 0.5) {
|
|
1474
1612
|
logger2.error("Critical audio scheduling gap", {
|
|
1475
1613
|
code: ErrorCodes.AUD_SCHEDULE_GAP,
|
|
@@ -2047,6 +2185,8 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
2047
2185
|
const t0 = getClock().now();
|
|
2048
2186
|
const result = await this.backend.infer(chunk, this.identityIndex);
|
|
2049
2187
|
const inferMs = Math.round(getClock().now() - t0);
|
|
2188
|
+
getTelemetry()?.recordHistogram(MetricNames.INFERENCE_LATENCY, inferMs);
|
|
2189
|
+
getTelemetry()?.incrementCounter(MetricNames.INFERENCE_TOTAL);
|
|
2050
2190
|
const effectiveSamples = actualSamples ?? chunk.length;
|
|
2051
2191
|
const actualDuration = effectiveSamples / this.sampleRate;
|
|
2052
2192
|
const actualFrameCount = Math.ceil(actualDuration * FRAME_RATE);
|
|
@@ -2093,79 +2233,13 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
2093
2233
|
error: error.message,
|
|
2094
2234
|
code
|
|
2095
2235
|
});
|
|
2236
|
+
getTelemetry()?.incrementCounter(MetricNames.ERRORS_TOTAL, 1, { source: "A2EProcessor", code });
|
|
2096
2237
|
this.onError?.(error);
|
|
2097
2238
|
}
|
|
2098
2239
|
};
|
|
2099
2240
|
_A2EProcessor.MAX_PENDING_CHUNKS = 10;
|
|
2100
2241
|
var A2EProcessor = _A2EProcessor;
|
|
2101
2242
|
|
|
2102
|
-
// src/telemetry/types.ts
|
|
2103
|
-
var MetricNames = {
|
|
2104
|
-
// --- Inference ---
|
|
2105
|
-
/** Histogram: Inference latency in ms */
|
|
2106
|
-
INFERENCE_LATENCY: "omote.inference.latency",
|
|
2107
|
-
/** Histogram: Model load time in ms */
|
|
2108
|
-
MODEL_LOAD_TIME: "omote.model.load_time",
|
|
2109
|
-
/** Counter: Total inference operations */
|
|
2110
|
-
INFERENCE_TOTAL: "omote.inference.total",
|
|
2111
|
-
/** Counter: Total errors */
|
|
2112
|
-
ERRORS_TOTAL: "omote.errors.total",
|
|
2113
|
-
/** Counter: Cache hits */
|
|
2114
|
-
CACHE_HITS: "omote.cache.hits",
|
|
2115
|
-
/** Counter: Cache misses */
|
|
2116
|
-
CACHE_MISSES: "omote.cache.misses",
|
|
2117
|
-
/** Counter: Cache stale (version/etag mismatch) */
|
|
2118
|
-
CACHE_STALE: "omote.cache.stale",
|
|
2119
|
-
/** Counter: Cache quota warning (>90% used) */
|
|
2120
|
-
CACHE_QUOTA_WARNING: "omote.cache.quota_warning",
|
|
2121
|
-
/** Counter: Cache eviction (LRU) */
|
|
2122
|
-
CACHE_EVICTION: "omote.cache.eviction",
|
|
2123
|
-
// --- Pipeline ---
|
|
2124
|
-
/** Histogram: Voice turn latency (speech end → transcript ready, excludes playback) */
|
|
2125
|
-
VOICE_TURN_LATENCY: "omote.voice.turn.latency",
|
|
2126
|
-
/** Histogram: ASR transcription latency in ms */
|
|
2127
|
-
VOICE_TRANSCRIPTION_LATENCY: "omote.voice.transcription.latency",
|
|
2128
|
-
/** Histogram: Response handler latency in ms */
|
|
2129
|
-
VOICE_RESPONSE_LATENCY: "omote.voice.response.latency",
|
|
2130
|
-
/** Counter: Total transcriptions */
|
|
2131
|
-
VOICE_TRANSCRIPTIONS: "omote.voice.transcriptions",
|
|
2132
|
-
/** Counter: Total interruptions */
|
|
2133
|
-
VOICE_INTERRUPTIONS: "omote.voice.interruptions",
|
|
2134
|
-
// --- Playback ---
|
|
2135
|
-
/** Histogram: PlaybackPipeline session duration in ms */
|
|
2136
|
-
PLAYBACK_SESSION_DURATION: "omote.playback.session.duration",
|
|
2137
|
-
/** Histogram: Audio chunk processing latency in ms */
|
|
2138
|
-
PLAYBACK_CHUNK_LATENCY: "omote.playback.chunk.latency",
|
|
2139
|
-
// --- TTS ---
|
|
2140
|
-
/** Histogram: TTSSpeaker.connect() latency in ms */
|
|
2141
|
-
TTS_CONNECT_LATENCY: "omote.tts.connect.latency",
|
|
2142
|
-
/** Histogram: TTSSpeaker.speak() latency in ms */
|
|
2143
|
-
TTS_SPEAK_LATENCY: "omote.tts.speak.latency",
|
|
2144
|
-
/** Counter: TTSSpeaker.stop() aborted speak calls */
|
|
2145
|
-
TTS_SPEAK_ABORTED: "omote.tts.speak.aborted",
|
|
2146
|
-
// --- Mic ---
|
|
2147
|
-
/** Counter: MicLipSync sessions started */
|
|
2148
|
-
MIC_SESSIONS: "omote.mic.sessions",
|
|
2149
|
-
// --- Frame budget ---
|
|
2150
|
-
/** Histogram: CharacterController.update() latency in µs */
|
|
2151
|
-
AVATAR_FRAME_LATENCY: "omote.avatar.frame.latency_us",
|
|
2152
|
-
/** Histogram: FaceCompositor.compose() latency in µs */
|
|
2153
|
-
COMPOSITOR_COMPOSE_LATENCY: "omote.compositor.compose.latency_us",
|
|
2154
|
-
/** Counter: Frames exceeding budget threshold */
|
|
2155
|
-
AVATAR_FRAME_DROPS: "omote.avatar.frame.drops"
|
|
2156
|
-
};
|
|
2157
|
-
var ErrorTypes = {
|
|
2158
|
-
INFERENCE: "inference_error",
|
|
2159
|
-
NETWORK: "network_error",
|
|
2160
|
-
TIMEOUT: "timeout",
|
|
2161
|
-
USER: "user_error",
|
|
2162
|
-
RUNTIME: "runtime_error",
|
|
2163
|
-
MEDIA: "media_error",
|
|
2164
|
-
MODEL: "model_error"
|
|
2165
|
-
};
|
|
2166
|
-
var INFERENCE_LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3];
|
|
2167
|
-
var MODEL_LOAD_TIME_BUCKETS = [100, 500, 1e3, 2500, 5e3, 1e4, 3e4, 6e4];
|
|
2168
|
-
|
|
2169
2243
|
// src/inference/blendshapeUtils.ts
|
|
2170
2244
|
var ARKIT_BLENDSHAPES = [
|
|
2171
2245
|
"browDownLeft",
|
|
@@ -2901,6 +2975,9 @@ function resetModelUrls() {
|
|
|
2901
2975
|
}
|
|
2902
2976
|
var HF_CDN_URLS = HF_MODEL_URLS;
|
|
2903
2977
|
|
|
2978
|
+
// src/telemetry/index.ts
|
|
2979
|
+
init_otlp();
|
|
2980
|
+
|
|
2904
2981
|
// src/utils/runtime.ts
|
|
2905
2982
|
var logger8 = createLogger("Runtime");
|
|
2906
2983
|
function isIOSSafari() {
|
|
@@ -4365,7 +4442,7 @@ var SenseVoiceUnifiedAdapter = class {
|
|
|
4365
4442
|
});
|
|
4366
4443
|
span?.setAttributes({ "model.backend": "wasm", "model.load_time_ms": result.loadTimeMs });
|
|
4367
4444
|
span?.end();
|
|
4368
|
-
telemetry?.recordHistogram(
|
|
4445
|
+
telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, result.loadTimeMs, {
|
|
4369
4446
|
model: "sensevoice-unified",
|
|
4370
4447
|
backend: "wasm"
|
|
4371
4448
|
});
|
|
@@ -4389,11 +4466,11 @@ var SenseVoiceUnifiedAdapter = class {
|
|
|
4389
4466
|
try {
|
|
4390
4467
|
const result = await this.worker.transcribe(audio);
|
|
4391
4468
|
const latencyMs = getClock().now() - startTime;
|
|
4392
|
-
telemetry?.recordHistogram(
|
|
4469
|
+
telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latencyMs, {
|
|
4393
4470
|
model: "sensevoice-unified",
|
|
4394
4471
|
backend: "wasm"
|
|
4395
4472
|
});
|
|
4396
|
-
telemetry?.incrementCounter(
|
|
4473
|
+
telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {
|
|
4397
4474
|
model: "sensevoice-unified",
|
|
4398
4475
|
backend: "wasm",
|
|
4399
4476
|
status: "success"
|
|
@@ -4402,7 +4479,7 @@ var SenseVoiceUnifiedAdapter = class {
|
|
|
4402
4479
|
span?.end();
|
|
4403
4480
|
resolve(result);
|
|
4404
4481
|
} catch (err) {
|
|
4405
|
-
telemetry?.incrementCounter(
|
|
4482
|
+
telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {
|
|
4406
4483
|
model: "sensevoice-unified",
|
|
4407
4484
|
backend: "wasm",
|
|
4408
4485
|
status: "error"
|
|
@@ -4470,7 +4547,7 @@ var A2EUnifiedAdapter = class {
|
|
|
4470
4547
|
});
|
|
4471
4548
|
span?.setAttributes({ "model.backend": result.backend, "model.load_time_ms": result.loadTimeMs });
|
|
4472
4549
|
span?.end();
|
|
4473
|
-
telemetry?.recordHistogram(
|
|
4550
|
+
telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, result.loadTimeMs, {
|
|
4474
4551
|
model: "a2e-unified",
|
|
4475
4552
|
backend: result.backend
|
|
4476
4553
|
});
|
|
@@ -5839,14 +5916,14 @@ var KokoroTTSUnifiedAdapter = class {
|
|
|
5839
5916
|
});
|
|
5840
5917
|
span?.setAttributes({ "model.backend": this._backend, "model.load_time_ms": loadTimeMs });
|
|
5841
5918
|
span?.end();
|
|
5842
|
-
telemetry?.recordHistogram(
|
|
5919
|
+
telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, loadTimeMs, {
|
|
5843
5920
|
model: "kokoro-tts-unified",
|
|
5844
5921
|
backend: this._backend
|
|
5845
5922
|
});
|
|
5846
5923
|
return { backend: this._backend, loadTimeMs, defaultVoice: this.config.defaultVoice };
|
|
5847
5924
|
} catch (error) {
|
|
5848
5925
|
span?.endWithError(error instanceof Error ? error : new Error(String(error)));
|
|
5849
|
-
getTelemetry()?.incrementCounter(
|
|
5926
|
+
getTelemetry()?.incrementCounter(MetricNames.ERRORS_TOTAL, 1, {
|
|
5850
5927
|
model: "kokoro-tts-unified",
|
|
5851
5928
|
error_type: "load_failed"
|
|
5852
5929
|
});
|
|
@@ -5911,22 +5988,27 @@ var KokoroTTSUnifiedAdapter = class {
|
|
|
5911
5988
|
try {
|
|
5912
5989
|
const result = await this.worker.inferKokoro(tokens, style, speed);
|
|
5913
5990
|
const latencyMs = getClock().now() - startTime;
|
|
5914
|
-
telemetry?.recordHistogram(
|
|
5991
|
+
telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latencyMs, {
|
|
5915
5992
|
model: "kokoro-tts-unified",
|
|
5916
5993
|
backend: this._backend
|
|
5917
5994
|
});
|
|
5918
|
-
telemetry?.incrementCounter(
|
|
5995
|
+
telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {
|
|
5919
5996
|
model: "kokoro-tts-unified",
|
|
5920
5997
|
backend: this._backend,
|
|
5921
5998
|
status: "success"
|
|
5922
5999
|
});
|
|
5923
6000
|
resolve(result.audio);
|
|
5924
6001
|
} catch (err) {
|
|
5925
|
-
telemetry?.incrementCounter(
|
|
6002
|
+
telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {
|
|
5926
6003
|
model: "kokoro-tts-unified",
|
|
5927
6004
|
backend: this._backend,
|
|
5928
6005
|
status: "error"
|
|
5929
6006
|
});
|
|
6007
|
+
const span = telemetry?.startSpan("KokoroTTSUnifiedAdapter.inferError", {
|
|
6008
|
+
"model.name": "kokoro-tts-unified",
|
|
6009
|
+
"model.backend": this._backend
|
|
6010
|
+
});
|
|
6011
|
+
span?.endWithError(err instanceof Error ? err : new Error(String(err)));
|
|
5930
6012
|
reject(err);
|
|
5931
6013
|
}
|
|
5932
6014
|
});
|
|
@@ -6004,7 +6086,7 @@ var SileroVADUnifiedAdapter = class {
|
|
|
6004
6086
|
});
|
|
6005
6087
|
span?.setAttributes({ "model.backend": "wasm", "model.load_time_ms": result.loadTimeMs });
|
|
6006
6088
|
span?.end();
|
|
6007
|
-
telemetry?.recordHistogram(
|
|
6089
|
+
telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, result.loadTimeMs, {
|
|
6008
6090
|
model: "silero-vad-unified",
|
|
6009
6091
|
backend: "wasm"
|
|
6010
6092
|
});
|
|
@@ -6615,6 +6697,7 @@ function createKokoroTTS(config = {}) {
|
|
|
6615
6697
|
}
|
|
6616
6698
|
|
|
6617
6699
|
// src/audio/createTTSPlayer.ts
|
|
6700
|
+
var logger22 = createLogger("TTSPlayer");
|
|
6618
6701
|
function createTTSPlayer(config) {
|
|
6619
6702
|
return new TTSPlayer(config);
|
|
6620
6703
|
}
|
|
@@ -6628,19 +6711,27 @@ var TTSPlayer = class extends TTSSpeaker {
|
|
|
6628
6711
|
}
|
|
6629
6712
|
/** Load TTS model and connect in audio-only mode. */
|
|
6630
6713
|
async load() {
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
worker =
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6714
|
+
const span = getTelemetry()?.startSpan("TTSPlayer.load");
|
|
6715
|
+
try {
|
|
6716
|
+
let worker = this.ttsConfig.unifiedWorker;
|
|
6717
|
+
if (!worker) {
|
|
6718
|
+
worker = await acquireSharedWorker();
|
|
6719
|
+
this.ttsPlayerUsesSharedWorker = true;
|
|
6720
|
+
}
|
|
6721
|
+
this.backend = createKokoroTTS({
|
|
6722
|
+
defaultVoice: this.ttsConfig.voice,
|
|
6723
|
+
modelUrl: this.ttsConfig.modelUrl,
|
|
6724
|
+
voiceBaseUrl: this.ttsConfig.voiceBaseUrl,
|
|
6725
|
+
unifiedWorker: worker
|
|
6726
|
+
});
|
|
6727
|
+
await this.backend.load();
|
|
6728
|
+
await this.connect(this.backend, { audioOnly: true });
|
|
6729
|
+
logger22.info("TTSPlayer loaded");
|
|
6730
|
+
span?.end();
|
|
6731
|
+
} catch (err) {
|
|
6732
|
+
span?.endWithError(err instanceof Error ? err : new Error(String(err)));
|
|
6733
|
+
throw err;
|
|
6734
|
+
}
|
|
6644
6735
|
}
|
|
6645
6736
|
/** Whether the TTS model is loaded and ready. */
|
|
6646
6737
|
get isLoaded() {
|
|
@@ -6659,7 +6750,7 @@ var TTSPlayer = class extends TTSSpeaker {
|
|
|
6659
6750
|
};
|
|
6660
6751
|
|
|
6661
6752
|
// src/inference/createSenseVoice.ts
|
|
6662
|
-
var
|
|
6753
|
+
var logger23 = createLogger("createSenseVoice");
|
|
6663
6754
|
var LazySenseVoice = class {
|
|
6664
6755
|
constructor(config) {
|
|
6665
6756
|
this.inner = null;
|
|
@@ -6707,7 +6798,7 @@ var LazySenseVoice = class {
|
|
|
6707
6798
|
function createSenseVoice(config = {}) {
|
|
6708
6799
|
const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.senseVoice;
|
|
6709
6800
|
if (config.unifiedWorker) {
|
|
6710
|
-
|
|
6801
|
+
logger23.info("Creating SenseVoiceUnifiedAdapter (shared unified worker)");
|
|
6711
6802
|
return new SenseVoiceUnifiedAdapter(config.unifiedWorker, {
|
|
6712
6803
|
modelUrl,
|
|
6713
6804
|
tokensUrl: config.tokensUrl,
|
|
@@ -6715,12 +6806,12 @@ function createSenseVoice(config = {}) {
|
|
|
6715
6806
|
textNorm: config.textNorm
|
|
6716
6807
|
});
|
|
6717
6808
|
}
|
|
6718
|
-
|
|
6809
|
+
logger23.info("Creating SenseVoiceUnifiedAdapter (dedicated worker, lazy init)");
|
|
6719
6810
|
return new LazySenseVoice(config);
|
|
6720
6811
|
}
|
|
6721
6812
|
|
|
6722
6813
|
// src/inference/createSileroVAD.ts
|
|
6723
|
-
var
|
|
6814
|
+
var logger24 = createLogger("createSileroVAD");
|
|
6724
6815
|
var LazySileroVAD = class {
|
|
6725
6816
|
constructor(config) {
|
|
6726
6817
|
this.inner = null;
|
|
@@ -6781,15 +6872,15 @@ function createSileroVAD(config = {}) {
|
|
|
6781
6872
|
const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.sileroVad;
|
|
6782
6873
|
const resolvedConfig = { ...config, modelUrl };
|
|
6783
6874
|
if (config.unifiedWorker) {
|
|
6784
|
-
|
|
6875
|
+
logger24.info("Creating SileroVADUnifiedAdapter (shared unified worker)");
|
|
6785
6876
|
return new SileroVADUnifiedAdapter(config.unifiedWorker, resolvedConfig);
|
|
6786
6877
|
}
|
|
6787
|
-
|
|
6878
|
+
logger24.info("Creating SileroVADUnifiedAdapter (dedicated worker, lazy init)");
|
|
6788
6879
|
return new LazySileroVAD(config);
|
|
6789
6880
|
}
|
|
6790
6881
|
|
|
6791
6882
|
// src/audio/SpeechListener.ts
|
|
6792
|
-
var
|
|
6883
|
+
var logger25 = createLogger("SpeechListener");
|
|
6793
6884
|
var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
6794
6885
|
constructor(config) {
|
|
6795
6886
|
super();
|
|
@@ -6913,11 +7004,11 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
6913
7004
|
}
|
|
6914
7005
|
span?.end();
|
|
6915
7006
|
this.setState("ready");
|
|
6916
|
-
|
|
7007
|
+
logger25.info("SpeechListener models loaded");
|
|
6917
7008
|
} catch (error) {
|
|
6918
7009
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
6919
7010
|
span?.endWithError(err);
|
|
6920
|
-
|
|
7011
|
+
logger25.error("Model loading failed", { message: err.message });
|
|
6921
7012
|
this.emit("error", err);
|
|
6922
7013
|
this.setState("idle");
|
|
6923
7014
|
throw err;
|
|
@@ -6950,7 +7041,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
6950
7041
|
});
|
|
6951
7042
|
await this.mic.start();
|
|
6952
7043
|
this.setState("listening");
|
|
6953
|
-
|
|
7044
|
+
logger25.info("Listening started");
|
|
6954
7045
|
}
|
|
6955
7046
|
/** Stop listening — deactivates mic, clears buffers. */
|
|
6956
7047
|
stop() {
|
|
@@ -6970,7 +7061,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
6970
7061
|
if (this._state !== "idle") {
|
|
6971
7062
|
this.setState("ready");
|
|
6972
7063
|
}
|
|
6973
|
-
|
|
7064
|
+
logger25.info("Listening stopped");
|
|
6974
7065
|
}
|
|
6975
7066
|
/** Pause VAD/ASR but keep mic active for audio:chunk events (for interruption detection). */
|
|
6976
7067
|
pause() {
|
|
@@ -6991,7 +7082,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
6991
7082
|
}
|
|
6992
7083
|
/** Dispose all resources. */
|
|
6993
7084
|
async dispose() {
|
|
6994
|
-
|
|
7085
|
+
logger25.debug("Disposing SpeechListener");
|
|
6995
7086
|
this.stop();
|
|
6996
7087
|
this.epoch++;
|
|
6997
7088
|
await Promise.allSettled([
|
|
@@ -7026,14 +7117,14 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7026
7117
|
this.audioBufferSamples = 0;
|
|
7027
7118
|
this.lastProgressiveResult = null;
|
|
7028
7119
|
this.lastProgressiveSamples = 0;
|
|
7029
|
-
|
|
7120
|
+
logger25.debug("Speech start");
|
|
7030
7121
|
this.emit("speech:start");
|
|
7031
7122
|
this.startProgressiveTranscription();
|
|
7032
7123
|
}
|
|
7033
7124
|
this.audioBuffer.push(new Float32Array(samples));
|
|
7034
7125
|
this.audioBufferSamples += samples.length;
|
|
7035
7126
|
if (this.audioBufferSamples >= _SpeechListener.MAX_AUDIO_BUFFER_SAMPLES) {
|
|
7036
|
-
|
|
7127
|
+
logger25.warn("Audio buffer exceeded max, forcing transcription flush");
|
|
7037
7128
|
this.onSilenceDetected();
|
|
7038
7129
|
return;
|
|
7039
7130
|
}
|
|
@@ -7049,7 +7140,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7049
7140
|
}
|
|
7050
7141
|
}
|
|
7051
7142
|
} catch (err) {
|
|
7052
|
-
|
|
7143
|
+
logger25.warn("VAD error", { error: String(err) });
|
|
7053
7144
|
}
|
|
7054
7145
|
}
|
|
7055
7146
|
// ---------------------------------------------------------------------------
|
|
@@ -7067,11 +7158,11 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7067
7158
|
const capturedEpoch = this.epoch;
|
|
7068
7159
|
this.isSpeechActive = false;
|
|
7069
7160
|
const durationMs = getClock().now() - this.speechStartTime;
|
|
7070
|
-
|
|
7161
|
+
logger25.debug("Speech end", { durationMs: Math.round(durationMs) });
|
|
7071
7162
|
this.emit("speech:end", { durationMs });
|
|
7072
7163
|
this.clearSilenceTimer();
|
|
7073
7164
|
this.processEndOfSpeech(capturedEpoch).catch((err) => {
|
|
7074
|
-
|
|
7165
|
+
logger25.error("End of speech processing failed", { error: String(err) });
|
|
7075
7166
|
if (this.epoch === capturedEpoch) {
|
|
7076
7167
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
7077
7168
|
this.setState("listening");
|
|
@@ -7103,7 +7194,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7103
7194
|
const minEnergy = this.config.minAudioEnergy ?? 0.02;
|
|
7104
7195
|
const durationSec = totalSamples / 16e3;
|
|
7105
7196
|
if (durationSec < minDuration) {
|
|
7106
|
-
|
|
7197
|
+
logger25.info("Audio too short, discarding", { durationSec });
|
|
7107
7198
|
this.setState("listening");
|
|
7108
7199
|
return;
|
|
7109
7200
|
}
|
|
@@ -7113,7 +7204,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7113
7204
|
}
|
|
7114
7205
|
rms = Math.sqrt(rms / fullAudio.length);
|
|
7115
7206
|
if (rms < minEnergy) {
|
|
7116
|
-
|
|
7207
|
+
logger25.info("Audio too quiet, discarding", { rms });
|
|
7117
7208
|
this.setState("listening");
|
|
7118
7209
|
return;
|
|
7119
7210
|
}
|
|
@@ -7130,7 +7221,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7130
7221
|
}
|
|
7131
7222
|
if (this.epoch !== capturedEpoch) return;
|
|
7132
7223
|
if (!transcript || !transcript.text.trim()) {
|
|
7133
|
-
|
|
7224
|
+
logger25.info("No transcript, resuming listening");
|
|
7134
7225
|
this.setState("listening");
|
|
7135
7226
|
return;
|
|
7136
7227
|
}
|
|
@@ -7166,7 +7257,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7166
7257
|
} catch (err) {
|
|
7167
7258
|
this.progressiveErrorCount = (this.progressiveErrorCount ?? 0) + 1;
|
|
7168
7259
|
if (this.progressiveErrorCount % 10 === 1) {
|
|
7169
|
-
|
|
7260
|
+
logger25.warn("Progressive transcription error", {
|
|
7170
7261
|
code: ErrorCodes.SPH_ASR_ERROR,
|
|
7171
7262
|
error: String(err),
|
|
7172
7263
|
count: this.progressiveErrorCount
|
|
@@ -7218,9 +7309,9 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7218
7309
|
} catch (error) {
|
|
7219
7310
|
span?.endWithError(error instanceof Error ? error : new Error(String(error)));
|
|
7220
7311
|
this.asrErrorCount++;
|
|
7221
|
-
|
|
7312
|
+
logger25.warn("Transcription failed", { attempt: this.asrErrorCount, error: String(error) });
|
|
7222
7313
|
if (this.asrErrorCount >= 3 && this.config.models) {
|
|
7223
|
-
|
|
7314
|
+
logger25.warn("3 consecutive ASR errors, recreating session");
|
|
7224
7315
|
try {
|
|
7225
7316
|
await this.asr.dispose();
|
|
7226
7317
|
this.asr = createSenseVoice({
|
|
@@ -7233,7 +7324,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7233
7324
|
await this.asr.load();
|
|
7234
7325
|
this.asrErrorCount = 0;
|
|
7235
7326
|
} catch (recreateErr) {
|
|
7236
|
-
|
|
7327
|
+
logger25.error("ASR session recreation failed", { error: String(recreateErr) });
|
|
7237
7328
|
}
|
|
7238
7329
|
}
|
|
7239
7330
|
return null;
|
|
@@ -7262,7 +7353,7 @@ var _SpeechListener = class _SpeechListener extends EventEmitter {
|
|
|
7262
7353
|
// ---------------------------------------------------------------------------
|
|
7263
7354
|
setState(state) {
|
|
7264
7355
|
if (this._state === state) return;
|
|
7265
|
-
|
|
7356
|
+
logger25.debug("State transition", { from: this._state, to: state });
|
|
7266
7357
|
this._state = state;
|
|
7267
7358
|
this.emit("state", state);
|
|
7268
7359
|
}
|
|
@@ -7281,7 +7372,7 @@ _SpeechListener.MAX_AUDIO_BUFFER_SAMPLES = 16e3 * 30;
|
|
|
7281
7372
|
var SpeechListener = _SpeechListener;
|
|
7282
7373
|
|
|
7283
7374
|
// src/audio/InterruptionHandler.ts
|
|
7284
|
-
var
|
|
7375
|
+
var logger26 = createLogger("InterruptionHandler");
|
|
7285
7376
|
var InterruptionHandler = class extends EventEmitter {
|
|
7286
7377
|
constructor(config = {}) {
|
|
7287
7378
|
super();
|
|
@@ -7302,7 +7393,7 @@ var InterruptionHandler = class extends EventEmitter {
|
|
|
7302
7393
|
enabled: true,
|
|
7303
7394
|
...config
|
|
7304
7395
|
};
|
|
7305
|
-
|
|
7396
|
+
logger26.debug("Constructed with config", {
|
|
7306
7397
|
vadThreshold: this.config.vadThreshold,
|
|
7307
7398
|
minSpeechDurationMs: this.config.minSpeechDurationMs,
|
|
7308
7399
|
silenceTimeoutMs: this.config.silenceTimeoutMs,
|
|
@@ -7333,7 +7424,7 @@ var InterruptionHandler = class extends EventEmitter {
|
|
|
7333
7424
|
processVADResult(vadProbability, audioEnergy = 0) {
|
|
7334
7425
|
if (!this.config.enabled) return;
|
|
7335
7426
|
if (this.aiIsSpeaking) {
|
|
7336
|
-
|
|
7427
|
+
logger26.trace("VAD during AI speech", {
|
|
7337
7428
|
vadProbability,
|
|
7338
7429
|
audioEnergy,
|
|
7339
7430
|
threshold: this.config.vadThreshold
|
|
@@ -7347,12 +7438,12 @@ var InterruptionHandler = class extends EventEmitter {
|
|
|
7347
7438
|
}
|
|
7348
7439
|
/** Notify that AI started/stopped speaking */
|
|
7349
7440
|
setAISpeaking(speaking) {
|
|
7350
|
-
|
|
7441
|
+
logger26.debug("AI speaking state changed", { speaking });
|
|
7351
7442
|
this.aiIsSpeaking = speaking;
|
|
7352
7443
|
}
|
|
7353
7444
|
/** Enable/disable interruption detection */
|
|
7354
7445
|
setEnabled(enabled) {
|
|
7355
|
-
|
|
7446
|
+
logger26.debug("Enabled state changed", { enabled });
|
|
7356
7447
|
this.config.enabled = enabled;
|
|
7357
7448
|
if (!enabled) {
|
|
7358
7449
|
this.reset();
|
|
@@ -7396,7 +7487,8 @@ var InterruptionHandler = class extends EventEmitter {
|
|
|
7396
7487
|
const speechDuration = now - this.speechStartTime;
|
|
7397
7488
|
if (speechDuration >= this.config.minSpeechDurationMs) {
|
|
7398
7489
|
this.interruptionTriggeredThisSession = true;
|
|
7399
|
-
|
|
7490
|
+
logger26.debug("Interruption triggered", { rms, durationMs: speechDuration });
|
|
7491
|
+
getTelemetry()?.incrementCounter(MetricNames.VOICE_INTERRUPTIONS, 1, { source: "detector" });
|
|
7400
7492
|
this.emit("interruption.triggered", { rms, durationMs: speechDuration });
|
|
7401
7493
|
}
|
|
7402
7494
|
}
|
|
@@ -7416,7 +7508,7 @@ var InterruptionHandler = class extends EventEmitter {
|
|
|
7416
7508
|
};
|
|
7417
7509
|
|
|
7418
7510
|
// src/inference/SafariSpeechRecognition.ts
|
|
7419
|
-
var
|
|
7511
|
+
var logger27 = createLogger("SafariSpeech");
|
|
7420
7512
|
var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
7421
7513
|
constructor(config = {}) {
|
|
7422
7514
|
this.recognition = null;
|
|
@@ -7435,7 +7527,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7435
7527
|
interimResults: config.interimResults ?? true,
|
|
7436
7528
|
maxAlternatives: config.maxAlternatives ?? 1
|
|
7437
7529
|
};
|
|
7438
|
-
|
|
7530
|
+
logger27.debug("SafariSpeechRecognition created", {
|
|
7439
7531
|
language: this.config.language,
|
|
7440
7532
|
continuous: this.config.continuous
|
|
7441
7533
|
});
|
|
@@ -7496,7 +7588,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7496
7588
|
*/
|
|
7497
7589
|
async start() {
|
|
7498
7590
|
if (this.isListening) {
|
|
7499
|
-
|
|
7591
|
+
logger27.warn("Already listening");
|
|
7500
7592
|
return;
|
|
7501
7593
|
}
|
|
7502
7594
|
if (!_SafariSpeechRecognition.isAvailable()) {
|
|
@@ -7526,7 +7618,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7526
7618
|
this.isListening = true;
|
|
7527
7619
|
this.startTime = getClock().now();
|
|
7528
7620
|
this.accumulatedText = "";
|
|
7529
|
-
|
|
7621
|
+
logger27.info("Speech recognition started", {
|
|
7530
7622
|
language: this.config.language
|
|
7531
7623
|
});
|
|
7532
7624
|
span?.end();
|
|
@@ -7541,7 +7633,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7541
7633
|
*/
|
|
7542
7634
|
async stop() {
|
|
7543
7635
|
if (!this.isListening || !this.recognition) {
|
|
7544
|
-
|
|
7636
|
+
logger27.warn("Not currently listening");
|
|
7545
7637
|
return {
|
|
7546
7638
|
text: this.accumulatedText,
|
|
7547
7639
|
language: this.config.language,
|
|
@@ -7570,7 +7662,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7570
7662
|
if (this.recognition && this.isListening) {
|
|
7571
7663
|
this.recognition.abort();
|
|
7572
7664
|
this.isListening = false;
|
|
7573
|
-
|
|
7665
|
+
logger27.info("Speech recognition aborted");
|
|
7574
7666
|
}
|
|
7575
7667
|
}
|
|
7576
7668
|
/**
|
|
@@ -7592,7 +7684,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7592
7684
|
* Dispose of recognition resources
|
|
7593
7685
|
*/
|
|
7594
7686
|
dispose() {
|
|
7595
|
-
|
|
7687
|
+
logger27.debug("Disposed");
|
|
7596
7688
|
if (this.recognition) {
|
|
7597
7689
|
if (this.isListening) {
|
|
7598
7690
|
this.recognition.abort();
|
|
@@ -7602,7 +7694,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7602
7694
|
this.isListening = false;
|
|
7603
7695
|
this.resultCallbacks = [];
|
|
7604
7696
|
this.errorCallbacks = [];
|
|
7605
|
-
|
|
7697
|
+
logger27.debug("SafariSpeechRecognition disposed");
|
|
7606
7698
|
}
|
|
7607
7699
|
/**
|
|
7608
7700
|
* Set up event handlers for the recognition instance
|
|
@@ -7630,7 +7722,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7630
7722
|
confidence: alternative.confidence
|
|
7631
7723
|
};
|
|
7632
7724
|
this.emitResult(speechResult);
|
|
7633
|
-
|
|
7725
|
+
logger27.trace("Speech result", {
|
|
7634
7726
|
text: text.substring(0, 50),
|
|
7635
7727
|
isFinal,
|
|
7636
7728
|
confidence: alternative.confidence
|
|
@@ -7640,12 +7732,12 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7640
7732
|
span?.end();
|
|
7641
7733
|
} catch (error) {
|
|
7642
7734
|
span?.endWithError(error instanceof Error ? error : new Error(String(error)));
|
|
7643
|
-
|
|
7735
|
+
logger27.error("Error processing speech result", { error });
|
|
7644
7736
|
}
|
|
7645
7737
|
};
|
|
7646
7738
|
this.recognition.onerror = (event) => {
|
|
7647
7739
|
const error = new Error(`Speech recognition error: ${event.error} - ${event.message}`);
|
|
7648
|
-
|
|
7740
|
+
logger27.error("Speech recognition error", { error: event.error, message: event.message });
|
|
7649
7741
|
this.emitError(error);
|
|
7650
7742
|
if (this.stopRejecter) {
|
|
7651
7743
|
this.stopRejecter(error);
|
|
@@ -7655,7 +7747,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7655
7747
|
};
|
|
7656
7748
|
this.recognition.onend = () => {
|
|
7657
7749
|
this.isListening = false;
|
|
7658
|
-
|
|
7750
|
+
logger27.info("Speech recognition ended", {
|
|
7659
7751
|
totalText: this.accumulatedText.length,
|
|
7660
7752
|
durationMs: getClock().now() - this.startTime
|
|
7661
7753
|
});
|
|
@@ -7672,13 +7764,13 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7672
7764
|
}
|
|
7673
7765
|
};
|
|
7674
7766
|
this.recognition.onstart = () => {
|
|
7675
|
-
|
|
7767
|
+
logger27.debug("Speech recognition started by browser");
|
|
7676
7768
|
};
|
|
7677
7769
|
this.recognition.onspeechstart = () => {
|
|
7678
|
-
|
|
7770
|
+
logger27.debug("Speech detected");
|
|
7679
7771
|
};
|
|
7680
7772
|
this.recognition.onspeechend = () => {
|
|
7681
|
-
|
|
7773
|
+
logger27.debug("Speech ended");
|
|
7682
7774
|
};
|
|
7683
7775
|
}
|
|
7684
7776
|
/**
|
|
@@ -7689,7 +7781,7 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7689
7781
|
try {
|
|
7690
7782
|
callback(result);
|
|
7691
7783
|
} catch (error) {
|
|
7692
|
-
|
|
7784
|
+
logger27.error("Error in result callback", { error });
|
|
7693
7785
|
}
|
|
7694
7786
|
}
|
|
7695
7787
|
}
|
|
@@ -7701,14 +7793,14 @@ var SafariSpeechRecognition = class _SafariSpeechRecognition {
|
|
|
7701
7793
|
try {
|
|
7702
7794
|
callback(error);
|
|
7703
7795
|
} catch (callbackError) {
|
|
7704
|
-
|
|
7796
|
+
logger27.error("Error in error callback", { error: callbackError });
|
|
7705
7797
|
}
|
|
7706
7798
|
}
|
|
7707
7799
|
}
|
|
7708
7800
|
};
|
|
7709
7801
|
|
|
7710
7802
|
// src/inference/ElevenLabsTTSBackend.ts
|
|
7711
|
-
var
|
|
7803
|
+
var logger28 = createLogger("ElevenLabsTTS");
|
|
7712
7804
|
var DEFAULT_MODEL = "eleven_multilingual_v2";
|
|
7713
7805
|
var DEFAULT_OUTPUT_FORMAT = "pcm_16000";
|
|
7714
7806
|
var DEFAULT_STABILITY = 0.5;
|
|
@@ -7753,7 +7845,7 @@ var ElevenLabsTTSBackend = class {
|
|
|
7753
7845
|
*/
|
|
7754
7846
|
async load() {
|
|
7755
7847
|
this._isLoaded = true;
|
|
7756
|
-
|
|
7848
|
+
logger28.info("ElevenLabs TTS ready", { voiceId: this.voiceId, model: this.model });
|
|
7757
7849
|
}
|
|
7758
7850
|
// ─── Stream ─────────────────────────────────────────────────────────────
|
|
7759
7851
|
/**
|
|
@@ -7799,7 +7891,7 @@ var ElevenLabsTTSBackend = class {
|
|
|
7799
7891
|
if (!response.ok) {
|
|
7800
7892
|
const errorText = await response.text().catch(() => "unknown");
|
|
7801
7893
|
const msg = `ElevenLabsTTS: HTTP ${response.status} \u2014 ${this.getHttpErrorMessage(response.status, errorText)}`;
|
|
7802
|
-
|
|
7894
|
+
logger28.error(msg);
|
|
7803
7895
|
throw new Error(msg);
|
|
7804
7896
|
}
|
|
7805
7897
|
if (!response.body) {
|
|
@@ -7809,7 +7901,7 @@ var ElevenLabsTTSBackend = class {
|
|
|
7809
7901
|
const latency2 = getClock().now() - startTime;
|
|
7810
7902
|
span?.setAttributes({ "tts.duration_s": duration, "tts.latency_ms": latency2 });
|
|
7811
7903
|
span?.end();
|
|
7812
|
-
telemetry?.recordHistogram(
|
|
7904
|
+
telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latency2, {
|
|
7813
7905
|
model: "elevenlabs-tts",
|
|
7814
7906
|
backend: "cloud"
|
|
7815
7907
|
});
|
|
@@ -7822,7 +7914,7 @@ var ElevenLabsTTSBackend = class {
|
|
|
7822
7914
|
while (true) {
|
|
7823
7915
|
if (options?.signal?.aborted) {
|
|
7824
7916
|
reader.cancel();
|
|
7825
|
-
|
|
7917
|
+
logger28.debug("Stream aborted by signal");
|
|
7826
7918
|
return;
|
|
7827
7919
|
}
|
|
7828
7920
|
const { done, value } = await reader.read();
|
|
@@ -7841,32 +7933,32 @@ var ElevenLabsTTSBackend = class {
|
|
|
7841
7933
|
}
|
|
7842
7934
|
const latency = getClock().now() - startTime;
|
|
7843
7935
|
const totalDuration = totalSamples / this._sampleRate;
|
|
7844
|
-
|
|
7936
|
+
logger28.debug("Stream complete", {
|
|
7845
7937
|
totalDuration: `${totalDuration.toFixed(2)}s`,
|
|
7846
7938
|
latencyMs: Math.round(latency),
|
|
7847
7939
|
totalSamples
|
|
7848
7940
|
});
|
|
7849
7941
|
span?.setAttributes({ "tts.duration_s": totalDuration, "tts.latency_ms": latency });
|
|
7850
7942
|
span?.end();
|
|
7851
|
-
telemetry?.recordHistogram(
|
|
7943
|
+
telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latency, {
|
|
7852
7944
|
model: "elevenlabs-tts",
|
|
7853
7945
|
backend: "cloud"
|
|
7854
7946
|
});
|
|
7855
|
-
telemetry?.incrementCounter(
|
|
7947
|
+
telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {
|
|
7856
7948
|
model: "elevenlabs-tts",
|
|
7857
7949
|
backend: "cloud",
|
|
7858
7950
|
status: "success"
|
|
7859
7951
|
});
|
|
7860
7952
|
} catch (err) {
|
|
7861
7953
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
7862
|
-
|
|
7954
|
+
logger28.debug("Stream aborted");
|
|
7863
7955
|
span?.end();
|
|
7864
7956
|
return;
|
|
7865
7957
|
}
|
|
7866
7958
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7867
|
-
|
|
7959
|
+
logger28.error("Stream failed", { error: errMsg });
|
|
7868
7960
|
span?.endWithError(err instanceof Error ? err : new Error(String(err)));
|
|
7869
|
-
telemetry?.incrementCounter(
|
|
7961
|
+
telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {
|
|
7870
7962
|
model: "elevenlabs-tts",
|
|
7871
7963
|
backend: "cloud",
|
|
7872
7964
|
status: "error"
|
|
@@ -7877,7 +7969,7 @@ var ElevenLabsTTSBackend = class {
|
|
|
7877
7969
|
// ─── Dispose ────────────────────────────────────────────────────────────
|
|
7878
7970
|
async dispose() {
|
|
7879
7971
|
this._isLoaded = false;
|
|
7880
|
-
|
|
7972
|
+
logger28.info("ElevenLabs TTS disposed");
|
|
7881
7973
|
}
|
|
7882
7974
|
// ─── Private ────────────────────────────────────────────────────────────
|
|
7883
7975
|
getHttpErrorMessage(status, body) {
|
|
@@ -7897,7 +7989,7 @@ var ElevenLabsTTSBackend = class {
|
|
|
7897
7989
|
};
|
|
7898
7990
|
|
|
7899
7991
|
// src/emotion/Emotion.ts
|
|
7900
|
-
var
|
|
7992
|
+
var logger29 = createLogger("EmotionController");
|
|
7901
7993
|
var EMOTION_NAMES = [
|
|
7902
7994
|
"amazement",
|
|
7903
7995
|
"anger",
|
|
@@ -7919,7 +8011,7 @@ function createEmotionVector(weights = {}) {
|
|
|
7919
8011
|
if (idx >= 0) {
|
|
7920
8012
|
vector[idx] = Math.max(0, Math.min(1, value));
|
|
7921
8013
|
} else {
|
|
7922
|
-
|
|
8014
|
+
logger29.warn(`Invalid emotion name in createEmotionVector: "${name}"`);
|
|
7923
8015
|
}
|
|
7924
8016
|
}
|
|
7925
8017
|
return vector;
|
|
@@ -8002,7 +8094,7 @@ var EmotionController = class {
|
|
|
8002
8094
|
this.targetEmotion.set(newEmotion);
|
|
8003
8095
|
this.currentEmotion.set(newEmotion);
|
|
8004
8096
|
this.transitionProgress = 1;
|
|
8005
|
-
|
|
8097
|
+
logger29.debug("set", { weights });
|
|
8006
8098
|
}
|
|
8007
8099
|
/**
|
|
8008
8100
|
* Set emotion from preset immediately
|
|
@@ -8012,7 +8104,7 @@ var EmotionController = class {
|
|
|
8012
8104
|
this.targetEmotion.set(newEmotion);
|
|
8013
8105
|
this.currentEmotion.set(newEmotion);
|
|
8014
8106
|
this.transitionProgress = 1;
|
|
8015
|
-
|
|
8107
|
+
logger29.debug("setPreset", { preset });
|
|
8016
8108
|
}
|
|
8017
8109
|
/**
|
|
8018
8110
|
* Transition to new emotion over time
|
|
@@ -8026,7 +8118,7 @@ var EmotionController = class {
|
|
|
8026
8118
|
this.transitionDuration = durationMs;
|
|
8027
8119
|
this.transitionStartTime = getClock().now();
|
|
8028
8120
|
this.transitionProgress = 0;
|
|
8029
|
-
|
|
8121
|
+
logger29.debug("transitionTo", { weights, durationMs });
|
|
8030
8122
|
}
|
|
8031
8123
|
/**
|
|
8032
8124
|
* Transition to preset over time
|
|
@@ -8059,7 +8151,7 @@ var EmotionController = class {
|
|
|
8059
8151
|
this.currentEmotion.fill(0);
|
|
8060
8152
|
this.targetEmotion.fill(0);
|
|
8061
8153
|
this.transitionProgress = 1;
|
|
8062
|
-
|
|
8154
|
+
logger29.debug("reset");
|
|
8063
8155
|
}
|
|
8064
8156
|
};
|
|
8065
8157
|
|
|
@@ -8140,7 +8232,7 @@ var DEFAULT_ANIMATION_CONFIG = {
|
|
|
8140
8232
|
};
|
|
8141
8233
|
|
|
8142
8234
|
// src/animation/AnimationGraph.ts
|
|
8143
|
-
var
|
|
8235
|
+
var logger30 = createLogger("AnimationGraph");
|
|
8144
8236
|
var AnimationGraph = class extends EventEmitter {
|
|
8145
8237
|
constructor(config = {}) {
|
|
8146
8238
|
super();
|
|
@@ -8173,7 +8265,7 @@ var AnimationGraph = class extends EventEmitter {
|
|
|
8173
8265
|
this.stateEnterTime = Date.now();
|
|
8174
8266
|
this.lastUpdateTime = Date.now();
|
|
8175
8267
|
this.cachedOutput = this.computeOutput();
|
|
8176
|
-
|
|
8268
|
+
logger30.info("constructor", {
|
|
8177
8269
|
initialState: this.config.initialState,
|
|
8178
8270
|
stateCount: this.config.states.length,
|
|
8179
8271
|
transitionCount: this.config.transitions.length
|
|
@@ -8244,7 +8336,7 @@ var AnimationGraph = class extends EventEmitter {
|
|
|
8244
8336
|
setState(stateName, blendDuration = 300) {
|
|
8245
8337
|
const targetState = this.config.states.find((s) => s.name === stateName);
|
|
8246
8338
|
if (!targetState) {
|
|
8247
|
-
|
|
8339
|
+
logger30.warn(`State '${stateName}' not found`);
|
|
8248
8340
|
return;
|
|
8249
8341
|
}
|
|
8250
8342
|
if (targetState.name === this.currentState.name && !this.isTransitioning) {
|
|
@@ -8322,7 +8414,7 @@ var AnimationGraph = class extends EventEmitter {
|
|
|
8322
8414
|
(s) => s.name === transition.to
|
|
8323
8415
|
);
|
|
8324
8416
|
if (!targetState) {
|
|
8325
|
-
|
|
8417
|
+
logger30.warn(`Target state '${transition.to}' not found`);
|
|
8326
8418
|
return;
|
|
8327
8419
|
}
|
|
8328
8420
|
const fromState = this.currentState.name;
|
|
@@ -8336,7 +8428,7 @@ var AnimationGraph = class extends EventEmitter {
|
|
|
8336
8428
|
if (!this.currentState.emotionBlendEnabled) {
|
|
8337
8429
|
this.targetEmotionWeight = 0;
|
|
8338
8430
|
}
|
|
8339
|
-
|
|
8431
|
+
logger30.debug("state transition", {
|
|
8340
8432
|
from: fromState,
|
|
8341
8433
|
to: targetState.name,
|
|
8342
8434
|
trigger: event,
|
|
@@ -8373,7 +8465,7 @@ var AnimationGraph = class extends EventEmitter {
|
|
|
8373
8465
|
if (this.currentState.timeout <= 0) return;
|
|
8374
8466
|
const elapsed = now - this.stateEnterTime;
|
|
8375
8467
|
if (elapsed >= this.currentState.timeout) {
|
|
8376
|
-
|
|
8468
|
+
logger30.debug("timeout transition", {
|
|
8377
8469
|
state: this.currentState.name,
|
|
8378
8470
|
elapsed,
|
|
8379
8471
|
timeout: this.currentState.timeout
|
|
@@ -8603,7 +8695,7 @@ var EmphasisDetector = class {
|
|
|
8603
8695
|
|
|
8604
8696
|
// src/animation/ProceduralLifeLayer.ts
|
|
8605
8697
|
var import_simplex_noise = require("simplex-noise");
|
|
8606
|
-
var
|
|
8698
|
+
var logger31 = createLogger("ProceduralLifeLayer");
|
|
8607
8699
|
var simplex2d = (0, import_simplex_noise.createNoise2D)();
|
|
8608
8700
|
var LIFE_BS_INDEX = /* @__PURE__ */ new Map();
|
|
8609
8701
|
for (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {
|
|
@@ -8709,7 +8801,7 @@ var ProceduralLifeLayer = class {
|
|
|
8709
8801
|
}
|
|
8710
8802
|
this.blinkInterval = this.nextBlinkInterval();
|
|
8711
8803
|
this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);
|
|
8712
|
-
|
|
8804
|
+
logger31.debug("constructor", {
|
|
8713
8805
|
blinkIntervalRange: this.blinkIntervalRange,
|
|
8714
8806
|
useLogNormalBlinks: this.useLogNormalBlinks,
|
|
8715
8807
|
gazeBreakIntervalRange: this.gazeBreakIntervalRange,
|
|
@@ -8813,7 +8905,7 @@ var ProceduralLifeLayer = class {
|
|
|
8813
8905
|
* Reset all internal state to initial values.
|
|
8814
8906
|
*/
|
|
8815
8907
|
reset() {
|
|
8816
|
-
|
|
8908
|
+
logger31.debug("reset");
|
|
8817
8909
|
this.blinkTimer = 0;
|
|
8818
8910
|
this.blinkInterval = this.nextBlinkInterval();
|
|
8819
8911
|
this.blinkPhase = PHASE_OPEN;
|
|
@@ -8865,7 +8957,7 @@ var ProceduralLifeLayer = class {
|
|
|
8865
8957
|
this.blinkTimer = 0;
|
|
8866
8958
|
this.blinkInterval = this.nextBlinkInterval();
|
|
8867
8959
|
this.asymmetryRight = 0.95 + Math.random() * 0.08;
|
|
8868
|
-
|
|
8960
|
+
logger31.trace("blink", { nextInterval: this.blinkInterval });
|
|
8869
8961
|
}
|
|
8870
8962
|
if (this.blinkPhase > PHASE_OPEN) {
|
|
8871
8963
|
this.blinkProgress += delta;
|
|
@@ -8946,7 +9038,7 @@ var ProceduralLifeLayer = class {
|
|
|
8946
9038
|
this.gazeBreakTargetX = (Math.random() - 0.5) * 2 * amp;
|
|
8947
9039
|
this.gazeBreakTargetY = (Math.random() - 0.5) * amp * 0.4;
|
|
8948
9040
|
this.gazeBreakInterval = randomRange(...params.interval);
|
|
8949
|
-
|
|
9041
|
+
logger31.trace("gaze break", {
|
|
8950
9042
|
targetX: this.gazeBreakTargetX.toFixed(3),
|
|
8951
9043
|
targetY: this.gazeBreakTargetY.toFixed(3),
|
|
8952
9044
|
nextInterval: this.gazeBreakInterval.toFixed(2),
|
|
@@ -9189,7 +9281,7 @@ var ALL_AUS = [...new Set(
|
|
|
9189
9281
|
)];
|
|
9190
9282
|
|
|
9191
9283
|
// src/face/EmotionResolver.ts
|
|
9192
|
-
var
|
|
9284
|
+
var logger32 = createLogger("EmotionResolver");
|
|
9193
9285
|
var BS_INDEX = /* @__PURE__ */ new Map();
|
|
9194
9286
|
for (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {
|
|
9195
9287
|
BS_INDEX.set(ARKIT_BLENDSHAPES[i], i);
|
|
@@ -9216,7 +9308,7 @@ var EmotionResolver = class {
|
|
|
9216
9308
|
if (!emotionWeight || emotionWeight < 0.01) continue;
|
|
9217
9309
|
const auActivations = EMOTION_TO_AU[emotionName];
|
|
9218
9310
|
if (!auActivations) {
|
|
9219
|
-
|
|
9311
|
+
logger32.warn(`Unknown emotion name with no AU mapping: "${emotionName}"`);
|
|
9220
9312
|
continue;
|
|
9221
9313
|
}
|
|
9222
9314
|
for (const activation of auActivations) {
|
|
@@ -9241,7 +9333,7 @@ var EmotionResolver = class {
|
|
|
9241
9333
|
};
|
|
9242
9334
|
|
|
9243
9335
|
// src/face/FaceCompositor.ts
|
|
9244
|
-
var
|
|
9336
|
+
var logger33 = createLogger("FaceCompositor");
|
|
9245
9337
|
function smoothstep(t) {
|
|
9246
9338
|
return t * t * (3 - 2 * t);
|
|
9247
9339
|
}
|
|
@@ -9272,7 +9364,7 @@ var FaceCompositor = class {
|
|
|
9272
9364
|
if (config?.profile) {
|
|
9273
9365
|
this.applyProfileArrays(config.profile);
|
|
9274
9366
|
}
|
|
9275
|
-
|
|
9367
|
+
logger33.debug("constructor", {
|
|
9276
9368
|
emotionSmoothing: this.emotionSmoothing,
|
|
9277
9369
|
hasProfile: !!config?.profile,
|
|
9278
9370
|
hasLifeLayer: !!config?.lifeLayer
|
|
@@ -9345,7 +9437,7 @@ var FaceCompositor = class {
|
|
|
9345
9437
|
*/
|
|
9346
9438
|
setEmotion(weights) {
|
|
9347
9439
|
this.stickyEmotion = weights;
|
|
9348
|
-
|
|
9440
|
+
logger33.debug("setEmotion", { weights });
|
|
9349
9441
|
}
|
|
9350
9442
|
/**
|
|
9351
9443
|
* Update character profile at runtime.
|
|
@@ -9354,7 +9446,7 @@ var FaceCompositor = class {
|
|
|
9354
9446
|
this.multiplier.fill(1);
|
|
9355
9447
|
this.offset.fill(0);
|
|
9356
9448
|
this.applyProfileArrays(profile);
|
|
9357
|
-
|
|
9449
|
+
logger33.debug("setProfile", {
|
|
9358
9450
|
multiplierKeys: profile.multiplier ? Object.keys(profile.multiplier).length : 0,
|
|
9359
9451
|
offsetKeys: profile.offset ? Object.keys(profile.offset).length : 0
|
|
9360
9452
|
});
|
|
@@ -9368,7 +9460,7 @@ var FaceCompositor = class {
|
|
|
9368
9460
|
this.lifeBuffer.fill(0);
|
|
9369
9461
|
this.stickyEmotion = void 0;
|
|
9370
9462
|
this.lifeLayer.reset();
|
|
9371
|
-
|
|
9463
|
+
logger33.debug("reset");
|
|
9372
9464
|
}
|
|
9373
9465
|
/** Expand partial profile maps into dense Float32Arrays */
|
|
9374
9466
|
applyProfileArrays(profile) {
|
|
@@ -9453,7 +9545,7 @@ function parseEmotionTags(text) {
|
|
|
9453
9545
|
}
|
|
9454
9546
|
|
|
9455
9547
|
// src/character/CharacterController.ts
|
|
9456
|
-
var
|
|
9548
|
+
var logger34 = createLogger("CharacterController");
|
|
9457
9549
|
var FRAME_BUDGET_US = 33e3;
|
|
9458
9550
|
var EMOTION_MAP = {
|
|
9459
9551
|
// Synced with EmotionPresets (packages/core/src/emotion/Emotion.ts)
|
|
@@ -9523,7 +9615,7 @@ var CharacterController = class {
|
|
|
9523
9615
|
this.gazeYawInfluence = config?.gaze?.yawInfluence ?? 0.4;
|
|
9524
9616
|
this.gazePitchInfluence = config?.gaze?.pitchInfluence ?? 0.3;
|
|
9525
9617
|
this.gazeSmoothing = config?.gaze?.smoothing ?? 5;
|
|
9526
|
-
|
|
9618
|
+
logger34.debug("constructor", {
|
|
9527
9619
|
gazeEnabled: this.gazeEnabled,
|
|
9528
9620
|
gazeYawInfluence: this.gazeYawInfluence,
|
|
9529
9621
|
gazePitchInfluence: this.gazePitchInfluence,
|
|
@@ -9587,13 +9679,13 @@ var CharacterController = class {
|
|
|
9587
9679
|
const resolved = resolveEmotion(emotion);
|
|
9588
9680
|
if (resolved) {
|
|
9589
9681
|
this._compositor.setEmotion(resolved);
|
|
9590
|
-
|
|
9682
|
+
logger34.debug("setEmotion", { emotion, resolved });
|
|
9591
9683
|
}
|
|
9592
9684
|
}
|
|
9593
9685
|
/** Update character profile at runtime. */
|
|
9594
9686
|
setProfile(profile) {
|
|
9595
9687
|
this._compositor.setProfile(profile);
|
|
9596
|
-
|
|
9688
|
+
logger34.debug("setProfile", {
|
|
9597
9689
|
multiplierKeys: profile.multiplier ? Object.keys(profile.multiplier).length : 0,
|
|
9598
9690
|
offsetKeys: profile.offset ? Object.keys(profile.offset).length : 0
|
|
9599
9691
|
});
|
|
@@ -9628,11 +9720,11 @@ var CharacterController = class {
|
|
|
9628
9720
|
this._compositor.reset();
|
|
9629
9721
|
this.gazeHeadYaw = 0;
|
|
9630
9722
|
this.gazeHeadPitch = -0.1;
|
|
9631
|
-
|
|
9723
|
+
logger34.debug("reset");
|
|
9632
9724
|
}
|
|
9633
9725
|
dispose() {
|
|
9634
9726
|
this.reset();
|
|
9635
|
-
|
|
9727
|
+
logger34.debug("dispose");
|
|
9636
9728
|
}
|
|
9637
9729
|
// ---------------------------------------------------------------------------
|
|
9638
9730
|
// Eye angle math (extracted from r3f useGazeTracking.computeEyeTargets)
|
|
@@ -9714,7 +9806,7 @@ var CharacterController = class {
|
|
|
9714
9806
|
};
|
|
9715
9807
|
|
|
9716
9808
|
// src/orchestration/MicLipSync.ts
|
|
9717
|
-
var
|
|
9809
|
+
var logger35 = createLogger("MicLipSync");
|
|
9718
9810
|
var MicLipSync = class extends EventEmitter {
|
|
9719
9811
|
constructor(config) {
|
|
9720
9812
|
super();
|
|
@@ -9733,7 +9825,7 @@ var MicLipSync = class extends EventEmitter {
|
|
|
9733
9825
|
this.vadChunkSize = 0;
|
|
9734
9826
|
this.vadBuffer = null;
|
|
9735
9827
|
this.vadBufferOffset = 0;
|
|
9736
|
-
|
|
9828
|
+
logger35.info("MicLipSync created", {
|
|
9737
9829
|
sampleRate: config.sampleRate ?? 16e3,
|
|
9738
9830
|
micChunkSize: config.micChunkSize ?? 512,
|
|
9739
9831
|
hasVAD: !!config.vad,
|
|
@@ -9755,12 +9847,12 @@ var MicLipSync = class extends EventEmitter {
|
|
|
9755
9847
|
this._currentFrame = scaled;
|
|
9756
9848
|
if (!this._firstFrameEmitted) {
|
|
9757
9849
|
this._firstFrameEmitted = true;
|
|
9758
|
-
|
|
9850
|
+
logger35.trace("First blendshape frame emitted");
|
|
9759
9851
|
}
|
|
9760
9852
|
this.emit("frame", { blendshapes: scaled, rawBlendshapes: raw });
|
|
9761
9853
|
},
|
|
9762
9854
|
onError: (error) => {
|
|
9763
|
-
|
|
9855
|
+
logger35.error("A2E inference error", { message: error.message });
|
|
9764
9856
|
this.emit("error", error);
|
|
9765
9857
|
}
|
|
9766
9858
|
});
|
|
@@ -9769,7 +9861,7 @@ var MicLipSync = class extends EventEmitter {
|
|
|
9769
9861
|
this.processor.pushAudio(float32);
|
|
9770
9862
|
if (this.vad) {
|
|
9771
9863
|
this.vadQueue = this.vadQueue.then(() => this.processVAD(float32)).catch((err) => {
|
|
9772
|
-
|
|
9864
|
+
logger35.warn("VAD processing error", { error: String(err), code: ErrorCodes.SPH_VAD_ERROR });
|
|
9773
9865
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
9774
9866
|
});
|
|
9775
9867
|
}
|
|
@@ -9805,7 +9897,7 @@ var MicLipSync = class extends EventEmitter {
|
|
|
9805
9897
|
/** Start microphone capture and inference loop */
|
|
9806
9898
|
async start() {
|
|
9807
9899
|
if (this._state === "active") return;
|
|
9808
|
-
|
|
9900
|
+
logger35.info("Starting MicLipSync");
|
|
9809
9901
|
getTelemetry()?.incrementCounter(MetricNames.MIC_SESSIONS);
|
|
9810
9902
|
await this.mic.start();
|
|
9811
9903
|
this.processor.startDrip();
|
|
@@ -9815,7 +9907,7 @@ var MicLipSync = class extends EventEmitter {
|
|
|
9815
9907
|
/** Stop microphone and inference */
|
|
9816
9908
|
stop() {
|
|
9817
9909
|
if (this._state === "idle") return;
|
|
9818
|
-
|
|
9910
|
+
logger35.info("Stopping MicLipSync");
|
|
9819
9911
|
this.processor.stopDrip();
|
|
9820
9912
|
this.mic.stop();
|
|
9821
9913
|
this._isSpeaking = false;
|
|
@@ -9864,7 +9956,7 @@ var MicLipSync = class extends EventEmitter {
|
|
|
9864
9956
|
this.emit("speech:end", { durationMs });
|
|
9865
9957
|
}
|
|
9866
9958
|
} catch (err) {
|
|
9867
|
-
|
|
9959
|
+
logger35.warn("VAD process error", { error: String(err), code: ErrorCodes.SPH_VAD_ERROR });
|
|
9868
9960
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
9869
9961
|
}
|
|
9870
9962
|
this.vadBufferOffset = 0;
|
|
@@ -9882,7 +9974,7 @@ var MicLipSync = class extends EventEmitter {
|
|
|
9882
9974
|
};
|
|
9883
9975
|
|
|
9884
9976
|
// src/orchestration/VoiceOrchestrator.ts
|
|
9885
|
-
var
|
|
9977
|
+
var logger36 = createLogger("VoiceOrchestrator");
|
|
9886
9978
|
var VoiceOrchestrator = class extends EventEmitter {
|
|
9887
9979
|
constructor() {
|
|
9888
9980
|
super(...arguments);
|
|
@@ -9934,12 +10026,16 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
9934
10026
|
const epoch = ++this.connectEpoch;
|
|
9935
10027
|
this._mode = config.mode ?? "local";
|
|
9936
10028
|
this._sessionId = crypto.randomUUID();
|
|
10029
|
+
const span = getTelemetry()?.startSpan("VoiceOrchestrator.connect", {
|
|
10030
|
+
"mode": this._mode,
|
|
10031
|
+
"session.id": this._sessionId
|
|
10032
|
+
});
|
|
9937
10033
|
if (config.onStateChange) this.on("state", config.onStateChange);
|
|
9938
10034
|
if (config.onLoadingProgress) this.on("loading:progress", config.onLoadingProgress);
|
|
9939
10035
|
if (config.onError) this.on("error", config.onError);
|
|
9940
10036
|
if (config.onTranscriptEvent) this.on("transcript", config.onTranscriptEvent);
|
|
9941
10037
|
if (config.onInterruption) this.on("interruption", config.onInterruption);
|
|
9942
|
-
|
|
10038
|
+
logger36.info("Connecting voice orchestrator", { mode: this._mode });
|
|
9943
10039
|
try {
|
|
9944
10040
|
if (this._mode === "local") {
|
|
9945
10041
|
const localCfg = config;
|
|
@@ -10020,9 +10116,11 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
10020
10116
|
} else {
|
|
10021
10117
|
this.wireCloudTranscript(config);
|
|
10022
10118
|
}
|
|
10023
|
-
|
|
10119
|
+
logger36.info("Voice orchestrator connected", { mode: this._mode });
|
|
10120
|
+
span?.end();
|
|
10024
10121
|
} catch (err) {
|
|
10025
|
-
|
|
10122
|
+
logger36.error("Voice orchestrator connect failed, cleaning up", { error: String(err) });
|
|
10123
|
+
span?.endWithError(err instanceof Error ? err : new Error(String(err)));
|
|
10026
10124
|
await this.disconnect();
|
|
10027
10125
|
throw err;
|
|
10028
10126
|
}
|
|
@@ -10131,6 +10229,7 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
10131
10229
|
const handler = async (result) => {
|
|
10132
10230
|
this.emit("transcript", result);
|
|
10133
10231
|
if (!result.isFinal || !result.text.trim()) return;
|
|
10232
|
+
const turnStart = getClock().now();
|
|
10134
10233
|
this.setState("thinking");
|
|
10135
10234
|
this.speechListener?.pause();
|
|
10136
10235
|
this.interruption?.setAISpeaking(true);
|
|
@@ -10147,10 +10246,11 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
10147
10246
|
await this.speak(text);
|
|
10148
10247
|
}
|
|
10149
10248
|
} catch (e) {
|
|
10150
|
-
|
|
10249
|
+
logger36.error("Voice transcript handler error", { error: String(e) });
|
|
10151
10250
|
} finally {
|
|
10152
10251
|
this.interruption?.setAISpeaking(false);
|
|
10153
10252
|
this.speechListener?.resume();
|
|
10253
|
+
getTelemetry()?.recordHistogram(MetricNames.VOICE_TURN_LATENCY, getClock().now() - turnStart);
|
|
10154
10254
|
this.setState("listening");
|
|
10155
10255
|
}
|
|
10156
10256
|
};
|
|
@@ -10161,6 +10261,8 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
10161
10261
|
const handler = async (result) => {
|
|
10162
10262
|
this.emit("transcript", result);
|
|
10163
10263
|
if (!result.isFinal || !result.text.trim()) return;
|
|
10264
|
+
const turnStart = getClock().now();
|
|
10265
|
+
let firstChunkSent = false;
|
|
10164
10266
|
this.setState("thinking");
|
|
10165
10267
|
this.speechListener?.pause();
|
|
10166
10268
|
this.interruption?.setAISpeaking(true);
|
|
@@ -10177,10 +10279,15 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
10177
10279
|
setEmotion: (emotion) => this.playbackPipeline?.setEmotion(emotion),
|
|
10178
10280
|
send: async (chunk) => {
|
|
10179
10281
|
if (abortController.signal.aborted) return;
|
|
10282
|
+
if (!firstChunkSent) {
|
|
10283
|
+
firstChunkSent = true;
|
|
10284
|
+
getTelemetry()?.recordHistogram(MetricNames.VOICE_RESPONSE_LATENCY, getClock().now() - turnStart);
|
|
10285
|
+
}
|
|
10180
10286
|
await this.playbackPipeline.onAudioChunk(chunk);
|
|
10181
10287
|
},
|
|
10182
10288
|
done: async () => {
|
|
10183
10289
|
if (abortController.signal.aborted) return;
|
|
10290
|
+
getTelemetry()?.recordHistogram(MetricNames.VOICE_TURN_LATENCY, getClock().now() - turnStart);
|
|
10184
10291
|
await this.playbackPipeline.end();
|
|
10185
10292
|
},
|
|
10186
10293
|
signal: abortController.signal,
|
|
@@ -10188,7 +10295,7 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
10188
10295
|
});
|
|
10189
10296
|
} catch (e) {
|
|
10190
10297
|
if (!abortController.signal.aborted) {
|
|
10191
|
-
|
|
10298
|
+
logger36.error("Cloud response handler error", { error: String(e) });
|
|
10192
10299
|
}
|
|
10193
10300
|
} finally {
|
|
10194
10301
|
this.responseAbortController = null;
|
|
@@ -10202,9 +10309,10 @@ var VoiceOrchestrator = class extends EventEmitter {
|
|
|
10202
10309
|
// -------------------------------------------------------------------------
|
|
10203
10310
|
handleInterruption() {
|
|
10204
10311
|
if (this._state !== "speaking") return;
|
|
10205
|
-
|
|
10312
|
+
logger36.info("Interruption triggered");
|
|
10206
10313
|
this.stopSpeaking();
|
|
10207
10314
|
this.emit("interruption");
|
|
10315
|
+
getTelemetry()?.incrementCounter(MetricNames.VOICE_INTERRUPTIONS, 1, { source: "orchestrator" });
|
|
10208
10316
|
this.speechListener?.resume();
|
|
10209
10317
|
this.setState("listening");
|
|
10210
10318
|
}
|