@alexkroman1/aai 0.10.2 → 0.10.4

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.
Files changed (60) hide show
  1. package/dist/_internal-types.d.ts +8 -1
  2. package/dist/_runtime-conformance.d.ts +64 -0
  3. package/dist/_test-utils.d.ts +70 -0
  4. package/dist/_utils.d.ts +1 -8
  5. package/dist/_utils.js +1 -13
  6. package/dist/builtin-tools.d.ts +1 -5
  7. package/dist/constants-BbAOvKl_.js +47 -0
  8. package/dist/constants.d.ts +44 -0
  9. package/dist/direct-executor-BfHrDdPL.js +1589 -0
  10. package/dist/direct-executor.d.ts +90 -31
  11. package/dist/hooks.d.ts +44 -0
  12. package/dist/hooks.js +58 -0
  13. package/dist/index.d.ts +1 -2
  14. package/dist/index.js +2 -2
  15. package/dist/internal.d.ts +19 -0
  16. package/dist/internal.js +209 -0
  17. package/dist/kv.d.ts +1 -1
  18. package/dist/kv.js +5 -4
  19. package/dist/matchers.js +1 -1
  20. package/dist/protocol.d.ts +3 -29
  21. package/dist/protocol.js +2 -24
  22. package/dist/server.d.ts +25 -38
  23. package/dist/server.js +114 -138
  24. package/dist/session.d.ts +65 -44
  25. package/dist/{testing-MRl3SXsI.js → testing-BonJtfHJ.js} +26 -46
  26. package/dist/testing.d.ts +9 -14
  27. package/dist/testing.js +2 -2
  28. package/dist/types.d.ts +24 -226
  29. package/dist/types.js +6 -22
  30. package/dist/types.test-d.d.ts +7 -0
  31. package/dist/unstorage-kv.d.ts +33 -0
  32. package/dist/vite-plugin.d.ts +15 -0
  33. package/dist/vite-plugin.js +82 -0
  34. package/dist/ws-handler.d.ts +1 -2
  35. package/package.json +29 -84
  36. package/dist/_internal-types.js +0 -61
  37. package/dist/_session-ctx.d.ts +0 -73
  38. package/dist/_session-otel.d.ts +0 -43
  39. package/dist/_session-persist.d.ts +0 -30
  40. package/dist/_ssrf.d.ts +0 -30
  41. package/dist/_ssrf.js +0 -123
  42. package/dist/direct-executor-Ca0wt5H0.js +0 -572
  43. package/dist/middleware-core.d.ts +0 -47
  44. package/dist/middleware-core.js +0 -107
  45. package/dist/middleware.d.ts +0 -37
  46. package/dist/runtime.js +0 -53
  47. package/dist/s2s.js +0 -272
  48. package/dist/session-BkN9u0ni.js +0 -683
  49. package/dist/session.js +0 -2
  50. package/dist/sqlite-kv.d.ts +0 -34
  51. package/dist/sqlite-kv.js +0 -133
  52. package/dist/sqlite-vector.d.ts +0 -58
  53. package/dist/sqlite-vector.js +0 -149
  54. package/dist/telemetry.d.ts +0 -49
  55. package/dist/telemetry.js +0 -95
  56. package/dist/vector.d.ts +0 -85
  57. package/dist/vector.js +0 -49
  58. package/dist/worker-entry.d.ts +0 -47
  59. package/dist/worker-entry.js +0 -70
  60. package/dist/ws-handler.js +0 -207
