@alexkroman1/aai 1.7.1 → 1.8.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 (133) hide show
  1. package/.turbo/turbo-build.log +11 -9
  2. package/CHANGELOG.md +23 -0
  3. package/dist/{_internal-types-CrnTi9Ew.js → _internal-types-CfOAbK6V.js} +22 -35
  4. package/dist/constants-y68COEGj.js +29 -0
  5. package/dist/host/_base64.d.ts +2 -0
  6. package/dist/host/_mock-ws.d.ts +0 -61
  7. package/dist/host/_pipeline-test-fakes.d.ts +7 -4
  8. package/dist/host/_run-code.d.ts +0 -25
  9. package/dist/host/_runtime-conformance.d.ts +3 -34
  10. package/dist/host/memory-vector.d.ts +0 -11
  11. package/dist/host/providers/resolve-kv.d.ts +0 -7
  12. package/dist/host/providers/resolve-vector.d.ts +0 -8
  13. package/dist/host/providers/stt/assemblyai.d.ts +0 -14
  14. package/dist/host/providers/stt/deepgram.d.ts +2 -14
  15. package/dist/host/providers/stt/soniox.d.ts +0 -22
  16. package/dist/host/providers/tts/rime.d.ts +10 -31
  17. package/dist/host/runtime-barrel.js +670 -630
  18. package/dist/host/runtime-config.d.ts +9 -6
  19. package/dist/host/runtime.d.ts +3 -0
  20. package/dist/host/to-vercel-tools.d.ts +3 -33
  21. package/dist/host/transports/openai-realtime-transport.d.ts +45 -0
  22. package/dist/host/unstorage-kv.d.ts +0 -26
  23. package/dist/index.js +3 -3
  24. package/dist/openai-realtime-cjPAHMMx.js +10 -0
  25. package/dist/sdk/_internal-types.d.ts +6 -55
  26. package/dist/sdk/allowed-hosts.d.ts +4 -3
  27. package/dist/sdk/constants.d.ts +4 -29
  28. package/dist/sdk/define.d.ts +7 -4
  29. package/dist/sdk/kv.d.ts +13 -37
  30. package/dist/sdk/manifest-barrel.js +1 -1
  31. package/dist/sdk/manifest.d.ts +8 -2
  32. package/dist/sdk/protocol.js +1 -1
  33. package/dist/sdk/providers/s2s/openai-realtime.d.ts +17 -0
  34. package/dist/sdk/providers/s2s-barrel.d.ts +9 -0
  35. package/dist/sdk/providers/s2s-barrel.js +2 -0
  36. package/dist/sdk/providers/tts/rime.d.ts +1 -1
  37. package/dist/sdk/providers.d.ts +6 -2
  38. package/dist/sdk/types.d.ts +7 -1
  39. package/dist/{types-KUgezM6u.js → types-DOWVZhb9.js} +1 -7
  40. package/dist/{ws-upgrade-BeOQ7fXL.js → ws-upgrade-CG8-by1n.js} +2 -3
  41. package/host/_base64.ts +9 -0
  42. package/host/_mock-ws.ts +0 -65
  43. package/host/_pipeline-test-fakes.ts +19 -31
  44. package/host/_run-code.ts +10 -53
  45. package/host/_runtime-conformance.ts +3 -44
  46. package/host/_test-utils.ts +20 -42
  47. package/host/builtin-tools.test.ts +127 -222
  48. package/host/builtin-tools.ts +6 -10
  49. package/host/cleanup.test.ts +30 -73
  50. package/host/integration/pipeline-reference.integration.test.ts +12 -17
  51. package/host/integration.test.ts +0 -7
  52. package/host/memory-vector.test.ts +3 -1
  53. package/host/memory-vector.ts +16 -21
  54. package/host/pinecone-vector.test.ts +14 -17
  55. package/host/pinecone-vector.ts +10 -19
  56. package/host/providers/providers.test-d.ts +5 -3
  57. package/host/providers/resolve-kv.ts +23 -41
  58. package/host/providers/resolve-vector.ts +3 -12
  59. package/host/providers/resolve.test.ts +15 -28
  60. package/host/providers/resolve.ts +24 -24
  61. package/host/providers/stt/assemblyai.test.ts +2 -14
  62. package/host/providers/stt/assemblyai.ts +12 -35
  63. package/host/providers/stt/deepgram.test.ts +23 -83
  64. package/host/providers/stt/deepgram.ts +15 -40
  65. package/host/providers/stt/elevenlabs.test.ts +26 -38
  66. package/host/providers/stt/elevenlabs.ts +10 -9
  67. package/host/providers/stt/soniox.test.ts +35 -85
  68. package/host/providers/stt/soniox.ts +8 -53
  69. package/host/providers/tts/cartesia.test.ts +19 -58
  70. package/host/providers/tts/cartesia.ts +36 -66
  71. package/host/providers/tts/rime.test.ts +12 -38
  72. package/host/providers/tts/rime.ts +23 -86
  73. package/host/runtime-config.test.ts +9 -9
  74. package/host/runtime-config.ts +16 -22
  75. package/host/runtime.test.ts +111 -73
  76. package/host/runtime.ts +139 -86
  77. package/host/s2s.test.ts +92 -191
  78. package/host/s2s.ts +55 -49
  79. package/host/server-shutdown.test.ts +9 -30
  80. package/host/server.test.ts +2 -13
  81. package/host/server.ts +85 -100
  82. package/host/session-core.test.ts +15 -30
  83. package/host/session-core.ts +10 -13
  84. package/host/session-prompt.test.ts +1 -5
  85. package/host/to-vercel-tools.test.ts +53 -72
  86. package/host/to-vercel-tools.ts +9 -39
  87. package/host/tool-executor.test.ts +25 -51
  88. package/host/tool-executor.ts +18 -12
  89. package/host/transports/openai-realtime-transport.test.ts +439 -0
  90. package/host/transports/openai-realtime-transport.ts +371 -0
  91. package/host/transports/pipeline-transport.test.ts +125 -298
  92. package/host/transports/pipeline-transport.ts +20 -68
  93. package/host/transports/s2s-transport-fixtures.test.ts +31 -92
  94. package/host/transports/s2s-transport.test.ts +65 -134
  95. package/host/transports/s2s-transport.ts +15 -43
  96. package/host/transports/types.test.ts +4 -8
  97. package/host/unstorage-kv.test.ts +3 -2
  98. package/host/unstorage-kv.ts +5 -35
  99. package/host/ws-handler.test.ts +72 -176
  100. package/host/ws-handler.ts +6 -12
  101. package/package.json +6 -1
  102. package/sdk/__snapshots__/exports.test.ts.snap +7 -0
  103. package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
  104. package/sdk/_internal-types.test.ts +6 -9
  105. package/sdk/_internal-types.ts +16 -57
  106. package/sdk/_test-matchers.ts +25 -15
  107. package/sdk/allowed-hosts.test.ts +50 -114
  108. package/sdk/allowed-hosts.ts +8 -14
  109. package/sdk/constants.ts +5 -52
  110. package/sdk/define.test.ts +7 -6
  111. package/sdk/define.ts +7 -3
  112. package/sdk/exports.test.ts +6 -1
  113. package/sdk/kv.ts +13 -37
  114. package/sdk/manifest.test-d.ts +5 -0
  115. package/sdk/manifest.test.ts +61 -9
  116. package/sdk/manifest.ts +11 -11
  117. package/sdk/protocol-compat.test.ts +66 -98
  118. package/sdk/protocol-snapshot.test.ts +2 -16
  119. package/sdk/protocol.test.ts +13 -22
  120. package/sdk/providers/s2s/openai-realtime.ts +36 -0
  121. package/sdk/providers/s2s-barrel.ts +12 -0
  122. package/sdk/providers/tts/rime.ts +1 -1
  123. package/sdk/providers.ts +24 -5
  124. package/sdk/schema-alignment.test.ts +25 -73
  125. package/sdk/schema-shapes.test.ts +1 -29
  126. package/sdk/system-prompt.test.ts +0 -1
  127. package/sdk/system-prompt.ts +17 -19
  128. package/sdk/types-inference.test.ts +10 -36
  129. package/sdk/types.ts +7 -0
  130. package/sdk/ws-upgrade.test.ts +24 -23
  131. package/sdk/ws-upgrade.ts +2 -3
  132. package/tsdown.config.ts +8 -11
  133. package/dist/constants-C2nirZUI.js +0 -54
