@absolutejs/voice 0.0.22-beta.167 → 0.0.22-beta.168

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.
@@ -3156,6 +3156,80 @@ var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities",
3156
3156
  }
3157
3157
  };
3158
3158
  };
3159
+ // src/client/providerContracts.ts
3160
+ var fetchVoiceProviderContracts = async (path = "/api/provider-contracts", options = {}) => {
3161
+ const fetchImpl = options.fetch ?? globalThis.fetch;
3162
+ const response = await fetchImpl(path);
3163
+ if (!response.ok) {
3164
+ throw new Error(`Voice provider contracts failed: HTTP ${response.status}`);
3165
+ }
3166
+ return await response.json();
3167
+ };
3168
+ var createVoiceProviderContractsStore = (path = "/api/provider-contracts", options = {}) => {
3169
+ const listeners = new Set;
3170
+ let closed = false;
3171
+ let timer;
3172
+ let snapshot = {
3173
+ error: null,
3174
+ isLoading: false
3175
+ };
3176
+ const emit = () => {
3177
+ for (const listener of listeners) {
3178
+ listener();
3179
+ }
3180
+ };
3181
+ const refresh = async () => {
3182
+ if (closed) {
3183
+ return snapshot.report;
3184
+ }
3185
+ snapshot = { ...snapshot, error: null, isLoading: true };
3186
+ emit();
3187
+ try {
3188
+ const report = await fetchVoiceProviderContracts(path, options);
3189
+ snapshot = {
3190
+ error: null,
3191
+ isLoading: false,
3192
+ report,
3193
+ updatedAt: Date.now()
3194
+ };
3195
+ emit();
3196
+ return report;
3197
+ } catch (error) {
3198
+ snapshot = {
3199
+ ...snapshot,
3200
+ error: error instanceof Error ? error.message : String(error),
3201
+ isLoading: false
3202
+ };
3203
+ emit();
3204
+ throw error;
3205
+ }
3206
+ };
3207
+ const close = () => {
3208
+ closed = true;
3209
+ if (timer) {
3210
+ clearInterval(timer);
3211
+ timer = undefined;
3212
+ }
3213
+ listeners.clear();
3214
+ };
3215
+ if (options.intervalMs && options.intervalMs > 0) {
3216
+ timer = setInterval(() => {
3217
+ refresh().catch(() => {});
3218
+ }, options.intervalMs);
3219
+ }
3220
+ return {
3221
+ close,
3222
+ getServerSnapshot: () => snapshot,
3223
+ getSnapshot: () => snapshot,
3224
+ refresh,
3225
+ subscribe: (listener) => {
3226
+ listeners.add(listener);
3227
+ return () => {
3228
+ listeners.delete(listener);
3229
+ };
3230
+ }
3231
+ };
3232
+ };
3159
3233
  // src/client/turnQuality.ts
3160
3234
  var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
3161
3235
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -3939,10 +4013,110 @@ var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider
3939
4013
  }
3940
4014
  });
3941
4015
  };
3942
- // src/client/turnQualityWidget.ts
3943
- var DEFAULT_TITLE7 = "Turn Quality";
3944
- var DEFAULT_DESCRIPTION7 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
4016
+ // src/client/providerContractsWidget.ts
4017
+ var DEFAULT_TITLE7 = "Provider Contracts";
4018
+ var DEFAULT_DESCRIPTION7 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
3945
4019
  var escapeHtml9 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4020
