@yak-io/javascript 0.10.1 → 0.11.1
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 +11 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/embed.d.ts.map +1 -1
- package/dist/index.cjs +2061 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +2038 -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/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 -42
- 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 -520
package/dist/logger.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple logger utility for the yak client SDK.
|
|
3
|
-
* Debug/info logs are controlled by __YAK_LOGGING_ENABLED__.
|
|
4
|
-
* Warnings and errors are always logged.
|
|
5
|
-
*
|
|
6
|
-
* Logging is controlled via:
|
|
7
|
-
* 1. localStorage key "yakLogging" (persists across reloads)
|
|
8
|
-
* 2. window.__YAK_LOGGING_ENABLED__ (runtime override)
|
|
9
|
-
*
|
|
10
|
-
* Use the helper functions to enable/disable:
|
|
11
|
-
* enableYakLogging() - turns on logging, persists in localStorage
|
|
12
|
-
* disableYakLogging() - turns off logging, clears localStorage
|
|
13
|
-
*/
|
|
14
|
-
const STORAGE_KEY = "yakLogging";
|
|
15
|
-
function isLoggingEnabled() {
|
|
16
|
-
if (typeof window === "undefined") {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
// Window flag takes precedence (runtime override)
|
|
20
|
-
if (typeof window.__YAK_LOGGING_ENABLED__ === "boolean") {
|
|
21
|
-
return window.__YAK_LOGGING_ENABLED__;
|
|
22
|
-
}
|
|
23
|
-
// Fall back to localStorage for persistence across reloads
|
|
24
|
-
try {
|
|
25
|
-
return localStorage.getItem(STORAGE_KEY) === "true";
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
// localStorage may be unavailable (e.g., private browsing)
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Check if Yak logging is currently enabled.
|
|
34
|
-
* Useful for checking state before creating iframes.
|
|
35
|
-
*/
|
|
36
|
-
export function isYakLoggingEnabled() {
|
|
37
|
-
return isLoggingEnabled();
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Enable Yak debug logging.
|
|
41
|
-
* Persists in localStorage so it survives page reloads.
|
|
42
|
-
* Call this from browser console: `enableYakLogging()`
|
|
43
|
-
*/
|
|
44
|
-
export function enableYakLogging() {
|
|
45
|
-
if (typeof window === "undefined") {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
window.__YAK_LOGGING_ENABLED__ = true;
|
|
49
|
-
try {
|
|
50
|
-
localStorage.setItem(STORAGE_KEY, "true");
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// localStorage may be unavailable
|
|
54
|
-
}
|
|
55
|
-
console.info("[yak] Logging enabled");
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Disable Yak debug logging.
|
|
59
|
-
* Clears localStorage and window flag.
|
|
60
|
-
* Call this from browser console: `disableYakLogging()`
|
|
61
|
-
*/
|
|
62
|
-
export function disableYakLogging() {
|
|
63
|
-
if (typeof window === "undefined") {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
window.__YAK_LOGGING_ENABLED__ = false;
|
|
67
|
-
try {
|
|
68
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
// localStorage may be unavailable
|
|
72
|
-
}
|
|
73
|
-
console.info("[yak] Logging disabled");
|
|
74
|
-
}
|
|
75
|
-
// Expose helpers globally so they can be called from browser console
|
|
76
|
-
if (typeof window !== "undefined") {
|
|
77
|
-
window.enableYakLogging = enableYakLogging;
|
|
78
|
-
window.disableYakLogging = disableYakLogging;
|
|
79
|
-
}
|
|
80
|
-
export const logger = {
|
|
81
|
-
debug: (message, data) => {
|
|
82
|
-
if (isLoggingEnabled()) {
|
|
83
|
-
if (data !== undefined) {
|
|
84
|
-
console.log(`[yak-host] ${message}`, data);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
console.log(`[yak-host] ${message}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
info: (message, data) => {
|
|
92
|
-
if (isLoggingEnabled()) {
|
|
93
|
-
if (data !== undefined) {
|
|
94
|
-
console.info(`[yak-host] ${message}`, data);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
console.info(`[yak-host] ${message}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
warn: (message, data) => {
|
|
102
|
-
if (data !== undefined) {
|
|
103
|
-
console.warn(`[yak-host] ${message}`, data);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
console.warn(`[yak-host] ${message}`);
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
error: (message, data) => {
|
|
110
|
-
if (data !== undefined) {
|
|
111
|
-
console.error(`[yak-host] ${message}`, data);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
console.error(`[yak-host] ${message}`);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
};
|
package/dist/page-context.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extract visible text content from the DOM, filtering out non-content elements
|
|
3
|
-
*/
|
|
4
|
-
function extractPageText() {
|
|
5
|
-
if (typeof document === "undefined")
|
|
6
|
-
return "";
|
|
7
|
-
// Clone the body to avoid modifying the actual DOM
|
|
8
|
-
const bodyClone = document.body.cloneNode(true);
|
|
9
|
-
// Remove script, style, noscript tags and hidden elements
|
|
10
|
-
const unwantedSelectors = [
|
|
11
|
-
"script",
|
|
12
|
-
"style",
|
|
13
|
-
"noscript",
|
|
14
|
-
"iframe",
|
|
15
|
-
"[style*='display: none']",
|
|
16
|
-
"[style*='display:none']",
|
|
17
|
-
"[hidden]",
|
|
18
|
-
".yak-chat-widget",
|
|
19
|
-
];
|
|
20
|
-
for (const selector of unwantedSelectors) {
|
|
21
|
-
const elements = bodyClone.querySelectorAll(selector);
|
|
22
|
-
for (const el of elements) {
|
|
23
|
-
el.remove();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Get text content and clean it up
|
|
27
|
-
let text = bodyClone.textContent || bodyClone.innerText || "";
|
|
28
|
-
// Normalize whitespace: replace multiple spaces/newlines with single space
|
|
29
|
-
text = text.replace(/\s+/g, " ").trim();
|
|
30
|
-
// Limit to reasonable size (100KB max)
|
|
31
|
-
const maxLength = 100000;
|
|
32
|
-
if (text.length > maxLength) {
|
|
33
|
-
text = `${text.substring(0, maxLength)}... [truncated]`;
|
|
34
|
-
}
|
|
35
|
-
return text;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Extract page context including URL, title, and visible text content
|
|
39
|
-
*/
|
|
40
|
-
export function extractPageContext() {
|
|
41
|
-
if (typeof window === "undefined") {
|
|
42
|
-
return {
|
|
43
|
-
url: "",
|
|
44
|
-
title: "",
|
|
45
|
-
text: "",
|
|
46
|
-
timestamp: Date.now(),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
url: window.location.href,
|
|
51
|
-
title: document.title,
|
|
52
|
-
text: extractPageText(),
|
|
53
|
-
timestamp: Date.now(),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Debounce function to limit how often a function is called
|
|
58
|
-
*/
|
|
59
|
-
export function debounce(func, wait) {
|
|
60
|
-
let timeout = null;
|
|
61
|
-
return function executedFunction(...args) {
|
|
62
|
-
const later = () => {
|
|
63
|
-
timeout = null;
|
|
64
|
-
func(...args);
|
|
65
|
-
};
|
|
66
|
-
if (timeout) {
|
|
67
|
-
clearTimeout(timeout);
|
|
68
|
-
}
|
|
69
|
-
timeout = setTimeout(later, wait);
|
|
70
|
-
};
|
|
71
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { normalizeRouteSources, normalizeToolSources } from "./sources.js";
|
|
2
|
-
export function createYakHandler(config) {
|
|
3
|
-
const routeSources = normalizeRouteSources(config.routes);
|
|
4
|
-
const toolSources = normalizeToolSources(config.tools);
|
|
5
|
-
const GET = async function handleConfig(_req) {
|
|
6
|
-
const chatConfig = {
|
|
7
|
-
routes: await buildRouteManifest(routeSources),
|
|
8
|
-
};
|
|
9
|
-
if (toolSources.length > 0) {
|
|
10
|
-
const { manifest } = await buildToolRegistry(toolSources);
|
|
11
|
-
chatConfig.tools = manifest;
|
|
12
|
-
}
|
|
13
|
-
return jsonResponse(chatConfig, 200, {
|
|
14
|
-
"Cache-Control": "no-store",
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
|
-
const POST = async function handleToolCall(req) {
|
|
18
|
-
if (toolSources.length === 0) {
|
|
19
|
-
return errorResponse("Tool execution is not configured", 501);
|
|
20
|
-
}
|
|
21
|
-
let payload;
|
|
22
|
-
try {
|
|
23
|
-
payload = await req.json();
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return errorResponse("Invalid JSON payload", 400);
|
|
27
|
-
}
|
|
28
|
-
if (!isHostToolCallPayload(payload)) {
|
|
29
|
-
return errorResponse("Invalid tool call payload", 400);
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const { lookup } = await buildToolRegistry(toolSources);
|
|
33
|
-
const owner = lookup.get(payload.name);
|
|
34
|
-
if (!owner) {
|
|
35
|
-
return createToolErrorResponse(`Tool '${payload.name}' is not registered`);
|
|
36
|
-
}
|
|
37
|
-
if (!owner.executeTool) {
|
|
38
|
-
return createToolErrorResponse(`Tool '${payload.name}' does not expose an executor`);
|
|
39
|
-
}
|
|
40
|
-
const result = await owner.executeTool(payload.name, payload.args, req);
|
|
41
|
-
const successResult = {
|
|
42
|
-
ok: true,
|
|
43
|
-
result,
|
|
44
|
-
};
|
|
45
|
-
return jsonResponse(successResult);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
return createToolErrorResponse(extractErrorMessage(error));
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
return { GET, POST };
|
|
52
|
-
}
|
|
53
|
-
export function createYakConfigHandler(config) {
|
|
54
|
-
const routeSources = normalizeRouteSources(config.routes);
|
|
55
|
-
const toolSources = normalizeToolSources(config.tools);
|
|
56
|
-
return async function handleConfig(_req) {
|
|
57
|
-
const chatConfig = {
|
|
58
|
-
routes: await buildRouteManifest(routeSources),
|
|
59
|
-
};
|
|
60
|
-
if (toolSources.length > 0) {
|
|
61
|
-
const { manifest } = await buildToolRegistry(toolSources);
|
|
62
|
-
chatConfig.tools = manifest;
|
|
63
|
-
}
|
|
64
|
-
return jsonResponse(chatConfig, 200, {
|
|
65
|
-
"Cache-Control": "no-store",
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
export function createYakToolsHandler(config) {
|
|
70
|
-
const toolSources = normalizeToolSources(config.tools);
|
|
71
|
-
if (toolSources.length === 0) {
|
|
72
|
-
throw new Error("createYakToolsHandler requires at least one tool source");
|
|
73
|
-
}
|
|
74
|
-
return async function handleTools(req) {
|
|
75
|
-
let payload;
|
|
76
|
-
try {
|
|
77
|
-
payload = await req.json();
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return errorResponse("Invalid JSON payload", 400);
|
|
81
|
-
}
|
|
82
|
-
if (!isHostToolCallPayload(payload)) {
|
|
83
|
-
return errorResponse("Invalid tool call payload", 400);
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const { lookup } = await buildToolRegistry(toolSources);
|
|
87
|
-
const owner = lookup.get(payload.name);
|
|
88
|
-
if (!owner) {
|
|
89
|
-
return createToolErrorResponse(`Tool '${payload.name}' is not registered`);
|
|
90
|
-
}
|
|
91
|
-
if (!owner.executeTool) {
|
|
92
|
-
return createToolErrorResponse(`Tool '${payload.name}' does not expose an executor`);
|
|
93
|
-
}
|
|
94
|
-
const result = await owner.executeTool(payload.name, payload.args, req);
|
|
95
|
-
const successResult = {
|
|
96
|
-
ok: true,
|
|
97
|
-
result,
|
|
98
|
-
};
|
|
99
|
-
return jsonResponse(successResult);
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
return createToolErrorResponse(extractErrorMessage(error));
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
async function buildRouteManifest(routeSources) {
|
|
107
|
-
const entries = await Promise.all(routeSources.map(async (source) => ({
|
|
108
|
-
id: source.id ?? "routes",
|
|
109
|
-
routes: await source.getRoutes(),
|
|
110
|
-
})));
|
|
111
|
-
const merged = [];
|
|
112
|
-
const seen = new Set();
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
for (const route of entry.routes) {
|
|
115
|
-
const key = route.path;
|
|
116
|
-
if (!seen.has(key)) {
|
|
117
|
-
seen.add(key);
|
|
118
|
-
merged.push(route);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
merged.sort((a, b) => a.path.localeCompare(b.path));
|
|
123
|
-
return {
|
|
124
|
-
routes: merged,
|
|
125
|
-
generated_at: new Date().toISOString(),
|
|
126
|
-
sources: entries.map((entry) => ({ id: entry.id, count: entry.routes.length })),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
async function buildToolRegistry(toolSources) {
|
|
130
|
-
const entries = await Promise.all(toolSources.map(async (source) => ({
|
|
131
|
-
id: source.id ?? "tools",
|
|
132
|
-
source,
|
|
133
|
-
tools: await source.getTools(),
|
|
134
|
-
})));
|
|
135
|
-
const manifestTools = [];
|
|
136
|
-
const lookup = new Map();
|
|
137
|
-
for (const entry of entries) {
|
|
138
|
-
for (const tool of entry.tools) {
|
|
139
|
-
manifestTools.push(tool);
|
|
140
|
-
lookup.set(tool.name, entry.source);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
manifest: {
|
|
145
|
-
tools: manifestTools,
|
|
146
|
-
generated_at: new Date().toISOString(),
|
|
147
|
-
sources: entries.map((entry) => ({ id: entry.id, count: entry.tools.length })),
|
|
148
|
-
},
|
|
149
|
-
lookup,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function isHostToolCallPayload(payload) {
|
|
153
|
-
return (typeof payload === "object" &&
|
|
154
|
-
payload !== null &&
|
|
155
|
-
typeof payload.name === "string" &&
|
|
156
|
-
"args" in payload);
|
|
157
|
-
}
|
|
158
|
-
function extractErrorMessage(error) {
|
|
159
|
-
if (error instanceof Error) {
|
|
160
|
-
return error.message;
|
|
161
|
-
}
|
|
162
|
-
if (typeof error === "string") {
|
|
163
|
-
return error;
|
|
164
|
-
}
|
|
165
|
-
return "An unknown error occurred";
|
|
166
|
-
}
|
|
167
|
-
function jsonResponse(body, status = 200, headers = {}) {
|
|
168
|
-
return new Response(JSON.stringify(body), {
|
|
169
|
-
status,
|
|
170
|
-
headers: {
|
|
171
|
-
"Content-Type": "application/json",
|
|
172
|
-
...headers,
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
function errorResponse(message, status) {
|
|
177
|
-
return jsonResponse({ error: message }, status);
|
|
178
|
-
}
|
|
179
|
-
function createToolErrorResponse(error) {
|
|
180
|
-
const errorResult = {
|
|
181
|
-
ok: false,
|
|
182
|
-
error,
|
|
183
|
-
};
|
|
184
|
-
return jsonResponse(errorResult);
|
|
185
|
-
}
|
package/dist/server/index.js
DELETED
package/dist/server/sources.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
function isRouteSource(value) {
|
|
2
|
-
return Boolean(value &&
|
|
3
|
-
typeof value === "object" &&
|
|
4
|
-
"getRoutes" in value &&
|
|
5
|
-
typeof value.getRoutes === "function");
|
|
6
|
-
}
|
|
7
|
-
function isRouteInfoArray(value) {
|
|
8
|
-
return Array.isArray(value) && value.every((route) => route && typeof route.path === "string");
|
|
9
|
-
}
|
|
10
|
-
function normalizeRouteSourceItem(item, index) {
|
|
11
|
-
if (isRouteSource(item)) {
|
|
12
|
-
return {
|
|
13
|
-
id: item.id ?? `route-source-${index}`,
|
|
14
|
-
getRoutes: item.getRoutes,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
if (typeof item === "function") {
|
|
18
|
-
return {
|
|
19
|
-
id: `route-fn-${index}`,
|
|
20
|
-
getRoutes: item,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
if (isRouteInfoArray(item)) {
|
|
24
|
-
return {
|
|
25
|
-
id: `route-static-${index}`,
|
|
26
|
-
getRoutes: async () => item,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
throw new Error("Unsupported route source input");
|
|
30
|
-
}
|
|
31
|
-
export function normalizeRouteSources(input) {
|
|
32
|
-
if (Array.isArray(input)) {
|
|
33
|
-
if (input.length === 0) {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
if (isRouteInfoArray(input)) {
|
|
37
|
-
return [normalizeRouteSourceItem(input, 0)];
|
|
38
|
-
}
|
|
39
|
-
return input.map((item, index) => normalizeRouteSourceItem(item, index));
|
|
40
|
-
}
|
|
41
|
-
return [normalizeRouteSourceItem(input, 0)];
|
|
42
|
-
}
|
|
43
|
-
function isToolSource(value) {
|
|
44
|
-
return Boolean(value &&
|
|
45
|
-
typeof value === "object" &&
|
|
46
|
-
"getTools" in value &&
|
|
47
|
-
typeof value.getTools === "function");
|
|
48
|
-
}
|
|
49
|
-
function isToolManifest(value) {
|
|
50
|
-
return Boolean(value && typeof value === "object" && "tools" in value);
|
|
51
|
-
}
|
|
52
|
-
function isToolDefinitionArray(value) {
|
|
53
|
-
return Array.isArray(value) && value.every((tool) => tool && typeof tool.name === "string");
|
|
54
|
-
}
|
|
55
|
-
function toToolDefinitions(result) {
|
|
56
|
-
if (isToolDefinitionArray(result)) {
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
|
-
if (isToolManifest(result)) {
|
|
60
|
-
return result.tools;
|
|
61
|
-
}
|
|
62
|
-
throw new Error("Tool source must resolve to ToolDefinition[] or ToolManifest");
|
|
63
|
-
}
|
|
64
|
-
function normalizeToolSourceItem(item, index) {
|
|
65
|
-
if (isToolSource(item)) {
|
|
66
|
-
return {
|
|
67
|
-
id: item.id ?? `tool-source-${index}`,
|
|
68
|
-
getTools: async () => {
|
|
69
|
-
const result = await item.getTools();
|
|
70
|
-
return toToolDefinitions(result);
|
|
71
|
-
},
|
|
72
|
-
executeTool: item.executeTool,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
if (typeof item === "function") {
|
|
76
|
-
return {
|
|
77
|
-
id: `tool-fn-${index}`,
|
|
78
|
-
getTools: async () => {
|
|
79
|
-
const result = await item();
|
|
80
|
-
return toToolDefinitions(result);
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
if (isToolDefinitionArray(item)) {
|
|
85
|
-
return {
|
|
86
|
-
id: `tool-static-${index}`,
|
|
87
|
-
getTools: async () => item,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if (isToolManifest(item)) {
|
|
91
|
-
return {
|
|
92
|
-
id: `tool-manifest-${index}`,
|
|
93
|
-
getTools: async () => item.tools,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
if (item &&
|
|
97
|
-
typeof item === "object" &&
|
|
98
|
-
"getTools" in item &&
|
|
99
|
-
typeof item.getTools === "function") {
|
|
100
|
-
return {
|
|
101
|
-
id: item.id ?? `tool-wrapper-${index}`,
|
|
102
|
-
getTools: async () => {
|
|
103
|
-
const result = await item.getTools();
|
|
104
|
-
return toToolDefinitions(result);
|
|
105
|
-
},
|
|
106
|
-
executeTool: item.executeTool,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
throw new Error("Unsupported tool source input");
|
|
110
|
-
}
|
|
111
|
-
export function normalizeToolSources(input) {
|
|
112
|
-
if (!input) {
|
|
113
|
-
return [];
|
|
114
|
-
}
|
|
115
|
-
if (Array.isArray(input)) {
|
|
116
|
-
if (input.length === 0) {
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
|
-
if (isToolDefinitionArray(input)) {
|
|
120
|
-
return [normalizeToolSourceItem(input, 0)];
|
|
121
|
-
}
|
|
122
|
-
return input.map((item, index) => normalizeToolSourceItem(item, index));
|
|
123
|
-
}
|
|
124
|
-
return [normalizeToolSourceItem(input, 0)];
|
|
125
|
-
}
|
package/dist/tool-name.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/** OpenAI/Realtime function names must match `^[a-zA-Z0-9_-]{1,64}$`. */
|
|
2
|
-
const MAX_TOOL_NAME_LENGTH = 64;
|
|
3
|
-
/**
|
|
4
|
-
* Convert a host tool name into a model-facing function name.
|
|
5
|
-
*
|
|
6
|
-
* We sanitise to the allowed function-name charset (rather than hashing to an opaque
|
|
7
|
-
* `yt_<hex>`), so the model keeps the semantic signal of a readable name — e.g.
|
|
8
|
-
* `orders.list` → `orders_list`.
|
|
9
|
-
*
|
|
10
|
-
* This is a pure per-name transform; uniqueness across a manifest (and avoiding the
|
|
11
|
-
* reserved `redirect` name / `mcp__` namespace) is handled by the caller via
|
|
12
|
-
* {@link uniqueToolId}. Each runtime (the chat-ui iframe and this SDK's voice session)
|
|
13
|
-
* decorates and reverse-maps with its own manifest, so the ids never need to match
|
|
14
|
-
* across paths — only to be self-consistent within one.
|
|
15
|
-
*/
|
|
16
|
-
export function generateToolId(originalName) {
|
|
17
|
-
const sanitized = originalName.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, MAX_TOOL_NAME_LENGTH);
|
|
18
|
-
return sanitized.length > 0 ? sanitized : "tool";
|
|
19
|
-
}
|
|
20
|
-
/** The MCP namespace prefix the voice/chat dispatchers route on. */
|
|
21
|
-
const MCP_NAMESPACE_PREFIX = "mcp__";
|
|
22
|
-
/**
|
|
23
|
-
* Resolve a collision-free, model-safe id for a host tool name, given the ids already
|
|
24
|
-
* taken in this manifest (seed `used` with reserved names like `redirect`). Host ids must
|
|
25
|
-
* never start with the `mcp__` namespace, which the dispatcher routes on.
|
|
26
|
-
*/
|
|
27
|
-
export function uniqueToolId(originalName, used) {
|
|
28
|
-
let base = generateToolId(originalName);
|
|
29
|
-
if (base.startsWith(MCP_NAMESPACE_PREFIX)) {
|
|
30
|
-
base = `t_${base}`.slice(0, MAX_TOOL_NAME_LENGTH);
|
|
31
|
-
}
|
|
32
|
-
if (!used.has(base))
|
|
33
|
-
return base;
|
|
34
|
-
for (let i = 2; i < 1000; i++) {
|
|
35
|
-
const suffix = `_${i}`;
|
|
36
|
-
const candidate = `${base.slice(0, MAX_TOOL_NAME_LENGTH - suffix.length)}${suffix}`;
|
|
37
|
-
if (!used.has(candidate))
|
|
38
|
-
return candidate;
|
|
39
|
-
}
|
|
40
|
-
// Pathological fallback — vanishingly unlikely in practice.
|
|
41
|
-
return `${base.slice(0, 56)}_${used.size}`;
|
|
42
|
-
}
|
package/dist/toolset.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { logger } from "./logger.js";
|
|
2
|
-
/**
|
|
3
|
-
* Compose client-side {@link ToolAdapter}s into ONE merged tool manifest and ONE
|
|
4
|
-
* routed `onToolCall`. This is the single injection point for "available tools"
|
|
5
|
-
* into the iframe: every adapter — client-side (GraphQL/REST/custom, run by your own
|
|
6
|
-
* `execute` callback) or server-relayed ({@link createYakServerAdapter}) — contributes
|
|
7
|
-
* its tools to the merged manifest and its executor to the routed handler. Because every
|
|
8
|
-
* tool now flows through `onToolCall`, they all surface through `onToolCallComplete` /
|
|
9
|
-
* `useYakToolEvent`.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```tsx
|
|
13
|
-
* const toolset = createYakToolset([
|
|
14
|
-
* createYakServerAdapter({ endpoint: "/api/yak" }), // tRPC + custom server tools
|
|
15
|
-
* createGraphQLToolAdapter({ name: "shop", schema, execute: runShopQuery }),
|
|
16
|
-
* createRESTToolAdapter({ name: "billing", spec, execute: callBillingApi }),
|
|
17
|
-
* ]);
|
|
18
|
-
*
|
|
19
|
-
* <YakProvider
|
|
20
|
-
* getConfig={async () => ({ routes, ...(await toolset.getConfig()) })}
|
|
21
|
-
* onToolCall={toolset.onToolCall}
|
|
22
|
-
* />;
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export function createYakToolset(adapters) {
|
|
26
|
-
let cache = null;
|
|
27
|
-
async function resolve(force = false) {
|
|
28
|
-
if (cache && !force)
|
|
29
|
-
return cache;
|
|
30
|
-
cache = await Promise.all(adapters.map(async (adapter) => ({ adapter, tools: await adapter.getTools() })));
|
|
31
|
-
return cache;
|
|
32
|
-
}
|
|
33
|
-
async function getConfig() {
|
|
34
|
-
// Refresh on every config load — tools may depend on page/user.
|
|
35
|
-
const resolved = await resolve(true);
|
|
36
|
-
const tools = [];
|
|
37
|
-
const seen = new Set();
|
|
38
|
-
for (const { adapter, tools: adapterTools } of resolved) {
|
|
39
|
-
for (const tool of adapterTools) {
|
|
40
|
-
if (seen.has(tool.name)) {
|
|
41
|
-
logger.warn(`Duplicate tool name "${tool.name}"; keeping the first and ignoring the one from adapter "${adapter.id ?? "unknown"}".`);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
seen.add(tool.name);
|
|
45
|
-
tools.push(tool);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
tools: {
|
|
50
|
-
tools,
|
|
51
|
-
sources: resolved.map(({ adapter, tools: adapterTools }, index) => ({
|
|
52
|
-
id: adapter.id ?? `adapter-${index}`,
|
|
53
|
-
count: adapterTools.length,
|
|
54
|
-
})),
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
const onToolCall = async (name, args) => {
|
|
59
|
-
// Fast path: explicit ownership predicates need no resolution.
|
|
60
|
-
for (const adapter of adapters) {
|
|
61
|
-
if (adapter.ownsTool?.(name)) {
|
|
62
|
-
return adapter.execute(name, args);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// Otherwise route by the adapter's resolved tool names.
|
|
66
|
-
const resolved = await resolve();
|
|
67
|
-
for (const { adapter, tools } of resolved) {
|
|
68
|
-
if (adapter.ownsTool)
|
|
69
|
-
continue; // already consulted above
|
|
70
|
-
if (tools.some((tool) => tool.name === name)) {
|
|
71
|
-
return adapter.execute(name, args);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
75
|
-
};
|
|
76
|
-
return { getConfig, onToolCall };
|
|
77
|
-
}
|
|
78
|
-
async function buildHeaders(source, base) {
|
|
79
|
-
const headers = new Headers(base);
|
|
80
|
-
const resolved = typeof source === "function" ? await source() : source;
|
|
81
|
-
if (resolved) {
|
|
82
|
-
for (const [key, value] of new Headers(resolved)) {
|
|
83
|
-
headers.set(key, value);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return headers;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Bridge a server-side yak handler ({@link createYakHandler} /
|
|
90
|
-
* `createNextYakHandler`, e.g. fronting the `@yak-io/trpc` adapter) into a
|
|
91
|
-
* client-side {@link ToolAdapter}, so server-relayed tools merge into the same
|
|
92
|
-
* manifest + `onToolCall` as browser-executed adapters.
|
|
93
|
-
*/
|
|
94
|
-
export function createYakServerAdapter(config = {}) {
|
|
95
|
-
const endpoint = config.endpoint ?? "/api/yak";
|
|
96
|
-
return {
|
|
97
|
-
id: config.id ?? "server",
|
|
98
|
-
getTools: async () => {
|
|
99
|
-
const res = await fetch(endpoint, { headers: await buildHeaders(config.headers) });
|
|
100
|
-
if (!res.ok) {
|
|
101
|
-
throw new Error(`Failed to load tools from ${endpoint} (${res.status})`);
|
|
102
|
-
}
|
|
103
|
-
const chatConfig = (await res.json());
|
|
104
|
-
return chatConfig.tools?.tools ?? [];
|
|
105
|
-
},
|
|
106
|
-
execute: async (name, args) => {
|
|
107
|
-
const res = await fetch(endpoint, {
|
|
108
|
-
method: "POST",
|
|
109
|
-
headers: await buildHeaders(config.headers, { "Content-Type": "application/json" }),
|
|
110
|
-
body: JSON.stringify({ name, args }),
|
|
111
|
-
});
|
|
112
|
-
const data = (await res.json().catch(() => ({})));
|
|
113
|
-
if (!res.ok || !data.ok) {
|
|
114
|
-
throw new Error(data.error ?? `Tool "${name}" failed (${res.status})`);
|
|
115
|
-
}
|
|
116
|
-
return data.result;
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
}
|
package/dist/types/config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/messaging.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/routes.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/tools.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/version.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Embed protocol version.
|
|
3
|
-
*
|
|
4
|
-
* This version is used in the embed URL path (e.g., /embed/v1/[appId])
|
|
5
|
-
* and in the message protocol to ensure compatibility between the
|
|
6
|
-
* host packages (@yak-io/javascript, @yak-io/react, @yak-io/nextjs)
|
|
7
|
-
* and the embedded chat UI.
|
|
8
|
-
*
|
|
9
|
-
* Increment this version when making breaking changes to:
|
|
10
|
-
* - The postMessage protocol (IframeMessageFromHost, IframeMessageToHost)
|
|
11
|
-
* - The tool manifest format
|
|
12
|
-
* - The route manifest format
|
|
13
|
-
* - The config payload structure
|
|
14
|
-
*
|
|
15
|
-
* Version history:
|
|
16
|
-
* - v1: Initial versioned protocol
|
|
17
|
-
*/
|
|
18
|
-
export const EMBED_PROTOCOL_VERSION = "1";
|