@kb-labs/adapters 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/workflows/ci.yml +13 -0
  3. package/.github/workflows/deploy.yml +28 -0
  4. package/.github/workflows/docker-build.yml +25 -0
  5. package/.github/workflows/drift-check.yml +10 -0
  6. package/.github/workflows/profiles-validate.yml +16 -0
  7. package/.github/workflows/release.yml +8 -0
  8. package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
  9. package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
  10. package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
  11. package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
  12. package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
  13. package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
  14. package/.kb/devkit/agents/release-manager/context.globs +7 -0
  15. package/.kb/devkit/agents/release-manager/prompt.md +27 -0
  16. package/.kb/devkit/agents/release-manager/runbook.md +17 -0
  17. package/.kb/devkit/agents/test-generator/context.globs +7 -0
  18. package/.kb/devkit/agents/test-generator/prompt.md +27 -0
  19. package/.kb/devkit/agents/test-generator/runbook.md +18 -0
  20. package/CONTRIBUTING.md +90 -0
  21. package/IMPLEMENTATION_COMPLETE.md +416 -0
  22. package/LICENSE +186 -0
  23. package/README-TEMPLATE.md +179 -0
  24. package/README.md +306 -0
  25. package/docs/DOCUMENTATION.md +74 -0
  26. package/docs/adr/0000-template.md +49 -0
  27. package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
  28. package/docs/adr/0002-plugins-and-extensibility.md +46 -0
  29. package/docs/adr/0003-package-and-module-boundaries.md +37 -0
  30. package/docs/adr/0004-versioning-and-release-policy.md +38 -0
  31. package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
  32. package/docs/adr/0006-adopt-devkit-sync.md +47 -0
  33. package/docs/adr/0007-drift-kit-check.md +72 -0
  34. package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
  35. package/docs/naming-convention.md +272 -0
  36. package/eslint.config.js +27 -0
  37. package/kb-labs.config.json +5 -0
  38. package/package.json +84 -0
  39. package/package.json.bin +25 -0
  40. package/package.json.lib +30 -0
  41. package/packages/adapters-analytics-duckdb/package.json +54 -0
  42. package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
  43. package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
  44. package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
  45. package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
  46. package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
  47. package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
  48. package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
  49. package/packages/adapters-analytics-file/README.md +32 -0
  50. package/packages/adapters-analytics-file/eslint.config.js +27 -0
  51. package/packages/adapters-analytics-file/package.json +50 -0
  52. package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
  53. package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
  54. package/packages/adapters-analytics-file/src/index.test.ts +214 -0
  55. package/packages/adapters-analytics-file/src/index.ts +830 -0
  56. package/packages/adapters-analytics-file/src/manifest.ts +45 -0
  57. package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
  58. package/packages/adapters-analytics-file/tsconfig.json +9 -0
  59. package/packages/adapters-analytics-file/tsup.config.ts +9 -0
  60. package/packages/adapters-analytics-sqlite/package.json +55 -0
  61. package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
  62. package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
  63. package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
  64. package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
  65. package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
  66. package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
  67. package/packages/adapters-environment-docker/README.md +28 -0
  68. package/packages/adapters-environment-docker/eslint.config.js +5 -0
  69. package/packages/adapters-environment-docker/package.json +49 -0
  70. package/packages/adapters-environment-docker/src/index.test.ts +138 -0
  71. package/packages/adapters-environment-docker/src/index.ts +439 -0
  72. package/packages/adapters-environment-docker/src/manifest.ts +65 -0
  73. package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
  74. package/packages/adapters-environment-docker/tsconfig.json +16 -0
  75. package/packages/adapters-environment-docker/tsup.config.ts +9 -0
  76. package/packages/adapters-eventbus-cache/README.md +242 -0
  77. package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
  78. package/packages/adapters-eventbus-cache/package.json +46 -0
  79. package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
  80. package/packages/adapters-eventbus-cache/src/index.ts +215 -0
  81. package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
  82. package/packages/adapters-eventbus-cache/src/types.ts +58 -0
  83. package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
  84. package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
  85. package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
  86. package/packages/adapters-fs/README.md +171 -0
  87. package/packages/adapters-fs/allowed.txt +1 -0
  88. package/packages/adapters-fs/conflict.txt +1 -0
  89. package/packages/adapters-fs/dest.txt +1 -0
  90. package/packages/adapters-fs/eslint.config.js +27 -0
  91. package/packages/adapters-fs/exists.txt +1 -0
  92. package/packages/adapters-fs/not-allowed.txt +1 -0
  93. package/packages/adapters-fs/other.txt +1 -0
  94. package/packages/adapters-fs/package.json +55 -0
  95. package/packages/adapters-fs/public/file1.txt +1 -0
  96. package/packages/adapters-fs/public/file2.txt +1 -0
  97. package/packages/adapters-fs/secret.txt +1 -0
  98. package/packages/adapters-fs/secrets/key.txt +1 -0
  99. package/packages/adapters-fs/src/index.test.ts +243 -0
  100. package/packages/adapters-fs/src/index.ts +258 -0
  101. package/packages/adapters-fs/src/manifest.ts +35 -0
  102. package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
  103. package/packages/adapters-fs/src/secure-storage.ts +268 -0
  104. package/packages/adapters-fs/test.json +1 -0
  105. package/packages/adapters-fs/test.txt +1 -0
  106. package/packages/adapters-fs/test.xyz +1 -0
  107. package/packages/adapters-fs/test1.txt +1 -0
  108. package/packages/adapters-fs/test2.txt +1 -0
  109. package/packages/adapters-fs/tsconfig.build.json +15 -0
  110. package/packages/adapters-fs/tsconfig.json +9 -0
  111. package/packages/adapters-fs/tsup.config.ts +8 -0
  112. package/packages/adapters-fs/vitest.config.ts +19 -0
  113. package/packages/adapters-log-ringbuffer/README.md +228 -0
  114. package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
  115. package/packages/adapters-log-ringbuffer/package.json +47 -0
  116. package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
  117. package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
  118. package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
  119. package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
  120. package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
  121. package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
  122. package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
  123. package/packages/adapters-log-sqlite/README.md +396 -0
  124. package/packages/adapters-log-sqlite/eslint.config.js +27 -0
  125. package/packages/adapters-log-sqlite/package.json +49 -0
  126. package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
  127. package/packages/adapters-log-sqlite/src/index.ts +1068 -0
  128. package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
  129. package/packages/adapters-log-sqlite/src/schema.sql +46 -0
  130. package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
  131. package/packages/adapters-log-sqlite/tsconfig.json +9 -0
  132. package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
  133. package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
  134. package/packages/adapters-mongodb/README.md +147 -0
  135. package/packages/adapters-mongodb/eslint.config.js +27 -0
  136. package/packages/adapters-mongodb/package.json +53 -0
  137. package/packages/adapters-mongodb/src/index.ts +428 -0
  138. package/packages/adapters-mongodb/src/manifest.ts +45 -0
  139. package/packages/adapters-mongodb/src/secure-document.ts +231 -0
  140. package/packages/adapters-mongodb/tsconfig.build.json +15 -0
  141. package/packages/adapters-mongodb/tsconfig.json +9 -0
  142. package/packages/adapters-mongodb/tsup.config.ts +8 -0
  143. package/packages/adapters-openai/README.md +151 -0
  144. package/packages/adapters-openai/embeddings.ts +37 -0
  145. package/packages/adapters-openai/eslint.config.js +26 -0
  146. package/packages/adapters-openai/index.ts +22 -0
  147. package/packages/adapters-openai/package.json +57 -0
  148. package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
  149. package/packages/adapters-openai/src/embeddings.ts +104 -0
  150. package/packages/adapters-openai/src/index.ts +13 -0
  151. package/packages/adapters-openai/src/llm.ts +304 -0
  152. package/packages/adapters-openai/src/manifest.ts +47 -0
  153. package/packages/adapters-openai/tsconfig.build.json +15 -0
  154. package/packages/adapters-openai/tsconfig.json +9 -0
  155. package/packages/adapters-openai/tsup.config.ts +8 -0
  156. package/packages/adapters-pino/README.md +152 -0
  157. package/packages/adapters-pino/eslint.config.js +27 -0
  158. package/packages/adapters-pino/package.json +49 -0
  159. package/packages/adapters-pino/src/index.test.ts +44 -0
  160. package/packages/adapters-pino/src/index.ts +322 -0
  161. package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
  162. package/packages/adapters-pino/src/manifest.ts +49 -0
  163. package/packages/adapters-pino/tsconfig.build.json +15 -0
  164. package/packages/adapters-pino/tsconfig.json +9 -0
  165. package/packages/adapters-pino/tsup.config.ts +9 -0
  166. package/packages/adapters-pino-http/README.md +141 -0
  167. package/packages/adapters-pino-http/eslint.config.js +27 -0
  168. package/packages/adapters-pino-http/package.json +46 -0
  169. package/packages/adapters-pino-http/src/index.ts +229 -0
  170. package/packages/adapters-pino-http/tsconfig.build.json +15 -0
  171. package/packages/adapters-pino-http/tsconfig.json +9 -0
  172. package/packages/adapters-pino-http/tsup.config.ts +9 -0
  173. package/packages/adapters-qdrant/README.md +166 -0
  174. package/packages/adapters-qdrant/eslint.config.js +27 -0
  175. package/packages/adapters-qdrant/package.json +49 -0
  176. package/packages/adapters-qdrant/src/index.ts +490 -0
  177. package/packages/adapters-qdrant/src/manifest.ts +54 -0
  178. package/packages/adapters-qdrant/src/retry.ts +204 -0
  179. package/packages/adapters-qdrant/tsconfig.build.json +15 -0
  180. package/packages/adapters-qdrant/tsconfig.json +9 -0
  181. package/packages/adapters-qdrant/tsup.config.ts +9 -0
  182. package/packages/adapters-redis/README.md +159 -0
  183. package/packages/adapters-redis/eslint.config.js +27 -0
  184. package/packages/adapters-redis/package.json +49 -0
  185. package/packages/adapters-redis/src/index.ts +164 -0
  186. package/packages/adapters-redis/src/manifest.ts +49 -0
  187. package/packages/adapters-redis/tsconfig.build.json +15 -0
  188. package/packages/adapters-redis/tsconfig.json +9 -0
  189. package/packages/adapters-redis/tsup.config.ts +9 -0
  190. package/packages/adapters-snapshot-localfs/README.md +10 -0
  191. package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
  192. package/packages/adapters-snapshot-localfs/package.json +46 -0
  193. package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
  194. package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
  195. package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
  196. package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
  197. package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
  198. package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
  199. package/packages/adapters-sqlite/README.md +163 -0
  200. package/packages/adapters-sqlite/eslint.config.js +27 -0
  201. package/packages/adapters-sqlite/package.json +54 -0
  202. package/packages/adapters-sqlite/src/index.test.ts +245 -0
  203. package/packages/adapters-sqlite/src/index.ts +382 -0
  204. package/packages/adapters-sqlite/src/manifest.ts +47 -0
  205. package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
  206. package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
  207. package/packages/adapters-sqlite/tsconfig.build.json +15 -0
  208. package/packages/adapters-sqlite/tsconfig.json +9 -0
  209. package/packages/adapters-sqlite/tsup.config.ts +8 -0
  210. package/packages/adapters-sqlite/vitest.config.ts +19 -0
  211. package/packages/adapters-transport/README.md +170 -0
  212. package/packages/adapters-transport/eslint.config.js +27 -0
  213. package/packages/adapters-transport/package.json +49 -0
  214. package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
  215. package/packages/adapters-transport/src/index.ts +101 -0
  216. package/packages/adapters-transport/src/ipc-transport.ts +228 -0
  217. package/packages/adapters-transport/src/transport.ts +224 -0
  218. package/packages/adapters-transport/src/types.ts +92 -0
  219. package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
  220. package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
  221. package/packages/adapters-transport/tsconfig.build.json +15 -0
  222. package/packages/adapters-transport/tsconfig.json +9 -0
  223. package/packages/adapters-transport/tsup.config.ts +9 -0
  224. package/packages/adapters-vibeproxy/README.md +159 -0
  225. package/packages/adapters-vibeproxy/eslint.config.js +27 -0
  226. package/packages/adapters-vibeproxy/package.json +51 -0
  227. package/packages/adapters-vibeproxy/src/index.ts +13 -0
  228. package/packages/adapters-vibeproxy/src/llm.ts +437 -0
  229. package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
  230. package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
  231. package/packages/adapters-vibeproxy/tsconfig.json +9 -0
  232. package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
  233. package/packages/adapters-workspace-agent/package.json +46 -0
  234. package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
  235. package/packages/adapters-workspace-agent/src/index.ts +220 -0
  236. package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
  237. package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
  238. package/packages/adapters-workspace-agent/tsconfig.json +16 -0
  239. package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
  240. package/packages/adapters-workspace-localfs/README.md +9 -0
  241. package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
  242. package/packages/adapters-workspace-localfs/package.json +46 -0
  243. package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
  244. package/packages/adapters-workspace-localfs/src/index.ts +172 -0
  245. package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
  246. package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
  247. package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
  248. package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
  249. package/packages/adapters-workspace-worktree/README.md +9 -0
  250. package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
  251. package/packages/adapters-workspace-worktree/package.json +46 -0
  252. package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
  253. package/packages/adapters-workspace-worktree/src/index.ts +245 -0
  254. package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
  255. package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
  256. package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
  257. package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
  258. package/pnpm-workspace.yaml +2800 -0
  259. package/prettierrc.json +1 -0
  260. package/scripts/devkit-sync.mjs +37 -0
  261. package/scripts/hooks/post-push +9 -0
  262. package/scripts/hooks/pre-commit +9 -0
  263. package/scripts/hooks/pre-push +9 -0
  264. package/test-integration.ts +242 -0
  265. package/test.txt +1 -0
  266. package/tsconfig.base.json +6 -0
  267. package/tsconfig.build.json +15 -0
  268. package/tsconfig.json +9 -0
  269. package/tsconfig.paths.json +26 -0
  270. package/tsconfig.tools.json +17 -0
  271. package/tsup.config.bin.ts +34 -0
  272. package/tsup.config.cli.ts +41 -0
  273. package/tsup.config.dual.ts +46 -0
  274. package/tsup.config.ts +36 -0
  275. package/tsup.external.json +103 -0
  276. package/vitest.config.ts +2 -0