+ var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
4021
+ var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
4022
+ var contractDetail = (row) => {
4023
+ const failing = row.checks.filter((check) => check.status !== "pass");
4024
+ if (failing.length === 0) {
4025
+ return "Provider contract is production-ready.";
4026
+ }
4027
+ return failing.map((check) => `${check.label}: ${check.detail ?? check.status}`).join(" ");
4028
+ };
4029
+ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
4030
+ const rows = (snapshot.report?.rows ?? []).map((row) => ({
4031
+ ...row,
4032
+ detail: contractDetail(row),
4033
+ label: `${formatProvider3(row.provider)} ${row.kind.toUpperCase()}`,
4034
+ rows: [
4035
+ { label: "Status", value: formatStatus3(row.status) },
4036
+ { label: "Selected", value: row.selected ? "Yes" : "No" },
4037
+ { label: "Configured", value: row.configured ? "Yes" : "No" },
4038
+ {
4039
+ label: "Checks",
4040
+ value: row.checks.map((check) => `${check.label}: ${formatStatus3(check.status)}`).join(", ")
4041
+ }
4042
+ ]
4043
+ }));
4044
+ const warningCount = snapshot.report ? snapshot.report.failed + snapshot.report.warned : rows.filter((row) => row.status !== "pass").length;
4045
+ return {
4046
+ description: options.description ?? DEFAULT_DESCRIPTION7,
4047
+ error: snapshot.error,
4048
+ isLoading: snapshot.isLoading,
4049
+ label: snapshot.error ? "Unavailable" : rows.length ? warningCount > 0 ? `${warningCount} needs attention` : `${rows.length} passing` : snapshot.isLoading ? "Checking" : "No contracts",
4050
+ rows,
4051
+ status: snapshot.error ? "error" : rows.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
4052
+ title: options.title ?? DEFAULT_TITLE7,
4053
+ updatedAt: snapshot.updatedAt
4054
+ };
4055
+ };
4056
+ var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
4057
+ const model = createVoiceProviderContractsViewModel(snapshot, options);
4058
+ 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--${escapeHtml9(row.status)}">
4059
+ <header>
4060
+ <strong>${escapeHtml9(row.label)}</strong>
4061
+ <span>${escapeHtml9(formatStatus3(row.status))}</span>
4062
+ </header>
4063
+ <p>${escapeHtml9(row.detail)}</p>
4064
+ <dl>${row.rows.map((item) => `<div>
4065
+ <dt>${escapeHtml9(item.label)}</dt>
4066
+ <dd>${escapeHtml9(item.value)}</dd>
4067
+ </div>`).join("")}</dl>
4068
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-contracts__empty">Configure provider contracts to see production coverage.</p>';
4069
+ return `<section class="absolute-voice-provider-contracts absolute-voice-provider-contracts--${escapeHtml9(model.status)}">
4070
+ <header class="absolute-voice-provider-contracts__header">
4071
+ <span class="absolute-voice-provider-contracts__eyebrow">${escapeHtml9(model.title)}</span>
4072
+ <strong class="absolute-voice-provider-contracts__label">${escapeHtml9(model.label)}</strong>
4073
+ </header>
4074
+ <p class="absolute-voice-provider-contracts__description">${escapeHtml9(model.description)}</p>
4075
+ ${rows}
4076
+ ${model.error ? `<p class="absolute-voice-provider-contracts__error">${escapeHtml9(model.error)}</p>` : ""}
4077
+ </section>`;
4078
+ };
4079
+ 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__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}`;
4080
+ var mountVoiceProviderContracts = (element, path = "/api/provider-contracts", options = {}) => {
4081
+ const store = createVoiceProviderContractsStore(path, options);
4082
+ const render = () => {
4083
+ element.innerHTML = renderVoiceProviderContractsHTML(store.getSnapshot(), options);
4084
+ };
4085
+ const unsubscribe = store.subscribe(render);
4086
+ render();
4087
+ store.refresh().catch(() => {});
4088
+ return {
4089
+ close: () => {
4090
+ unsubscribe();
4091
+ store.close();
4092
+ },
4093
+ refresh: store.refresh
4094
+ };
4095
+ };
4096
+ var defineVoiceProviderContractsElement = (tagName = "absolute-voice-provider-contracts") => {
4097
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
4098
+ return;
4099
+ }
4100
+ customElements.define(tagName, class AbsoluteVoiceProviderContractsElement extends HTMLElement {
4101
+ mounted;
4102
+ connectedCallback() {
4103
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
4104
+ this.mounted = mountVoiceProviderContracts(this, this.getAttribute("path") ?? "/api/provider-contracts", {
4105
+ description: this.getAttribute("description") ?? undefined,
4106
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
4107
+ title: this.getAttribute("title") ?? undefined
4108
+ });
4109
+ }
4110
+ disconnectedCallback() {
4111
+ this.mounted?.close();
4112
+ this.mounted = undefined;
4113
+ }
4114
+ });
4115
+ };
4116
+ // src/client/turnQualityWidget.ts
4117
+ var DEFAULT_TITLE8 = "Turn Quality";
4118
+ var DEFAULT_DESCRIPTION8 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
4119
+ var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3946
4120
  var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
3947
4121
  var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
3948
4122
  var getTurnDetail = (turn) => {
@@ -3980,37 +4154,37 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
3980
4154
  const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3981
4155
  const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3982
4156
  return {
3983
- description: options.description ?? DEFAULT_DESCRIPTION7,
4157
+ description: options.description ?? DEFAULT_DESCRIPTION8,
3984
4158
  error: snapshot.error,
3985
4159
  isLoading: snapshot.isLoading,
3986
4160
  label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
3987
4161
  status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3988
- title: options.title ?? DEFAULT_TITLE7,
4162
+ title: options.title ?? DEFAULT_TITLE8,
3989
4163
  turns,
3990
4164
  updatedAt: snapshot.updatedAt
3991
4165
  };
3992
4166
  };
3993
4167
  var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
3994
4168
  const model = createVoiceTurnQualityViewModel(snapshot, options);
3995
- 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--${escapeHtml9(turn.status)}">
4169
+ 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--${escapeHtml10(turn.status)}">
3996
4170
  <header>
3997
- <strong>${escapeHtml9(turn.label)}</strong>
3998
- <span>${escapeHtml9(turn.status)}</span>
4171
+ <strong>${escapeHtml10(turn.label)}</strong>
4172
+ <span>${escapeHtml10(turn.status)}</span>
3999
4173
  </header>
4000
- <p>${escapeHtml9(turn.detail)}</p>
4174
+ <p>${escapeHtml10(turn.detail)}</p>
4001
4175
  <dl>${turn.rows.map((row) => `<div>