@@ -1,683 +0,0 @@
1
- import { DEFAULT_INSTRUCTIONS } from "./types.js";
2
- import { errorDetail, errorMessage, toolError } from "./_utils.js";
3
- import { HOOK_TIMEOUT_MS, MAX_TOOL_RESULT_CHARS } from "./protocol.js";
4
- import { consoleLogger } from "./runtime.js";
5
- import { activeSessionsUpDown, bargeInCounter, idleTimeoutCounter, sessionCounter, toolCallCounter, toolCallDuration, toolCallErrorCounter, tracer, turnCounter, turnStepsHistogram } from "./telemetry.js";
6
- import { connectS2s, defaultCreateS2sWebSocket } from "./s2s.js";
7
- //#region _session-ctx.ts
8
- const DEFAULT_MAX_HISTORY = 200;
9
- function buildCtx(opts) {
10
- const { id, agentConfig, hookInvoker, log } = opts;
11
- const maxHistory = opts.maxHistory ?? DEFAULT_MAX_HISTORY;
12
- /** Track in-flight hook promises so they can be awaited during shutdown. */
13
- const pendingHooks = /* @__PURE__ */ new Set();
14
- const ctx = {
15
- ...opts,
16
- s2s: null,
17
- pendingTools: [],
18
- toolCallCount: 0,
19
- turnPromise: null,
20
- conversationMessages: [],
21
- maxHistory,
22
- currentReplyId: null,
23
- filterChain: Promise.resolve(),
24
- resolveTurnConfig() {
25
- if (!hookInvoker) return Promise.resolve(null);
26
- return hookInvoker.resolveTurnConfig(id, HOOK_TIMEOUT_MS);
27
- },
28
- consumeToolCallStep(turnConfig, _name, replyId) {
29
- if (replyId === null || replyId !== ctx.currentReplyId) return toolError("Reply was interrupted. Discarding stale tool call.");
30
- const maxSteps = turnConfig?.maxSteps ?? agentConfig.maxSteps;
31
- ctx.toolCallCount++;
32
- if (maxSteps !== void 0 && ctx.toolCallCount > maxSteps) {
33
- log.info("maxSteps exceeded, refusing tool call", {
34
- toolCallCount: ctx.toolCallCount,
35
- maxSteps
36
- });
37
- return toolError("Maximum tool steps reached. Please respond to the user now.");
38
- }
39
- return null;
40
- },
41
- fireHook(name, fn) {
42
- if (!hookInvoker) return;
43
- const notifyOnError = (err) => {
44
- log.warn(`${name} hook failed`, { err: errorMessage(err) });
45
- if (name !== "onError") try {
46
- const r = hookInvoker.onError(id, { message: errorMessage(err) });
47
- if (r && typeof r.catch === "function") r.catch((e) => {
48
- log.warn("onError hook failed", { err: errorMessage(e) });
49
- });
50
- } catch (e) {
51
- log.warn("onError hook failed", { err: errorMessage(e) });
52
- }
53
- };
54
- try {
55
- const p = fn(hookInvoker).catch(notifyOnError).finally(() => pendingHooks.delete(p));
56
- pendingHooks.add(p);
57
- } catch (err) {
58
- notifyOnError(err);
59
- }
60
- },
61
- async drainHooks() {
62
- if (pendingHooks.size > 0) await Promise.all([...pendingHooks]);
63
- },
64
- pushMessages(...msgs) {
65
- ctx.conversationMessages.push(...msgs);
66
- if (maxHistory > 0 && ctx.conversationMessages.length > maxHistory) ctx.conversationMessages = ctx.conversationMessages.slice(-maxHistory);
67
- }
68
- };
69
- return ctx;
70
- }
71
- //#endregion
72
- //#region _session-otel.ts
73
- /**
74
- * Complete a tool call by truncating the result, emitting a `tool_call_done` event,
75
- * and accumulating the result in `ctx.pendingTools` — but only if the reply that
76
- * initiated this call is still active.
77
- */
78
- function finishToolCall(ctx, callId, result, replyId) {
79
- const truncatedResult = result.length > 4e3 ? result.slice(0, MAX_TOOL_RESULT_CHARS) : result;
80
- ctx.client.event({
81
- type: "tool_call_done",
82
- toolCallId: callId,
83
- result: truncatedResult
84
- });
85
- if (replyId !== null && replyId === ctx.currentReplyId) {
86
- ctx.pendingTools.push({
87
- callId,
88
- result
89
- });
90
- if (ctx.maxHistory > 0 && ctx.pendingTools.length > ctx.maxHistory) ctx.pendingTools.shift();
91
- }
92
- }
93
- /**
94
- * Orchestrate the full tool call pipeline for a single S2S tool invocation.
95
- *
96
- * Steps: resolve per-turn config → check step/tool limits → run middleware
97
- * `interceptToolCall` (which may block, return a cached result, or modify args)
98
- * → execute the tool → run `afterToolCall` middleware → record metrics and
99
- * finish via {@link finishToolCall}. Each step is wrapped in an OpenTelemetry
100
- * span (`tool.call`) with agent/session/tool attributes.
101
- *
102
- * @param ctx - The shared mutable session context (see {@link S2sSessionCtx}).
103
- * @param detail - The tool call details from the S2S API (call ID, name, parsed args).
104
- */
105
- async function handleToolCall(ctx, detail) {
106
- const { callId, name, args: parsedArgs } = detail;
107
- const replyId = ctx.currentReplyId;
108
- const span = tracer.startSpan("tool.call", { attributes: {
109
- "aai.tool.name": name,
110
- "aai.tool.call_id": callId,
111
- "aai.agent": ctx.agent,
112
- "aai.session.id": ctx.id
113
- } });
114
- const startTime = performance.now();
115
- ctx.client.event({
116
- type: "tool_call_start",
117
- toolCallId: callId,
118
- toolName: name,
119
- args: parsedArgs
120
- });
121
- let turnConfig;
122
- try {
123
- turnConfig = await ctx.resolveTurnConfig();
124
- } catch (err) {
125
- const msg = `resolveTurnConfig hook error: ${errorMessage(err)}`;
126
- ctx.log.error(msg);
127
- span.setStatus({
128
- code: 2,
129
- message: msg
130
- });
131
- span.end();
132
- finishToolCall(ctx, callId, toolError(msg), replyId);
133
- return;
134
- }
135
- const refused = ctx.consumeToolCallStep(turnConfig, name, replyId);
136
- if (refused !== null) {
137
- span.setAttribute("aai.tool.refused", true);
138
- span.end();
139
- finishToolCall(ctx, callId, refused, replyId);
140
- return;
141
- }
142
- ctx.fireHook("onStep", (h) => h.onStep(ctx.id, {
143
- stepNumber: ctx.toolCallCount - 1,
144
- toolCalls: [{
145
- toolName: name,
146
- args: parsedArgs
147
- }],
148
- text: ""
149
- }, HOOK_TIMEOUT_MS));
150
- ctx.log.info("S2S tool call", {
151
- tool: name,
152
- callId,
153
- args: parsedArgs,
154
- agent: ctx.agent
155
- });
156
- toolCallCounter.add(1, {
157
- agent: ctx.agent,
158
- tool: name
159
- });
160
- let effectiveArgs = parsedArgs;
161
- if (ctx.hookInvoker?.interceptToolCall) try {
162
- const ic = await ctx.hookInvoker.interceptToolCall(ctx.id, name, parsedArgs, HOOK_TIMEOUT_MS);
163
- if (ic?.type === "block") {
164
- span.setAttribute("aai.tool.blocked", true);
165
- span.end();
166
- finishToolCall(ctx, callId, toolError(ic.reason), replyId);
167
- return;
168
- }
169
- if (ic?.type === "result") {
170
- span.setAttribute("aai.tool.cached", true);
171
- span.end();
172
- finishToolCall(ctx, callId, ic.result, replyId);
173
- ctx.fireHook("afterToolCall", (h) => h.afterToolCall?.(ctx.id, name, parsedArgs, ic.result, 5e3) ?? Promise.resolve());
174
- return;
175
- }
176
- if (ic?.type === "args") effectiveArgs = ic.args;
177
- } catch (err) {
178
- ctx.log.warn("interceptToolCall middleware failed (fail-open, tool call proceeds)", {
179
- err: errorMessage(err),
180
- tool: name
181
- });
182
- }
183
- const onUpdate = (data) => {
184
- const serialized = typeof data === "string" ? data : JSON.stringify(data);
185
- const truncated = serialized.length > 4e3 ? serialized.slice(0, MAX_TOOL_RESULT_CHARS) : serialized;
186
- ctx.client.event({
187
- type: "tool_call_update",
188
- toolCallId: callId,
189
- data: truncated
190
- });
191
- };
192
- let result;
193
- try {
194
- result = await ctx.executeTool(name, effectiveArgs, ctx.id, ctx.conversationMessages, onUpdate);
195
- } catch (err) {
196
- const msg = errorMessage(err);
197
- ctx.log.error("Tool execution failed", {
198
- tool: name,
199
- error: errorDetail(err)
200
- });
201
- toolCallErrorCounter.add(1, {
202
- agent: ctx.agent,
203
- tool: name
204
- });
205
- span.setStatus({
206
- code: 2,
207
- message: msg
208
- });
209
- span.recordException(err instanceof Error ? err : new Error(msg));
210
- result = toolError(msg);
211
- }
212
- if (ctx.hookInvoker?.afterToolCall) ctx.fireHook("afterToolCall", (h) => h.afterToolCall?.(ctx.id, name, effectiveArgs, result, 5e3) ?? Promise.resolve());
213
- toolCallDuration.record((performance.now() - startTime) / 1e3, {
214
- agent: ctx.agent,
215
- tool: name
216
- });
217
- ctx.log.info("S2S tool result", {
218
- tool: name,
219
- callId,
220
- resultLength: result.length
221
- });
222
- finishToolCall(ctx, callId, result, replyId);
223
- span.end();
224
- }
225
- function handleUserTranscript(ctx, text) {
226
- ctx.log.info("S2S user transcript", { text });
227
- turnCounter.add(1, { agent: ctx.agent });
228
- ctx.client.event({
229
- type: "transcript",
230
- text,
231
- isFinal: true
232
- });
233
- ctx.client.event({
234
- type: "turn",
235
- text
236
- });
237
- const processFiltered = (filtered) => {
238
- ctx.pushMessages({
239
- role: "user",
240
- content: filtered
241
- });
242
- const fireTurn = () => ctx.fireHook("onTurn", (h) => h.onTurn(ctx.id, filtered, HOOK_TIMEOUT_MS));
243
- if (!ctx.hookInvoker?.beforeTurn) {
244
- fireTurn();
245
- return;
246
- }
247
- ctx.hookInvoker.beforeTurn(ctx.id, filtered, HOOK_TIMEOUT_MS).then((reason) => {
248
- if (reason) {
249
- ctx.log.info("Turn blocked by middleware", { reason });
250
- ctx.client.event({
251
- type: "chat",
252
- text: reason
253
- });
254
- ctx.client.event({ type: "tts_done" });
255
- } else fireTurn();
256
- }).catch((err) => {
257
- ctx.log.warn("beforeTurn hook failed", { error: errorMessage(err) });
258
- fireTurn();
259
- });
260
- };
261
- if (!ctx.hookInvoker?.filterInput) {
262
- processFiltered(text);
263
- return;
264
- }
265
- ctx.hookInvoker.filterInput(ctx.id, text, HOOK_TIMEOUT_MS).then((filtered) => processFiltered(filtered)).catch((err) => {
266
- ctx.log.warn("filterInput hook failed", { error: errorMessage(err) });
267
- processFiltered(text);
268
- });
269
- }
270
- function handleAgentTranscriptDelta(ctx, text) {
271
- const filterOutput = ctx.hookInvoker?.filterOutput;
272
- if (!filterOutput) {
273
- ctx.client.event({
274
- type: "chat_delta",
275
- text
276
- });
277
- return;
278
- }
279
- ctx.filterChain = ctx.filterChain.then(async () => {
280
- try {
281
- const f = await filterOutput.call(ctx.hookInvoker, ctx.id, text, HOOK_TIMEOUT_MS);
282
- ctx.client.event({
283
- type: "chat_delta",
284
- text: f
285
- });
286
- } catch (err) {
287
- ctx.log.warn("filterOutput hook failed", { error: errorMessage(err) });
288
- ctx.client.event({
289
- type: "chat_delta",
290
- text
291
- });
292
- }
293
- });
294
- }
295
- function handleAgentTranscript(ctx, text, interrupted) {
296
- const emit = (t) => {
297
- ctx.client.event({
298
- type: "chat",
299
- text: t
300
- });
301
- if (!interrupted) ctx.pushMessages({
302
- role: "assistant",
303
- content: t
304
- });
305
- };
306
- const filterOutput = ctx.hookInvoker?.filterOutput;
307
- if (!filterOutput) {
308
- emit(text);
309
- return;
310
- }
311
- ctx.filterChain = ctx.filterChain.then(async () => {
312
- try {
313
- emit(await filterOutput.call(ctx.hookInvoker, ctx.id, text, HOOK_TIMEOUT_MS));
314
- } catch (err) {
315
- ctx.log.warn("filterOutput hook failed", { error: errorMessage(err) });
316
- emit(text);
317
- }
318
- });
319
- }
320
- function handleReplyDone(ctx, status) {
321
- if (status === "interrupted") {
322
- ctx.log.info("S2S reply interrupted (barge-in)");
323
- bargeInCounter.add(1, { agent: ctx.agent });
324
- ctx.currentReplyId = null;
325
- ctx.pendingTools = [];
326
- ctx.filterChain = Promise.resolve();
327
- ctx.client.event({ type: "cancelled" });
328
- return;
329
- }
330
- const doneReplyId = ctx.currentReplyId;
331
- const sendPending = () => {
332
- if (ctx.currentReplyId !== doneReplyId) {
333
- ctx.pendingTools = [];
334
- return;
335
- }
336
- if (ctx.pendingTools.length > 0) {
337
- for (const tool of ctx.pendingTools) ctx.s2s?.sendToolResult(tool.callId, tool.result);
338
- ctx.pendingTools = [];
339
- } else {
340
- const stepsUsed = ctx.toolCallCount;
341
- if (stepsUsed > 0) {
342
- ctx.log.info("Turn complete", {
343
- steps: stepsUsed,
344
- agent: ctx.agent
345
- });
346
- turnStepsHistogram.record(stepsUsed, { agent: ctx.agent });
347
- }
348
- if (ctx.hookInvoker?.afterTurn) {
349
- const last = ctx.conversationMessages.at(-1);
350
- ctx.fireHook("afterTurn", (h) => h.afterTurn?.(ctx.id, last?.content ?? "", 5e3) ?? Promise.resolve());
351
- }
352
- ctx.client.playAudioDone();
353
- ctx.client.event({ type: "tts_done" });
354
- }
355
- };
356
- if (ctx.turnPromise !== null) ctx.turnPromise.then(sendPending);
357
- else sendPending();
358
- }
359
- /**
360
- * Wire all S2S events to the client sink, hooks, and session state.
361
- *
362
- * Registers listeners on the S2S handle for: ready, session expiry, speech
363
- * start/stop, user/agent transcripts, reply lifecycle, tool calls, audio
364
- * chunks, errors, and close. Each listener delegates to a focused handler
365
- * function that updates `ctx` and emits client events.
366
- *
367
- * @param ctx - The shared mutable session context.
368
- * @param handle - The S2S WebSocket handle to listen on.
369
- * @param opts - Optional overrides for listener behavior.
370
- */
371
- function setupListeners(ctx, handle, opts) {
372
- handle.on("ready", ({ sessionId }) => ctx.log.info("S2S session ready", { sessionId }));
373
- handle.on("sessionExpired", () => {
374
- if (opts?.onSessionExpired) opts.onSessionExpired();
375
- else {
376
- ctx.log.info("S2S session expired");
377
- handle.close();
378
- }
379
- });
380
- handle.on("speechStarted", () => ctx.client.event({ type: "speech_started" }));
381
- handle.on("speechStopped", () => ctx.client.event({ type: "speech_stopped" }));
382
- handle.on("userTranscriptDelta", ({ text }) => ctx.client.event({
383
- type: "transcript",
384
- text,
385
- isFinal: false
386
- }));
387
- handle.on("userTranscript", ({ text }) => handleUserTranscript(ctx, text));
388
- handle.on("replyStarted", ({ replyId }) => {
389
- ctx.toolCallCount = 0;
390
- ctx.currentReplyId = replyId;
391
- ctx.pendingTools = [];
392
- ctx.turnPromise = null;
393
- ctx.filterChain = Promise.resolve();
394
- });
395
- handle.on("audio", ({ audio }) => ctx.client.playAudioChunk(audio));
396
- handle.on("agentTranscriptDelta", ({ text }) => handleAgentTranscriptDelta(ctx, text));
397
- handle.on("agentTranscript", ({ text, interrupted }) => handleAgentTranscript(ctx, text, interrupted));
398
- handle.on("toolCall", (detail) => {
399
- const p = handleToolCall(ctx, detail).catch((err) => {
400
- ctx.log.error("Tool call handler failed", { err: errorMessage(err) });
401
- });
402
- ctx.turnPromise = (ctx.turnPromise ?? Promise.resolve()).then(() => p);
403
- });
404
- handle.on("replyDone", ({ status }) => handleReplyDone(ctx, status));
405
- handle.on("error", ({ code, message }) => {
406
- ctx.log.error("S2S error", {
407
- code,
408
- message
409
- });
410
- ctx.client.event({
411
- type: "error",
412
- code: "internal",
413
- message
414
- });
415
- handle.close();
416
- });
417
- handle.on("close", () => {
418
- ctx.log.info("S2S closed");
419
- ctx.s2s = null;
420
- ctx.currentReplyId = null;
421
- ctx.pendingTools = [];
422
- });
423
- }
424
- //#endregion
425
- //#region _session-persist.ts
426
- const PERSIST_PREFIX = "__persist:";
427
- function persistKey(sessionId) {
428
- return `${PERSIST_PREFIX}${sessionId}`;
429
- }
430
- async function restorePersistedSession(persistence, resumeFrom, ctx, log) {
431
- const persisted = await persistence.kv.get(persistKey(resumeFrom));
432
- if (!persisted) return null;
433
- log.info("Restoring persisted session", { resumeFrom });
434
- persistence.setState(persisted.state);
435
- if (persisted.messages.length > 0) ctx.pushMessages(...persisted.messages);
436
- return persisted.s2sSessionId;
437
- }
438
- async function saveSessionData(persistence, sessionId, ctx, s2sSessionId, log, cleanupKey) {
439
- const data = {
440
- s2sSessionId,
441
- messages: ctx.conversationMessages,
442
- state: persistence.getState()
443
- };
444
- const key = persistKey(sessionId);
445
- const ops = [persistence.kv.set(key, data, { expireIn: persistence.ttl })];
446
- if (cleanupKey && cleanupKey !== sessionId) ops.push(persistence.kv.delete(persistKey(cleanupKey)));
447
- await Promise.all(ops);
448
- log.info("Session persisted", { sessionId });
449
- }
450
- //#endregion
451
- //#region system-prompt.ts
452
- function getFormattedDate() {
453
- return (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
454
- weekday: "long",
455
- year: "numeric",
456
- month: "long",
457
- day: "numeric"
458
- });
459
- }
460
- const VOICE_RULES = "\n\nCRITICAL OUTPUT RULES — you MUST follow these for EVERY response:\nYour response will be spoken aloud by a TTS system and displayed as plain text.\n- NEVER use markdown: no **, no *, no _, no #, no `, no [](), no ---\n- NEVER use bullet points (-, *, •) or numbered lists (1., 2.)\n- NEVER use code blocks or inline code\n- NEVER mention tools, search, APIs, or technical failures to the user. If a tool returns no results, just answer naturally without explaining why.\n- Write exactly as you would say it out loud to a friend\n- Use short conversational sentences. To list things, say \"First,\" \"Next,\" \"Finally,\"\n- Keep responses concise — 1 to 3 sentences max";
461
- /**
462
- * Build the system prompt sent to the LLM from the agent configuration.
463
- *
464
- * Assembles the default instructions, today's date, agent-specific instructions,
465
- * and optional sections for tool usage preamble and voice output rules.
466
- *
467
- * @param config - The serializable agent configuration (name, instructions, etc.).
468
- * @param opts.hasTools - When `true`, appends a preamble instructing the LLM to
469
- * speak a brief phrase before each tool call to fill silence.
470
- * @param opts.voice - When `true`, appends strict voice-specific output rules
471
- * (no markdown, no bullet points, conversational tone, concise responses).
472
- * @returns The assembled system prompt string.
473
- */
474
- function buildSystemPrompt(config, opts) {
475
- const { hasTools } = opts;
476
- const agentInstructions = config.instructions && config.instructions !== DEFAULT_INSTRUCTIONS ? `\n\nAgent-Specific Instructions:\n${config.instructions}` : "";
477
- const toolPreamble = hasTools ? "\n\nWhen you decide to use a tool, ALWAYS say a brief natural phrase BEFORE the tool call (e.g. \"Let me look that up\" or \"One moment while I check\"). This fills silence while the tool executes. Keep preambles to one short sentence." : "";
478
- return DEFAULT_INSTRUCTIONS + `\n\nToday's date is ${getFormattedDate()}.` + agentInstructions + toolPreamble + (opts.voice ? VOICE_RULES : "");
479
- }
480
- //#endregion
481
- //#region session.ts
482
- /** @internal Not part of the public API. Exposed for testing only. */
483
- const _internals = { connectS2s };
484
- const DEFAULT_IDLE_TIMEOUT_MS = 3e5;
485
- function createIdleTimer(opts) {
486
- if (opts.timeoutMs <= 0) return {
487
- reset() {},
488
- clear() {}
489
- };
490
- let timer = null;
491
- return {
492
- reset() {
493
- if (timer !== null) clearTimeout(timer);
494
- timer = setTimeout(() => {
495
- opts.log.info("S2S idle timeout", {
496
- timeoutMs: opts.timeoutMs,
497
- agent: opts.agent
498
- });
499
- idleTimeoutCounter.add(1, { agent: opts.agent });
500
- opts.client.event({ type: "idle_timeout" });
501
- opts.ctx.s2s?.close();
502
- }, opts.timeoutMs);
503
- },
504
- clear() {
505
- if (timer !== null) {
506
- clearTimeout(timer);
507
- timer = null;
508
- }
509
- }
510
- };
511
- }
512
- /**
513
- * Create a Speech-to-Speech backed session implementing the {@link Session} interface.
514
- *
515
- * Connects to AssemblyAI's S2S WebSocket, configures the system prompt and tools,
516
- * and wires up event listeners for user transcripts, agent replies, tool calls,
517
- * barge-ins, and session lifecycle. Manages reconnection on `onReset` via a
518
- * `connectGeneration` guard that prevents stale connection attempts from overwriting
519
- * newer ones during rapid resets. A `sessionAbort` AbortController is used to
520
- * coordinate cleanup on `stop()`.
521
- *
522
- * @param opts - Session configuration. See {@link S2sSessionOptions} for all fields
523
- * including the agent config, tool schemas, API key, and optional hooks.
524
- * @returns A {@link Session} with `start`, `stop`, `onAudio`, `onReset`, and other
525
- * lifecycle methods.
526
- */
527
- function createS2sSession(opts) {
528
- const { id, agent, client, toolSchemas, apiKey, s2sConfig, executeTool, createWebSocket = defaultCreateS2sWebSocket, hookInvoker, logger: log = consoleLogger, persistence, resumeFrom } = opts;
529
- const agentConfig = opts.skipGreeting ? {
530
- ...opts.agentConfig,
531
- greeting: ""
532
- } : opts.agentConfig;
533
- const systemPrompt = buildSystemPrompt(agentConfig, {
534
- hasTools: toolSchemas.length > 0 || (agentConfig.builtinTools?.length ?? 0) > 0,
535
- voice: true
536
- });
537
- const s2sTools = toolSchemas.map((ts) => ({
538
- type: "function",
539
- name: ts.name,
540
- description: ts.description,
541
- parameters: ts.parameters
542
- }));
543
- const sessionAbort = new AbortController();
544
- const ctx = buildCtx({
545
- id,
546
- agent,
547
- client,
548
- agentConfig,
549
- executeTool,
550
- hookInvoker,
551
- log,
552
- maxHistory: opts.maxHistory
553
- });
554
- const rawTimeout = agentConfig.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
555
- const idle = createIdleTimer({
556
- timeoutMs: rawTimeout === 0 || !Number.isFinite(rawTimeout) ? 0 : rawTimeout,
557
- agent,
558
- log,
559
- client,
560
- ctx
561
- });
562
- let currentS2sSessionId = null;
563
- let resumeS2sId = null;
564
- const pendingCleanupKey = resumeFrom;
565
- /** Monotonically increasing counter bumped on each connectAndSetup call.
566
- * Only the most recent invocation is allowed to set ctx.s2s, preventing
567
- * earlier completions from overwriting a newer connection during rapid resets. */
568
- let connectGeneration = 0;
569
- /** The session.update payload shared by fresh and fallback paths. */
570
- const sessionUpdatePayload = {
571
- systemPrompt,
572
- tools: s2sTools,
573
- ...agentConfig.greeting ? { greeting: agentConfig.greeting } : {}
574
- };
575
- async function connectAndSetup() {
576
- const generation = ++connectGeneration;
577
- try {
578
- const handle = await _internals.connectS2s({
579
- apiKey,
580
- config: s2sConfig,
581
- createWebSocket,
582
- logger: log
583
- });
584
- if (sessionAbort.signal.aborted || generation !== connectGeneration) {
585
- handle.close();
586
- return;
587
- }
588
- handle.on("ready", ({ sessionId: s2sId }) => {
589
- currentS2sSessionId = s2sId;
590
- });
591
- if (resumeS2sId) {
592
- const s2sId = resumeS2sId;
593
- resumeS2sId = null;
594
- let resumeFallbackUsed = false;
595
- setupListeners(ctx, handle, { onSessionExpired: () => {
596
- if (!resumeFallbackUsed) {
597
- resumeFallbackUsed = true;
598
- log.info("S2S session resume failed, falling back to new session");
599
- handle.updateSession(sessionUpdatePayload);
600
- } else {
601
- ctx.log.info("S2S session expired");
602
- handle.close();
603
- }
604
- } });
605
- handle.resumeSession(s2sId);
606
- } else {
607
- setupListeners(ctx, handle);
608
- handle.updateSession(sessionUpdatePayload);
609
- }
610
- ctx.s2s = handle;
611
- idle.reset();
612
- } catch (err) {
613
- const msg = errorMessage(err);
614
- log.error("S2S connect failed", { error: errorDetail(err) });
615
- client.event({
616
- type: "error",
617
- code: "internal",
618
- message: msg
619
- });
620
- }
621
- }
622
- return {
623
- async start() {
624
- if (persistence && resumeFrom) try {
625
- const s2sId = await restorePersistedSession(persistence, resumeFrom, ctx, log);
626
- if (s2sId) resumeS2sId = s2sId;
627
- } catch (err) {
628
- log.warn("Failed to restore persisted session", { error: errorMessage(err) });
629
- }
630
- sessionCounter.add(1, { agent });
631
- activeSessionsUpDown.add(1, { agent });
632
- ctx.fireHook("onConnect", (h) => h.onConnect(id, HOOK_TIMEOUT_MS));
633
- await connectAndSetup();
634
- },
635
- async stop() {
636
- if (sessionAbort.signal.aborted) return;
637
- sessionAbort.abort();
638
- idle.clear();
639
- activeSessionsUpDown.add(-1, { agent });
640
- if (ctx.turnPromise !== null) await ctx.turnPromise;
641
- await ctx.drainHooks();
642
- if (persistence) try {
643
- await saveSessionData(persistence, id, ctx, currentS2sSessionId, log, pendingCleanupKey);
644
- } catch (err) {
645
- log.warn("Failed to persist session", { error: errorMessage(err) });
646
- }
647
- ctx.s2s?.close();
648
- ctx.fireHook("onDisconnect", (h) => h.onDisconnect(id, HOOK_TIMEOUT_MS));
649
- await ctx.drainHooks();
650
- },
651
- onAudio(data) {
652
- idle.reset();
653
- ctx.s2s?.sendAudio(data);
654
- },
655
- onAudioReady() {},
656
- onCancel() {
657
- client.event({ type: "cancelled" });
658
- },
659
- onReset() {
660
- ctx.conversationMessages = [];
661
- ctx.toolCallCount = 0;
662
- ctx.turnPromise = null;
663
- ctx.pendingTools = [];
664
- ctx.currentReplyId = null;
665
- currentS2sSessionId = null;
666
- idle.clear();
667
- ctx.s2s?.close();
668
- client.event({ type: "reset" });
669
- connectAndSetup().catch((err) => log.error("S2S reset reconnect failed", { error: errorMessage(err) }));
670
- },
671
- onHistory(incoming) {
672
- ctx.pushMessages(...incoming.map((m) => ({
673
- role: m.role,
674
- content: m.content
675
- })));
676
- },
677
- waitForTurn() {
678
- return ctx.turnPromise ?? Promise.resolve();
679
- }
680
- };
681
- }
682
- //#endregion
683
- export { persistKey as i, createS2sSession as n, buildSystemPrompt as r, _internals as t };
package/dist/session.js DELETED
@@ -1,2 +0,0 @@
1
- import { i as persistKey, n as createS2sSession, r as buildSystemPrompt, t as _internals } from "./session-BkN9u0ni.js";
2
- export { _internals, buildSystemPrompt, createS2sSession, persistKey };