@alexkroman1/aai 1.4.5 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.turbo/turbo-build.log +9 -9
  2. package/CHANGELOG.md +13 -0
  3. package/dist/assemblyai-C969QGi4.js +35 -0
  4. package/dist/cartesia-BfQPOQ7Y.js +37 -0
  5. package/dist/host/_pipeline-test-fakes.d.ts +3 -1
  6. package/dist/host/providers/stt/deepgram.d.ts +28 -0
  7. package/dist/host/providers/tts/cartesia.d.ts +1 -1
  8. package/dist/host/providers/tts/rime.d.ts +44 -0
  9. package/dist/host/runtime-barrel.d.ts +4 -2
  10. package/dist/host/runtime-barrel.js +1432 -1208
  11. package/dist/host/runtime.d.ts +2 -2
  12. package/dist/host/s2s.d.ts +16 -16
  13. package/dist/host/session-core.d.ts +37 -0
  14. package/dist/host/transports/pipeline-transport.d.ts +48 -0
  15. package/dist/host/transports/s2s-transport.d.ts +19 -0
  16. package/dist/host/transports/types.d.ts +45 -0
  17. package/dist/host/ws-handler.d.ts +14 -10
  18. package/dist/sdk/protocol.d.ts +6 -5
  19. package/dist/sdk/providers/llm-barrel.js +1 -1
  20. package/dist/sdk/providers/stt/deepgram.d.ts +35 -0
  21. package/dist/sdk/providers/stt-barrel.d.ts +1 -0
  22. package/dist/sdk/providers/stt-barrel.js +2 -2
  23. package/dist/sdk/providers/tts/cartesia.d.ts +12 -4
  24. package/dist/sdk/providers/tts/rime.d.ts +42 -0
  25. package/dist/sdk/providers/tts-barrel.d.ts +1 -0
  26. package/dist/sdk/providers/tts-barrel.js +2 -2
  27. package/host/_pipeline-test-fakes.ts +6 -3
  28. package/host/_test-utils.ts +209 -128
  29. package/host/cleanup.test.ts +25 -298
  30. package/host/integration/pipeline-reference.integration.test.ts +30 -35
  31. package/host/providers/resolve.ts +10 -2
  32. package/host/providers/stt/deepgram.test.ts +229 -0
  33. package/host/providers/stt/deepgram.ts +172 -0
  34. package/host/providers/tts/cartesia.ts +7 -3
  35. package/host/providers/tts/rime.test.ts +251 -0
  36. package/host/providers/tts/rime.ts +322 -0
  37. package/host/runtime-barrel.ts +4 -2
  38. package/host/runtime.test.ts +13 -46
  39. package/host/runtime.ts +131 -23
  40. package/host/s2s.test.ts +122 -131
  41. package/host/s2s.ts +44 -52
  42. package/host/session-core.test.ts +257 -0
  43. package/host/session-core.ts +262 -0
  44. package/host/transports/pipeline-transport.test.ts +651 -0
  45. package/host/transports/pipeline-transport.ts +532 -0
  46. package/host/{fixture-replay.test.ts → transports/s2s-transport-fixtures.test.ts} +76 -106
  47. package/host/transports/s2s-transport.test.ts +56 -0
  48. package/host/transports/s2s-transport.ts +116 -0
  49. package/host/transports/types.test.ts +22 -0
  50. package/host/transports/types.ts +51 -0
  51. package/host/ws-handler.test.ts +324 -242
  52. package/host/ws-handler.ts +56 -59
  53. package/package.json +2 -1
  54. package/sdk/__snapshots__/exports.test.ts.snap +3 -3
  55. package/sdk/protocol-compat.test.ts +8 -0
  56. package/sdk/protocol.ts +6 -5
  57. package/sdk/providers/stt/deepgram.ts +43 -0
  58. package/sdk/providers/stt-barrel.ts +2 -0
  59. package/sdk/providers/tts/cartesia.ts +15 -5
  60. package/sdk/providers/tts/rime.ts +52 -0
  61. package/sdk/providers/tts-barrel.ts +2 -0
  62. package/dist/assemblyai-Cxg9eobY.js +0 -18
  63. package/dist/cartesia-DwDk2tEu.js +0 -10
  64. package/dist/host/pipeline-session-ctx.d.ts +0 -24
  65. package/dist/host/pipeline-session.d.ts +0 -52
  66. package/dist/host/session-ctx.d.ts +0 -73
  67. package/dist/host/session.d.ts +0 -62
  68. package/host/pipeline-session-ctx.test.ts +0 -31
  69. package/host/pipeline-session-ctx.ts +0 -36
  70. package/host/pipeline-session.test.ts +0 -672
  71. package/host/pipeline-session.ts +0 -533
  72. package/host/s2s-fixtures.test.ts +0 -237
  73. package/host/session-ctx.test.ts +0 -387
  74. package/host/session-ctx.ts +0 -134
  75. package/host/session-fixture-replay.test.ts +0 -128
  76. package/host/session.test.ts +0 -634
  77. package/host/session.ts +0 -412
  78. /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
+ }