@runtypelabs/persona 4.28.2 → 4.29.0
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/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/approval-ui.d.cts +1 -1
- package/dist/approval-ui.d.ts +1 -1
- package/dist/artifacts-ui.d.cts +2 -2
- package/dist/artifacts-ui.d.ts +2 -2
- package/dist/chunk-BkW8s3fj.js +2 -0
- package/dist/chunk-CIVsNcrk.js +1 -0
- package/dist/chunk-CKdlNyzM.js +9 -0
- package/dist/{chunk-DrYAfpvk.js → chunk-DjOvgFsn.js} +1 -1
- package/dist/chunk-lA5CJCgK.js +1 -0
- package/dist/client-history.js +1 -1
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/{composer-input-C39npJaz.d.cts → composer-input-D3sB8_P_.d.cts} +1 -1
- package/dist/{composer-input-C39npJaz.d.ts → composer-input-D3sB8_P_.d.ts} +1 -1
- package/dist/{context-mention-manager-DLCB1l9E.d.cts → context-mention-manager-D2qxE5op.d.cts} +1 -1
- package/dist/{context-mention-manager-DLCB1l9E.d.ts → context-mention-manager-D2qxE5op.d.ts} +1 -1
- package/dist/context-mentions-inline.d.cts +2 -2
- package/dist/context-mentions-inline.d.ts +2 -2
- package/dist/context-mentions.d.cts +3 -3
- package/dist/context-mentions.d.ts +3 -3
- package/dist/{defaults-DiAgWHXy.d.cts → defaults-Dsw66XqE.d.cts} +1 -1
- package/dist/{defaults-DiAgWHXy.d.ts → defaults-Dsw66XqE.d.ts} +1 -1
- package/dist/event-stream-view.d.cts +1 -1
- package/dist/event-stream-view.d.ts +1 -1
- package/dist/{fallback-speech-engine-C5_PUZjH.d.cts → fallback-speech-engine-DJpJcWAk.d.cts} +1 -1
- package/dist/{fallback-speech-engine-C5_PUZjH.d.ts → fallback-speech-engine-DJpJcWAk.d.ts} +1 -1
- package/dist/forms-ui.d.cts +2 -2
- package/dist/forms-ui.d.ts +2 -2
- package/dist/history-view.d.cts +1 -1
- package/dist/history-view.d.ts +1 -1
- package/dist/index.cjs +45 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +34 -33
- package/dist/index.js.map +1 -1
- package/dist/{markdown-parsers-loader-BBdyIEU7.d.cts → markdown-parsers-loader-CCY7T4Up.d.cts} +1 -1
- package/dist/{markdown-parsers-loader-BBdyIEU7.d.ts → markdown-parsers-loader-CCY7T4Up.d.ts} +1 -1
- package/dist/{preview-Chjb0bmI.d.cts → preview-DKKpfTo7.d.cts} +2 -2
- package/dist/{preview-Chjb0bmI.d.ts → preview-DKKpfTo7.d.ts} +2 -2
- package/dist/{session-a_qIp4xp.d.ts → session-DleMr9dZ.d.cts} +13 -7
- package/dist/{session-a_qIp4xp.d.cts → session-DleMr9dZ.d.ts} +13 -7
- package/dist/session-reconnect.d.cts +2 -2
- package/dist/session-reconnect.d.ts +2 -2
- package/dist/smart-dom-reader.d.cts +1 -1
- package/dist/smart-dom-reader.d.ts +1 -1
- package/dist/theme-editor-preview.cjs +41 -40
- package/dist/theme-editor-preview.d.cts +1 -1
- package/dist/theme-editor-preview.d.ts +1 -1
- package/dist/theme-editor-preview.js +20 -20
- package/dist/theme-editor.d.cts +2 -2
- package/dist/theme-editor.d.ts +2 -2
- package/dist/theme-reference.d.cts +2 -2
- package/dist/theme-reference.d.ts +2 -2
- package/dist/{types-BdO1jNRm.d.cts → types-oAZ1VReS.d.cts} +166 -21
- package/dist/{types-BdO1jNRm.d.ts → types-oAZ1VReS.d.ts} +166 -21
- package/dist/voice-client-tools.cjs +1 -0
- package/dist/voice-client-tools.d.cts +13 -0
- package/dist/voice-client-tools.d.ts +13 -0
- package/dist/voice-client-tools.js +1 -0
- package/dist/voice-runtime.cjs +3 -3
- package/dist/voice-runtime.d.cts +7 -3
- package/dist/voice-runtime.d.ts +7 -3
- package/dist/voice-runtime.js +4 -4
- package/dist/voice-worklet-player.d.cts +2 -2
- package/dist/voice-worklet-player.d.ts +2 -2
- package/dist/{webmcp-runtime-entry-B6bpVWSa.d.ts → webmcp-runtime-entry-Deg0NfmB.d.cts} +26 -5
- package/dist/{webmcp-runtime-entry-B6bpVWSa.d.cts → webmcp-runtime-entry-Deg0NfmB.d.ts} +26 -5
- package/dist/webmcp-runtime.cjs +2 -1
- package/dist/webmcp-runtime.d.cts +1 -1
- package/dist/webmcp-runtime.d.ts +1 -1
- package/dist/webmcp-runtime.js +2 -1
- package/package.json +6 -1
- package/src/client.ts +37 -0
- package/src/client.voice-tools.test.ts +65 -0
- package/src/generated/runtype-openapi-contract.ts +38 -0
- package/src/index-core.ts +11 -0
- package/src/index-global.ts +6 -0
- package/src/session.test.ts +26 -0
- package/src/session.ts +8 -1
- package/src/session.voice.test.ts +43 -0
- package/src/types.ts +141 -20
- package/src/voice/runtype-voice-provider.ts +85 -3
- package/src/voice/session-voice-wiring.ts +26 -1
- package/src/voice/voice-client-tools-session.ts +116 -0
- package/src/voice/voice-delegation.ts +9 -0
- package/src/voice/voice.test.ts +360 -0
- package/src/voice-client-tools-bundle.test.ts +37 -0
- package/src/voice-client-tools-loader.ts +24 -0
- package/src/voice-client-tools.ts +9 -0
- package/src/webmcp-bridge.test.ts +152 -1
- package/src/webmcp-bridge.ts +5 -6
- package/src/webmcp-polyfill.integration.test.ts +5 -1
- package/src/webmcp-runtime-entry.ts +151 -26
- package/dist/chunk-CztSjKHK.js +0 -9
- package/dist/chunk-HUOUif-P.js +0 -1
- package/dist/chunk-UlcxdHef.js +0 -1
|
@@ -268,7 +268,11 @@ describe("WebMcpBridge.snapshotForDispatch", () => {
|
|
|
268
268
|
annotations: {
|
|
269
269
|
readOnlyHint: false,
|
|
270
270
|
untrustedContentHint: false,
|
|
271
|
-
destructiveHint: true,
|
|
271
|
+
destructiveHint: true,
|
|
272
|
+
consequentialHint: true,
|
|
273
|
+
idempotentHint: false,
|
|
274
|
+
openWorldHint: false,
|
|
275
|
+
debugging: true,
|
|
272
276
|
evil: () => "not JSON",
|
|
273
277
|
},
|
|
274
278
|
}),
|
|
@@ -277,6 +281,11 @@ describe("WebMcpBridge.snapshotForDispatch", () => {
|
|
|
277
281
|
expect(snap[0]!.annotations).toEqual({
|
|
278
282
|
readOnlyHint: false,
|
|
279
283
|
untrustedContentHint: false,
|
|
284
|
+
destructiveHint: true,
|
|
285
|
+
consequentialHint: true,
|
|
286
|
+
idempotentHint: false,
|
|
287
|
+
openWorldHint: false,
|
|
288
|
+
debugging: true,
|
|
280
289
|
});
|
|
281
290
|
expect(snap[0]!).not.toHaveProperty("untrustedContentHint");
|
|
282
291
|
});
|
|
@@ -380,6 +389,20 @@ describe("setWebMcpPolyfillLoader", () => {
|
|
|
380
389
|
});
|
|
381
390
|
|
|
382
391
|
describe("WebMCP display titles", () => {
|
|
392
|
+
it("passes sanitized annotations to confirmation without skipping the gate", async () => {
|
|
393
|
+
registry.tools = [fakeTool({ name: "search", annotations: {
|
|
394
|
+
readOnlyHint: true, destructiveHint: false, consequentialHint: false,
|
|
395
|
+
debugging: "yes", extra: () => "ignored",
|
|
396
|
+
} })];
|
|
397
|
+
const onConfirm = vi.fn(async () => false);
|
|
398
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
399
|
+
const result = await bridge.executeToolCall("webmcp:search", {});
|
|
400
|
+
expect(onConfirm).toHaveBeenCalledWith(expect.objectContaining({ annotations: {
|
|
401
|
+
readOnlyHint: true, destructiveHint: false, consequentialHint: false,
|
|
402
|
+
} }));
|
|
403
|
+
expect(result.isError).toBe(true);
|
|
404
|
+
});
|
|
405
|
+
|
|
383
406
|
it("records declared titles on snapshot and exposes them via getWebMcpToolDisplayTitle", async () => {
|
|
384
407
|
registry.tools = [
|
|
385
408
|
fakeTool({ name: "add_to_cart", title: "Add to Cart" }),
|
|
@@ -811,3 +834,131 @@ describe("native preview argument conversion", () => {
|
|
|
811
834
|
expect(nativeExecute).toHaveBeenCalledTimes(1);
|
|
812
835
|
});
|
|
813
836
|
});
|
|
837
|
+
|
|
838
|
+
describe("WebMcpBridge.executeVoiceToolCall", () => {
|
|
839
|
+
it("skips the confirm gate for a tool the registry marks readOnlyHint: true", async () => {
|
|
840
|
+
const onConfirm = vi.fn(async () => false);
|
|
841
|
+
registry.tools = [
|
|
842
|
+
fakeTool({ name: "next_slide", annotations: { readOnlyHint: true }, execute: () => ({ slidePosition: 2 }) }),
|
|
843
|
+
];
|
|
844
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
845
|
+
const r = await bridge.executeVoiceToolCall("webmcp:next_slide", {}, { autoConfirmReadOnly: true });
|
|
846
|
+
expect(onConfirm).not.toHaveBeenCalled();
|
|
847
|
+
expect(r).toEqual({ status: "completed", result: { slidePosition: 2 } });
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it.each([
|
|
851
|
+
["no annotations", undefined],
|
|
852
|
+
["readOnlyHint false", { readOnlyHint: false }],
|
|
853
|
+
["only other hints", { idempotentHint: true }],
|
|
854
|
+
])("still shows the gate for a tool with %s, and maps a decline to denied", async (_label, annotations) => {
|
|
855
|
+
const onConfirm = vi.fn(async () => false);
|
|
856
|
+
const execute = vi.fn(() => "done");
|
|
857
|
+
registry.tools = [fakeTool({ name: "edit_slide", annotations, execute })];
|
|
858
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
859
|
+
const r = await bridge.executeVoiceToolCall("webmcp:edit_slide", {}, { autoConfirmReadOnly: true });
|
|
860
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
861
|
+
expect(execute).not.toHaveBeenCalled();
|
|
862
|
+
expect(r).toEqual({ status: "denied", error: { message: "User declined the tool call." } });
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it("shows the gate even for a read-only tool when autoConfirmReadOnly is off", async () => {
|
|
866
|
+
const onConfirm = vi.fn(async () => true);
|
|
867
|
+
registry.tools = [fakeTool({ name: "next_slide", annotations: { readOnlyHint: true } })];
|
|
868
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
869
|
+
await bridge.executeVoiceToolCall("webmcp:next_slide", {});
|
|
870
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it("answers stale for a tool missing from the registry or excluded by the allowlist", async () => {
|
|
874
|
+
registry.tools = [fakeTool({ name: "secret", annotations: { readOnlyHint: true } })];
|
|
875
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm: allowAll, allowlist: ["public_*"] });
|
|
876
|
+
expect((await bridge.executeVoiceToolCall("webmcp:gone", {})).status).toBe("stale");
|
|
877
|
+
expect((await bridge.executeVoiceToolCall("webmcp:secret", {})).status).toBe("stale");
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it("answers failed with the message when the page tool throws or returns an MCP error", async () => {
|
|
881
|
+
registry.tools = [
|
|
882
|
+
fakeTool({
|
|
883
|
+
name: "boom",
|
|
884
|
+
annotations: { readOnlyHint: true },
|
|
885
|
+
execute: () => {
|
|
886
|
+
throw new Error("kaboom");
|
|
887
|
+
},
|
|
888
|
+
}),
|
|
889
|
+
fakeTool({
|
|
890
|
+
name: "mcp_err",
|
|
891
|
+
annotations: { readOnlyHint: true },
|
|
892
|
+
execute: () => ({ isError: true, content: [{ type: "text", text: "no such slide" }] }),
|
|
893
|
+
}),
|
|
894
|
+
];
|
|
895
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm: allowAll });
|
|
896
|
+
const thrown = await bridge.executeVoiceToolCall("webmcp:boom", {}, { autoConfirmReadOnly: true });
|
|
897
|
+
expect(thrown).toEqual({ status: "failed", error: { message: "kaboom" } });
|
|
898
|
+
expect(await bridge.executeVoiceToolCall("webmcp:mcp_err", {}, { autoConfirmReadOnly: true })).toEqual({
|
|
899
|
+
status: "failed",
|
|
900
|
+
error: { message: "no such slide" },
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it("hands the call's signal to the confirm handler so the gate can close on abort", async () => {
|
|
905
|
+
const abort = new AbortController();
|
|
906
|
+
const onConfirm = vi.fn(async (info: { signal?: AbortSignal }) => {
|
|
907
|
+
expect(info.signal).toBe(abort.signal);
|
|
908
|
+
return false;
|
|
909
|
+
});
|
|
910
|
+
registry.tools = [fakeTool({ name: "edit_slide" })];
|
|
911
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
912
|
+
await bridge.executeVoiceToolCall("webmcp:edit_slide", {}, { signal: abort.signal, autoConfirmReadOnly: true });
|
|
913
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it("does not run the tool when the signal aborts while the gate is open", async () => {
|
|
917
|
+
const abort = new AbortController();
|
|
918
|
+
const execute = vi.fn(() => "done");
|
|
919
|
+
const onConfirm = vi.fn(async () => {
|
|
920
|
+
abort.abort();
|
|
921
|
+
return true;
|
|
922
|
+
});
|
|
923
|
+
registry.tools = [fakeTool({ name: "edit_slide", execute })];
|
|
924
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
925
|
+
const r = await bridge.executeVoiceToolCall("webmcp:edit_slide", {}, {
|
|
926
|
+
signal: abort.signal,
|
|
927
|
+
autoConfirmReadOnly: true,
|
|
928
|
+
});
|
|
929
|
+
expect(execute).not.toHaveBeenCalled();
|
|
930
|
+
expect(r.status).toBe("failed");
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
it("keeps executeToolCall's /resume shape and gate unchanged", async () => {
|
|
934
|
+
const onConfirm = vi.fn(async () => false);
|
|
935
|
+
registry.tools = [fakeTool({ name: "next_slide", annotations: { readOnlyHint: true } })];
|
|
936
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
937
|
+
const r = await bridge.executeToolCall("webmcp:next_slide", {});
|
|
938
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
939
|
+
expect(r).toEqual({ isError: true, content: [{ type: "text", text: "User declined the tool call." }] });
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
describe("WebMcpBridge.watchToolChanges", () => {
|
|
944
|
+
it("calls the listener on the registry's toolchange event until unsubscribed", async () => {
|
|
945
|
+
const mc = Object.assign(new EventTarget(), makeModelContext());
|
|
946
|
+
vi.stubGlobal("document", { modelContext: mc });
|
|
947
|
+
const bridge = new WebMcpBridge({ enabled: true });
|
|
948
|
+
const listener = vi.fn();
|
|
949
|
+
const stop = bridge.watchToolChanges(listener);
|
|
950
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
951
|
+
mc.dispatchEvent(new Event("toolchange"));
|
|
952
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
953
|
+
stop();
|
|
954
|
+
mc.dispatchEvent(new Event("toolchange"));
|
|
955
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
it("is inert on a registry without events", async () => {
|
|
959
|
+
const bridge = new WebMcpBridge({ enabled: true });
|
|
960
|
+
const stop = bridge.watchToolChanges(vi.fn());
|
|
961
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
962
|
+
expect(() => stop()).not.toThrow();
|
|
963
|
+
});
|
|
964
|
+
});
|
package/src/webmcp-bridge.ts
CHANGED
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
* not replace an existing context. v5's initializeWebMCPPolyfill() and v6's
|
|
26
26
|
* installWebMCP() are both accepted by custom loaders.
|
|
27
27
|
*
|
|
28
|
-
* Every call passes one confirm gate before execution, regardless of
|
|
29
|
-
* annotations.readOnlyHint.
|
|
28
|
+
* Every chat-path call passes one confirm gate before execution, regardless of
|
|
29
|
+
* annotations.readOnlyHint. The one exception is a voice fast-tier
|
|
30
|
+
* `client_tool_call`, which skips the gate only for a tool whose registry entry
|
|
31
|
+
* declares `readOnlyHint: true`. v6 callbacks also receive an invocation signal.
|
|
30
32
|
*/
|
|
31
33
|
|
|
32
34
|
import type { ClientToolDefinition } from "./types";
|
|
@@ -74,10 +76,7 @@ export interface ModelContextToolInfo {
|
|
|
74
76
|
* absent on v4 and whenever the tool declared none -- the spec omits the
|
|
75
77
|
* member rather than emitting an empty object.
|
|
76
78
|
*/
|
|
77
|
-
annotations?:
|
|
78
|
-
readOnlyHint?: boolean;
|
|
79
|
-
untrustedContentHint?: boolean;
|
|
80
|
-
};
|
|
79
|
+
annotations?: ClientToolDefinition["annotations"];
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
export interface ModelContextCoreLike {
|
|
@@ -129,7 +129,11 @@ describe.each([
|
|
|
129
129
|
type: "object",
|
|
130
130
|
properties: { query: { type: "string" } },
|
|
131
131
|
},
|
|
132
|
-
annotations: {
|
|
132
|
+
annotations: {
|
|
133
|
+
readOnlyHint: true,
|
|
134
|
+
untrustedContentHint: true,
|
|
135
|
+
...(legacy ? {} : { consequentialHint: false, debugging: false }),
|
|
136
|
+
},
|
|
133
137
|
untrustedContentHint: true,
|
|
134
138
|
}),
|
|
135
139
|
]);
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import type {
|
|
17
17
|
AgentWidgetWebMcpConfig,
|
|
18
18
|
ClientToolDefinition,
|
|
19
|
+
VoiceClientToolOutcome,
|
|
19
20
|
WebMcpConfirmHandler,
|
|
20
21
|
WebMcpConfirmInfo,
|
|
21
22
|
WebMcpToolResult,
|
|
@@ -38,6 +39,22 @@ export interface WebMcpBridgeDeps {
|
|
|
38
39
|
|
|
39
40
|
const DEFAULT_TOOL_TIMEOUT_MS = 30_000;
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* One run of a page tool, before it is shaped for `/resume` (`executeToolCall`)
|
|
44
|
+
* or for a voice `client_tool_result` (`executeVoiceToolCall`). `message` is
|
|
45
|
+
* the exact text the `/resume` error result has always carried.
|
|
46
|
+
*/
|
|
47
|
+
type ToolRunFailureReason = "stale" | "denied" | "failed" | "timeout" | "aborted";
|
|
48
|
+
type ToolRunOutcome =
|
|
49
|
+
| { ok: true; raw: string | null }
|
|
50
|
+
| { ok: false; reason: ToolRunFailureReason; message: string };
|
|
51
|
+
|
|
52
|
+
const runFailure = (reason: ToolRunFailureReason, message: string): ToolRunOutcome => ({
|
|
53
|
+
ok: false,
|
|
54
|
+
reason,
|
|
55
|
+
message,
|
|
56
|
+
});
|
|
57
|
+
|
|
41
58
|
// Local copies of the wire-prefix helpers: pure, and duplicating them keeps
|
|
42
59
|
// this chunk free of any value edge into core webmcp-bridge state.
|
|
43
60
|
const WEBMCP_TOOL_PREFIX = "webmcp:";
|
|
@@ -177,11 +194,77 @@ export class WebMcpBridge {
|
|
|
177
194
|
args: unknown,
|
|
178
195
|
signal?: AbortSignal,
|
|
179
196
|
): Promise<WebMcpToolResult> {
|
|
197
|
+
const outcome = await this.#run(wireToolName, args, signal, false);
|
|
198
|
+
return outcome.ok
|
|
199
|
+
? normalizeSerializedResult(outcome.raw)
|
|
200
|
+
: errorResult(outcome.message);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Execute a page tool the voice server picked for a delegation
|
|
205
|
+
* (`client_tool_call`) and return its `client_tool_result` outcome.
|
|
206
|
+
*
|
|
207
|
+
* Same registry lookup, allowlist, abort points and timeout as
|
|
208
|
+
* `executeToolCall`, with one difference: when `autoConfirmReadOnly` is set,
|
|
209
|
+
* a tool whose LIVE registry entry declares `annotations.readOnlyHint ===
|
|
210
|
+
* true` skips the confirm gate. Every other tool still shows it, and a
|
|
211
|
+
* decline answers `denied`. A tool missing from the registry or excluded by
|
|
212
|
+
* the allowlist answers `stale` (the server's snapshot is out of date).
|
|
213
|
+
* `signal` aborting (the caller's deadline, a hang-up) answers `failed`;
|
|
214
|
+
* the caller decides whether that was its timeout.
|
|
215
|
+
*/
|
|
216
|
+
public async executeVoiceToolCall(
|
|
217
|
+
wireToolName: string,
|
|
218
|
+
args: unknown,
|
|
219
|
+
options: { signal?: AbortSignal; autoConfirmReadOnly?: boolean } = {},
|
|
220
|
+
): Promise<VoiceClientToolOutcome> {
|
|
221
|
+
const outcome = await this.#run(
|
|
222
|
+
wireToolName,
|
|
223
|
+
args,
|
|
224
|
+
options.signal,
|
|
225
|
+
options.autoConfirmReadOnly === true,
|
|
226
|
+
);
|
|
227
|
+
if (!outcome.ok) {
|
|
228
|
+
return {
|
|
229
|
+
status: outcome.reason === "aborted" ? "failed" : outcome.reason,
|
|
230
|
+
error: { message: outcome.message },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return voiceOutcomeFromRaw(outcome.raw);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Call `listener` whenever the page's tool registry changes (the WebMCP
|
|
238
|
+
* `toolchange` event on `document.modelContext`). Returns an unsubscribe.
|
|
239
|
+
* A registry without the event never calls it: callers then keep the
|
|
240
|
+
* snapshot they took first.
|
|
241
|
+
*/
|
|
242
|
+
public watchToolChanges(listener: () => void): () => void {
|
|
243
|
+
let stopped = false;
|
|
244
|
+
let target: EventTarget | null = null;
|
|
245
|
+
void this.#ensureReady().then(() => {
|
|
246
|
+
if (stopped || this.config.enabled !== true) return;
|
|
247
|
+
const mc = this.#getModelContext() as unknown as Partial<EventTarget> | null;
|
|
248
|
+
if (!mc || typeof mc.addEventListener !== "function") return;
|
|
249
|
+
target = mc as EventTarget;
|
|
250
|
+
target.addEventListener("toolchange", listener);
|
|
251
|
+
});
|
|
252
|
+
return () => {
|
|
253
|
+
stopped = true;
|
|
254
|
+
target?.removeEventListener("toolchange", listener);
|
|
255
|
+
target = null;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async #run(
|
|
260
|
+
wireToolName: string,
|
|
261
|
+
args: unknown,
|
|
262
|
+
signal: AbortSignal | undefined,
|
|
263
|
+
autoConfirmReadOnly: boolean,
|
|
264
|
+
): Promise<ToolRunOutcome> {
|
|
180
265
|
await this.#ensureReady();
|
|
181
266
|
if (this.config.enabled !== true) {
|
|
182
|
-
return
|
|
183
|
-
"WebMCP is not enabled on this widget.",
|
|
184
|
-
);
|
|
267
|
+
return runFailure("failed", "WebMCP is not enabled on this widget.");
|
|
185
268
|
}
|
|
186
269
|
|
|
187
270
|
const mc = this.#getModelContext();
|
|
@@ -193,7 +276,8 @@ export class WebMcpBridge {
|
|
|
193
276
|
const present =
|
|
194
277
|
typeof document !== "undefined" &&
|
|
195
278
|
Boolean((document as Document & { modelContext?: unknown }).modelContext);
|
|
196
|
-
return
|
|
279
|
+
return runFailure(
|
|
280
|
+
"failed",
|
|
197
281
|
present
|
|
198
282
|
? "WebMCP is not operational: document.modelContext is present but does not expose the strict getTools()/executeTool() surface (likely a different or older WebMCP polyfill)."
|
|
199
283
|
: "WebMCP bridge is not operational on this page (document.modelContext not available).",
|
|
@@ -207,13 +291,14 @@ export class WebMcpBridge {
|
|
|
207
291
|
infos = (await mc.getTools()).filter(isLocalTool);
|
|
208
292
|
} catch (err) {
|
|
209
293
|
const message = err instanceof Error ? err.message : String(err);
|
|
210
|
-
return
|
|
294
|
+
return runFailure("failed", `Failed to read WebMCP registry: ${message}`);
|
|
211
295
|
}
|
|
212
296
|
this.#deps.recordToolDisplayTitles(infos);
|
|
213
297
|
const info = infos.find((candidate) => candidate.name === bareName);
|
|
214
298
|
|
|
215
299
|
if (!info) {
|
|
216
|
-
return
|
|
300
|
+
return runFailure(
|
|
301
|
+
"stale",
|
|
217
302
|
`WebMCP tool not registered on this page: ${bareName}`,
|
|
218
303
|
);
|
|
219
304
|
}
|
|
@@ -225,7 +310,8 @@ export class WebMcpBridge {
|
|
|
225
310
|
// tool. The server is the trust boundary; this is a defense-in-depth
|
|
226
311
|
// convenience check to keep us symmetric with the snapshot.
|
|
227
312
|
if (!this.#passesClientAllowlist(bareName)) {
|
|
228
|
-
return
|
|
313
|
+
return runFailure(
|
|
314
|
+
"stale",
|
|
229
315
|
`WebMCP tool not allowed by client allowlist: ${bareName}`,
|
|
230
316
|
);
|
|
231
317
|
}
|
|
@@ -233,28 +319,33 @@ export class WebMcpBridge {
|
|
|
233
319
|
// Bail before the confirm renders: a late approval after cancel() would
|
|
234
320
|
// otherwise fire a host-page side effect with no matching /resume.
|
|
235
321
|
if (signal?.aborted) {
|
|
236
|
-
return
|
|
322
|
+
return runFailure("aborted", "Aborted by cancel()");
|
|
237
323
|
}
|
|
238
324
|
|
|
239
|
-
// Confirm-by-default gate. Every `webmcp:*` call routes through
|
|
240
|
-
// regardless of `annotations.readOnlyHint
|
|
325
|
+
// Confirm-by-default gate. Every chat-path `webmcp:*` call routes through
|
|
326
|
+
// here regardless of `annotations.readOnlyHint`; only a voice fast-tier
|
|
327
|
+
// call (`autoConfirmReadOnly`) of a tool the registry marks read-only skips it.
|
|
241
328
|
const displayTitle = this.#deps.getToolDisplayTitle(bareName);
|
|
329
|
+
const annotations = pickAnnotations(info.annotations);
|
|
242
330
|
const gateInfo: WebMcpConfirmInfo = {
|
|
243
331
|
toolName: bareName,
|
|
244
332
|
args,
|
|
245
333
|
description: info.description,
|
|
334
|
+
...(annotations ? { annotations } : {}),
|
|
246
335
|
...(displayTitle ? { title: displayTitle } : {}),
|
|
336
|
+
...(signal ? { signal } : {}),
|
|
247
337
|
reason: "gate",
|
|
248
338
|
};
|
|
249
|
-
|
|
250
|
-
|
|
339
|
+
const skipGate = autoConfirmReadOnly && annotations?.readOnlyHint === true;
|
|
340
|
+
if (!skipGate && !(await this.#requestConfirm(gateInfo))) {
|
|
341
|
+
return runFailure("denied", "User declined the tool call.");
|
|
251
342
|
}
|
|
252
343
|
|
|
253
344
|
// The await above may have parked us long enough for cancel() to fire.
|
|
254
345
|
// Bail before invoking `execute()` so we don't fire a side effect that
|
|
255
346
|
// the server can no longer accept a `/resume` for.
|
|
256
347
|
if (signal?.aborted) {
|
|
257
|
-
return
|
|
348
|
+
return runFailure("aborted", "Aborted by cancel()");
|
|
258
349
|
}
|
|
259
350
|
|
|
260
351
|
// Drive both the 30s timeout and the caller's `signal` through a single
|
|
@@ -278,18 +369,19 @@ export class WebMcpBridge {
|
|
|
278
369
|
const raw = await mc.executeTool(info, createCompatibleToolInput(args), {
|
|
279
370
|
signal: controller.signal,
|
|
280
371
|
});
|
|
281
|
-
return
|
|
372
|
+
return { ok: true, raw };
|
|
282
373
|
} catch (err) {
|
|
283
374
|
if (timedOut) {
|
|
284
|
-
return
|
|
375
|
+
return runFailure(
|
|
376
|
+
"timeout",
|
|
285
377
|
`WebMCP tool '${bareName}' timed out after ${this.#timeoutMs}ms`,
|
|
286
378
|
);
|
|
287
379
|
}
|
|
288
380
|
if (signal?.aborted) {
|
|
289
|
-
return
|
|
381
|
+
return runFailure("aborted", "Aborted by cancel()");
|
|
290
382
|
}
|
|
291
383
|
const message = err instanceof Error ? err.message : String(err);
|
|
292
|
-
return
|
|
384
|
+
return runFailure("failed", message);
|
|
293
385
|
} finally {
|
|
294
386
|
clearTimeout(timer);
|
|
295
387
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
@@ -442,7 +534,7 @@ const EMPTY_INPUT_SCHEMA = { type: "object", properties: {} } as const;
|
|
|
442
534
|
* Copy the WebMCP annotation hints onto the wire, dropping anything else.
|
|
443
535
|
*
|
|
444
536
|
* `getTools()` returns `annotations` since `@mcp-b/webmcp-polyfill` v5 (absent
|
|
445
|
-
* on v4 and when the tool declared none). Only
|
|
537
|
+
* on v4 and when the tool declared none). Only supported boolean hints are
|
|
446
538
|
* forwarded -- annotations are page-authored, so an explicit copy keeps
|
|
447
539
|
* functions/junk off the dispatch payload. Returns `undefined` when nothing
|
|
448
540
|
* usable remains so the member is omitted, matching the spec's own
|
|
@@ -452,14 +544,14 @@ const pickAnnotations = (
|
|
|
452
544
|
raw: ModelContextToolInfo["annotations"],
|
|
453
545
|
): ClientToolDefinition["annotations"] | undefined => {
|
|
454
546
|
if (!raw || typeof raw !== "object") return undefined;
|
|
455
|
-
const picked = {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
547
|
+
const picked: NonNullable<ClientToolDefinition["annotations"]> = {};
|
|
548
|
+
for (const key of [
|
|
549
|
+
"readOnlyHint", "destructiveHint", "consequentialHint", "untrustedContentHint",
|
|
550
|
+
"idempotentHint", "openWorldHint", "debugging",
|
|
551
|
+
] as const) {
|
|
552
|
+
const value = raw[key];
|
|
553
|
+
if (typeof value === "boolean") picked[key] = value;
|
|
554
|
+
}
|
|
463
555
|
return Object.keys(picked).length > 0 ? picked : undefined;
|
|
464
556
|
};
|
|
465
557
|
|
|
@@ -544,6 +636,39 @@ const errorResult = (message: string): WebMcpToolResult => ({
|
|
|
544
636
|
content: [{ type: "text", text: message }],
|
|
545
637
|
});
|
|
546
638
|
|
|
639
|
+
const textOfContent = (content: unknown): string =>
|
|
640
|
+
Array.isArray(content)
|
|
641
|
+
? content
|
|
642
|
+
.map((block: unknown) =>
|
|
643
|
+
block && typeof block === "object" && typeof (block as { text?: unknown }).text === "string"
|
|
644
|
+
? (block as { text: string }).text
|
|
645
|
+
: "",
|
|
646
|
+
)
|
|
647
|
+
.filter(Boolean)
|
|
648
|
+
.join("\n")
|
|
649
|
+
: "";
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* A page tool's serialized return as a voice `client_tool_result`: the tool's
|
|
653
|
+
* own JSON value (not the MCP content wrapper `/resume` gets), so the server
|
|
654
|
+
* can template a readout from its fields. A tool that returned an MCP error
|
|
655
|
+
* (`isError: true`) is `failed`, with its text as the message.
|
|
656
|
+
*/
|
|
657
|
+
const voiceOutcomeFromRaw = (raw: string | null): VoiceClientToolOutcome => {
|
|
658
|
+
if (raw === null || raw === undefined) return { status: "completed" };
|
|
659
|
+
let parsed: unknown;
|
|
660
|
+
try {
|
|
661
|
+
parsed = JSON.parse(raw);
|
|
662
|
+
} catch {
|
|
663
|
+
return { status: "completed", result: raw };
|
|
664
|
+
}
|
|
665
|
+
if (parsed !== null && typeof parsed === "object" && (parsed as { isError?: unknown }).isError === true) {
|
|
666
|
+
const message = textOfContent((parsed as { content?: unknown }).content);
|
|
667
|
+
return { status: "failed", error: { message: message || "The page tool reported an error." } };
|
|
668
|
+
}
|
|
669
|
+
return parsed === undefined ? { status: "completed" } : { status: "completed", result: parsed };
|
|
670
|
+
};
|
|
671
|
+
|
|
547
672
|
/**
|
|
548
673
|
* Fallback confirm UI: `window.confirm()`. Production deployments should wire
|
|
549
674
|
* `config.webmcp.onConfirm` to a handler matched to their UX (e.g. an inline
|
package/dist/chunk-CztSjKHK.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./chunk-UlcxdHef.js";import{t}from"./chunk-DqbzUVEI.js";const n=16e3,r=/* @__PURE__ */ new Map([[`idle_timeout`,`Voice call ended after a quiet period.`],[`max_duration`,`This voice call reached its time limit.`],[`provider_ended`,`The voice session ended.`],[`quota`,`Voice is unavailable right now.`],[`auth_expired`,`Voice session expired. Tap the mic to reconnect.`]]),i=[`provider_error`,`server_restart`],a=`Voice connection lost.`;function o(e,t){let n=t.slice(0,4e3).trim(),r=8e3-(n?n.length+2:0)-20,i=[];for(let t of e.slice(-12).reverse()){let e=t.content.replace(/\s+/g,` `).trim();if(!e)continue;e.length>2e3&&(e=`${e.slice(0,1999)}…`);let n=`${t.role===`user`?`User`:`Assistant`}: ${e}`;if(n.length+1>r)break;r-=n.length+1,i.unshift(n)}let a=i.length>0?[`Conversation so far:\n${i.join(`
|
|
2
|
-
`)}`]:[];return n&&a.push(n),a.join(`
|
|
3
|
-
|
|
4
|
-
`)}function s(e){return e.byteLength>=44&&new DataView(e).getUint32(0,!1)===1380533830?new Uint8Array(e,44):new Uint8Array(e)}function c(e){let t=e.replace(/\/+$/,``);return/^wss?:\/\//i.test(t)?t:/^https?:\/\//i.test(t)?t.replace(/^http/i,`ws`):`${typeof window<`u`&&window.location?.protocol===`https:`?`wss:`:`ws:`}//${t}`}function l(e){return c(e).replace(/^ws/i,`http`)}function u(e){return typeof e!=`number`||!Number.isFinite(e)?3e4:Math.min(6e5,Math.max(3e4,Math.round(e)))}var d=class{#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;#d;#f;#p;#m;#h;#g;#_;#v;#y;#b;#x;#S;#C;#w;#T;#E;#D;#O;#k;#A;#j;#M;#N;#P;#F;#I;#L;#R;#z;#B;#V;#H;#U;#W;#G;constructor(e){this.config=e,this.type=`runtype`,this.#e=null,this.#t=null,this.#n=[],this.#r=null,this.#i=null,this.#a=null,this.#o=[],this.#s=0,this.#c=null,this.#l=!1,this.#u=!1,this.#d=0,this.#f=!1,this.#p=-1/0,this.#m=null,this.#h=!1,this.#g=!1,this.#_=0,this.#v=0,this.#b=!1,this.bridge=null,this.#x=!1,this.#S=/* @__PURE__ */ new Set,this.#C=/* @__PURE__ */ new Map,this.#w=/* @__PURE__ */ new Set,this.#E=null,this.#D=null,this.#k=!1,this.#A=!1,this.#j=!1,this.#M=null,this.#N=null,this.#P=Promise.resolve(),this.#F=/* @__PURE__ */ new Set,this.#I=/* @__PURE__ */ new Set,this.#L=null,this.#R=!1,this.#z=/* @__PURE__ */ new Set,this.#B=/* @__PURE__ */ new Set,this.#V=[],this.#H=[],this.#U=[],this.#W=[],this.#G=[]}async connect(){}setSessionBridge(e){this.bridge=e}prewarm(){if(this.#l||this.#m)return;let e=this.config?.agentId,t=this.config?.clientToken,n=this.config?.host;if(!e||!t||!n)return;let r=Date.now();if(!(r-this.#p<3e4)){this.#p=r;try{this.config?.prewarmMode===`attach`?this.#q(n,e,t):this.#K(n,e,t)}catch{}}}#K(e,t,n){if(typeof fetch!=`function`)return;let r=`${l(e)}/v1/client/agents/${encodeURIComponent(t)}/voice/prewarm`;fetch(r,{method:`POST`,headers:{Authorization:`Bearer ${n}`},keepalive:!0}).catch(()=>{})}#q(e,t,n){let r=new URLSearchParams,i=this.config?.attachIdleMs,a=u(i);i!==void 0&&r.set(`attachIdleMs`,String(a));let o=new WebSocket(this.#Z(e,t,r),[`runtype.bearer`,`runtype.attach`,n]);o.binaryType=`arraybuffer`;let s=()=>{},c={ws:o,state:`connecting`,ready:new Promise(e=>{s=e}),settle:s,openedAt:0,idleTimer:void 0};this.#m=c,o.onopen=()=>{this.#m===c&&(c.state=o.protocol===`runtype.attach`?`attached`:`live`,c.openedAt=Date.now(),this.#J(c,a),c.state===`attached`&&o.send(`{"type":"ping"}`),c.settle())},o.onmessage=e=>{if(this.#m!==c||typeof e.data!=`string`)return;let t;try{t=JSON.parse(e.data)}catch{return}t.type===`attached`&&typeof t.idleMs==`number`&&this.#J(c,t.idleMs-(Date.now()-c.openedAt))},o.onerror=()=>{},o.onclose=()=>{this.#m===c&&this.#Y()}}#J(e,t){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{this.#m===e&&this.#Y()},Math.max(0,t))}#Y(){let e=this.#m;if(!e)return;this.#m=null,clearTimeout(e.idleTimer);let{ws:t}=e;if(t.onopen=null,t.onmessage=null,t.onerror=null,t.onclose=null,e.settle(),t.readyState===WebSocket.CONNECTING||t.readyState===WebSocket.OPEN)try{t.close(1e3,`prewarm released`)}catch{}}async#X(){let e=this.#m;if(!e)return null;if(e.state===`connecting`){let t;await Promise.race([e.ready,new Promise(e=>{t=setTimeout(e,5e3)})]),clearTimeout(t)}return this.#m===e?e.state===`connecting`||e.ws.readyState!==WebSocket.OPEN?(this.#Y(),null):(this.#m=null,clearTimeout(e.idleTimer),{ws:e.ws,live:e.state===`live`}):null}#Z(t,n,r){let i=[`partial_transcript`,`context`];this.bridge&&this.config?.clientDelegation!==!1&&i.push(`client_delegation`,`delegation_update`,`delegation_read_back`,`delegation_stream`),r&&i.unshift(`attach`);let a=new URLSearchParams({voiceProtocol:`runtype-browser-v1`,clientVersion:`persona/${e}`,voiceCapabilities:`full-duplex-v1`,clientCapabilities:i.join(`,`)});return r?.forEach((e,t)=>a.set(t,e)),`${c(t)}/ws/agents/${encodeURIComponent(n)}/voice?${a}`}async startListening(){if(this.#l)return;let e=this.#A;e||(this.#k=!1,this.#D=null),this.#A=!1;let r=this.config?.agentId,i=this.config?.clientToken,o=this.config?.host;if(!r)throw Error(`Runtype voice requires an agentId`);if(!i)throw Error(`Runtype voice requires a clientToken`);if(!o)throw Error(`Runtype voice requires a host (or widget apiUrl)`);let s=++this.#d;this.#f=!1,this.#l=!0;try{let c=await navigator.mediaDevices.getUserMedia({audio:{sampleRate:n,channelCount:1,echoCancellation:!0}});if(s!==this.#d){c.getTracks().forEach(e=>e.stop());return}this.#r=c;let l=new(window.AudioContext||window.webkitAudioContext)({sampleRate:n});if(this.#t=l,l.state===`suspended`){if(await Promise.race([l.resume().catch(()=>{}),new Promise(t=>setTimeout(t,e?500:1e3))]),s!==this.#d)return;if(l.state!==`running`)throw Error(e?this.#D=`${a} Tap the mic to reconnect.`:`Audio blocked until a tap`)}let u=this.config?.createPlaybackEngine?await this.config.createPlaybackEngine():new t(24e3);if(s!==this.#d){u.destroy(),c.getTracks().forEach(e=>e.stop()),l.close().catch(()=>{});return}this.#c=u,this.#de(),this.#ee(l,c,s);let d=await this.#X();if(s!==this.#d){d?.ws.close(1e3,`client ended call`);return}if(d){let{ws:e}=d;this.#e=e,this.#Q(e,s),d.live||e.send(`{"type":"start"}`),this.#te(e),this.#ge(`listening`);return}let f=new WebSocket(this.#Z(o,r),[`runtype.bearer`,i]);f.binaryType=`arraybuffer`,this.#e=f,f.onopen=()=>{s===this.#d&&(this.#te(f),this.#ge(`listening`))},this.#Q(f,s)}catch(e){throw this.#he(),this.#_e(e),this.#ge(`error`),e}}#Q(e,t){e.onmessage=e=>this.#ne(e,t),e.onerror=()=>{t!==this.#d||this.#E||(this.#_e(/* @__PURE__ */ Error(`Voice connection failed`)),this.#ge(`error`),this.#he())},e.onclose=e=>{if(this.#f){this.#f=!1;return}if(t!==this.#d)return;let n=this.#E??(e.code===4408?{reason:`idle_timeout`}:null);if(n)this.#$(n.reason,n.message);else{if(e.code!==1e3){let t=e.code?` (code ${e.code})`:``;this.#_e(/* @__PURE__ */ Error(`Voice connection closed${t}`)),this.#ge(`error`)}else this.#ge(`idle`);this.#he()}}}#$(e,t){let n=i.includes(e)&&!this.#k;if(this.#he(),n)this.#k=!0,this.#l=!0,this.#D=e===`provider_error`?`${a} Reconnecting…`:`Reconnecting…`,this.#O=setTimeout(()=>{this.#l=!1,this.#A=!0,this.startListening().catch(()=>{})},1e3+Math.random()*1e3),this.#ge(`idle`);else{if(i.includes(e))return this.#D=a,this.#_e(/* @__PURE__ */ Error(a)),this.#ge(`error`);if(e===`error`)return this.#ge(`error`);this.#D=e===`quota`&&t||r.get(e)||null,this.#ge(`idle`)}}takeNotice(){let e=this.#D;return this.#D=null,e}getDisclosure(){let e=this.config?.disclosureText;return this.#l&&this.#h&&e!==!1?e||`You're talking to an AI assistant. Voice is processed by OpenAI.`:null}async stopListening(){this.#he(),this.#ge(`idle`)}async disconnect(){this.#he(),this.#ge(`disconnected`),this.#V=[],this.#H=[],this.#U=[],this.#W=[],this.#G=[]}stopPlayback(){this.#me();let e=this.#e;e&&e.readyState===WebSocket.OPEN&&(this.#h&&(this.#_=Date.now()+2e3,e.send(`{"type":"cancel"}`)),this.#ge(`listening`))}getInterruptionMode(){return`barge-in`}isBargeInActive(){return this.#l}onLevel(e){this.#n.push(e)}async deactivateBargeIn(){this.#he(),this.#ge(`idle`)}#ee(e,t,n){let r=e.createMediaStreamSource(t);this.#i=r;let i=e.createScriptProcessor(4096,1,1);this.#a=i,i.onaudioprocess=e=>{if(n!==this.#d)return;let t=this.#e,r=t?.readyState===WebSocket.OPEN;if(t&&!r&&t.readyState!==WebSocket.CONNECTING)return;let i=e.inputBuffer.getChannelData(0),a=new Int16Array(i.length),o=0;for(let e=0;e<i.length;e++){let t=Math.max(-1,Math.min(1,i[e]));o+=t*t,a[e]=t<0?t*32768:t*32767}if(t&&r)this.#te(t),t.send(a.buffer);else for(this.#o.push(a.buffer),this.#s+=a.byteLength;this.#s>256e3;)this.#s-=this.#o.shift().byteLength;if(this.#n.length>0){let e=Math.sqrt(o/i.length),t=Math.max(0,Math.min(1,e*4));for(let e of this.#n)e(t)}},r.connect(i),i.connect(e.destination)}#te(e){for(let t of this.#o)e.send(t);this.#o=[],this.#s=0}#ne(e,t){if(t!==this.#d)return;if(e.data instanceof ArrayBuffer){this.#le(e.data,t);return}let n;try{n=JSON.parse(e.data)}catch{return}switch(n.type){case`session_config`:n.speechMode&&(this.#h=n.speechMode===`speech_to_speech`,this.#c?.setContinuousMode?.(this.#h)),Array.isArray(n.capabilities)&&(this.#S=new Set(n.capabilities)),typeof n.callId==`string`&&(this.#T=n.callId),this.#x=this.#h&&this.#S.has(`client_delegation`)&&!!this.bridge,this.#h&&!this.#u&&this.#ge(`listening`),this.#S.has(`context`)&&!this.#j&&(this.#j=!0,this.#M=this.#re());break;case`transcript_update`:{let e=n.role===`assistant`?`assistant`:`user`,r=n.utteranceId??n.turnId;if(!r||e===`assistant`&&this.#ue())break;let i=String(r);if(e===`user`)n.final===!0&&this.#ie(t),this.#R=!1;else{if(this.#S.has(`delegation_read_back`)){let e=n.delegationId;(typeof e==`string`&&this.#F.has(e)&&!this.#z.has(i)||this.#R&&!this.#z.has(i))&&(this.#B.add(i),this.#R=!1)}else this.#R&&!this.#z.has(i)&&(this.#B.add(i),this.#R=!1);if(this.#z.add(i),this.#B.has(i))break}this.#ve(e,n.text??``,n.final===!0,{turnId:i,...typeof n.startMs==`number`&&{startMs:n.startMs},...typeof n.endMs==`number`&&{endMs:n.endMs},...this.#x&&{caption:!0}});break}case`delegation_started`:case`delegation_completed`:{let e=String(n.delegationId??n.turnId);if(n.type===`delegation_started`){this.#ie(t);let r=n.input;this.#x&&r&&this.#ce(e,{delegationId:e,userText:String(r.text??``),userUtteranceIds:Array.isArray(r.userUtteranceIds)?r.userUtteranceIds.filter(e=>typeof e==`string`):[],messages:Array.isArray(r.messages)?r.messages:[]},t)}else n.speak!==!1&&this.#F.has(e)&&!this.#I.delete(e)&&!this.#S.has(`delegation_read_back`)&&this.#oe(e);this.#g=n.type===`delegation_started`,!this.#u&&!this.#ue()&&this.#ge(this.#g?`processing`:`listening`);break}case`delegation_progress`:{let e=String(n.delegationId);this.#F.has(e)&&!this.#I.has(e)&&!this.#w.has(e)&&(this.#I.add(e),this.#oe(e));break}case`delegation_cancelled`:this.#se(String(n.delegationId),n.reason!==`session_ending`&&n.reason!==`provider_cancelled`);break;case`warning`:console.warn(`[Persona voice] ${n.code}: ${n.message??``}`,this.#T??``),n.delegationId&&(n.code===`UNKNOWN_DELEGATION`||n.code===`LATE_RESULT_LIMIT`)&&this.#se(String(n.delegationId));break;case`audio_clear`:this.#me(),this.#_=0,this.#ge(`listening`);break;case`transcript_interim`:this.#ge(`listening`),this.#ve(`user`,n.text??``,!1);break;case`transcript_final`:{let e=n.role===`assistant`?`assistant`:`user`;this.#ge(e===`user`?`processing`:`speaking`),this.#ve(e,n.text??``,!0);break}case`audio_end`:this.#c?this.#c.markStreamEnd():(this.#u=!1,this.#ge(`listening`));break;case`metrics`:this.#ye({llmMs:n.llm_ms,ttsMs:n.tts_ms,firstAudioMs:n.first_audio_ms,totalMs:n.total_ms});break;case`error`:this.#_e(Error(n.error||`Voice error`)),this.#ge(`error`);break;case`session_end`:this.#E={reason:String(n.reason),...typeof n.message==`string`&&{message:n.message}}}}async#re(){let e=this.bridge?.getHistory()??[],t=``;try{let e=this.config?.callContext;t=String((typeof e==`function`?await e():e)??``)}catch{}return o(e,t)}#ie(e){let t=this.#M;if(!t)return;this.#M=null;let n=!1,r=t.then(t=>{let r=this.#e;!n&&t&&e===this.#d&&r?.readyState===WebSocket.OPEN&&r.send(JSON.stringify({type:`context`,text:t}))});this.#N={done:r,drop:()=>n=!0}}async#ae(){let e=this.#N;if(!e)return;let t,n=await Promise.race([e.done.then(()=>!1),new Promise(e=>{t=setTimeout(()=>e(!0),2e3)})]);clearTimeout(t),n&&e.drop()}#oe(e){this.#R=!0,this.#L=e}#se(e,t=!1){this.#w.add(e),this.#L===e&&(this.#R=!1),this.#I.delete(e),this.bridge?.dropDelegation?.(e,t),this.#C.get(e)?.abort()}#ce(e,t,n){let r=this.bridge,i=({type:t,...r})=>{let i=this.#e;return n!==this.#d||i?.readyState!==WebSocket.OPEN||this.#w.has(e)?!1:(i.send(JSON.stringify({type:t,delegationId:e,...r})),!0)},a=(e,t,n,r=0)=>i({type:e,status:t,text:n,...r>0&&{streamedChars:r}});this.#P=this.#P.then(async()=>{if(n!==this.#d||this.#w.has(e))return;let o=null,s=this.#S.has(`delegation_stream`)&&this.#S.has(`delegation_read_back`),c=t=>{o=(o??this.#ae()).then(()=>{i({type:`delegation_delta`,text:t})&&this.#F.add(e)})},l=await r.runDelegatedTurn(s?{...t,stream:c}:t).catch(()=>({status:`failed`,text:``}));await this.#ae(),await o;let u=s?l.streamedChars??0:0,d=!!l.followUp,f=d&&this.#S.has(`delegation_update`);if(!a(f?`delegation_update`:`delegation_result`,d&&!f?`completed`:l.status,l.text,u)||(l.status!==`failed`&&l.inChat!==!1&&this.#F.add(e),!l.followUp))return;let p=new AbortController;this.#C.set(e,p),l.followUp({signal:p.signal,approvalTimeoutMs:this.config?.approvalTimeoutMs&&Math.min(this.config.approvalTimeoutMs,54e4),readBack:f,onUpdate:e=>a(`delegation_update`,`pending_approval`,e)}).then(t=>{this.#C.delete(e),f&&t&&!p.signal.aborted&&a(`delegation_result`,t.status,t.text)})})}#le(e,t){if(t!==this.#d||!this.#c||this.#ue())return;let n=s(e);n.length!==0&&(this.#u||(this.#u=!0,this.#ge(`speaking`)),this.#de(),this.#c.enqueue(n),this.#h&&this.#fe(n.length))}#ue(){return Date.now()<this.#_}#de(){if(this.#b||!this.#c)return;this.#b=!0;let e=this.#d;this.#c.onFinished(()=>{e===this.#d&&(this.#b=!1,this.#pe())})}#fe(e){let t=Date.now();this.#v=Math.max(t,this.#v)+e/48,clearTimeout(this.#y),this.#y=setTimeout(()=>this.#pe(),this.#v-t+300)}#pe(){this.#u=!1,this.#v=0,this.#e&&this.#e.readyState===WebSocket.OPEN&&this.#ge(this.#g?`processing`:`listening`)}#me(){this.#c?.flush(),this.#b=!1,this.#de(),this.#u=!1,this.#v=0,clearTimeout(this.#y)}#he(){this.#d+=1,this.#l=!1,this.#u=!1,this.#Y(),this.#h=!1,this.#g=!1,this.#x=!1,this.#S=/* @__PURE__ */ new Set;for(let e of this.#C.values())e.abort();if(this.#C.clear(),this.#w.clear(),this.#T=void 0,this.#E=null,clearTimeout(this.#O),this.#j=!1,this.#M=null,this.#N=null,this.#P=Promise.resolve(),this.#F.clear(),this.#I.clear(),this.#L=null,this.#R=!1,this.#z.clear(),this.#B.clear(),this.#b=!1,this.#_=0,this.#v=0,clearTimeout(this.#y),this.#o=[],this.#s=0,this.#a&&=(this.#a.onaudioprocess=null,this.#a.disconnect(),null),this.#i&&=(this.#i.disconnect(),null),this.#r&&=(this.#r.getTracks().forEach(e=>e.stop()),null),this.#t&&=(this.#t.close().catch(()=>{}),null),this.#c&&=(this.#c.destroy(),null),this.#e){this.#f=!0;try{this.#e.close(1e3,`client ended call`)}catch{}this.#e=null}}onResult(e){this.#V.push(e)}onError(e){this.#H.push(e)}onStatusChange(e){this.#U.push(e)}onTranscript(e){this.#W.push(e)}onMetrics(e){this.#G.push(e)}#ge(e){this.#U.forEach(t=>t(e))}#_e(e){this.#H.forEach(t=>t(e))}#ve(e,t,n,r){this.#W.forEach(i=>i(e,t,n,r))}#ye(e){this.#G.forEach(t=>t(e))}},f=class{#e;#t;#n;#r;#i;#a;constructor(e={}){this.config=e,this.type=`browser`,this.#e=null,this.#t=[],this.#n=[],this.#r=[],this.#i=!1,this.#a=typeof window<`u`?window:void 0}async connect(){this.#r.forEach(e=>e(`connected`))}async startListening(){try{if(this.#i)throw Error(`Already listening`);if(!this.#a)throw Error(`Window object not available`);let e=this.#a.SpeechRecognition||this.#a.webkitSpeechRecognition;if(!e)throw Error(`Browser speech recognition not supported`);this.#e=new e,this.#e.lang=this.config?.language||`en-US`,this.#e.continuous=this.config?.continuous||!1,this.#e.interimResults=!0,this.#e.onresult=e=>{let t=Array.from(e.results).map(e=>e[0]).map(e=>e.transcript).join(``),n=e.results[e.results.length-1].isFinal;this.#t.forEach(e=>e({text:t,confidence:n?.8:.5,provider:`browser`})),n&&!this.config?.continuous&&this.stopListening()},this.#e.onerror=e=>{this.#n.forEach(t=>t(Error(e.error))),this.#r.forEach(e=>e(`error`))},this.#e.onstart=()=>{this.#i=!0,this.#r.forEach(e=>e(`listening`))},this.#e.onend=()=>{this.#i=!1,this.#r.forEach(e=>e(`idle`))},this.#e.start()}catch(e){throw this.#n.forEach(t=>t(e)),this.#r.forEach(e=>e(`error`)),e}}async stopListening(){this.#e&&=(this.#e.stop(),null),this.#i=!1,this.#r.forEach(e=>e(`idle`))}onResult(e){this.#t.push(e)}onError(e){this.#n.push(e)}onStatusChange(e){this.#r.push(e)}async disconnect(){await this.stopListening(),this.#r.forEach(e=>e(`disconnected`))}static isSupported(){return`SpeechRecognition`in window||`webkitSpeechRecognition`in window}};function p(e){switch(e.type){case`runtype`:if(!e.runtype)throw Error(`Runtype voice provider requires configuration`);return new d(e.runtype);case`browser`:if(!f.isSupported())throw Error(`Browser speech recognition not supported`);return new f(e.browser||{});case`custom`:{let t=e.custom;if(!t)throw Error("Custom voice provider requires a `custom` provider instance or factory");let n=typeof t==`function`?t():t;if(!n||typeof n.startListening!=`function`)throw Error("Custom voice provider `custom` must be a VoiceProvider (or a factory returning one)");return n}default:throw Error(`Unknown voice provider type: ${e.type}`)}}function m(e){if(e?.type===`custom`&&e.custom)return p({type:`custom`,custom:e.custom});if(e?.type===`runtype`&&e.runtype)return p({type:`runtype`,runtype:e.runtype});if(f.isSupported())return p({type:`browser`,browser:e?.browser||{language:`en-US`}});throw Error(`No supported voice providers available`)}function h(e){try{return m(e),!0}catch{return!1}}const g=e=>e.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,``);var _=class{constructor(e){this.host=e,this.turns=/* @__PURE__ */ new Map,this.latestId=null,this.cancelled=/* @__PURE__ */ new Set}apply(e,t,n,r,i,a){let o=this.host;t=t.trim();let s=this.turns.get(r);if(s||(s={},this.turns.set(r,s),this.latestId=r),e===`user`){s.userFinal=n;let e=s.userId?o.find(s.userId):void 0;if(e){let r=s.userStartMs===void 0&&i!==void 0;r&&(s.userStartMs=i),o.upsert({...e,content:t,voiceProcessing:!n,...r&&this.placeBefore(this.startedAfter(i))})}else{s.userStartMs=i;let e=s.assistantId?o.find(s.assistantId):void 0;s.userId=o.inject({role:`user`,content:t,streaming:!1,voiceProcessing:!n,voiceCaption:a,...this.placeBefore(this.startedAfter(i)??e)}).id}}else if(!this.cancelled.has(r)){s.assistantFinal=n;let e=s.assistantId?o.find(s.assistantId):void 0;if(e){let r=s.assistantStartMs===void 0&&i!==void 0;r&&(s.assistantStartMs=i),o.upsert({...e,content:t,streaming:!n,voiceProcessing:!n,...r&&this.placeBefore(this.startedAfter(i))})}else t&&(s.assistantStartMs=i,s.assistantId=o.inject({role:`assistant`,content:t,streaming:!n,voiceProcessing:!n,voiceCaption:a,...this.placeBefore(this.startedAfter(i))}).id);n&&s.assistantId&&o.markSpoken(s.assistantId)}this.sync()}claimUserTurn(e,t=[]){let n=t[t.length-1];for(let e of t)e!==n&&(this.turns.has(e)||this.turns.set(e,{}),this.turns.get(e).claimed=!0);let r;if(n){if(r=this.turns.get(n),r||(r={},this.turns.set(n,r)),r.claimed)return null;(!r.userId||!this.host.find(r.userId))&&(r.userFinal=!0,r.userId=this.host.inject({role:`user`,content:e.trim()}).id)}else{let t=g(e),n=[...this.turns.values()].reverse().flatMap(e=>{let t=e.userId&&!e.claimed?this.host.find(e.userId):void 0;return t?[{turn:e,text:g(t.content)}]:[]}).filter(({text:e})=>e&&t);r=(n.find(({text:e})=>e===t)??n.find(({text:e})=>e.startsWith(t)||t.startsWith(e)))?.turn}return r?(r.claimed=!0,r.userId):null}cancel(){let e=this.inFlight();if(e.length===0){let t=this.awaiting();if(!t)return;e=[t]}for(let t of e)this.cancelled.add(t);this.closeReplies(e),this.sync()}fail(e){let t=this.awaiting(),n=this.inFlight();if(!t&&n.length===0)return;for(let e of n)this.turns.get(e).assistantFinal=!0;this.closeReplies(n);let r=this.host.inject({role:`assistant`,content:e,streaming:!1,voiceProcessing:!1}),i=t?this.turns.get(t):void 0;i&&(i.assistantId=r.id,i.assistantFinal=!0),this.sync()}settle(){if(this.turns.size===0)return;let e=this.pending(),t=/* @__PURE__ */ new Set;for(let e of this.turns.values())e.userId&&t.add(e.userId),e.assistantId&&(t.add(e.assistantId),this.host.markSpoken(e.assistantId));this.turns.clear(),this.latestId=null,this.host.settle(t),e&&this.host.setStreaming(!1)}startedAfter(e){if(e===void 0)return;let t;for(let n of this.turns.values())for(let[r,i]of[[n.userId,n.userStartMs],[n.assistantId,n.assistantStartMs]])r&&i!==void 0&&i>e&&(!t||i<t.start)&&(t={start:i,id:r});return t&&this.host.find(t.id)}placeBefore(e){if(!e)return;let t=e.sequence??0,n=t-1;for(let r of this.turns.values())for(let i of[r.userId,r.assistantId]){let r=i?this.host.find(i):void 0,a=r?.sequence;r?.createdAt===e.createdAt&&a!==void 0&&a>n&&a<t&&(n=a)}return{createdAt:e.createdAt,sequence:(n+t)/2}}closeReplies(e){let t=/* @__PURE__ */ new Set;for(let n of e){let e=this.turns.get(n)?.assistantId;e&&(t.add(e),this.host.markSpoken(e))}t.size>0&&this.host.settle(t)}inFlight(){let e=[];for(let[t,n]of this.turns)n.assistantId&&!n.assistantFinal&&!this.cancelled.has(t)&&e.push(t);return e}awaiting(){let e=this.latestId;if(!e)return null;let t=this.turns.get(e);return t?.userFinal&&!t.assistantId&&!t.claimed&&!this.cancelled.has(e)?e:null}pending(){return this.awaiting()!==null||this.inFlight().length>0}sync(){this.host.setStreaming(this.pending())}};const v=/* @__PURE__ */ new Set(/* @__PURE__ */ `mr,mrs,ms,dr,prof,sr,jr,st,mt,vs,etc,approx,inc,ltd,co,corp,no,nos,fig,ft,dept,est,min,max,e.g,i.e,a.m,p.m,u.s,u.k`.split(`,`)),y=/([.!?]+|[,;:])(["'”’)\]*_]*)(\s+)|\n\s*/g;function b(e){let t=/(\S+)$/.exec(e)?.[1];if(!t)return!1;let n=t.replace(/^[("'“‘[*_]+/,``).toLowerCase();if(v.has(n)||/^[a-z](?:\.[a-z])*$/.test(n))return!0;if(/^\d+$/.test(t)){let n=e.length-t.length;return n===0||/\n\s*$/.test(e.slice(0,n))}return!1}function*x(e,t){let n=new RegExp(y.source,`g`);for(let r=n.exec(e);r;r=n.exec(e)){let n=r[1];if(!e.slice(0,r.index+(n?.length??0)).trim()||n!==void 0&&(/^[,;:]$/.test(n)&&!t||n===`.`&&b(e.slice(0,r.index))))continue;let i=r.index+r[0].length;i<e.length&&(yield i)}}function S(e,t){let n=e.slice(0,t),r=0;for(let e of n.matchAll(/\s+/g))n.slice(0,e.index).trim()&&(r=e.index+e[0].length);return r}function C(e){let t=0;for(let n of x(e.slice(0,220),!0))t=n;if(t)return t;let n=S(e,220);if(n)return n;let r=e.charCodeAt(219);return r>=55296&&r<=56319?219:220}var w=class{#e=``;#t=!0;push(e){return this.#e+=e,this.#n()}flush(){let e=this.#n(),t=this.#e;if(this.#e=``,t.trim()){for(let n=0;n<t.length;){let r=t.slice(n),i=r.length>220?C(r):r.length;e.push(r.slice(0,i)),n+=i}this.#t=!1}return e}idle(){let e=this.#e;if(!e.trim()||!/(?:[.!?…]["'”’)\]*_]*|\n)\s*$/.test(e))return[];this.#e=``,this.#t=!1;let t=[];for(let n=0;n<e.length;){let r=e.slice(n),i=r.length>220?C(r):r.length;t.push(r.slice(0,i)),n+=i}return t}#n(){let e=[];for(let t=this.#r();t>0;t=this.#r())e.push(this.#e.slice(0,t)),this.#e=this.#e.slice(t),this.#t=!1;return e}#r(){let e=this.#e;if(!e.trim())return 0;let t=x(e,this.#t).next().value??0;if(t&&t<=220)return t;let n=/[.!?]["'”’)\]*_]*$/.test(e);if(this.#t&&!n){let t=e.match(/\S+\s+/g)?.length??0;if(e.trim().length>=30||t>=6){let t=S(e,220);if(t)return t}}return e.length>220?C(e):0}};const T=(e,t)=>{let n=0;for(;n<e.length&&n<t.length&&e[n]===t[n];)n+=1;return n},E=`no|nope|never ?mind|decline(?: it| that)?|cancel(?: it| that| this)?(?: (?:the|my|that|this) (?:[a-z]+ )?(?:order|request))?|don'?t (?:do|place) (?:it|that|the (?:[a-z]+ )?order)`,D=RegExp(`^(?:${E})(?: (?:${E}))?(?: thanks| thank you)?$`),O=e=>D.test(e.toLowerCase().replace(/[^a-z' ]+/g,` `).replace(/\s+/g,` `).trim()),k=()=>new Promise(e=>setTimeout(e,50)),A=(e,t)=>e.length>t?`${e.slice(0,t-1).trimEnd()}…`:e,j=e=>e.replace(/^webmcp:/,``).replace(/([a-z\d])([A-Z])/g,`$1 $2`).replace(/[_\-.]+/g,` `).trim().toLowerCase();function M(e,t=0){if(e==null||e===``)return``;if(typeof e!=`object`)return String(e);if(t>2)return``;if(Array.isArray(e))return e.map(e=>M(e,t+1)).filter(Boolean).join(`, `);let n=e,r=n.quantity??n.qty??n.count,i=n.name??n.item??n.product??n.title;return r!=null&&i!=null&&typeof i!=`object`?`${r} ${i}`:Object.entries(n).filter(([e])=>!e.startsWith(`_`)).map(([e,n])=>{let r=M(n,t+1);return r&&(t?`${j(e)} ${r}`:`${j(e)}: ${r}`)}).filter(Boolean).join(t?`, `:`; `)}function N(e){let t=e.length>1?`These actions need the user's approval in the chat before they happen:`:`This action needs the user's approval in the chat before it happens:`,n=1e3-t.length-119-2;return`${t}\n${e.map(t=>{let r=[j(t.toolName)];t.description&&r.push(`(${A(t.description.trim(),160)})`);let i=M(t.parameters);return i&&r.push(`with ${i}`),t.reason&&r.push(`because: ${A(t.reason.trim(),160)}`),A(`- ${r.join(` `)}`,Math.floor(n/e.length)-1)}).join(`
|
|
5
|
-
`)}\n\nBriefly tell the user what you're about to do and ask them to approve or decline it in the chat. Don't claim it's done.`}const P=e=>e.variant===`approval`&&e.approval?.status===`pending`;function F(e){let t=null,n=null,r=()=>e.track(t??n),i=[],a=/* @__PURE__ */ new Map,o=/* @__PURE__ */ new Map,s=(e,t)=>{a.set(e,[...a.get(e)??[],t]),a.size>32&&a.delete(a.keys().next().value)},c=t=>e.messages().find(e=>e.id===t),l=e=>e.approvals.filter(e=>c(e)?.approval?.status===`pending`),u=e=>j(c(e)?.approval?.toolName??``),d=(t,n,r)=>{t.outcome={status:n,text:r};for(let n of l(t))e.decide(n)},f=t=>t.flatMap(t=>e.messages().find(e=>e.id===t)??[]).filter(e=>!e.variant&&e.content.trim()).map(e=>e.content.trim()).join(`
|
|
6
|
-
|
|
7
|
-
`),p=async t=>{do await k();while(e.busy()&&!(t&&(e.parked()||t.failed)))};return{getHistory:()=>{let t=e.messages().filter(e=>(e.role===`user`||e.role===`assistant`)&&!e.variant&&!e.voiceProcessing&&!e.streaming&&e.content),n=!1,r=[];for(let e=t.length-1;e>=0;--e){let i=t[e];if(i.role===`user`)n=!1;else if(!i.voiceCaption)n=!0;else if(n)continue;r.unshift({role:i.role,content:i.content})}return r},dropDelegation(t,n){o.set(t,!!n),o.size>32&&o.delete(o.keys().next().value);let r=n&&i.find(e=>e.delegationId===t);r&&!r.outcome&&d(r,`timeout`,``);for(let n of a.get(t)??[])n.stopStream?.(),n.dropped=!0,n.ids.length&&e.unspoken(n.ids)},async runDelegatedTurn({delegationId:m,userText:h,userUtteranceIds:g,stream:_}){let v=i.filter(e=>l(e).length),y=v.flatMap(l);if(y.length===1&&O(h)){let e=u(y[0]);return d(v[0],`denied`,``),{status:`denied`,text:`Okay, I cancelled the ${e} request. Nothing was done.`,inChat:!1}}let b=e.parked()&&e.messages().every(e=>!P(e)||y.includes(e.id));e.busy()&&!b&&await p();let x=new Set(e.messages().map(e=>e.id)),S=()=>e.messages().filter(e=>!x.has(e.id)&&e.variant===`approval`),C={ids:[],failed:!1},E=_?new w:null,D=``,A=``,M=!1,F=e=>{for(let t of e)A+=t,_(t)},I;E&&(C.onUpdate=()=>{if(M)return;let e=f(C.ids);if(!e.startsWith(D)){M=!0,clearTimeout(I);return}if(e.length===D.length)return;let t=e.slice(D.length);D=e,F(E.push(t)),clearTimeout(I),I=setTimeout(()=>{M||F(E.idle())},200)},C.stopStream=()=>{clearTimeout(I),M=!0,C.onUpdate=void 0}),s(m,C),t=C,r();try{await e.send(h,e.claim(h,g)??void 0),await p(C)}catch{C.failed=!0}finally{t=null,r()}E&&(C.onUpdate?.(),clearTimeout(I),M||F(E.flush()),C.onUpdate=void 0,C.stopStream=void 0);let L=e=>{let t=T(A,e);return t>0?{streamedChars:t}:{}},R=C.ids.flatMap(t=>e.messages().find(e=>e.id===t)??[]),z=f(C.ids),B=C.failed?[]:S().filter(P),V=new Set(B.map(e=>j(e.approval.toolName)));for(let t of v){if(t.outcome)continue;for(let n of l(t).filter(e=>V.has(u(e))))t.replaced.add(n),e.decide(n);let n=u(t.approvals[0]);(t.approvals.every(e=>t.replaced.has(e))||b&&t.approvals.every(e=>c(e)?.approval?.status===`denied`))&&(t.outcome={status:`cancelled`,text:`The earlier ${n} request was replaced by the new one; it was not done.`})}if(B.length){let c=new Set(B.map(e=>e.id)),p={delegationId:m,approvals:[...c],at:Date.now(),replaced:/* @__PURE__ */ new Set};return i.push(p),o.get(m)&&d(p,`timeout`,``),z=`${z}\n\n${N(B.map(e=>e.approval))}`.trim(),{status:`pending_approval`,text:z,...L(z),followUp:async({signal:o,approvalTimeoutMs:h=3e5,readBack:g=!0,onUpdate:_})=>{let v={ids:[],failed:!1};a.delete(m),s(m,v);let y=()=>S().filter(e=>c.has(e.id)||v.ids.includes(e.id)),b=()=>S().filter(e=>P(e)&&!c.has(e.id)&&!i.some(t=>t.approvals.includes(e.id))&&(v.ids.includes(e.id)||e.approval?.toolType===`webmcp`&&n===v)),x=()=>{n=v,r()};g&&x();try{for(;!o.aborted&&!p.outcome&&(t||e.busy()||y().some(P));){g&&n!==v&&!l(p).length&&x();let t=b();if(t.length){for(let e of t)c.add(e.id),p.approvals.push(e.id);p.at=Date.now(),_?.(N(t.map(e=>e.approval)))}if(Date.now()-p.at>=h&&l(p).length&&(!e.busy()||e.parked())){d(p,`timeout`,`That ${u(p.approvals[0])} request expired, so nothing was done.`);break}await k()}}finally{n===v&&(n=null,r()),i.splice(i.indexOf(p),1)}if(o.aborted)return null;if(p.outcome)return p.outcome;if(v.failed)return{status:`failed`,text:`The ${u(p.approvals[0])} request didn't complete.`};let C=y(),w=e=>C.filter(e).map(e=>j(e.approval.toolName)).join(`, `),T=C.some(e=>e.approval?.status===`approved`),E=w(e=>e.approval?.status===`denied`&&!p.replaced.has(e.id)),D=w(e=>p.replaced.has(e.id)),O=w(e=>e.approval?.status===`timeout`),A=[T?f(v.ids):``,E&&`The user declined the ${E} request in the chat, so ${T?`that part was not done`:`nothing was done`}.`,D&&`The earlier ${D} request was replaced by the new one; it was not done.`,O&&`The ${O} request timed out, so it was not done.`].filter(Boolean).join(`
|
|
8
|
-
|
|
9
|
-
`);return{status:T?`completed`:E?`denied`:D?`cancelled`:O?`timeout`:`completed`,text:A}}}}return!C.failed&&e.parked()||R.some(e=>e.agentMetadata?.awaitingLocalTool&&!e.agentMetadata.askUserQuestionAnswered&&!e.toolCall?.name?.startsWith(`webmcp:`))?(z=`${z}\n\nI need your answer in the chat before I can continue.`.trim(),{status:C.failed?`failed`:`completed`,text:z,...L(z)}):{status:!C.failed&&z?`completed`:`failed`,text:z,...L(z)}}}}const I=(e,t,n,r)=>{let i=e.session;try{e.keyed=n?new n({find:e=>i.messages.find(t=>t.id===e),inject:e=>i.injectMessage(e),upsert:e=>i.upsertMessage(e),settle:e=>{i.messages=i.messages.map(t=>e.has(t.id)?{...t,streaming:!1,voiceProcessing:!1}:t),i.callbacks.onMessagesChanged([...i.messages])},setStreaming:t=>{(t||!e.busy())&&e.setStreaming(t)},markSpoken:e=>{i.ttsSpokenMessageIds.add(e)}}):null,r&&t.setSessionBridge&&t.setSessionBridge(r({messages:()=>i.messages,busy:()=>e.busy(),parked:()=>i.webMcpApprovalResolvers.size>0,claim:(t,n)=>e.keyed?.claimUserTurn(t,n)??null,send:(e,t)=>i.sendMessage(e,{viaVoice:!0,voiceTurn:{userMessageId:t}}),track:t=>{e.setDelegation(t)},unspoken:e=>{for(let t of e)i.ttsSpokenMessageIds.delete(t);i.streaming||i.speakLatestAssistantMessage(e)},decide:e=>{if(i.webMcpApprovalResolvers.has(e))return i.resolveWebMcpApproval(e,`denied`);let t=i.messages.find(t=>t.id===e)?.approval;t?.status===`pending`&&i.resolveApproval(t,`denied`)}}));let a=e.generation(),o=()=>e.provider()===t&&a===e.generation(),s=(i.config.voiceRecognition??{}).processingErrorText??`Voice processing failed. Please try again.`;t.onResult(e=>{o()&&e.provider!==`runtype`&&e.text&&e.text.trim()&&i.sendMessage(e.text,{viaVoice:!0})}),t.onTranscript&&t.onTranscript((t,n,r,a)=>{if(o()){if(a?.turnId)e.keyed?.apply(t,n,r,a.turnId,a.startMs,a.caption);else if(t===`user`)e.pendingUser?i.upsertMessage({id:e.pendingUser,role:`user`,content:n,createdAt:(/* @__PURE__ */ new Date()).toISOString(),streaming:!1,voiceProcessing:!r}):e.pendingUser=i.injectMessage({role:`user`,content:n,streaming:!1,voiceProcessing:!r}).id,r&&(e.turns().start(a?.turnId),e.pendingUser=null,e.pendingAssistant=i.injectMessage({role:`assistant`,content:``,streaming:!0,voiceProcessing:!0}).id,e.setStreaming(!0));else{if(!e.turns().accepts(a?.turnId))return;e.pendingAssistant?i.upsertMessage({id:e.pendingAssistant,role:`assistant`,content:n,createdAt:(/* @__PURE__ */ new Date()).toISOString(),streaming:!r,voiceProcessing:!r}):e.pendingAssistant=i.injectMessage({role:`assistant`,content:n,streaming:!r,voiceProcessing:!r}).id,r&&(e.pendingAssistant&&i.ttsSpokenMessageIds.add(e.pendingAssistant),e.setStreaming(!1),e.pendingAssistant=null)}}}),t.onLevel&&t.onLevel(t=>{o()&&e.setLevel(Number.isFinite(t)?Math.max(0,Math.min(1,t)):0)}),t.onMetrics&&t.onMetrics(e=>{o()&&i.config.voiceRecognition?.onMetrics?.(e)}),t.onError(t=>{o()&&(console.error(`Voice error:`,t),e.pendingAssistant&&=(i.upsertMessage({id:e.pendingAssistant,role:`assistant`,content:s,createdAt:(/* @__PURE__ */ new Date()).toISOString(),streaming:!1,voiceProcessing:!1}),e.setStreaming(!1),e.pendingUser=null,null),e.keyed?.fail(s))}),t.onStatusChange(t=>{o()&&(e.setStatus(t),(t===`listening`||t===`idle`||t===`disconnected`)&&e.settlePending(t!==`listening`),(t===`idle`||t===`disconnected`)&&e.keyed?.settle(),i.callbacks.onVoiceStatusChanged?.(t))}),t.connect()}catch(e){console.error(`Failed to setup voice:`,e)}},L=e=>{let t=t=>{if(e.original)return;let n=e.config().voiceRecognition??{};e.original={backgroundColor:t.style.backgroundColor,color:t.style.color,borderColor:t.style.borderColor,iconName:n.iconName??`mic`,iconSize:parseFloat(n.iconSize??``)||e.iconFallbackPx}},n=(t,n,r)=>{let i=t.querySelector(`svg`);i&&i.remove();let a=e.original?.iconSize??(parseFloat(e.config().voiceRecognition?.iconSize??``)||e.iconFallbackPx),o=e.renderIcon(n,a,r,1.5);o&&t.appendChild(o)},r=e=>{e.classList.remove(`persona-voice-recording`,`persona-voice-processing`,`persona-voice-speaking`)};return{recording:()=>{let n=e.mic();if(!n)return;t(n);let i=e.config().voiceRecognition??{},a=i.recordingBackgroundColor,o=i.recordingIconColor,s=i.recordingBorderColor;if(r(n),n.classList.add(`persona-voice-recording`),e.setMicState(`recording`),n.style.backgroundColor=a??`var(--persona-voice-recording-bg, #ef4444)`,n.style.color=o??`var(--persona-voice-recording-indicator, #ffffff)`,o){let e=n.querySelector(`svg`);e&&e.setAttribute(`stroke`,o)}s&&(n.style.borderColor=s),n.setAttribute(`aria-label`,`Stop voice recognition`)},processing:()=>{let i=e.mic();if(!i)return;t(i);let a=e.config().voiceRecognition??{},o=e.session().getVoiceInterruptionMode(),s=e.original,c=a.processingIconName??`loader`,l=a.processingIconColor??s?.color??``,u=a.processingBackgroundColor??s?.backgroundColor??``,d=a.processingBorderColor??s?.borderColor??``;r(i),i.classList.add(`persona-voice-processing`),e.setMicState(`processing`),i.style.backgroundColor=u,i.style.borderColor=d;let f=l||`currentColor`;i.style.color=f,n(i,c,f),i.setAttribute(`aria-label`,`Processing voice input`),o===`none`&&(i.style.cursor=`default`)},speaking:()=>{let i=e.mic();if(!i)return;t(i);let a=e.config().voiceRecognition??{},o=e.session().getVoiceInterruptionMode(),s=e.original,c=o===`cancel`?`square`:o===`barge-in`?`mic`:`volume-2`,l=a.speakingIconName??c,u=a.speakingIconColor??(o===`barge-in`?a.recordingIconColor??s?.color??``:s?.color??``),d=a.speakingBackgroundColor??(o===`barge-in`?a.recordingBackgroundColor??`var(--persona-voice-recording-bg, #ef4444)`:s?.backgroundColor??``),f=a.speakingBorderColor??(o===`barge-in`?a.recordingBorderColor??``:s?.borderColor??``);r(i),i.classList.add(`persona-voice-speaking`),e.setMicState(`speaking`),i.style.backgroundColor=d,i.style.borderColor=f;let p=u||`currentColor`;i.style.color=p,n(i,l,p);let m=o===`cancel`?`Stop playback and re-record`:o===`barge-in`?`Speak to interrupt`:`Agent is speaking`;i.setAttribute(`aria-label`,m),o===`none`&&(i.style.cursor=`default`),o===`barge-in`&&i.classList.add(`persona-voice-recording`)},reset:()=>{let t=e.mic();if(!t)return;r(t),e.setMicState(`idle`);let i=e.original;i&&(t.style.backgroundColor=i.backgroundColor??``,t.style.color=i.color??``,t.style.borderColor=i.borderColor??``,n(t,i.iconName,i.color||`currentColor`),e.original=null),t.style.cursor=``,t.setAttribute(`aria-label`,`Start voice recognition`)}}};export{f as BrowserVoiceProvider,_ as KeyedVoiceTranscript,d as RuntypeVoiceProvider,m as createBestAvailableVoiceProvider,L as createMicStateStyles,p as createVoiceProvider,F as createVoiceSessionBridge,h as isVoiceSupported,I as wireSessionVoice};
|
package/dist/chunk-HUOUif-P.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=e=>e.startsWith(`webmcp:`)?e.slice(7):e,t={warn(e,...t){typeof console<`u`&&typeof console.warn==`function`&&console.warn(`[Persona/WebMCP] ${e}`,...t)}};var n=class{#e;#t;#n=!1;#r=null;#i=!1;#a;constructor(e,t){this.config=e,this.#a=t,this.#e=e.onConfirm??null,this.#t=3e4}setConfirmHandler(e){this.#e=e}isOperational(){return this.config.enabled!==!0||!this.#n?!1:this.#c()!==null}async snapshotForDispatch(){if(await this.#o(),this.config.enabled!==!0)return[];let e=this.#c();if(!e)return[];let n;try{n=(await e.getTools()).filter(f)}catch(e){return t.warn(`getTools() threw: shipping an empty WebMCP snapshot.`,e),[]}this.#a.recordToolDisplayTitles(n);let r=typeof location<`u`?location.origin:``;return n.filter(e=>this.#u(e.name)).map(e=>{let t=a(e.annotations);return{name:e.name,description:e.description,parametersSchema:o(e.inputSchema),origin:`webmcp`,...r?{pageOrigin:r}:{},...t?{annotations:t}:{},...t?.untrustedContentHint===!0?{untrustedContentHint:!0}:{}}})}async executeToolCall(t,n,r){if(await this.#o(),this.config.enabled!==!0)return l(`WebMCP is not enabled on this widget.`);let i=this.#c();if(!i){let e=typeof document<`u`&&!!document.modelContext;return l(e?`WebMCP is not operational: document.modelContext is present but does not expose the strict getTools()/executeTool() surface (likely a different or older WebMCP polyfill).`:`WebMCP bridge is not operational on this page (document.modelContext not available).`)}let a=e(t),o;try{o=(await i.getTools()).filter(f)}catch(e){let t=e instanceof Error?e.message:String(e);return l(`Failed to read WebMCP registry: ${t}`)}this.#a.recordToolDisplayTitles(o);let u=o.find(e=>e.name===a);if(!u)return l(`WebMCP tool not registered on this page: ${a}`);if(!this.#u(a))return l(`WebMCP tool not allowed by client allowlist: ${a}`);if(r?.aborted)return l(`Aborted by cancel()`);let d=this.#a.getToolDisplayTitle(a),p={toolName:a,args:n,description:u.description,...d?{title:d}:{},reason:`gate`};if(!await this.#l(p))return l(`User declined the tool call.`);if(r?.aborted)return l(`Aborted by cancel()`);let m=new AbortController,h=!1,g=setTimeout(()=>{h=!0,m.abort()},this.#t),_=()=>m.abort();r&&(r.aborted?m.abort():r.addEventListener(`abort`,_,{once:!0}));try{let e=await i.executeTool(u,c(n),{signal:m.signal});return s(e)}catch(e){if(h)return l(`WebMCP tool '${a}' timed out after ${this.#t}ms`);if(r?.aborted)return l(`Aborted by cancel()`);let t=e instanceof Error?e.message:String(e);return l(t)}finally{clearTimeout(g),r&&r.removeEventListener(`abort`,_)}}#o(){return this.config.enabled===!0?(this.#r||=this.#s(),this.#r):Promise.resolve()}async#s(){try{if(this.#c()){this.#n=!0;return}let e=await this.#a.loadPolyfill();`initializeWebMCPPolyfill`in e?e.initializeWebMCPPolyfill():e.installWebMCP(),this.#n=!0}catch(e){t.warn(`Failed to load @mcp-b/webmcp-polyfill: WebMCP consumption disabled.`,e),this.#n=!1}}#c(){if(typeof document>`u`)return null;let e=document.modelContext;if(!e||typeof e!=`object`)return null;let n=e;return typeof n.getTools!=`function`||typeof n.executeTool!=`function`?(this.#i||(this.#i=!0,t.warn(`document.modelContext is present but does not expose getTools()/executeTool(): WebMCP consumption is disabled. Another (incompatible or older) WebMCP polyfill likely installed document.modelContext before Persona. Remove it, or use a polyfill implementing the strict standard surface (e.g. @mcp-b/webmcp-polyfill).`)),null):e}async#l(e){let n=this.#e??u;try{return await n(e)}catch(n){return t.warn(`Confirm handler threw for WebMCP tool '${e.toolName}'; declining.`,n),!1}}#u(e){let t=this.config.allowlist;return!t||t.length===0||t.some(t=>r(e,t))}};const r=(e,t)=>{if(t===`*`)return!0;let n=t.replace(/[.+?^${}()|[\]\\]/g,`\\$&`);return RegExp(`^`+n.replace(/\*/g,`.*`)+`$`).test(e)},i={type:`object`,properties:{}},a=e=>{if(!e||typeof e!=`object`)return;let t={...typeof e.readOnlyHint==`boolean`?{readOnlyHint:e.readOnlyHint}:{},...typeof e.untrustedContentHint==`boolean`?{untrustedContentHint:e.untrustedContentHint}:{}};return Object.keys(t).length>0?t:void 0},o=e=>{if(e===void 0||e===``)return{...i};if(typeof e==`object`)return e!==null&&!Array.isArray(e)?e:{...i};try{let t=JSON.parse(e);return typeof t==`object`&&t&&!Array.isArray(t)?t:{...i}}catch{return{...i}}},s=e=>{if(e==null)return{content:[{type:`text`,text:``}]};let t;try{t=JSON.parse(e)}catch{return{content:[{type:`text`,text:e}]}}return typeof t==`object`&&t&&Array.isArray(t.content)?t:{content:[{type:`text`,text:typeof t==`string`?t:p(t)}]}},c=e=>{let t;try{t=JSON.stringify(e)??`{}`}catch{t=`{}`}let n=JSON.parse(t);if(typeof n!=`object`||!n)throw TypeError(`WebMCP tool arguments must be an object or array.`);return Object.defineProperty(n,Symbol.toPrimitive,{value:()=>t}),n},l=e=>({isError:!0,content:[{type:`text`,text:e}]}),u=async e=>{if(typeof window>`u`||typeof window.confirm!=`function`)return!1;let t=d(e.args),n=`Allow the AI to call ${e.toolName}`+(t?`\n\nArguments:\n${t}`:``)+(e.description?`\n\n${e.description}`:``);return window.confirm(n)},d=e=>{if(e==null)return``;try{let t=JSON.stringify(e,null,2);return t.length>500?t.slice(0,500)+`…`:t}catch{return String(e)}},f=e=>(e.window===void 0||typeof window<`u`&&e.window===window)&&(e.origin===void 0||typeof location<`u`&&e.origin===location.origin),p=e=>{if(e===void 0)return``;try{return JSON.stringify(e)}catch{return String(e)}};export{n as WebMcpBridge};
|
package/dist/chunk-UlcxdHef.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=`4.28.2`;export{e as t};
|