@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.
- package/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +13 -0
- package/dist/assemblyai-C969QGi4.js +35 -0
- package/dist/cartesia-BfQPOQ7Y.js +37 -0
- package/dist/host/_pipeline-test-fakes.d.ts +3 -1
- package/dist/host/providers/stt/deepgram.d.ts +28 -0
- package/dist/host/providers/tts/cartesia.d.ts +1 -1
- package/dist/host/providers/tts/rime.d.ts +44 -0
- package/dist/host/runtime-barrel.d.ts +4 -2
- package/dist/host/runtime-barrel.js +1432 -1208
- package/dist/host/runtime.d.ts +2 -2
- package/dist/host/s2s.d.ts +16 -16
- package/dist/host/session-core.d.ts +37 -0
- package/dist/host/transports/pipeline-transport.d.ts +48 -0
- package/dist/host/transports/s2s-transport.d.ts +19 -0
- package/dist/host/transports/types.d.ts +45 -0
- package/dist/host/ws-handler.d.ts +14 -10
- package/dist/sdk/protocol.d.ts +6 -5
- package/dist/sdk/providers/llm-barrel.js +1 -1
- package/dist/sdk/providers/stt/deepgram.d.ts +35 -0
- package/dist/sdk/providers/stt-barrel.d.ts +1 -0
- package/dist/sdk/providers/stt-barrel.js +2 -2
- package/dist/sdk/providers/tts/cartesia.d.ts +12 -4
- package/dist/sdk/providers/tts/rime.d.ts +42 -0
- package/dist/sdk/providers/tts-barrel.d.ts +1 -0
- package/dist/sdk/providers/tts-barrel.js +2 -2
- package/host/_pipeline-test-fakes.ts +6 -3
- package/host/_test-utils.ts +209 -128
- package/host/cleanup.test.ts +25 -298
- package/host/integration/pipeline-reference.integration.test.ts +30 -35
- package/host/providers/resolve.ts +10 -2
- package/host/providers/stt/deepgram.test.ts +229 -0
- package/host/providers/stt/deepgram.ts +172 -0
- package/host/providers/tts/cartesia.ts +7 -3
- package/host/providers/tts/rime.test.ts +251 -0
- package/host/providers/tts/rime.ts +322 -0
- package/host/runtime-barrel.ts +4 -2
- package/host/runtime.test.ts +13 -46
- package/host/runtime.ts +131 -23
- package/host/s2s.test.ts +122 -131
- package/host/s2s.ts +44 -52
- package/host/session-core.test.ts +257 -0
- package/host/session-core.ts +262 -0
- package/host/transports/pipeline-transport.test.ts +651 -0
- package/host/transports/pipeline-transport.ts +532 -0
- package/host/{fixture-replay.test.ts → transports/s2s-transport-fixtures.test.ts} +76 -106
- package/host/transports/s2s-transport.test.ts +56 -0
- package/host/transports/s2s-transport.ts +116 -0
- package/host/transports/types.test.ts +22 -0
- package/host/transports/types.ts +51 -0
- package/host/ws-handler.test.ts +324 -242
- package/host/ws-handler.ts +56 -59
- package/package.json +2 -1
- package/sdk/__snapshots__/exports.test.ts.snap +3 -3
- package/sdk/protocol-compat.test.ts +8 -0
- package/sdk/protocol.ts +6 -5
- package/sdk/providers/stt/deepgram.ts +43 -0
- package/sdk/providers/stt-barrel.ts +2 -0
- package/sdk/providers/tts/cartesia.ts +15 -5
- package/sdk/providers/tts/rime.ts +52 -0
- package/sdk/providers/tts-barrel.ts +2 -0
- package/dist/assemblyai-Cxg9eobY.js +0 -18
- package/dist/cartesia-DwDk2tEu.js +0 -10
- package/dist/host/pipeline-session-ctx.d.ts +0 -24
- package/dist/host/pipeline-session.d.ts +0 -52
- package/dist/host/session-ctx.d.ts +0 -73
- package/dist/host/session.d.ts +0 -62
- package/host/pipeline-session-ctx.test.ts +0 -31
- package/host/pipeline-session-ctx.ts +0 -36
- package/host/pipeline-session.test.ts +0 -672
- package/host/pipeline-session.ts +0 -533
- package/host/s2s-fixtures.test.ts +0 -237
- package/host/session-ctx.test.ts +0 -387
- package/host/session-ctx.ts +0 -134
- package/host/session-fixture-replay.test.ts +0 -128
- package/host/session.test.ts +0 -634
- package/host/session.ts +0 -412
- /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
|
+
}
|