@alexkroman1/aai 1.0.6 → 1.2.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 (54) hide show
  1. package/.turbo/turbo-build.log +11 -11
  2. package/CHANGELOG.md +22 -0
  3. package/dist/_internal-types-CoDTiBd1.js +61 -0
  4. package/dist/host/_mock-ws.d.ts +0 -24
  5. package/dist/host/runtime-barrel.d.ts +0 -1
  6. package/dist/host/runtime-barrel.js +55 -5
  7. package/dist/host/runtime.d.ts +2 -0
  8. package/dist/host/tool-executor.d.ts +1 -0
  9. package/dist/host/ws-handler.d.ts +2 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +90 -1
  12. package/dist/sdk/allowed-hosts.d.ts +34 -0
  13. package/dist/sdk/manifest-barrel.d.ts +3 -5
  14. package/dist/sdk/manifest-barrel.js +2 -52
  15. package/dist/sdk/manifest.d.ts +2 -0
  16. package/dist/sdk/protocol.d.ts +11 -28
  17. package/dist/sdk/protocol.js +6 -3
  18. package/dist/sdk/types.d.ts +2 -0
  19. package/host/_mock-ws.ts +0 -50
  20. package/host/_test-utils.ts +1 -0
  21. package/host/runtime-barrel.ts +0 -1
  22. package/host/runtime.ts +13 -1
  23. package/host/session-ctx.test.ts +387 -0
  24. package/host/session-fixture-replay.test.ts +2 -10
  25. package/host/session.test.ts +19 -41
  26. package/host/tool-executor.test.ts +36 -0
  27. package/host/tool-executor.ts +4 -0
  28. package/host/ws-handler.ts +3 -0
  29. package/index.ts +1 -0
  30. package/package.json +1 -1
  31. package/sdk/__snapshots__/exports.test.ts.snap +79 -0
  32. package/sdk/__snapshots__/schema-shapes.test.ts.snap +187 -0
  33. package/sdk/_test-matchers.test.ts +75 -0
  34. package/sdk/_test-matchers.ts +73 -0
  35. package/sdk/allowed-hosts.test.ts +236 -0
  36. package/sdk/allowed-hosts.ts +113 -0
  37. package/sdk/exports.test.ts +31 -0
  38. package/sdk/manifest-barrel.ts +13 -7
  39. package/sdk/manifest.test.ts +103 -2
  40. package/sdk/manifest.ts +19 -0
  41. package/sdk/protocol-compat.test.ts +0 -6
  42. package/sdk/protocol-snapshot.test.ts +7 -5
  43. package/sdk/protocol.test.ts +107 -21
  44. package/sdk/protocol.ts +7 -15
  45. package/sdk/schema-alignment.test.ts +1 -27
  46. package/sdk/schema-shapes.test.ts +103 -0
  47. package/sdk/tsconfig.json +1 -1
  48. package/sdk/types.test.ts +56 -1
  49. package/sdk/types.ts +2 -0
  50. package/sdk/ws-upgrade.test.ts +8 -8
  51. package/tsconfig.build.json +8 -1
  52. package/tsconfig.json +1 -1
  53. package/vitest.config.ts +1 -0
  54. package/dist/system-prompt-nik_iavo.js +0 -92
@@ -1,15 +1,18 @@
1
- import { describe, expect, test } from "vitest";
1
+ import fc from "fast-check";
2
+ import { describe, expect, expectTypeOf, test } from "vitest";
3
+ import { z } from "zod";
2
4
  import {
3
5
  DEFAULT_STT_SAMPLE_RATE,
4
6
  DEFAULT_TTS_SAMPLE_RATE,
5
7
  TOOL_EXECUTION_TIMEOUT_MS,
6
8
  } from "./constants.ts";
9
+ import type { ClientEvent, ServerMessage } from "./protocol.ts";
7
10
  import {
8
- AUDIO_FORMAT,
9
11
  buildReadyConfig,
10
12
  ClientEventSchema,
11
13
  ClientMessageSchema,
12
14
  KvRequestSchema,
15
+ lenientParse,
13
16
  SessionErrorCodeSchema,
14
17
  } from "./protocol.ts";
