@alexkroman1/aai 0.12.2 → 1.0.2
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 +20 -0
- package/CHANGELOG.md +174 -0
- package/dist/constants-VTFoymJ-.js +47 -0
- package/dist/host/_run-code.d.ts +4 -2
- package/dist/host/_runtime-conformance.d.ts +4 -5
- package/dist/host/builtin-tools.d.ts +11 -7
- package/dist/host/runtime-barrel.d.ts +15 -0
- package/dist/{direct-executor-ZUU0Ke4j.js → host/runtime-barrel.js} +463 -345
- package/dist/host/runtime-config.d.ts +42 -0
- package/dist/host/runtime.d.ts +119 -35
- package/dist/host/s2s.d.ts +14 -38
- package/dist/host/server.d.ts +16 -8
- package/dist/host/session-ctx.d.ts +55 -0
- package/dist/host/session.d.ts +21 -70
- package/dist/host/tool-executor.d.ts +20 -0
- package/dist/host/unstorage-kv.d.ts +1 -1
- package/dist/host/ws-handler.d.ts +4 -2
- package/dist/index.d.ts +9 -20
- package/dist/index.js +63 -2
- package/dist/{isolate → sdk}/_internal-types.d.ts +6 -10
- package/dist/{isolate → sdk}/constants.d.ts +6 -4
- package/dist/sdk/define.d.ts +66 -0
- package/dist/{isolate → sdk}/kv.d.ts +1 -49
- package/dist/sdk/manifest-barrel.d.ts +8 -0
- package/dist/sdk/manifest-barrel.js +52 -0
- package/dist/sdk/manifest.d.ts +50 -0
- package/dist/{isolate → sdk}/protocol.d.ts +59 -36
- package/dist/sdk/protocol.js +163 -0
- package/dist/{isolate → sdk}/system-prompt.d.ts +3 -2
- package/dist/sdk/types.d.ts +201 -0
- package/dist/sdk/ws-upgrade.d.ts +5 -0
- package/dist/{system-prompt-CVJSQJiA.js → system-prompt-nik_iavo.js} +11 -10
- package/dist/types-Cfx_4QDK.js +39 -0
- package/dist/ws-upgrade-BeOQ7fXL.js +30 -0
- package/exports-no-dev-deps.test.ts +62 -0
- package/host/_mock-ws.ts +185 -0
- package/host/_run-code.ts +217 -0
- package/host/_runtime-conformance.ts +143 -0
- package/host/_test-utils.ts +276 -0
- package/host/builtin-tools.test.ts +774 -0
- package/host/builtin-tools.ts +255 -0
- package/host/cleanup.test.ts +422 -0
- package/host/fixture-replay.test.ts +463 -0
- package/host/fixtures/README.md +40 -0
- package/host/fixtures/greeting-session-sequence.json +40 -0
- package/host/fixtures/reply-audio-samples.json +42 -0
- package/host/fixtures/reply-lifecycle.json +21 -0
- package/host/fixtures/session-ready.json +48 -0
- package/host/fixtures/session-updated.json +45 -0
- package/host/fixtures/simple-question-sequence.json +73 -0
- package/host/fixtures/tool-call-sequence.json +114 -0
- package/host/fixtures/tool-calls.json +11 -0
- package/host/fixtures/tool-config-session-sequence.json +51 -0
- package/host/fixtures/user-speech-recognition.json +30 -0
- package/host/fixtures/web-search-sequence.json +122 -0
- package/host/integration.test.ts +222 -0
- package/host/runtime-barrel.ts +25 -0
- package/host/runtime-config.test.ts +71 -0
- package/host/runtime-config.ts +99 -0
- package/host/runtime.test.ts +641 -0
- package/host/runtime.ts +308 -0
- package/host/s2s-fixtures.test.ts +237 -0
- package/host/s2s.test.ts +562 -0
- package/host/s2s.ts +310 -0
- package/host/server-shutdown.test.ts +76 -0
- package/host/server.test.ts +116 -0
- package/host/server.ts +223 -0
- package/host/session-ctx.ts +107 -0
- package/host/session-fixture-replay.test.ts +136 -0
- package/host/session-prompt.test.ts +77 -0
- package/host/session.test.ts +590 -0
- package/host/session.ts +370 -0
- package/host/tool-executor.test.ts +124 -0
- package/host/tool-executor.ts +80 -0
- package/host/unstorage-kv.test.ts +99 -0
- package/host/unstorage-kv.ts +69 -0
- package/host/ws-handler.test.ts +739 -0
- package/host/ws-handler.ts +255 -0
- package/index.ts +16 -0
- package/package.json +28 -72
- package/sdk/_internal-types.test.ts +34 -0
- package/sdk/_internal-types.ts +115 -0
- package/sdk/compat-fixtures/README.md +26 -0
- package/sdk/compat-fixtures/v1.json +68 -0
- package/sdk/constants.ts +77 -0
- package/sdk/define.test.ts +57 -0
- package/sdk/define.ts +88 -0
- package/sdk/kv.ts +60 -0
- package/sdk/manifest-barrel.ts +12 -0
- package/sdk/manifest.test.ts +56 -0
- package/sdk/manifest.ts +89 -0
- package/sdk/protocol-compat.test.ts +187 -0
- package/sdk/protocol-snapshot.test.ts +199 -0
- package/sdk/protocol.test.ts +170 -0
- package/sdk/protocol.ts +223 -0
- package/sdk/schema-alignment.test.ts +191 -0
- package/sdk/system-prompt.test.ts +111 -0
- package/sdk/system-prompt.ts +74 -0
- package/sdk/tsconfig.json +12 -0
- package/sdk/types-inference.test.ts +122 -0
- package/sdk/types.test.ts +14 -0
- package/sdk/types.ts +226 -0
- package/sdk/utils.test.ts +52 -0
- package/sdk/utils.ts +20 -0
- package/sdk/ws-upgrade.test.ts +48 -0
- package/sdk/ws-upgrade.ts +13 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +26 -0
- package/vitest.config.ts +17 -0
- package/dist/host/_test-utils.d.ts +0 -73
- package/dist/host/direct-executor.d.ts +0 -128
- package/dist/host/index.d.ts +0 -18
- package/dist/host/index.js +0 -165
- package/dist/host/matchers.d.ts +0 -20
- package/dist/host/matchers.js +0 -41
- package/dist/host/server.js +0 -164
- package/dist/host/testing.d.ts +0 -294
- package/dist/host/testing.js +0 -2
- package/dist/host/vite-plugin.d.ts +0 -15
- package/dist/host/vite-plugin.js +0 -83
- package/dist/isolate/_kv-utils.d.ts +0 -10
- package/dist/isolate/_utils.js +0 -17
- package/dist/isolate/hooks.d.ts +0 -44
- package/dist/isolate/hooks.js +0 -58
- package/dist/isolate/index.d.ts +0 -18
- package/dist/isolate/index.js +0 -6
- package/dist/isolate/kv.js +0 -1
- package/dist/isolate/protocol.js +0 -2
- package/dist/isolate/types.d.ts +0 -418
- package/dist/isolate/types.js +0 -175
- package/dist/protocol-rcOrz7T3.js +0 -183
- package/dist/testing-Bb2B5Uob.js +0 -513
- package/dist/types.test-d.d.ts +0 -7
- /package/dist/{isolate/_utils.d.ts → sdk/utils.d.ts} +0 -0
|
@@ -1,18 +1,76 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { errorDetail, errorMessage,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { a as DEFAULT_SHUTDOWN_TIMEOUT_MS, c as FETCH_TIMEOUT_MS, d as MAX_PAGE_CHARS, f as MAX_TOOL_RESULT_CHARS, g as TOOL_EXECUTION_TIMEOUT_MS, h as RUN_CODE_TIMEOUT_MS, l as MAX_HTML_BYTES, m as MAX_WS_PAYLOAD_BYTES, o as DEFAULT_STT_SAMPLE_RATE, p as MAX_VALUE_SIZE, s as DEFAULT_TTS_SAMPLE_RATE, t as AGENT_CSP } from "../constants-VTFoymJ-.js";
|
|
2
|
+
import { i as toolError, n as errorDetail, r as errorMessage, t as parseWsUpgradeParams } from "../ws-upgrade-BeOQ7fXL.js";
|
|
3
|
+
import { ClientMessageSchema, buildReadyConfig, lenientParse } from "../sdk/protocol.js";
|
|
4
|
+
import { a as agentToolsToSchemas, o as toAgentConfig, r as EMPTY_PARAMS, t as buildSystemPrompt } from "../system-prompt-nik_iavo.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { convert } from "html-to-text";
|
|
7
|
+
import vm from "node:vm";
|
|
6
8
|
import pTimeout from "p-timeout";
|
|
7
9
|
import { createStorage, prefixStorage } from "unstorage";
|
|
8
|
-
import vm from "node:vm";
|
|
9
10
|
import { createNanoEvents } from "nanoevents";
|
|
10
|
-
import WsWebSocket from "ws";
|
|
11
|
+
import WsWebSocket, { WebSocketServer } from "ws";
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import http from "node:http";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import escapeHtml from "escape-html";
|
|
16
|
+
import { lookup } from "mime-types";
|
|
11
17
|
//#region host/_run-code.ts
|
|
12
18
|
/**
|
|
13
19
|
* run_code built-in tool — executes user JavaScript in a fresh `node:vm`
|
|
14
20
|
* context with no network, filesystem, or process access.
|
|
15
21
|
*/
|
|
22
|
+
const SKIPPED_CLASS_KEYS = new Set([
|
|
23
|
+
"constructor",
|
|
24
|
+
"prototype",
|
|
25
|
+
"length",
|
|
26
|
+
"name"
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* Copy static members from a class constructor to a wrapper function,
|
|
30
|
+
* skipping built-in keys that must not be forwarded.
|
|
31
|
+
*/
|
|
32
|
+
function copyStaticMembers(src, dst) {
|
|
33
|
+
for (const key of Object.getOwnPropertyNames(src)) {
|
|
34
|
+
if (SKIPPED_CLASS_KEYS.has(key)) continue;
|
|
35
|
+
try {
|
|
36
|
+
const desc = Object.getOwnPropertyDescriptor(src, key);
|
|
37
|
+
if (desc) Object.defineProperty(dst, key, desc);
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Neuter the `.constructor` chain on a host function or class constructor.
|
|
43
|
+
*
|
|
44
|
+
* For plain functions: wraps the function so calling `.constructor` or
|
|
45
|
+
* `.constructor.constructor` no longer exposes the host `Function`.
|
|
46
|
+
*
|
|
47
|
+
* For class constructors: additionally copies static methods and neutralizes
|
|
48
|
+
* `prototype.constructor` so instances created via `new` also cannot escape.
|
|
49
|
+
*
|
|
50
|
+
* This prevents sandbox code from reaching the host `Function` constructor
|
|
51
|
+
* via patterns like `fn.constructor.constructor('return process')()`.
|
|
52
|
+
*/
|
|
53
|
+
function neutralizeConstructor(fn) {
|
|
54
|
+
const hasPrototype = typeof fn.prototype === "object" && fn.prototype !== null;
|
|
55
|
+
function Wrapper(...args) {
|
|
56
|
+
if (hasPrototype) return new fn(...args);
|
|
57
|
+
return fn(...args);
|
|
58
|
+
}
|
|
59
|
+
if (hasPrototype) {
|
|
60
|
+
copyStaticMembers(fn, Wrapper);
|
|
61
|
+
if (Wrapper.prototype) Object.defineProperty(Wrapper.prototype, "constructor", {
|
|
62
|
+
value: void 0,
|
|
63
|
+
writable: false,
|
|
64
|
+
configurable: false
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
Object.defineProperty(Wrapper, "constructor", {
|
|
68
|
+
value: void 0,
|
|
69
|
+
writable: false,
|
|
70
|
+
configurable: false
|
|
71
|
+
});
|
|
72
|
+
return Wrapper;
|
|
73
|
+
}
|
|
16
74
|
const runCodeParams = z.object({ code: z.string().describe("JavaScript code to execute. Use console.log() for output.") });
|
|
17
75
|
/**
|
|
18
76
|
* Execute JavaScript code inside a fresh `node:vm` context.
|
|
@@ -29,6 +87,7 @@ const runCodeParams = z.object({ code: z.string().describe("JavaScript code to e
|
|
|
29
87
|
*/
|
|
30
88
|
function createRunCode() {
|
|
31
89
|
return {
|
|
90
|
+
guidance: "You MUST use the run_code tool for ANY question involving math, counting, calculations, data processing, or code. NEVER do mental math or recite code verbally. run_code executes JavaScript (not Python). Always write JavaScript.",
|
|
32
91
|
description: "Execute JavaScript code in a sandbox and return the output. Use this for calculations, data transformations, string manipulation, or any task that benefits from running code. Output is captured from console.log(). No network or filesystem access.",
|
|
33
92
|
parameters: runCodeParams,
|
|
34
93
|
async execute(args) {
|
|
@@ -47,34 +106,67 @@ function createRunCode() {
|
|
|
47
106
|
async function executeInIsolate(code) {
|
|
48
107
|
const output = [];
|
|
49
108
|
const capture = (...args) => output.push(args.map(String).join(" "));
|
|
109
|
+
const activeTimers = /* @__PURE__ */ new Set();
|
|
110
|
+
const sandboxSetTimeout = (fn, delay, ...args) => {
|
|
111
|
+
const id = setTimeout((...a) => {
|
|
112
|
+
activeTimers.delete(id);
|
|
113
|
+
fn(...a);
|
|
114
|
+
}, delay, ...args);
|
|
115
|
+
activeTimers.add(id);
|
|
116
|
+
return id;
|
|
117
|
+
};
|
|
118
|
+
const sandboxClearTimeout = (id) => {
|
|
119
|
+
if (id !== void 0) {
|
|
120
|
+
activeTimers.delete(id);
|
|
121
|
+
clearTimeout(id);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const sandboxSetInterval = (fn, delay, ...args) => {
|
|
125
|
+
const id = setInterval(fn, delay, ...args);
|
|
126
|
+
activeTimers.add(id);
|
|
127
|
+
return id;
|
|
128
|
+
};
|
|
129
|
+
const sandboxClearInterval = (id) => {
|
|
130
|
+
if (id !== void 0) {
|
|
131
|
+
activeTimers.delete(id);
|
|
132
|
+
clearInterval(id);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
50
135
|
const context = vm.createContext({
|
|
51
|
-
console: {
|
|
52
|
-
log: capture,
|
|
53
|
-
info: capture,
|
|
54
|
-
warn: capture,
|
|
55
|
-
error: capture,
|
|
56
|
-
debug: capture
|
|
57
|
-
},
|
|
58
|
-
setTimeout,
|
|
59
|
-
clearTimeout,
|
|
60
|
-
setInterval,
|
|
61
|
-
clearInterval,
|
|
62
|
-
URL,
|
|
63
|
-
URLSearchParams,
|
|
64
|
-
TextEncoder,
|
|
65
|
-
TextDecoder,
|
|
66
|
-
atob,
|
|
67
|
-
btoa,
|
|
68
|
-
structuredClone
|
|
69
|
-
|
|
70
|
-
|
|
136
|
+
console: Object.freeze({
|
|
137
|
+
log: neutralizeConstructor(capture),
|
|
138
|
+
info: neutralizeConstructor(capture),
|
|
139
|
+
warn: neutralizeConstructor(capture),
|
|
140
|
+
error: neutralizeConstructor(capture),
|
|
141
|
+
debug: neutralizeConstructor(capture)
|
|
142
|
+
}),
|
|
143
|
+
setTimeout: neutralizeConstructor(sandboxSetTimeout),
|
|
144
|
+
clearTimeout: neutralizeConstructor(sandboxClearTimeout),
|
|
145
|
+
setInterval: neutralizeConstructor(sandboxSetInterval),
|
|
146
|
+
clearInterval: neutralizeConstructor(sandboxClearInterval),
|
|
147
|
+
URL: neutralizeConstructor(URL),
|
|
148
|
+
URLSearchParams: neutralizeConstructor(URLSearchParams),
|
|
149
|
+
TextEncoder: neutralizeConstructor(TextEncoder),
|
|
150
|
+
TextDecoder: neutralizeConstructor(TextDecoder),
|
|
151
|
+
atob: neutralizeConstructor(atob),
|
|
152
|
+
btoa: neutralizeConstructor(btoa),
|
|
153
|
+
structuredClone: neutralizeConstructor(structuredClone)
|
|
154
|
+
}, { codeGeneration: {
|
|
155
|
+
strings: false,
|
|
156
|
+
wasm: false
|
|
157
|
+
} });
|
|
71
158
|
try {
|
|
72
159
|
const wrapped = `(async () => {\n${code}\n})()`;
|
|
73
|
-
|
|
74
|
-
await Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("Code execution timed out")), RUN_CODE_TIMEOUT_MS))]);
|
|
160
|
+
await new vm.Script(wrapped, { filename: "run_code.js" }).runInContext(context, { timeout: RUN_CODE_TIMEOUT_MS });
|
|
75
161
|
return output.join("\n").trim() || "Code ran successfully (no output)";
|
|
76
162
|
} catch (err) {
|
|
77
163
|
return { error: errorMessage(err) };
|
|
164
|
+
} finally {
|
|
165
|
+
for (const id of activeTimers) {
|
|
166
|
+
clearTimeout(id);
|
|
167
|
+
clearInterval(id);
|
|
168
|
+
}
|
|
169
|
+
activeTimers.clear();
|
|
78
170
|
}
|
|
79
171
|
}
|
|
80
172
|
//#endregion
|
|
@@ -87,10 +179,7 @@ async function executeInIsolate(code) {
|
|
|
87
179
|
* Network requests go through the host's fetch proxy (with SSRF protection).
|
|
88
180
|
*/
|
|
89
181
|
const fetchSignal = () => AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
90
|
-
|
|
91
|
-
function htmlToText(html) {
|
|
92
|
-
return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/ /g, " ").replace(/\s{2,}/g, " ").trim();
|
|
93
|
-
}
|
|
182
|
+
const htmlToText = (html) => convert(html, { wordwrap: false });
|
|
94
183
|
const webSearchParams = z.object({
|
|
95
184
|
query: z.string().describe("The search query"),
|
|
96
185
|
max_results: z.number().describe("Maximum number of results to return (default 5)").optional()
|
|
@@ -103,6 +192,7 @@ const BraveSearchResponseSchema = z.object({ web: z.object({ results: z.array(z.
|
|
|
103
192
|
})) }).optional() });
|
|
104
193
|
function createWebSearch(fetchFn = globalThis.fetch) {
|
|
105
194
|
return {
|
|
195
|
+
guidance: "Use web_search for factual questions, current events, or anything you are unsure about. Search first rather than guessing.",
|
|
106
196
|
description: "Search the web for current information, facts, news, or answers to questions. Returns a list of results with title, URL, and description. Use this when the user asks about something you don't know, need up-to-date information, or want to verify facts.",
|
|
107
197
|
parameters: webSearchParams,
|
|
108
198
|
async execute(args, ctx) {
|
|
@@ -132,6 +222,7 @@ function createWebSearch(fetchFn = globalThis.fetch) {
|
|
|
132
222
|
const visitWebpageParams = z.object({ url: z.string().describe("The full URL to fetch (e.g., 'https://example.com/page')") });
|
|
133
223
|
function createVisitWebpage(fetchFn = globalThis.fetch) {
|
|
134
224
|
return {
|
|
225
|
+
guidance: "Use visit_webpage to read the full content of a URL when search snippets are not detailed enough.",
|
|
135
226
|
description: "Fetch a webpage and return its content as clean text. Use this to read the full content of a URL found via web_search, or any link the user shares. Good for reading articles, documentation, blog posts, or product pages.",
|
|
136
227
|
parameters: visitWebpageParams,
|
|
137
228
|
async execute(args, _ctx) {
|
|
@@ -187,6 +278,7 @@ function sanitizeHeaders(raw) {
|
|
|
187
278
|
}
|
|
188
279
|
function createFetchJson(fetchFn = globalThis.fetch) {
|
|
189
280
|
return {
|
|
281
|
+
guidance: "Use fetch_json to call REST APIs and retrieve structured JSON data.",
|
|
190
282
|
description: "Call a REST API endpoint via HTTP GET and return the JSON response. Use this to fetch structured data from APIs — for example, weather data, stock prices, exchange rates, or any public JSON API. Supports custom headers for authenticated APIs.",
|
|
191
283
|
parameters: fetchJsonParams,
|
|
192
284
|
async execute(args, _ctx) {
|
|
@@ -222,36 +314,46 @@ function resolveBuiltin(name, opts) {
|
|
|
222
314
|
}
|
|
223
315
|
}
|
|
224
316
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
317
|
+
* Resolve all builtin tools in one pass, returning defs, schemas, and guidance.
|
|
318
|
+
* Avoids redundant calls to `resolveBuiltin` and `z.toJSONSchema`.
|
|
227
319
|
*/
|
|
228
|
-
function
|
|
320
|
+
function resolveAllBuiltins(names, opts) {
|
|
229
321
|
const defs = {};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
322
|
+
const schemas = [];
|
|
323
|
+
const guidance = [];
|
|
324
|
+
for (const name of names) for (const [toolName, def] of resolveBuiltin(name, opts)) {
|
|
325
|
+
defs[toolName] = def;
|
|
326
|
+
schemas.push({
|
|
327
|
+
name: toolName,
|
|
328
|
+
description: def.description,
|
|
329
|
+
parameters: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
|
|
330
|
+
});
|
|
331
|
+
const g = def.guidance;
|
|
332
|
+
if (g) guidance.push(g);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
defs,
|
|
336
|
+
schemas,
|
|
337
|
+
guidance
|
|
338
|
+
};
|
|
240
339
|
}
|
|
241
340
|
//#endregion
|
|
242
|
-
//#region host/runtime.ts
|
|
341
|
+
//#region host/runtime-config.ts
|
|
243
342
|
/**
|
|
244
343
|
* Runtime dependencies injected into the session pipeline.
|
|
245
344
|
*
|
|
246
345
|
* Defines the {@link Logger} interface, a default {@link consoleLogger},
|
|
247
346
|
* and the {@link S2SConfig} for Speech-to-Speech endpoint configuration.
|
|
248
347
|
*/
|
|
348
|
+
function consoleLog(fn) {
|
|
349
|
+
return (msg, ctx) => ctx ? fn(msg, ctx) : fn(msg);
|
|
350
|
+
}
|
|
249
351
|
/** Default console-backed logger. */
|
|
250
352
|
const consoleLogger = {
|
|
251
|
-
info: (
|
|
252
|
-
warn: (
|
|
253
|
-
error: (
|
|
254
|
-
debug: (
|
|
353
|
+
info: consoleLog(console.log),
|
|
354
|
+
warn: consoleLog(console.warn),
|
|
355
|
+
error: consoleLog(console.error),
|
|
356
|
+
debug: consoleLog(console.debug)
|
|
255
357
|
};
|
|
256
358
|
/**
|
|
257
359
|
* Structured JSON logger for production diagnostics. Each log entry is a
|
|
@@ -277,7 +379,7 @@ const jsonLogger = {
|
|
|
277
379
|
};
|
|
278
380
|
/** Default S2S endpoint configuration. */
|
|
279
381
|
const DEFAULT_S2S_CONFIG = {
|
|
280
|
-
wssUrl: "wss://
|
|
382
|
+
wssUrl: "wss://agents.assemblyai.com/v1/voice",
|
|
281
383
|
inputSampleRate: DEFAULT_STT_SAMPLE_RATE,
|
|
282
384
|
outputSampleRate: DEFAULT_TTS_SAMPLE_RATE
|
|
283
385
|
};
|
|
@@ -285,7 +387,6 @@ const DEFAULT_S2S_CONFIG = {
|
|
|
285
387
|
//#region host/s2s.ts
|
|
286
388
|
const uint8ToBase64 = (bytes) => Buffer.from(bytes).toString("base64");
|
|
287
389
|
const base64ToUint8 = (base64) => new Uint8Array(Buffer.from(base64, "base64"));
|
|
288
|
-
const WS_OPEN = 1;
|
|
289
390
|
const defaultCreateS2sWebSocket = (url, opts) => new WsWebSocket(url, { headers: opts.headers });
|
|
290
391
|
const S2sMessageSchema = z.discriminatedUnion("type", [
|
|
291
392
|
z.object({
|
|
@@ -295,10 +396,6 @@ const S2sMessageSchema = z.discriminatedUnion("type", [
|
|
|
295
396
|
z.object({ type: z.literal("session.updated") }).passthrough(),
|
|
296
397
|
z.object({ type: z.literal("input.speech.started") }),
|
|
297
398
|
z.object({ type: z.literal("input.speech.stopped") }),
|
|
298
|
-
z.object({
|
|
299
|
-
type: z.literal("transcript.user.delta"),
|
|
300
|
-
text: z.string()
|
|
301
|
-
}),
|
|
302
399
|
z.object({
|
|
303
400
|
type: z.literal("transcript.user"),
|
|
304
401
|
item_id: z.string(),
|
|
@@ -308,10 +405,6 @@ const S2sMessageSchema = z.discriminatedUnion("type", [
|
|
|
308
405
|
type: z.literal("reply.started"),
|
|
309
406
|
reply_id: z.string()
|
|
310
407
|
}),
|
|
311
|
-
z.object({
|
|
312
|
-
type: z.literal("transcript.agent.delta"),
|
|
313
|
-
delta: z.string()
|
|
314
|
-
}).passthrough(),
|
|
315
408
|
z.object({
|
|
316
409
|
type: z.literal("transcript.agent"),
|
|
317
410
|
text: z.string(),
|
|
@@ -319,8 +412,6 @@ const S2sMessageSchema = z.discriminatedUnion("type", [
|
|
|
319
412
|
item_id: z.string().optional().default(""),
|
|
320
413
|
interrupted: z.boolean().optional().default(false)
|
|
321
414
|
}),
|
|
322
|
-
z.object({ type: z.literal("reply.content_part.started") }).passthrough(),
|
|
323
|
-
z.object({ type: z.literal("reply.content_part.done") }).passthrough(),
|
|
324
415
|
z.object({
|
|
325
416
|
type: z.literal("tool.call"),
|
|
326
417
|
call_id: z.string(),
|
|
@@ -350,66 +441,48 @@ function dispatchS2sMessage(emitter, msg) {
|
|
|
350
441
|
case "session.ready":
|
|
351
442
|
emitter.emit("ready", { sessionId: msg.session_id });
|
|
352
443
|
break;
|
|
353
|
-
case "session.updated":
|
|
354
|
-
emitter.emit("sessionUpdated", msg);
|
|
355
|
-
break;
|
|
444
|
+
case "session.updated": break;
|
|
356
445
|
case "input.speech.started":
|
|
357
|
-
emitter.emit("
|
|
446
|
+
emitter.emit("event", { type: "speech_started" });
|
|
358
447
|
break;
|
|
359
448
|
case "input.speech.stopped":
|
|
360
|
-
emitter.emit("
|
|
361
|
-
break;
|
|
362
|
-
case "transcript.user.delta":
|
|
363
|
-
emitter.emit("userTranscriptDelta", { text: msg.text });
|
|
449
|
+
emitter.emit("event", { type: "speech_stopped" });
|
|
364
450
|
break;
|
|
365
451
|
case "transcript.user":
|
|
366
|
-
emitter.emit("
|
|
367
|
-
|
|
452
|
+
emitter.emit("event", {
|
|
453
|
+
type: "user_transcript",
|
|
368
454
|
text: msg.text
|
|
369
455
|
});
|
|
370
456
|
break;
|
|
371
457
|
case "reply.started":
|
|
372
458
|
emitter.emit("replyStarted", { replyId: msg.reply_id });
|
|
373
459
|
break;
|
|
374
|
-
case "transcript.agent.delta":
|
|
375
|
-
emitter.emit("agentTranscriptDelta", { text: msg.delta });
|
|
376
|
-
break;
|
|
377
460
|
case "transcript.agent":
|
|
378
|
-
emitter.emit("
|
|
461
|
+
emitter.emit("event", {
|
|
462
|
+
type: "agent_transcript",
|
|
379
463
|
text: msg.text,
|
|
380
|
-
|
|
381
|
-
itemId: msg.item_id,
|
|
382
|
-
interrupted: msg.interrupted
|
|
464
|
+
_interrupted: msg.interrupted
|
|
383
465
|
});
|
|
384
466
|
break;
|
|
385
467
|
case "tool.call":
|
|
386
|
-
emitter.emit("
|
|
387
|
-
|
|
388
|
-
|
|
468
|
+
emitter.emit("event", {
|
|
469
|
+
type: "tool_call",
|
|
470
|
+
toolCallId: msg.call_id,
|
|
471
|
+
toolName: msg.name,
|
|
389
472
|
args: msg.args
|
|
390
473
|
});
|
|
391
474
|
break;
|
|
392
475
|
case "reply.done":
|
|
393
|
-
emitter.emit("
|
|
476
|
+
if (msg.status === "interrupted") emitter.emit("event", { type: "cancelled" });
|
|
477
|
+
else emitter.emit("event", { type: "reply_done" });
|
|
394
478
|
break;
|
|
395
479
|
case "session.error":
|
|
396
|
-
if (msg.code === "session_not_found" || msg.code === "session_forbidden") emitter.emit("sessionExpired"
|
|
397
|
-
|
|
398
|
-
message: msg.message
|
|
399
|
-
});
|
|
400
|
-
else emitter.emit("error", {
|
|
401
|
-
code: msg.code,
|
|
402
|
-
message: msg.message
|
|
403
|
-
});
|
|
480
|
+
if (msg.code === "session_not_found" || msg.code === "session_forbidden") emitter.emit("sessionExpired");
|
|
481
|
+
else emitter.emit("error", new Error(msg.message));
|
|
404
482
|
break;
|
|
405
483
|
case "error":
|
|
406
|
-
emitter.emit("error",
|
|
407
|
-
code: "connection",
|
|
408
|
-
message: msg.message
|
|
409
|
-
});
|
|
484
|
+
emitter.emit("error", new Error(msg.message));
|
|
410
485
|
break;
|
|
411
|
-
case "reply.content_part.started":
|
|
412
|
-
case "reply.content_part.done": break;
|
|
413
486
|
default: break;
|
|
414
487
|
}
|
|
415
488
|
}
|
|
@@ -421,18 +494,19 @@ function connectS2s(opts) {
|
|
|
421
494
|
const emitter = createNanoEvents();
|
|
422
495
|
let opened = false;
|
|
423
496
|
function send(msg) {
|
|
424
|
-
if (ws.readyState !==
|
|
497
|
+
if (ws.readyState !== 1) {
|
|
425
498
|
log.debug("S2S send dropped: socket not open", { type: msg.type });
|
|
426
499
|
return;
|
|
427
500
|
}
|
|
428
501
|
const json = JSON.stringify(msg);
|
|
429
|
-
if (msg.type !== "input.audio") log.info(`S2S >> ${msg.type}`,
|
|
502
|
+
if (msg.type !== "input.audio") if (msg.type === "session.update") log.info(`S2S >> ${msg.type}`, { payload: json });
|
|
503
|
+
else log.info(`S2S >> ${msg.type}`);
|
|
430
504
|
ws.send(json);
|
|
431
505
|
}
|
|
432
506
|
const handle = {
|
|
433
507
|
on: emitter.on.bind(emitter),
|
|
434
508
|
sendAudio(audio) {
|
|
435
|
-
if (ws.readyState !==
|
|
509
|
+
if (ws.readyState !== 1) {
|
|
436
510
|
log.debug("S2S sendAudio dropped: socket not open");
|
|
437
511
|
return;
|
|
438
512
|
}
|
|
@@ -493,7 +567,7 @@ function connectS2s(opts) {
|
|
|
493
567
|
}
|
|
494
568
|
function logIncoming(obj) {
|
|
495
569
|
if (obj.type === "reply.audio" || obj.type === "input.audio") return;
|
|
496
|
-
log.info(`S2S << ${obj.type}
|
|
570
|
+
log.info(`S2S << ${obj.type}`);
|
|
497
571
|
}
|
|
498
572
|
function handleS2sMessage(ev) {
|
|
499
573
|
const raw = tryParseJson(ev.data);
|
|
@@ -514,32 +588,29 @@ function connectS2s(opts) {
|
|
|
514
588
|
}
|
|
515
589
|
ws.addEventListener("message", handleS2sMessage);
|
|
516
590
|
ws.addEventListener("close", (ev) => {
|
|
591
|
+
const code = ev.code ?? 0;
|
|
592
|
+
const reason = ev.reason ?? "";
|
|
517
593
|
log.info("S2S WebSocket closed", {
|
|
518
|
-
code
|
|
519
|
-
reason
|
|
594
|
+
code,
|
|
595
|
+
reason
|
|
520
596
|
});
|
|
521
|
-
if (!opened) reject(/* @__PURE__ */ new Error(`WebSocket closed before open (code: ${
|
|
522
|
-
emitter.emit("close");
|
|
597
|
+
if (!opened) reject(/* @__PURE__ */ new Error(`WebSocket closed before open (code: ${code})`));
|
|
598
|
+
emitter.emit("close", code, reason);
|
|
523
599
|
});
|
|
524
600
|
ws.addEventListener("error", (ev) => {
|
|
525
601
|
const message = typeof ev.message === "string" ? ev.message : "WebSocket error";
|
|
526
602
|
const errObj = new Error(message);
|
|
527
603
|
log.error("S2S WebSocket error", { error: errObj.message });
|
|
528
604
|
if (!opened) reject(errObj);
|
|
529
|
-
else emitter.emit("error",
|
|
530
|
-
code: "ws_error",
|
|
531
|
-
message: errObj.message
|
|
532
|
-
});
|
|
605
|
+
else emitter.emit("error", errObj);
|
|
533
606
|
});
|
|
534
607
|
});
|
|
535
608
|
}
|
|
536
609
|
//#endregion
|
|
537
|
-
//#region host/session.ts
|
|
610
|
+
//#region host/session-ctx.ts
|
|
538
611
|
function buildCtx(opts) {
|
|
539
|
-
const {
|
|
612
|
+
const { agentConfig, log } = opts;
|
|
540
613
|
const maxHistory = opts.maxHistory ?? 200;
|
|
541
|
-
/** Track in-flight hook promises so they can be awaited during shutdown. */
|
|
542
|
-
const pendingHooks = /* @__PURE__ */ new Set();
|
|
543
614
|
const ctx = {
|
|
544
615
|
...opts,
|
|
545
616
|
s2s: null,
|
|
@@ -551,12 +622,9 @@ function buildCtx(opts) {
|
|
|
551
622
|
turnPromise: null,
|
|
552
623
|
conversationMessages: [],
|
|
553
624
|
maxHistory,
|
|
554
|
-
|
|
555
|
-
return callResolveTurnConfig(hooks, id, HOOK_TIMEOUT_MS);
|
|
556
|
-
},
|
|
557
|
-
consumeToolCallStep(turnConfig, _name, replyId) {
|
|
625
|
+
consumeToolCallStep(_name, replyId) {
|
|
558
626
|
if (replyId === null || replyId !== ctx.reply.currentReplyId) return toolError("Reply was interrupted. Discarding stale tool call.");
|
|
559
|
-
const maxSteps =
|
|
627
|
+
const maxSteps = agentConfig.maxSteps;
|
|
560
628
|
ctx.reply.toolCallCount++;
|
|
561
629
|
if (maxSteps !== void 0 && ctx.reply.toolCallCount > maxSteps) {
|
|
562
630
|
log.info("maxSteps exceeded, refusing tool call", {
|
|
@@ -567,32 +635,9 @@ function buildCtx(opts) {
|
|
|
567
635
|
}
|
|
568
636
|
return null;
|
|
569
637
|
},
|
|
570
|
-
fireHook(name, ...args) {
|
|
571
|
-
if (!hooks) return;
|
|
572
|
-
const notifyOnError = (err) => {
|
|
573
|
-
log.warn(`${name} hook failed`, { err: errorMessage(err) });
|
|
574
|
-
if (name !== "error") {
|
|
575
|
-
const ep = hooks.callHook("error", id, { message: errorMessage(err) });
|
|
576
|
-
if (ep && typeof ep.catch === "function") ep.catch((e) => {
|
|
577
|
-
log.warn("error hook failed", { err: errorMessage(e) });
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
try {
|
|
582
|
-
const result = hooks.callHook(name, ...args);
|
|
583
|
-
if (result == null) return;
|
|
584
|
-
const p = result.catch(notifyOnError).finally(() => pendingHooks.delete(p));
|
|
585
|
-
pendingHooks.add(p);
|
|
586
|
-
} catch (err) {
|
|
587
|
-
notifyOnError(err);
|
|
588
|
-
}
|
|
589
|
-
},
|
|
590
|
-
async drainHooks() {
|
|
591
|
-
if (pendingHooks.size > 0) await Promise.all([...pendingHooks]);
|
|
592
|
-
},
|
|
593
638
|
pushMessages(...msgs) {
|
|
594
639
|
ctx.conversationMessages.push(...msgs);
|
|
595
|
-
if (maxHistory > 0 && ctx.conversationMessages.length > maxHistory) ctx.conversationMessages
|
|
640
|
+
if (maxHistory > 0 && ctx.conversationMessages.length > maxHistory) ctx.conversationMessages.splice(0, ctx.conversationMessages.length - maxHistory);
|
|
596
641
|
},
|
|
597
642
|
beginReply(replyId) {
|
|
598
643
|
ctx.reply = {
|
|
@@ -615,8 +660,15 @@ function buildCtx(opts) {
|
|
|
615
660
|
};
|
|
616
661
|
return ctx;
|
|
617
662
|
}
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region host/session.ts
|
|
618
665
|
/** @internal Not part of the public API. Exposed for testing only. */
|
|
619
666
|
const _internals = { connectS2s };
|
|
667
|
+
/**
|
|
668
|
+
* Create an idle timer that closes the S2S connection after inactivity.
|
|
669
|
+
* Convention: `timeoutMs <= 0` disables the timer entirely (returns a no-op).
|
|
670
|
+
* This allows agents to opt out of idle timeout via `idleTimeoutMs: 0` in their config.
|
|
671
|
+
*/
|
|
620
672
|
function createIdleTimer(opts) {
|
|
621
673
|
if (opts.timeoutMs <= 0) return {
|
|
622
674
|
reset() {},
|
|
@@ -663,25 +715,11 @@ function finishToolCall(ctx, callId, result, replyId) {
|
|
|
663
715
|
if (ctx.maxHistory > 0 && ctx.reply.pendingTools.length > ctx.maxHistory) ctx.reply.pendingTools.shift();
|
|
664
716
|
}
|
|
665
717
|
}
|
|
666
|
-
async function handleToolCall(ctx,
|
|
667
|
-
const { callId, name, args: parsedArgs } =
|
|
718
|
+
async function handleToolCall(ctx, event) {
|
|
719
|
+
const { toolCallId: callId, toolName: name, args: parsedArgs } = event;
|
|
668
720
|
const replyId = ctx.reply.currentReplyId;
|
|
669
|
-
ctx.client.event(
|
|
670
|
-
|
|
671
|
-
toolCallId: callId,
|
|
672
|
-
toolName: name,
|
|
673
|
-
args: parsedArgs
|
|
674
|
-
});
|
|
675
|
-
let turnConfig;
|
|
676
|
-
try {
|
|
677
|
-
turnConfig = await ctx.resolveTurnConfig();
|
|
678
|
-
} catch (err) {
|
|
679
|
-
const msg = `resolveTurnConfig hook error: ${errorMessage(err)}`;
|
|
680
|
-
ctx.log.error(msg);
|
|
681
|
-
finishToolCall(ctx, callId, toolError(msg), replyId);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
const refused = ctx.consumeToolCallStep(turnConfig, name, replyId);
|
|
721
|
+
ctx.client.event(event);
|
|
722
|
+
const refused = ctx.consumeToolCallStep(name, replyId);
|
|
685
723
|
if (refused !== null) {
|
|
686
724
|
finishToolCall(ctx, callId, refused, replyId);
|
|
687
725
|
return;
|
|
@@ -713,23 +751,17 @@ async function handleToolCall(ctx, detail) {
|
|
|
713
751
|
function handleUserTranscript(ctx, text) {
|
|
714
752
|
ctx.log.info("S2S user transcript", { text });
|
|
715
753
|
ctx.client.event({
|
|
716
|
-
type: "
|
|
717
|
-
text,
|
|
718
|
-
isFinal: true
|
|
719
|
-
});
|
|
720
|
-
ctx.client.event({
|
|
721
|
-
type: "turn",
|
|
754
|
+
type: "user_transcript",
|
|
722
755
|
text
|
|
723
756
|
});
|
|
724
757
|
ctx.pushMessages({
|
|
725
758
|
role: "user",
|
|
726
759
|
content: text
|
|
727
760
|
});
|
|
728
|
-
ctx.fireHook("turn", ctx.id, text, HOOK_TIMEOUT_MS);
|
|
729
761
|
}
|
|
730
762
|
function handleAgentTranscript(ctx, text, interrupted) {
|
|
731
763
|
ctx.client.event({
|
|
732
|
-
type: "
|
|
764
|
+
type: "agent_transcript",
|
|
733
765
|
text
|
|
734
766
|
});
|
|
735
767
|
if (!interrupted) ctx.pushMessages({
|
|
@@ -737,13 +769,12 @@ function handleAgentTranscript(ctx, text, interrupted) {
|
|
|
737
769
|
content: text
|
|
738
770
|
});
|
|
739
771
|
}
|
|
740
|
-
function
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
}
|
|
772
|
+
function handleReplyCancelled(ctx) {
|
|
773
|
+
ctx.log.info("S2S reply interrupted (barge-in)");
|
|
774
|
+
ctx.cancelReply();
|
|
775
|
+
ctx.client.event({ type: "cancelled" });
|
|
776
|
+
}
|
|
777
|
+
function handleReplyDone(ctx) {
|
|
747
778
|
const doneReplyId = ctx.reply.currentReplyId;
|
|
748
779
|
const sendPending = () => {
|
|
749
780
|
if (ctx.reply.currentReplyId !== doneReplyId) {
|
|
@@ -760,7 +791,7 @@ function handleReplyDone(ctx, status) {
|
|
|
760
791
|
agent: ctx.agent
|
|
761
792
|
});
|
|
762
793
|
ctx.client.playAudioDone();
|
|
763
|
-
ctx.client.event({ type: "
|
|
794
|
+
ctx.client.event({ type: "reply_done" });
|
|
764
795
|
}
|
|
765
796
|
};
|
|
766
797
|
if (ctx.turnPromise !== null) ctx.turnPromise.then(sendPending);
|
|
@@ -768,61 +799,66 @@ function handleReplyDone(ctx, status) {
|
|
|
768
799
|
}
|
|
769
800
|
function setupListeners(ctx, handle) {
|
|
770
801
|
handle.on("ready", ({ sessionId }) => ctx.log.info("S2S session ready", { sessionId }));
|
|
802
|
+
handle.on("replyStarted", ({ replyId }) => {
|
|
803
|
+
ctx.beginReply(replyId);
|
|
804
|
+
});
|
|
771
805
|
handle.on("sessionExpired", () => {
|
|
772
806
|
ctx.log.info("S2S session expired");
|
|
773
807
|
handle.close();
|
|
774
808
|
});
|
|
775
|
-
handle.on("speechStarted", () => ctx.client.event({ type: "speech_started" }));
|
|
776
|
-
handle.on("speechStopped", () => ctx.client.event({ type: "speech_stopped" }));
|
|
777
|
-
handle.on("userTranscriptDelta", ({ text }) => ctx.client.event({
|
|
778
|
-
type: "transcript",
|
|
779
|
-
text,
|
|
780
|
-
isFinal: false
|
|
781
|
-
}));
|
|
782
|
-
handle.on("userTranscript", ({ text }) => handleUserTranscript(ctx, text));
|
|
783
|
-
handle.on("replyStarted", ({ replyId }) => {
|
|
784
|
-
ctx.beginReply(replyId);
|
|
785
|
-
});
|
|
786
809
|
handle.on("audio", ({ audio }) => ctx.client.playAudioChunk(audio));
|
|
787
|
-
handle.on("
|
|
788
|
-
|
|
789
|
-
text
|
|
790
|
-
}));
|
|
791
|
-
handle.on("agentTranscript", ({ text, interrupted }) => handleAgentTranscript(ctx, text, interrupted));
|
|
792
|
-
handle.on("toolCall", (detail) => {
|
|
793
|
-
const p = handleToolCall(ctx, detail).catch((err) => {
|
|
794
|
-
ctx.log.error("Tool call handler failed", { err: errorMessage(err) });
|
|
795
|
-
});
|
|
796
|
-
ctx.chainTurn(p);
|
|
797
|
-
});
|
|
798
|
-
handle.on("replyDone", ({ status }) => handleReplyDone(ctx, status));
|
|
799
|
-
handle.on("error", ({ code, message }) => {
|
|
800
|
-
ctx.log.error("S2S error", {
|
|
801
|
-
code,
|
|
802
|
-
message
|
|
803
|
-
});
|
|
810
|
+
handle.on("error", (err) => {
|
|
811
|
+
ctx.log.error("S2S error", { message: err.message });
|
|
804
812
|
ctx.client.event({
|
|
805
813
|
type: "error",
|
|
806
814
|
code: "internal",
|
|
807
|
-
message
|
|
815
|
+
message: err.message
|
|
808
816
|
});
|
|
809
817
|
handle.close();
|
|
810
818
|
});
|
|
811
|
-
handle.on("close", () => {
|
|
812
|
-
ctx.log.info("S2S closed"
|
|
819
|
+
handle.on("close", (code, reason) => {
|
|
820
|
+
ctx.log.info("S2S closed", {
|
|
821
|
+
code,
|
|
822
|
+
reason
|
|
823
|
+
});
|
|
813
824
|
ctx.s2s = null;
|
|
814
825
|
ctx.cancelReply();
|
|
815
826
|
});
|
|
827
|
+
handle.on("event", (event) => {
|
|
828
|
+
switch (event.type) {
|
|
829
|
+
case "user_transcript":
|
|
830
|
+
handleUserTranscript(ctx, event.text);
|
|
831
|
+
break;
|
|
832
|
+
case "agent_transcript":
|
|
833
|
+
handleAgentTranscript(ctx, event.text, event._interrupted ?? false);
|
|
834
|
+
break;
|
|
835
|
+
case "tool_call": {
|
|
836
|
+
const p = handleToolCall(ctx, event).catch((err) => {
|
|
837
|
+
ctx.log.error("Tool call handler failed", { err: errorMessage(err) });
|
|
838
|
+
});
|
|
839
|
+
ctx.chainTurn(p);
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
case "reply_done":
|
|
843
|
+
handleReplyDone(ctx);
|
|
844
|
+
break;
|
|
845
|
+
case "cancelled":
|
|
846
|
+
handleReplyCancelled(ctx);
|
|
847
|
+
break;
|
|
848
|
+
default: ctx.client.event(event);
|
|
849
|
+
}
|
|
850
|
+
});
|
|
816
851
|
}
|
|
817
852
|
function createS2sSession(opts) {
|
|
818
|
-
const { id, agent, client, toolSchemas, apiKey, s2sConfig, executeTool, createWebSocket = defaultCreateS2sWebSocket,
|
|
853
|
+
const { id, agent, client, toolSchemas, apiKey, s2sConfig, executeTool, createWebSocket = defaultCreateS2sWebSocket, logger: log = consoleLogger } = opts;
|
|
819
854
|
const agentConfig = opts.skipGreeting ? {
|
|
820
855
|
...opts.agentConfig,
|
|
821
856
|
greeting: ""
|
|
822
857
|
} : opts.agentConfig;
|
|
823
858
|
const systemPrompt = buildSystemPrompt(agentConfig, {
|
|
824
859
|
hasTools: toolSchemas.length > 0 || (agentConfig.builtinTools?.length ?? 0) > 0,
|
|
825
|
-
voice: true
|
|
860
|
+
voice: true,
|
|
861
|
+
toolGuidance: opts.toolGuidance
|
|
826
862
|
});
|
|
827
863
|
const s2sTools = toolSchemas.map((ts) => ({
|
|
828
864
|
type: "function",
|
|
@@ -837,7 +873,6 @@ function createS2sSession(opts) {
|
|
|
837
873
|
client,
|
|
838
874
|
agentConfig,
|
|
839
875
|
executeTool,
|
|
840
|
-
hooks,
|
|
841
876
|
log,
|
|
842
877
|
maxHistory: opts.maxHistory
|
|
843
878
|
});
|
|
@@ -884,7 +919,6 @@ function createS2sSession(opts) {
|
|
|
884
919
|
}
|
|
885
920
|
return {
|
|
886
921
|
async start() {
|
|
887
|
-
ctx.fireHook("connect", id, HOOK_TIMEOUT_MS);
|
|
888
922
|
await connectAndSetup();
|
|
889
923
|
},
|
|
890
924
|
async stop() {
|
|
@@ -892,10 +926,7 @@ function createS2sSession(opts) {
|
|
|
892
926
|
sessionAbort.abort();
|
|
893
927
|
idle.clear();
|
|
894
928
|
if (ctx.turnPromise !== null) await ctx.turnPromise;
|
|
895
|
-
await ctx.drainHooks();
|
|
896
929
|
ctx.s2s?.close();
|
|
897
|
-
ctx.fireHook("disconnect", id, HOOK_TIMEOUT_MS);
|
|
898
|
-
await ctx.drainHooks();
|
|
899
930
|
},
|
|
900
931
|
onAudio(data) {
|
|
901
932
|
idle.reset();
|
|
@@ -927,33 +958,50 @@ function createS2sSession(opts) {
|
|
|
927
958
|
};
|
|
928
959
|
}
|
|
929
960
|
//#endregion
|
|
930
|
-
//#region
|
|
931
|
-
/**
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
961
|
+
//#region host/tool-executor.ts
|
|
962
|
+
/**
|
|
963
|
+
* Tool execution — validates arguments and invokes tool handlers.
|
|
964
|
+
*
|
|
965
|
+
* {@link executeToolCall} is the single entry point used by both the
|
|
966
|
+
* direct (self-hosted) runtime and the platform sandbox sidecar.
|
|
967
|
+
*/
|
|
968
|
+
const yieldTick = () => new Promise((r) => setTimeout(r, 0));
|
|
969
|
+
function buildToolContext(opts) {
|
|
970
|
+
const { env, state, kv, messages, sessionId } = opts;
|
|
971
|
+
return {
|
|
972
|
+
env,
|
|
973
|
+
state: state ?? {},
|
|
974
|
+
get kv() {
|
|
975
|
+
if (!kv) throw new Error("KV not available");
|
|
976
|
+
return kv;
|
|
977
|
+
},
|
|
978
|
+
messages: messages ?? [],
|
|
979
|
+
sessionId: sessionId ?? ""
|
|
980
|
+
};
|
|
938
981
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
982
|
+
async function executeToolCall(name, args, options) {
|
|
983
|
+
const { tool } = options;
|
|
984
|
+
const parsed = (tool.parameters ?? EMPTY_PARAMS).safeParse(args);
|
|
985
|
+
if (!parsed.success) return toolError(`Invalid arguments for tool "${name}": ${(parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".")}: ${i.message}`).join(", ")}`);
|
|
986
|
+
try {
|
|
987
|
+
const ctx = buildToolContext(options);
|
|
988
|
+
await yieldTick();
|
|
989
|
+
const result = await pTimeout(Promise.resolve(tool.execute(parsed.data, ctx)), {
|
|
990
|
+
milliseconds: TOOL_EXECUTION_TIMEOUT_MS,
|
|
991
|
+
message: `Tool "${name}" timed out after ${TOOL_EXECUTION_TIMEOUT_MS}ms`
|
|
992
|
+
});
|
|
993
|
+
await yieldTick();
|
|
994
|
+
if (result == null) return "null";
|
|
995
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
996
|
+
} catch (err) {
|
|
997
|
+
const log = options.logger;
|
|
998
|
+
if (log) log.warn("Tool execution failed", {
|
|
999
|
+
tool: name,
|
|
1000
|
+
error: errorDetail(err)
|
|
1001
|
+
});
|
|
1002
|
+
else console.warn(`[tool-executor] Tool execution failed: ${name}`, err);
|
|
1003
|
+
return toolError(errorMessage(err));
|
|
955
1004
|
}
|
|
956
|
-
return pos <= end;
|
|
957
1005
|
}
|
|
958
1006
|
//#endregion
|
|
959
1007
|
//#region host/unstorage-kv.ts
|
|
@@ -994,23 +1042,6 @@ function createUnstorageKv(options) {
|
|
|
994
1042
|
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
995
1043
|
await Promise.all(keyArray.map((k) => store.removeItem(k)));
|
|
996
1044
|
},
|
|
997
|
-
async list(listPrefix, listOptions) {
|
|
998
|
-
const allKeys = await store.getKeys(listPrefix);
|
|
999
|
-
const entries = [];
|
|
1000
|
-
for (const key of allKeys) {
|
|
1001
|
-
const value = await store.getItem(key);
|
|
1002
|
-
if (value != null) entries.push({
|
|
1003
|
-
key,
|
|
1004
|
-
value
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
return sortAndPaginate(entries, listOptions);
|
|
1008
|
-
},
|
|
1009
|
-
async keys(pattern) {
|
|
1010
|
-
const allKeys = await store.getKeys();
|
|
1011
|
-
if (!pattern) return allKeys.sort((a, b) => a.localeCompare(b));
|
|
1012
|
-
return allKeys.filter((key) => matchGlob(key, pattern)).sort((a, b) => a.localeCompare(b));
|
|
1013
|
-
},
|
|
1014
1045
|
close() {
|
|
1015
1046
|
store.dispose();
|
|
1016
1047
|
}
|
|
@@ -1077,12 +1108,12 @@ function handleTextMessage(data, session, log, ctx, sid) {
|
|
|
1077
1108
|
});
|
|
1078
1109
|
return;
|
|
1079
1110
|
}
|
|
1080
|
-
const parsed = ClientMessageSchema
|
|
1081
|
-
if (!parsed.
|
|
1082
|
-
log.warn("Invalid client message", {
|
|
1111
|
+
const parsed = lenientParse(ClientMessageSchema, json);
|
|
1112
|
+
if (!parsed.ok) {
|
|
1113
|
+
if (parsed.malformed) log.warn("Invalid client message", {
|
|
1083
1114
|
...ctx,
|
|
1084
1115
|
sid,
|
|
1085
|
-
error: parsed.error
|
|
1116
|
+
error: parsed.error
|
|
1086
1117
|
});
|
|
1087
1118
|
return;
|
|
1088
1119
|
}
|
|
@@ -1174,7 +1205,7 @@ function wireSessionSocket(ws, opts) {
|
|
|
1174
1205
|
ws.addEventListener("message", (event) => {
|
|
1175
1206
|
if (!session) return;
|
|
1176
1207
|
if (!sessionReady) {
|
|
1177
|
-
messageBuffer
|
|
1208
|
+
if (messageBuffer && messageBuffer.length < 100) messageBuffer.push(event);
|
|
1178
1209
|
return;
|
|
1179
1210
|
}
|
|
1180
1211
|
const { data } = event;
|
|
@@ -1194,6 +1225,7 @@ function wireSessionSocket(ws, opts) {
|
|
|
1194
1225
|
});
|
|
1195
1226
|
}).finally(() => {
|
|
1196
1227
|
sessions.delete(sessionId);
|
|
1228
|
+
opts.onSessionEnd?.(sessionId);
|
|
1197
1229
|
});
|
|
1198
1230
|
opts.onClose?.();
|
|
1199
1231
|
});
|
|
@@ -1207,7 +1239,7 @@ function wireSessionSocket(ws, opts) {
|
|
|
1207
1239
|
});
|
|
1208
1240
|
}
|
|
1209
1241
|
//#endregion
|
|
1210
|
-
//#region host/
|
|
1242
|
+
//#region host/runtime.ts
|
|
1211
1243
|
/**
|
|
1212
1244
|
* Agent runtime — the execution engine for voice agents.
|
|
1213
1245
|
*
|
|
@@ -1215,45 +1247,6 @@ function wireSessionSocket(ws, opts) {
|
|
|
1215
1247
|
* self-hosted servers and the platform sandbox. It wires up tool execution,
|
|
1216
1248
|
* lifecycle hooks, and session management.
|
|
1217
1249
|
*/
|
|
1218
|
-
const yieldTick = () => new Promise((r) => setTimeout(r, 0));
|
|
1219
|
-
function buildToolContext(opts) {
|
|
1220
|
-
const { env, state, kv, messages, fetch: fetchFn, sessionId } = opts;
|
|
1221
|
-
return {
|
|
1222
|
-
env: { ...env },
|
|
1223
|
-
state: state ?? {},
|
|
1224
|
-
get kv() {
|
|
1225
|
-
if (!kv) throw new Error("KV not available");
|
|
1226
|
-
return kv;
|
|
1227
|
-
},
|
|
1228
|
-
messages: messages ?? [],
|
|
1229
|
-
fetch: fetchFn ?? globalThis.fetch,
|
|
1230
|
-
sessionId: sessionId ?? ""
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
async function executeToolCall(name, args, options) {
|
|
1234
|
-
const { tool } = options;
|
|
1235
|
-
const parsed = (tool.parameters ?? EMPTY_PARAMS).safeParse(args);
|
|
1236
|
-
if (!parsed.success) return toolError(`Invalid arguments for tool "${name}": ${(parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".")}: ${i.message}`).join(", ")}`);
|
|
1237
|
-
try {
|
|
1238
|
-
const ctx = buildToolContext(options);
|
|
1239
|
-
await yieldTick();
|
|
1240
|
-
const result = await pTimeout(Promise.resolve(tool.execute(parsed.data, ctx)), {
|
|
1241
|
-
milliseconds: TOOL_EXECUTION_TIMEOUT_MS,
|
|
1242
|
-
message: `Tool "${name}" timed out after ${TOOL_EXECUTION_TIMEOUT_MS}ms`
|
|
1243
|
-
});
|
|
1244
|
-
await yieldTick();
|
|
1245
|
-
if (result == null) return "null";
|
|
1246
|
-
return typeof result === "string" ? result : JSON.stringify(result);
|
|
1247
|
-
} catch (err) {
|
|
1248
|
-
const log = options.logger;
|
|
1249
|
-
if (log) log.warn("Tool execution failed", {
|
|
1250
|
-
tool: name,
|
|
1251
|
-
error: errorDetail(err)
|
|
1252
|
-
});
|
|
1253
|
-
else console.warn(`[tool-executor] Tool execution failed: ${name}`, err);
|
|
1254
|
-
return toolError(errorMessage(err));
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
1250
|
/** Create an in-memory KV store (default for self-hosted). */
|
|
1258
1251
|
function createLocalKv() {
|
|
1259
1252
|
return createUnstorageKv({ storage: createStorage() });
|
|
@@ -1276,37 +1269,43 @@ function createRuntime(opts) {
|
|
|
1276
1269
|
const sessions = /* @__PURE__ */ new Map();
|
|
1277
1270
|
const readyConfig = buildReadyConfig(s2sConfig);
|
|
1278
1271
|
let executeTool;
|
|
1279
|
-
let hooks;
|
|
1280
1272
|
let toolSchemas;
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1273
|
+
let toolGuidance = [];
|
|
1274
|
+
const builtinFetchOpt = opts.fetch ? { fetch: opts.fetch } : void 0;
|
|
1275
|
+
if (opts.executeTool && opts.toolSchemas) {
|
|
1276
|
+
const builtinDefs = opts.builtinDefs ?? resolveAllBuiltins(agent.builtinTools ?? [], builtinFetchOpt).defs;
|
|
1277
|
+
const rpcExecuteTool = opts.executeTool;
|
|
1278
|
+
const frozenEnv = Object.freeze({ ...env });
|
|
1279
|
+
executeTool = async (name, args, sessionId, messages) => {
|
|
1280
|
+
if (builtinDefs[name]) {
|
|
1281
|
+
const tool = builtinDefs[name];
|
|
1282
|
+
return executeToolCall(name, args, {
|
|
1283
|
+
tool,
|
|
1284
|
+
env: frozenEnv,
|
|
1285
|
+
sessionId: sessionId ?? "",
|
|
1286
|
+
kv,
|
|
1287
|
+
messages,
|
|
1288
|
+
logger
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
return rpcExecuteTool(name, args, sessionId, messages);
|
|
1292
|
+
};
|
|
1284
1293
|
toolSchemas = opts.toolSchemas;
|
|
1294
|
+
toolGuidance = opts.toolGuidance ?? [];
|
|
1285
1295
|
} else {
|
|
1296
|
+
const builtins = resolveAllBuiltins(agent.builtinTools ?? [], builtinFetchOpt);
|
|
1286
1297
|
const allTools = {
|
|
1287
|
-
...
|
|
1298
|
+
...builtins.defs,
|
|
1288
1299
|
...agent.tools
|
|
1289
1300
|
};
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
toolSchemas = [...customSchemas, ...builtinSchemas];
|
|
1301
|
+
toolSchemas = [...agentToolsToSchemas(agent.tools ?? {}), ...builtins.schemas];
|
|
1302
|
+
toolGuidance = builtins.guidance;
|
|
1293
1303
|
const stateMap = /* @__PURE__ */ new Map();
|
|
1294
1304
|
const getState = (sid) => {
|
|
1295
1305
|
if (!stateMap.has(sid) && agent.state) stateMap.set(sid, agent.state());
|
|
1296
1306
|
return stateMap.get(sid) ?? {};
|
|
1297
1307
|
};
|
|
1298
1308
|
const frozenEnv = Object.freeze({ ...env });
|
|
1299
|
-
function makeHookContext(sessionId) {
|
|
1300
|
-
return {
|
|
1301
|
-
env: frozenEnv,
|
|
1302
|
-
state: getState(sessionId),
|
|
1303
|
-
sessionId,
|
|
1304
|
-
get kv() {
|
|
1305
|
-
return kv;
|
|
1306
|
-
},
|
|
1307
|
-
fetch: globalThis.fetch
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
1309
|
executeTool = async (name, args, sessionId, messages) => {
|
|
1311
1310
|
const tool = allTools[name];
|
|
1312
1311
|
if (!tool) return toolError(`Unknown tool: ${name}`);
|
|
@@ -1317,17 +1316,9 @@ function createRuntime(opts) {
|
|
|
1317
1316
|
sessionId: sessionId ?? "",
|
|
1318
1317
|
kv,
|
|
1319
1318
|
messages,
|
|
1320
|
-
logger
|
|
1321
|
-
fetch: globalThis.fetch
|
|
1319
|
+
logger
|
|
1322
1320
|
});
|
|
1323
1321
|
};
|
|
1324
|
-
hooks = createAgentHooks({
|
|
1325
|
-
agent,
|
|
1326
|
-
makeCtx: makeHookContext
|
|
1327
|
-
});
|
|
1328
|
-
hooks.hook("disconnect", async (sessionId) => {
|
|
1329
|
-
stateMap.delete(sessionId);
|
|
1330
|
-
});
|
|
1331
1322
|
}
|
|
1332
1323
|
function createSession(sessionOpts) {
|
|
1333
1324
|
const apiKey = env.ASSEMBLYAI_API_KEY ?? "";
|
|
@@ -1337,11 +1328,11 @@ function createRuntime(opts) {
|
|
|
1337
1328
|
client: sessionOpts.client,
|
|
1338
1329
|
agentConfig,
|
|
1339
1330
|
toolSchemas,
|
|
1331
|
+
toolGuidance,
|
|
1340
1332
|
apiKey,
|
|
1341
1333
|
s2sConfig,
|
|
1342
1334
|
executeTool,
|
|
1343
1335
|
...createWebSocket ? { createWebSocket } : {},
|
|
1344
|
-
hooks,
|
|
1345
1336
|
skipGreeting: sessionOpts.skipGreeting ?? false,
|
|
1346
1337
|
logger,
|
|
1347
1338
|
...sessionOpts.resumeFrom ? { resumeFrom: sessionOpts.resumeFrom } : {}
|
|
@@ -1363,32 +1354,23 @@ function createRuntime(opts) {
|
|
|
1363
1354
|
...startOpts?.logContext ? { logContext: startOpts.logContext } : {},
|
|
1364
1355
|
...startOpts?.onOpen ? { onOpen: startOpts.onOpen } : {},
|
|
1365
1356
|
...startOpts?.onClose ? { onClose: startOpts.onClose } : {},
|
|
1357
|
+
...startOpts?.onSessionEnd ? { onSessionEnd: startOpts.onSessionEnd } : {},
|
|
1366
1358
|
...sessionStartTimeoutMs !== void 0 ? { sessionStartTimeoutMs } : {},
|
|
1367
1359
|
...resumeFrom ? { resumeFrom } : {}
|
|
1368
1360
|
});
|
|
1369
1361
|
}
|
|
1370
1362
|
async function shutdown() {
|
|
1371
1363
|
if (sessions.size === 0) return;
|
|
1372
|
-
let timer;
|
|
1373
|
-
const timeout = new Promise((resolve) => {
|
|
1374
|
-
timer = setTimeout(resolve, shutdownTimeoutMs, "timeout");
|
|
1375
|
-
});
|
|
1376
|
-
const graceful = Promise.allSettled([...sessions.values()].map((s) => s.stop())).then((results) => {
|
|
1377
|
-
for (const r of results) if (r.status === "rejected") logger.warn(`Session stop failed during shutdown: ${r.reason}`);
|
|
1378
|
-
return "done";
|
|
1379
|
-
});
|
|
1380
|
-
let outcome;
|
|
1381
1364
|
try {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1365
|
+
const results = await pTimeout(Promise.allSettled([...sessions.values()].map((s) => s.stop())), { milliseconds: shutdownTimeoutMs });
|
|
1366
|
+
for (const r of results) if (r.status === "rejected") logger.warn(`Session stop failed during shutdown: ${r.reason}`);
|
|
1367
|
+
} catch {
|
|
1368
|
+
logger.warn(`Shutdown timeout (${shutdownTimeoutMs}ms) exceeded — force-closing ${sessions.size} remaining session(s)`);
|
|
1385
1369
|
}
|
|
1386
|
-
if (outcome === "timeout") logger.warn(`Shutdown timeout (${shutdownTimeoutMs}ms) exceeded — force-closing ${sessions.size} remaining session(s)`);
|
|
1387
1370
|
sessions.clear();
|
|
1388
1371
|
}
|
|
1389
1372
|
return {
|
|
1390
1373
|
executeTool,
|
|
1391
|
-
hooks,
|
|
1392
1374
|
toolSchemas,
|
|
1393
1375
|
createSession,
|
|
1394
1376
|
startSession,
|
|
@@ -1397,4 +1379,140 @@ function createRuntime(opts) {
|
|
|
1397
1379
|
};
|
|
1398
1380
|
}
|
|
1399
1381
|
//#endregion
|
|
1400
|
-
|
|
1382
|
+
//#region host/server.ts
|
|
1383
|
+
/**
|
|
1384
|
+
* Agent HTTP+WebSocket server.
|
|
1385
|
+
*
|
|
1386
|
+
* {@link createServer} wraps a {@link Runtime} with an HTTP + WebSocket
|
|
1387
|
+
* server using only `node:http` and `ws` (no framework dependencies).
|
|
1388
|
+
*
|
|
1389
|
+
* **Internal module** — used by `aai-cli` dev server. Not a public API.
|
|
1390
|
+
* Import via `aai/host`.
|
|
1391
|
+
*/
|
|
1392
|
+
async function serveStatic(dir, req, res) {
|
|
1393
|
+
const url = req.url?.split("?")[0] ?? "/";
|
|
1394
|
+
const filePath = path.join(dir, url === "/" ? "index.html" : url);
|
|
1395
|
+
const resolved = path.resolve(dir);
|
|
1396
|
+
if (!filePath.startsWith(resolved + path.sep) && filePath !== resolved) return false;
|
|
1397
|
+
try {
|
|
1398
|
+
const stat = await fs.promises.stat(filePath);
|
|
1399
|
+
if (!stat.isFile()) return false;
|
|
1400
|
+
const mime = lookup(path.extname(filePath).toLowerCase()) || "application/octet-stream";
|
|
1401
|
+
res.writeHead(200, {
|
|
1402
|
+
"Content-Type": mime,
|
|
1403
|
+
"Content-Length": stat.size
|
|
1404
|
+
});
|
|
1405
|
+
fs.createReadStream(filePath).pipe(res);
|
|
1406
|
+
return true;
|
|
1407
|
+
} catch {
|
|
1408
|
+
return false;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function handleKvGet(kv, req, res) {
|
|
1412
|
+
const key = new URL(req.url ?? "/", "http://localhost").searchParams.get("key");
|
|
1413
|
+
if (!key) {
|
|
1414
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1415
|
+
res.end(JSON.stringify({ error: "Missing key query parameter" }));
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
kv.get(key).then((value) => {
|
|
1419
|
+
if (value === null) {
|
|
1420
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1421
|
+
res.end("null");
|
|
1422
|
+
} else {
|
|
1423
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1424
|
+
res.end(JSON.stringify(value));
|
|
1425
|
+
}
|
|
1426
|
+
}).catch(() => {
|
|
1427
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1428
|
+
res.end(JSON.stringify({ error: "KV error" }));
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Create an HTTP + WebSocket server for an agent.
|
|
1433
|
+
*
|
|
1434
|
+
* @internal Used by aai-cli dev server.
|
|
1435
|
+
*/
|
|
1436
|
+
function createServer(options) {
|
|
1437
|
+
const { runtime, clientHtml, clientDir, logger = consoleLogger, kv } = options;
|
|
1438
|
+
const name = options.name ?? "agent";
|
|
1439
|
+
if (clientHtml && clientDir) throw new Error("clientHtml and clientDir are mutually exclusive");
|
|
1440
|
+
const escapedName = escapeHtml(name);
|
|
1441
|
+
const defaultHtml = clientHtml ?? `<!DOCTYPE html><html><body><h1>${escapedName}</h1><p>Agent server running.</p></body></html>`;
|
|
1442
|
+
const httpServer = http.createServer((req, res) => {
|
|
1443
|
+
const url = req.url?.split("?")[0] ?? "/";
|
|
1444
|
+
const method = req.method ?? "GET";
|
|
1445
|
+
res.setHeader("Content-Security-Policy", AGENT_CSP);
|
|
1446
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
1447
|
+
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
|
1448
|
+
if (method === "GET" && url === "/health") {
|
|
1449
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1450
|
+
res.end(JSON.stringify({
|
|
1451
|
+
status: "ok",
|
|
1452
|
+
name
|
|
1453
|
+
}));
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
if (kv && method === "GET" && url === "/kv") {
|
|
1457
|
+
handleKvGet(kv, req, res);
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
handleRequest(req, res, url, method);
|
|
1461
|
+
});
|
|
1462
|
+
async function handleRequest(req, res, url, method) {
|
|
1463
|
+
if (clientDir && await serveStatic(clientDir, req, res)) return;
|
|
1464
|
+
if (method === "GET" && url === "/") {
|
|
1465
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1466
|
+
res.end(defaultHtml);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
logger.error(`${method} ${url} 404`);
|
|
1470
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1471
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
1472
|
+
}
|
|
1473
|
+
const wss = new WebSocketServer({
|
|
1474
|
+
noServer: true,
|
|
1475
|
+
maxPayload: MAX_WS_PAYLOAD_BYTES
|
|
1476
|
+
});
|
|
1477
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
1478
|
+
const url = req.url?.split("?")[0] ?? "";
|
|
1479
|
+
if (!url.startsWith("/websocket")) return;
|
|
1480
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
1481
|
+
const startOpts = parseWsUpgradeParams(req.url ?? "");
|
|
1482
|
+
logger.info(`WS upgrade ${url}${startOpts.skipGreeting ? " (resume)" : ""}`);
|
|
1483
|
+
runtime.startSession(ws, startOpts);
|
|
1484
|
+
});
|
|
1485
|
+
});
|
|
1486
|
+
let listenPort;
|
|
1487
|
+
return {
|
|
1488
|
+
get port() {
|
|
1489
|
+
return listenPort;
|
|
1490
|
+
},
|
|
1491
|
+
async listen(port = 3e3) {
|
|
1492
|
+
await new Promise((resolve, reject) => {
|
|
1493
|
+
httpServer.on("error", reject);
|
|
1494
|
+
httpServer.listen(port, () => {
|
|
1495
|
+
const addr = httpServer.address();
|
|
1496
|
+
listenPort = typeof addr === "object" && addr ? addr.port : port;
|
|
1497
|
+
resolve();
|
|
1498
|
+
});
|
|
1499
|
+
});
|
|
1500
|
+
},
|
|
1501
|
+
async close() {
|
|
1502
|
+
try {
|
|
1503
|
+
await runtime.shutdown();
|
|
1504
|
+
} finally {
|
|
1505
|
+
try {
|
|
1506
|
+
wss.close();
|
|
1507
|
+
} finally {
|
|
1508
|
+
if (listenPort !== void 0) await new Promise((resolve, reject) => {
|
|
1509
|
+
httpServer.close((err) => err ? reject(err) : resolve());
|
|
1510
|
+
});
|
|
1511
|
+
listenPort = void 0;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
//#endregion
|
|
1518
|
+
export { DEFAULT_S2S_CONFIG, _internals, buildCtx, connectS2s, consoleLogger, createRuntime, createS2sSession, createServer, createUnstorageKv, defaultCreateS2sWebSocket, executeInIsolate, executeToolCall, jsonLogger, resolveAllBuiltins, wireSessionSocket };
|