@alexkroman1/aai 0.3.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.js +3436 -0
  3. package/package.json +78 -0
  4. package/sdk/_internal_types.ts +89 -0
  5. package/sdk/_mock_ws.ts +172 -0
  6. package/sdk/_timeout.ts +24 -0
  7. package/sdk/builtin_tools.ts +309 -0
  8. package/sdk/capnweb.ts +341 -0
  9. package/sdk/define_agent.ts +70 -0
  10. package/sdk/direct_executor.ts +195 -0
  11. package/sdk/kv.ts +183 -0
  12. package/sdk/mod.ts +35 -0
  13. package/sdk/protocol.ts +313 -0
  14. package/sdk/runtime.ts +65 -0
  15. package/sdk/s2s.ts +271 -0
  16. package/sdk/server.ts +198 -0
  17. package/sdk/session.ts +438 -0
  18. package/sdk/system_prompt.ts +47 -0
  19. package/sdk/types.ts +406 -0
  20. package/sdk/vector.ts +133 -0
  21. package/sdk/winterc_server.ts +141 -0
  22. package/sdk/worker_entry.ts +99 -0
  23. package/sdk/worker_shim.ts +170 -0
  24. package/sdk/ws_handler.ts +190 -0
  25. package/templates/_shared/.env.example +5 -0
  26. package/templates/_shared/package.json +17 -0
  27. package/templates/code-interpreter/agent.ts +27 -0
  28. package/templates/code-interpreter/client.tsx +2 -0
  29. package/templates/dispatch-center/agent.ts +1536 -0
  30. package/templates/dispatch-center/client.tsx +504 -0
  31. package/templates/embedded-assets/agent.ts +49 -0
  32. package/templates/embedded-assets/client.tsx +2 -0
  33. package/templates/embedded-assets/knowledge.json +20 -0
  34. package/templates/health-assistant/agent.ts +160 -0
  35. package/templates/health-assistant/client.tsx +2 -0
  36. package/templates/infocom-adventure/agent.ts +164 -0
  37. package/templates/infocom-adventure/client.tsx +299 -0
  38. package/templates/math-buddy/agent.ts +21 -0
  39. package/templates/math-buddy/client.tsx +2 -0
  40. package/templates/memory-agent/agent.ts +74 -0
  41. package/templates/memory-agent/client.tsx +2 -0
  42. package/templates/night-owl/agent.ts +98 -0
  43. package/templates/night-owl/client.tsx +28 -0
  44. package/templates/personal-finance/agent.ts +26 -0
  45. package/templates/personal-finance/client.tsx +2 -0
  46. package/templates/simple/agent.ts +6 -0
  47. package/templates/simple/client.tsx +2 -0
  48. package/templates/smart-research/agent.ts +164 -0
  49. package/templates/smart-research/client.tsx +2 -0
  50. package/templates/support/README.md +62 -0
  51. package/templates/support/agent.ts +19 -0
  52. package/templates/support/client.tsx +2 -0
  53. package/templates/travel-concierge/agent.ts +29 -0
  54. package/templates/travel-concierge/client.tsx +2 -0
  55. package/templates/web-researcher/agent.ts +17 -0
  56. package/templates/web-researcher/client.tsx +2 -0
  57. package/ui/_components/app.tsx +37 -0
  58. package/ui/_components/chat_view.tsx +36 -0
  59. package/ui/_components/controls.tsx +32 -0
  60. package/ui/_components/error_banner.tsx +18 -0
  61. package/ui/_components/message_bubble.tsx +21 -0
  62. package/ui/_components/message_list.tsx +61 -0
  63. package/ui/_components/state_indicator.tsx +17 -0
  64. package/ui/_components/thinking_indicator.tsx +19 -0
  65. package/ui/_components/tool_call_block.tsx +110 -0
  66. package/ui/_components/tool_icons.tsx +101 -0
  67. package/ui/_components/transcript.tsx +20 -0
  68. package/ui/audio.ts +170 -0
  69. package/ui/components.ts +49 -0
  70. package/ui/components_mod.ts +37 -0
  71. package/ui/mod.ts +48 -0
  72. package/ui/mount.tsx +112 -0
  73. package/ui/mount_context.ts +19 -0
  74. package/ui/session.ts +456 -0
  75. package/ui/session_mod.ts +27 -0
  76. package/ui/signals.ts +111 -0
  77. package/ui/types.ts +50 -0
  78. package/ui/worklets/capture-processor.js +62 -0
  79. package/ui/worklets/playback-processor.js +110 -0