15
18
 
@@ -22,10 +25,6 @@ describe("protocol constants", () => {
22
25
  expect(DEFAULT_TTS_SAMPLE_RATE).toBe(24_000);
23
26
  });
24
27
 
25
- test('AUDIO_FORMAT is "pcm16"', () => {
26
- expect(AUDIO_FORMAT).toBe("pcm16");
27
- });
28
-
29
28
  test("TOOL_EXECUTION_TIMEOUT_MS is 30000", () => {
30
29
  expect(TOOL_EXECUTION_TIMEOUT_MS).toBe(30_000);
31
30
  });
@@ -91,32 +90,23 @@ describe("SessionErrorCodeSchema", () => {
91
90
 
92
91
  describe("ClientEventSchema", () => {
93
92
  test("accepts speech_started", () => {
94
- const result = ClientEventSchema.safeParse({ type: "speech_started" });
95
- expect(result.success).toBe(true);
93
+ expect({ type: "speech_started" }).toBeValidClientEvent();
96
94
  });
97
95
 
98
96
  test("accepts user_transcript", () => {
99
- const result = ClientEventSchema.safeParse({
100
- type: "user_transcript",
101
- text: "hello world",
102
- });
103
- expect(result.success).toBe(true);
97
+ expect({ type: "user_transcript", text: "hello world" }).toBeValidClientEvent();
104
98
  });
105
99
 
106
100
  test("accepts error event", () => {
107
- const result = ClientEventSchema.safeParse({
101
+ expect({
108
102
  type: "error",
109
103
  code: "internal",
110
104
  message: "something went wrong",
111
- });
112
- expect(result.success).toBe(true);
105
+ }).toBeValidClientEvent();
113
106
  });
114
107
 
115
108
  test("rejects unknown type", () => {
116
- const result = ClientEventSchema.safeParse({
117
- type: "unknown_event_type",
118
- });
119
- expect(result.success).toBe(false);
109
+ expect({ type: "unknown_event_type" }).not.toBeValidClientEvent();
120
110
  });
121
111
  });
122
112
 
@@ -156,7 +146,7 @@ describe("buildReadyConfig", () => {
156
146
  test("builds config from sample rates", () => {
157
147
  const config = buildReadyConfig({ inputSampleRate: 16_000, outputSampleRate: 24_000 });
158
148
  expect(config).toEqual({
159
- audioFormat: AUDIO_FORMAT,
149
+ audioFormat: "pcm16",
160
150
  sampleRate: 16_000,
161
151
  ttsSampleRate: 24_000,
162
152
  });
@@ -168,3 +158,99 @@ describe("buildReadyConfig", () => {
168
158
  expect(config.ttsSampleRate).toBe(48_000);
169
159
  });
170
160
  });
161
+
162
+ // ── Property-based tests ─────────────────────────────────────────────────
163
+
164
+ describe("property: lenientParse", () => {
165
+ test("never throws on arbitrary input", () => {
166
+ fc.assert(
167
+ fc.property(fc.anything(), (input) => {
168
+ const result = lenientParse(ClientEventSchema, input);
169
+ expect(result).toHaveProperty("ok");
170
+ }),
171
+ );
172
+ });
173
+
174
+ test("valid ClientEvents round-trip through parse", () => {
175
+ const errorCodes = [
176
+ "stt",
177
+ "llm",
178
+ "tts",
179
+ "tool",
180
+ "protocol",
181
+ "connection",
182
+ "audio",
183
+ "internal",
184
+ ] as const;
185
+
186
+ const speechStartedArb = fc.constant({ type: "speech_started" as const });
187
+
188
+ const userTranscriptArb = fc.record({
189
+ type: fc.constant("user_transcript" as const),
190
+ text: fc.string(),
191
+ });
192
+
193
+ const errorEventArb = fc.record({
194
+ type: fc.constant("error" as const),
195
+ code: fc.constantFrom(...errorCodes),
196
+ message: fc.string(),
197
+ });
198
+
199
+ const clientEventArb = fc.oneof(speechStartedArb, userTranscriptArb, errorEventArb);
200
+
201
+ fc.assert(
202
+ fc.property(clientEventArb, (event) => {
203
+ const result = lenientParse(ClientEventSchema, event);
204
+ expect(result.ok).toBe(true);
205
+ }),
206
+ );
207
+ });
208
+
209
+ test("objects without type field are malformed", () => {
210
+ const noTypeArb = fc.object().filter((obj) => !("type" in obj));
211
+
212
+ fc.assert(
213
+ fc.property(noTypeArb, (obj) => {
214
+ const result = lenientParse(ClientEventSchema, obj);
215
+ expect(result.ok).toBe(false);
216
+ if (!result.ok) {
217
+ expect(result.malformed).toBe(true);
218
+ }
219
+ }),
220
+ );
221
+ });
222
+ });
223
+
224
+ describe("protocol type contracts", () => {
225
+ test("ClientEvent narrows on user_transcript discriminant", () => {
226
+ type UserTranscript = Extract<ClientEvent, { type: "user_transcript" }>;
227
+ expectTypeOf<UserTranscript>().toHaveProperty("text");
228
+ expectTypeOf<UserTranscript["text"]>().toBeString();
229
+ });
230
+
231
+ test("ClientEvent narrows on tool_call discriminant", () => {
232
+ type ToolCall = Extract<ClientEvent, { type: "tool_call" }>;
233
+ expectTypeOf<ToolCall>().toHaveProperty("toolCallId");
234
+ expectTypeOf<ToolCall>().toHaveProperty("toolName");
235
+ expectTypeOf<ToolCall>().toHaveProperty("args");
236
+ });
237
+
238
+ test("ClientEvent narrows on error discriminant", () => {
239
+ type ErrorEvent = Extract<ClientEvent, { type: "error" }>;
240
+ expectTypeOf<ErrorEvent>().toHaveProperty("code");
241
+ expectTypeOf<ErrorEvent>().toHaveProperty("message");
242
+ });
243
+
244
+ test("ServerMessage has type property on all variants", () => {
245
+ expectTypeOf<ServerMessage>().toHaveProperty("type");
246
+ });
247
+
248
+ test("lenientParse returns ok/error discriminated union", () => {
249
+ const schema = z.object({ type: z.literal("test"), value: z.number() });
250
+ type Parsed = z.infer<typeof schema>;
251
+ const result = lenientParse(schema, {});
252
+ expectTypeOf(result).toEqualTypeOf<
253
+ { ok: true; data: Parsed } | { ok: false; malformed: boolean; error: string }
254
+ >();
255
+ });
256
+ });
package/sdk/protocol.ts CHANGED
@@ -14,7 +14,7 @@ import { MAX_TOOL_RESULT_CHARS } from "./constants.ts";
14
14
  *
15
15
  * All audio frames are 16-bit signed PCM, little-endian, mono.
16
16
  */
17
- export const AUDIO_FORMAT = "pcm16";
17
+ const AUDIO_FORMAT = "pcm16";
18
18
 
19
19
  /**
20
20
  * Minimal envelope schema for two-phase message parsing.
@@ -24,7 +24,7 @@ export const AUDIO_FORMAT = "pcm16";
24
24
  * *unrecognised* type (safe to ignore during rolling upgrades) or genuinely
25
25
  * malformed (should be warned about).
26
26
  */
27
- export const MessageEnvelopeSchema = z.object({ type: z.string() }).passthrough();
27
+ const MessageEnvelopeSchema = z.object({ type: z.string() }).passthrough();
28
28
 
29
29
  /**
30
30
  * Two-phase message parse: tries the strict schema first, then falls back to
@@ -128,6 +128,11 @@ export const ClientEventSchema = z.discriminatedUnion("type", [
128
128
  ev("reset"),
129
129
  ev("idle_timeout"),
130
130
  z.object({ type: z.literal("error"), code: SessionErrorCodeSchema, message: z.string() }),
131
+ z.object({
132
+ type: z.literal("custom_event"),
133
+ event: z.string().min(1),
134
+ data: z.unknown(),
135
+ }),
131
136
  ]);
132
137
 
133
138
  /** Discriminated union of all server→client session events. */
@@ -151,9 +156,6 @@ export interface ClientSink {
151
156
 
152
157
  // ─── WebSocket message types ────────────────────────────────────────────────
153
158
 
154
- /** Supported audio formats for the wire protocol. */
155
- export type AudioFormatId = "pcm16";
156
-
157
159
  /** Zod schema for {@link ReadyConfig}. */
158
160
  export const ReadyConfigSchema = z.object({
159
161
  audioFormat: z.enum(["pcm16"]),
@@ -211,13 +213,3 @@ export function buildReadyConfig(s2sConfig: {
211
213
  ttsSampleRate: s2sConfig.outputSampleRate,
212
214
  };
213
215
  }
214
-
215
- // ─── Worker RPC interfaces ─────────────────────────────────────────────────
216
-
217
- /** Zod schema for {@link TurnConfig}. */
218
- export const TurnConfigSchema = z.object({
219
- maxSteps: z.number().int().positive().optional(),
220
- });
221
-
222
- /** Combined turn configuration resolved from the worker before a turn starts. */
223
- export type TurnConfig = z.infer<typeof TurnConfigSchema>;
@@ -10,12 +10,7 @@
10
10
  import { describe, expect, expectTypeOf, test } from "vitest";
11
11
  import type { z } from "zod";
12
12
  import { type AgentConfig, AgentConfigSchema, ToolSchemaSchema } from "./_internal-types.ts";
13
- import {
14
- type ReadyConfig,
15
- ReadyConfigSchema,
16
- type TurnConfig,
17
- TurnConfigSchema,
18
- } from "./protocol.ts";
13
+ import { type ReadyConfig, ReadyConfigSchema } from "./protocol.ts";
19
14
  import { type BuiltinTool, BuiltinToolSchema, type ToolChoice, ToolChoiceSchema } from "./types.ts";
20
15
 
21
16
  // ── AgentConfigSchema ────────────────────────────────────────────────────
@@ -105,27 +100,6 @@ describe("ToolSchemaSchema", () => {
105
100
  });
106
101
  });
107
102
 
108
- // ── TurnConfigSchema ─────────────────────────────────────────────────────
109
-
110
- describe("TurnConfigSchema", () => {
111
- test("accepts empty config", () => {
112
- expect(TurnConfigSchema.parse({})).toEqual({});
113
- });
114
-
115
- test("accepts full config", () => {
116
- const full: TurnConfig = { maxSteps: 3 };
117
- expect(TurnConfigSchema.parse(full)).toEqual(full);
118
- });
119
-
120
- test("rejects non-positive maxSteps", () => {
121
- expect(TurnConfigSchema.safeParse({ maxSteps: 0 }).success).toBe(false);
122
- });
123
-
124
- test("type derived from schema matches TurnConfig", () => {
125
- expectTypeOf<z.infer<typeof TurnConfigSchema>>().toEqualTypeOf<TurnConfig>();
126
- });
127
- });
128
-
129
103
  // ── ReadyConfigSchema ────────────────────────────────────────────────────
130
104
 
131
105
  describe("ReadyConfigSchema", () => {
@@ -0,0 +1,103 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+ /**
3
+ * Schema shape snapshot tests for public Zod schemas.
4
+ *
5
+ * These tests snapshot the field names of public schemas exported from
6
+ * `@alexkroman1/aai/protocol` and `@alexkroman1/aai/manifest`. A breaking
7
+ * snapshot signals a wire-format change (field additions, removals, or renames)
8
+ * that may require a changeset and protocol-version bump.
9
+ *
10
+ * For discriminated unions (ClientEventSchema, ServerMessageSchema,
11
+ * ClientMessageSchema), each option's shape keys are keyed by its
12
+ * discriminator value. For object schemas (AgentConfigSchema,
13
+ * ToolSchemaSchema), the top-level shape keys are snapshotted directly.
14
+ */
15
+ import { describe, expect, test } from "vitest";
16
+ import { AgentConfigSchema, ToolSchemaSchema } from "./_internal-types.ts";
17
+ import {
18
+ ClientEventSchema,
19
+ ClientMessageSchema,
20
+ KvDelSchema,
21
+ KvGetSchema,
22
+ KvSetSchema,
23
+ ReadyConfigSchema,
24
+ ServerMessageSchema,
25
+ } from "./protocol.ts";
26
+
27
+ // ── Helpers ──────────────────────────────────────────────────────────────
28
+
29
+ type ZodObjectLike = { shape: Record<string, unknown> };
30
+
31
+ /**
32
+ * Extract shape keys (field names) from a ZodObject-like schema.
33
+ */
34
+ function shapeKeys(schema: ZodObjectLike): string[] {
35
+ return Object.keys(schema.shape).sort();
36
+ }
37
+
38
+ /**
39
+ * Extract a map of { discriminatorValue → sorted shape keys } from a
40
+ * ZodDiscriminatedUnion. The discriminator literal may be a single value
41
+ * or an array (Zod v4 stores it as `.values`).
42
+ */
43
+ function discriminatedUnionShapes(schema: {
44
+ options: Array<{ shape: Record<string, unknown> }>;
45
+ }): Record<string, string[]> {
46
+ const result: Record<string, string[]> = {};
47
+ for (const option of schema.options) {
48
+ const typeSchema = option.shape?.type as
49
+ | { _def?: { value?: string; values?: string[] } }
50
+ | undefined;
51
+ const def = typeSchema?._def;
52
+ // Zod v4 uses `.values` (array); Zod v3 uses `.value` (scalar)
53
+ const raw = def?.values ?? def?.value;
54
+ const discriminatorValue = Array.isArray(raw) ? raw[0] : raw;
55
+ const key = String(discriminatorValue ?? "unknown");
56
+ result[key] = Object.keys(option.shape).sort();
57
+ }
58
+ return result;
59
+ }
60
+
61
+ // ── Protocol schemas ─────────────────────────────────────────────────────
62
+
63
+ describe("protocol schema shapes", () => {
64
+ test("ClientEventSchema option shapes", () => {
65
+ expect(discriminatedUnionShapes(ClientEventSchema)).toMatchSnapshot();
66
+ });
67
+
68
+ test("ServerMessageSchema option shapes", () => {
69
+ expect(discriminatedUnionShapes(ServerMessageSchema)).toMatchSnapshot();
70
+ });
71
+
72
+ test("ClientMessageSchema option shapes", () => {
73
+ expect(discriminatedUnionShapes(ClientMessageSchema)).toMatchSnapshot();
74
+ });
75
+
76
+ test("ReadyConfigSchema shape", () => {
77
+ expect(shapeKeys(ReadyConfigSchema)).toMatchSnapshot();
78
+ });
79
+
80
+ test("KvGetSchema shape", () => {
81
+ expect(shapeKeys(KvGetSchema)).toMatchSnapshot();
82
+ });
83
+
84
+ test("KvSetSchema shape", () => {
85
+ expect(shapeKeys(KvSetSchema)).toMatchSnapshot();
86
+ });
87
+
88
+ test("KvDelSchema shape", () => {
89
+ expect(shapeKeys(KvDelSchema)).toMatchSnapshot();
90
+ });
91
+ });
92
+
93
+ // ── Manifest schemas ──────────────────────────────────────────────────────
94
+
95
+ describe("manifest schema shapes", () => {
96
+ test("AgentConfigSchema shape", () => {
97
+ expect(shapeKeys(AgentConfigSchema)).toMatchSnapshot();
98
+ });
99
+
100
+ test("ToolSchemaSchema shape", () => {
101
+ expect(shapeKeys(ToolSchemaSchema)).toMatchSnapshot();
102
+ });
103
+ });
package/sdk/tsconfig.json CHANGED
@@ -8,5 +8,5 @@
8
8
  "tsBuildInfoFile": "../../../.tsbuildinfo/aai-sdk.tsbuildinfo"
9
9
  },
10
10
  "include": ["./**/*.ts"],
11
- "exclude": ["**/*.test.ts", "**/*.test-d.ts"]
11
+ "exclude": ["**/*.test.ts", "**/*.test-d.ts", "**/_test-*.ts"]
12
12
  }
package/sdk/types.test.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, expect, expectTypeOf, test } from "vitest";
2
+ import { z } from "zod";
3
+ import type { AgentDef, Kv, ToolDef } from "../index.ts";
4
+ import { agent, tool } from "../index.ts";
2
5
  import { DEFAULT_GREETING, DEFAULT_SYSTEM_PROMPT } from "./types.ts";
3
6
 
4
7
  describe("constants", () => {
@@ -12,3 +15,55 @@ describe("constants", () => {
12
15
  expect(DEFAULT_GREETING.length).toBeGreaterThan(0);
13
16
  });
14
17
  });
18
+
19
+ describe("type contracts", () => {
20
+ test("agent() returns AgentDef", () => {
21
+ const def = agent({ name: "test" });
22
+ expectTypeOf(def).toEqualTypeOf<AgentDef>();
23
+ });
24
+
25
+ test("tool() infers parameter type from Zod schema", () => {
26
+ const params = z.object({ city: z.string() });
27
+ const t = tool({
28
+ description: "weather",
29
+ parameters: params,
30
+ execute: (args) => {
31
+ expectTypeOf(args).toEqualTypeOf<{ city: string }>();
32
+ return "ok";
33
+ },
34
+ });
35
+ expectTypeOf(t).toMatchTypeOf<ToolDef<typeof params>>();
36
+ });
37
+
38
+ test("tool() works without parameters", () => {
39
+ const t = tool({ description: "no params", execute: () => "ok" });
40
+ expectTypeOf(t).toMatchTypeOf<ToolDef>();
41
+ });
42
+
43
+ test("agent() accepts tools record", () => {
44
+ const t = tool({
45
+ description: "echo",
46
+ parameters: z.object({ msg: z.string() }),
47
+ execute: ({ msg }) => msg,
48
+ });
49
+ const def = agent({ name: "with-tools", tools: { echo: t } });
50
+ expectTypeOf(def).toEqualTypeOf<AgentDef>();
51
+ });
52
+
53
+ test("Kv.get returns Promise<unknown> by default", () => {
54
+ expectTypeOf<Kv["get"]>().returns.toEqualTypeOf<Promise<unknown>>();
55
+ });
56
+
57
+ test("Kv.set accepts various value types", () => {
58
+ expectTypeOf<Kv["set"]>().toBeCallableWith("key", "string-value");
59
+ expectTypeOf<Kv["set"]>().toBeCallableWith("key", 42);
60
+ expectTypeOf<Kv["set"]>().toBeCallableWith("key", { nested: true });
61
+ expectTypeOf<Kv["set"]>().returns.toEqualTypeOf<Promise<void>>();
62
+ });
63
+
64
+ test("Kv.delete accepts string or string[]", () => {
65
+ expectTypeOf<Kv["delete"]>().toBeCallableWith("single-key");
66
+ expectTypeOf<Kv["delete"]>().toBeCallableWith(["key1", "key2"]);
67
+ expectTypeOf<Kv["delete"]>().returns.toEqualTypeOf<Promise<void>>();
68
+ });
69
+ });
package/sdk/types.ts CHANGED
@@ -83,6 +83,8 @@ export type ToolContext<S = Record<string, unknown>> = {
83
83
  messages: readonly Message[];
84
84
  /** Unique identifier for the current session. Useful for correlating logs across concurrent sessions. */
85
85
  sessionId: string;
86
+ /** Push a custom event to the connected browser client. Fire-and-forget. */
87
+ send(event: string, data: unknown): void;
86
88
  };
87
89
 
88
90
  /**
@@ -1,45 +1,45 @@
1
1
  // Copyright 2025 the AAI authors. MIT license.
2
2
 
3
- import { describe, expect, it } from "vitest";
3
+ import { describe, expect, test } from "vitest";
4
4
  import { parseWsUpgradeParams } from "./ws-upgrade.ts";
5
5
 
6
6
  describe("parseWsUpgradeParams", () => {
7
- it("returns defaults for URL with no query params", () => {
7
+ test("returns defaults for URL with no query params", () => {
8
8
  const result = parseWsUpgradeParams("/websocket");
9
9
  expect(result).toEqual({ resumeFrom: undefined, skipGreeting: false });
10
10
  });
11
11
 
12
- it("extracts sessionId and sets skipGreeting", () => {
12
+ test("extracts sessionId and sets skipGreeting", () => {
13
13
  const result = parseWsUpgradeParams("/ws?sessionId=abc-123");
14
14
  expect(result.resumeFrom).toBe("abc-123");
15
15
  expect(result.skipGreeting).toBe(true);
16
16
  });
17
17
 
18
- it("sets skipGreeting when resume param is present", () => {
18
+ test("sets skipGreeting when resume param is present", () => {
19
19
  const result = parseWsUpgradeParams("/ws?resume=1");
20
20
  expect(result.resumeFrom).toBeUndefined();
21
21
  expect(result.skipGreeting).toBe(true);
22
22
  });
23
23
 
24
- it("sessionId takes precedence for resumeFrom", () => {
24
+ test("sessionId takes precedence for resumeFrom", () => {
25
25
  const result = parseWsUpgradeParams("/ws?resume=1&sessionId=sess-42");
26
26
  expect(result.resumeFrom).toBe("sess-42");
27
27
  expect(result.skipGreeting).toBe(true);
28
28
  });
29
29
 
30
- it("handles URL with no query string", () => {
30
+ test("handles URL with no query string", () => {
31
31
  const result = parseWsUpgradeParams("/websocket");
32
32
  expect(result.resumeFrom).toBeUndefined();
33
33
  expect(result.skipGreeting).toBe(false);
34
34
  });
35
35
 
36
- it("handles full URL with query params", () => {
36
+ test("handles full URL with query params", () => {
37
37
  const result = parseWsUpgradeParams("ws://localhost:3000/websocket?sessionId=s1");
38
38
  expect(result.resumeFrom).toBe("s1");
39
39
  expect(result.skipGreeting).toBe(true);
40
40
  });
41
41
 
42
- it("handles empty sessionId", () => {
42
+ test("handles empty sessionId", () => {
43
43
  const result = parseWsUpgradeParams("/ws?sessionId=");
44
44
  // Empty string from URLSearchParams.get is truthy for ?? check
45
45
  expect(result.resumeFrom).toBe("");
@@ -10,5 +10,12 @@
10
10
  "allowImportingTsExtensions": false,
11
11
  "rewriteRelativeImportExtensions": true
12
12
  },
13
- "exclude": ["node_modules", "dist", "**/*.test.ts", "**/_test-utils.ts", "vitest*.config.ts"]
13
+ "exclude": [
14
+ "node_modules",
15
+ "dist",
16
+ "**/*.test.ts",
17
+ "**/*.test-d.ts",
18
+ "**/_test-*.ts",
19
+ "vitest*.config.ts"
20
+ ]
14
21
  }
package/tsconfig.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "incremental": true,
5
5
  "tsBuildInfoFile": "../../.tsbuildinfo/aai.tsbuildinfo",
6
- "types": ["node"]
6
+ "types": ["node", "vitest/globals"]
7
7
  },
8
8
  "include": ["./**/*.ts"],
9
9
  "exclude": ["node_modules", "dist", "**/__snapshots__"]
package/vitest.config.ts CHANGED
@@ -13,5 +13,6 @@ export default defineConfig({
13
13
  "node_modules",
14
14
  "dist",
15
15
  ],
16
+ setupFiles: ["./sdk/_test-matchers.ts"],
16
17
  },
17
18
  });
