@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.
- package/.turbo/turbo-build.log +11 -9
- package/CHANGELOG.md +23 -0
- package/dist/{_internal-types-CrnTi9Ew.js → _internal-types-CfOAbK6V.js} +22 -35
- package/dist/constants-y68COEGj.js +29 -0
- package/dist/host/_base64.d.ts +2 -0
- package/dist/host/_mock-ws.d.ts +0 -61
- package/dist/host/_pipeline-test-fakes.d.ts +7 -4
- package/dist/host/_run-code.d.ts +0 -25
- package/dist/host/_runtime-conformance.d.ts +3 -34
- package/dist/host/memory-vector.d.ts +0 -11
- package/dist/host/providers/resolve-kv.d.ts +0 -7
- package/dist/host/providers/resolve-vector.d.ts +0 -8
- package/dist/host/providers/stt/assemblyai.d.ts +0 -14
- package/dist/host/providers/stt/deepgram.d.ts +2 -14
- package/dist/host/providers/stt/soniox.d.ts +0 -22
- package/dist/host/providers/tts/rime.d.ts +10 -31
- package/dist/host/runtime-barrel.js +670 -630
- package/dist/host/runtime-config.d.ts +9 -6
- package/dist/host/runtime.d.ts +3 -0
- package/dist/host/to-vercel-tools.d.ts +3 -33
- package/dist/host/transports/openai-realtime-transport.d.ts +45 -0
- package/dist/host/unstorage-kv.d.ts +0 -26
- package/dist/index.js +3 -3
- package/dist/openai-realtime-cjPAHMMx.js +10 -0
- package/dist/sdk/_internal-types.d.ts +6 -55
- package/dist/sdk/allowed-hosts.d.ts +4 -3
- package/dist/sdk/constants.d.ts +4 -29
- package/dist/sdk/define.d.ts +7 -4
- package/dist/sdk/kv.d.ts +13 -37
- package/dist/sdk/manifest-barrel.js +1 -1
- package/dist/sdk/manifest.d.ts +8 -2
- package/dist/sdk/protocol.js +1 -1
- package/dist/sdk/providers/s2s/openai-realtime.d.ts +17 -0
- package/dist/sdk/providers/s2s-barrel.d.ts +9 -0
- package/dist/sdk/providers/s2s-barrel.js +2 -0
- package/dist/sdk/providers/tts/rime.d.ts +1 -1
- package/dist/sdk/providers.d.ts +6 -2
- package/dist/sdk/types.d.ts +7 -1
- package/dist/{types-KUgezM6u.js → types-DOWVZhb9.js} +1 -7
- package/dist/{ws-upgrade-BeOQ7fXL.js → ws-upgrade-CG8-by1n.js} +2 -3
- package/host/_base64.ts +9 -0
- package/host/_mock-ws.ts +0 -65
- package/host/_pipeline-test-fakes.ts +19 -31
- package/host/_run-code.ts +10 -53
- package/host/_runtime-conformance.ts +3 -44
- package/host/_test-utils.ts +20 -42
- package/host/builtin-tools.test.ts +127 -222
- package/host/builtin-tools.ts +6 -10
- package/host/cleanup.test.ts +30 -73
- package/host/integration/pipeline-reference.integration.test.ts +12 -17
- package/host/integration.test.ts +0 -7
- package/host/memory-vector.test.ts +3 -1
- package/host/memory-vector.ts +16 -21
- package/host/pinecone-vector.test.ts +14 -17
- package/host/pinecone-vector.ts +10 -19
- package/host/providers/providers.test-d.ts +5 -3
- package/host/providers/resolve-kv.ts +23 -41
- package/host/providers/resolve-vector.ts +3 -12
- package/host/providers/resolve.test.ts +15 -28
- package/host/providers/resolve.ts +24 -24
- package/host/providers/stt/assemblyai.test.ts +2 -14
- package/host/providers/stt/assemblyai.ts +12 -35
- package/host/providers/stt/deepgram.test.ts +23 -83
- package/host/providers/stt/deepgram.ts +15 -40
- package/host/providers/stt/elevenlabs.test.ts +26 -38
- package/host/providers/stt/elevenlabs.ts +10 -9
- package/host/providers/stt/soniox.test.ts +35 -85
- package/host/providers/stt/soniox.ts +8 -53
- package/host/providers/tts/cartesia.test.ts +19 -58
- package/host/providers/tts/cartesia.ts +36 -66
- package/host/providers/tts/rime.test.ts +12 -38
- package/host/providers/tts/rime.ts +23 -86
- package/host/runtime-config.test.ts +9 -9
- package/host/runtime-config.ts +16 -22
- package/host/runtime.test.ts +111 -73
- package/host/runtime.ts +139 -86
- package/host/s2s.test.ts +92 -191
- package/host/s2s.ts +55 -49
- package/host/server-shutdown.test.ts +9 -30
- package/host/server.test.ts +2 -13
- package/host/server.ts +85 -100
- package/host/session-core.test.ts +15 -30
- package/host/session-core.ts +10 -13
- package/host/session-prompt.test.ts +1 -5
- package/host/to-vercel-tools.test.ts +53 -72
- package/host/to-vercel-tools.ts +9 -39
- package/host/tool-executor.test.ts +25 -51
- package/host/tool-executor.ts +18 -12
- package/host/transports/openai-realtime-transport.test.ts +439 -0
- package/host/transports/openai-realtime-transport.ts +371 -0
- package/host/transports/pipeline-transport.test.ts +125 -298
- package/host/transports/pipeline-transport.ts +20 -68
- package/host/transports/s2s-transport-fixtures.test.ts +31 -92
- package/host/transports/s2s-transport.test.ts +65 -134
- package/host/transports/s2s-transport.ts +15 -43
- package/host/transports/types.test.ts +4 -8
- package/host/unstorage-kv.test.ts +3 -2
- package/host/unstorage-kv.ts +5 -35
- package/host/ws-handler.test.ts +72 -176
- package/host/ws-handler.ts +6 -12
- package/package.json +6 -1
- package/sdk/__snapshots__/exports.test.ts.snap +7 -0
- package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
- package/sdk/_internal-types.test.ts +6 -9
- package/sdk/_internal-types.ts +16 -57
- package/sdk/_test-matchers.ts +25 -15
- package/sdk/allowed-hosts.test.ts +50 -114
- package/sdk/allowed-hosts.ts +8 -14
- package/sdk/constants.ts +5 -52
- package/sdk/define.test.ts +7 -6
- package/sdk/define.ts +7 -3
- package/sdk/exports.test.ts +6 -1
- package/sdk/kv.ts +13 -37
- package/sdk/manifest.test-d.ts +5 -0
- package/sdk/manifest.test.ts +61 -9
- package/sdk/manifest.ts +11 -11
- package/sdk/protocol-compat.test.ts +66 -98
- package/sdk/protocol-snapshot.test.ts +2 -16
- package/sdk/protocol.test.ts +13 -22
- package/sdk/providers/s2s/openai-realtime.ts +36 -0
- package/sdk/providers/s2s-barrel.ts +12 -0
- package/sdk/providers/tts/rime.ts +1 -1
- package/sdk/providers.ts +24 -5
- package/sdk/schema-alignment.test.ts +25 -73
- package/sdk/schema-shapes.test.ts +1 -29
- package/sdk/system-prompt.test.ts +0 -1
- package/sdk/system-prompt.ts +17 -19
- package/sdk/types-inference.test.ts +10 -36
- package/sdk/types.ts +7 -0
- package/sdk/ws-upgrade.test.ts +24 -23
- package/sdk/ws-upgrade.ts +2 -3
- package/tsdown.config.ts +8 -11
- 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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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: () =>
|
|
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:
|
|
83
|
+
ASSEMBLYAI_API_KEY: ASSEMBLYAI_API_KEY!,
|
|
88
84
|
// biome-ignore lint/style/noNonNullAssertion: envReady guard ensures presence
|
|
89
|
-
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
|
-
//
|
|
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
|
-
|
|
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();
|
package/host/integration.test.ts
CHANGED
|
@@ -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++)
|
|
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
|
|
package/host/memory-vector.ts
CHANGED
|
@@ -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
|
|
10
|
+
type StoredRecord = {
|
|
21
11
|
text: string;
|
|
22
12
|
metadata: Record<string, unknown> | undefined;
|
|
23
13
|
vec: Float32Array;
|
|
24
14
|
};
|
|
25
15
|
|
|
26
|
-
const
|
|
16
|
+
const DIM = 64;
|
|
17
|
+
const stores = new Map<string, Map<string, StoredRecord>>();
|
|
27
18
|
|
|
28
|
-
function getStore(ns: string): Map<string,
|
|
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
|
-
|
|
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++)
|
|
44
|
-
|
|
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
|
-
|
|
82
|
+
const match: VectorMatch = {
|
|
88
83
|
id,
|
|
89
84
|
score: cosine(probe, rec.vec),
|
|
90
85
|
text: rec.text,
|
|
91
|
-
|
|
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
|
-
|
|
24
|
-
var
|
|
25
|
-
var
|
|
26
|
-
var
|
|
27
|
-
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(
|
|
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(
|
|
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(
|
|
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(
|
|
72
|
+
const v = createPineconeVector(config);
|
|
76
73
|
await v.delete(["a", "b"]);
|
|
77
74
|
expect(deleteMany).toHaveBeenCalledWith(["a", "b"]);
|
|
78
75
|
});
|
package/host/pinecone-vector.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
54
|
-
const
|
|
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
|
|
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
|
-
|
|
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<
|
|
17
|
-
expectTypeOf<LlmProvider>().toMatchTypeOf<
|
|
18
|
-
expectTypeOf<TtsProvider>().toMatchTypeOf<
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
function get(): Driver {
|
|
41
24
|
if (!resolved) {
|
|
42
|
-
|
|
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: () =>
|
|
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<
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
34
|
-
return createPineconeVector({ apiKey, index
|
|
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
|
|
13
|
-
import { GOOGLE_KIND
|
|
14
|
-
import { GROQ_KIND
|
|
15
|
-
import { MISTRAL_KIND
|
|
16
|
-
import { OPENAI_KIND
|
|
17
|
-
import { XAI_KIND
|
|
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
|
-
//
|
|
76
|
-
//
|
|
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];
|