@@ -0,0 +1,490 @@
1
+ /**
2
+ * @module @kb-labs/adapters-qdrant
3
+ * Qdrant adapter implementing IVectorStore interface.
4
+ *
5
+ * All Qdrant client calls that touch the network are wrapped with
6
+ * exponential-backoff retry logic ({@link withRetry}) so that transient
7
+ * failures (ECONNREFUSED, ETIMEDOUT, HTTP 503, etc.) are handled
8
+ * automatically without propagating to callers.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createAdapter } from '@kb-labs/adapters-qdrant';
13
+ *
14
+ * const vectorStore = createAdapter({
15
+ * url: 'http://localhost:6333',
16
+ * apiKey: process.env.QDRANT_API_KEY,
17
+ * collectionName: 'my-collection',
18
+ * dimension: 1536,
19
+ * });
20
+ *
21
+ * await vectorStore.upsert([
22
+ * { id: '1', vector: [...], metadata: { text: 'hello' } },
23
+ * ]);
24
+ *
25
+ * const results = await vectorStore.search([...], 10);
26
+ * ```
27
+ */
28
+
29
+ import { QdrantClient } from "@qdrant/js-client-rest";
30
+ import type {
31
+ IVectorStore,
32
+ VectorRecord,
33
+ VectorSearchResult,
34
+ VectorFilter,
35
+ } from "@kb-labs/core-platform";
36
+
37
+ // Re-export manifest
38
+ export { manifest } from "./manifest.js";
39
+ // Re-export retry utilities so callers can customise if needed
40
+ export { withRetry, isTransientError } from "./retry.js";
41
+ export type { RetryOptions } from "./retry.js";
42
+
43
+ import { createHash } from "node:crypto";
44
+ import { withRetry, type RetryOptions } from "./retry.js";
45
+
46
+ /**
47
+ * Configuration for Qdrant vector store adapter.
48
+ */
49
+ export interface QdrantVectorStoreConfig {
50
+ /** Qdrant server URL (e.g., 'http://localhost:6333') */
51
+ url: string;
52
+ /** API key for authentication (optional) */
53
+ apiKey?: string;
54
+ /** Collection name (default: 'kb-vectors') */
55
+ collectionName?: string;
56
+ /** Vector dimension (default: 1536, for OpenAI text-embedding-3-small) */
57
+ dimension?: number;
58
+ /** Request timeout in ms (default: 30000) */
59
+ timeout?: number;
60
+ /**
61
+ * Retry configuration applied to every Qdrant client call.
62
+ * Transient errors (ECONNREFUSED, ETIMEDOUT, HTTP 503 / 429 / 502 / 504)
63
+ * are retried automatically with full-jitter exponential back-off.
64
+ *
65
+ * Set `maxAttempts: 1` to disable retries entirely.
66
+ */
67
+ retry?: RetryOptions;
68
+ }
69
+
70
+ /**
71
+ * Convert a string to a deterministic UUID v4-like format.
72
+ * Qdrant requires point IDs to be either unsigned integers or UUIDs.
73
+ */
74
+ function stringToUUID(str: string): string {
75
+ const hash = createHash("sha256").update(str).digest();
76
+ // Format as UUID v4 (8-4-4-4-12 hex digits)
77
+ return [
78
+ hash.slice(0, 4).toString("hex"),
79
+ hash.slice(4, 6).toString("hex"),
80
+ hash.slice(6, 8).toString("hex"),
81
+ hash.slice(8, 10).toString("hex"),
82
+ hash.slice(10, 16).toString("hex"),
83
+ ].join("-");
84
+ }
85
+
86
+ /**
87
+ * Default retry options used for all Qdrant client calls.
88
+ *
89
+ * Strategy: up to 4 attempts, full-jitter exponential back-off starting at
90
+ * 200 ms, capped at 10 s. That gives worst-case wait of ~10 s before the
91
+ * final attempt, which comfortably covers a Qdrant cold-start or a brief
92
+ * 503 during a rolling restart.
93
+ */
94
+ const DEFAULT_RETRY_OPTIONS: Required<
95
+ Pick<RetryOptions, "maxAttempts" | "baseDelayMs" | "maxDelayMs">
96
+ > = {
97
+ maxAttempts: 4,
98
+ baseDelayMs: 200,
99
+ maxDelayMs: 10_000,
100
+ };
101
+
102
+ /**
103
+ * Qdrant implementation of IVectorStore interface.
104
+ *
105
+ * All network-facing operations (`search`, `upsert`, `delete`, `scroll`,
106
+ * `retrieve`, `getCollections`, `createCollection`, `getCollection`) are
107
+ * wrapped with {@link withRetry} to transparently handle transient errors.
108
+ */
109
+ export class QdrantVectorStore implements IVectorStore {
110
+ private client: QdrantClient;
111
+ private collectionName: string;
112
+ private dimension: number;
113
+ private initialized = false;
114
+ private url: string;
115
+ private retryOptions: RetryOptions;
116
+
117
+ // Adaptive concurrency state
118
+ private concurrency = 3; // Start with 3 parallel batches
119
+ private consecutiveErrors = 0;
120
+ private consecutiveSuccesses = 0;
121
+
122
+ constructor(config: QdrantVectorStoreConfig) {
123
+ this.url = config.url;
124
+ this.client = new QdrantClient({
125
+ url: config.url,
126
+ apiKey: config.apiKey,
127
+ timeout: config.timeout ?? 30000,
128
+ });
129
+ this.collectionName = config.collectionName ?? "kb-vectors";
130
+ this.dimension = config.dimension ?? 1536;
131
+ // Merge caller-supplied retry options over the defaults
132
+ this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...config.retry };
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Private helpers
137
+ // ---------------------------------------------------------------------------
138
+
139
+ /**
140
+ * Ensure collection exists with correct configuration.
141
+ *
142
+ * `getCollections` and `createCollection` are both wrapped with retry so
143
+ * that a fresh Qdrant instance that is still starting up (ECONNREFUSED) is
144
+ * handled gracefully.
145
+ */
146
+ private async ensureCollection(): Promise<void> {
147
+ if (this.initialized) {
148
+ return;
149
+ }
150
+
151
+ try {
152
+ const collections = await withRetry(
153
+ () => this.client.getCollections(),
154
+ this.retryOptions,
155
+ );
156
+
157
+ const exists = collections.collections.some(
158
+ (c) => c.name === this.collectionName,
159
+ );
160
+
161
+ if (!exists) {
162
+ await withRetry(
163
+ () =>
164
+ this.client.createCollection(this.collectionName, {
165
+ vectors: {
166
+ size: this.dimension,
167
+ distance: "Cosine",
168
+ },
169
+ }),
170
+ this.retryOptions,
171
+ );
172
+ }
173
+
174
+ this.initialized = true;
175
+ } catch (error) {
176
+ throw new Error(
177
+ `Failed to initialize Qdrant collection: ${error instanceof Error ? error.message : String(error)}`,
178
+ );
179
+ }
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // IVectorStore — required methods
184
+ // ---------------------------------------------------------------------------
185
+
186
+ /**
187
+ * Search for the nearest vectors.
188
+ *
189
+ * The underlying `this.client.search()` call is wrapped with
190
+ * {@link withRetry} to handle ECONNREFUSED, ETIMEDOUT, and HTTP 503
191
+ * transparently.
192
+ */
193
+ async search(
194
+ query: number[],
195
+ limit: number,
196
+ filter?: VectorFilter,
197
+ ): Promise<VectorSearchResult[]> {
198
+ await this.ensureCollection();
199
+
200
+ // Build Qdrant filter if provided
201
+ const qdrantFilter = filter
202
+ ? {
203
+ must: [
204
+ {
205
+ key: filter.field,
206
+ match:
207
+ filter.operator === "eq" ? { value: filter.value } : undefined,
208
+ range:
209
+ filter.operator === "gt" || filter.operator === "gte"
210
+ ? { gt: filter.value }
211
+ : filter.operator === "lt" || filter.operator === "lte"
212
+ ? { lt: filter.value }
213
+ : undefined,
214
+ },
215
+ ],
216
+ }
217
+ : undefined;
218
+
219
+ const searchParams = {
220
+ vector: query,
221
+ limit,
222
+ filter: qdrantFilter,
223
+ with_payload: true,
224
+ } as const;
225
+
226
+ const response = await withRetry(
227
+ () => this.client.search(this.collectionName, searchParams),
228
+ this.retryOptions,
229
+ );
230
+
231
+ return response.map((point) => ({
232
+ id: String(point.id),
233
+ score: point.score,
234
+ metadata: point.payload as Record<string, unknown> | undefined,
235
+ }));
236
+ }
237
+
238
+ /**
239
+ * Upsert vectors in batches with adaptive parallel processing.
240
+ *
241
+ * Each individual `this.client.upsert()` call is wrapped with
242
+ * {@link withRetry}. The adaptive-concurrency logic is preserved:
243
+ * on sustained success the concurrency goes up; on sustained failure
244
+ * it is halved (the retry wrapper already handles brief transient
245
+ * errors before they reach the concurrency bookkeeping).
246
+ */
247
+ async upsert(vectors: VectorRecord[]): Promise<void> {
248
+ await this.ensureCollection();
249
+
250
+ if (vectors.length === 0) {
251
+ return;
252
+ }
253
+
254
+ const points = vectors.map((record) => ({
255
+ id: stringToUUID(record.id),
256
+ vector: record.vector,
257
+ payload: record.metadata ?? {},
258
+ }));
259
+
260
+ // Batch upsert with adaptive parallel processing (Qdrant supports up to 100 points per request)
261
+ const batchSize = 100;
262
+ const _totalBatches = Math.ceil(points.length / batchSize);
263
+
264
+ // Create all batches
265
+ type QdrantPoint = {
266
+ id: string;
267
+ vector: number[];
268
+ payload: Record<string, unknown>;
269
+ };
270
+ const batches: QdrantPoint[][] = [];
271
+ for (let i = 0; i < points.length; i += batchSize) {
272
+ batches.push(points.slice(i, i + batchSize) as QdrantPoint[]);
273
+ }
274
+
275
+ // Process batches with adaptive concurrency
276
+ let batchIndex = 0;
277
+ while (batchIndex < batches.length) {
278
+ const currentConcurrency = Math.min(
279
+ this.concurrency,
280
+ batches.length - batchIndex,
281
+ );
282
+ const batchGroup = batches.slice(
283
+ batchIndex,
284
+ batchIndex + currentConcurrency,
285
+ );
286
+
287
+ const batchPromises = batchGroup.map(async (batch) => {
288
+ try {
289
+ // ⚡ Don't wait for indexing — let Qdrant index asynchronously.
290
+ // Transient failures are transparently retried before bubbling up.
291
+ await withRetry(
292
+ () =>
293
+ this.client.upsert(this.collectionName, {
294
+ wait: false,
295
+ points: batch,
296
+ }),
297
+ this.retryOptions,
298
+ );
299
+
300
+ // Success: increase concurrency gradually
301
+ this.consecutiveErrors = 0;
302
+ this.consecutiveSuccesses++;
303
+ if (this.consecutiveSuccesses >= 5 && this.concurrency < 10) {
304
+ this.concurrency++;
305
+ this.consecutiveSuccesses = 0;
306
+ }
307
+ } catch (error) {
308
+ // Persistent (non-transient) error after all retries — adjust concurrency
309
+ this.consecutiveSuccesses = 0;
310
+ this.consecutiveErrors++;
311
+ if (this.consecutiveErrors >= 2 && this.concurrency > 1) {
312
+ this.concurrency = Math.max(1, Math.floor(this.concurrency / 2));
313
+ this.consecutiveErrors = 0;
314
+ }
315
+
316
+ throw error;
317
+ }
318
+ });
319
+
320
+ // Wait for this group of concurrent batches to complete
321
+ try {
322
+ await Promise.all(batchPromises);
323
+ batchIndex += batchGroup.length;
324
+ } catch (_error) {
325
+ // If group fails, retry with reduced concurrency (already adjusted above)
326
+ // Don't increment batchIndex — retry same batches
327
+ }
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Delete vectors by ID.
333
+ *
334
+ * The underlying `this.client.delete()` call is wrapped with
335
+ * {@link withRetry}.
336
+ */
337
+ async delete(ids: string[]): Promise<void> {
338
+ await this.ensureCollection();
339
+
340
+ if (ids.length === 0) {
341
+ return;
342
+ }
343
+
344
+ const uuids = ids.map(stringToUUID);
345
+
346
+ await withRetry(
347
+ () =>
348
+ this.client.delete(this.collectionName, {
349
+ wait: false, // ⚡ Don't wait for indexing - let Qdrant process asynchronously
350
+ points: uuids,
351
+ }),
352
+ this.retryOptions,
353
+ );
354
+ }
355
+
356
+ /**
357
+ * Return the total number of vectors in the collection.
358
+ *
359
+ * The underlying `this.client.getCollection()` call is wrapped with
360
+ * {@link withRetry}.
361
+ */
362
+ async count(): Promise<number> {
363
+ await this.ensureCollection();
364
+
365
+ const info = await withRetry(
366
+ () => this.client.getCollection(this.collectionName),
367
+ this.retryOptions,
368
+ );
369
+ return info.points_count ?? 0;
370
+ }
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // IVectorStore — optional methods
374
+ // ---------------------------------------------------------------------------
375
+
376
+ /**
377
+ * Get vectors by IDs.
378
+ *
379
+ * The underlying `this.client.retrieve()` call is wrapped with
380
+ * {@link withRetry}.
381
+ */
382
+ async get(ids: string[]): Promise<VectorRecord[]> {
383
+ await this.ensureCollection();
384
+
385
+ if (ids.length === 0) {
386
+ return [];
387
+ }
388
+
389
+ const response = await withRetry(
390
+ () =>
391
+ this.client.retrieve(this.collectionName, {
392
+ ids: ids.map((id) => stringToUUID(id)),
393
+ with_vector: true,
394
+ with_payload: true,
395
+ }),
396
+ this.retryOptions,
397
+ );
398
+
399
+ return response.map((point) => ({
400
+ id: String(point.id),
401
+ vector: point.vector as number[],
402
+ metadata: point.payload as Record<string, unknown> | undefined,
403
+ }));
404
+ }
405
+
406
+ /**
407
+ * Query vectors by metadata filter.
408
+ *
409
+ * The underlying `this.client.scroll()` call is wrapped with
410
+ * {@link withRetry}.
411
+ */
412
+ async query(filter: VectorFilter): Promise<VectorRecord[]> {
413
+ await this.ensureCollection();
414
+
415
+ const response = await withRetry(
416
+ () =>
417
+ this.client.scroll(this.collectionName, {
418
+ filter: this.convertFilter(filter),
419
+ with_vector: true,
420
+ with_payload: true,
421
+ limit: 10000, // Max limit for bulk retrieval
422
+ }),
423
+ this.retryOptions,
424
+ );
425
+
426
+ return response.points.map((point) => ({
427
+ id: String(point.id),
428
+ vector: point.vector as number[],
429
+ metadata: point.payload as Record<string, unknown> | undefined,
430
+ }));
431
+ }
432
+
433
+ // ---------------------------------------------------------------------------
434
+ // Private helpers
435
+ // ---------------------------------------------------------------------------
436
+
437
+ /**
438
+ * Convert platform VectorFilter to Qdrant filter format.
439
+ */
440
+ private convertFilter(filter: VectorFilter): any {
441
+ const fieldParts = filter.field.split(".");
442
+ const fieldName = fieldParts[fieldParts.length - 1]; // Get last part after dots
443
+
444
+ switch (filter.operator) {
445
+ case "eq":
446
+ return { must: [{ key: fieldName, match: { value: filter.value } }] };
447
+ case "ne":
448
+ return {
449
+ must_not: [{ key: fieldName, match: { value: filter.value } }],
450
+ };
451
+ case "in":
452
+ return {
453
+ must: [{ key: fieldName, match: { any: filter.value as any[] } }],
454
+ };
455
+ case "nin":
456
+ return {
457
+ must_not: [{ key: fieldName, match: { any: filter.value as any[] } }],
458
+ };
459
+ default:
460
+ // For gt/gte/lt/lte - use range filter
461
+ return {
462
+ must: [
463
+ { key: fieldName, range: { [filter.operator]: filter.value } },
464
+ ],
465
+ };
466
+ }
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Create Qdrant vector store adapter.
472
+ * This is the factory function called by initPlatform() when loading adapters.
473
+ */
474
+ export function createAdapter(
475
+ config?: QdrantVectorStoreConfig,
476
+ ): QdrantVectorStore {
477
+ const fallbackUrl = process.env.QDRANT_URL ?? "http://localhost:6333";
478
+ const finalConfig: QdrantVectorStoreConfig = {
479
+ url: config?.url ?? fallbackUrl,
480
+ apiKey: config?.apiKey ?? process.env.QDRANT_API_KEY,
481
+ collectionName: config?.collectionName,
482
+ dimension: config?.dimension,
483
+ timeout: config?.timeout,
484
+ retry: config?.retry,
485
+ };
486
+ return new QdrantVectorStore(finalConfig);
487
+ }
488
+
489
+ // Default export for direct import
490
+ export default createAdapter;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @module @kb-labs/adapters-qdrant/manifest
3
+ * Adapter manifest for Qdrant vector store.
4
+ */
5
+
6
+ import type { AdapterManifest } from "@kb-labs/core-platform";
7
+
8
+ /**
9
+ * Adapter manifest for Qdrant vector store.
10
+ */
11
+ export const manifest: AdapterManifest = {
12
+ manifestVersion: "1.0.0",
13
+ id: "qdrant-vectorstore",
14
+ name: "Qdrant Vector Store",
15
+ version: "1.0.0",
16
+ description: "High-performance vector database for semantic search and RAG",
17
+ author: "KB Labs Team",
18
+ license: "KBPL-1.1",
19
+ type: "core",
20
+ implements: "IVectorStore",
21
+ capabilities: {
22
+ search: true,
23
+ batch: true,
24
+ custom: {
25
+ hybridSearch: true,
26
+ filtering: true,
27
+ },
28
+ },
29
+ configSchema: {
30
+ url: {
31
+ type: "string",
32
+ description: "Qdrant server URL (e.g., http://localhost:6333)",
33
+ },
34
+ apiKey: {
35
+ type: "string",
36
+ description: "API key for authentication (optional)",
37
+ },
38
+ collectionName: {
39
+ type: "string",
40
+ default: "kb-vectors",
41
+ description: "Collection name",
42
+ },
43
+ dimension: {
44
+ type: "number",
45
+ default: 1536,
46
+ description: "Vector dimension (default: 1536 for OpenAI embeddings)",
47
+ },
48
+ timeout: {
49
+ type: "number",
50
+ default: 30000,
51
+ description: "Request timeout in milliseconds",
52
+ },
53
+ },
54
+ };