@absolutejs/voice 0.0.22-beta.189 → 0.0.22-beta.190
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular/index.d.ts +1 -0
- package/dist/angular/index.js +277 -148
- package/dist/angular/voice-live-ops.service.d.ts +11 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +1385 -105
- package/dist/client/liveOps.d.ts +22 -0
- package/dist/client/liveOpsWidget.d.ts +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +252 -59
- package/dist/liveOps.d.ts +122 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +119 -16
- package/dist/react/useVoiceLiveOps.d.ts +9 -0
- package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +1389 -101
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.js +165 -44
- package/dist/vue/useVoiceLiveOps.d.ts +9 -0
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -2252,6 +2252,91 @@ var createVoiceOpsActionCenterStore = (options = {}) => {
|
|
|
2252
2252
|
}
|
|
2253
2253
|
};
|
|
2254
2254
|
};
|
|
2255
|
+
// src/client/liveOps.ts
|
|
2256
|
+
var postVoiceLiveOpsAction = async (input, options = {}) => {
|
|
2257
|
+
if (!input.sessionId) {
|
|
2258
|
+
throw new Error("Start a voice session before running live ops actions.");
|
|
2259
|
+
}
|
|
2260
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2261
|
+
const response = await fetchImpl(options.actionPath ?? "/api/voice/live-ops/action", {
|
|
2262
|
+
body: JSON.stringify(input),
|
|
2263
|
+
headers: {
|
|
2264
|
+
"Content-Type": "application/json"
|
|
2265
|
+
},
|
|
2266
|
+
method: "POST"
|
|
2267
|
+
});
|
|
2268
|
+
const payload = await response.json().catch(() => null);
|
|
2269
|
+
if (!response.ok || !payload?.ok) {
|
|
2270
|
+
const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Voice live ops action failed: HTTP ${response.status}`;
|
|
2271
|
+
throw new Error(message);
|
|
2272
|
+
}
|
|
2273
|
+
return payload;
|
|
2274
|
+
};
|
|
2275
|
+
var createVoiceLiveOpsStore = (options = {}) => {
|
|
2276
|
+
const listeners = new Set;
|
|
2277
|
+
let closed = false;
|
|
2278
|
+
let snapshot = {
|
|
2279
|
+
error: null,
|
|
2280
|
+
isRunning: false
|
|
2281
|
+
};
|
|
2282
|
+
const emit = () => {
|
|
2283
|
+
for (const listener of listeners) {
|
|
2284
|
+
listener();
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
const run = async (input) => {
|
|
2288
|
+
if (closed) {
|
|
2289
|
+
return snapshot.lastResult;
|
|
2290
|
+
}
|
|
2291
|
+
snapshot = {
|
|
2292
|
+
...snapshot,
|
|
2293
|
+
error: null,
|
|
2294
|
+
isRunning: true,
|
|
2295
|
+
runningAction: input.action
|
|
2296
|
+
};
|
|
2297
|
+
emit();
|
|
2298
|
+
try {
|
|
2299
|
+
const result = await postVoiceLiveOpsAction(input, options);
|
|
2300
|
+
await options.onControl?.(result);
|
|
2301
|
+
snapshot = {
|
|
2302
|
+
...snapshot,
|
|
2303
|
+
error: null,
|
|
2304
|
+
isRunning: false,
|
|
2305
|
+
lastResult: result,
|
|
2306
|
+
runningAction: undefined,
|
|
2307
|
+
updatedAt: Date.now()
|
|
2308
|
+
};
|
|
2309
|
+
emit();
|
|
2310
|
+
return result;
|
|
2311
|
+
} catch (error) {
|
|
2312
|
+
snapshot = {
|
|
2313
|
+
...snapshot,
|
|
2314
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2315
|
+
isRunning: false,
|
|
2316
|
+
runningAction: undefined,
|
|
2317
|
+
updatedAt: Date.now()
|
|
2318
|
+
};
|
|
2319
|
+
emit();
|
|
2320
|
+
throw error;
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
const close = () => {
|
|
2324
|
+
closed = true;
|
|
2325
|
+
listeners.clear();
|
|
2326
|
+
};
|
|
2327
|
+
return {
|
|
2328
|
+
close,
|
|
2329
|
+
getServerSnapshot: () => snapshot,
|
|
2330
|
+
getSnapshot: () => snapshot,
|
|
2331
|
+
run,
|
|
2332
|
+
subscribe: (listener) => {
|
|
2333
|
+
listeners.add(listener);
|
|
2334
|
+
return () => {
|
|
2335
|
+
listeners.delete(listener);
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
};
|
|
2255
2340
|
// src/client/opsActionHistory.ts
|
|
2256
2341
|
var fetchVoiceOpsActionHistory = async (path = "/api/voice/ops-actions/history", options = {}) => {
|
|
2257
2342
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -2673,17 +2758,1205 @@ var defineVoiceOpsActionCenterElement = (tagName = "absolute-voice-ops-action-ce
|
|
|
2673
2758
|
}
|
|
2674
2759
|
});
|
|
2675
2760
|
};
|
|
2761
|
+
// src/liveOps.ts
|
|
2762
|
+
import { Elysia } from "elysia";
|
|
2763
|
+
|
|
2764
|
+
// src/audit.ts
|
|
2765
|
+
var includes = (filter, value) => {
|
|
2766
|
+
if (!filter) {
|
|
2767
|
+
return true;
|
|
2768
|
+
}
|
|
2769
|
+
if (!value) {
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
return Array.isArray(filter) ? filter.includes(value) : filter === value;
|
|
2773
|
+
};
|
|
2774
|
+
var createVoiceAuditEvent = (event) => ({
|
|
2775
|
+
...event,
|
|
2776
|
+
at: event.at ?? Date.now(),
|
|
2777
|
+
id: event.id ?? crypto.randomUUID()
|
|
2778
|
+
});
|
|
2779
|
+
var filterVoiceAuditEvents = (events, filter = {}) => {
|
|
2780
|
+
const sorted = events.filter((event) => {
|
|
2781
|
+
if (!includes(filter.type, event.type)) {
|
|
2782
|
+
return false;
|
|
2783
|
+
}
|
|
2784
|
+
if (!includes(filter.outcome, event.outcome)) {
|
|
2785
|
+
return false;
|
|
2786
|
+
}
|
|
2787
|
+
if (filter.actorId && event.actor?.id !== filter.actorId) {
|
|
2788
|
+
return false;
|
|
2789
|
+
}
|
|
2790
|
+
if (filter.resourceId && event.resource?.id !== filter.resourceId) {
|
|
2791
|
+
return false;
|
|
2792
|
+
}
|
|
2793
|
+
if (filter.resourceType && event.resource?.type !== filter.resourceType) {
|
|
2794
|
+
return false;
|
|
2795
|
+
}
|
|
2796
|
+
if (filter.sessionId && event.sessionId !== filter.sessionId) {
|
|
2797
|
+
return false;
|
|
2798
|
+
}
|
|
2799
|
+
if (filter.traceId && event.traceId !== filter.traceId) {
|
|
2800
|
+
return false;
|
|
2801
|
+
}
|
|
2802
|
+
if (typeof filter.after === "number" && event.at <= filter.after) {
|
|
2803
|
+
return false;
|
|
2804
|
+
}
|
|
2805
|
+
if (typeof filter.afterOrAt === "number" && event.at < filter.afterOrAt) {
|
|
2806
|
+
return false;
|
|
2807
|
+
}
|
|
2808
|
+
if (typeof filter.before === "number" && event.at >= filter.before) {
|
|
2809
|
+
return false;
|
|
2810
|
+
}
|
|
2811
|
+
if (typeof filter.beforeOrAt === "number" && event.at > filter.beforeOrAt) {
|
|
2812
|
+
return false;
|
|
2813
|
+
}
|
|
2814
|
+
return true;
|
|
2815
|
+
}).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
|
|
2816
|
+
return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
|
|
2817
|
+
};
|
|
2818
|
+
var createVoiceMemoryAuditEventStore = () => {
|
|
2819
|
+
const events = new Map;
|
|
2820
|
+
return {
|
|
2821
|
+
append: (event) => {
|
|
2822
|
+
const stored = createVoiceAuditEvent(event);
|
|
2823
|
+
events.set(stored.id, stored);
|
|
2824
|
+
return stored;
|
|
2825
|
+
},
|
|
2826
|
+
get: (id) => events.get(id),
|
|
2827
|
+
list: (filter) => filterVoiceAuditEvents([...events.values()], filter)
|
|
2828
|
+
};
|
|
2829
|
+
};
|
|
2830
|
+
var recordVoiceAuditEvent = (store, event) => store.append(createVoiceAuditEvent(event));
|
|
2831
|
+
var recordVoiceProviderAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
2832
|
+
action: `${input.kind}.provider.call`,
|
|
2833
|
+
actor: input.actor,
|
|
2834
|
+
metadata: input.metadata,
|
|
2835
|
+
outcome: input.outcome,
|
|
2836
|
+
payload: {
|
|
2837
|
+
cost: input.cost,
|
|
2838
|
+
elapsedMs: input.elapsedMs,
|
|
2839
|
+
error: input.error,
|
|
2840
|
+
kind: input.kind,
|
|
2841
|
+
model: input.model,
|
|
2842
|
+
provider: input.provider
|
|
2843
|
+
},
|
|
2844
|
+
resource: {
|
|
2845
|
+
id: input.provider,
|
|
2846
|
+
type: "provider"
|
|
2847
|
+
},
|
|
2848
|
+
sessionId: input.sessionId,
|
|
2849
|
+
traceId: input.traceId,
|
|
2850
|
+
type: "provider.call"
|
|
2851
|
+
});
|
|
2852
|
+
var recordVoiceToolAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
2853
|
+
action: "tool.call",
|
|
2854
|
+
actor: input.actor,
|
|
2855
|
+
metadata: input.metadata,
|
|
2856
|
+
outcome: input.outcome,
|
|
2857
|
+
payload: {
|
|
2858
|
+
elapsedMs: input.elapsedMs,
|
|
2859
|
+
error: input.error,
|
|
2860
|
+
toolCallId: input.toolCallId,
|
|
2861
|
+
toolName: input.toolName
|
|
2862
|
+
},
|
|
2863
|
+
resource: {
|
|
2864
|
+
id: input.toolName,
|
|
2865
|
+
type: "tool"
|
|
2866
|
+
},
|
|
2867
|
+
sessionId: input.sessionId,
|
|
2868
|
+
traceId: input.traceId,
|
|
2869
|
+
type: "tool.call"
|
|
2870
|
+
});
|
|
2871
|
+
var recordVoiceHandoffAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
2872
|
+
action: "handoff",
|
|
2873
|
+
actor: input.actor,
|
|
2874
|
+
metadata: input.metadata,
|
|
2875
|
+
outcome: input.outcome,
|
|
2876
|
+
payload: {
|
|
2877
|
+
fromAgentId: input.fromAgentId,
|
|
2878
|
+
reason: input.reason,
|
|
2879
|
+
target: input.target,
|
|
2880
|
+
toAgentId: input.toAgentId
|
|
2881
|
+
},
|
|
2882
|
+
resource: {
|
|
2883
|
+
id: input.toAgentId ?? input.target,
|
|
2884
|
+
type: "handoff"
|
|
2885
|
+
},
|
|
2886
|
+
sessionId: input.sessionId,
|
|
2887
|
+
traceId: input.traceId,
|
|
2888
|
+
type: "handoff"
|
|
2889
|
+
});
|
|
2890
|
+
var recordVoiceRetentionAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
2891
|
+
action: input.dryRun ? "retention.plan" : "retention.apply",
|
|
2892
|
+
actor: input.actor ?? {
|
|
2893
|
+
id: "voice-retention",
|
|
2894
|
+
kind: "system"
|
|
2895
|
+
},
|
|
2896
|
+
metadata: input.metadata,
|
|
2897
|
+
outcome: "success",
|
|
2898
|
+
payload: {
|
|
2899
|
+
deletedCount: input.report.deletedCount,
|
|
2900
|
+
dryRun: input.dryRun,
|
|
2901
|
+
scopes: input.report.scopes
|
|
2902
|
+
},
|
|
2903
|
+
resource: {
|
|
2904
|
+
type: "retention-policy"
|
|
2905
|
+
},
|
|
2906
|
+
type: "retention.policy"
|
|
2907
|
+
});
|
|
2908
|
+
var recordVoiceOperatorAuditEvent = (input) => recordVoiceAuditEvent(input.store, {
|
|
2909
|
+
action: input.action,
|
|
2910
|
+
actor: input.actor,
|
|
2911
|
+
metadata: input.metadata,
|
|
2912
|
+
outcome: input.outcome ?? "success",
|
|
2913
|
+
payload: input.payload,
|
|
2914
|
+
resource: input.resource,
|
|
2915
|
+
sessionId: input.sessionId,
|
|
2916
|
+
traceId: input.traceId,
|
|
2917
|
+
type: "operator.action"
|
|
2918
|
+
});
|
|
2919
|
+
var createVoiceAuditLogger = (store) => ({
|
|
2920
|
+
handoff: (input) => recordVoiceHandoffAuditEvent({ ...input, store }),
|
|
2921
|
+
operatorAction: (input) => recordVoiceOperatorAuditEvent({ ...input, store }),
|
|
2922
|
+
providerCall: (input) => recordVoiceProviderAuditEvent({ ...input, store }),
|
|
2923
|
+
record: (event) => recordVoiceAuditEvent(store, event),
|
|
2924
|
+
retention: (input) => recordVoiceRetentionAuditEvent({ ...input, store }),
|
|
2925
|
+
toolCall: (input) => recordVoiceToolAuditEvent({ ...input, store })
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
// src/trace.ts
|
|
2929
|
+
var createVoiceTraceEventId = (event) => [
|
|
2930
|
+
event.sessionId,
|
|
2931
|
+
event.turnId ?? "session",
|
|
2932
|
+
event.type,
|
|
2933
|
+
String(event.at ?? Date.now()),
|
|
2934
|
+
crypto.randomUUID()
|
|
2935
|
+
].map(encodeURIComponent).join(":");
|
|
2936
|
+
var createVoiceTraceEvent = (event) => ({
|
|
2937
|
+
...event,
|
|
2938
|
+
at: event.at,
|
|
2939
|
+
id: event.id ?? createVoiceTraceEventId({
|
|
2940
|
+
at: event.at,
|
|
2941
|
+
sessionId: event.sessionId,
|
|
2942
|
+
turnId: event.turnId,
|
|
2943
|
+
type: event.type
|
|
2944
|
+
})
|
|
2945
|
+
});
|
|
2946
|
+
var createVoiceTraceSinkDeliveryId = (events) => {
|
|
2947
|
+
const firstEvent = events[0];
|
|
2948
|
+
return [
|
|
2949
|
+
firstEvent?.sessionId ?? "trace",
|
|
2950
|
+
firstEvent?.traceId ?? "sink",
|
|
2951
|
+
String(firstEvent?.at ?? Date.now()),
|
|
2952
|
+
crypto.randomUUID()
|
|
2953
|
+
].map(encodeURIComponent).join(":");
|
|
2954
|
+
};
|
|
2955
|
+
var createVoiceTraceSinkDeliveryRecord = (input) => {
|
|
2956
|
+
const createdAt = input.createdAt ?? Date.now();
|
|
2957
|
+
return {
|
|
2958
|
+
createdAt,
|
|
2959
|
+
deliveredAt: input.deliveredAt,
|
|
2960
|
+
deliveryAttempts: input.deliveryAttempts,
|
|
2961
|
+
deliveryError: input.deliveryError,
|
|
2962
|
+
deliveryStatus: input.deliveryStatus ?? "pending",
|
|
2963
|
+
events: input.events,
|
|
2964
|
+
id: input.id ?? createVoiceTraceSinkDeliveryId(input.events),
|
|
2965
|
+
sinkDeliveries: input.sinkDeliveries,
|
|
2966
|
+
updatedAt: input.updatedAt ?? createdAt
|
|
2967
|
+
};
|
|
2968
|
+
};
|
|
2969
|
+
var matchesTraceFilter = (event, filter) => {
|
|
2970
|
+
if (filter.sessionId !== undefined && event.sessionId !== filter.sessionId) {
|
|
2971
|
+
return false;
|
|
2972
|
+
}
|
|
2973
|
+
if (filter.turnId !== undefined && event.turnId !== filter.turnId) {
|
|
2974
|
+
return false;
|
|
2975
|
+
}
|
|
2976
|
+
if (filter.scenarioId !== undefined && event.scenarioId !== filter.scenarioId) {
|
|
2977
|
+
return false;
|
|
2978
|
+
}
|
|
2979
|
+
if (filter.traceId !== undefined && event.traceId !== filter.traceId) {
|
|
2980
|
+
return false;
|
|
2981
|
+
}
|
|
2982
|
+
if (filter.type !== undefined) {
|
|
2983
|
+
const types = Array.isArray(filter.type) ? filter.type : [filter.type];
|
|
2984
|
+
if (!types.includes(event.type)) {
|
|
2985
|
+
return false;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
return true;
|
|
2989
|
+
};
|
|
2990
|
+
var filterVoiceTraceEvents = (events, filter = {}) => {
|
|
2991
|
+
const sorted = events.filter((event) => matchesTraceFilter(event, filter)).sort((left, right) => left.at - right.at || left.id.localeCompare(right.id));
|
|
2992
|
+
return typeof filter.limit === "number" && filter.limit >= 0 ? sorted.slice(0, filter.limit) : sorted;
|
|
2993
|
+
};
|
|
2994
|
+
var isPruneTimeMatch = (event, options) => {
|
|
2995
|
+
if (typeof options.before === "number" && event.at >= options.before) {
|
|
2996
|
+
return false;
|
|
2997
|
+
}
|
|
2998
|
+
if (typeof options.beforeOrAt === "number" && event.at > options.beforeOrAt) {
|
|
2999
|
+
return false;
|
|
3000
|
+
}
|
|
3001
|
+
return true;
|
|
3002
|
+
};
|
|
3003
|
+
var selectVoiceTraceEventsForPrune = (events, options = {}) => {
|
|
3004
|
+
let candidates = filterVoiceTraceEvents(events, options.filter).filter((event) => isPruneTimeMatch(event, options));
|
|
3005
|
+
if (typeof options.keepNewest === "number" && options.keepNewest >= 0) {
|
|
3006
|
+
const newestIds = new Set([...candidates].sort((left, right) => right.at - left.at || right.id.localeCompare(left.id)).slice(0, options.keepNewest).map((event) => event.id));
|
|
3007
|
+
candidates = candidates.filter((event) => !newestIds.has(event.id));
|
|
3008
|
+
}
|
|
3009
|
+
return typeof options.limit === "number" && options.limit >= 0 ? candidates.slice(0, options.limit) : candidates;
|
|
3010
|
+
};
|
|
3011
|
+
var pruneVoiceTraceEvents = async (options) => {
|
|
3012
|
+
const events = await options.store.list(options.filter);
|
|
3013
|
+
const deleted = selectVoiceTraceEventsForPrune(events, options);
|
|
3014
|
+
if (!options.dryRun) {
|
|
3015
|
+
await Promise.all(deleted.map((event) => options.store.remove(event.id)));
|
|
3016
|
+
}
|
|
3017
|
+
return {
|
|
3018
|
+
deleted,
|
|
3019
|
+
deletedCount: deleted.length,
|
|
3020
|
+
dryRun: Boolean(options.dryRun),
|
|
3021
|
+
scannedCount: events.length
|
|
3022
|
+
};
|
|
3023
|
+
};
|
|
3024
|
+
var sleep = async (delayMs) => {
|
|
3025
|
+
if (delayMs <= 0) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
3029
|
+
};
|
|
3030
|
+
var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3031
|
+
var signVoiceTraceSinkBody = async (input) => {
|
|
3032
|
+
const encoder = new TextEncoder;
|
|
3033
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(input.secret), {
|
|
3034
|
+
hash: "SHA-256",
|
|
3035
|
+
name: "HMAC"
|
|
3036
|
+
}, false, ["sign"]);
|
|
3037
|
+
const payload = encoder.encode(`${input.timestamp}.${input.body}`);
|
|
3038
|
+
const signature = await crypto.subtle.sign("HMAC", key, payload);
|
|
3039
|
+
return `sha256=${toHex(new Uint8Array(signature))}`;
|
|
3040
|
+
};
|
|
3041
|
+
var createVoiceTraceSinkDeliveryError = (input) => {
|
|
3042
|
+
if (input.response) {
|
|
3043
|
+
const statusText = input.response.statusText?.trim();
|
|
3044
|
+
return `Attempt ${input.attempt} failed with trace sink response ${input.response.status}${statusText ? ` ${statusText}` : ""}.`;
|
|
3045
|
+
}
|
|
3046
|
+
if (input.error instanceof Error) {
|
|
3047
|
+
return `Attempt ${input.attempt} failed: ${input.error.message}`;
|
|
3048
|
+
}
|
|
3049
|
+
return `Attempt ${input.attempt} failed: ${String(input.error)}`;
|
|
3050
|
+
};
|
|
3051
|
+
var normalizeVoiceTraceS3KeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/trace-deliveries";
|
|
3052
|
+
var createVoiceTraceS3ObjectKey = (prefix, events) => {
|
|
3053
|
+
const firstEvent = events[0];
|
|
3054
|
+
const safeSessionId = encodeURIComponent(firstEvent?.sessionId ?? "trace");
|
|
3055
|
+
const safeEventId = encodeURIComponent(firstEvent?.id ?? crypto.randomUUID());
|
|
3056
|
+
return `${prefix}/${safeSessionId}/${Date.now()}-${safeEventId}.json`;
|
|
3057
|
+
};
|
|
3058
|
+
var resolveVoiceS3DeliveredTo = (options, key) => {
|
|
3059
|
+
const bucket = options.bucket;
|
|
3060
|
+
return bucket ? `s3://${bucket}/${key}` : `s3://${key}`;
|
|
3061
|
+
};
|
|
3062
|
+
var aggregateVoiceTraceSinkDeliveryStatus = (deliveries) => {
|
|
3063
|
+
const statuses = Object.values(deliveries).map((delivery) => delivery.status);
|
|
3064
|
+
if (statuses.length === 0 || statuses.every((status) => status === "skipped")) {
|
|
3065
|
+
return "skipped";
|
|
3066
|
+
}
|
|
3067
|
+
if (statuses.some((status) => status === "failed")) {
|
|
3068
|
+
return "failed";
|
|
3069
|
+
}
|
|
3070
|
+
return "delivered";
|
|
3071
|
+
};
|
|
3072
|
+
var createVoiceTraceHTTPSink = (options) => ({
|
|
3073
|
+
deliver: async ({ events }) => {
|
|
3074
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
3075
|
+
if (typeof fetchImpl !== "function") {
|
|
3076
|
+
return {
|
|
3077
|
+
attempts: 0,
|
|
3078
|
+
deliveredTo: options.url,
|
|
3079
|
+
error: "Trace sink delivery failed: fetch is not available in this runtime.",
|
|
3080
|
+
eventCount: events.length,
|
|
3081
|
+
status: "failed"
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
const maxRetries = Math.max(0, options.retries ?? 0);
|
|
3085
|
+
const backoffMs = Math.max(0, options.backoffMs ?? 250);
|
|
3086
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? 1e4);
|
|
3087
|
+
const payload = options.body ? await options.body({ events }) : {
|
|
3088
|
+
eventCount: events.length,
|
|
3089
|
+
events,
|
|
3090
|
+
source: "absolutejs-voice"
|
|
3091
|
+
};
|
|
3092
|
+
const body = JSON.stringify(payload);
|
|
3093
|
+
let lastError = "Trace sink delivery failed.";
|
|
3094
|
+
for (let attempt = 1;attempt <= maxRetries + 1; attempt += 1) {
|
|
3095
|
+
let controller;
|
|
3096
|
+
let timeout;
|
|
3097
|
+
try {
|
|
3098
|
+
const headers = {
|
|
3099
|
+
"content-type": "application/json",
|
|
3100
|
+
...options.headers
|
|
3101
|
+
};
|
|
3102
|
+
if (options.signingSecret) {
|
|
3103
|
+
const timestamp = String(Date.now());
|
|
3104
|
+
headers["x-absolutejs-timestamp"] = timestamp;
|
|
3105
|
+
headers["x-absolutejs-signature"] = await signVoiceTraceSinkBody({
|
|
3106
|
+
body,
|
|
3107
|
+
secret: options.signingSecret,
|
|
3108
|
+
timestamp
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
controller = timeoutMs > 0 ? new AbortController : undefined;
|
|
3112
|
+
if (controller && timeoutMs > 0) {
|
|
3113
|
+
timeout = setTimeout(() => controller?.abort(), timeoutMs);
|
|
3114
|
+
}
|
|
3115
|
+
const response = await fetchImpl(options.url, {
|
|
3116
|
+
body,
|
|
3117
|
+
headers,
|
|
3118
|
+
method: options.method ?? "POST",
|
|
3119
|
+
signal: controller?.signal
|
|
3120
|
+
});
|
|
3121
|
+
if (response.ok) {
|
|
3122
|
+
let responseBody;
|
|
3123
|
+
try {
|
|
3124
|
+
responseBody = await response.clone().json();
|
|
3125
|
+
} catch {
|
|
3126
|
+
responseBody = undefined;
|
|
3127
|
+
}
|
|
3128
|
+
return {
|
|
3129
|
+
attempts: attempt,
|
|
3130
|
+
deliveredAt: Date.now(),
|
|
3131
|
+
deliveredTo: options.url,
|
|
3132
|
+
eventCount: events.length,
|
|
3133
|
+
responseBody,
|
|
3134
|
+
status: "delivered"
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
lastError = createVoiceTraceSinkDeliveryError({
|
|
3138
|
+
attempt,
|
|
3139
|
+
response
|
|
3140
|
+
});
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
lastError = createVoiceTraceSinkDeliveryError({
|
|
3143
|
+
attempt,
|
|
3144
|
+
error
|
|
3145
|
+
});
|
|
3146
|
+
} finally {
|
|
3147
|
+
if (timeout) {
|
|
3148
|
+
clearTimeout(timeout);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
if (attempt <= maxRetries) {
|
|
3152
|
+
await sleep(backoffMs * attempt);
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
return {
|
|
3156
|
+
attempts: maxRetries + 1,
|
|
3157
|
+
deliveredTo: options.url,
|
|
3158
|
+
error: lastError,
|
|
3159
|
+
eventCount: events.length,
|
|
3160
|
+
status: "failed"
|
|
3161
|
+
};
|
|
3162
|
+
},
|
|
3163
|
+
eventTypes: options.eventTypes,
|
|
3164
|
+
id: options.id,
|
|
3165
|
+
kind: options.kind ?? "http"
|
|
3166
|
+
});
|
|
3167
|
+
var createVoiceTraceS3Sink = (options) => {
|
|
3168
|
+
const client = options.client ?? new Bun.S3Client(options);
|
|
3169
|
+
const keyPrefix = normalizeVoiceTraceS3KeyPrefix(options.keyPrefix);
|
|
3170
|
+
return {
|
|
3171
|
+
deliver: async ({ events }) => {
|
|
3172
|
+
const key = createVoiceTraceS3ObjectKey(keyPrefix, events);
|
|
3173
|
+
const payload = options.body ? await options.body({ events, key }) : {
|
|
3174
|
+
eventCount: events.length,
|
|
3175
|
+
events,
|
|
3176
|
+
key,
|
|
3177
|
+
source: "absolutejs-voice"
|
|
3178
|
+
};
|
|
3179
|
+
try {
|
|
3180
|
+
const file = client.file(key, options);
|
|
3181
|
+
await file.write(JSON.stringify(payload), {
|
|
3182
|
+
type: options.contentType ?? "application/json"
|
|
3183
|
+
});
|
|
3184
|
+
return {
|
|
3185
|
+
attempts: 1,
|
|
3186
|
+
deliveredAt: Date.now(),
|
|
3187
|
+
deliveredTo: resolveVoiceS3DeliveredTo(options, key),
|
|
3188
|
+
eventCount: events.length,
|
|
3189
|
+
responseBody: { key },
|
|
3190
|
+
status: "delivered"
|
|
3191
|
+
};
|
|
3192
|
+
} catch (error) {
|
|
3193
|
+
return {
|
|
3194
|
+
attempts: 1,
|
|
3195
|
+
deliveredTo: resolveVoiceS3DeliveredTo(options, key),
|
|
3196
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3197
|
+
eventCount: events.length,
|
|
3198
|
+
status: "failed"
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
},
|
|
3202
|
+
eventTypes: options.eventTypes,
|
|
3203
|
+
id: options.id,
|
|
3204
|
+
kind: options.kind ?? "s3"
|
|
3205
|
+
};
|
|
3206
|
+
};
|
|
3207
|
+
var deliverVoiceTraceEventsToSinks = async (input) => {
|
|
3208
|
+
const events = input.redact ? redactVoiceTraceEvents(input.events, input.redact) : input.events;
|
|
3209
|
+
const sinkDeliveries = {};
|
|
3210
|
+
for (const sink of input.sinks) {
|
|
3211
|
+
const sinkEvents = sink.eventTypes?.length ? events.filter((event) => sink.eventTypes?.includes(event.type)) : events;
|
|
3212
|
+
if (sinkEvents.length === 0) {
|
|
3213
|
+
sinkDeliveries[sink.id] = {
|
|
3214
|
+
attempts: 0,
|
|
3215
|
+
eventCount: 0,
|
|
3216
|
+
status: "skipped"
|
|
3217
|
+
};
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3220
|
+
try {
|
|
3221
|
+
sinkDeliveries[sink.id] = await sink.deliver({
|
|
3222
|
+
events: sinkEvents
|
|
3223
|
+
});
|
|
3224
|
+
} catch (error) {
|
|
3225
|
+
sinkDeliveries[sink.id] = {
|
|
3226
|
+
attempts: 1,
|
|
3227
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3228
|
+
eventCount: sinkEvents.length,
|
|
3229
|
+
status: "failed"
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
return {
|
|
3234
|
+
deliveredAt: Date.now(),
|
|
3235
|
+
eventCount: events.length,
|
|
3236
|
+
sinkDeliveries,
|
|
3237
|
+
status: aggregateVoiceTraceSinkDeliveryStatus(sinkDeliveries)
|
|
3238
|
+
};
|
|
3239
|
+
};
|
|
3240
|
+
var createVoiceTraceSinkStore = (options) => {
|
|
3241
|
+
const deliver = async (event) => {
|
|
3242
|
+
const result = await deliverVoiceTraceEventsToSinks({
|
|
3243
|
+
events: [event],
|
|
3244
|
+
redact: options.redact,
|
|
3245
|
+
sinks: options.sinks
|
|
3246
|
+
});
|
|
3247
|
+
await options.onDelivery?.(result);
|
|
3248
|
+
};
|
|
3249
|
+
return {
|
|
3250
|
+
append: async (event) => {
|
|
3251
|
+
const stored = await options.store.append(event);
|
|
3252
|
+
if (options.deliveryQueue) {
|
|
3253
|
+
const delivery2 = createVoiceTraceSinkDeliveryRecord({
|
|
3254
|
+
events: [stored]
|
|
3255
|
+
});
|
|
3256
|
+
await options.deliveryQueue.set(delivery2.id, delivery2);
|
|
3257
|
+
return stored;
|
|
3258
|
+
}
|
|
3259
|
+
const delivery = deliver(stored);
|
|
3260
|
+
if (options.awaitDelivery) {
|
|
3261
|
+
await delivery;
|
|
3262
|
+
} else {
|
|
3263
|
+
delivery.catch((error) => {
|
|
3264
|
+
options.onError?.(error);
|
|
3265
|
+
});
|
|
3266
|
+
}
|
|
3267
|
+
return stored;
|
|
3268
|
+
},
|
|
3269
|
+
get: (id) => options.store.get(id),
|
|
3270
|
+
list: (filter) => options.store.list(filter),
|
|
3271
|
+
remove: (id) => options.store.remove(id)
|
|
3272
|
+
};
|
|
3273
|
+
};
|
|
3274
|
+
var createVoiceMemoryTraceSinkDeliveryStore = () => {
|
|
3275
|
+
const deliveries = new Map;
|
|
3276
|
+
return {
|
|
3277
|
+
get: async (id) => deliveries.get(id),
|
|
3278
|
+
list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
|
|
3279
|
+
remove: async (id) => {
|
|
3280
|
+
deliveries.delete(id);
|
|
3281
|
+
},
|
|
3282
|
+
set: async (id, delivery) => {
|
|
3283
|
+
deliveries.set(id, delivery);
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
};
|
|
3287
|
+
var createVoiceMemoryTraceEventStore = () => {
|
|
3288
|
+
const events = new Map;
|
|
3289
|
+
const append = async (event) => {
|
|
3290
|
+
const stored = createVoiceTraceEvent(event);
|
|
3291
|
+
events.set(stored.id, stored);
|
|
3292
|
+
return stored;
|
|
3293
|
+
};
|
|
3294
|
+
const get = async (id) => events.get(id);
|
|
3295
|
+
const list = async (filter) => filterVoiceTraceEvents([...events.values()], filter);
|
|
3296
|
+
const remove = async (id) => {
|
|
3297
|
+
events.delete(id);
|
|
3298
|
+
};
|
|
3299
|
+
return { append, get, list, remove };
|
|
3300
|
+
};
|
|
3301
|
+
var exportVoiceTrace = async (input) => {
|
|
3302
|
+
const events = await input.store.list(input.filter);
|
|
3303
|
+
return {
|
|
3304
|
+
exportedAt: Date.now(),
|
|
3305
|
+
events: input.redact ? redactVoiceTraceEvents(events, input.redact) : events,
|
|
3306
|
+
filter: input.filter,
|
|
3307
|
+
redacted: Boolean(input.redact)
|
|
3308
|
+
};
|
|
3309
|
+
};
|
|
3310
|
+
var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3311
|
+
var escapeHtml3 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3312
|
+
var formatTraceValue = (value) => {
|
|
3313
|
+
if (value === undefined || value === null) {
|
|
3314
|
+
return "";
|
|
3315
|
+
}
|
|
3316
|
+
if (typeof value === "string") {
|
|
3317
|
+
return value;
|
|
3318
|
+
}
|
|
3319
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
3320
|
+
return String(value);
|
|
3321
|
+
}
|
|
3322
|
+
try {
|
|
3323
|
+
return JSON.stringify(value);
|
|
3324
|
+
} catch {
|
|
3325
|
+
return String(value);
|
|
3326
|
+
}
|
|
3327
|
+
};
|
|
3328
|
+
var DEFAULT_REDACTION_KEYS = [
|
|
3329
|
+
"apiKey",
|
|
3330
|
+
"authorization",
|
|
3331
|
+
"creditCard",
|
|
3332
|
+
"email",
|
|
3333
|
+
"externalId",
|
|
3334
|
+
"password",
|
|
3335
|
+
"phone",
|
|
3336
|
+
"secret",
|
|
3337
|
+
"ssn",
|
|
3338
|
+
"token"
|
|
3339
|
+
];
|
|
3340
|
+
var DEFAULT_REDACTION_TEXT_KEYS = [
|
|
3341
|
+
"assistantText",
|
|
3342
|
+
"content",
|
|
3343
|
+
"error",
|
|
3344
|
+
"reason",
|
|
3345
|
+
"summary",
|
|
3346
|
+
"text"
|
|
3347
|
+
];
|
|
3348
|
+
var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
3349
|
+
var PHONE_PATTERN = /(?<!\d)(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4}(?!\d)/g;
|
|
3350
|
+
var normalizeRedactionKey = (key) => key.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3351
|
+
var resolveVoiceTraceRedactionOptions = (options = {}) => ({
|
|
3352
|
+
keys: typeof options === "boolean" ? DEFAULT_REDACTION_KEYS : options.keys ?? DEFAULT_REDACTION_KEYS,
|
|
3353
|
+
redactEmails: typeof options === "boolean" ? true : options.redactEmails ?? true,
|
|
3354
|
+
redactPhoneNumbers: typeof options === "boolean" ? true : options.redactPhoneNumbers ?? true,
|
|
3355
|
+
redactText: typeof options === "boolean" ? true : options.redactText ?? true,
|
|
3356
|
+
replacement: typeof options === "boolean" ? "[redacted]" : options.replacement ?? "[redacted]",
|
|
3357
|
+
textKeys: typeof options === "boolean" ? DEFAULT_REDACTION_TEXT_KEYS : options.textKeys ?? DEFAULT_REDACTION_TEXT_KEYS
|
|
3358
|
+
});
|
|
3359
|
+
var resolveReplacement = (input) => typeof input.options.replacement === "function" ? input.options.replacement({
|
|
3360
|
+
key: input.key,
|
|
3361
|
+
path: input.path,
|
|
3362
|
+
value: input.value
|
|
3363
|
+
}) : input.options.replacement;
|
|
3364
|
+
var redactVoiceTraceText = (value, options = {}, input = {}) => {
|
|
3365
|
+
const resolved = resolveVoiceTraceRedactionOptions(options);
|
|
3366
|
+
let redacted = value;
|
|
3367
|
+
const replacement = resolveReplacement({
|
|
3368
|
+
key: input.key,
|
|
3369
|
+
options: resolved,
|
|
3370
|
+
path: input.path ?? [],
|
|
3371
|
+
value
|
|
3372
|
+
});
|
|
3373
|
+
if (resolved.redactEmails) {
|
|
3374
|
+
redacted = redacted.replace(EMAIL_PATTERN, replacement);
|
|
3375
|
+
}
|
|
3376
|
+
if (resolved.redactPhoneNumbers) {
|
|
3377
|
+
redacted = redacted.replace(PHONE_PATTERN, replacement);
|
|
3378
|
+
}
|
|
3379
|
+
return redacted;
|
|
3380
|
+
};
|
|
3381
|
+
var redactTraceValue = (value, options, path) => {
|
|
3382
|
+
const key = path.at(-1);
|
|
3383
|
+
const normalizedKey = key ? normalizeRedactionKey(key) : undefined;
|
|
3384
|
+
const sensitiveKeys = new Set(options.keys.map(normalizeRedactionKey));
|
|
3385
|
+
const textKeys = new Set(options.textKeys.map(normalizeRedactionKey));
|
|
3386
|
+
if (normalizedKey && sensitiveKeys.has(normalizedKey) && (value === null || ["boolean", "number", "string", "undefined"].includes(typeof value))) {
|
|
3387
|
+
return resolveReplacement({
|
|
3388
|
+
key,
|
|
3389
|
+
options,
|
|
3390
|
+
path,
|
|
3391
|
+
value: String(value ?? "")
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
if (typeof value === "string") {
|
|
3395
|
+
const shouldRedactText = options.redactText && (!normalizedKey || textKeys.has(normalizedKey) || path.length === 0);
|
|
3396
|
+
return shouldRedactText ? redactVoiceTraceText(value, options, {
|
|
3397
|
+
key,
|
|
3398
|
+
path
|
|
3399
|
+
}) : value;
|
|
3400
|
+
}
|
|
3401
|
+
if (Array.isArray(value)) {
|
|
3402
|
+
return value.map((item, index) => redactTraceValue(item, options, [...path, String(index)]));
|
|
3403
|
+
}
|
|
3404
|
+
if (typeof value === "object" && value) {
|
|
3405
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
3406
|
+
entryKey,
|
|
3407
|
+
redactTraceValue(entryValue, options, [...path, entryKey])
|
|
3408
|
+
]));
|
|
3409
|
+
}
|
|
3410
|
+
return value;
|
|
3411
|
+
};
|
|
3412
|
+
var redactVoiceTraceEvent = (event, options = {}) => {
|
|
3413
|
+
const resolved = resolveVoiceTraceRedactionOptions(options);
|
|
3414
|
+
return {
|
|
3415
|
+
...event,
|
|
3416
|
+
metadata: redactTraceValue(event.metadata, resolved, ["metadata"]),
|
|
3417
|
+
payload: redactTraceValue(event.payload, resolved, ["payload"])
|
|
3418
|
+
};
|
|
3419
|
+
};
|
|
3420
|
+
var redactVoiceTraceEvents = (events, options = {}) => events.map((event) => redactVoiceTraceEvent(event, options));
|
|
3421
|
+
var summarizeVoiceTrace = (events) => {
|
|
3422
|
+
const sorted = filterVoiceTraceEvents(events);
|
|
3423
|
+
const firstEvent = sorted[0];
|
|
3424
|
+
const lastEvent = sorted.at(-1);
|
|
3425
|
+
const lifecycleEvents = sorted.filter((event) => event.type === "call.lifecycle");
|
|
3426
|
+
const startEvent = lifecycleEvents.find((event) => event.payload.type === "start");
|
|
3427
|
+
const endEvent = lifecycleEvents.toReversed().find((event) => event.payload.type === "end");
|
|
3428
|
+
const costEvents = sorted.filter((event) => event.type === "turn.cost");
|
|
3429
|
+
const toolEvents = sorted.filter((event) => event.type === "agent.tool");
|
|
3430
|
+
const startedAt = startEvent?.at ?? firstEvent?.at;
|
|
3431
|
+
const endedAt = endEvent?.at ?? lastEvent?.at;
|
|
3432
|
+
const failed = sorted.some((event) => event.type === "session.error") || endEvent?.payload.disposition === "failed";
|
|
3433
|
+
return {
|
|
3434
|
+
assistantReplyCount: sorted.filter((event) => event.type === "turn.assistant").length,
|
|
3435
|
+
callDurationMs: startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined,
|
|
3436
|
+
cost: {
|
|
3437
|
+
estimatedRelativeCostUnits: costEvents.reduce((total, event) => total + toNumber(event.payload.estimatedRelativeCostUnits), 0),
|
|
3438
|
+
totalBillableAudioMs: costEvents.reduce((total, event) => total + toNumber(event.payload.totalBillableAudioMs), 0)
|
|
3439
|
+
},
|
|
3440
|
+
endedAt,
|
|
3441
|
+
errorCount: sorted.filter((event) => event.type === "session.error").length,
|
|
3442
|
+
eventCount: sorted.length,
|
|
3443
|
+
failed,
|
|
3444
|
+
handoffCount: sorted.filter((event) => event.type === "agent.handoff").length,
|
|
3445
|
+
modelCallCount: sorted.filter((event) => event.type === "agent.model").length,
|
|
3446
|
+
sessionId: firstEvent?.sessionId,
|
|
3447
|
+
startedAt,
|
|
3448
|
+
toolCallCount: toolEvents.length,
|
|
3449
|
+
toolErrorCount: toolEvents.filter((event) => event.payload.status === "error").length,
|
|
3450
|
+
traceId: firstEvent?.traceId,
|
|
3451
|
+
transcriptCount: sorted.filter((event) => event.type === "turn.transcript").length,
|
|
3452
|
+
turnCount: sorted.filter((event) => event.type === "turn.committed").length
|
|
3453
|
+
};
|
|
3454
|
+
};
|
|
3455
|
+
var evaluateVoiceTrace = (events, options = {}) => {
|
|
3456
|
+
const summary = summarizeVoiceTrace(events);
|
|
3457
|
+
const issues = [];
|
|
3458
|
+
const maxHandoffs = options.maxHandoffs ?? 3;
|
|
3459
|
+
const maxToolErrors = options.maxToolErrors ?? 0;
|
|
3460
|
+
const maxModelCallsPerTurn = options.maxModelCallsPerTurn ?? 6;
|
|
3461
|
+
const turnCountForRatio = Math.max(1, summary.turnCount);
|
|
3462
|
+
if (options.requireCompletedCall !== false && !summary.endedAt) {
|
|
3463
|
+
issues.push({
|
|
3464
|
+
code: "call-not-ended",
|
|
3465
|
+
message: "Trace does not include a call end lifecycle event.",
|
|
3466
|
+
severity: "warning"
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
if (summary.failed) {
|
|
3470
|
+
issues.push({
|
|
3471
|
+
code: "session-error",
|
|
3472
|
+
message: "Trace contains a session error or failed call disposition.",
|
|
3473
|
+
severity: "error"
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
if (options.requireTranscript !== false && summary.transcriptCount === 0) {
|
|
3477
|
+
issues.push({
|
|
3478
|
+
code: "missing-transcript",
|
|
3479
|
+
message: "Trace does not include any transcript events.",
|
|
3480
|
+
severity: "error"
|
|
3481
|
+
});
|
|
3482
|
+
}
|
|
3483
|
+
if (options.requireTurn !== false && summary.turnCount === 0) {
|
|
3484
|
+
issues.push({
|
|
3485
|
+
code: "missing-turn",
|
|
3486
|
+
message: "Trace does not include any committed turns.",
|
|
3487
|
+
severity: "error"
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
if (options.requireAssistantReply !== false && summary.turnCount > 0 && summary.assistantReplyCount === 0) {
|
|
3491
|
+
issues.push({
|
|
3492
|
+
code: "missing-assistant-reply",
|
|
3493
|
+
message: "Trace has committed turns but no assistant replies.",
|
|
3494
|
+
severity: "warning"
|
|
3495
|
+
});
|
|
3496
|
+
}
|
|
3497
|
+
if (summary.toolErrorCount > maxToolErrors) {
|
|
3498
|
+
issues.push({
|
|
3499
|
+
code: "tool-errors",
|
|
3500
|
+
message: `Trace has ${summary.toolErrorCount} tool error(s), above the allowed ${maxToolErrors}.`,
|
|
3501
|
+
severity: "error"
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
if (summary.handoffCount > maxHandoffs) {
|
|
3505
|
+
issues.push({
|
|
3506
|
+
code: "too-many-handoffs",
|
|
3507
|
+
message: `Trace has ${summary.handoffCount} handoff(s), above the allowed ${maxHandoffs}.`,
|
|
3508
|
+
severity: "warning"
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
if (summary.modelCallCount / turnCountForRatio > maxModelCallsPerTurn) {
|
|
3512
|
+
issues.push({
|
|
3513
|
+
code: "too-many-model-calls",
|
|
3514
|
+
message: `Trace averages more than ${maxModelCallsPerTurn} model calls per committed turn.`,
|
|
3515
|
+
severity: "warning"
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3518
|
+
return {
|
|
3519
|
+
issues,
|
|
3520
|
+
pass: !issues.some((issue) => issue.severity === "error"),
|
|
3521
|
+
summary
|
|
3522
|
+
};
|
|
3523
|
+
};
|
|
3524
|
+
var renderTraceEventMarkdown = (event, startedAt) => {
|
|
3525
|
+
const offset = startedAt === undefined ? `${event.at}` : `+${Math.max(0, event.at - startedAt)}ms`;
|
|
3526
|
+
const label = `- ${offset} [${event.type}]`;
|
|
3527
|
+
switch (event.type) {
|
|
3528
|
+
case "turn.transcript":
|
|
3529
|
+
return `${label} ${event.payload.isFinal ? "final" : "partial"} "${formatTraceValue(event.payload.text)}"`;
|
|
3530
|
+
case "turn.committed":
|
|
3531
|
+
return `${label} committed "${formatTraceValue(event.payload.text)}"`;
|
|
3532
|
+
case "turn.assistant":
|
|
3533
|
+
return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
|
|
3534
|
+
case "agent.tool":
|
|
3535
|
+
return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
|
|
3536
|
+
case "agent.handoff":
|
|
3537
|
+
return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
|
|
3538
|
+
case "session.error":
|
|
3539
|
+
return `${label} ${formatTraceValue(event.payload.error)}`;
|
|
3540
|
+
case "call.lifecycle":
|
|
3541
|
+
return `${label} ${formatTraceValue(event.payload.type)} ${formatTraceValue(event.payload.disposition)}`.trim();
|
|
3542
|
+
default:
|
|
3543
|
+
return `${label} ${formatTraceValue(event.payload)}`;
|
|
3544
|
+
}
|
|
3545
|
+
};
|
|
3546
|
+
var renderVoiceTraceMarkdown = (events, options = {}) => {
|
|
3547
|
+
const sorted = filterVoiceTraceEvents(options.redact ? redactVoiceTraceEvents(events, options.redact) : events);
|
|
3548
|
+
const summary = summarizeVoiceTrace(sorted);
|
|
3549
|
+
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
3550
|
+
const lines = [
|
|
3551
|
+
`# ${options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim()}`,
|
|
3552
|
+
"",
|
|
3553
|
+
`Pass: ${evaluation.pass ? "yes" : "no"}`,
|
|
3554
|
+
`Session: ${summary.sessionId ?? "unknown"}`,
|
|
3555
|
+
`Events: ${summary.eventCount}`,
|
|
3556
|
+
`Turns: ${summary.turnCount}`,
|
|
3557
|
+
`Transcripts: ${summary.transcriptCount}`,
|
|
3558
|
+
`Assistant replies: ${summary.assistantReplyCount}`,
|
|
3559
|
+
`Model calls: ${summary.modelCallCount}`,
|
|
3560
|
+
`Tool calls: ${summary.toolCallCount}`,
|
|
3561
|
+
`Handoffs: ${summary.handoffCount}`,
|
|
3562
|
+
`Errors: ${summary.errorCount}`,
|
|
3563
|
+
`Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
|
|
3564
|
+
""
|
|
3565
|
+
];
|
|
3566
|
+
if (evaluation.issues.length > 0) {
|
|
3567
|
+
lines.push("## Issues", "");
|
|
3568
|
+
for (const issue of evaluation.issues) {
|
|
3569
|
+
lines.push(`- [${issue.severity}] ${issue.code}: ${issue.message}`);
|
|
3570
|
+
}
|
|
3571
|
+
lines.push("");
|
|
3572
|
+
}
|
|
3573
|
+
lines.push("## Timeline", "");
|
|
3574
|
+
for (const event of sorted) {
|
|
3575
|
+
lines.push(renderTraceEventMarkdown(event, summary.startedAt));
|
|
3576
|
+
}
|
|
3577
|
+
return lines.join(`
|
|
3578
|
+
`);
|
|
3579
|
+
};
|
|
3580
|
+
var renderVoiceTraceHTML = (events, options = {}) => {
|
|
3581
|
+
const markdown = renderVoiceTraceMarkdown(events, options);
|
|
3582
|
+
const renderEvents = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
3583
|
+
const summary = summarizeVoiceTrace(renderEvents);
|
|
3584
|
+
const evaluation = evaluateVoiceTrace(renderEvents, options.evaluation);
|
|
3585
|
+
const eventRows = filterVoiceTraceEvents(renderEvents).map((event) => {
|
|
3586
|
+
const offset = summary.startedAt === undefined ? event.at : Math.max(0, event.at - summary.startedAt);
|
|
3587
|
+
return [
|
|
3588
|
+
"<tr>",
|
|
3589
|
+
`<td>${escapeHtml3(String(offset))}</td>`,
|
|
3590
|
+
`<td>${escapeHtml3(event.type)}</td>`,
|
|
3591
|
+
`<td>${escapeHtml3(event.turnId ?? "")}</td>`,
|
|
3592
|
+
`<td><code>${escapeHtml3(JSON.stringify(event.payload))}</code></td>`,
|
|
3593
|
+
"</tr>"
|
|
3594
|
+
].join("");
|
|
3595
|
+
}).join(`
|
|
3596
|
+
`);
|
|
3597
|
+
return [
|
|
3598
|
+
"<!doctype html>",
|
|
3599
|
+
'<html lang="en">',
|
|
3600
|
+
"<head>",
|
|
3601
|
+
'<meta charset="utf-8" />',
|
|
3602
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
3603
|
+
`<title>${escapeHtml3(options.title ?? "Voice Trace")}</title>`,
|
|
3604
|
+
"<style>",
|
|
3605
|
+
"body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;line-height:1.45;background:#f8f7f2;color:#181713}",
|
|
3606
|
+
"main{max-width:1100px;margin:auto}",
|
|
3607
|
+
".summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.75rem;margin:1rem 0}",
|
|
3608
|
+
".card{background:white;border:1px solid #ded9cc;border-radius:12px;padding:1rem}",
|
|
3609
|
+
".pass{color:#126b3a}.fail{color:#9d2222}",
|
|
3610
|
+
"table{border-collapse:collapse;width:100%;background:white;border:1px solid #ded9cc}",
|
|
3611
|
+
"th,td{border-bottom:1px solid #eee8dc;padding:.65rem;text-align:left;vertical-align:top}",
|
|
3612
|
+
"code{white-space:pre-wrap;word-break:break-word}",
|
|
3613
|
+
"pre{background:#181713;color:#f8f7f2;padding:1rem;border-radius:12px;overflow:auto}",
|
|
3614
|
+
"</style>",
|
|
3615
|
+
"</head>",
|
|
3616
|
+
"<body><main>",
|
|
3617
|
+
`<h1>${escapeHtml3(options.title ?? `Voice Trace ${summary.sessionId ?? ""}`.trim())}</h1>`,
|
|
3618
|
+
`<p class="${evaluation.pass ? "pass" : "fail"}">QA: ${evaluation.pass ? "pass" : "fail"}</p>`,
|
|
3619
|
+
'<section class="summary">',
|
|
3620
|
+
`<div class="card"><strong>Events</strong><br>${summary.eventCount}</div>`,
|
|
3621
|
+
`<div class="card"><strong>Turns</strong><br>${summary.turnCount}</div>`,
|
|
3622
|
+
`<div class="card"><strong>Transcripts</strong><br>${summary.transcriptCount}</div>`,
|
|
3623
|
+
`<div class="card"><strong>Tool errors</strong><br>${summary.toolErrorCount}</div>`,
|
|
3624
|
+
`<div class="card"><strong>Cost units</strong><br>${summary.cost.estimatedRelativeCostUnits}</div>`,
|
|
3625
|
+
"</section>",
|
|
3626
|
+
"<h2>Timeline</h2>",
|
|
3627
|
+
"<table><thead><tr><th>Offset ms</th><th>Type</th><th>Turn</th><th>Payload</th></tr></thead><tbody>",
|
|
3628
|
+
eventRows,
|
|
3629
|
+
"</tbody></table>",
|
|
3630
|
+
"<h2>Markdown Export</h2>",
|
|
3631
|
+
`<pre>${escapeHtml3(markdown)}</pre>`,
|
|
3632
|
+
"</main></body></html>"
|
|
3633
|
+
].join(`
|
|
3634
|
+
`);
|
|
3635
|
+
};
|
|
3636
|
+
var buildVoiceTraceReplay = (events, options = {}) => ({
|
|
3637
|
+
evaluation: evaluateVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events, options.evaluation),
|
|
3638
|
+
html: renderVoiceTraceHTML(events, options),
|
|
3639
|
+
markdown: renderVoiceTraceMarkdown(events, options),
|
|
3640
|
+
summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
|
|
3641
|
+
});
|
|
3642
|
+
|
|
3643
|
+
// src/liveOps.ts
|
|
3644
|
+
var VOICE_LIVE_OPS_ACTIONS = [
|
|
3645
|
+
"assign",
|
|
3646
|
+
"create-task",
|
|
3647
|
+
"escalate",
|
|
3648
|
+
"force-handoff",
|
|
3649
|
+
"inject-instruction",
|
|
3650
|
+
"operator-takeover",
|
|
3651
|
+
"pause-assistant",
|
|
3652
|
+
"resume-assistant",
|
|
3653
|
+
"tag"
|
|
3654
|
+
];
|
|
3655
|
+
var isVoiceLiveOpsAction = (value) => typeof value === "string" && VOICE_LIVE_OPS_ACTIONS.includes(value);
|
|
3656
|
+
var toStringValue = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
3657
|
+
var createVoiceMemoryLiveOpsControlStore = () => {
|
|
3658
|
+
const states = new Map;
|
|
3659
|
+
return {
|
|
3660
|
+
get: (sessionId) => states.get(sessionId),
|
|
3661
|
+
set: (sessionId, state) => {
|
|
3662
|
+
states.set(sessionId, state);
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
};
|
|
3666
|
+
var getVoiceLiveOpsControlStatus = (action) => {
|
|
3667
|
+
switch (action) {
|
|
3668
|
+
case "force-handoff":
|
|
3669
|
+
return "handoff-forced";
|
|
3670
|
+
case "inject-instruction":
|
|
3671
|
+
return "instruction-injected";
|
|
3672
|
+
case "operator-takeover":
|
|
3673
|
+
return "operator-takeover";
|
|
3674
|
+
case "pause-assistant":
|
|
3675
|
+
return "assistant-paused";
|
|
3676
|
+
case "resume-assistant":
|
|
3677
|
+
return "assistant-resumed";
|
|
3678
|
+
default:
|
|
3679
|
+
return "recorded";
|
|
3680
|
+
}
|
|
3681
|
+
};
|
|
3682
|
+
var buildVoiceLiveOpsControlState = (input) => ({
|
|
3683
|
+
assistantPaused: input.action === "pause-assistant" || input.action === "operator-takeover" || input.action === "force-handoff" ? true : input.action === "resume-assistant" ? false : input.previous?.assistantPaused ?? false,
|
|
3684
|
+
handoffTarget: input.action === "force-handoff" ? input.tag : input.previous?.handoffTarget,
|
|
3685
|
+
injectedInstruction: input.action === "inject-instruction" ? input.detail : input.previous?.injectedInstruction,
|
|
3686
|
+
lastAction: input.action,
|
|
3687
|
+
lastUpdatedAt: input.at ?? Date.now(),
|
|
3688
|
+
operator: input.assignee,
|
|
3689
|
+
operatorTakeover: input.action === "operator-takeover" ? true : input.action === "resume-assistant" ? false : input.previous?.operatorTakeover ?? false,
|
|
3690
|
+
status: getVoiceLiveOpsControlStatus(input.action),
|
|
3691
|
+
tag: input.tag
|
|
3692
|
+
});
|
|
3693
|
+
var createVoiceLiveOpsController = (options = {}) => {
|
|
3694
|
+
const store = options.store ?? createVoiceMemoryLiveOpsControlStore();
|
|
3695
|
+
const perform = async (input) => {
|
|
3696
|
+
if (!input.sessionId) {
|
|
3697
|
+
throw new Error("Voice live ops action requires sessionId.");
|
|
3698
|
+
}
|
|
3699
|
+
if (!isVoiceLiveOpsAction(input.action)) {
|
|
3700
|
+
throw new Error("Voice live ops action is not supported.");
|
|
3701
|
+
}
|
|
3702
|
+
const at = Date.now();
|
|
3703
|
+
const assignee = input.assignee ?? options.defaultAssignee ?? "operator";
|
|
3704
|
+
const tag = input.tag ?? options.defaultTag ?? "live-ops";
|
|
3705
|
+
const detail = input.detail ?? options.defaultDetail ?? input.action;
|
|
3706
|
+
const previous = await store.get(input.sessionId);
|
|
3707
|
+
const control = buildVoiceLiveOpsControlState({
|
|
3708
|
+
...input,
|
|
3709
|
+
assignee,
|
|
3710
|
+
at,
|
|
3711
|
+
detail,
|
|
3712
|
+
previous,
|
|
3713
|
+
tag
|
|
3714
|
+
});
|
|
3715
|
+
await store.set(input.sessionId, control);
|
|
3716
|
+
const traceId = `voice-live-ops:${input.sessionId}:${input.action}:${at}`;
|
|
3717
|
+
await Promise.all([
|
|
3718
|
+
options.audit?.append(createVoiceAuditEvent({
|
|
3719
|
+
action: `voice.live_ops.${input.action}`,
|
|
3720
|
+
actor: {
|
|
3721
|
+
id: assignee,
|
|
3722
|
+
kind: "operator",
|
|
3723
|
+
name: assignee
|
|
3724
|
+
},
|
|
3725
|
+
at,
|
|
3726
|
+
metadata: {
|
|
3727
|
+
source: "voice-live-ops",
|
|
3728
|
+
tag
|
|
3729
|
+
},
|
|
3730
|
+
outcome: "success",
|
|
3731
|
+
payload: {
|
|
3732
|
+
action: input.action,
|
|
3733
|
+
assignee,
|
|
3734
|
+
control,
|
|
3735
|
+
detail,
|
|
3736
|
+
tag
|
|
3737
|
+
},
|
|
3738
|
+
resource: {
|
|
3739
|
+
id: input.sessionId,
|
|
3740
|
+
type: "voice.session"
|
|
3741
|
+
},
|
|
3742
|
+
sessionId: input.sessionId,
|
|
3743
|
+
traceId,
|
|
3744
|
+
type: "operator.action"
|
|
3745
|
+
})),
|
|
3746
|
+
options.trace?.append(createVoiceTraceEvent({
|
|
3747
|
+
at,
|
|
3748
|
+
metadata: {
|
|
3749
|
+
source: "voice-live-ops",
|
|
3750
|
+
tag
|
|
3751
|
+
},
|
|
3752
|
+
payload: {
|
|
3753
|
+
action: input.action,
|
|
3754
|
+
assignee,
|
|
3755
|
+
control,
|
|
3756
|
+
detail,
|
|
3757
|
+
status: "success",
|
|
3758
|
+
tag
|
|
3759
|
+
},
|
|
3760
|
+
sessionId: input.sessionId,
|
|
3761
|
+
traceId,
|
|
3762
|
+
type: "operator.action"
|
|
3763
|
+
}))
|
|
3764
|
+
]);
|
|
3765
|
+
const result = {
|
|
3766
|
+
action: input.action,
|
|
3767
|
+
control,
|
|
3768
|
+
ok: true,
|
|
3769
|
+
sessionId: input.sessionId
|
|
3770
|
+
};
|
|
3771
|
+
await options.onAction?.({
|
|
3772
|
+
...result,
|
|
3773
|
+
assignee,
|
|
3774
|
+
detail,
|
|
3775
|
+
tag
|
|
3776
|
+
});
|
|
3777
|
+
return result;
|
|
3778
|
+
};
|
|
3779
|
+
return {
|
|
3780
|
+
get: (sessionId) => store.get(sessionId),
|
|
3781
|
+
perform,
|
|
3782
|
+
store
|
|
3783
|
+
};
|
|
3784
|
+
};
|
|
3785
|
+
var readVoiceLiveOpsActionInput = async (request) => {
|
|
3786
|
+
const body = await request.json().catch(() => null);
|
|
3787
|
+
if (!body || typeof body !== "object") {
|
|
3788
|
+
throw new Error("Voice live ops action requires a JSON body.");
|
|
3789
|
+
}
|
|
3790
|
+
const record = body;
|
|
3791
|
+
const action = record.action;
|
|
3792
|
+
const sessionId = toStringValue(record.sessionId);
|
|
3793
|
+
if (!sessionId || !isVoiceLiveOpsAction(action)) {
|
|
3794
|
+
throw new Error("Voice live ops action requires valid sessionId and action.");
|
|
3795
|
+
}
|
|
3796
|
+
return {
|
|
3797
|
+
action,
|
|
3798
|
+
assignee: toStringValue(record.assignee),
|
|
3799
|
+
detail: toStringValue(record.detail),
|
|
3800
|
+
sessionId,
|
|
3801
|
+
tag: toStringValue(record.tag)
|
|
3802
|
+
};
|
|
3803
|
+
};
|
|
3804
|
+
var createVoiceLiveOpsRoutes = (options = {}) => {
|
|
3805
|
+
const controller = createVoiceLiveOpsController(options);
|
|
3806
|
+
const path = options.path ?? "/api/voice/live-ops/action";
|
|
3807
|
+
const controlPath = options.controlPath ?? "/api/voice/live-ops/control/:sessionId";
|
|
3808
|
+
return new Elysia({
|
|
3809
|
+
name: options.name ?? "absolutejs-voice-live-ops"
|
|
3810
|
+
}).post(path, async ({ request, set }) => {
|
|
3811
|
+
try {
|
|
3812
|
+
return await controller.perform(await readVoiceLiveOpsActionInput(request));
|
|
3813
|
+
} catch (error) {
|
|
3814
|
+
set.status = 400;
|
|
3815
|
+
return {
|
|
3816
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3817
|
+
ok: false
|
|
3818
|
+
};
|
|
3819
|
+
}
|
|
3820
|
+
}).get(controlPath, async ({ params }) => {
|
|
3821
|
+
const sessionId = params.sessionId;
|
|
3822
|
+
return {
|
|
3823
|
+
control: await controller.get(sessionId),
|
|
3824
|
+
ok: true,
|
|
3825
|
+
sessionId
|
|
3826
|
+
};
|
|
3827
|
+
});
|
|
3828
|
+
};
|
|
3829
|
+
|
|
3830
|
+
// src/client/liveOpsWidget.ts
|
|
3831
|
+
var ACTION_LABELS = {
|
|
3832
|
+
assign: "Assign",
|
|
3833
|
+
"create-task": "Create task",
|
|
3834
|
+
escalate: "Escalate",
|
|
3835
|
+
"force-handoff": "Force handoff",
|
|
3836
|
+
"inject-instruction": "Inject instruction",
|
|
3837
|
+
"operator-takeover": "Take over",
|
|
3838
|
+
"pause-assistant": "Pause assistant",
|
|
3839
|
+
"resume-assistant": "Resume assistant",
|
|
3840
|
+
tag: "Tag"
|
|
3841
|
+
};
|
|
3842
|
+
var escapeHtml4 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3843
|
+
var createVoiceLiveOpsInput = (action, input) => ({
|
|
3844
|
+
action,
|
|
3845
|
+
assignee: input.assignee,
|
|
3846
|
+
detail: input.detail,
|
|
3847
|
+
sessionId: input.sessionId ?? "",
|
|
3848
|
+
tag: input.tag
|
|
3849
|
+
});
|
|
3850
|
+
var renderVoiceLiveOpsHTML = (snapshot, options = {}) => {
|
|
3851
|
+
const sessionId = options.getSessionId?.() ?? "";
|
|
3852
|
+
const disabled = snapshot.isRunning || !sessionId;
|
|
3853
|
+
const actions = VOICE_LIVE_OPS_ACTIONS.map((action) => `<button type="button" data-absolute-voice-live-ops-action="${escapeHtml4(action)}"${disabled ? " disabled" : ""}>${escapeHtml4(snapshot.runningAction === action ? "Running..." : ACTION_LABELS[action])}</button>`).join("");
|
|
3854
|
+
const result = snapshot.error ? `<p class="absolute-voice-live-ops__error">${escapeHtml4(snapshot.error)}</p>` : snapshot.lastResult ? `<p class="absolute-voice-live-ops__result">Recorded ${escapeHtml4(snapshot.lastResult.action)}. Control: ${escapeHtml4(snapshot.lastResult.control.status)}.</p>` : '<p class="absolute-voice-live-ops__result">No live ops action has run yet.</p>';
|
|
3855
|
+
return `<section class="absolute-voice-live-ops">
|
|
3856
|
+
<header class="absolute-voice-live-ops__header">
|
|
3857
|
+
<span>${escapeHtml4(options.title ?? "Live Ops")}</span>
|
|
3858
|
+
<strong>${escapeHtml4(sessionId || "No active session")}</strong>
|
|
3859
|
+
</header>
|
|
3860
|
+
<p class="absolute-voice-live-ops__description">${escapeHtml4(options.description ?? "Pause, resume, take over, force handoff, or inject operator instructions during a live voice session.")}</p>
|
|
3861
|
+
<label><span>Operator</span><input data-absolute-voice-live-ops-assignee value="${escapeHtml4(options.defaultAssignee ?? "operator")}" /></label>
|
|
3862
|
+
<label><span>Tag / handoff target</span><input data-absolute-voice-live-ops-tag value="${escapeHtml4(options.defaultTag ?? "live-ops")}" /></label>
|
|
3863
|
+
<label><span>Detail / instruction</span><input data-absolute-voice-live-ops-detail value="${escapeHtml4(options.defaultDetail ?? "Operator marked this live session.")}" /></label>
|
|
3864
|
+
<div class="absolute-voice-live-ops__actions">${actions}</div>
|
|
3865
|
+
${result}
|
|
3866
|
+
</section>`;
|
|
3867
|
+
};
|
|
3868
|
+
var getVoiceLiveOpsCSS = () => `.absolute-voice-live-ops{border:1px solid #f59e0b66;border-radius:20px;background:#111827;color:#f8fafc;padding:18px;font-family:inherit}.absolute-voice-live-ops__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-live-ops__header span{color:#fbbf24;font-size:12px;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-live-ops__header strong{font-size:18px;overflow-wrap:anywhere}.absolute-voice-live-ops__description,.absolute-voice-live-ops__result{color:#cbd5e1}.absolute-voice-live-ops label{display:grid;gap:6px;margin-top:12px}.absolute-voice-live-ops label span{color:#94a3b8;font-size:13px}.absolute-voice-live-ops input{background:#020617;border:1px solid #f59e0b66;border-radius:12px;color:#f8fafc;font:inherit;padding:10px 12px}.absolute-voice-live-ops__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-live-ops__actions button{background:#f59e0b;border:0;border-radius:999px;color:#111827;cursor:pointer;font:inherit;font-weight:900;padding:8px 12px}.absolute-voice-live-ops__actions button:disabled{cursor:not-allowed;opacity:.5}.absolute-voice-live-ops__error{color:#fecaca;font-weight:800}`;
|
|
3869
|
+
var mountVoiceLiveOps = (element, options = {}) => {
|
|
3870
|
+
const store = createVoiceLiveOpsStore(options);
|
|
3871
|
+
let assignee = options.defaultAssignee ?? "operator";
|
|
3872
|
+
let detail = options.defaultDetail ?? "Operator marked this live session.";
|
|
3873
|
+
let tag = options.defaultTag ?? "live-ops";
|
|
3874
|
+
const syncInputs = () => {
|
|
3875
|
+
const assigneeInput = element.querySelector("[data-absolute-voice-live-ops-assignee]");
|
|
3876
|
+
const detailInput = element.querySelector("[data-absolute-voice-live-ops-detail]");
|
|
3877
|
+
const tagInput = element.querySelector("[data-absolute-voice-live-ops-tag]");
|
|
3878
|
+
if (assigneeInput instanceof HTMLInputElement) {
|
|
3879
|
+
assignee = assigneeInput.value;
|
|
3880
|
+
}
|
|
3881
|
+
if (detailInput instanceof HTMLInputElement) {
|
|
3882
|
+
detail = detailInput.value;
|
|
3883
|
+
}
|
|
3884
|
+
if (tagInput instanceof HTMLInputElement) {
|
|
3885
|
+
tag = tagInput.value;
|
|
3886
|
+
}
|
|
3887
|
+
};
|
|
3888
|
+
const render = () => {
|
|
3889
|
+
element.innerHTML = renderVoiceLiveOpsHTML(store.getSnapshot(), {
|
|
3890
|
+
...options,
|
|
3891
|
+
defaultAssignee: assignee,
|
|
3892
|
+
defaultDetail: detail,
|
|
3893
|
+
defaultTag: tag
|
|
3894
|
+
});
|
|
3895
|
+
};
|
|
3896
|
+
const unsubscribe = store.subscribe(render);
|
|
3897
|
+
const handleInput = () => syncInputs();
|
|
3898
|
+
const handleClick = (event) => {
|
|
3899
|
+
const target = event.target;
|
|
3900
|
+
if (!(target instanceof Element)) {
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
const button = target.closest("[data-absolute-voice-live-ops-action]");
|
|
3904
|
+
const action = button?.getAttribute("data-absolute-voice-live-ops-action");
|
|
3905
|
+
if (!action) {
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
syncInputs();
|
|
3909
|
+
store.run(createVoiceLiveOpsInput(action, {
|
|
3910
|
+
assignee,
|
|
3911
|
+
detail,
|
|
3912
|
+
sessionId: options.getSessionId?.(),
|
|
3913
|
+
tag
|
|
3914
|
+
})).catch(() => {});
|
|
3915
|
+
};
|
|
3916
|
+
element.addEventListener?.("click", handleClick);
|
|
3917
|
+
element.addEventListener?.("input", handleInput);
|
|
3918
|
+
render();
|
|
3919
|
+
return {
|
|
3920
|
+
close: () => {
|
|
3921
|
+
element.removeEventListener?.("click", handleClick);
|
|
3922
|
+
element.removeEventListener?.("input", handleInput);
|
|
3923
|
+
unsubscribe();
|
|
3924
|
+
store.close();
|
|
3925
|
+
},
|
|
3926
|
+
run: store.run
|
|
3927
|
+
};
|
|
3928
|
+
};
|
|
3929
|
+
var defineVoiceLiveOpsElement = (tagName = "absolute-voice-live-ops", options = {}) => {
|
|
3930
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
customElements.define(tagName, class AbsoluteVoiceLiveOpsElement extends HTMLElement {
|
|
3934
|
+
mounted;
|
|
3935
|
+
connectedCallback() {
|
|
3936
|
+
this.mounted = mountVoiceLiveOps(this, {
|
|
3937
|
+
...options,
|
|
3938
|
+
description: this.getAttribute("description") ?? options.description,
|
|
3939
|
+
getSessionId: options.getSessionId ?? (() => this.getAttribute("session-id") ?? undefined),
|
|
3940
|
+
title: this.getAttribute("title") ?? options.title
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3943
|
+
disconnectedCallback() {
|
|
3944
|
+
this.mounted?.close();
|
|
3945
|
+
this.mounted = undefined;
|
|
3946
|
+
}
|
|
3947
|
+
});
|
|
3948
|
+
};
|
|
2676
3949
|
// src/client/opsActionHistoryWidget.ts
|
|
2677
|
-
var
|
|
3950
|
+
var escapeHtml5 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2678
3951
|
var renderVoiceOpsActionHistoryWidgetHTML = (snapshot, options = {}) => {
|
|
2679
3952
|
const report = snapshot.report;
|
|
2680
3953
|
const entries = (report?.entries ?? []).slice(0, options.limit ?? 5);
|
|
2681
|
-
const rows = entries.map((entry) => `<li class="absolute-voice-ops-action-history__entry absolute-voice-ops-action-history__entry--${entry.ok ? "success" : "error"}"><span>${
|
|
3954
|
+
const rows = entries.map((entry) => `<li class="absolute-voice-ops-action-history__entry absolute-voice-ops-action-history__entry--${entry.ok ? "success" : "error"}"><span>${escapeHtml5(entry.actionId)}</span><strong>${escapeHtml5(entry.ok ? "Success" : "Failed")}</strong><small>${escapeHtml5(new Date(entry.at).toLocaleString())}${entry.status ? ` \xB7 HTTP ${String(entry.status)}` : ""}</small></li>`).join("");
|
|
2682
3955
|
return `<section class="absolute-voice-ops-action-history">
|
|
2683
|
-
<header><span>Operator proof</span><strong>${
|
|
3956
|
+
<header><span>Operator proof</span><strong>${escapeHtml5(options.title ?? "Action History")}</strong></header>
|
|
2684
3957
|
<p>${String(report?.total ?? 0)} action(s), ${String(report?.failed ?? 0)} failed.</p>
|
|
2685
3958
|
<ul>${rows || "<li>No operator actions recorded yet.</li>"}</ul>
|
|
2686
|
-
${snapshot.error ? `<p class="absolute-voice-ops-action-history__error">${
|
|
3959
|
+
${snapshot.error ? `<p class="absolute-voice-ops-action-history__error">${escapeHtml5(snapshot.error)}</p>` : ""}
|
|
2687
3960
|
</section>`;
|
|
2688
3961
|
};
|
|
2689
3962
|
var getVoiceOpsActionHistoryCSS = () => `.absolute-voice-ops-action-history{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;font-family:inherit}.absolute-voice-ops-action-history header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-action-history header span{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-action-history header strong{font-size:24px}.absolute-voice-ops-action-history p{color:#514733}.absolute-voice-ops-action-history ul{display:grid;gap:8px;list-style:none;margin:12px 0 0;padding:0}.absolute-voice-ops-action-history__entry{background:#fff;border:1px solid #eee4d2;border-radius:14px;display:grid;gap:3px;padding:10px 12px}.absolute-voice-ops-action-history__entry--error{border-color:#f2a7a7}.absolute-voice-ops-action-history__entry span{font-weight:800}.absolute-voice-ops-action-history__entry small{color:#655944}.absolute-voice-ops-action-history__error{color:#9f1239;font-weight:700}`;
|
|
@@ -2706,7 +3979,7 @@ var mountVoiceOpsActionHistory = (element, path = "/api/voice/ops-actions/histor
|
|
|
2706
3979
|
// src/client/deliveryRuntimeWidget.ts
|
|
2707
3980
|
var DEFAULT_TITLE3 = "Voice Delivery Runtime";
|
|
2708
3981
|
var DEFAULT_DESCRIPTION3 = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
|
|
2709
|
-
var
|
|
3982
|
+
var escapeHtml6 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2710
3983
|
var createSurface = (id, summary) => {
|
|
2711
3984
|
if (!summary) {
|
|
2712
3985
|
return {
|
|
@@ -2755,26 +4028,26 @@ var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
|
|
|
2755
4028
|
};
|
|
2756
4029
|
var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
|
|
2757
4030
|
const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
|
|
2758
|
-
const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${
|
|
2759
|
-
<span>${
|
|
2760
|
-
<strong>${
|
|
4031
|
+
const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml6(surface.status)}">
|
|
4032
|
+
<span>${escapeHtml6(surface.label)}</span>
|
|
4033
|
+
<strong>${escapeHtml6(surface.detail)}</strong>
|
|
2761
4034
|
<small>${String(surface.failed)} failed · ${String(surface.deadLettered)} dead-lettered</small>
|
|
2762
4035
|
</li>`).join("");
|
|
2763
4036
|
const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
|
|
2764
4037
|
<button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
|
|
2765
4038
|
<button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
|
|
2766
4039
|
</div>`;
|
|
2767
|
-
const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${
|
|
2768
|
-
return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${
|
|
4040
|
+
const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml6(model.actionError)}</p>` : "";
|
|
4041
|
+
return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml6(model.status)}">
|
|
2769
4042
|
<header class="absolute-voice-delivery-runtime__header">
|
|
2770
|
-
<span class="absolute-voice-delivery-runtime__eyebrow">${
|
|
2771
|
-
<strong class="absolute-voice-delivery-runtime__label">${
|
|
4043
|
+
<span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml6(model.title)}</span>
|
|
4044
|
+
<strong class="absolute-voice-delivery-runtime__label">${escapeHtml6(model.label)}</strong>
|
|
2772
4045
|
</header>
|
|
2773
|
-
<p class="absolute-voice-delivery-runtime__description">${
|
|
4046
|
+
<p class="absolute-voice-delivery-runtime__description">${escapeHtml6(model.description)}</p>
|
|
2774
4047
|
<ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
|
|
2775
4048
|
${actions}
|
|
2776
4049
|
${actionError}
|
|
2777
|
-
${model.error ? `<p class="absolute-voice-delivery-runtime__error">${
|
|
4050
|
+
${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml6(model.error)}</p>` : ""}
|
|
2778
4051
|
</section>`;
|
|
2779
4052
|
};
|
|
2780
4053
|
var getVoiceDeliveryRuntimeCSS = () => `.absolute-voice-delivery-runtime{border:1px solid #c9d8cf;border-radius:20px;background:#f6fff9;color:#0d1b12;padding:18px;box-shadow:0 18px 40px rgba(19,55,35,.12);font-family:inherit}.absolute-voice-delivery-runtime--warn,.absolute-voice-delivery-runtime--error{border-color:#f2b56b;background:#fff9ed}.absolute-voice-delivery-runtime__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-delivery-runtime__eyebrow{color:#4e6b59;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-delivery-runtime__label{font-size:28px;line-height:1}.absolute-voice-delivery-runtime__description{color:#33483b;margin:12px 0 0}.absolute-voice-delivery-runtime__surfaces{display:grid;gap:8px;list-style:none;margin:16px 0 0;padding:0}.absolute-voice-delivery-runtime__surface{background:#fff;border:1px solid #d9eadf;border-radius:14px;display:grid;gap:4px;padding:10px 12px}.absolute-voice-delivery-runtime__surface--warn{border-color:#f2b56b}.absolute-voice-delivery-runtime__surface--disabled{opacity:.72}.absolute-voice-delivery-runtime__surface span,.absolute-voice-delivery-runtime__surface small{color:#587063}.absolute-voice-delivery-runtime__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-delivery-runtime__actions button{background:#134e2d;border:0;border-radius:999px;color:#f6fff9;cursor:pointer;font:inherit;font-weight:800;padding:8px 12px}.absolute-voice-delivery-runtime__actions button:disabled{cursor:not-allowed;opacity:.48}.absolute-voice-delivery-runtime__error{color:#9f1239;font-weight:700}`;
|
|
@@ -2912,7 +4185,7 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
|
|
|
2912
4185
|
// src/client/routingStatusWidget.ts
|
|
2913
4186
|
var DEFAULT_TITLE4 = "Voice Routing";
|
|
2914
4187
|
var DEFAULT_DESCRIPTION4 = "Latest provider routing decision from the self-hosted trace store.";
|
|
2915
|
-
var
|
|
4188
|
+
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2916
4189
|
var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
|
|
2917
4190
|
var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
2918
4191
|
const decision = snapshot.decision;
|
|
@@ -2949,17 +4222,17 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
|
2949
4222
|
var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
|
|
2950
4223
|
const model = createVoiceRoutingStatusViewModel(snapshot, options);
|
|
2951
4224
|
const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
|
|
2952
|
-
<span>${
|
|
2953
|
-
<strong>${
|
|
4225
|
+
<span>${escapeHtml7(row.label)}</span>
|
|
4226
|
+
<strong>${escapeHtml7(row.value)}</strong>
|
|
2954
4227
|
</div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
|
|
2955
|
-
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${
|
|
4228
|
+
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml7(model.status)}">
|
|
2956
4229
|
<header class="absolute-voice-routing-status__header">
|
|
2957
|
-
<span class="absolute-voice-routing-status__eyebrow">${
|
|
2958
|
-
<strong class="absolute-voice-routing-status__label">${
|
|
4230
|
+
<span class="absolute-voice-routing-status__eyebrow">${escapeHtml7(model.title)}</span>
|
|
4231
|
+
<strong class="absolute-voice-routing-status__label">${escapeHtml7(model.label)}</strong>
|
|
2959
4232
|
</header>
|
|
2960
|
-
<p class="absolute-voice-routing-status__description">${
|
|
4233
|
+
<p class="absolute-voice-routing-status__description">${escapeHtml7(model.description)}</p>
|
|
2961
4234
|
${rows}
|
|
2962
|
-
${model.error ? `<p class="absolute-voice-routing-status__error">${
|
|
4235
|
+
${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml7(model.error)}</p>` : ""}
|
|
2963
4236
|
</section>`;
|
|
2964
4237
|
};
|
|
2965
4238
|
var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}`;
|
|
@@ -3685,7 +4958,7 @@ var createVoiceProviderSimulationControlsStore = (options) => {
|
|
|
3685
4958
|
};
|
|
3686
4959
|
};
|
|
3687
4960
|
// src/client/providerSimulationControlsWidget.ts
|
|
3688
|
-
var
|
|
4961
|
+
var escapeHtml8 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3689
4962
|
var formatKind = (kind) => (kind ?? "stt").toUpperCase();
|
|
3690
4963
|
var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
|
|
3691
4964
|
const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
|
|
@@ -3705,18 +4978,18 @@ var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
|
|
|
3705
4978
|
};
|
|
3706
4979
|
var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
|
|
3707
4980
|
const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
|
|
3708
|
-
const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${
|
|
3709
|
-
const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${
|
|
4981
|
+
const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml8(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml8(provider.provider)} ${escapeHtml8(formatKind(options.kind))} failure</button>`).join("");
|
|
4982
|
+
const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml8(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml8(provider.provider)} recovered</button>`).join("");
|
|
3710
4983
|
return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
|
|
3711
4984
|
<header class="absolute-voice-provider-simulation__header">
|
|
3712
|
-
<span class="absolute-voice-provider-simulation__eyebrow">${
|
|
3713
|
-
<strong class="absolute-voice-provider-simulation__label">${
|
|
4985
|
+
<span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml8(model.title)}</span>
|
|
4986
|
+
<strong class="absolute-voice-provider-simulation__label">${escapeHtml8(model.label)}</strong>
|
|
3714
4987
|
</header>
|
|
3715
|
-
<p class="absolute-voice-provider-simulation__description">${
|
|
3716
|
-
${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${
|
|
4988
|
+
<p class="absolute-voice-provider-simulation__description">${escapeHtml8(model.description)}</p>
|
|
4989
|
+
${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml8(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
|
|
3717
4990
|
<div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
|
|
3718
|
-
${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${
|
|
3719
|
-
${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${
|
|
4991
|
+
${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml8(snapshot.error)}</p>` : ""}
|
|
4992
|
+
${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml8(model.resultText)}</pre>` : ""}
|
|
3720
4993
|
</section>`;
|
|
3721
4994
|
};
|
|
3722
4995
|
var bindVoiceProviderSimulationControls = (element, store) => {
|
|
@@ -3783,7 +5056,7 @@ var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-pr
|
|
|
3783
5056
|
// src/client/providerStatusWidget.ts
|
|
3784
5057
|
var DEFAULT_TITLE5 = "Voice Providers";
|
|
3785
5058
|
var DEFAULT_DESCRIPTION5 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
|
|
3786
|
-
var
|
|
5059
|
+
var escapeHtml9 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3787
5060
|
var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
3788
5061
|
var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
3789
5062
|
var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
|
|
@@ -3839,25 +5112,25 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
|
|
|
3839
5112
|
};
|
|
3840
5113
|
var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
|
|
3841
5114
|
const model = createVoiceProviderStatusViewModel(snapshot, options);
|
|
3842
|
-
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${
|
|
5115
|
+
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml9(provider.status)}">
|
|
3843
5116
|
<header>
|
|
3844
|
-
<strong>${
|
|
3845
|
-
<span>${
|
|
5117
|
+
<strong>${escapeHtml9(provider.label)}</strong>
|
|
5118
|
+
<span>${escapeHtml9(formatStatus(provider.status))}</span>
|
|
3846
5119
|
</header>
|
|
3847
|
-
<p>${
|
|
5120
|
+
<p>${escapeHtml9(provider.detail)}</p>
|
|
3848
5121
|
<dl>${provider.rows.map((row) => `<div>
|
|
3849
|
-
<dt>${
|
|
3850
|
-
<dd>${
|
|
5122
|
+
<dt>${escapeHtml9(row.label)}</dt>
|
|
5123
|
+
<dd>${escapeHtml9(row.value)}</dd>
|
|
3851
5124
|
</div>`).join("")}</dl>
|
|
3852
5125
|
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
|
|
3853
|
-
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${
|
|
5126
|
+
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml9(model.status)}">
|
|
3854
5127
|
<header class="absolute-voice-provider-status__header">
|
|
3855
|
-
<span class="absolute-voice-provider-status__eyebrow">${
|
|
3856
|
-
<strong class="absolute-voice-provider-status__label">${
|
|
5128
|
+
<span class="absolute-voice-provider-status__eyebrow">${escapeHtml9(model.title)}</span>
|
|
5129
|
+
<strong class="absolute-voice-provider-status__label">${escapeHtml9(model.label)}</strong>
|
|
3857
5130
|
</header>
|
|
3858
|
-
<p class="absolute-voice-provider-status__description">${
|
|
5131
|
+
<p class="absolute-voice-provider-status__description">${escapeHtml9(model.description)}</p>
|
|
3859
5132
|
${providers}
|
|
3860
|
-
${model.error ? `<p class="absolute-voice-provider-status__error">${
|
|
5133
|
+
${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml9(model.error)}</p>` : ""}
|
|
3861
5134
|
</section>`;
|
|
3862
5135
|
};
|
|
3863
5136
|
var getVoiceProviderStatusCSS = () => `.absolute-voice-provider-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-provider-status--error,.absolute-voice-provider-status--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-status__header,.absolute-voice-provider-status__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-status__label{font-size:24px;line-height:1}.absolute-voice-provider-status__description,.absolute-voice-provider-status__provider p,.absolute-voice-provider-status__provider dt,.absolute-voice-provider-status__empty{color:#514733}.absolute-voice-provider-status__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-status__provider{background:#fff;border:1px solid #eee4d2;border-radius:16px;padding:14px}.absolute-voice-provider-status__provider--degraded,.absolute-voice-provider-status__provider--rate-limited,.absolute-voice-provider-status__provider--suppressed{border-color:#f2a7a7}.absolute-voice-provider-status__provider--recoverable{border-color:#fbbf24}.absolute-voice-provider-status__provider p{margin:10px 0}.absolute-voice-provider-status__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-status__provider div{background:#fffaf0;border:1px solid #eee4d2;border-radius:12px;padding:8px}.absolute-voice-provider-status__provider dt{font-size:12px}.absolute-voice-provider-status__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-status__empty{margin:14px 0 0}.absolute-voice-provider-status__error{color:#9f1239;font-weight:700}`;
|
|
@@ -3900,7 +5173,7 @@ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-statu
|
|
|
3900
5173
|
// src/client/providerCapabilitiesWidget.ts
|
|
3901
5174
|
var DEFAULT_TITLE6 = "Provider Capabilities";
|
|
3902
5175
|
var DEFAULT_DESCRIPTION6 = "Configured, selected, and healthy voice providers for this deployment.";
|
|
3903
|
-
var
|
|
5176
|
+
var escapeHtml10 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3904
5177
|
var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
3905
5178
|
var formatKind2 = (kind) => kind.toUpperCase();
|
|
3906
5179
|
var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
@@ -3955,25 +5228,25 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
|
|
|
3955
5228
|
};
|
|
3956
5229
|
var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
|
|
3957
5230
|
const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
|
|
3958
|
-
const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${
|
|
5231
|
+
const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml10(capability.status)}">
|
|
3959
5232
|
<header>
|
|
3960
|
-
<strong>${
|
|
3961
|
-
<span>${
|
|
5233
|
+
<strong>${escapeHtml10(capability.label)}</strong>
|
|
5234
|
+
<span>${escapeHtml10(formatStatus2(capability.status))}</span>
|
|
3962
5235
|
</header>
|
|
3963
|
-
<p>${
|
|
5236
|
+
<p>${escapeHtml10(capability.detail)}</p>
|
|
3964
5237
|
<dl>${capability.rows.map((row) => `<div>
|
|
3965
|
-
<dt>${
|
|
3966
|
-
<dd>${
|
|
5238
|
+
<dt>${escapeHtml10(row.label)}</dt>
|
|
5239
|
+
<dd>${escapeHtml10(row.value)}</dd>
|
|
3967
5240
|
</div>`).join("")}</dl>
|
|
3968
5241
|
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
|
|
3969
|
-
return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${
|
|
5242
|
+
return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml10(model.status)}">
|
|
3970
5243
|
<header class="absolute-voice-provider-capabilities__header">
|
|
3971
|
-
<span class="absolute-voice-provider-capabilities__eyebrow">${
|
|
3972
|
-
<strong class="absolute-voice-provider-capabilities__label">${
|
|
5244
|
+
<span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml10(model.title)}</span>
|
|
5245
|
+
<strong class="absolute-voice-provider-capabilities__label">${escapeHtml10(model.label)}</strong>
|
|
3973
5246
|
</header>
|
|
3974
|
-
<p class="absolute-voice-provider-capabilities__description">${
|
|
5247
|
+
<p class="absolute-voice-provider-capabilities__description">${escapeHtml10(model.description)}</p>
|
|
3975
5248
|
${capabilities}
|
|
3976
|
-
${model.error ? `<p class="absolute-voice-provider-capabilities__error">${
|
|
5249
|
+
${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml10(model.error)}</p>` : ""}
|
|
3977
5250
|
</section>`;
|
|
3978
5251
|
};
|
|
3979
5252
|
var getVoiceProviderCapabilitiesCSS = () => `.absolute-voice-provider-capabilities{border:1px solid #bfd7ea;border-radius:20px;background:#f6fbff;color:#08131f;padding:18px;box-shadow:0 18px 40px rgba(14,51,78,.12);font-family:inherit}.absolute-voice-provider-capabilities--error,.absolute-voice-provider-capabilities--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-capabilities__header,.absolute-voice-provider-capabilities__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-capabilities__eyebrow{color:#255f85;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-capabilities__label{font-size:24px;line-height:1}.absolute-voice-provider-capabilities__description,.absolute-voice-provider-capabilities__provider p,.absolute-voice-provider-capabilities__provider dt,.absolute-voice-provider-capabilities__empty{color:#405467}.absolute-voice-provider-capabilities__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-capabilities__provider{background:#fff;border:1px solid #d7e7f3;border-radius:16px;padding:14px}.absolute-voice-provider-capabilities__provider--selected,.absolute-voice-provider-capabilities__provider--healthy{border-color:#86efac}.absolute-voice-provider-capabilities__provider--degraded,.absolute-voice-provider-capabilities__provider--rate-limited,.absolute-voice-provider-capabilities__provider--suppressed,.absolute-voice-provider-capabilities__provider--unconfigured{border-color:#f2a7a7}.absolute-voice-provider-capabilities__provider p{margin:10px 0}.absolute-voice-provider-capabilities__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-capabilities__provider div{background:#f6fbff;border:1px solid #d7e7f3;border-radius:12px;padding:8px}.absolute-voice-provider-capabilities__provider dt{font-size:12px}.absolute-voice-provider-capabilities__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-capabilities__empty{margin:14px 0 0}.absolute-voice-provider-capabilities__error{color:#9f1239;font-weight:700}`;
|
|
@@ -4016,7 +5289,7 @@ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider
|
|
|
4016
5289
|
// src/client/providerContractsWidget.ts
|
|
4017
5290
|
var DEFAULT_TITLE7 = "Provider Contracts";
|
|
4018
5291
|
var DEFAULT_DESCRIPTION7 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
|
|
4019
|
-
var
|
|
5292
|
+
var escapeHtml11 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4020
5293
|
var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
4021
5294
|
var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
4022
5295
|
var contractDetail = (row) => {
|
|
@@ -4060,26 +5333,26 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
|
|
|
4060
5333
|
};
|
|
4061
5334
|
var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
|
|
4062
5335
|
const model = createVoiceProviderContractsViewModel(snapshot, options);
|
|
4063
|
-
const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${
|
|
5336
|
+
const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml11(row.status)}">
|
|
4064
5337
|
<header>
|
|
4065
|
-
<strong>${
|
|
4066
|
-
<span>${
|
|
5338
|
+
<strong>${escapeHtml11(row.label)}</strong>
|
|
5339
|
+
<span>${escapeHtml11(formatStatus3(row.status))}</span>
|
|
4067
5340
|
</header>
|
|
4068
|
-
<p>${
|
|
4069
|
-
${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${
|
|
5341
|
+
<p>${escapeHtml11(row.detail)}</p>
|
|
5342
|
+
${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml11(remediation.href)}">${escapeHtml11(remediation.label)}</a>` : `<strong>${escapeHtml11(remediation.label)}</strong>`}<span>${escapeHtml11(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
|
|
4070
5343
|
<dl>${row.rows.map((item) => `<div>
|
|
4071
|
-
<dt>${
|
|
4072
|
-
<dd>${
|
|
5344
|
+
<dt>${escapeHtml11(item.label)}</dt>
|
|
5345
|
+
<dd>${escapeHtml11(item.value)}</dd>
|
|
4073
5346
|
</div>`).join("")}</dl>
|
|
4074
5347
|
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
|
|
4075
|
-
return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${
|
|
5348
|
+
return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml11(model.status)}">
|
|
4076
5349
|
<header class="absolute-voice-provider-contracts__header">
|
|
4077
|
-
<span class="absolute-voice-provider-contracts__eyebrow">${
|
|
4078
|
-
<strong class="absolute-voice-provider-contracts__label">${
|
|
5350
|
+
<span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml11(model.title)}</span>
|
|
5351
|
+
<strong class="absolute-voice-provider-contracts__label">${escapeHtml11(model.label)}</strong>
|
|
4079
5352
|
</header>
|
|
4080
|
-
<p class="absolute-voice-provider-contracts__description">${
|
|
5353
|
+
<p class="absolute-voice-provider-contracts__description">${escapeHtml11(model.description)}</p>
|
|
4081
5354
|
${rows}
|
|
4082
|
-
${model.error ? `<p class="absolute-voice-provider-contracts__error">${
|
|
5355
|
+
${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml11(model.error)}</p>` : ""}
|
|
4083
5356
|
</section>`;
|
|
4084
5357
|
};
|
|
4085
5358
|
var getVoiceProviderContractsCSS = () => `.absolute-voice-provider-contracts{border:1px solid #b8dcc7;border-radius:20px;background:#f7fff9;color:#09140d;padding:18px;box-shadow:0 18px 40px rgba(21,83,45,.12);font-family:inherit}.absolute-voice-provider-contracts--error,.absolute-voice-provider-contracts--warning{border-color:#f2a7a7;background:#fff7f4}.absolute-voice-provider-contracts__header,.absolute-voice-provider-contracts__row header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-contracts__eyebrow{color:#166534;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-contracts__label{font-size:24px;line-height:1}.absolute-voice-provider-contracts__description,.absolute-voice-provider-contracts__row p,.absolute-voice-provider-contracts__row dt,.absolute-voice-provider-contracts__empty{color:#405448}.absolute-voice-provider-contracts__rows{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-contracts__row{background:#fff;border:1px solid #d6eadb;border-radius:16px;padding:14px}.absolute-voice-provider-contracts__row--pass{border-color:#86efac}.absolute-voice-provider-contracts__row--warn,.absolute-voice-provider-contracts__row--fail{border-color:#f2a7a7}.absolute-voice-provider-contracts__row p{margin:10px 0}.absolute-voice-provider-contracts__remediations{display:grid;gap:8px;list-style:none;margin:0 0 10px;padding:0}.absolute-voice-provider-contracts__remediations li{background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;display:grid;gap:3px;padding:8px}.absolute-voice-provider-contracts__remediations a,.absolute-voice-provider-contracts__remediations strong{color:#9a3412}.absolute-voice-provider-contracts__remediations span{color:#7c2d12}.absolute-voice-provider-contracts__row dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-contracts__row div{background:#f7fff9;border:1px solid #d6eadb;border-radius:12px;padding:8px}.absolute-voice-provider-contracts__row dt{font-size:12px}.absolute-voice-provider-contracts__row dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-contracts__error{color:#9f1239;font-weight:700}`;
|
|
@@ -4122,7 +5395,7 @@ var defineVoiceProviderContractsElement = (tagName = "absolute-voice-provider-co
|
|
|
4122
5395
|
// src/client/turnQualityWidget.ts
|
|
4123
5396
|
var DEFAULT_TITLE8 = "Turn Quality";
|
|
4124
5397
|
var DEFAULT_DESCRIPTION8 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
|
|
4125
|
-
var
|
|
5398
|
+
var escapeHtml12 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4126
5399
|
var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
|
|
4127
5400
|
var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
|
|
4128
5401
|
var getTurnDetail = (turn) => {
|
|
@@ -4172,25 +5445,25 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
|
|
|
4172
5445
|
};
|
|
4173
5446
|
var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
|
|
4174
5447
|
const model = createVoiceTurnQualityViewModel(snapshot, options);
|
|
4175
|
-
const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${
|
|
5448
|
+
const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml12(turn.status)}">
|
|
4176
5449
|
<header>
|
|
4177
|
-
<strong>${
|
|
4178
|
-
<span>${
|
|
5450
|
+
<strong>${escapeHtml12(turn.label)}</strong>
|
|
5451
|
+
<span>${escapeHtml12(turn.status)}</span>
|
|
4179
5452
|
</header>
|
|
4180
|
-
<p>${
|
|
5453
|
+
<p>${escapeHtml12(turn.detail)}</p>
|
|
4181
5454
|
<dl>${turn.rows.map((row) => `<div>
|
|
4182
|
-
<dt>${
|
|
4183
|
-
<dd>${
|
|
5455
|
+
<dt>${escapeHtml12(row.label)}</dt>
|
|
5456
|
+
<dd>${escapeHtml12(row.value)}</dd>
|
|
4184
5457
|
</div>`).join("")}</dl>
|
|
4185
5458
|
</article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
|
|
4186
|
-
return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${
|
|
5459
|
+
return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml12(model.status)}">
|
|
4187
5460
|
<header class="absolute-voice-turn-quality__header">
|
|
4188
|
-
<span class="absolute-voice-turn-quality__eyebrow">${
|
|
4189
|
-
<strong class="absolute-voice-turn-quality__label">${
|
|
5461
|
+
<span class="absolute-voice-turn-quality__eyebrow">${escapeHtml12(model.title)}</span>
|
|
5462
|
+
<strong class="absolute-voice-turn-quality__label">${escapeHtml12(model.label)}</strong>
|
|
4190
5463
|
</header>
|
|
4191
|
-
<p class="absolute-voice-turn-quality__description">${
|
|
5464
|
+
<p class="absolute-voice-turn-quality__description">${escapeHtml12(model.description)}</p>
|
|
4192
5465
|
${turns}
|
|
4193
|
-
${model.error ? `<p class="absolute-voice-turn-quality__error">${
|
|
5466
|
+
${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml12(model.error)}</p>` : ""}
|
|
4194
5467
|
</section>`;
|
|
4195
5468
|
};
|
|
4196
5469
|
var getVoiceTurnQualityCSS = () => `.absolute-voice-turn-quality{border:1px solid #e4d1a3;border-radius:20px;background:#fff9eb;color:#17120a;padding:18px;box-shadow:0 18px 40px rgba(73,48,14,.12);font-family:inherit}.absolute-voice-turn-quality--error,.absolute-voice-turn-quality--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-turn-quality__header,.absolute-voice-turn-quality__turn header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-turn-quality__eyebrow{color:#8a5a0a;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-turn-quality__label{font-size:24px;line-height:1}.absolute-voice-turn-quality__description,.absolute-voice-turn-quality__turn p,.absolute-voice-turn-quality__turn dt,.absolute-voice-turn-quality__empty{color:#5a4930}.absolute-voice-turn-quality__turns{display:grid;gap:12px;margin-top:14px}.absolute-voice-turn-quality__turn{background:#fff;border:1px solid #f0dfba;border-radius:16px;padding:14px}.absolute-voice-turn-quality__turn--pass{border-color:#86efac}.absolute-voice-turn-quality__turn--warn,.absolute-voice-turn-quality__turn--unknown{border-color:#fbbf24}.absolute-voice-turn-quality__turn--fail{border-color:#f2a7a7}.absolute-voice-turn-quality__turn p{margin:10px 0}.absolute-voice-turn-quality__turn dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-turn-quality__turn div{background:#fff9eb;border:1px solid #f0dfba;border-radius:12px;padding:8px}.absolute-voice-turn-quality__turn dt{font-size:12px}.absolute-voice-turn-quality__turn dd{font-weight:800;margin:4px 0 0}.absolute-voice-turn-quality__empty{margin:14px 0 0}.absolute-voice-turn-quality__error{color:#9f1239;font-weight:700}`;
|
|
@@ -4234,7 +5507,7 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
|
|
|
4234
5507
|
var DEFAULT_TITLE9 = "Turn Latency";
|
|
4235
5508
|
var DEFAULT_DESCRIPTION9 = "Per-turn timing from first transcript to commit and assistant response start.";
|
|
4236
5509
|
var DEFAULT_PROOF_LABEL = "Run latency proof";
|
|
4237
|
-
var
|
|
5510
|
+
var escapeHtml13 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4238
5511
|
var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
4239
5512
|
var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
4240
5513
|
const turns = (snapshot.report?.turns ?? []).map((turn) => ({
|
|
@@ -4262,25 +5535,25 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
|
4262
5535
|
};
|
|
4263
5536
|
var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
|
|
4264
5537
|
const model = createVoiceTurnLatencyViewModel(snapshot, options);
|
|
4265
|
-
const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${
|
|
5538
|
+
const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${escapeHtml13(turn.status)}">
|
|
4266
5539
|
<header>
|
|
4267
|
-
<strong>${
|
|
4268
|
-
<span>${
|
|
5540
|
+
<strong>${escapeHtml13(turn.label)}</strong>
|
|
5541
|
+
<span>${escapeHtml13(turn.status)}</span>
|
|
4269
5542
|
</header>
|
|
4270
5543
|
<dl>${turn.rows.map((row) => `<div>
|
|
4271
|
-
<dt>${
|
|
4272
|
-
<dd>${
|
|
5544
|
+
<dt>${escapeHtml13(row.label)}</dt>
|
|
5545
|
+
<dd>${escapeHtml13(row.value)}</dd>
|
|
4273
5546
|
</div>`).join("")}</dl>
|
|
4274
5547
|
</article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
|
|
4275
|
-
return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${
|
|
5548
|
+
return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml13(model.status)}">
|
|
4276
5549
|
<header class="absolute-voice-turn-latency__header">
|
|
4277
|
-
<span class="absolute-voice-turn-latency__eyebrow">${
|
|
4278
|
-
<strong class="absolute-voice-turn-latency__label">${
|
|
5550
|
+
<span class="absolute-voice-turn-latency__eyebrow">${escapeHtml13(model.title)}</span>
|
|
5551
|
+
<strong class="absolute-voice-turn-latency__label">${escapeHtml13(model.label)}</strong>
|
|
4279
5552
|
</header>
|
|
4280
|
-
<p class="absolute-voice-turn-latency__description">${
|
|
4281
|
-
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${
|
|
5553
|
+
<p class="absolute-voice-turn-latency__description">${escapeHtml13(model.description)}</p>
|
|
5554
|
+
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml13(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
|
|
4282
5555
|
${turns}
|
|
4283
|
-
${model.error ? `<p class="absolute-voice-turn-latency__error">${
|
|
5556
|
+
${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml13(model.error)}</p>` : ""}
|
|
4284
5557
|
</section>`;
|
|
4285
5558
|
};
|
|
4286
5559
|
var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
|
|
@@ -4332,7 +5605,7 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
|
|
|
4332
5605
|
// src/client/traceTimelineWidget.ts
|
|
4333
5606
|
var DEFAULT_TITLE10 = "Voice Traces";
|
|
4334
5607
|
var DEFAULT_DESCRIPTION10 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
|
|
4335
|
-
var
|
|
5608
|
+
var escapeHtml14 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4336
5609
|
var formatMs2 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
|
|
4337
5610
|
var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
|
|
4338
5611
|
var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
|
|
@@ -4362,27 +5635,27 @@ var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
|
|
|
4362
5635
|
const model = createVoiceTraceTimelineViewModel(snapshot, options);
|
|
4363
5636
|
const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => {
|
|
4364
5637
|
const supportLinks = [
|
|
4365
|
-
`<a href="${
|
|
4366
|
-
session.operationsRecordHref ? `<a href="${
|
|
4367
|
-
session.incidentBundleHref ? `<a href="${
|
|
5638
|
+
`<a href="${escapeHtml14(session.detailHref)}">Open timeline</a>`,
|
|
5639
|
+
session.operationsRecordHref ? `<a href="${escapeHtml14(session.operationsRecordHref)}">Open operations record</a>` : undefined,
|
|
5640
|
+
session.incidentBundleHref ? `<a href="${escapeHtml14(session.incidentBundleHref)}">Export incident bundle</a>` : undefined
|
|
4368
5641
|
].filter(Boolean).join("");
|
|
4369
|
-
return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${
|
|
5642
|
+
return `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml14(session.status)}">
|
|
4370
5643
|
<header>
|
|
4371
|
-
<strong>${
|
|
4372
|
-
<span>${
|
|
5644
|
+
<strong>${escapeHtml14(session.sessionId)}</strong>
|
|
5645
|
+
<span>${escapeHtml14(session.status)}</span>
|
|
4373
5646
|
</header>
|
|
4374
|
-
<p>${
|
|
5647
|
+
<p>${escapeHtml14(session.label)} \xB7 ${escapeHtml14(session.durationLabel)} \xB7 ${escapeHtml14(session.providerLabel)}</p>
|
|
4375
5648
|
<p class="absolute-voice-trace-timeline__actions">${supportLinks}</p>
|
|
4376
5649
|
</article>`;
|
|
4377
5650
|
}).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
|
|
4378
|
-
return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${
|
|
5651
|
+
return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml14(model.status)}">
|
|
4379
5652
|
<header class="absolute-voice-trace-timeline__header">
|
|
4380
|
-
<span class="absolute-voice-trace-timeline__eyebrow">${
|
|
4381
|
-
<strong class="absolute-voice-trace-timeline__label">${
|
|
5653
|
+
<span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml14(model.title)}</span>
|
|
5654
|
+
<strong class="absolute-voice-trace-timeline__label">${escapeHtml14(model.label)}</strong>
|
|
4382
5655
|
</header>
|
|
4383
|
-
<p class="absolute-voice-trace-timeline__description">${
|
|
5656
|
+
<p class="absolute-voice-trace-timeline__description">${escapeHtml14(model.description)}</p>
|
|
4384
5657
|
${sessions}
|
|
4385
|
-
${model.error ? `<p class="absolute-voice-trace-timeline__error">${
|
|
5658
|
+
${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml14(model.error)}</p>` : ""}
|
|
4386
5659
|
</section>`;
|
|
4387
5660
|
};
|
|
4388
5661
|
var getVoiceTraceTimelineCSS = () => `.absolute-voice-trace-timeline{border:1px solid #bad7d3;border-radius:20px;background:#f3fffb;color:#09201c;padding:18px;box-shadow:0 18px 40px rgba(9,32,28,.12);font-family:inherit}.absolute-voice-trace-timeline--error,.absolute-voice-trace-timeline--failed{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-trace-timeline--warning{border-color:#fbbf24;background:#fffaf0}.absolute-voice-trace-timeline__header,.absolute-voice-trace-timeline__session header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-trace-timeline__eyebrow{color:#17665b;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-trace-timeline__label{font-size:24px;line-height:1}.absolute-voice-trace-timeline__description,.absolute-voice-trace-timeline__session p,.absolute-voice-trace-timeline__empty{color:#35544f}.absolute-voice-trace-timeline__sessions{display:grid;gap:12px;margin-top:14px}.absolute-voice-trace-timeline__session{background:#fff;border:1px solid #cfe7e2;border-radius:16px;padding:14px}.absolute-voice-trace-timeline__session--failed{border-color:#f2a7a7}.absolute-voice-trace-timeline__session--warning{border-color:#fbbf24}.absolute-voice-trace-timeline__session p{margin:10px 0}.absolute-voice-trace-timeline__actions{display:flex;flex-wrap:wrap;gap:10px}.absolute-voice-trace-timeline__session a{color:#0f766e;font-weight:800}.absolute-voice-trace-timeline__empty{margin:14px 0 0}.absolute-voice-trace-timeline__error{color:#9f1239;font-weight:700}`;
|
|
@@ -4521,8 +5794,10 @@ export {
|
|
|
4521
5794
|
renderVoiceOpsStatusHTML,
|
|
4522
5795
|
renderVoiceOpsActionHistoryWidgetHTML,
|
|
4523
5796
|
renderVoiceOpsActionCenterHTML,
|
|
5797
|
+
renderVoiceLiveOpsHTML,
|
|
4524
5798
|
renderVoiceDeliveryRuntimeHTML,
|
|
4525
5799
|
recordVoiceOpsActionResult,
|
|
5800
|
+
postVoiceLiveOpsAction,
|
|
4526
5801
|
mountVoiceTurnQuality,
|
|
4527
5802
|
mountVoiceTurnLatency,
|
|
4528
5803
|
mountVoiceTraceTimeline,
|
|
@@ -4534,6 +5809,7 @@ export {
|
|
|
4534
5809
|
mountVoiceOpsStatus,
|
|
4535
5810
|
mountVoiceOpsActionHistory,
|
|
4536
5811
|
mountVoiceOpsActionCenter,
|
|
5812
|
+
mountVoiceLiveOps,
|
|
4537
5813
|
mountVoiceDeliveryRuntime,
|
|
4538
5814
|
getVoiceTurnQualityCSS,
|
|
4539
5815
|
getVoiceTraceTimelineCSS,
|
|
@@ -4545,6 +5821,7 @@ export {
|
|
|
4545
5821
|
getVoiceOpsStatusCSS,
|
|
4546
5822
|
getVoiceOpsActionHistoryCSS,
|
|
4547
5823
|
getVoiceOpsActionCenterCSS,
|
|
5824
|
+
getVoiceLiveOpsCSS,
|
|
4548
5825
|
getVoiceDeliveryRuntimeCSS,
|
|
4549
5826
|
fetchVoiceWorkflowStatus,
|
|
4550
5827
|
fetchVoiceTurnQuality,
|
|
@@ -4568,6 +5845,7 @@ export {
|
|
|
4568
5845
|
defineVoiceProviderCapabilitiesElement,
|
|
4569
5846
|
defineVoiceOpsStatusElement,
|
|
4570
5847
|
defineVoiceOpsActionCenterElement,
|
|
5848
|
+
defineVoiceLiveOpsElement,
|
|
4571
5849
|
defineVoiceDeliveryRuntimeElement,
|
|
4572
5850
|
decodeVoiceAudioChunk,
|
|
4573
5851
|
createVoiceWorkflowStatusStore,
|
|
@@ -4595,6 +5873,8 @@ export {
|
|
|
4595
5873
|
createVoiceOpsActionCenterStore,
|
|
4596
5874
|
createVoiceOpsActionCenterActions,
|
|
4597
5875
|
createVoiceLiveTurnLatencyMonitor,
|
|
5876
|
+
createVoiceLiveOpsStore,
|
|
5877
|
+
createVoiceLiveOpsInput,
|
|
4598
5878
|
createVoiceDuplexController,
|
|
4599
5879
|
createVoiceDeliveryRuntimeViewModel,
|
|
4600
5880
|
createVoiceDeliveryRuntimeStore,
|