@alexkroman1/aai 1.4.5 → 1.5.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.
Files changed (86) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +19 -0
  3. package/dist/{_internal-types-3p3OJZPb.js → _internal-types-DFL07G3f.js} +2 -0
  4. package/dist/assemblyai-C969QGi4.js +35 -0
  5. package/dist/cartesia-BfQPOQ7Y.js +37 -0
  6. package/dist/host/_pipeline-test-fakes.d.ts +3 -1
  7. package/dist/host/providers/stt/deepgram.d.ts +28 -0
  8. package/dist/host/providers/tts/cartesia.d.ts +1 -1
  9. package/dist/host/providers/tts/rime.d.ts +44 -0
  10. package/dist/host/runtime-barrel.d.ts +4 -2
  11. package/dist/host/runtime-barrel.js +1434 -1209
  12. package/dist/host/runtime.d.ts +2 -2
  13. package/dist/host/s2s.d.ts +16 -16
  14. package/dist/host/session-core.d.ts +37 -0
  15. package/dist/host/transports/pipeline-transport.d.ts +48 -0
  16. package/dist/host/transports/s2s-transport.d.ts +19 -0
  17. package/dist/host/transports/types.d.ts +45 -0
  18. package/dist/host/ws-handler.d.ts +14 -10
  19. package/dist/sdk/_internal-types.d.ts +2 -0
  20. package/dist/sdk/manifest-barrel.js +1 -1
  21. package/dist/sdk/protocol.d.ts +6 -5
  22. package/dist/sdk/providers/llm-barrel.js +1 -1
  23. package/dist/sdk/providers/stt/deepgram.d.ts +35 -0
  24. package/dist/sdk/providers/stt-barrel.d.ts +1 -0
  25. package/dist/sdk/providers/stt-barrel.js +2 -2
  26. package/dist/sdk/providers/tts/cartesia.d.ts +12 -4
  27. package/dist/sdk/providers/tts/rime.d.ts +42 -0
  28. package/dist/sdk/providers/tts-barrel.d.ts +1 -0
  29. package/dist/sdk/providers/tts-barrel.js +2 -2
  30. package/host/_pipeline-test-fakes.ts +6 -3
  31. package/host/_test-utils.ts +209 -128
  32. package/host/builtin-tools.ts +1 -0
  33. package/host/cleanup.test.ts +25 -298
  34. package/host/integration/pipeline-reference.integration.test.ts +30 -35
  35. package/host/providers/resolve.ts +10 -2
  36. package/host/providers/stt/deepgram.test.ts +229 -0
  37. package/host/providers/stt/deepgram.ts +172 -0
  38. package/host/providers/tts/cartesia.ts +7 -3
  39. package/host/providers/tts/rime.test.ts +251 -0
  40. package/host/providers/tts/rime.ts +322 -0
  41. package/host/runtime-barrel.ts +4 -2
  42. package/host/runtime.test.ts +16 -47
  43. package/host/runtime.ts +131 -23
  44. package/host/s2s.test.ts +122 -131
  45. package/host/s2s.ts +44 -52
  46. package/host/session-core.test.ts +257 -0
  47. package/host/session-core.ts +262 -0
  48. package/host/to-vercel-tools.test.ts +9 -1
  49. package/host/transports/pipeline-transport.test.ts +653 -0
  50. package/host/transports/pipeline-transport.ts +532 -0
  51. package/host/{fixture-replay.test.ts → transports/s2s-transport-fixtures.test.ts} +76 -106
  52. package/host/transports/s2s-transport.test.ts +56 -0
  53. package/host/transports/s2s-transport.ts +116 -0
  54. package/host/transports/types.test.ts +22 -0
  55. package/host/transports/types.ts +51 -0
  56. package/host/ws-handler.test.ts +324 -242
  57. package/host/ws-handler.ts +56 -59
  58. package/package.json +2 -1
  59. package/sdk/__snapshots__/exports.test.ts.snap +3 -3
  60. package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
  61. package/sdk/_internal-types.ts +3 -0
  62. package/sdk/protocol-compat.test.ts +8 -0
  63. package/sdk/protocol.ts +6 -5
  64. package/sdk/providers/stt/deepgram.ts +43 -0
  65. package/sdk/providers/stt-barrel.ts +2 -0
  66. package/sdk/providers/tts/cartesia.ts +15 -5
  67. package/sdk/providers/tts/rime.ts +52 -0
  68. package/sdk/providers/tts-barrel.ts +2 -0
  69. package/sdk/schema-alignment.test.ts +18 -6
  70. package/dist/assemblyai-Cxg9eobY.js +0 -18
  71. package/dist/cartesia-DwDk2tEu.js +0 -10
  72. package/dist/host/pipeline-session-ctx.d.ts +0 -24
  73. package/dist/host/pipeline-session.d.ts +0 -52
  74. package/dist/host/session-ctx.d.ts +0 -73
  75. package/dist/host/session.d.ts +0 -62
  76. package/host/pipeline-session-ctx.test.ts +0 -31
  77. package/host/pipeline-session-ctx.ts +0 -36
  78. package/host/pipeline-session.test.ts +0 -672
  79. package/host/pipeline-session.ts +0 -533
  80. package/host/s2s-fixtures.test.ts +0 -237
  81. package/host/session-ctx.test.ts +0 -387
  82. package/host/session-ctx.ts +0 -134
  83. package/host/session-fixture-replay.test.ts +0 -128
  84. package/host/session.test.ts +0 -634
  85. package/host/session.ts +0 -412
  86. /package/dist/{anthropic-BrUCPKUc.js → anthropic-CcLZygAr.js} +0 -0
