@yak-io/javascript 0.10.0 → 0.11.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/client.d.ts.map +1 -1
- package/dist/index.cjs +2043 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +2020 -16
- package/dist/index.js.map +7 -0
- package/dist/index.server.cjs +339 -0
- package/dist/index.server.cjs.map +7 -0
- package/dist/index.server.js +316 -1
- package/dist/index.server.js.map +7 -0
- package/dist/tool-name.d.ts +16 -5
- package/dist/tool-name.d.ts.map +1 -1
- package/dist/voice-session.d.ts +5 -5
- package/dist/voice-session.d.ts.map +1 -1
- package/package.json +5 -3
- package/dist/client.js +0 -524
- package/dist/embed.js +0 -743
- package/dist/logger.js +0 -117
- package/dist/page-context.js +0 -71
- package/dist/server/createYakHandler.js +0 -185
- package/dist/server/index.js +0 -2
- package/dist/server/sources.js +0 -125
- package/dist/tool-name.js +0 -24
- package/dist/toolset.js +0 -119
- package/dist/types/config.js +0 -1
- package/dist/types/messaging.js +0 -1
- package/dist/types/routes.js +0 -1
- package/dist/types/tools.js +0 -1
- package/dist/version.js +0 -18
- package/dist/voice-machine.js +0 -168
- package/dist/voice-session.js +0 -518
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/server/sources.ts", "../src/server/createYakHandler.ts"],
|
|
4
|
+
"sourcesContent": ["import type { RouteInfo } from \"../types/routes.js\";\nimport type { ToolDefinition, ToolExecutor, ToolManifest } from \"../types/tools.js\";\n\nexport type RouteSource = {\n id?: string;\n getRoutes: () => Promise<RouteInfo[]>;\n};\n\ntype RouteSourceFn = () => Promise<RouteInfo[]>;\n\ntype RouteSourceInputItem = RouteSource | RouteSourceFn | RouteInfo[];\n\nexport type RouteSourceInput = RouteSourceInputItem | RouteSourceInputItem[];\n\nexport type ToolSource = {\n id?: string;\n getTools: () => Promise<ToolDefinition[]>;\n executeTool?: ToolExecutor;\n};\n\ntype ToolSourceFn = () => Promise<ToolDefinition[] | ToolManifest>;\n\ntype ToolSourceInputItem =\n | ToolSource\n | ToolSourceFn\n | ToolDefinition[]\n | ToolManifest\n | {\n getTools: ToolSourceFn;\n executeTool?: ToolExecutor;\n };\n\nexport type ToolSourceInput = ToolSourceInputItem | ToolSourceInputItem[];\n\nfunction isRouteSource(value: unknown): value is RouteSource {\n return Boolean(\n value &&\n typeof value === \"object\" &&\n \"getRoutes\" in value &&\n typeof (value as RouteSource).getRoutes === \"function\"\n );\n}\n\nfunction isRouteInfoArray(value: unknown): value is RouteInfo[] {\n return Array.isArray(value) && value.every((route) => route && typeof route.path === \"string\");\n}\n\nfunction normalizeRouteSourceItem(item: RouteSourceInputItem, index: number): RouteSource {\n if (isRouteSource(item)) {\n return {\n id: item.id ?? `route-source-${index}`,\n getRoutes: item.getRoutes,\n };\n }\n\n if (typeof item === \"function\") {\n return {\n id: `route-fn-${index}`,\n getRoutes: item,\n };\n }\n\n if (isRouteInfoArray(item)) {\n return {\n id: `route-static-${index}`,\n getRoutes: async () => item,\n };\n }\n\n throw new Error(\"Unsupported route source input\");\n}\n\nexport function normalizeRouteSources(input: RouteSourceInput): RouteSource[] {\n if (Array.isArray(input)) {\n if (input.length === 0) {\n return [];\n }\n if (isRouteInfoArray(input)) {\n return [normalizeRouteSourceItem(input, 0)];\n }\n return (input as RouteSourceInputItem[]).map((item, index) =>\n normalizeRouteSourceItem(item, index)\n );\n }\n\n return [normalizeRouteSourceItem(input, 0)];\n}\n\nfunction isToolSource(value: unknown): value is ToolSource {\n return Boolean(\n value &&\n typeof value === \"object\" &&\n \"getTools\" in value &&\n typeof (value as ToolSource).getTools === \"function\"\n );\n}\n\nfunction isToolManifest(value: unknown): value is ToolManifest {\n return Boolean(value && typeof value === \"object\" && \"tools\" in value);\n}\n\nfunction isToolDefinitionArray(value: unknown): value is ToolDefinition[] {\n return Array.isArray(value) && value.every((tool) => tool && typeof tool.name === \"string\");\n}\n\nfunction toToolDefinitions(result: unknown): ToolDefinition[] {\n if (isToolDefinitionArray(result)) {\n return result;\n }\n if (isToolManifest(result)) {\n return result.tools;\n }\n throw new Error(\"Tool source must resolve to ToolDefinition[] or ToolManifest\");\n}\n\nfunction normalizeToolSourceItem(item: ToolSourceInputItem, index: number): ToolSource {\n if (isToolSource(item)) {\n return {\n id: item.id ?? `tool-source-${index}`,\n getTools: async () => {\n const result = await item.getTools();\n return toToolDefinitions(result);\n },\n executeTool: item.executeTool,\n };\n }\n\n if (typeof item === \"function\") {\n return {\n id: `tool-fn-${index}`,\n getTools: async () => {\n const result = await item();\n return toToolDefinitions(result);\n },\n };\n }\n\n if (isToolDefinitionArray(item)) {\n return {\n id: `tool-static-${index}`,\n getTools: async () => item,\n };\n }\n\n if (isToolManifest(item)) {\n return {\n id: `tool-manifest-${index}`,\n getTools: async () => item.tools,\n };\n }\n\n if (\n item &&\n typeof item === \"object\" &&\n \"getTools\" in item &&\n typeof item.getTools === \"function\"\n ) {\n return {\n id: (item as { id?: string }).id ?? `tool-wrapper-${index}`,\n getTools: async () => {\n const result = await item.getTools();\n return toToolDefinitions(result);\n },\n executeTool: item.executeTool,\n };\n }\n\n throw new Error(\"Unsupported tool source input\");\n}\n\nexport function normalizeToolSources(input?: ToolSourceInput): ToolSource[] {\n if (!input) {\n return [];\n }\n if (Array.isArray(input)) {\n if (input.length === 0) {\n return [];\n }\n if (isToolDefinitionArray(input)) {\n return [normalizeToolSourceItem(input, 0)];\n }\n return (input as ToolSourceInputItem[]).map((item, index) =>\n normalizeToolSourceItem(item, index)\n );\n }\n\n return [normalizeToolSourceItem(input, 0)];\n}\n", "import type { ChatConfig } from \"../types/config.js\";\nimport type { RouteInfo, RouteManifest } from \"../types/routes.js\";\nimport type { ToolCallResult, ToolDefinition, ToolManifest } from \"../types/tools.js\";\nimport type { RouteSource, RouteSourceInput, ToolSource, ToolSourceInput } from \"./sources.js\";\nimport { normalizeRouteSources, normalizeToolSources } from \"./sources.js\";\n\nexport type YakHandlerConfig = {\n routes: RouteSourceInput;\n tools?: ToolSourceInput;\n};\n\nexport function createYakHandler(config: YakHandlerConfig) {\n const routeSources = normalizeRouteSources(config.routes);\n const toolSources = normalizeToolSources(config.tools);\n\n const GET = async function handleConfig(_req: Request): Promise<Response> {\n const chatConfig: ChatConfig = {\n routes: await buildRouteManifest(routeSources),\n };\n\n if (toolSources.length > 0) {\n const { manifest } = await buildToolRegistry(toolSources);\n chatConfig.tools = manifest;\n }\n\n return jsonResponse(chatConfig, 200, {\n \"Cache-Control\": \"no-store\",\n });\n };\n\n const POST = async function handleToolCall(req: Request): Promise<Response> {\n if (toolSources.length === 0) {\n return errorResponse(\"Tool execution is not configured\", 501);\n }\n\n let payload: unknown;\n\n try {\n payload = await req.json();\n } catch {\n return errorResponse(\"Invalid JSON payload\", 400);\n }\n\n if (!isHostToolCallPayload(payload)) {\n return errorResponse(\"Invalid tool call payload\", 400);\n }\n\n try {\n const { lookup } = await buildToolRegistry(toolSources);\n const owner = lookup.get(payload.name);\n\n if (!owner) {\n return createToolErrorResponse(`Tool '${payload.name}' is not registered`);\n }\n\n if (!owner.executeTool) {\n return createToolErrorResponse(`Tool '${payload.name}' does not expose an executor`);\n }\n\n const result = await owner.executeTool(payload.name, payload.args, req);\n\n const successResult: ToolCallResult = {\n ok: true,\n result,\n };\n\n return jsonResponse(successResult);\n } catch (error) {\n return createToolErrorResponse(extractErrorMessage(error));\n }\n };\n\n return { GET, POST };\n}\n\nexport type YakConfigHandlerConfig = {\n routes: RouteSourceInput;\n tools?: ToolSourceInput;\n};\n\nexport function createYakConfigHandler(config: YakConfigHandlerConfig) {\n const routeSources = normalizeRouteSources(config.routes);\n const toolSources = normalizeToolSources(config.tools);\n\n return async function handleConfig(_req: Request): Promise<Response> {\n const chatConfig: ChatConfig = {\n routes: await buildRouteManifest(routeSources),\n };\n\n if (toolSources.length > 0) {\n const { manifest } = await buildToolRegistry(toolSources);\n chatConfig.tools = manifest;\n }\n\n return jsonResponse(chatConfig, 200, {\n \"Cache-Control\": \"no-store\",\n });\n };\n}\n\nexport type YakToolsHandlerConfig = {\n tools: ToolSourceInput;\n};\n\nexport function createYakToolsHandler(config: YakToolsHandlerConfig) {\n const toolSources = normalizeToolSources(config.tools);\n\n if (toolSources.length === 0) {\n throw new Error(\"createYakToolsHandler requires at least one tool source\");\n }\n\n return async function handleTools(req: Request): Promise<Response> {\n let payload: unknown;\n\n try {\n payload = await req.json();\n } catch {\n return errorResponse(\"Invalid JSON payload\", 400);\n }\n\n if (!isHostToolCallPayload(payload)) {\n return errorResponse(\"Invalid tool call payload\", 400);\n }\n\n try {\n const { lookup } = await buildToolRegistry(toolSources);\n const owner = lookup.get(payload.name);\n\n if (!owner) {\n return createToolErrorResponse(`Tool '${payload.name}' is not registered`);\n }\n\n if (!owner.executeTool) {\n return createToolErrorResponse(`Tool '${payload.name}' does not expose an executor`);\n }\n\n const result = await owner.executeTool(payload.name, payload.args, req);\n const successResult: ToolCallResult = {\n ok: true,\n result,\n };\n\n return jsonResponse(successResult);\n } catch (error) {\n return createToolErrorResponse(extractErrorMessage(error));\n }\n };\n}\n\nasync function buildRouteManifest(routeSources: RouteSource[]): Promise<RouteManifest> {\n const entries = await Promise.all(\n routeSources.map(async (source) => ({\n id: source.id ?? \"routes\",\n routes: await source.getRoutes(),\n }))\n );\n\n const merged: RouteInfo[] = [];\n const seen = new Set<string>();\n\n for (const entry of entries) {\n for (const route of entry.routes) {\n const key = route.path;\n if (!seen.has(key)) {\n seen.add(key);\n merged.push(route);\n }\n }\n }\n\n merged.sort((a, b) => a.path.localeCompare(b.path));\n\n return {\n routes: merged,\n generated_at: new Date().toISOString(),\n sources: entries.map((entry) => ({ id: entry.id, count: entry.routes.length })),\n };\n}\n\ntype ToolRegistry = {\n manifest: ToolManifest;\n lookup: Map<string, ToolSource>;\n};\n\nasync function buildToolRegistry(toolSources: ToolSource[]): Promise<ToolRegistry> {\n const entries = await Promise.all(\n toolSources.map(async (source) => ({\n id: source.id ?? \"tools\",\n source,\n tools: await source.getTools(),\n }))\n );\n\n const manifestTools: ToolDefinition[] = [];\n const lookup = new Map<string, ToolSource>();\n\n for (const entry of entries) {\n for (const tool of entry.tools) {\n manifestTools.push(tool);\n lookup.set(tool.name, entry.source);\n }\n }\n\n return {\n manifest: {\n tools: manifestTools,\n generated_at: new Date().toISOString(),\n sources: entries.map((entry) => ({ id: entry.id, count: entry.tools.length })),\n },\n lookup,\n };\n}\n\n/**\n * Payload received from the host (id is optional since YakClient handles correlation)\n */\ntype HostToolCallPayload = {\n name: string;\n args: unknown;\n};\n\nfunction isHostToolCallPayload(payload: unknown): payload is HostToolCallPayload {\n return (\n typeof payload === \"object\" &&\n payload !== null &&\n typeof (payload as HostToolCallPayload).name === \"string\" &&\n \"args\" in payload\n );\n}\n\nfunction extractErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n return \"An unknown error occurred\";\n}\n\nfunction jsonResponse(body: unknown, status = 200, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: {\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n });\n}\n\nfunction errorResponse(message: string, status: number): Response {\n return jsonResponse({ error: message }, status);\n}\n\nfunction createToolErrorResponse(error: string): Response {\n const errorResult: ToolCallResult = {\n ok: false,\n error,\n };\n\n return jsonResponse(errorResult);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/tool-name.d.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Format: `yt_<8-char-hex-hash>`.
|
|
2
|
+
* Convert a host tool name into a model-facing function name.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* We sanitise to the allowed function-name charset (rather than hashing to an opaque
|
|
5
|
+
* `yt_<hex>`), so the model keeps the semantic signal of a readable name — e.g.
|
|
6
|
+
* `orders.list` → `orders_list`.
|
|
7
|
+
*
|
|
8
|
+
* This is a pure per-name transform; uniqueness across a manifest (and avoiding the
|
|
9
|
+
* reserved `redirect` name / `mcp__` namespace) is handled by the caller via
|
|
10
|
+
* {@link uniqueToolId}. Each runtime (the chat-ui iframe and this SDK's voice session)
|
|
11
|
+
* decorates and reverse-maps with its own manifest, so the ids never need to match
|
|
12
|
+
* across paths — only to be self-consistent within one.
|
|
8
13
|
*/
|
|
9
14
|
export declare function generateToolId(originalName: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a collision-free, model-safe id for a host tool name, given the ids already
|
|
17
|
+
* taken in this manifest (seed `used` with reserved names like `redirect`). Host ids must
|
|
18
|
+
* never start with the `mcp__` namespace, which the dispatcher routes on.
|
|
19
|
+
*/
|
|
20
|
+
export declare function uniqueToolId(originalName: string, used: Set<string>): string;
|
|
10
21
|
//# sourceMappingURL=tool-name.d.ts.map
|
package/dist/tool-name.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-name.d.ts","sourceRoot":"","sources":["../src/tool-name.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-name.d.ts","sourceRoot":"","sources":["../src/tool-name.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG3D;AAKD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAc5E"}
|
package/dist/voice-session.d.ts
CHANGED
|
@@ -81,11 +81,11 @@ export declare class YakVoiceSession {
|
|
|
81
81
|
private execMcpTool;
|
|
82
82
|
private mintToken;
|
|
83
83
|
/**
|
|
84
|
-
* Decorate the host's tool manifest with
|
|
85
|
-
* `this.toolNameById` for reverse lookup. Mirrors the decoration the
|
|
86
|
-
* iframe applies before sending tools to `/api/chat`. GraphQL/REST tools are
|
|
87
|
-
* ordinary manifest entries here (contributed by their adapters), so no
|
|
88
|
-
*
|
|
84
|
+
* Decorate the host's tool manifest with readable, collision-free model-facing ids
|
|
85
|
+
* and populate `this.toolNameById` for reverse lookup. Mirrors the decoration the
|
|
86
|
+
* chat-ui iframe applies before sending tools to `/api/chat`. GraphQL/REST tools are
|
|
87
|
+
* ordinary manifest entries here (contributed by their adapters), so no special-casing
|
|
88
|
+
* is needed.
|
|
89
89
|
*/
|
|
90
90
|
private buildDecoratedManifest;
|
|
91
91
|
private exchangeSdp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-session.d.ts","sourceRoot":"","sources":["../src/voice-session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAKL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC;CACF;AAiBD,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,eAAe,CAA6B;IACpD,6EAA6E;IAC7E,OAAO,CAAC,KAAK,CAAkC;IAC/C;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAA6B;gBAErC,MAAM,EAAE,qBAAqB;IAKzC;;;;OAIG;IACH,OAAO,KAAK,SAAS,GAEpB;IAED,0DAA0D;IACnD,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAIzD,QAAQ,IAAI,YAAY;IAI/B;;;OAGG;IACI,YAAY,IAAI,MAAM;IAItB,aAAa,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAO9D;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwJnC,oDAAoD;IACvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,2FAA2F;IACpF,OAAO,IAAI,IAAI;IAWtB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,mBAAmB;YAeb,aAAa;IA8B3B;;;;OAIG;YACW,WAAW;
|
|
1
|
+
{"version":3,"file":"voice-session.d.ts","sourceRoot":"","sources":["../src/voice-session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAKL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC;CACF;AAiBD,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,eAAe,CAA6B;IACpD,6EAA6E;IAC7E,OAAO,CAAC,KAAK,CAAkC;IAC/C;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAA6B;gBAErC,MAAM,EAAE,qBAAqB;IAKzC;;;;OAIG;IACH,OAAO,KAAK,SAAS,GAEpB;IAED,0DAA0D;IACnD,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAIzD,QAAQ,IAAI,YAAY;IAI/B;;;OAGG;IACI,YAAY,IAAI,MAAM;IAItB,aAAa,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAO9D;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwJnC,oDAAoD;IACvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,2FAA2F;IACpF,OAAO,IAAI,IAAI;IAWtB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,mBAAmB;YAeb,aAAa;IA8B3B;;;;OAIG;YACW,WAAW;YA2BX,SAAS;IAsBvB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;YAgBhB,WAAW;IAmBzB,OAAO,CAAC,kBAAkB;YAWZ,gBAAgB;YA2BhB,QAAQ;YAuCR,QAAQ;IAMtB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,cAAc;CAevB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yak-io/javascript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Core JavaScript SDK for embedding yak chatbot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -30,18 +30,20 @@
|
|
|
30
30
|
"LICENSE"
|
|
31
31
|
],
|
|
32
32
|
"sideEffects": false,
|
|
33
|
-
"main": "./dist/index.
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
34
|
"module": "./dist/index.js",
|
|
35
35
|
"types": "./dist/index.d.ts",
|
|
36
36
|
"exports": {
|
|
37
37
|
".": {
|
|
38
38
|
"types": "./dist/index.d.ts",
|
|
39
39
|
"import": "./dist/index.js",
|
|
40
|
+
"require": "./dist/index.cjs",
|
|
40
41
|
"default": "./dist/index.js"
|
|
41
42
|
},
|
|
42
43
|
"./server": {
|
|
43
44
|
"types": "./dist/index.server.d.ts",
|
|
44
45
|
"import": "./dist/index.server.js",
|
|
46
|
+
"require": "./dist/index.server.cjs",
|
|
45
47
|
"default": "./dist/index.server.js"
|
|
46
48
|
},
|
|
47
49
|
"./package.json": "./package.json"
|
|
@@ -65,7 +67,7 @@
|
|
|
65
67
|
},
|
|
66
68
|
"homepage": "https://docs.yak.io/docs/sdks/javascript",
|
|
67
69
|
"scripts": {
|
|
68
|
-
"build": "tsc",
|
|
70
|
+
"build": "node ../../scripts/build-package.mjs && tsc --emitDeclarationOnly",
|
|
69
71
|
"check-types": "tsc --noEmit",
|
|
70
72
|
"test": "vitest run",
|
|
71
73
|
"lint": "biome lint ./src --fix",
|
package/dist/client.js
DELETED
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
import { isYakLoggingEnabled, logger } from "./logger.js";
|
|
2
|
-
import { debounce, extractPageContext } from "./page-context.js";
|
|
3
|
-
import { EMBED_PROTOCOL_VERSION } from "./version.js";
|
|
4
|
-
/** localStorage key for the per-app signed session token. */
|
|
5
|
-
const SESSION_STORAGE_KEY = (appId) => `yak:session:${appId}`;
|
|
6
|
-
/** localStorage key for the per-app active-conversation pointer. */
|
|
7
|
-
const CONVERSATION_POINTER_KEY = (appId) => `yak:conversation:${appId}`;
|
|
8
|
-
/** Read a stored session token for this app, if any. SSR-safe. */
|
|
9
|
-
function readStoredSessionToken(appId) {
|
|
10
|
-
if (typeof window === "undefined" || typeof window.localStorage === "undefined")
|
|
11
|
-
return undefined;
|
|
12
|
-
try {
|
|
13
|
-
return window.localStorage.getItem(SESSION_STORAGE_KEY(appId)) ?? undefined;
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function writeStoredSessionToken(appId, token) {
|
|
20
|
-
if (typeof window === "undefined" || typeof window.localStorage === "undefined")
|
|
21
|
-
return;
|
|
22
|
-
try {
|
|
23
|
-
window.localStorage.setItem(SESSION_STORAGE_KEY(appId), token);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
// localStorage can throw in private-browsing modes; silently ignore.
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function readStoredConversationPointer(appId) {
|
|
30
|
-
if (typeof window === "undefined" || typeof window.localStorage === "undefined")
|
|
31
|
-
return undefined;
|
|
32
|
-
try {
|
|
33
|
-
return window.localStorage.getItem(CONVERSATION_POINTER_KEY(appId)) ?? undefined;
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function writeStoredConversationPointer(appId, pointer) {
|
|
40
|
-
if (typeof window === "undefined" || typeof window.localStorage === "undefined")
|
|
41
|
-
return;
|
|
42
|
-
try {
|
|
43
|
-
window.localStorage.setItem(CONVERSATION_POINTER_KEY(appId), pointer);
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// localStorage can throw in private-browsing modes; silently ignore.
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function clearStoredConversationPointer(appId) {
|
|
50
|
-
if (typeof window === "undefined" || typeof window.localStorage === "undefined")
|
|
51
|
-
return;
|
|
52
|
-
try {
|
|
53
|
-
window.localStorage.removeItem(CONVERSATION_POINTER_KEY(appId));
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// localStorage can throw in private-browsing modes; silently ignore.
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/** Production chat origin — where the widget iframe loads from by default. */
|
|
60
|
-
const DEFAULT_CHAT_ORIGIN = "https://chat.yak.io";
|
|
61
|
-
/**
|
|
62
|
-
* Resolves the iframe origin when no explicit `origin` is configured. Points at
|
|
63
|
-
* a local chat UI during yak's own local development; production otherwise.
|
|
64
|
-
* Integrators override the result with the `origin` config option.
|
|
65
|
-
*/
|
|
66
|
-
function getDefaultIframeOrigin() {
|
|
67
|
-
if (typeof window !== "undefined" &&
|
|
68
|
-
(window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") &&
|
|
69
|
-
typeof window.__YAK_INTERNAL_DEV__ !== "undefined") {
|
|
70
|
-
return "http://localhost:3001";
|
|
71
|
-
}
|
|
72
|
-
return DEFAULT_CHAT_ORIGIN;
|
|
73
|
-
}
|
|
74
|
-
export class YakClient {
|
|
75
|
-
config;
|
|
76
|
-
iframeWindow = null;
|
|
77
|
-
isWidgetOpen = false;
|
|
78
|
-
readyTarget = null;
|
|
79
|
-
unexpectedOriginLogged = false;
|
|
80
|
-
lastUrl = " ";
|
|
81
|
-
debouncedSendContext;
|
|
82
|
-
observer = null;
|
|
83
|
-
constructor(config) {
|
|
84
|
-
this.config = config;
|
|
85
|
-
this.debouncedSendContext = debounce(() => {
|
|
86
|
-
logger.debug("DOM mutation detected, sending page context");
|
|
87
|
-
this.sendPageContext();
|
|
88
|
-
}, 2000);
|
|
89
|
-
}
|
|
90
|
-
updateConfig(newConfig) {
|
|
91
|
-
this.config = { ...this.config, ...newConfig };
|
|
92
|
-
// Resend config when the iframe is ready and we have anything to deliver —
|
|
93
|
-
// tool/route manifests, or a user identity that needs to land in the
|
|
94
|
-
// widget so persistence/history light up.
|
|
95
|
-
if (this.readyTarget && (this.config.chatConfig || this.config.user)) {
|
|
96
|
-
this.sendConfigToIframe(this.readyTarget.window, this.readyTarget.origin);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Get the iframe origin URL (base URL for the chat widget). Recomputed on
|
|
101
|
-
* each call so environment-dependent defaults resolve correctly.
|
|
102
|
-
*/
|
|
103
|
-
getIframeOrigin() {
|
|
104
|
-
return this.config.origin ?? getDefaultIframeOrigin();
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Get the full iframe embed URL for the chatbot
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```ts
|
|
111
|
-
* const client = new YakClient({ appId: "my-app" });
|
|
112
|
-
* const iframeSrc = client.getEmbedUrl();
|
|
113
|
-
* // Returns: "https://chat.yak.io/embed/v1/my-app"
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
getEmbedUrl() {
|
|
117
|
-
const origin = this.getIframeOrigin();
|
|
118
|
-
const baseUrl = `${origin}/embed/v${EMBED_PROTOCOL_VERSION}/${encodeURIComponent(this.config.appId)}`;
|
|
119
|
-
const params = new URLSearchParams();
|
|
120
|
-
const theme = this.config.theme;
|
|
121
|
-
if (theme?.colorMode && theme.colorMode !== "system") {
|
|
122
|
-
params.set("colorMode", theme.colorMode);
|
|
123
|
-
}
|
|
124
|
-
this.appendThemeColors(params, theme?.light, "light");
|
|
125
|
-
this.appendThemeColors(params, theme?.dark, "dark");
|
|
126
|
-
// Pass debug flag to iframe so it can enable logging immediately
|
|
127
|
-
if (isYakLoggingEnabled()) {
|
|
128
|
-
params.set("yakDebug", "1");
|
|
129
|
-
}
|
|
130
|
-
const queryString = params.toString();
|
|
131
|
-
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Append theme color parameters to a URLSearchParams object
|
|
135
|
-
*/
|
|
136
|
-
appendThemeColors(params, colors, prefix) {
|
|
137
|
-
if (!colors)
|
|
138
|
-
return;
|
|
139
|
-
const map = [
|
|
140
|
-
[`${prefix}Bg`, colors.background],
|
|
141
|
-
[`${prefix}Border`, colors.border],
|
|
142
|
-
[`${prefix}MessageBg`, colors.messageBackground],
|
|
143
|
-
[`${prefix}Placeholder`, colors.placeholderColor],
|
|
144
|
-
[`${prefix}SubmitBtn`, colors.submitButtonColor],
|
|
145
|
-
[`${prefix}SubmitBtnText`, colors.submitButtonTextColor],
|
|
146
|
-
[`${prefix}HeaderIcon`, colors.headerIconColor],
|
|
147
|
-
];
|
|
148
|
-
for (const [key, value] of map) {
|
|
149
|
-
if (value)
|
|
150
|
-
params.set(key, value);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Get the app ID
|
|
155
|
-
*/
|
|
156
|
-
getAppId() {
|
|
157
|
-
return this.config.appId;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Get the current theme configuration
|
|
161
|
-
*/
|
|
162
|
-
getTheme() {
|
|
163
|
-
return this.config.theme;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Send a prompt message to the chatbot iframe
|
|
167
|
-
* Note: The iframe must be ready to receive messages (onReady callback must have fired)
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* ```ts
|
|
171
|
-
* const client = new YakClient({ appId: "my-app", onReady: () => {
|
|
172
|
-
* client.sendPrompt("Help me with my order");
|
|
173
|
-
* }});
|
|
174
|
-
* ```
|
|
175
|
-
*/
|
|
176
|
-
sendPrompt(prompt) {
|
|
177
|
-
if (!this.iframeWindow) {
|
|
178
|
-
logger.warn("Cannot send prompt: iframe not ready");
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const message = {
|
|
182
|
-
type: "yak:prompt",
|
|
183
|
-
payload: { prompt },
|
|
184
|
-
};
|
|
185
|
-
this.iframeWindow.postMessage(message, this.getIframeOrigin());
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Send a focus request to the chatbot iframe
|
|
189
|
-
* This will focus the chat input field
|
|
190
|
-
*/
|
|
191
|
-
sendFocus() {
|
|
192
|
-
if (!this.iframeWindow) {
|
|
193
|
-
logger.warn("Cannot send focus: iframe not ready");
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const message = {
|
|
197
|
-
type: "yak:focus",
|
|
198
|
-
};
|
|
199
|
-
this.iframeWindow.postMessage(message, this.getIframeOrigin());
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Check if the iframe is ready to receive messages
|
|
203
|
-
*/
|
|
204
|
-
isReady() {
|
|
205
|
-
return this.readyTarget !== null;
|
|
206
|
-
}
|
|
207
|
-
setIframeWindow(window) {
|
|
208
|
-
this.iframeWindow = window;
|
|
209
|
-
if (!window) {
|
|
210
|
-
this.readyTarget = null;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
setWidgetOpen(isOpen) {
|
|
214
|
-
this.isWidgetOpen = isOpen;
|
|
215
|
-
if (isOpen && this.readyTarget && this.config.chatConfig) {
|
|
216
|
-
this.sendConfigToIframe(this.readyTarget.window, this.readyTarget.origin);
|
|
217
|
-
}
|
|
218
|
-
if (isOpen) {
|
|
219
|
-
this.startObserving();
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
this.stopObserving();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
mount() {
|
|
226
|
-
if (typeof window !== "undefined") {
|
|
227
|
-
window.addEventListener("message", this.handleMessage);
|
|
228
|
-
window.addEventListener("popstate", this.handlePopState);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
unmount() {
|
|
232
|
-
if (typeof window !== "undefined") {
|
|
233
|
-
window.removeEventListener("message", this.handleMessage);
|
|
234
|
-
window.removeEventListener("popstate", this.handlePopState);
|
|
235
|
-
}
|
|
236
|
-
this.stopObserving();
|
|
237
|
-
}
|
|
238
|
-
startObserving() {
|
|
239
|
-
if (typeof window === "undefined" || !this.iframeWindow)
|
|
240
|
-
return;
|
|
241
|
-
// Initial check
|
|
242
|
-
const currentUrl = window.location.href;
|
|
243
|
-
if (currentUrl !== this.lastUrl) {
|
|
244
|
-
this.lastUrl = currentUrl;
|
|
245
|
-
logger.debug("URL changed, sending page context");
|
|
246
|
-
this.sendPageContext();
|
|
247
|
-
}
|
|
248
|
-
if (this.observer)
|
|
249
|
-
return;
|
|
250
|
-
this.observer = new MutationObserver((mutations) => {
|
|
251
|
-
const hasSubstantialChanges = mutations.some((mutation) => mutation.type === "childList" &&
|
|
252
|
-
(mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0));
|
|
253
|
-
if (hasSubstantialChanges) {
|
|
254
|
-
this.debouncedSendContext();
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
this.observer.observe(document.body, {
|
|
258
|
-
childList: true,
|
|
259
|
-
subtree: true,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
stopObserving() {
|
|
263
|
-
if (this.observer) {
|
|
264
|
-
this.observer.disconnect();
|
|
265
|
-
this.observer = null;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
handlePopState = () => {
|
|
269
|
-
logger.debug("Navigation detected, sending page context");
|
|
270
|
-
this.sendPageContext();
|
|
271
|
-
};
|
|
272
|
-
handleMessage = (event) => {
|
|
273
|
-
if (typeof window === "undefined")
|
|
274
|
-
return;
|
|
275
|
-
if (!this.isWidgetOpen && event.data?.type !== "yak:ready") {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const hostOrigin = window.location.origin;
|
|
279
|
-
const allowedOrigins = new Set();
|
|
280
|
-
allowedOrigins.add(this.getIframeOrigin());
|
|
281
|
-
if (hostOrigin) {
|
|
282
|
-
allowedOrigins.add(hostOrigin);
|
|
283
|
-
}
|
|
284
|
-
if (!allowedOrigins.has(event.origin)) {
|
|
285
|
-
if (!this.unexpectedOriginLogged) {
|
|
286
|
-
logger.warn(`Ignoring message from unexpected origin: ${event.origin}, allowed: ${Array.from(allowedOrigins).join(", ")}`);
|
|
287
|
-
this.unexpectedOriginLogged = true;
|
|
288
|
-
}
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
// Validate message structure
|
|
292
|
-
if (!event.data || typeof event.data !== "object" || !("type" in event.data)) {
|
|
293
|
-
// Filter out known browser extension messages
|
|
294
|
-
const data = event.data;
|
|
295
|
-
const isReactDevTools = data?.source === "react-devtools-content-script" ||
|
|
296
|
-
data?.source === "react-devtools-bridge" ||
|
|
297
|
-
data?.source === "react-devtools-inject-backend";
|
|
298
|
-
const isReduxDevTools = data?.source === "@devtools-page";
|
|
299
|
-
if (isReactDevTools || isReduxDevTools) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
const message = event.data;
|
|
305
|
-
const targetWindow = (event.source && "postMessage" in event.source ? event.source : null) ??
|
|
306
|
-
this.iframeWindow;
|
|
307
|
-
const targetOrigin = this.getIframeOrigin();
|
|
308
|
-
logger.debug("Message received from iframe:", message.type);
|
|
309
|
-
switch (message.type) {
|
|
310
|
-
case "yak:ready": {
|
|
311
|
-
logger.debug("Iframe ready, sending config");
|
|
312
|
-
if (targetWindow) {
|
|
313
|
-
this.readyTarget = { window: targetWindow, origin: targetOrigin };
|
|
314
|
-
this.sendConfigToIframe(targetWindow, targetOrigin);
|
|
315
|
-
// Send initial page context after config
|
|
316
|
-
setTimeout(() => this.sendPageContext(), 100);
|
|
317
|
-
// Mark iframe as ready after config is sent
|
|
318
|
-
setTimeout(() => this.config.onReady?.(), 200);
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
logger.warn("Unable to send config: iframe window not registered yet");
|
|
322
|
-
}
|
|
323
|
-
break;
|
|
324
|
-
}
|
|
325
|
-
case "yak:tool_call": {
|
|
326
|
-
const { id, name, args } = message.payload;
|
|
327
|
-
void this.handleToolCall(id, name, args);
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
case "yak:redirect": {
|
|
331
|
-
const { path } = message.payload;
|
|
332
|
-
logger.debug("Redirect request received:", path);
|
|
333
|
-
if (!this.isAllowedRedirect(path)) {
|
|
334
|
-
logger.warn("Blocked potentially unsafe redirect:", path);
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
if (this.config.onRedirect) {
|
|
338
|
-
this.config.onRedirect(path);
|
|
339
|
-
}
|
|
340
|
-
else if (typeof window !== "undefined") {
|
|
341
|
-
window.location.assign(path);
|
|
342
|
-
}
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
case "yak:session": {
|
|
346
|
-
const { sessionToken } = message.payload;
|
|
347
|
-
logger.debug("Session token received from iframe; persisting");
|
|
348
|
-
writeStoredSessionToken(this.config.appId, sessionToken);
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
case "yak:conversation": {
|
|
352
|
-
const { pointer } = message.payload;
|
|
353
|
-
if (pointer === null) {
|
|
354
|
-
logger.debug("Conversation pointer cleared by iframe");
|
|
355
|
-
clearStoredConversationPointer(this.config.appId);
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
logger.debug("Conversation pointer received from iframe; persisting");
|
|
359
|
-
writeStoredConversationPointer(this.config.appId, pointer);
|
|
360
|
-
}
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
case "yak:close": {
|
|
364
|
-
logger.debug("Close message received from iframe");
|
|
365
|
-
this.config.onClose?.();
|
|
366
|
-
break;
|
|
367
|
-
}
|
|
368
|
-
default:
|
|
369
|
-
logger.debug("Unknown message type:", message.type);
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
sendConfigToIframe(targetWindow, targetOrigin) {
|
|
374
|
-
// Get logging enabled state from window flag
|
|
375
|
-
const loggingEnabled = typeof window !== "undefined" && typeof window.__YAK_LOGGING_ENABLED__ === "boolean"
|
|
376
|
-
? window.__YAK_LOGGING_ENABLED__
|
|
377
|
-
: undefined;
|
|
378
|
-
const storedSessionToken = readStoredSessionToken(this.config.appId);
|
|
379
|
-
const storedConversationPointer = readStoredConversationPointer(this.config.appId);
|
|
380
|
-
const configMessage = {
|
|
381
|
-
type: "yak:config",
|
|
382
|
-
payload: {
|
|
383
|
-
version: EMBED_PROTOCOL_VERSION,
|
|
384
|
-
appId: this.config.appId,
|
|
385
|
-
theme: this.config.theme,
|
|
386
|
-
toolManifest: this.config.chatConfig?.tools ?? undefined,
|
|
387
|
-
routeManifest: this.config.chatConfig?.routes ?? undefined,
|
|
388
|
-
options: this.config.options,
|
|
389
|
-
loggingEnabled,
|
|
390
|
-
user: this.config.user,
|
|
391
|
-
sessionToken: storedSessionToken,
|
|
392
|
-
conversationPointer: storedConversationPointer,
|
|
393
|
-
},
|
|
394
|
-
};
|
|
395
|
-
logger.debug("Posting config to iframe origin:", {
|
|
396
|
-
origin: targetOrigin,
|
|
397
|
-
version: EMBED_PROTOCOL_VERSION,
|
|
398
|
-
hasToolManifest: Boolean(this.config.chatConfig?.tools),
|
|
399
|
-
toolCount: this.config.chatConfig?.tools?.tools.length ?? 0,
|
|
400
|
-
hasRouteManifest: Boolean(this.config.chatConfig?.routes),
|
|
401
|
-
});
|
|
402
|
-
targetWindow.postMessage(configMessage, targetOrigin);
|
|
403
|
-
}
|
|
404
|
-
sendPageContext() {
|
|
405
|
-
if (!this.iframeWindow)
|
|
406
|
-
return;
|
|
407
|
-
try {
|
|
408
|
-
const pageContext = extractPageContext();
|
|
409
|
-
const message = {
|
|
410
|
-
type: "yak:page_context",
|
|
411
|
-
payload: pageContext,
|
|
412
|
-
};
|
|
413
|
-
logger.debug("Sending page context to iframe:", {
|
|
414
|
-
url: pageContext.url,
|
|
415
|
-
title: pageContext.title,
|
|
416
|
-
textLength: pageContext.text.length,
|
|
417
|
-
});
|
|
418
|
-
this.iframeWindow.postMessage(message, this.getIframeOrigin());
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
logger.error("Error extracting page context:", error);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
async handleToolCall(id, name, args) {
|
|
425
|
-
logger.debug(`Tool call received: ${name}`, { id, args });
|
|
426
|
-
if (!this.config.onToolCall) {
|
|
427
|
-
logger.error("Tool call received but no onToolCall handler configured");
|
|
428
|
-
this.sendToolResultToIframe(id, false, undefined, "No tool call handler configured");
|
|
429
|
-
this.config.onToolCallComplete?.({
|
|
430
|
-
name,
|
|
431
|
-
args,
|
|
432
|
-
ok: false,
|
|
433
|
-
error: "No tool call handler configured",
|
|
434
|
-
});
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
try {
|
|
438
|
-
const result = await this.config.onToolCall(name, args);
|
|
439
|
-
logger.debug(`Tool call succeeded: ${name}`, { id });
|
|
440
|
-
this.sendToolResultToIframe(id, true, result);
|
|
441
|
-
this.config.onToolCallComplete?.({ name, args, ok: true, result });
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
const errorMessage = this.extractErrorMessage(error);
|
|
445
|
-
logger.error(`Tool call failed: ${name}`, { id, error });
|
|
446
|
-
this.sendToolResultToIframe(id, false, undefined, errorMessage);
|
|
447
|
-
this.config.onToolCallComplete?.({ name, args, ok: false, error: errorMessage });
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
sendToolResultToIframe(id, ok, result, error) {
|
|
451
|
-
if (!this.iframeWindow)
|
|
452
|
-
return;
|
|
453
|
-
if (ok) {
|
|
454
|
-
// Serialize result to remove non-cloneable values (functions, etc.)
|
|
455
|
-
// postMessage uses structured clone which cannot handle functions
|
|
456
|
-
const safeResult = this.toSerializable(result);
|
|
457
|
-
const message = {
|
|
458
|
-
type: "yak:tool_result",
|
|
459
|
-
payload: { id, ok: true, result: safeResult },
|
|
460
|
-
};
|
|
461
|
-
this.iframeWindow.postMessage(message, this.getIframeOrigin());
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
const message = {
|
|
465
|
-
type: "yak:tool_result",
|
|
466
|
-
payload: { id, ok: false, error: error ?? "Unknown error" },
|
|
467
|
-
};
|
|
468
|
-
this.iframeWindow.postMessage(message, this.getIframeOrigin());
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Convert a value to a serializable form by stripping functions and other non-cloneable values.
|
|
473
|
-
* Uses JSON.parse(JSON.stringify()) which handles most cases.
|
|
474
|
-
*/
|
|
475
|
-
toSerializable(value) {
|
|
476
|
-
if (value === undefined || value === null) {
|
|
477
|
-
return value;
|
|
478
|
-
}
|
|
479
|
-
try {
|
|
480
|
-
return JSON.parse(JSON.stringify(value));
|
|
481
|
-
}
|
|
482
|
-
catch {
|
|
483
|
-
// If JSON serialization fails, return a string representation
|
|
484
|
-
logger.warn("Failed to serialize tool result, returning string representation");
|
|
485
|
-
return String(value);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
extractErrorMessage(error) {
|
|
489
|
-
if (error instanceof Error) {
|
|
490
|
-
return error.message;
|
|
491
|
-
}
|
|
492
|
-
if (typeof error === "string") {
|
|
493
|
-
return error;
|
|
494
|
-
}
|
|
495
|
-
return "Unknown error";
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Validates that a redirect path is safe (relative path or same-origin).
|
|
499
|
-
* Blocks absolute URLs to external domains to prevent open redirect attacks.
|
|
500
|
-
*/
|
|
501
|
-
isAllowedRedirect(path) {
|
|
502
|
-
// Allow relative paths that don't start with // (protocol-relative URLs)
|
|
503
|
-
if (path.startsWith("/") && !path.startsWith("//")) {
|
|
504
|
-
return true;
|
|
505
|
-
}
|
|
506
|
-
// Allow hash-only and query-only paths
|
|
507
|
-
if (path.startsWith("#") || path.startsWith("?")) {
|
|
508
|
-
return true;
|
|
509
|
-
}
|
|
510
|
-
// For absolute URLs, verify same origin
|
|
511
|
-
if (typeof window !== "undefined") {
|
|
512
|
-
try {
|
|
513
|
-
const url = new URL(path, window.location.origin);
|
|
514
|
-
return url.origin === window.location.origin;
|
|
515
|
-
}
|
|
516
|
-
catch {
|
|
517
|
-
// Invalid URL - block it
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
// In non-browser environments, only allow relative paths
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
|
-
}
|