@@ -34,19 +34,13 @@ const here = dirname(fileURLToPath(import.meta.url));
34
34
  const fixturePath = join(here, "fixtures/hello-how-are-you.pcm16");
35
35
 
36
36
  async function fixtureExists(): Promise<boolean> {
37
- try {
38
- const s = await stat(fixturePath);
39
- return s.isFile() && s.size > 0;
40
- } catch {
41
- return false;
42
- }
37
+ const s = await stat(fixturePath).catch(() => null);
38
+ return s !== null && s.isFile() && s.size > 0;
43
39
  }
44
40
 
41
+ const { ASSEMBLYAI_API_KEY, OPENAI_API_KEY, CARTESIA_API_KEY, VITEST_PROFILE } = process.env;
45
42
  const envReady = Boolean(
46
- process.env.VITEST_PROFILE === "integration" &&
47
- process.env.ASSEMBLYAI_API_KEY &&
48
- process.env.OPENAI_API_KEY &&
49
- process.env.CARTESIA_API_KEY,
43
+ VITEST_PROFILE === "integration" && ASSEMBLYAI_API_KEY && OPENAI_API_KEY && CARTESIA_API_KEY,
50
44
  );
51
45
 
52
46
  describe.skipIf(!envReady)("pipeline integration — reference stack", () => {
@@ -66,12 +60,14 @@ describe.skipIf(!envReady)("pipeline integration — reference stack", () => {
66
60
  open: true,
67
61
  event: (e) => {
68
62
  if (e.type === "user_transcript") userTranscripts.push(e.text);
69
- if (e.type === "reply_done") replyDone = true;
63
+ else if (e.type === "reply_done") replyDone = true;
70
64
  },
71
65
  playAudioChunk: (chunk) => {
72
66
  audioOut.push(chunk);
73
67
  },
74
- playAudioDone: () => undefined,
68
+ playAudioDone: () => {
69
+ /* no-op */
70
+ },
75
71
  };
76
72
 
77
73
  const runtime = createRuntime({
@@ -84,9 +80,9 @@ describe.skipIf(!envReady)("pipeline integration — reference stack", () => {
84
80
  },
85
81
  env: {
86
82
  // biome-ignore lint/style/noNonNullAssertion: envReady guard ensures presence
87
- ASSEMBLYAI_API_KEY: process.env.ASSEMBLYAI_API_KEY!,
83
+ ASSEMBLYAI_API_KEY: ASSEMBLYAI_API_KEY!,
88
84
  // biome-ignore lint/style/noNonNullAssertion: envReady guard ensures presence
89
- CARTESIA_API_KEY: process.env.CARTESIA_API_KEY!,
85
+ CARTESIA_API_KEY: CARTESIA_API_KEY!,
90
86
  },
91
87
  stt: openAssemblyAI({ model: "u3pro-rt" }),
92
88
  llm: openai("gpt-4o-mini"),
@@ -103,11 +99,10 @@ describe.skipIf(!envReady)("pipeline integration — reference stack", () => {
103
99
  await session.start();
104
100
  session.onAudioReady();
105
101
 
106
- // Stream the PCM fixture in ~100ms chunks (16 kHz PCM16 → 3200 bytes/100ms).
102
+ // 16 kHz PCM16 → 3200 bytes per 100ms.
107
103
  const chunkBytes = 3200;
108
104
  for (let i = 0; i < pcm.length; i += chunkBytes) {
109
- const chunk = pcm.subarray(i, Math.min(i + chunkBytes, pcm.length));
110
- session.onAudio(new Uint8Array(chunk));
105
+ session.onAudio(new Uint8Array(pcm.subarray(i, i + chunkBytes)));
111
106
  await new Promise((r) => setTimeout(r, 100));
112
107
  }
113
108
  await session.stop();
@@ -108,7 +108,6 @@ describe("SDK integration: AgentDef → tool execution", () => {
108
108
  const exec = createRuntime({ agent, env: {} });
109
109
  expect(await exec.executeTool("increment", {}, "session-a", [])).toBe("1");
110
110
  expect(await exec.executeTool("increment", {}, "session-a", [])).toBe("2");
111
- // Different session starts fresh
112
111
  expect(await exec.executeTool("increment", {}, "session-b", [])).toBe("1");
113
112
  });
114
113
 
@@ -141,9 +140,7 @@ describe("SDK integration: AgentDef → tool execution", () => {
141
140
  };
142
141
 
143
142
  const exec = createRuntime({ agent, env: {} });
144
- // Valid input
145
143
  expect(await exec.executeTool("typed", { count: 5 }, "s1", [])).toBe("10");
146
- // Invalid input
147
144
  const err = await exec.executeTool("typed", { count: "not a number" }, "s1", []);
148
145
  expect(err).toContain("error");
149
146
  });
@@ -160,7 +157,6 @@ describe("SDK integration: AgentDef → tool execution", () => {
160
157
  };
161
158
 
162
159
  const config = toAgentConfig(agent);
163
- // Should survive JSON round-trip
164
160
  const parsed = JSON.parse(JSON.stringify(config));
165
161
  expect(parsed.name).toBe("config-test");
166
162
  expect(parsed.systemPrompt).toBe("Custom instructions");
@@ -182,9 +178,7 @@ describe("SDK integration: AgentDef → tool execution", () => {
182
178
  };
183
179
 
184
180
  const exec = createRuntime({ agent, env: {} });
185
- // Custom tool works
186
181
  expect(await exec.executeTool("custom", {}, "s1", [])).toBe("custom result");
187
- // Builtin tool works
188
182
  const codeResult = await exec.executeTool(
189
183
  "run_code",
190
184
  { code: 'console.log("from builtin")' },
@@ -192,7 +186,6 @@ describe("SDK integration: AgentDef → tool execution", () => {
192
186
  [],
193
187
  );
194
188
  expect(codeResult).toBe("from builtin");
195
- // Tool schemas include both
196
189
  const names = exec.toolSchemas.map((s) => s.name);
197
190
  expect(names).toContain("custom");
198
191
  expect(names).toContain("run_code");
@@ -33,7 +33,9 @@ describe("createMemoryVector", () => {
33
33
 
34
34
  it("topK caps the number of results", async () => {
35
35
  const v = createMemoryVector({ namespace: "ns1" });
36
- for (let i = 0; i < 10; i++) await v.upsert(`id-${i}`, `text ${i}`);
36
+ for (let i = 0; i < 10; i++) {
37
+ await v.upsert(`id-${i}`, `text ${i}`);
38
+ }
37
39
  expect(await v.query("text", { topK: 3 })).toHaveLength(3);
38
40
  });
39
41
 
@@ -1,14 +1,4 @@
1
1
  // Copyright 2025 the AAI authors. MIT license.
2
- /**
3
- * In-memory Vector implementation.
4
- *
5
- * INTENTIONALLY BAD QUALITY. Pseudo-embedding hashes the text into a
6
- * 64-dim Float32Array of values in [-1, ~0.99], then L2-normalizes
7
- * the result. Because both stored and probe vectors are unit-length,
8
- * cosine similarity reduces to a plain dot product — that's what
9
- * `cosine()` computes. Used only for `aai dev` and tests — the goal
10
- * is proving tool wiring, not retrieval ranking.
11
- */
12
2
 
13
3
  import { createHash } from "node:crypto";
14
4
  import type { Vector, VectorMatch, VectorQueryOptions } from "../sdk/vector.ts";
@@ -17,15 +7,16 @@ export type MemoryVectorOptions = {
17
7
  namespace: string;
18
8
  };
19
9
 
20
- type Record_ = {
10
+ type StoredRecord = {
21
11
  text: string;
22
12
  metadata: Record<string, unknown> | undefined;
23
13
  vec: Float32Array;
24
14
  };
25
15
 
26
- const stores = new Map<string, Map<string, Record_>>();
16
+ const DIM = 64;
17
+ const stores = new Map<string, Map<string, StoredRecord>>();
27
18
 
28
- function getStore(ns: string): Map<string, Record_> {
19
+ function getStore(ns: string): Map<string, StoredRecord> {
29
20
  let store = stores.get(ns);
30
21
  if (!store) {
31
22
  store = new Map();
@@ -34,14 +25,18 @@ function getStore(ns: string): Map<string, Record_> {
34
25
  return store;
35
26
  }
36
27
 
37
- const DIM = 64;
38
-
28
+ // Pseudo-embedding: hash text → 64-dim unit vector. Both stored and probe
29
+ // vectors are unit-length, so cosine similarity reduces to a dot product.
30
+ // Intentionally low-quality — this is for `aai dev` and tests only, where
31
+ // the goal is proving tool wiring rather than retrieval ranking.
39
32
  function pseudoEmbed(text: string): Float32Array {
40
33
  const out = new Float32Array(DIM);
41
34
  const h1 = createHash("sha256").update(text).digest();
42
35
  const h2 = createHash("sha256").update(h1).digest();
43
- for (let i = 0; i < 32; i++) out[i] = ((h1[i] as number) - 128) / 128;
44
- for (let i = 0; i < 32; i++) out[i + 32] = ((h2[i] as number) - 128) / 128;
36
+ for (let i = 0; i < 32; i++) {
37
+ out[i] = ((h1[i] as number) - 128) / 128;
38
+ out[i + 32] = ((h2[i] as number) - 128) / 128;
39
+ }
45
40
  let norm = 0;
46
41
  for (let i = 0; i < DIM; i++) norm += (out[i] as number) * (out[i] as number);
47
42
  norm = Math.sqrt(norm) || 1;
@@ -84,12 +79,13 @@ export function createMemoryVector(opts: MemoryVectorOptions): Vector {
84
79
  const scored: VectorMatch[] = [];
85
80
  for (const [id, rec] of getStore(ns)) {
86
81
  if (filter && !matches(rec.metadata, filter)) continue;
87
- scored.push({
82
+ const match: VectorMatch = {
88
83
  id,
89
84
  score: cosine(probe, rec.vec),
90
85
  text: rec.text,
91
- ...(rec.metadata !== undefined ? { metadata: rec.metadata } : {}),
92
- });
86
+ };
87
+ if (rec.metadata !== undefined) match.metadata = rec.metadata;
88
+ scored.push(match);
93
89
  }
94
90
  scored.sort((a, b) => b.score - a.score);
95
91
  return scored.slice(0, topK);
@@ -102,7 +98,6 @@ export function createMemoryVector(opts: MemoryVectorOptions): Vector {
102
98
  };
103
99
  }
104
100
 
105
- /** Test-only: clear all in-memory state. Not exported from the package. */
106
101
  export function _resetMemoryVectorForTests(): void {
107
102
  stores.clear();
108
103
  }
@@ -1,8 +1,6 @@
1
1
  // Copyright 2025 the AAI authors. MIT license.
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
 
4
- // Intercept createRequire from node:module so that require("@pinecone-database/pinecone")
5
- // returns a controlled fake — works even when the package is not installed.
6
4
  vi.mock("node:module", async (importOriginal) => {
7
5
  const original = await importOriginal<typeof import("node:module")>();
8
6
  return {
@@ -20,30 +18,29 @@ vi.mock("node:module", async (importOriginal) => {
20
18
 
21
19
  // Must use var (not const/let) to avoid TDZ when the vi.mock factory above
22
20
  // is hoisted — the variables are referenced inside the factory closure.
23
- var upsertRecords = vi.fn(); // eslint-disable-line no-var
24
- var searchRecords = vi.fn(); // eslint-disable-line no-var
25
- var deleteMany = vi.fn(); // eslint-disable-line no-var
26
- var namespace = vi.fn(() => ({ upsertRecords, searchRecords, deleteMany })); // eslint-disable-line no-var
27
- var index = vi.fn(() => ({ namespace })); // eslint-disable-line no-var
21
+ /* eslint-disable no-var */
22
+ var upsertRecords = vi.fn();
23
+ var searchRecords = vi.fn();
24
+ var deleteMany = vi.fn();
25
+ var namespace = vi.fn(() => ({ upsertRecords, searchRecords, deleteMany }));
26
+ var index = vi.fn(() => ({ namespace }));
28
27
  var PineconeFake = vi.fn(function (this: unknown) {
29
- // eslint-disable-line no-var
30
28
  return { index };
31
29
  });
32
30
 
31
+ /* eslint-enable no-var */
32
+
33
33
  import { createPineconeVector } from "./pinecone-vector.ts";
34
34
 
35
+ const config = { apiKey: "k", index: "ix", namespace: "ns" };
36
+
35
37
  beforeEach(() => {
36
38
  vi.clearAllMocks();
37
- PineconeFake.mockImplementation(function (this: unknown) {
38
- return { index };
39
- });
40
- index.mockImplementation(() => ({ namespace }));
41
- namespace.mockImplementation(() => ({ upsertRecords, searchRecords, deleteMany }));
42
39
  });
43
40
 
44
41
  describe("createPineconeVector", () => {
45
42
  it("threads namespace and index on upsert", async () => {
46
- const v = createPineconeVector({ apiKey: "k", index: "ix", namespace: "ns" });
43
+ const v = createPineconeVector(config);
47
44
  await v.upsert("doc-1", "hello", { tag: "x" });
48
45
  expect(index).toHaveBeenCalledWith("ix");
49
46
  expect(namespace).toHaveBeenCalledWith("ns");
@@ -56,7 +53,7 @@ describe("createPineconeVector", () => {
56
53
  hits: [{ _id: "doc-1", _score: 0.9, fields: { text: "hello", tag: "x" } }],
57
54
  },
58
55
  });
59
- const v = createPineconeVector({ apiKey: "k", index: "ix", namespace: "ns" });
56
+ const v = createPineconeVector(config);
60
57
  const matches = await v.query("hello", { topK: 3, filter: { tag: "x" } });
61
58
  expect(searchRecords).toHaveBeenCalledWith({
62
59
  query: { inputs: { text: "hello" }, topK: 3, filter: { tag: "x" } },
@@ -66,13 +63,13 @@ describe("createPineconeVector", () => {
66
63
  });
67
64
 
68
65
  it("delete with single id", async () => {
69
- const v = createPineconeVector({ apiKey: "k", index: "ix", namespace: "ns" });
66
+ const v = createPineconeVector(config);
70
67
  await v.delete("doc-1");
71
68
  expect(deleteMany).toHaveBeenCalledWith(["doc-1"]);
72
69
  });
73
70
 
74
71
  it("delete with array", async () => {
75
- const v = createPineconeVector({ apiKey: "k", index: "ix", namespace: "ns" });
72
+ const v = createPineconeVector(config);
76
73
  await v.delete(["a", "b"]);
77
74
  expect(deleteMany).toHaveBeenCalledWith(["a", "b"]);
78
75
  });
@@ -37,43 +37,34 @@ type PineconeClient = {
37
37
  };
38
38
 
39
39
  export function createPineconeVector(opts: PineconeVectorOptions): Vector {
40
- // Lazy-load via loadProviderPackage so the package is a true optional peer dep.
41
40
  const { Pinecone } = loadProviderPackage<{
42
41
  Pinecone: new (opts: { apiKey: string }) => PineconeClient;
43
42
  }>("@pinecone-database/pinecone", "Pinecone Vector");
44
- const client = new Pinecone({ apiKey: opts.apiKey });
45
- const ns = (): PineconeNs => client.index(opts.index).namespace(opts.namespace);
43
+ const ns = new Pinecone({ apiKey: opts.apiKey }).index(opts.index).namespace(opts.namespace);
46
44
 
47
45
  return {
48
46
  async upsert(id, text, metadata) {
49
- const record: Record<string, unknown> = { _id: id, text, ...(metadata ?? {}) };
50
- await ns().upsertRecords([record]);
47
+ await ns.upsertRecords([{ _id: id, text, ...(metadata ?? {}) }]);
51
48
  },
52
49
  async query(text, queryOpts?: VectorQueryOptions) {
53
- const topK = queryOpts?.topK ?? 5;
54
- const req = {
55
- query: {
56
- inputs: { text },
57
- topK,
58
- ...(queryOpts?.filter !== undefined ? { filter: queryOpts.filter } : {}),
59
- },
50
+ const { topK = 5, filter } = queryOpts ?? {};
51
+ const resp = await ns.searchRecords({
52
+ query: { inputs: { text }, topK, ...(filter !== undefined ? { filter } : {}) },
60
53
  fields: ["*"],
61
- };
62
- const resp = await ns().searchRecords(req);
54
+ });
63
55
  return resp.result.hits.map((hit): VectorMatch => {
64
56
  const { text: hitText, ...rest } = hit.fields;
65
- const metadata = Object.keys(rest).length > 0 ? rest : undefined;
66
- return {
57
+ const match: VectorMatch = {
67
58
  id: hit._id,
68
59
  score: hit._score,
69
60
  text: typeof hitText === "string" ? hitText : "",
70
- ...(metadata !== undefined ? { metadata } : {}),
71
61
  };
62
+ if (Object.keys(rest).length > 0) match.metadata = rest;
63
+ return match;
72
64
  });
73
65
  },
74
66
  async delete(ids) {
75
- const list = Array.isArray(ids) ? ids : [ids];
76
- await ns().deleteMany(list);
67
+ await ns.deleteMany(Array.isArray(ids) ? ids : [ids]);
77
68
  },
78
69
  };
79
70
  }
@@ -12,10 +12,12 @@ import type {
12
12
  Unsubscribe,
13
13
  } from "../../sdk/providers.ts";
14
14
 
15
+ type Descriptor = { kind: string; options: Record<string, unknown> };
16
+
15
17
  test("Descriptors are { kind, options } data", () => {
16
- expectTypeOf<SttProvider>().toMatchTypeOf<{ kind: string; options: Record<string, unknown> }>();
17
- expectTypeOf<LlmProvider>().toMatchTypeOf<{ kind: string; options: Record<string, unknown> }>();
18
- expectTypeOf<TtsProvider>().toMatchTypeOf<{ kind: string; options: Record<string, unknown> }>();
18
+ expectTypeOf<SttProvider>().toMatchTypeOf<Descriptor>();
19
+ expectTypeOf<LlmProvider>().toMatchTypeOf<Descriptor>();
20
+ expectTypeOf<TtsProvider>().toMatchTypeOf<Descriptor>();
19
21
  });
20
22
 
21
23
  test("SttOpener.open returns Promise<SttSession>", () => {
@@ -1,10 +1,4 @@
1
1
  // Copyright 2025 the AAI authors. MIT license.
2
- /**
3
- * Descriptor → concrete `Kv` resolver. Mirror of `resolveLlm` /
4
- * `resolveVector`. Always wraps the produced unstorage Storage in
5
- * `createUnstorageKv` with the provided per-tenant prefix so namespace
6
- * isolation is enforced regardless of backend choice.
7
- */
8
2
 
9
3
  import { createStorage, type Driver } from "unstorage";
10
4
  import type { Kv } from "../../sdk/kv.ts";
@@ -16,34 +10,22 @@ import type { KvProvider } from "../../sdk/providers.ts";
16
10
  import { createUnstorageKv } from "../unstorage-kv.ts";
17
11
  import { loadProviderPackage, resolveApiKey } from "./resolve.ts";
18
12
 
19
- /**
20
- * Load a CJS unstorage driver factory. The CJS variants use
21
- * `module.exports = defineDriver(...)` so the require result is the
22
- * factory itself (not an object with `.default`).
23
- *
24
- * Delegates to loadProviderPackage (lazy-load via createRequire so the
25
- * driver is a true optional peer dep).
26
- */
27
- function loadDriver<T>(modulePath: string, label: string): T {
28
- return loadProviderPackage<T>(modulePath, `${label} KV: driver`);
13
+ type DriverFactory<O> = (opts: O) => Driver;
14
+
15
+ function loadDriver<O>(modulePath: string, label: string): DriverFactory<O> {
16
+ return loadProviderPackage<DriverFactory<O>>(modulePath, `${label} KV: driver`);
29
17
  }
30
18
 
31
- /**
32
- * Build a lazy unstorage Driver that defers loading the real driver
33
- * factory until the first I/O operation. This is necessary for drivers
34
- * whose peer dependencies (e.g. `ioredis`) may not be installed on the
35
- * host at startup — the missing package will only surface when the agent
36
- * actually performs KV operations, not at session creation time.
37
- */
19
+ // Defers driver factory loading until first I/O so missing optional peer
20
+ // deps (e.g. `ioredis`) surface at use-time rather than session start.
38
21
  function makeLazyDriver(modulePath: string, label: string, opts: Record<string, unknown>): Driver {
39
22
  let resolved: Driver | null = null;
40
- const get = (): Driver => {
23
+ function get(): Driver {
41
24
  if (!resolved) {
42
- const factory = loadDriver<(o: Record<string, unknown>) => Driver>(modulePath, label);
43
- resolved = factory(opts);
25
+ resolved = loadDriver<Record<string, unknown>>(modulePath, label)(opts);
44
26
  }
45
27
  return resolved;
46
- };
28
+ }
47
29
  return {
48
30
  name: label.toLowerCase(),
49
31
  hasItem: (key, txOpts) => get().hasItem(key, txOpts),
@@ -54,24 +36,24 @@ function makeLazyDriver(modulePath: string, label: string, opts: Record<string,
54
36
  removeItem: (key, txOpts) => get().removeItem?.(key, txOpts),
55
37
  getKeys: (base, txOpts) => get().getKeys(base, txOpts),
56
38
  clear: (base, txOpts) => get().clear?.(base, txOpts),
57
- dispose: () => (resolved ? resolved.dispose?.() : undefined),
39
+ dispose: () => resolved?.dispose?.(),
58
40
  };
59
41
  }
60
42
 
61
- /** Resolve a {@link KvProvider} descriptor into a {@link Kv}. */
62
43
  export function resolveKv(descriptor: KvProvider, env: Record<string, string>, prefix: string): Kv {
63
44
  switch (descriptor.kind) {
64
- case MEMORY_KV_KIND: {
45
+ case MEMORY_KV_KIND:
65
46
  return createUnstorageKv({ storage: createStorage(), prefix });
66
- }
47
+
67
48
  case FS_KV_KIND: {
68
49
  const opts = descriptor.options as unknown as FsKvOptions;
69
- const fsDriver = loadDriver<(o: { base: string }) => Driver>("unstorage/drivers/fs", "fs");
50
+ const fsDriver = loadDriver<{ base: string }>("unstorage/drivers/fs", "fs");
70
51
  return createUnstorageKv({
71
52
  storage: createStorage({ driver: fsDriver({ base: opts.base }) }),
72
53
  prefix,
73
54
  });
74
55
  }
56
+
75
57
  case S3_KV_KIND: {
76
58
  const opts = descriptor.options as unknown as S3KvOptions;
77
59
  const accessKeyId = resolveApiKey("AWS_ACCESS_KEY_ID", env);
@@ -81,15 +63,13 @@ export function resolveKv(descriptor: KvProvider, env: Record<string, string>, p
81
63
  "S3 KV: missing AWS credentials. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the agent env.",
82
64
  );
83
65
  }
84
- const s3Driver = loadDriver<
85
- (o: {
86
- bucket: string;
87
- endpoint?: string;
88
- region: string;
89
- accessKeyId: string;
90
- secretAccessKey: string;
91
- }) => Driver
92
- >("unstorage/drivers/s3", "S3");
66
+ const s3Driver = loadDriver<{
67
+ bucket: string;
68
+ endpoint?: string;
69
+ region: string;
70
+ accessKeyId: string;
71
+ secretAccessKey: string;
72
+ }>("unstorage/drivers/s3", "S3");
93
73
  return createUnstorageKv({
94
74
  storage: createStorage({
95
75
  driver: s3Driver({
@@ -103,6 +83,7 @@ export function resolveKv(descriptor: KvProvider, env: Record<string, string>, p
103
83
  prefix,
104
84
  });
105
85
  }
86
+
106
87
  case REDIS_KV_KIND: {
107
88
  const opts = descriptor.options as unknown as RedisKvOptions;
108
89
  const url = resolveApiKey("REDIS_URL", env);
@@ -119,6 +100,7 @@ export function resolveKv(descriptor: KvProvider, env: Record<string, string>, p
119
100
  prefix,
120
101
  });
121
102
  }
103
+
122
104
  default:
123
105
  throw new Error(
124
106
  `Unknown KV provider kind: "${descriptor.kind}". ` +
@@ -1,11 +1,4 @@
1
1
  // Copyright 2025 the AAI authors. MIT license.
2
- /**
3
- * Descriptor → concrete `Vector` resolver. Mirror of `resolveLlm`.
4
- *
5
- * Pulls API keys from the agent env so descriptors stay
6
- * secret-free. Lazy-loads provider SDKs via `createRequire` so
7
- * unused providers never enter the bundle.
8
- */
9
2
 
10
3
  import { IN_MEMORY_VECTOR_KIND } from "../../sdk/providers/vector/in-memory.ts";
11
4
  import { PINECONE_VECTOR_KIND, type PineconeOptions } from "../../sdk/providers/vector/pinecone.ts";
@@ -15,23 +8,21 @@ import { createMemoryVector } from "../memory-vector.ts";
15
8
  import { createPineconeVector } from "../pinecone-vector.ts";
16
9
  import { resolveApiKey } from "./resolve.ts";
17
10
 
18
- /** Resolve a {@link VectorProvider} descriptor into a {@link Vector}. */
19
11
  export function resolveVector(
20
12
  descriptor: VectorProvider,
21
13
  env: Record<string, string>,
22
14
  namespace: string,
23
15
  ): Vector {
24
16
  switch (descriptor.kind) {
25
- case IN_MEMORY_VECTOR_KIND: {
17
+ case IN_MEMORY_VECTOR_KIND:
26
18
  return createMemoryVector({ namespace });
27
- }
28
19
  case PINECONE_VECTOR_KIND: {
29
20
  const apiKey = resolveApiKey("PINECONE_API_KEY", env);
30
21
  if (!apiKey) {
31
22
  throw new Error("Pinecone Vector: missing API key. Set PINECONE_API_KEY in the agent env.");
32
23
  }
33
- const opts = descriptor.options as unknown as PineconeOptions;
34
- return createPineconeVector({ apiKey, index: opts.index, namespace });
24
+ const { index } = descriptor.options as unknown as PineconeOptions;
25
+ return createPineconeVector({ apiKey, index, namespace });
35
26
  }
36
27
  default:
37
28
  throw new Error(
@@ -9,17 +9,16 @@
9
9
  */
10
10
 
11
11
  import { describe, expect, it } from "vitest";
12
- import { ANTHROPIC_KIND, type AnthropicProvider } from "../../sdk/providers/llm/anthropic.ts";
13
- import { GOOGLE_KIND, type GoogleProvider } from "../../sdk/providers/llm/google.ts";
14
- import { GROQ_KIND, type GroqProvider } from "../../sdk/providers/llm/groq.ts";
15
- import { MISTRAL_KIND, type MistralProvider } from "../../sdk/providers/llm/mistral.ts";
16
- import { OPENAI_KIND, type OpenAIProvider } from "../../sdk/providers/llm/openai.ts";
17
- import { XAI_KIND, type XaiProvider } from "../../sdk/providers/llm/xai.ts";
12
+ import { ANTHROPIC_KIND } from "../../sdk/providers/llm/anthropic.ts";
13
+ import { GOOGLE_KIND } from "../../sdk/providers/llm/google.ts";
14
+ import { GROQ_KIND } from "../../sdk/providers/llm/groq.ts";
15
+ import { MISTRAL_KIND } from "../../sdk/providers/llm/mistral.ts";
16
+ import { OPENAI_KIND } from "../../sdk/providers/llm/openai.ts";
17
+ import { XAI_KIND } from "../../sdk/providers/llm/xai.ts";
18
18
  import type { LlmProvider } from "../../sdk/providers.ts";
19
19
  import { resolveLlm } from "./resolve.ts";
20
20
 
21
21
  type ProviderCase = {
22
- kind: string;
23
22
  provider: LlmProvider;
24
23
  envVar: string;
25
24
  label: string;
@@ -27,41 +26,32 @@ type ProviderCase = {
27
26
 
28
27
  const cases: ProviderCase[] = [
29
28
  {
30
- kind: ANTHROPIC_KIND,
31
- provider: { kind: ANTHROPIC_KIND, options: { model: "claude-haiku-4-5" } } as AnthropicProvider,
29
+ provider: { kind: ANTHROPIC_KIND, options: { model: "claude-haiku-4-5" } },
32
30
  envVar: "ANTHROPIC_API_KEY",
33
31
  label: "Anthropic",
34
32
  },
35
33
  {
36
- kind: OPENAI_KIND,
37
- provider: { kind: OPENAI_KIND, options: { model: "gpt-4o" } } as OpenAIProvider,
34
+ provider: { kind: OPENAI_KIND, options: { model: "gpt-4o" } },
38
35
  envVar: "OPENAI_API_KEY",
39
36
  label: "OpenAI",
40
37
  },
41
38
  {
42
- kind: GOOGLE_KIND,
43
- provider: { kind: GOOGLE_KIND, options: { model: "gemini-2.0-flash" } } as GoogleProvider,
39
+ provider: { kind: GOOGLE_KIND, options: { model: "gemini-2.0-flash" } },
44
40
  envVar: "GOOGLE_GENERATIVE_AI_API_KEY",
45
41
  label: "Google",
46
42
  },
47
43
  {
48
- kind: MISTRAL_KIND,
49
- provider: {
50
- kind: MISTRAL_KIND,
51
- options: { model: "mistral-large-latest" },
52
- } as MistralProvider,
44
+ provider: { kind: MISTRAL_KIND, options: { model: "mistral-large-latest" } },
53
45
  envVar: "MISTRAL_API_KEY",
54
46
  label: "Mistral",
55
47
  },
56
48
  {
57
- kind: XAI_KIND,
58
- provider: { kind: XAI_KIND, options: { model: "grok-2-1212" } } as XaiProvider,
49
+ provider: { kind: XAI_KIND, options: { model: "grok-2-1212" } },
59
50
  envVar: "XAI_API_KEY",
60
51
  label: "xAI",
61
52
  },
62
53
  {
63
- kind: GROQ_KIND,
64
- provider: { kind: GROQ_KIND, options: { model: "llama-3.3-70b-versatile" } } as GroqProvider,
54
+ provider: { kind: GROQ_KIND, options: { model: "llama-3.3-70b-versatile" } },
65
55
  envVar: "GROQ_API_KEY",
66
56
  label: "Groq",
67
57
  },
@@ -72,10 +62,8 @@ describe("resolveLlm", () => {
72
62
  describe(tc.label, () => {
73
63
  it("returns a LanguageModel when the API key is present", () => {
74
64
  const model = resolveLlm(tc.provider, { [tc.envVar]: "fake-key" });
75
- // Vercel AI SDK LanguageModels are objects with a
76
- // `specificationVersion` property; this is the cheapest stable
77
- // handle to confirm resolve dispatched to the right factory and
78
- // returned a real model.
65
+ // `specificationVersion` is the cheapest stable handle on a Vercel AI SDK
66
+ // LanguageModel confirms resolve dispatched to the right factory.
79
67
  expect(model).toBeTypeOf("object");
80
68
  expect(model).toHaveProperty("specificationVersion");
81
69
  });
@@ -94,13 +82,12 @@ describe("resolveLlm", () => {
94
82
  }
95
83
 
96
84
  it("throws a useful error for an unknown kind, listing supported kinds", () => {
97
- const bogus = { kind: "claude-direct", options: {} } as LlmProvider;
85
+ const bogus = { kind: "claude-direct", options: {} } as unknown as LlmProvider;
98
86
  expect(() => resolveLlm(bogus, {})).toThrow(/Unknown LLM provider kind: "claude-direct"/);
99
87
  expect(() => resolveLlm(bogus, {})).toThrow(/anthropic.*openai.*google.*mistral.*xai.*groq/);
100
88
  });
101
89
  });
102
90
 
103
- /** Temporarily delete a process.env var, returning a restore function. */
104
91
  function stripEnv(name: string): () => void {
105
92
  const prev = process.env[name];
106
93
  delete process.env[name];