@@ -0,0 +1,262 @@
1
+ // Copyright 2026 the AAI authors. MIT license.
2
+ // Unified session — owns reply lifecycle, conversation history, idle timeout,
3
+ // and tool-step enforcement. Replaces session.ts + pipeline-session.ts.
4
+
5
+ import type { AgentConfig, ExecuteTool } from "../sdk/_internal-types.ts";
6
+ import { DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_MAX_HISTORY } from "../sdk/constants.ts";
7
+ import type { ClientEvent, ClientSink, SessionErrorCode } from "../sdk/protocol.ts";
8
+ import type { Message } from "../sdk/types.ts";
9
+ import type { Logger } from "./runtime-config.ts";
10
+ import { consoleLogger } from "./runtime-config.ts";
11
+ import type { Transport } from "./transports/types.ts";
12
+
13
+ const REPLY_DONE_SLOW_THRESHOLD_MS = 50;
14
+
15
+ type PendingTool = { callId: string; result: string };
16
+
17
+ type ReplyState = {
18
+ currentReplyId: string | null;
19
+ pendingTools: PendingTool[];
20
+ toolCallCount: number;
21
+ };
22
+
23
+ export type SessionCoreOptions = {
24
+ id: string;
25
+ agent: string;
26
+ client: ClientSink;
27
+ agentConfig: AgentConfig;
28
+ executeTool: ExecuteTool;
29
+ transport: Transport;
30
+ logger?: Logger;
31
+ maxHistory?: number;
32
+ };
33
+
34
+ export type SessionCore = {
35
+ readonly id: string;
36
+ start(): Promise<void>;
37
+ stop(): Promise<void>;
38
+ // Inbound from client (decoded by ws-handler)
39
+ onAudio(bytes: Uint8Array): void;
40
+ onAudioReady(): void;
41
+ onCancel(): void;
42
+ onReset(): void;
43
+ onHistory(messages: readonly Message[]): void;
44
+ // Inbound from transport (spec §4.2)
45
+ onReplyStarted(replyId: string): void;
46
+ onReplyDone(): void;
47
+ onCancelled(): void;
48
+ onAudioChunk(bytes: Uint8Array): void;
49
+ onAudioDone(): void;
50
+ onUserTranscript(text: string): void;
51
+ onAgentTranscript(text: string, interrupted: boolean): void;
52
+ onToolCall(callId: string, name: string, args: Record<string, unknown>): void;
53
+ onError(code: SessionErrorCode, message: string): void;
54
+ onSpeechStarted(): void;
55
+ onSpeechStopped(): void;
56
+ };
57
+
58
+ export function createSessionCore(opts: SessionCoreOptions): SessionCore {
59
+ const log = opts.logger ?? consoleLogger;
60
+ const maxHistory = opts.maxHistory ?? DEFAULT_MAX_HISTORY;
61
+ const idleMs = (() => {
62
+ const raw = opts.agentConfig.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
63
+ return raw === 0 || !Number.isFinite(raw) ? 0 : raw;
64
+ })();
65
+
66
+ let reply: ReplyState = { currentReplyId: null, pendingTools: [], toolCallCount: 0 };
67
+ let history: Message[] = [];
68
+ let turnPromise: Promise<void> | null = null;
69
+ let idleTimer: NodeJS.Timeout | null = null;
70
+ let stopped = false;
71
+
72
+ function emit(event: ClientEvent): void {
73
+ opts.client.event(event);
74
+ }
75
+
76
+ function resetIdle(): void {
77
+ if (stopped || idleMs <= 0) return;
78
+ if (idleTimer) clearTimeout(idleTimer);
79
+ idleTimer = setTimeout(() => {
80
+ log.info("session idle timeout", { sid: opts.id });
81
+ emit({ type: "idle_timeout" });
82
+ }, idleMs);
83
+ }
84
+
85
+ function pushMessages(...msgs: Message[]): void {
86
+ history.push(...msgs);
87
+ if (maxHistory > 0 && history.length > maxHistory) {
88
+ history.splice(0, history.length - maxHistory);
89
+ }
90
+ }
91
+
92
+ function beginReply(replyId: string): void {
93
+ reply = { currentReplyId: replyId, pendingTools: [], toolCallCount: 0 };
94
+ turnPromise = null;
95
+ }
96
+
97
+ function cancelReply(): void {
98
+ reply = { currentReplyId: null, pendingTools: [], toolCallCount: 0 };
99
+ }
100
+
101
+ function flushReply(startMs: number, hadTurnPromise: boolean): void {
102
+ const stepsUsed = reply.toolCallCount;
103
+ if (stepsUsed > 0) log.info("Turn complete", { steps: stepsUsed, agent: opts.agent });
104
+ opts.client.playAudioDone();
105
+ emit({ type: "reply_done" });
106
+ reply.currentReplyId = null;
107
+ const durationMs = Date.now() - startMs;
108
+ if (durationMs >= REPLY_DONE_SLOW_THRESHOLD_MS) {
109
+ log.warn("slow reply_done dispatch", {
110
+ sid: opts.id,
111
+ agent: opts.agent,
112
+ durationMs,
113
+ hadTurnPromise,
114
+ });
115
+ }
116
+ }
117
+
118
+ return {
119
+ id: opts.id,
120
+
121
+ async start() {
122
+ resetIdle();
123
+ await opts.transport.start();
124
+ },
125
+
126
+ async stop() {
127
+ if (stopped) return;
128
+ stopped = true;
129
+ if (idleTimer) {
130
+ clearTimeout(idleTimer);
131
+ idleTimer = null;
132
+ }
133
+ if (turnPromise !== null) await turnPromise;
134
+ await opts.transport.stop();
135
+ },
136
+
137
+ // ─── Inbound from client ──────────────────────────────────────────────
138
+ onAudio(bytes) {
139
+ resetIdle();
140
+ opts.transport.sendUserAudio(bytes);
141
+ },
142
+ onAudioReady() {
143
+ // S2S greeting is automatic; pipeline transports may override via callbacks.
144
+ },
145
+ onCancel() {
146
+ opts.transport.cancelReply();
147
+ emit({ type: "cancelled" });
148
+ },
149
+ onReset() {
150
+ cancelReply();
151
+ history = [];
152
+ emit({ type: "reset" });
153
+ },
154
+ onHistory(messages) {
155
+ pushMessages(...messages);
156
+ },
157
+
158
+ // ─── Inbound from transport ───────────────────────────────────────────
159
+ onReplyStarted(replyId) {
160
+ beginReply(replyId);
161
+ },
162
+
163
+ onReplyDone() {
164
+ const startMs = Date.now();
165
+ const doneReplyId = reply.currentReplyId;
166
+ // Dedup duplicate reply.done events — once the reply is fully dispatched
167
+ // (or was never started) currentReplyId is null.
168
+ if (doneReplyId === null) {
169
+ log.debug("Dropping duplicate reply.done (no active reply)");
170
+ return;
171
+ }
172
+ const hadTurnPromise = turnPromise !== null;
173
+ const sendPending = () => {
174
+ if (reply.currentReplyId !== doneReplyId) {
175
+ reply.pendingTools = [];
176
+ return;
177
+ }
178
+ if (reply.pendingTools.length > 0) {
179
+ for (const tool of reply.pendingTools)
180
+ opts.transport.sendToolResult(tool.callId, tool.result);
181
+ reply.pendingTools = [];
182
+ } else {
183
+ flushReply(startMs, hadTurnPromise);
184
+ }
185
+ };
186
+ if (hadTurnPromise) void turnPromise?.then(sendPending);
187
+ else sendPending();
188
+ },
189
+
190
+ onCancelled() {
191
+ cancelReply();
192
+ emit({ type: "cancelled" });
193
+ },
194
+
195
+ onAudioChunk(bytes) {
196
+ opts.client.playAudioChunk(bytes);
197
+ },
198
+ onAudioDone() {
199
+ opts.client.playAudioDone();
200
+ },
201
+
202
+ onUserTranscript(text) {
203
+ emit({ type: "user_transcript", text });
204
+ pushMessages({ role: "user", content: text });
205
+ },
206
+ onAgentTranscript(text, interrupted) {
207
+ emit({ type: "agent_transcript", text });
208
+ if (!interrupted) pushMessages({ role: "assistant", content: text });
209
+ },
210
+
211
+ onToolCall(callId, name, args) {
212
+ emit({
213
+ type: "tool_call",
214
+ toolCallId: callId,
215
+ toolName: name,
216
+ args: args as Record<string, unknown>,
217
+ });
218
+ if (reply.currentReplyId === null) {
219
+ log.warn("tool_call with no active reply", { sid: opts.id, name });
220
+ return;
221
+ }
222
+ reply.toolCallCount++;
223
+ const maxSteps = opts.agentConfig.maxSteps;
224
+ if (maxSteps !== undefined && reply.toolCallCount > maxSteps) {
225
+ log.info("maxSteps exceeded; refusing tool call", {
226
+ toolCallCount: reply.toolCallCount,
227
+ maxSteps,
228
+ });
229
+ reply.pendingTools.push({
230
+ callId,
231
+ result: JSON.stringify({
232
+ error: "Maximum tool steps reached. Please respond to the user now.",
233
+ }),
234
+ });
235
+ emit({ type: "tool_call_done", toolCallId: callId, result: "{}" });
236
+ return;
237
+ }
238
+ const p = (async () => {
239
+ try {
240
+ const result = await opts.executeTool(name, args, opts.id, history);
241
+ reply.pendingTools.push({ callId, result });
242
+ emit({ type: "tool_call_done", toolCallId: callId, result });
243
+ } catch (err) {
244
+ const message = err instanceof Error ? err.message : String(err);
245
+ reply.pendingTools.push({ callId, result: JSON.stringify({ error: message }) });
246
+ emit({ type: "tool_call_done", toolCallId: callId, result: message });
247
+ }
248
+ })();
249
+ turnPromise = (turnPromise ?? Promise.resolve()).then(() => p);
250
+ },
251
+
252
+ onError(code, message) {
253
+ emit({ type: "error", code, message });
254
+ },
255
+ onSpeechStarted() {
256
+ emit({ type: "speech_started" });
257
+ },
258
+ onSpeechStopped() {
259
+ emit({ type: "speech_stopped" });
260
+ },
261
+ };
262
+ }
@@ -6,6 +6,7 @@ import { toVercelTools } from "./to-vercel-tools.ts";
6
6
 
7
7
  const schemas: ToolSchema[] = [
8
8
  {
9
+ type: "function",
9
10
  name: "get_weather",
10
11
  description: "Look up the weather.",
11
12
  parameters: {
@@ -166,7 +167,14 @@ describe("toVercelTools — message snapshot isolation", () => {
166
167
  };
167
168
 
168
169
  const tools = toVercelTools(
169
- [{ name: "t", description: "", parameters: { type: "object", properties: {} } }],
170
+ [
171
+ {
172
+ type: "function",
173
+ name: "t",
174
+ description: "",
175
+ parameters: { type: "object", properties: {} },
176
+ },
177
+ ],
170
178
  {
171
179
  executeTool,
172
180
  sessionId: "s",