4002
- <dt>${escapeHtml9(row.label)}</dt>
4003
- <dd>${escapeHtml9(row.value)}</dd>
4176
+ <dt>${escapeHtml10(row.label)}</dt>
4177
+ <dd>${escapeHtml10(row.value)}</dd>
4004
4178
  </div>`).join("")}</dl>
4005
4179
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
4006
- return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml9(model.status)}">
4180
+ return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml10(model.status)}">
4007
4181
  <header class="absolute-voice-turn-quality__header">
4008
- <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml9(model.title)}</span>
4009
- <strong class="absolute-voice-turn-quality__label">${escapeHtml9(model.label)}</strong>
4182
+ <span class="absolute-voice-turn-quality__eyebrow">${escapeHtml10(model.title)}</span>
4183
+ <strong class="absolute-voice-turn-quality__label">${escapeHtml10(model.label)}</strong>
4010
4184
  </header>
4011
- <p class="absolute-voice-turn-quality__description">${escapeHtml9(model.description)}</p>
4185
+ <p class="absolute-voice-turn-quality__description">${escapeHtml10(model.description)}</p>
4012
4186
  ${turns}
4013
- ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml9(model.error)}</p>` : ""}
4187
+ ${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml10(model.error)}</p>` : ""}
4014
4188
  </section>`;
4015
4189
  };
4016
4190
  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}`;
@@ -4051,10 +4225,10 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
4051
4225
  });
4052
4226
  };
4053
4227
  // src/client/turnLatencyWidget.ts
4054
- var DEFAULT_TITLE8 = "Turn Latency";
4055
- var DEFAULT_DESCRIPTION8 = "Per-turn timing from first transcript to commit and assistant response start.";
4228
+ var DEFAULT_TITLE9 = "Turn Latency";
4229
+ var DEFAULT_DESCRIPTION9 = "Per-turn timing from first transcript to commit and assistant response start.";
4056
4230
  var DEFAULT_PROOF_LABEL = "Run latency proof";
4057
- var escapeHtml10 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4231
+ var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4058
4232
  var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
4059
4233
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
4060
4234
  const turns = (snapshot.report?.turns ?? []).map((turn) => ({
@@ -4068,39 +4242,39 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
4068
4242
  const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
4069
4243
  const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
4070
4244
  return {
4071
- description: options.description ?? DEFAULT_DESCRIPTION8,
4245
+ description: options.description ?? DEFAULT_DESCRIPTION9,
4072
4246
  error: snapshot.error,
4073
4247
  isLoading: snapshot.isLoading,
4074
4248
  label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
4075
4249
  proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
4076
4250
  showProofAction: Boolean(options.proofPath),
4077
4251
  status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
4078
- title: options.title ?? DEFAULT_TITLE8,
4252
+ title: options.title ?? DEFAULT_TITLE9,
4079
4253
  turns,
4080
4254
  updatedAt: snapshot.updatedAt
4081
4255
  };
4082
4256
  };
4083
4257
  var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
4084
4258
  const model = createVoiceTurnLatencyViewModel(snapshot, options);
4085
- 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--${escapeHtml10(turn.status)}">
4259
+ 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--${escapeHtml11(turn.status)}">
4086
4260
  <header>
4087
- <strong>${escapeHtml10(turn.label)}</strong>
4088
- <span>${escapeHtml10(turn.status)}</span>
4261
+ <strong>${escapeHtml11(turn.label)}</strong>
4262
+ <span>${escapeHtml11(turn.status)}</span>
4089
4263
  </header>
4090
4264
  <dl>${turn.rows.map((row) => `<div>
