claude-code-swarm 0.3.3 → 0.3.5

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 (273) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +22 -1
  3. package/.claude-plugin/run-agent-inbox-mcp.sh +76 -0
  4. package/.claude-plugin/run-minimem-mcp.sh +98 -0
  5. package/.claude-plugin/run-opentasks-mcp.sh +65 -0
  6. package/CLAUDE.md +200 -36
  7. package/README.md +65 -0
  8. package/e2e/helpers/cleanup.mjs +17 -3
  9. package/e2e/helpers/map-mock-server.mjs +201 -25
  10. package/e2e/helpers/sidecar.mjs +222 -0
  11. package/e2e/helpers/workspace.mjs +2 -1
  12. package/e2e/tier5-sidecar-inbox.test.mjs +900 -0
  13. package/e2e/tier6-inbox-mcp.test.mjs +173 -0
  14. package/e2e/tier6-live-agent.test.mjs +759 -0
  15. package/e2e/vitest.config.e2e.mjs +1 -1
  16. package/hooks/hooks.json +15 -8
  17. package/package.json +13 -1
  18. package/references/agent-inbox/CLAUDE.md +151 -0
  19. package/references/agent-inbox/README.md +238 -0
  20. package/references/agent-inbox/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
  21. package/references/agent-inbox/docs/DESIGN.md +1156 -0
  22. package/references/agent-inbox/hooks/inbox-hook.mjs +119 -0
  23. package/references/agent-inbox/hooks/register-hook.mjs +69 -0
  24. package/references/agent-inbox/package-lock.json +3347 -0
  25. package/references/agent-inbox/package.json +58 -0
  26. package/references/agent-inbox/rules/agent-inbox.md +78 -0
  27. package/references/agent-inbox/src/federation/address.ts +61 -0
  28. package/references/agent-inbox/src/federation/connection-manager.ts +573 -0
  29. package/references/agent-inbox/src/federation/delivery-queue.ts +222 -0
  30. package/references/agent-inbox/src/federation/index.ts +6 -0
  31. package/references/agent-inbox/src/federation/routing-engine.ts +188 -0
  32. package/references/agent-inbox/src/federation/trust.ts +71 -0
  33. package/references/agent-inbox/src/index.ts +390 -0
  34. package/references/agent-inbox/src/ipc/ipc-server.ts +207 -0
  35. package/references/agent-inbox/src/jsonrpc/mail-server.ts +382 -0
  36. package/references/agent-inbox/src/map/map-client.ts +414 -0
  37. package/references/agent-inbox/src/mcp/mcp-server.ts +272 -0
  38. package/references/agent-inbox/src/mesh/delivery-bridge.ts +110 -0
  39. package/references/agent-inbox/src/mesh/mesh-connector.ts +41 -0
  40. package/references/agent-inbox/src/mesh/mesh-transport.ts +157 -0
  41. package/references/agent-inbox/src/mesh/type-mapper.ts +239 -0
  42. package/references/agent-inbox/src/push/notifier.ts +233 -0
  43. package/references/agent-inbox/src/registry/warm-registry.ts +255 -0
  44. package/references/agent-inbox/src/router/message-router.ts +175 -0
  45. package/references/agent-inbox/src/storage/interface.ts +48 -0
  46. package/references/agent-inbox/src/storage/memory.ts +145 -0
  47. package/references/agent-inbox/src/storage/sqlite.ts +671 -0
  48. package/references/agent-inbox/src/traceability/traceability.ts +183 -0
  49. package/references/agent-inbox/src/types.ts +303 -0
  50. package/references/agent-inbox/test/federation/address.test.ts +101 -0
  51. package/references/agent-inbox/test/federation/connection-manager.test.ts +546 -0
  52. package/references/agent-inbox/test/federation/delivery-queue.test.ts +159 -0
  53. package/references/agent-inbox/test/federation/integration.test.ts +857 -0
  54. package/references/agent-inbox/test/federation/routing-engine.test.ts +117 -0
  55. package/references/agent-inbox/test/federation/sdk-integration.test.ts +744 -0
  56. package/references/agent-inbox/test/federation/trust.test.ts +89 -0
  57. package/references/agent-inbox/test/ipc-jsonrpc.test.ts +113 -0
  58. package/references/agent-inbox/test/ipc-server.test.ts +197 -0
  59. package/references/agent-inbox/test/mail-server.test.ts +285 -0
  60. package/references/agent-inbox/test/map-client.test.ts +408 -0
  61. package/references/agent-inbox/test/mesh/delivery-bridge.test.ts +178 -0
  62. package/references/agent-inbox/test/mesh/e2e-mesh.test.ts +527 -0
  63. package/references/agent-inbox/test/mesh/e2e-real-meshpeer.test.ts +629 -0
  64. package/references/agent-inbox/test/mesh/federation-mesh.test.ts +269 -0
  65. package/references/agent-inbox/test/mesh/mesh-connector.test.ts +66 -0
  66. package/references/agent-inbox/test/mesh/mesh-transport.test.ts +191 -0
  67. package/references/agent-inbox/test/mesh/meshpeer-integration.test.ts +442 -0
  68. package/references/agent-inbox/test/mesh/mock-mesh.ts +125 -0
  69. package/references/agent-inbox/test/mesh/mock-meshpeer.ts +266 -0
  70. package/references/agent-inbox/test/mesh/type-mapper.test.ts +226 -0
  71. package/references/agent-inbox/test/message-router.test.ts +184 -0
  72. package/references/agent-inbox/test/push-notifier.test.ts +139 -0
  73. package/references/agent-inbox/test/registry/warm-registry.test.ts +171 -0
  74. package/references/agent-inbox/test/sqlite-prefix.test.ts +192 -0
  75. package/references/agent-inbox/test/sqlite-storage.test.ts +243 -0
  76. package/references/agent-inbox/test/storage.test.ts +196 -0
  77. package/references/agent-inbox/test/traceability.test.ts +123 -0
  78. package/references/agent-inbox/test/wake.test.ts +330 -0
  79. package/references/agent-inbox/tsconfig.json +20 -0
  80. package/references/agent-inbox/tsup.config.ts +10 -0
  81. package/references/agent-inbox/vitest.config.ts +8 -0
  82. package/references/minimem/.claude/settings.json +7 -0
  83. package/references/minimem/.sudocode/issues.jsonl +18 -0
  84. package/references/minimem/.sudocode/specs.jsonl +1 -0
  85. package/references/minimem/CLAUDE.md +329 -0
  86. package/references/minimem/README.md +565 -0
  87. package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
  88. package/references/minimem/claude-plugin/.mcp.json +7 -0
  89. package/references/minimem/claude-plugin/README.md +158 -0
  90. package/references/minimem/claude-plugin/commands/recall.md +47 -0
  91. package/references/minimem/claude-plugin/commands/remember.md +41 -0
  92. package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
  93. package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
  94. package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
  95. package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
  96. package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
  97. package/references/minimem/media/banner.png +0 -0
  98. package/references/minimem/package-lock.json +5373 -0
  99. package/references/minimem/package.json +76 -0
  100. package/references/minimem/scripts/postbuild.js +49 -0
  101. package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
  102. package/references/minimem/src/__tests__/errors.test.ts +265 -0
  103. package/references/minimem/src/__tests__/helpers.ts +199 -0
  104. package/references/minimem/src/__tests__/internal.test.ts +407 -0
  105. package/references/minimem/src/__tests__/knowledge-frontmatter.test.ts +148 -0
  106. package/references/minimem/src/__tests__/knowledge.test.ts +148 -0
  107. package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
  108. package/references/minimem/src/__tests__/session.test.ts +190 -0
  109. package/references/minimem/src/cli/__tests__/commands.test.ts +760 -0
  110. package/references/minimem/src/cli/__tests__/contained-layout.test.ts +286 -0
  111. package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
  112. package/references/minimem/src/cli/commands/append.ts +76 -0
  113. package/references/minimem/src/cli/commands/config.ts +262 -0
  114. package/references/minimem/src/cli/commands/conflicts.ts +415 -0
  115. package/references/minimem/src/cli/commands/daemon.ts +169 -0
  116. package/references/minimem/src/cli/commands/index.ts +12 -0
  117. package/references/minimem/src/cli/commands/init.ts +166 -0
  118. package/references/minimem/src/cli/commands/mcp.ts +221 -0
  119. package/references/minimem/src/cli/commands/push-pull.ts +213 -0
  120. package/references/minimem/src/cli/commands/search.ts +223 -0
  121. package/references/minimem/src/cli/commands/status.ts +84 -0
  122. package/references/minimem/src/cli/commands/store.ts +189 -0
  123. package/references/minimem/src/cli/commands/sync-init.ts +290 -0
  124. package/references/minimem/src/cli/commands/sync.ts +70 -0
  125. package/references/minimem/src/cli/commands/upsert.ts +197 -0
  126. package/references/minimem/src/cli/config.ts +611 -0
  127. package/references/minimem/src/cli/index.ts +299 -0
  128. package/references/minimem/src/cli/shared.ts +189 -0
  129. package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
  130. package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
  131. package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
  132. package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
  133. package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
  134. package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
  135. package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
  136. package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
  137. package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
  138. package/references/minimem/src/cli/sync/central.ts +292 -0
  139. package/references/minimem/src/cli/sync/conflicts.ts +205 -0
  140. package/references/minimem/src/cli/sync/daemon.ts +407 -0
  141. package/references/minimem/src/cli/sync/detection.ts +138 -0
  142. package/references/minimem/src/cli/sync/index.ts +107 -0
  143. package/references/minimem/src/cli/sync/operations.ts +373 -0
  144. package/references/minimem/src/cli/sync/registry.ts +279 -0
  145. package/references/minimem/src/cli/sync/state.ts +358 -0
  146. package/references/minimem/src/cli/sync/validation.ts +206 -0
  147. package/references/minimem/src/cli/sync/watcher.ts +237 -0
  148. package/references/minimem/src/cli/version.ts +34 -0
  149. package/references/minimem/src/core/index.ts +9 -0
  150. package/references/minimem/src/core/indexer.ts +628 -0
  151. package/references/minimem/src/core/searcher.ts +221 -0
  152. package/references/minimem/src/db/schema.ts +183 -0
  153. package/references/minimem/src/db/sqlite-vec.ts +24 -0
  154. package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
  155. package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
  156. package/references/minimem/src/embeddings/batch-openai.ts +409 -0
  157. package/references/minimem/src/embeddings/embeddings.ts +434 -0
  158. package/references/minimem/src/index.ts +132 -0
  159. package/references/minimem/src/internal.ts +299 -0
  160. package/references/minimem/src/minimem.ts +1291 -0
  161. package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
  162. package/references/minimem/src/search/graph.ts +234 -0
  163. package/references/minimem/src/search/hybrid.ts +151 -0
  164. package/references/minimem/src/search/search.ts +256 -0
  165. package/references/minimem/src/server/__tests__/mcp.test.ts +347 -0
  166. package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
  167. package/references/minimem/src/server/mcp.ts +326 -0
  168. package/references/minimem/src/server/tools.ts +720 -0
  169. package/references/minimem/src/session.ts +460 -0
  170. package/references/minimem/src/store/__tests__/manifest.test.ts +177 -0
  171. package/references/minimem/src/store/__tests__/materialize.test.ts +52 -0
  172. package/references/minimem/src/store/__tests__/store-graph.test.ts +228 -0
  173. package/references/minimem/src/store/index.ts +27 -0
  174. package/references/minimem/src/store/manifest.ts +203 -0
  175. package/references/minimem/src/store/materialize.ts +185 -0
  176. package/references/minimem/src/store/store-graph.ts +252 -0
  177. package/references/minimem/tsconfig.json +19 -0
  178. package/references/minimem/tsup.config.ts +26 -0
  179. package/references/minimem/vitest.config.ts +29 -0
  180. package/references/openteams/src/cli/generate.ts +23 -1
  181. package/references/openteams/src/generators/agent-prompt-generator.test.ts +94 -0
  182. package/references/openteams/src/generators/agent-prompt-generator.ts +42 -13
  183. package/references/openteams/src/generators/package-generator.ts +9 -1
  184. package/references/openteams/src/generators/skill-generator.test.ts +28 -0
  185. package/references/openteams/src/generators/skill-generator.ts +10 -4
  186. package/references/skill-tree/.claude/settings.json +6 -0
  187. package/references/skill-tree/.sudocode/issues.jsonl +19 -0
  188. package/references/skill-tree/.sudocode/specs.jsonl +3 -0
  189. package/references/skill-tree/CLAUDE.md +132 -0
  190. package/references/skill-tree/README.md +396 -0
  191. package/references/skill-tree/docs/GAPS_v1.md +221 -0
  192. package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
  193. package/references/skill-tree/docs/TODOS.md +91 -0
  194. package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
  195. package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
  196. package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
  197. package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
  198. package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
  199. package/references/skill-tree/docs/scraper/README.md +170 -0
  200. package/references/skill-tree/examples/basic-usage.ts +157 -0
  201. package/references/skill-tree/package-lock.json +1852 -0
  202. package/references/skill-tree/package.json +66 -0
  203. package/references/skill-tree/plan.md +78 -0
  204. package/references/skill-tree/scraper/README.md +123 -0
  205. package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
  206. package/references/skill-tree/scraper/docs/PLAN.md +336 -0
  207. package/references/skill-tree/scraper/drizzle.config.ts +10 -0
  208. package/references/skill-tree/scraper/package-lock.json +6329 -0
  209. package/references/skill-tree/scraper/package.json +68 -0
  210. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
  211. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
  212. package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
  213. package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
  214. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
  215. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
  216. package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
  217. package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
  218. package/references/skill-tree/scraper/tsup.config.ts +14 -0
  219. package/references/skill-tree/scraper/vitest.config.ts +17 -0
  220. package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
  221. package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
  222. package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
  223. package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
  224. package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
  225. package/references/skill-tree/test/run-all.ts +106 -0
  226. package/references/skill-tree/test/utils.ts +128 -0
  227. package/references/skill-tree/vitest.config.ts +16 -0
  228. package/references/swarmkit/src/commands/init/phases/configure.ts +0 -22
  229. package/references/swarmkit/src/commands/init/phases/global-setup.ts +5 -3
  230. package/references/swarmkit/src/commands/init/wizard.ts +2 -2
  231. package/references/swarmkit/src/packages/setup.test.ts +53 -7
  232. package/references/swarmkit/src/packages/setup.ts +37 -1
  233. package/scripts/bootstrap.mjs +26 -1
  234. package/scripts/generate-agents.mjs +5 -1
  235. package/scripts/map-hook.mjs +97 -64
  236. package/scripts/map-sidecar.mjs +179 -25
  237. package/scripts/team-loader.mjs +12 -41
  238. package/skills/swarm/SKILL.md +89 -25
  239. package/src/__tests__/agent-generator.test.mjs +6 -13
  240. package/src/__tests__/bootstrap.test.mjs +124 -1
  241. package/src/__tests__/config.test.mjs +200 -27
  242. package/src/__tests__/e2e-live-map.test.mjs +536 -0
  243. package/src/__tests__/e2e-mesh-sidecar.test.mjs +570 -0
  244. package/src/__tests__/e2e-native-task-hooks.test.mjs +376 -0
  245. package/src/__tests__/e2e-sidecar-bridge.test.mjs +477 -0
  246. package/src/__tests__/helpers.mjs +13 -0
  247. package/src/__tests__/inbox.test.mjs +22 -89
  248. package/src/__tests__/index.test.mjs +35 -9
  249. package/src/__tests__/integration.test.mjs +513 -0
  250. package/src/__tests__/map-events.test.mjs +514 -150
  251. package/src/__tests__/mesh-connection.test.mjs +308 -0
  252. package/src/__tests__/opentasks-client.test.mjs +517 -0
  253. package/src/__tests__/paths.test.mjs +185 -41
  254. package/src/__tests__/sidecar-client.test.mjs +35 -0
  255. package/src/__tests__/sidecar-server.test.mjs +124 -0
  256. package/src/__tests__/skilltree-client.test.mjs +80 -0
  257. package/src/agent-generator.mjs +104 -33
  258. package/src/bootstrap.mjs +150 -10
  259. package/src/config.mjs +81 -17
  260. package/src/context-output.mjs +58 -8
  261. package/src/inbox.mjs +9 -54
  262. package/src/index.mjs +39 -8
  263. package/src/map-connection.mjs +4 -3
  264. package/src/map-events.mjs +350 -80
  265. package/src/mesh-connection.mjs +148 -0
  266. package/src/opentasks-client.mjs +269 -0
  267. package/src/paths.mjs +182 -27
  268. package/src/sessionlog.mjs +14 -9
  269. package/src/sidecar-client.mjs +81 -27
  270. package/src/sidecar-server.mjs +175 -16
  271. package/src/skilltree-client.mjs +173 -0
  272. package/src/template.mjs +68 -4
  273. package/vitest.config.mjs +1 -0