package/sdk/kv.ts ADDED
@@ -0,0 +1,183 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ /**
3
+ * Key-value storage interface and in-memory implementation.
4
+ *
5
+ * @module
6
+ */
7
+
8
+ /**
9
+ * A single key-value entry returned by {@linkcode Kv.list}.
10
+ *
11
+ * @typeParam T The type of the stored value. Defaults to `unknown`.
12
+ */
13
+ export type KvEntry<T = unknown> = {
14
+ /** The key under which the value is stored. */
15
+ key: string;
16
+ /** The deserialized value. */
17
+ value: T;
18
+ };
19
+
20
+ /**
21
+ * Options for listing keys from the KV store.
22
+ *
23
+ * Used with {@linkcode Kv.list} to control result ordering and pagination.
24
+ */
25
+ export type KvListOptions = {
26
+ /** Maximum number of entries to return. */
27
+ limit?: number;
28
+ /** Return entries in reverse key order. */
29
+ reverse?: boolean;
30
+ };
31
+
32
+ /**
33
+ * Async key-value store interface used by agents.
34
+ *
35
+ * Agents access the KV store via {@linkcode ToolContext.kv} or
36
+ * {@linkcode HookContext.kv}. Values are JSON-serialized and stored as
37
+ * strings with an optional TTL.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * // Inside a tool execute function:
42
+ * const myTool = {
43
+ * description: "Save and retrieve data",
44
+ * execute: async (_args: unknown, ctx: { kv: Kv }) => {
45
+ * await ctx.kv.set("user:name", "Alice", { expireIn: 60_000 });
46
+ * const name = await ctx.kv.get<string>("user:name");
47
+ * return name; // "Alice"
48
+ * },
49
+ * };
50
+ * ```
51
+ */
52
+ export type Kv = {
53
+ /**
54
+ * Get a value by key, or `null` if not found.
55
+ *
56
+ * @typeParam T The expected type of the stored value.
57
+ * @param key The key to look up.
58
+ * @returns The deserialized value, or `null` if the key does not exist
59
+ * or has expired.
60
+ */
61
+ get<T = unknown>(key: string): Promise<T | null>;
62
+ /**
63
+ * Set a value, optionally with a TTL in milliseconds.
64
+ *
65
+ * @param key The key to store the value under.
66
+ * @param value The value to store. Must be JSON-serializable.
67
+ * @param options Optional settings.
68
+ * @param options.expireIn Time-to-live in milliseconds. The entry is
69
+ * automatically removed after this duration.
70
+ * @throws {Error} If the serialized value exceeds 65,536 bytes.
71
+ */
72
+ set(key: string, value: unknown, options?: { expireIn?: number }): Promise<void>;
73
+ /**
74
+ * Delete a key.
75
+ *
76
+ * @param key The key to remove. No-op if the key does not exist.
77
+ */
78
+ delete(key: string): Promise<void>;
79
+ /**
80
+ * List entries whose keys start with the given prefix.
81
+ *
82
+ * Results are sorted by key in ascending lexicographic order by default.
83
+ *
84
+ * @typeParam T The expected type of the stored values.
85
+ * @param prefix Key prefix to filter by. Use `""` to list all entries.
86
+ * @param options Optional pagination and ordering settings.
87
+ * @returns An array of matching {@linkcode KvEntry} objects.
88
+ */
89
+ list<T = unknown>(prefix: string, options?: KvListOptions): Promise<KvEntry<T>[]>;
90
+ };
91
+
92
+ export const MAX_VALUE_SIZE = 65_536;
93
+
94
+ /** Sort entries by key and apply reverse/limit options. Mutates the array. */
95
+ export function sortAndPaginate<T extends { key: string }>(
96
+ entries: T[],
97
+ options?: { limit?: number; reverse?: boolean },
98
+ ): T[] {
99
+ entries.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
100
+ if (options?.reverse) entries.reverse();
101
+ if (options?.limit && options.limit > 0) {
102
+ entries.length = Math.min(entries.length, options.limit);
103
+ }
104
+ return entries;
105
+ }
106
+
107
+ /**
108
+ * Create an in-memory KV store (useful for testing and local development).
109
+ *
110
+ * Data is stored in a plain `Map` and does not persist across restarts.
111
+ * TTL expiration is checked lazily on reads and list operations.
112
+ *
113
+ * @returns A {@linkcode Kv} instance backed by in-memory storage.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * import { createMemoryKv } from "./kv.ts";
118
+ *
119
+ * const kv = createMemoryKv();
120
+ * await kv.set("greeting", "hello");
121
+ * const value = await kv.get<string>("greeting"); // "hello"
122
+ * ```
123
+ *
124
+ * @example With TTL
125
+ * ```ts
126
+ * import { createMemoryKv } from "./kv.ts";
127
+ *
128
+ * const kv = createMemoryKv();
129
+ * await kv.set("temp", "expires soon", { expireIn: 5000 });
130
+ * ```
131
+ */
132
+ export function createMemoryKv(): Kv {
133
+ const store = new Map<string, { raw: string; expiresAt?: number }>();
134
+
135
+ function isExpired(entry: { expiresAt?: number }): boolean {
136
+ return entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
137
+ }
138
+
139
+ return {
140
+ get<T = unknown>(key: string): Promise<T | null> {
141
+ const entry = store.get(key);
142
+ if (!entry || isExpired(entry)) {
143
+ if (entry) store.delete(key);
144
+ return Promise.resolve(null);
145
+ }
146
+ return Promise.resolve(JSON.parse(entry.raw) as T);
147
+ },
148
+
149
+ set(key: string, value: unknown, options?: { expireIn?: number }): Promise<void> {
150
+ const raw = JSON.stringify(value);
151
+ if (raw.length > MAX_VALUE_SIZE) {
152
+ throw new Error(`Value exceeds max size of ${MAX_VALUE_SIZE} bytes`);
153
+ }
154
+ const expireIn = options?.expireIn;
155
+ const entry: { raw: string; expiresAt?: number } = { raw };
156
+ if (expireIn && expireIn > 0) {
157
+ entry.expiresAt = Date.now() + expireIn;
158
+ }
159
+ store.set(key, entry);
160
+ return Promise.resolve();
161
+ },
162
+
163
+ delete(key: string): Promise<void> {
164
+ store.delete(key);
165
+ return Promise.resolve();
166
+ },
167
+
168
+ list<T = unknown>(prefix: string, options?: KvListOptions): Promise<KvEntry<T>[]> {
169
+ const now = Date.now();
170
+ const entries: KvEntry<T>[] = [];
171
+ for (const [key, entry] of store) {
172
+ if (entry.expiresAt && entry.expiresAt <= now) {
173
+ store.delete(key);
174
+ continue;
175
+ }
176
+ if (key.startsWith(prefix)) {
177
+ entries.push({ key, value: JSON.parse(entry.raw) as T });
178
+ }
179
+ }
180
+ return Promise.resolve(sortAndPaginate(entries, options));
181
+ },
182
+ };
183
+ }
package/sdk/mod.ts ADDED
@@ -0,0 +1,35 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ /**
3
+ * AAI SDK — build voice agents powered by STT, LLM, and TTS.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { defineAgent } from "aai";
8
+ * import { z } from "zod";
9
+ *
10
+ * export default defineAgent({
11
+ * name: "my-agent",
12
+ * instructions: "You are a helpful voice assistant.",
13
+ * tools: {
14
+ * greet: {
15
+ * description: "Greet the user by name",
16
+ * parameters: z.object({ name: z.string() }),
17
+ * execute: ({ name }) => `Hello, ${name}!`,
18
+ * },
19
+ * },
20
+ * });
21
+ * ```
22
+ *
23
+ * @module
24
+ */
25
+
26
+ export { defineAgent } from "./define_agent.ts";
27
+ export type {
28
+ AgentOptions,
29
+ BeforeStepResult,
30
+ HookContext,
31
+ Message,
32
+ PipelineMode,
33
+ ToolContext,
34
+ ToolDef,
35
+ } from "./types.ts";
@@ -0,0 +1,313 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ /**
3
+ * WebSocket wire-format types shared by server and client.
4
+ *
5
+ * Note: this module is for internal use only and should not be used directly.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import { z } from "zod";
11
+ import type { WorkerConfig } from "./_internal_types.ts";
12
+ import type { Message, StepInfo } from "./types.ts";
13
+
14
+ /**
15
+ * Current protocol version for client-server compatibility checks.
16
+ *
17
+ * Increment this when making breaking changes to the wire protocol.
18
+ */
19
+ export const PROTOCOL_VERSION = 1;
20
+
21
+ /**
22
+ * Default sample rate for speech-to-text audio in Hz.
23
+ *
24
+ * This is the sample rate expected by the STT provider (AssemblyAI).
25
+ */
26
+ export const DEFAULT_STT_SAMPLE_RATE = 16_000;
27
+
28
+ /**
29
+ * Default sample rate for text-to-speech audio in Hz.
30
+ *
31
+ * This is the sample rate produced by the TTS provider (Rime).
32
+ */
33
+ export const DEFAULT_TTS_SAMPLE_RATE = 24_000;
34
+
35
+ /**
36
+ * Audio codec identifier used in the wire protocol.
37
+ *
38
+ * All audio frames are 16-bit signed PCM, little-endian, mono.
39
+ */
40
+ export const AUDIO_FORMAT = "pcm16" as const;
41
+
42
+ /**
43
+ * Binary audio frame specification. All audio exchanged over the WebSocket as
44
+ * binary frames MUST conform to this spec. Any change here is a breaking
45
+ * protocol change.
46
+ */
47
+ const _bitsPerSample = 16 as const;
48
+ const _channels = 1 as const;
49
+
50
+ /** Specification for binary audio frames exchanged over WebSocket. */
51
+ export const AudioFrameSpec = {
52
+ /** Audio codec identifier sent in the `ready` message. */
53
+ format: AUDIO_FORMAT,
54
+ /** Signed 16-bit integer samples. */
55
+ bitsPerSample: _bitsPerSample,
56
+ /** Little-endian byte order. */
57
+ endianness: "little" as const,
58
+ /** Mono audio. */
59
+ channels: _channels,
60
+ /** Bytes per sample — derived from bitsPerSample and channels. */
61
+ bytesPerSample: (_bitsPerSample / 8) * _channels,
62
+ } as const;
63
+
64
+ /**
65
+ * KV operation request sent from the worker to the host via postMessage RPC.
66
+ *
67
+ * This is a discriminated union on the `op` field, representing the four
68
+ * key-value store operations available to sandboxed agent workers.
69
+ */
70
+ export type KvRequest =
71
+ | { op: "get"; key: string }
72
+ | { op: "set"; key: string; value: string; ttl?: number | undefined }
73
+ | { op: "del"; key: string }
74
+ | {
75
+ op: "list";
76
+ prefix: string;
77
+ limit?: number | undefined;
78
+ reverse?: boolean | undefined;
79
+ };
80
+
81
+ /** Zod schema for {@linkcode KvRequest}. */
82
+ export const KvRequestBaseSchema: z.ZodType<KvRequest> = z.discriminatedUnion("op", [
83
+ z.object({ op: z.literal("get"), key: z.string().min(1) }),
84
+ z.object({
85
+ op: z.literal("set"),
86
+ key: z.string().min(1),
87
+ value: z.string(),
88
+ ttl: z.number().int().positive().optional(),
89
+ }),
90
+ z.object({ op: z.literal("del"), key: z.string().min(1) }),
91
+ z.object({
92
+ op: z.literal("list"),
93
+ prefix: z.string(),
94
+ limit: z.number().int().positive().optional(),
95
+ reverse: z.boolean().optional(),
96
+ }),
97
+ ]);
98
+
99
+ // ─── Timeout constants ─────────────────────────────────────────────────────
100
+
101
+ /** Default timeout for agent lifecycle hooks (onConnect, onTurn, etc). */
102
+ export const HOOK_TIMEOUT_MS = 5_000;
103
+
104
+ /** Default timeout for tool execution in the worker. */
105
+ export const TOOL_EXECUTION_TIMEOUT_MS = 30_000;
106
+
107
+ // ─── Error codes ───────────────────────────────────────────────────────────
108
+
109
+ /** Error codes for categorizing session errors on the wire. */
110
+ export type SessionErrorCode =
111
+ | "stt"
112
+ | "llm"
113
+ | "tts"
114
+ | "tool"
115
+ | "protocol"
116
+ | "connection"
117
+ | "audio"
118
+ | "internal";
119
+
120
+ /** Zod schema for {@linkcode SessionErrorCode}. */
121
+ export const SessionErrorCodeSchema: z.ZodType<SessionErrorCode> = z.enum([
122
+ "stt",
123
+ "llm",
124
+ "tts",
125
+ "tool",
126
+ "protocol",
127
+ "connection",
128
+ "audio",
129
+ "internal",
130
+ ]);
131
+
132
+ // ─── Client events ─────────────────────────────────────────────────────────
133
+
134
+ /**
135
+ * Discriminated union of all server→client session events.
136
+ *
137
+ * Sent via a single `event()` RPC method instead of one method per type.
138
+ */
139
+ export type ClientEvent =
140
+ | { type: "speech_started" }
141
+ | { type: "speech_stopped" }
142
+ | { type: "transcript"; text: string; isFinal: false }
143
+ | {
144
+ type: "transcript";
145
+ text: string;
146
+ isFinal: true;
147
+ turnOrder?: number | undefined;
148
+ }
149
+ | { type: "turn"; text: string; turnOrder?: number | undefined }
150
+ | { type: "chat"; text: string }
151
+ | {
152
+ type: "tool_call_start";
153
+ toolCallId: string;
154
+ toolName: string;
155
+ args: Record<string, unknown>;
156
+ }
157
+ | { type: "tool_call_done"; toolCallId: string; result: string }
158
+ | { type: "tts_done" }
159
+ | { type: "cancelled" }
160
+ | { type: "reset" }
161
+ | { type: "error"; code: SessionErrorCode; message: string };
162
+
163
+ /** Zod schema for a transcript event (partial or final). */
164
+ const TranscriptEventSchema = z.object({
165
+ type: z.literal("transcript"),
166
+ text: z.string(),
167
+ isFinal: z.boolean(),
168
+ turnOrder: z.number().int().nonnegative().optional(),
169
+ });
170
+
171
+ /** Zod schema for {@linkcode ClientEvent}. */
172
+ export const ClientEventSchema: z.ZodType<ClientEvent> = z.discriminatedUnion("type", [
173
+ z.object({ type: z.literal("speech_started") }),
174
+ z.object({ type: z.literal("speech_stopped") }),
175
+ TranscriptEventSchema,
176
+ z.object({
177
+ type: z.literal("turn"),
178
+ text: z.string(),
179
+ turnOrder: z.number().int().nonnegative().optional(),
180
+ }),
181
+ z.object({ type: z.literal("chat"), text: z.string() }),
182
+ z.object({
183
+ type: z.literal("tool_call_start"),
184
+ toolCallId: z.string(),
185
+ toolName: z.string(),
186
+ args: z.record(z.string(), z.unknown()),
187
+ }),
188
+ z.object({
189
+ type: z.literal("tool_call_done"),
190
+ toolCallId: z.string(),
191
+ result: z.string().max(4000),
192
+ }),
193
+ z.object({ type: z.literal("tts_done") }),
194
+ z.object({ type: z.literal("cancelled") }),
195
+ z.object({ type: z.literal("reset") }),
196
+ z.object({
197
+ type: z.literal("error"),
198
+ code: SessionErrorCodeSchema,
199
+ message: z.string(),
200
+ }),
201
+ ]);
202
+
203
+ /**
204
+ * Typed interface for pushing session events to a connected client.
205
+ *
206
+ * For WebSocket sessions this sends JSON text frames and binary audio frames.
207
+ */
208
+ export interface ClientSink {
209
+ /** Whether the underlying connection is open and accepting calls. */
210
+ readonly open: boolean;
211
+ /** Push a session event to the client. */
212
+ event(e: ClientEvent): void;
213
+ /** Send a single TTS audio chunk to the client. */
214
+ playAudioChunk(chunk: Uint8Array): void;
215
+ /** Signal that TTS audio is complete. */
216
+ playAudioDone(): void;
217
+ }
218
+
219
+ // ─── WebSocket message types ────────────────────────────────────────────────
220
+
221
+ /** Supported audio formats for the wire protocol. */
222
+ export type AudioFormatId = "pcm16";
223
+
224
+ /** Protocol-level session config returned to the client on connect. */
225
+ export type ReadyConfig = {
226
+ protocolVersion: number;
227
+ audioFormat: AudioFormatId;
228
+ sampleRate: number;
229
+ ttsSampleRate: number;
230
+ mode?: "s2s";
231
+ };
232
+
233
+ /** Client→server text messages (binary frames carry raw PCM16 audio). */
234
+ export type ClientMessage =
235
+ | { type: "audio_ready" }
236
+ | { type: "cancel" }
237
+ | { type: "reset" }
238
+ | {
239
+ type: "history";
240
+ messages: readonly { role: "user" | "assistant"; text: string }[];
241
+ };
242
+
243
+ /** Server→client text messages (binary frames carry raw PCM16 audio). */
244
+ export type ServerMessage =
245
+ | ({ type: "config" } & ReadyConfig)
246
+ | { type: "audio_done" }
247
+ | ClientEvent;
248
+
249
+ /** Zod schema for {@linkcode ClientMessage}. */
250
+ export const ClientMessageSchema: z.ZodType<ClientMessage> = z.discriminatedUnion("type", [
251
+ z.object({ type: z.literal("audio_ready") }),
252
+ z.object({ type: z.literal("cancel") }),
253
+ z.object({ type: z.literal("reset") }),
254
+ z.object({
255
+ type: z.literal("history"),
256
+ messages: z
257
+ .array(
258
+ z.object({
259
+ role: z.enum(["user", "assistant"]),
260
+ text: z.string().max(100_000),
261
+ }),
262
+ )
263
+ .max(200),
264
+ }),
265
+ ]);
266
+
267
+ // ─── Worker RPC interfaces ─────────────────────────────────────────────────
268
+
269
+ /**
270
+ * API shape the host process exposes to the sandboxed worker.
271
+ *
272
+ * Since workers run with all permissions denied, they use this interface
273
+ * to proxy network requests and KV operations back to the host.
274
+ */
275
+ export type HostApi = {
276
+ fetch(req: {
277
+ url: string;
278
+ method: string;
279
+ headers: Readonly<Record<string, string>>;
280
+ body: string | null;
281
+ }): Promise<{
282
+ status: number;
283
+ statusText: string;
284
+ headers: Record<string, string>;
285
+ body: string;
286
+ }>;
287
+ kv(req: KvRequest): Promise<{ result: unknown }>;
288
+ vectorSearch(req: { query: string; topK: number }): Promise<string>;
289
+ };
290
+
291
+ /** Combined turn configuration resolved from the worker before a turn starts. */
292
+ export type TurnConfig = {
293
+ maxSteps?: number;
294
+ activeTools?: string[];
295
+ };
296
+
297
+ /** Worker-side RPC target interface (host calls these methods). */
298
+ export interface WorkerRpcApi {
299
+ withEnv(env: Record<string, string>): WorkerRpcApi;
300
+ getConfig(): Promise<WorkerConfig>;
301
+ executeTool(
302
+ name: string,
303
+ args: Readonly<Record<string, unknown>>,
304
+ sessionId: string | undefined,
305
+ messages: readonly Message[] | undefined,
306
+ ): Promise<string>;
307
+ onConnect(sessionId: string): Promise<void>;
308
+ onDisconnect(sessionId: string): Promise<void>;
309
+ onTurn(sessionId: string, text: string): Promise<void>;
310
+ onError(sessionId: string, error: string): void;
311
+ onStep(sessionId: string, step: StepInfo): Promise<void>;
312
+ resolveTurnConfig(sessionId: string): Promise<TurnConfig | null>;
313
+ }
package/sdk/runtime.ts ADDED
@@ -0,0 +1,65 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ /**
3
+ * Pluggable interfaces for cross-runtime concerns.
4
+ *
5
+ * @module
6
+ */
7
+
8
+ import { DEFAULT_TTS_SAMPLE_RATE } from "./protocol.ts";
9
+
10
+ /** Runtime-agnostic structured logger. */
11
+ export type Logger = {
12
+ info(msg: string, ctx?: Record<string, unknown>): void;
13
+ warn(msg: string, ctx?: Record<string, unknown>): void;
14
+ error(msg: string, ctx?: Record<string, unknown>): void;
15
+ debug(msg: string, ctx?: Record<string, unknown>): void;
16
+ };
17
+
18
+ /** Runtime-agnostic session metrics. */
19
+ export type Metrics = {
20
+ sessionsTotal: { inc(labels: Record<string, string>): void };
21
+ sessionsActive: {
22
+ inc(labels: Record<string, string>): void;
23
+ dec(labels: Record<string, string>): void;
24
+ };
25
+ };
26
+
27
+ /** Console-based logger that works in all runtimes. */
28
+ export const consoleLogger: Logger = {
29
+ info(msg, ctx) {
30
+ if (ctx) console.log(msg, ctx);
31
+ else console.log(msg);
32
+ },
33
+ warn(msg, ctx) {
34
+ if (ctx) console.warn(msg, ctx);
35
+ else console.warn(msg);
36
+ },
37
+ error(msg, ctx) {
38
+ if (ctx) console.error(msg, ctx);
39
+ else console.error(msg);
40
+ },
41
+ debug(msg, ctx) {
42
+ if (ctx) console.debug(msg, ctx);
43
+ else console.debug(msg);
44
+ },
45
+ };
46
+
47
+ /** No-op metrics implementation for environments without monitoring. */
48
+ export const noopMetrics: Metrics = {
49
+ sessionsTotal: { inc() {} },
50
+ sessionsActive: { inc() {}, dec() {} },
51
+ };
52
+
53
+ /** Configuration for the AssemblyAI Speech-to-Speech connection. */
54
+ export type S2SConfig = {
55
+ wssUrl: string;
56
+ inputSampleRate: number;
57
+ outputSampleRate: number;
58
+ };
59
+
60
+ /** Default S2S configuration pointing to AssemblyAI's production endpoint. */
61
+ export const DEFAULT_S2S_CONFIG: S2SConfig = {
62
+ wssUrl: "wss://speech-to-speech.us.assemblyai.com/v1/realtime",
63
+ inputSampleRate: DEFAULT_TTS_SAMPLE_RATE,
64
+ outputSampleRate: DEFAULT_TTS_SAMPLE_RATE,
65
+ };