4091
- <dt>${escapeHtml10(row.label)}</dt>
4092
- <dd>${escapeHtml10(row.value)}</dd>
4265
+ <dt>${escapeHtml11(row.label)}</dt>
4266
+ <dd>${escapeHtml11(row.value)}</dd>
4093
4267
  </div>`).join("")}</dl>
4094
4268
  </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
4095
- return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml10(model.status)}">
4269
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml11(model.status)}">
4096
4270
  <header class="absolute-voice-turn-latency__header">
4097
- <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml10(model.title)}</span>
4098
- <strong class="absolute-voice-turn-latency__label">${escapeHtml10(model.label)}</strong>
4271
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml11(model.title)}</span>
4272
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml11(model.label)}</strong>
4099
4273
  </header>
4100
- <p class="absolute-voice-turn-latency__description">${escapeHtml10(model.description)}</p>
4101
- ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml10(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
4274
+ <p class="absolute-voice-turn-latency__description">${escapeHtml11(model.description)}</p>
4275
+ ${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml11(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
4102
4276
  ${turns}
4103
- ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml10(model.error)}</p>` : ""}
4277
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml11(model.error)}</p>` : ""}
4104
4278
  </section>`;
4105
4279
  };
4106
4280
  var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
@@ -4150,9 +4324,9 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
4150
4324
  });
4151
4325
  };
4152
4326
  // src/client/traceTimelineWidget.ts
4153
- var DEFAULT_TITLE9 = "Voice Traces";
4154
- var DEFAULT_DESCRIPTION9 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
4155
- var escapeHtml11 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4327
+ var DEFAULT_TITLE10 = "Voice Traces";
4328
+ var DEFAULT_DESCRIPTION10 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
4329
+ var escapeHtml12 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
4156
4330
  var formatMs2 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
4157
4331
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
4158
4332
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
@@ -4166,34 +4340,34 @@ var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
4166
4340
  const failed = sessions.filter((session) => session.status === "failed").length;
4167
4341
  const warnings = sessions.filter((session) => session.status === "warning").length;
4168
4342
  return {
4169
- description: options.description ?? DEFAULT_DESCRIPTION9,
4343
+ description: options.description ?? DEFAULT_DESCRIPTION10,
4170
4344
  error: snapshot.error,
4171
4345
  isLoading: snapshot.isLoading,
4172
4346
  label: snapshot.error ? "Unavailable" : failed > 0 ? `${failed} failed` : warnings > 0 ? `${warnings} warning` : sessions.length ? `${sessions.length} recent` : snapshot.isLoading ? "Checking" : "No traces yet",
4173
4347
  sessions,
4174
4348
  status: snapshot.error ? "error" : failed > 0 ? "failed" : warnings > 0 ? "warning" : sessions.length ? "ready" : snapshot.isLoading ? "loading" : "empty",
4175
- title: options.title ?? DEFAULT_TITLE9,
4349
+ title: options.title ?? DEFAULT_TITLE10,
4176
4350
  updatedAt: snapshot.updatedAt
4177
4351
  };
4178
4352
  };
4179
4353
  var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
4180
4354
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
4181
- const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml11(session.status)}">
4355
+ const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml12(session.status)}">
4182
4356
  <header>
4183
- <strong>${escapeHtml11(session.sessionId)}</strong>
4184
- <span>${escapeHtml11(session.status)}</span>
4357
+ <strong>${escapeHtml12(session.sessionId)}</strong>
4358
+ <span>${escapeHtml12(session.status)}</span>
4185
4359
  </header>