@@ -1,92 +0,0 @@
1
- import { i as ToolChoiceSchema, r as DEFAULT_SYSTEM_PROMPT, t as BuiltinToolSchema } from "./types-Cfx_4QDK.js";
2
- import { z } from "zod";
3
- //#region sdk/_internal-types.ts
4
- /**
5
- * Zod schema for serializable agent configuration sent over the wire.
6
- *
7
- * This is the JSON-safe subset of the agent definition that can be
8
- * transmitted between the worker and the host process via structured clone.
9
- */
10
- const AgentConfigSchema = z.object({
11
- name: z.string().min(1),
12
- systemPrompt: z.string(),
13
- greeting: z.string(),
14
- sttPrompt: z.string().optional(),
15
- maxSteps: z.number().int().positive().optional(),
16
- toolChoice: ToolChoiceSchema.optional(),
17
- builtinTools: z.array(BuiltinToolSchema).readonly().optional(),
18
- idleTimeoutMs: z.number().nonnegative().optional()
19
- });
20
- /** Extract the serializable {@link AgentConfig} subset from a source object. */
21
- function toAgentConfig(src) {
22
- const config = {
23
- name: src.name,
24
- systemPrompt: src.systemPrompt,
25
- greeting: src.greeting
26
- };
27
- if (src.sttPrompt !== void 0) config.sttPrompt = src.sttPrompt;
28
- if (src.maxSteps !== void 0) config.maxSteps = src.maxSteps;
29
- if (src.toolChoice !== void 0) config.toolChoice = src.toolChoice;
30
- if (src.builtinTools) config.builtinTools = [...src.builtinTools];
31
- if (src.idleTimeoutMs !== void 0) config.idleTimeoutMs = src.idleTimeoutMs;
32
- return config;
33
- }
34
- /**
35
- * Zod schema for serialized tool definitions sent over the wire.
36
- *
37
- * `parameters` must be a valid JSON Schema object (with `type`, `properties`,
38
- * etc.) — the Vercel AI SDK wraps it via `jsonSchema()`.
39
- */
40
- const ToolSchemaSchema = z.object({
41
- name: z.string().min(1),
42
- description: z.string().min(1),
43
- parameters: z.record(z.string(), z.unknown())
44
- });
45
- /** Empty Zod object schema used as default when tools have no parameters. */
46
- const EMPTY_PARAMS = z.object({});
47
- /**
48
- * Convert agent tool definitions to JSON Schema format for wire transport.
49
- *
50
- * Transforms the Zod-based `parameters` of each tool into a plain JSON Schema
51
- * object suitable for structured clone / JSON serialization.
52
- */
53
- function agentToolsToSchemas(tools) {
54
- return Object.entries(tools).map(([name, def]) => ({
55
- name,
56
- description: def.description,
57
- parameters: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
58
- }));
59
- }
60
- //#endregion
61
- //#region sdk/system-prompt.ts
62
- function getFormattedDate() {
63
- return (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
64
- weekday: "long",
65
- year: "numeric",
66
- month: "long",
67
- day: "numeric"
68
- });
69
- }
70
- 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";
71
- /**
72
- * Build the system prompt sent to the LLM from the agent configuration.
73
- *
74
- * Assembles the default system prompt, today's date, agent-specific instructions,
75
- * and optional sections for tool usage preamble and voice output rules.
76
- *
77
- * @param config - The serializable agent configuration (name, systemPrompt, etc.).
78
- * @param opts.hasTools - When `true`, appends a preamble instructing the LLM to
79
- * speak a brief phrase before each tool call to fill silence.
80
- * @param opts.voice - When `true`, appends strict voice-specific output rules
81
- * (no markdown, no bullet points, conversational tone, concise responses).
82
- * @returns The assembled system prompt string.
83
- */
84
- function buildSystemPrompt(config, opts) {
85
- const { hasTools } = opts;
86
- const agentInstructions = config.systemPrompt && config.systemPrompt !== DEFAULT_SYSTEM_PROMPT ? `\n\nAgent-Specific Instructions:\n${config.systemPrompt}` : "";
87
- 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." : "";
88
- const guidance = opts.toolGuidance && opts.toolGuidance.length > 0 ? `\n\nBuilt-in Tool Usage:\n${opts.toolGuidance.join("\n")}` : "";
89
- return DEFAULT_SYSTEM_PROMPT + `\n\nToday's date is ${getFormattedDate()}.` + agentInstructions + toolPreamble + guidance + (opts.voice ? VOICE_RULES : "");
90
- }
91
- //#endregion
92
- export { agentToolsToSchemas as a, ToolSchemaSchema as i, AgentConfigSchema as n, toAgentConfig as o, EMPTY_PARAMS as r, buildSystemPrompt as t };