@kognitivedev/cloud-voice 0.2.29
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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-test.log +13 -0
- package/CHANGELOG.md +10 -0
- package/README.md +226 -0
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +301 -0
- package/dist/client.d.ts +219 -0
- package/dist/client.js +535 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +15 -0
- package/dist/server.d.ts +58 -0
- package/dist/server.js +92 -0
- package/dist/server.test.d.ts +1 -0
- package/dist/server.test.js +78 -0
- package/dist/sse.d.ts +7 -0
- package/dist/sse.js +75 -0
- package/dist/types.d.ts +865 -0
- package/dist/types.js +2 -0
- package/package.json +52 -0
- package/src/__tests__/browser.test.ts +196 -0
- package/src/__tests__/client.test.ts +482 -0
- package/src/__tests__/server.test.ts +84 -0
- package/src/browser.ts +342 -0
- package/src/client.ts +610 -0
- package/src/index.ts +100 -0
- package/src/server.ts +140 -0
- package/src/sse.ts +57 -0
- package/src/types.ts +927 -0
- package/tsconfig.json +14 -0
- package/vitest.config.ts +8 -0
package/src/browser.ts
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { KognitiveCloudVoiceEmbedClient } from "./client";
|
|
2
|
+
import type {
|
|
3
|
+
CloudVoiceClientTool,
|
|
4
|
+
CloudVoiceClientToolDefinition,
|
|
5
|
+
CloudVoiceClientToolManifest,
|
|
6
|
+
CloudVoiceClientToolRenderContext,
|
|
7
|
+
CloudVoiceClientToolRenderer,
|
|
8
|
+
CloudVoiceClientTools,
|
|
9
|
+
CloudVoiceEmbedOptions,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
const MESSAGE_READY = "kognitive.cloudVoice.iframe.ready";
|
|
13
|
+
const MESSAGE_REGISTER_TOOLS = "kognitive.cloudVoice.tools.register";
|
|
14
|
+
const MESSAGE_TOOLS_REGISTERED = "kognitive.cloudVoice.tools.registered";
|
|
15
|
+
const MESSAGE_TOOL_CALL = "kognitive.cloudVoice.tool.call";
|
|
16
|
+
const MESSAGE_TOOL_RESULT = "kognitive.cloudVoice.tool.result";
|
|
17
|
+
const MESSAGE_TOOL_STATE = "kognitive.cloudVoice.tool.state";
|
|
18
|
+
const iframeCleanups = new WeakMap<HTMLIFrameElement, () => void>();
|
|
19
|
+
|
|
20
|
+
type ToolCallMessage = {
|
|
21
|
+
type: typeof MESSAGE_TOOL_CALL;
|
|
22
|
+
requestId: string;
|
|
23
|
+
toolName: string;
|
|
24
|
+
toolCallId?: string;
|
|
25
|
+
args?: unknown;
|
|
26
|
+
context?: {
|
|
27
|
+
callId?: string;
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type ToolResultMessage = {
|
|
33
|
+
type: typeof MESSAGE_TOOL_RESULT;
|
|
34
|
+
requestId: string;
|
|
35
|
+
result?: unknown;
|
|
36
|
+
error?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type ToolStateMessage = {
|
|
40
|
+
type: typeof MESSAGE_TOOL_STATE;
|
|
41
|
+
toolName: string;
|
|
42
|
+
toolCallId?: string;
|
|
43
|
+
input?: unknown;
|
|
44
|
+
output?: unknown;
|
|
45
|
+
error?: string;
|
|
46
|
+
state: CloudVoiceClientToolRenderContext["state"];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function resolveTarget(target?: HTMLElement | string): HTMLElement {
|
|
50
|
+
if (target instanceof HTMLElement) return target;
|
|
51
|
+
if (typeof target === "string") {
|
|
52
|
+
const element = document.querySelector(target);
|
|
53
|
+
if (element instanceof HTMLElement) return element;
|
|
54
|
+
}
|
|
55
|
+
return document.body;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function appendStyleParams(params: URLSearchParams, style: CloudVoiceEmbedOptions["style"] | undefined) {
|
|
59
|
+
if (!style) return;
|
|
60
|
+
const entries: Array<[string, string | undefined]> = [
|
|
61
|
+
["theme", style.theme],
|
|
62
|
+
["accent", style.accentColor],
|
|
63
|
+
["bg", style.backgroundColor],
|
|
64
|
+
["surface", style.surfaceColor],
|
|
65
|
+
["text", style.textColor],
|
|
66
|
+
["muted", style.mutedColor],
|
|
67
|
+
["width", style.width],
|
|
68
|
+
["padding", style.padding],
|
|
69
|
+
["gap", style.gap],
|
|
70
|
+
["radius", style.radius],
|
|
71
|
+
["panelRadius", style.panelRadius],
|
|
72
|
+
["buttonRadius", style.buttonRadius],
|
|
73
|
+
["orbSize", style.orbSize],
|
|
74
|
+
["shadow", style.shadow],
|
|
75
|
+
["align", style.align],
|
|
76
|
+
["density", style.density],
|
|
77
|
+
];
|
|
78
|
+
for (const [key, value] of entries) {
|
|
79
|
+
if (value) params.set(key, value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isToolDefinition(value: CloudVoiceClientTool): value is CloudVoiceClientToolDefinition {
|
|
84
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getToolRenderer(tool: CloudVoiceClientTool | undefined): CloudVoiceClientToolRenderer | undefined {
|
|
88
|
+
if (!tool) return undefined;
|
|
89
|
+
if (typeof tool === "function") return tool;
|
|
90
|
+
return typeof tool.render === "function" ? tool.render : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getToolExecutor(tool: CloudVoiceClientTool | undefined): CloudVoiceClientToolDefinition["execute"] | undefined {
|
|
94
|
+
if (!tool || !isToolDefinition(tool)) return undefined;
|
|
95
|
+
return typeof tool.execute === "function" ? tool.execute : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getExecutableToolManifests(tools: CloudVoiceClientTools | undefined): CloudVoiceClientToolManifest[] {
|
|
99
|
+
if (!tools) return [];
|
|
100
|
+
return Object.entries(tools)
|
|
101
|
+
.filter(([, tool]) => Boolean(getToolExecutor(tool)))
|
|
102
|
+
.map(([id, tool]) => {
|
|
103
|
+
const definition = isToolDefinition(tool) ? tool : {};
|
|
104
|
+
return {
|
|
105
|
+
id,
|
|
106
|
+
name: definition.name ?? id,
|
|
107
|
+
description: definition.description ?? definition.name ?? id,
|
|
108
|
+
inputSchema: definition.inputSchema ?? { type: "object", additionalProperties: true },
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getIframeTargetOrigin(src: string) {
|
|
114
|
+
try {
|
|
115
|
+
return new URL(src).origin;
|
|
116
|
+
} catch {
|
|
117
|
+
return "*";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isMessage(value: unknown): value is { type?: unknown } {
|
|
122
|
+
return Boolean(value && typeof value === "object" && "type" in value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function escapeSelector(value: string) {
|
|
126
|
+
return typeof CSS !== "undefined" && typeof CSS.escape === "function"
|
|
127
|
+
? CSS.escape(value)
|
|
128
|
+
: value.replace(/["\\]/g, "\\$&");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createToolRoot(options: CloudVoiceEmbedOptions, target: HTMLElement) {
|
|
132
|
+
if (!options.tools) return { root: null, owned: false };
|
|
133
|
+
const explicitTarget = options.toolTarget ? resolveTarget(options.toolTarget) : null;
|
|
134
|
+
const root = explicitTarget ?? document.createElement("div");
|
|
135
|
+
root.className = [root.className, "kognitive-cloud-voice-tools"].filter(Boolean).join(" ");
|
|
136
|
+
if (!explicitTarget) {
|
|
137
|
+
target.appendChild(root);
|
|
138
|
+
}
|
|
139
|
+
return { root, owned: !explicitTarget };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderToolState(input: {
|
|
143
|
+
root: HTMLElement | null;
|
|
144
|
+
tools?: CloudVoiceClientTools;
|
|
145
|
+
state: CloudVoiceClientToolRenderContext;
|
|
146
|
+
onToolState?: (state: CloudVoiceClientToolRenderContext) => void;
|
|
147
|
+
}) {
|
|
148
|
+
input.onToolState?.(input.state);
|
|
149
|
+
const renderer = getToolRenderer(input.tools?.[input.state.toolName]);
|
|
150
|
+
if (!renderer || !input.root) return;
|
|
151
|
+
|
|
152
|
+
const rendered = renderer(input.state);
|
|
153
|
+
const id = input.state.toolCallId || input.state.toolName;
|
|
154
|
+
let mount = input.root.querySelector(`[data-kognitive-tool-call-id="${escapeSelector(id)}"]`) as HTMLElement | null;
|
|
155
|
+
if (!mount) {
|
|
156
|
+
mount = document.createElement("div");
|
|
157
|
+
mount.dataset.kognitiveToolCallId = id;
|
|
158
|
+
mount.dataset.kognitiveToolName = input.state.toolName;
|
|
159
|
+
input.root.appendChild(mount);
|
|
160
|
+
}
|
|
161
|
+
mount.dataset.kognitiveToolState = input.state.state;
|
|
162
|
+
|
|
163
|
+
if (typeof Node !== "undefined" && rendered instanceof Node) {
|
|
164
|
+
mount.replaceChildren(rendered);
|
|
165
|
+
} else if (typeof rendered === "string") {
|
|
166
|
+
mount.textContent = rendered;
|
|
167
|
+
} else if (rendered == null) {
|
|
168
|
+
mount.replaceChildren();
|
|
169
|
+
} else {
|
|
170
|
+
mount.dispatchEvent(new CustomEvent("kognitive-cloud-voice-render", {
|
|
171
|
+
detail: { rendered, state: input.state },
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function mountIframe(options: CloudVoiceEmbedOptions): HTMLIFrameElement {
|
|
177
|
+
const target = resolveTarget(options.target);
|
|
178
|
+
const iframe = document.createElement("iframe");
|
|
179
|
+
const clientTools = getExecutableToolManifests(options.tools);
|
|
180
|
+
const params = new URLSearchParams({
|
|
181
|
+
publicKey: options.publicKey,
|
|
182
|
+
agent: options.agent,
|
|
183
|
+
...(options.userId ? { userId: options.userId } : {}),
|
|
184
|
+
...(options.parameters ? { parameters: JSON.stringify(options.parameters) } : {}),
|
|
185
|
+
...(options.tools ? { sdk: "1" } : {}),
|
|
186
|
+
});
|
|
187
|
+
appendStyleParams(params, options.style);
|
|
188
|
+
iframe.src = `${options.baseUrl.replace(/\/$/, "")}/voice/embed?${params.toString()}`;
|
|
189
|
+
iframe.title = options.iframe?.title ?? "Kognitive voice agent";
|
|
190
|
+
iframe.width = options.iframe?.width ?? "380";
|
|
191
|
+
iframe.height = options.iframe?.height ?? "620";
|
|
192
|
+
iframe.setAttribute("allow", "microphone; autoplay");
|
|
193
|
+
iframe.style.border = "0";
|
|
194
|
+
iframe.style.maxWidth = "100%";
|
|
195
|
+
if (options.style?.width) iframe.style.width = options.style.width;
|
|
196
|
+
if (options.iframe?.style) Object.assign(iframe.style, options.iframe.style);
|
|
197
|
+
|
|
198
|
+
const toolRoot = createToolRoot(options, target);
|
|
199
|
+
const targetOrigin = getIframeTargetOrigin(iframe.src);
|
|
200
|
+
|
|
201
|
+
function postToolRegistration() {
|
|
202
|
+
iframe.contentWindow?.postMessage({
|
|
203
|
+
type: MESSAGE_REGISTER_TOOLS,
|
|
204
|
+
tools: clientTools,
|
|
205
|
+
}, targetOrigin);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function handleMessage(event: MessageEvent) {
|
|
209
|
+
if (event.source !== iframe.contentWindow || !isMessage(event.data)) return;
|
|
210
|
+
if (event.data.type === MESSAGE_READY) {
|
|
211
|
+
postToolRegistration();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (event.data.type === MESSAGE_TOOL_STATE) {
|
|
215
|
+
const message = event.data as ToolStateMessage;
|
|
216
|
+
renderToolState({
|
|
217
|
+
root: toolRoot.root,
|
|
218
|
+
tools: options.tools,
|
|
219
|
+
onToolState: options.onToolState,
|
|
220
|
+
state: {
|
|
221
|
+
toolName: message.toolName,
|
|
222
|
+
toolCallId: message.toolCallId ?? message.toolName,
|
|
223
|
+
input: message.input,
|
|
224
|
+
output: message.output,
|
|
225
|
+
error: message.error,
|
|
226
|
+
state: message.state,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (event.data.type !== MESSAGE_TOOL_CALL) return;
|
|
232
|
+
|
|
233
|
+
const message = event.data as ToolCallMessage;
|
|
234
|
+
const tool = options.tools?.[message.toolName];
|
|
235
|
+
const execute = getToolExecutor(tool);
|
|
236
|
+
const toolCallId = message.toolCallId ?? message.requestId;
|
|
237
|
+
|
|
238
|
+
void (async () => {
|
|
239
|
+
renderToolState({
|
|
240
|
+
root: toolRoot.root,
|
|
241
|
+
tools: options.tools,
|
|
242
|
+
onToolState: options.onToolState,
|
|
243
|
+
state: {
|
|
244
|
+
toolName: message.toolName,
|
|
245
|
+
toolCallId,
|
|
246
|
+
input: message.args,
|
|
247
|
+
state: "input-available",
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (!execute) {
|
|
252
|
+
iframe.contentWindow?.postMessage({
|
|
253
|
+
type: MESSAGE_TOOL_RESULT,
|
|
254
|
+
requestId: message.requestId,
|
|
255
|
+
error: `No client executor registered for ${message.toolName}`,
|
|
256
|
+
} satisfies ToolResultMessage, targetOrigin);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const result = await execute(message.args, {
|
|
262
|
+
toolName: message.toolName,
|
|
263
|
+
toolCallId,
|
|
264
|
+
callId: message.context?.callId,
|
|
265
|
+
sessionId: message.context?.sessionId,
|
|
266
|
+
});
|
|
267
|
+
renderToolState({
|
|
268
|
+
root: toolRoot.root,
|
|
269
|
+
tools: options.tools,
|
|
270
|
+
onToolState: options.onToolState,
|
|
271
|
+
state: {
|
|
272
|
+
toolName: message.toolName,
|
|
273
|
+
toolCallId,
|
|
274
|
+
input: message.args,
|
|
275
|
+
output: result,
|
|
276
|
+
state: "output-available",
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
iframe.contentWindow?.postMessage({
|
|
280
|
+
type: MESSAGE_TOOL_RESULT,
|
|
281
|
+
requestId: message.requestId,
|
|
282
|
+
result,
|
|
283
|
+
} satisfies ToolResultMessage, targetOrigin);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
const messageText = error instanceof Error ? error.message : "Client tool failed";
|
|
286
|
+
renderToolState({
|
|
287
|
+
root: toolRoot.root,
|
|
288
|
+
tools: options.tools,
|
|
289
|
+
onToolState: options.onToolState,
|
|
290
|
+
state: {
|
|
291
|
+
toolName: message.toolName,
|
|
292
|
+
toolCallId,
|
|
293
|
+
input: message.args,
|
|
294
|
+
error: messageText,
|
|
295
|
+
state: "error",
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
iframe.contentWindow?.postMessage({
|
|
299
|
+
type: MESSAGE_TOOL_RESULT,
|
|
300
|
+
requestId: message.requestId,
|
|
301
|
+
error: messageText,
|
|
302
|
+
} satisfies ToolResultMessage, targetOrigin);
|
|
303
|
+
}
|
|
304
|
+
})();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
window.addEventListener("message", handleMessage);
|
|
308
|
+
iframe.addEventListener("load", postToolRegistration);
|
|
309
|
+
iframeCleanups.set(iframe, () => {
|
|
310
|
+
window.removeEventListener("message", handleMessage);
|
|
311
|
+
iframe.removeEventListener("load", postToolRegistration);
|
|
312
|
+
if (toolRoot.owned) toolRoot.root?.remove();
|
|
313
|
+
});
|
|
314
|
+
target.appendChild(iframe);
|
|
315
|
+
return iframe;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function createVoiceSession(options: CloudVoiceEmbedOptions) {
|
|
319
|
+
const client = new KognitiveCloudVoiceEmbedClient({
|
|
320
|
+
baseUrl: options.baseUrl,
|
|
321
|
+
publicKey: options.publicKey,
|
|
322
|
+
});
|
|
323
|
+
return client.createSession({
|
|
324
|
+
agentSlug: options.agent,
|
|
325
|
+
channel: "script",
|
|
326
|
+
userId: options.userId,
|
|
327
|
+
parameters: options.parameters,
|
|
328
|
+
clientTools: getExecutableToolManifests(options.tools),
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function createVoiceWidget(options: CloudVoiceEmbedOptions) {
|
|
333
|
+
const iframe = mountIframe(options);
|
|
334
|
+
return {
|
|
335
|
+
iframe,
|
|
336
|
+
destroy() {
|
|
337
|
+
iframeCleanups.get(iframe)?.();
|
|
338
|
+
iframeCleanups.delete(iframe);
|
|
339
|
+
iframe.remove();
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|