4186
- <p>${escapeHtml11(session.label)} \xB7 ${escapeHtml11(session.durationLabel)} \xB7 ${escapeHtml11(session.providerLabel)}</p>
4187
- <a href="${escapeHtml11(session.detailHref)}">Open timeline</a>
4360
+ <p>${escapeHtml12(session.label)} \xB7 ${escapeHtml12(session.durationLabel)} \xB7 ${escapeHtml12(session.providerLabel)}</p>
4361
+ <a href="${escapeHtml12(session.detailHref)}">Open timeline</a>
4188
4362
  </article>`).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
4189
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml11(model.status)}">
4363
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml12(model.status)}">
4190
4364
  <header class="absolute-voice-trace-timeline__header">
4191
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml11(model.title)}</span>
4192
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml11(model.label)}</strong>
4365
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml12(model.title)}</span>
4366
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml12(model.label)}</strong>
4193
4367
  </header>
4194
- <p class="absolute-voice-trace-timeline__description">${escapeHtml11(model.description)}</p>
4368
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml12(model.description)}</p>
4195
4369
  ${sessions}
4196
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml11(model.error)}</p>` : ""}
4370
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml12(model.error)}</p>` : ""}
4197
4371
  </section>`;
4198
4372
  };
4199
4373
  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__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}`;
@@ -4325,6 +4499,7 @@ export {
4325
4499
  renderVoiceRoutingStatusHTML,
4326
4500
  renderVoiceProviderStatusHTML,
4327
4501
  renderVoiceProviderSimulationControlsHTML,
4502
+ renderVoiceProviderContractsHTML,
4328
4503
  renderVoiceProviderCapabilitiesHTML,
4329
4504
  renderVoiceOpsStatusHTML,
4330
4505
  renderVoiceOpsActionHistoryWidgetHTML,
@@ -4337,6 +4512,7 @@ export {
4337
4512
  mountVoiceRoutingStatus,
4338
4513
  mountVoiceProviderStatus,
4339
4514
  mountVoiceProviderSimulationControls,
4515
+ mountVoiceProviderContracts,
4340
4516
  mountVoiceProviderCapabilities,
4341
4517
  mountVoiceOpsStatus,
4342
4518
  mountVoiceOpsActionHistory,
@@ -4346,6 +4522,7 @@ export {
4346
4522
  getVoiceTraceTimelineCSS,
4347
4523
  getVoiceRoutingStatusCSS,
4348
4524
  getVoiceProviderStatusCSS,
4525
+ getVoiceProviderContractsCSS,
4349
4526
  getVoiceProviderCapabilitiesCSS,
4350
4527
  getVoiceOpsStatusLabel,
4351
4528
  getVoiceOpsStatusCSS,
@@ -4358,6 +4535,7 @@ export {
4358
4535
  fetchVoiceTraceTimeline,
4359
4536
  fetchVoiceRoutingStatus,
4360
4537
  fetchVoiceProviderStatus,
4538
+ fetchVoiceProviderContracts,
4361
4539
  fetchVoiceProviderCapabilities,
4362
4540
  fetchVoiceOpsStatus,
4363
4541
  fetchVoiceOpsActionHistory,
@@ -4369,6 +4547,7 @@ export {
4369
4547
  defineVoiceRoutingStatusElement,
4370
4548
  defineVoiceProviderStatusElement,
4371
4549
  defineVoiceProviderSimulationControlsElement,
4550
+ defineVoiceProviderContractsElement,
4372
4551
  defineVoiceProviderCapabilitiesElement,
4373
4552
  defineVoiceOpsStatusElement,
4374
4553
  defineVoiceOpsActionCenterElement,
@@ -4388,6 +4567,8 @@ export {
4388
4567
  createVoiceProviderStatusStore,
4389
4568
  createVoiceProviderSimulationControlsViewModel,
4390
4569
  createVoiceProviderSimulationControlsStore,
4570
+ createVoiceProviderContractsViewModel,
4571
+ createVoiceProviderContractsStore,
4391
4572
  createVoiceProviderCapabilitiesViewModel,
4392
4573
  createVoiceProviderCapabilitiesStore,
4393
4574
  createVoiceOpsStatusViewModel,
@@ -0,0 +1,19 @@
1
+ import type { VoiceProviderContractMatrixReport } from '../providerStackRecommendations';
2
+ export type VoiceProviderContractsClientOptions = {
3
+ fetch?: typeof fetch;
4
+ intervalMs?: number;
5
+ };
6
+ export type VoiceProviderContractsSnapshot<TProvider extends string = string> = {
7
+ error: string | null;
8
+ isLoading: boolean;
9
+ report?: VoiceProviderContractMatrixReport<TProvider>;
10
+ updatedAt?: number;
11
+ };
12
+ export declare const fetchVoiceProviderContracts: <TProvider extends string = string>(path?: string, options?: Pick<VoiceProviderContractsClientOptions, "fetch">) => Promise<VoiceProviderContractMatrixReport<TProvider>>;
13
+ export declare const createVoiceProviderContractsStore: <TProvider extends string = string>(path?: string, options?: VoiceProviderContractsClientOptions) => {
14
+ close: () => void;
15
+ getServerSnapshot: () => VoiceProviderContractsSnapshot<TProvider>;
16
+ getSnapshot: () => VoiceProviderContractsSnapshot<TProvider>;
17
+ refresh: () => Promise<VoiceProviderContractMatrixReport<TProvider> | undefined>;
18
+ subscribe: (listener: () => void) => () => void;
19
+ };
@@ -0,0 +1,32 @@
1
+ import type { VoiceProviderContractMatrixRow } from '../providerStackRecommendations';
2
+ import { type VoiceProviderContractsClientOptions, type VoiceProviderContractsSnapshot } from './providerContracts';
3
+ export type VoiceProviderContractRowView<TProvider extends string = string> = VoiceProviderContractMatrixRow<TProvider> & {
4
+ detail: string;
5
+ label: string;
6
+ rows: Array<{
7
+ label: string;
8
+ value: string;
9
+ }>;
10
+ };
11
+ export type VoiceProviderContractsViewModel<TProvider extends string = string> = {
12
+ description: string;
13
+ error: string | null;
14
+ isLoading: boolean;
15
+ label: string;
16
+ rows: VoiceProviderContractRowView<TProvider>[];
17
+ status: 'empty' | 'error' | 'loading' | 'ready' | 'warning';
18
+ title: string;
19
+ updatedAt?: number;
20
+ };
21
+ export type VoiceProviderContractsWidgetOptions = VoiceProviderContractsClientOptions & {
22
+ description?: string;
23
+ title?: string;
24
+ };
25
+ export declare const createVoiceProviderContractsViewModel: <TProvider extends string = string>(snapshot: VoiceProviderContractsSnapshot<TProvider>, options?: VoiceProviderContractsWidgetOptions) => VoiceProviderContractsViewModel<TProvider>;
26
+ export declare const renderVoiceProviderContractsHTML: <TProvider extends string = string>(snapshot: VoiceProviderContractsSnapshot<TProvider>, options?: VoiceProviderContractsWidgetOptions) => string;
27
+ export declare const getVoiceProviderContractsCSS: () => string;
28
+ export declare const mountVoiceProviderContracts: <TProvider extends string = string>(element: Element, path?: string, options?: VoiceProviderContractsWidgetOptions) => {
29
+ close: () => void;
30
+ refresh: () => Promise<import("..").VoiceProviderContractMatrixReport<TProvider> | undefined>;
31
+ };
32
+ export declare const defineVoiceProviderContractsElement: (tagName?: string) => void;
@@ -0,0 +1,6 @@
1
+ import { type VoiceProviderContractsWidgetOptions } from '../client/providerContractsWidget';
2
+ export type VoiceProviderContractsProps = VoiceProviderContractsWidgetOptions & {
3
+ className?: string;
4
+ path?: string;
5
+ };
6
+ export declare const VoiceProviderContracts: ({ className, path, ...options }: VoiceProviderContractsProps) => import("react/jsx-runtime").JSX.Element;
@@ -3,6 +3,7 @@ export { VoiceOpsActionCenter } from './VoiceOpsActionCenter';
3
3
  export { VoiceDeliveryRuntime } from './VoiceDeliveryRuntime';
4
4
  export { VoiceProviderSimulationControls } from './VoiceProviderSimulationControls';
5
5
  export { VoiceProviderCapabilities } from './VoiceProviderCapabilities';
6
+ export { VoiceProviderContracts } from './VoiceProviderContracts';
6
7
  export { VoiceProviderStatus } from './VoiceProviderStatus';
7
8
  export { VoiceRoutingStatus } from './VoiceRoutingStatus';
8
9
  export { VoiceTraceTimeline } from './VoiceTraceTimeline';
@@ -16,6 +17,7 @@ export { useVoiceStream } from './useVoiceStream';
16
17
  export { useVoiceController } from './useVoiceController';
17
18
  export { useVoiceProviderStatus } from './useVoiceProviderStatus';
18
19
  export { useVoiceProviderCapabilities } from './useVoiceProviderCapabilities';
20
+ export { useVoiceProviderContracts } from './useVoiceProviderContracts';
19
21
  export { useVoiceProviderSimulationControls } from './useVoiceProviderSimulationControls';
20
22
  export { useVoiceRoutingStatus } from './useVoiceRoutingStatus';
21
23
  export { useVoiceTraceTimeline } from './useVoiceTraceTimeline';