@@ -0,0 +1,431 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const createFetchMock = (response?: unknown) =>
4
+ vi.fn(async () => ({
5
+ ok: true,
6
+ status: 200,
7
+ json: async () => response ?? { data: [{ embedding: [1, 2, 3] }] },
8
+ text: async () => JSON.stringify(response ?? { data: [{ embedding: [1, 2, 3] }] }),
9
+ })) as unknown as typeof fetch;
10
+
11
+ describe("OpenAI embedding provider", () => {
12
+ afterEach(() => {
13
+ vi.resetAllMocks();
14
+ vi.resetModules();
15
+ vi.unstubAllGlobals();
16
+ delete process.env.OPENAI_API_KEY;
17
+ });
18
+
19
+ it("makes requests with correct authorization", async () => {
20
+ const fetchMock = createFetchMock();
21
+ vi.stubGlobal("fetch", fetchMock);
22
+
23
+ const { createOpenAiEmbeddingProvider } = await import("../embeddings.js");
24
+
25
+ const result = await createOpenAiEmbeddingProvider({
26
+ provider: "openai",
27
+ openai: {
28
+ apiKey: "test-api-key",
29
+ },
30
+ model: "text-embedding-3-small",
31
+ });
32
+
33
+ await result.provider.embedQuery("hello");
34
+
35
+ expect(fetchMock).toHaveBeenCalledTimes(1);
36
+ const [url, init] = fetchMock.mock.calls[0] ?? [];
37
+ expect(url).toBe("https://api.openai.com/v1/embeddings");
38
+ const headers = (init?.headers ?? {}) as Record<string, string>;
39
+ expect(headers.Authorization).toBe("Bearer test-api-key");
40
+ expect(headers["Content-Type"]).toBe("application/json");
41
+ });
42
+
43
+ it("uses custom base URL", async () => {
44
+ const fetchMock = createFetchMock();
45
+ vi.stubGlobal("fetch", fetchMock);
46
+
47
+ const { createOpenAiEmbeddingProvider } = await import("../embeddings.js");
48
+
49
+ await createOpenAiEmbeddingProvider({
50
+ provider: "openai",
51
+ openai: {
52
+ apiKey: "test-key",
53
+ baseUrl: "https://custom.api.com/v1",
54
+ },
55
+ });
56
+
57
+ const result = await import("../embeddings.js").then((m) =>
58
+ m.createOpenAiEmbeddingProvider({
59
+ provider: "openai",
60
+ openai: {
61
+ apiKey: "test-key",
62
+ baseUrl: "https://custom.api.com/v1",
63
+ },
64
+ }),
65
+ );
66
+
67
+ await result.provider.embedQuery("hello");
68
+
69
+ const [url] = fetchMock.mock.calls[0] ?? [];
70
+ expect(url).toBe("https://custom.api.com/v1/embeddings");
71
+ });
72
+
73
+ it("uses OPENAI_API_KEY env var when no apiKey provided", async () => {
74
+ process.env.OPENAI_API_KEY = "env-api-key";
75
+
76
+ const fetchMock = createFetchMock();
77
+ vi.stubGlobal("fetch", fetchMock);
78
+
79
+ const { createOpenAiEmbeddingProvider } = await import("../embeddings.js");
80
+
81
+ const result = await createOpenAiEmbeddingProvider({
82
+ provider: "openai",
83
+ });
84
+
85
+ await result.provider.embedQuery("hello");
86
+
87
+ const headers = (fetchMock.mock.calls[0]?.[1]?.headers ?? {}) as Record<string, string>;
88
+ expect(headers.Authorization).toBe("Bearer env-api-key");
89
+ });
90
+
91
+ it("throws when no API key available", async () => {
92
+ const { createOpenAiEmbeddingProvider } = await import("../embeddings.js");
93
+
94
+ await expect(
95
+ createOpenAiEmbeddingProvider({
96
+ provider: "openai",
97
+ }),
98
+ ).rejects.toThrow(/API key/i);
99
+ });
100
+
101
+ it("embedBatch sends multiple texts", async () => {
102
+ const fetchMock = vi.fn(async () => ({
103
+ ok: true,
104
+ status: 200,
105
+ json: async () => ({
106
+ data: [{ embedding: [1, 0, 0] }, { embedding: [0, 1, 0] }, { embedding: [0, 0, 1] }],
107
+ }),
108
+ })) as unknown as typeof fetch;
109
+ vi.stubGlobal("fetch", fetchMock);
110
+
111
+ const { createOpenAiEmbeddingProvider } = await import("../embeddings.js");
112
+
113
+ const result = await createOpenAiEmbeddingProvider({
114
+ provider: "openai",
115
+ openai: { apiKey: "test-key" },
116
+ });
117
+
118
+ const embeddings = await result.provider.embedBatch(["hello", "world", "test"]);
119
+
120
+ expect(embeddings).toHaveLength(3);
121
+ const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}"));
122
+ expect(body.input).toEqual(["hello", "world", "test"]);
123
+ });
124
+
125
+ it("includes custom headers", async () => {
126
+ const fetchMock = createFetchMock();
127
+ vi.stubGlobal("fetch", fetchMock);
128
+
129
+ const { createOpenAiEmbeddingProvider } = await import("../embeddings.js");
130
+
131
+ const result = await createOpenAiEmbeddingProvider({
132
+ provider: "openai",
133
+ openai: {
134
+ apiKey: "test-key",
135
+ headers: { "X-Custom": "value" },
136
+ },
137
+ });
138
+
139
+ await result.provider.embedQuery("hello");
140
+
141
+ const headers = (fetchMock.mock.calls[0]?.[1]?.headers ?? {}) as Record<string, string>;
142
+ expect(headers["X-Custom"]).toBe("value");
143
+ });
144
+ });
145
+
146
+ describe("Gemini embedding provider", () => {
147
+ afterEach(() => {
148
+ vi.resetAllMocks();
149
+ vi.resetModules();
150
+ vi.unstubAllGlobals();
151
+ delete process.env.GOOGLE_API_KEY;
152
+ delete process.env.GEMINI_API_KEY;
153
+ });
154
+
155
+ it("makes requests with x-goog-api-key header", async () => {
156
+ const fetchMock = vi.fn(async () => ({
157
+ ok: true,
158
+ status: 200,
159
+ json: async () => ({ embedding: { values: [1, 2, 3] } }),
160
+ })) as unknown as typeof fetch;
161
+ vi.stubGlobal("fetch", fetchMock);
162
+
163
+ const { createGeminiEmbeddingProvider } = await import("../embeddings.js");
164
+
165
+ const result = await createGeminiEmbeddingProvider({
166
+ provider: "gemini",
167
+ gemini: {
168
+ apiKey: "gemini-test-key",
169
+ },
170
+ });
171
+
172
+ await result.provider.embedQuery("hello");
173
+
174
+ const [url, init] = fetchMock.mock.calls[0] ?? [];
175
+ expect(url).toContain("generativelanguage.googleapis.com");
176
+ expect(url).toContain(":embedContent");
177
+ const headers = (init?.headers ?? {}) as Record<string, string>;
178
+ expect(headers["x-goog-api-key"]).toBe("gemini-test-key");
179
+ });
180
+
181
+ it("uses GOOGLE_API_KEY env var", async () => {
182
+ process.env.GOOGLE_API_KEY = "google-env-key";
183
+
184
+ const fetchMock = vi.fn(async () => ({
185
+ ok: true,
186
+ status: 200,
187
+ json: async () => ({ embedding: { values: [1, 2, 3] } }),
188
+ })) as unknown as typeof fetch;
189
+ vi.stubGlobal("fetch", fetchMock);
190
+
191
+ const { createGeminiEmbeddingProvider } = await import("../embeddings.js");
192
+
193
+ const result = await createGeminiEmbeddingProvider({
194
+ provider: "gemini",
195
+ });
196
+
197
+ await result.provider.embedQuery("hello");
198
+
199
+ const headers = (fetchMock.mock.calls[0]?.[1]?.headers ?? {}) as Record<string, string>;
200
+ expect(headers["x-goog-api-key"]).toBe("google-env-key");
201
+ });
202
+
203
+ it("uses GEMINI_API_KEY env var as fallback", async () => {
204
+ process.env.GEMINI_API_KEY = "gemini-env-key";
205
+
206
+ const fetchMock = vi.fn(async () => ({
207
+ ok: true,
208
+ status: 200,
209
+ json: async () => ({ embedding: { values: [1, 2, 3] } }),
210
+ })) as unknown as typeof fetch;
211
+ vi.stubGlobal("fetch", fetchMock);
212
+
213
+ const { createGeminiEmbeddingProvider } = await import("../embeddings.js");
214
+
215
+ const result = await createGeminiEmbeddingProvider({
216
+ provider: "gemini",
217
+ });
218
+
219
+ await result.provider.embedQuery("hello");
220
+
221
+ const headers = (fetchMock.mock.calls[0]?.[1]?.headers ?? {}) as Record<string, string>;
222
+ expect(headers["x-goog-api-key"]).toBe("gemini-env-key");
223
+ });
224
+
225
+ it("embedBatch uses batchEmbedContents endpoint", async () => {
226
+ const fetchMock = vi.fn(async () => ({
227
+ ok: true,
228
+ status: 200,
229
+ json: async () => ({
230
+ embeddings: [{ values: [1, 0] }, { values: [0, 1] }],
231
+ }),
232
+ })) as unknown as typeof fetch;
233
+ vi.stubGlobal("fetch", fetchMock);
234
+
235
+ const { createGeminiEmbeddingProvider } = await import("../embeddings.js");
236
+
237
+ const result = await createGeminiEmbeddingProvider({
238
+ provider: "gemini",
239
+ gemini: { apiKey: "test-key" },
240
+ });
241
+
242
+ const embeddings = await result.provider.embedBatch(["hello", "world"]);
243
+
244
+ expect(embeddings).toHaveLength(2);
245
+ const [url] = fetchMock.mock.calls[0] ?? [];
246
+ expect(url).toContain(":batchEmbedContents");
247
+ });
248
+
249
+ it("uses custom model", async () => {
250
+ const fetchMock = vi.fn(async () => ({
251
+ ok: true,
252
+ status: 200,
253
+ json: async () => ({ embedding: { values: [1, 2, 3] } }),
254
+ })) as unknown as typeof fetch;
255
+ vi.stubGlobal("fetch", fetchMock);
256
+
257
+ const { createGeminiEmbeddingProvider } = await import("../embeddings.js");
258
+
259
+ const result = await createGeminiEmbeddingProvider({
260
+ provider: "gemini",
261
+ gemini: { apiKey: "test-key" },
262
+ model: "text-embedding-004",
263
+ });
264
+
265
+ await result.provider.embedQuery("hello");
266
+
267
+ const [url] = fetchMock.mock.calls[0] ?? [];
268
+ expect(url).toContain("models/text-embedding-004");
269
+ });
270
+ });
271
+
272
+ describe("createEmbeddingProvider auto selection", () => {
273
+ afterEach(() => {
274
+ vi.resetAllMocks();
275
+ vi.resetModules();
276
+ vi.unstubAllGlobals();
277
+ delete process.env.OPENAI_API_KEY;
278
+ delete process.env.GOOGLE_API_KEY;
279
+ delete process.env.GEMINI_API_KEY;
280
+ });
281
+
282
+ it("prefers OpenAI when OPENAI_API_KEY is set", async () => {
283
+ process.env.OPENAI_API_KEY = "openai-key";
284
+
285
+ const fetchMock = createFetchMock();
286
+ vi.stubGlobal("fetch", fetchMock);
287
+
288
+ const { createEmbeddingProvider } = await import("../embeddings.js");
289
+
290
+ const result = await createEmbeddingProvider({
291
+ provider: "auto",
292
+ });
293
+
294
+ expect(result.requestedProvider).toBe("auto");
295
+ expect(result.provider.id).toBe("openai");
296
+ });
297
+
298
+ it("falls back to Gemini when OpenAI key missing", async () => {
299
+ process.env.GOOGLE_API_KEY = "google-key";
300
+
301
+ const fetchMock = vi.fn(async () => ({
302
+ ok: true,
303
+ status: 200,
304
+ json: async () => ({ embedding: { values: [1, 2, 3] } }),
305
+ })) as unknown as typeof fetch;
306
+ vi.stubGlobal("fetch", fetchMock);
307
+
308
+ const { createEmbeddingProvider } = await import("../embeddings.js");
309
+
310
+ const result = await createEmbeddingProvider({
311
+ provider: "auto",
312
+ });
313
+
314
+ expect(result.requestedProvider).toBe("auto");
315
+ expect(result.provider.id).toBe("gemini");
316
+ });
317
+
318
+ it("falls back to BM25-only when no providers available", async () => {
319
+ const { createEmbeddingProvider } = await import("../embeddings.js");
320
+
321
+ const result = await createEmbeddingProvider({
322
+ provider: "auto",
323
+ });
324
+
325
+ // Should fall back to BM25-only (no-op provider)
326
+ expect(result.provider.id).toBe("none");
327
+ expect(result.provider.model).toBe("bm25-only");
328
+ expect(result.fallbackFrom).toBe("auto");
329
+ expect(result.fallbackReason).toContain("BM25");
330
+
331
+ // No-op provider should return empty embeddings
332
+ const queryEmbed = await result.provider.embedQuery("test");
333
+ expect(queryEmbed).toEqual([]);
334
+
335
+ const batchEmbed = await result.provider.embedBatch(["test1", "test2"]);
336
+ expect(batchEmbed).toEqual([[], []]);
337
+ });
338
+ });
339
+
340
+ describe("createEmbeddingProvider with fallback", () => {
341
+ afterEach(() => {
342
+ vi.resetAllMocks();
343
+ vi.resetModules();
344
+ vi.unstubAllGlobals();
345
+ delete process.env.OPENAI_API_KEY;
346
+ delete process.env.GOOGLE_API_KEY;
347
+ });
348
+
349
+ it("falls back to specified provider on failure", async () => {
350
+ process.env.GOOGLE_API_KEY = "google-key";
351
+
352
+ const fetchMock = vi.fn(async () => ({
353
+ ok: true,
354
+ status: 200,
355
+ json: async () => ({ embedding: { values: [1, 2, 3] } }),
356
+ })) as unknown as typeof fetch;
357
+ vi.stubGlobal("fetch", fetchMock);
358
+
359
+ const { createEmbeddingProvider } = await import("../embeddings.js");
360
+
361
+ const result = await createEmbeddingProvider({
362
+ provider: "openai",
363
+ fallback: "gemini",
364
+ });
365
+
366
+ expect(result.provider.id).toBe("gemini");
367
+ expect(result.fallbackFrom).toBe("openai");
368
+ expect(result.fallbackReason).toContain("API key");
369
+ });
370
+
371
+ it("throws when fallback is none", async () => {
372
+ const { createEmbeddingProvider } = await import("../embeddings.js");
373
+
374
+ await expect(
375
+ createEmbeddingProvider({
376
+ provider: "openai",
377
+ fallback: "none",
378
+ }),
379
+ ).rejects.toThrow(/API key/i);
380
+ });
381
+ });
382
+
383
+ describe("local embedding provider", () => {
384
+ afterEach(() => {
385
+ vi.resetAllMocks();
386
+ vi.resetModules();
387
+ vi.unstubAllGlobals();
388
+ vi.doUnmock("node-llama-cpp");
389
+ });
390
+
391
+ it("throws helpful error when node-llama-cpp is missing", async () => {
392
+ vi.doMock("node-llama-cpp", () => {
393
+ throw Object.assign(new Error("Cannot find package 'node-llama-cpp'"), {
394
+ code: "ERR_MODULE_NOT_FOUND",
395
+ });
396
+ });
397
+
398
+ const { createEmbeddingProvider } = await import("../embeddings.js");
399
+
400
+ await expect(
401
+ createEmbeddingProvider({
402
+ provider: "local",
403
+ fallback: "none",
404
+ }),
405
+ ).rejects.toThrow(/node-llama-cpp/i);
406
+ });
407
+
408
+ it("falls back to OpenAI when local fails", async () => {
409
+ process.env.OPENAI_API_KEY = "openai-key";
410
+
411
+ vi.doMock("node-llama-cpp", () => {
412
+ throw Object.assign(new Error("Cannot find package 'node-llama-cpp'"), {
413
+ code: "ERR_MODULE_NOT_FOUND",
414
+ });
415
+ });
416
+
417
+ const fetchMock = createFetchMock();
418
+ vi.stubGlobal("fetch", fetchMock);
419
+
420
+ const { createEmbeddingProvider } = await import("../embeddings.js");
421
+
422
+ const result = await createEmbeddingProvider({
423
+ provider: "local",
424
+ fallback: "openai",
425
+ });
426
+
427
+ expect(result.provider.id).toBe("openai");
428
+ expect(result.fallbackFrom).toBe("local");
429
+ expect(result.fallbackReason).toContain("node-llama-cpp");
430
+ });
431
+ });