@open-mercato/search 0.4.2-canary-c02407ff85

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 (237) hide show
  1. package/AGENTS.md +678 -0
  2. package/build.mjs +92 -0
  3. package/dist/di.js +157 -0
  4. package/dist/di.js.map +7 -0
  5. package/dist/fulltext/drivers/index.js +21 -0
  6. package/dist/fulltext/drivers/index.js.map +7 -0
  7. package/dist/fulltext/drivers/meilisearch/index.js +320 -0
  8. package/dist/fulltext/drivers/meilisearch/index.js.map +7 -0
  9. package/dist/fulltext/index.js +7 -0
  10. package/dist/fulltext/index.js.map +7 -0
  11. package/dist/fulltext/types.js +1 -0
  12. package/dist/fulltext/types.js.map +7 -0
  13. package/dist/index.js +12 -0
  14. package/dist/index.js.map +7 -0
  15. package/dist/indexer/index.js +8 -0
  16. package/dist/indexer/index.js.map +7 -0
  17. package/dist/indexer/search-indexer.js +848 -0
  18. package/dist/indexer/search-indexer.js.map +7 -0
  19. package/dist/indexer/subscribers/delete.js +41 -0
  20. package/dist/indexer/subscribers/delete.js.map +7 -0
  21. package/dist/lib/debug.js +34 -0
  22. package/dist/lib/debug.js.map +7 -0
  23. package/dist/lib/fallback-presenter.js +107 -0
  24. package/dist/lib/fallback-presenter.js.map +7 -0
  25. package/dist/lib/field-policy.js +75 -0
  26. package/dist/lib/field-policy.js.map +7 -0
  27. package/dist/lib/index.js +19 -0
  28. package/dist/lib/index.js.map +7 -0
  29. package/dist/lib/merger.js +93 -0
  30. package/dist/lib/merger.js.map +7 -0
  31. package/dist/lib/presenter-enricher.js +192 -0
  32. package/dist/lib/presenter-enricher.js.map +7 -0
  33. package/dist/modules/search/acl.js +14 -0
  34. package/dist/modules/search/acl.js.map +7 -0
  35. package/dist/modules/search/ai-tools.js +284 -0
  36. package/dist/modules/search/ai-tools.js.map +7 -0
  37. package/dist/modules/search/api/embeddings/reindex/cancel/route.js +65 -0
  38. package/dist/modules/search/api/embeddings/reindex/cancel/route.js.map +7 -0
  39. package/dist/modules/search/api/embeddings/reindex/route.js +165 -0
  40. package/dist/modules/search/api/embeddings/reindex/route.js.map +7 -0
  41. package/dist/modules/search/api/embeddings/route.js +246 -0
  42. package/dist/modules/search/api/embeddings/route.js.map +7 -0
  43. package/dist/modules/search/api/index/route.js +245 -0
  44. package/dist/modules/search/api/index/route.js.map +7 -0
  45. package/dist/modules/search/api/reindex/cancel/route.js +65 -0
  46. package/dist/modules/search/api/reindex/cancel/route.js.map +7 -0
  47. package/dist/modules/search/api/reindex/route.js +332 -0
  48. package/dist/modules/search/api/reindex/route.js.map +7 -0
  49. package/dist/modules/search/api/search/global/route.js +100 -0
  50. package/dist/modules/search/api/search/global/route.js.map +7 -0
  51. package/dist/modules/search/api/search/route.js +101 -0
  52. package/dist/modules/search/api/search/route.js.map +7 -0
  53. package/dist/modules/search/api/settings/fulltext/route.js +55 -0
  54. package/dist/modules/search/api/settings/fulltext/route.js.map +7 -0
  55. package/dist/modules/search/api/settings/global-search/route.js +80 -0
  56. package/dist/modules/search/api/settings/global-search/route.js.map +7 -0
  57. package/dist/modules/search/api/settings/route.js +118 -0
  58. package/dist/modules/search/api/settings/route.js.map +7 -0
  59. package/dist/modules/search/api/settings/vector-store/route.js +77 -0
  60. package/dist/modules/search/api/settings/vector-store/route.js.map +7 -0
  61. package/dist/modules/search/backend/config/search/page.js +10 -0
  62. package/dist/modules/search/backend/config/search/page.js.map +7 -0
  63. package/dist/modules/search/backend/config/search/page.meta.js +24 -0
  64. package/dist/modules/search/backend/config/search/page.meta.js.map +7 -0
  65. package/dist/modules/search/cli.js +698 -0
  66. package/dist/modules/search/cli.js.map +7 -0
  67. package/dist/modules/search/di.js +32 -0
  68. package/dist/modules/search/di.js.map +7 -0
  69. package/dist/modules/search/frontend/components/GlobalSearchDialog.js +357 -0
  70. package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +7 -0
  71. package/dist/modules/search/frontend/components/HybridSearchTable.js +343 -0
  72. package/dist/modules/search/frontend/components/HybridSearchTable.js.map +7 -0
  73. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js +303 -0
  74. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +7 -0
  75. package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js +360 -0
  76. package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js.map +7 -0
  77. package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js +101 -0
  78. package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js.map +7 -0
  79. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +608 -0
  80. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +7 -0
  81. package/dist/modules/search/frontend/index.js +9 -0
  82. package/dist/modules/search/frontend/index.js.map +7 -0
  83. package/dist/modules/search/frontend/utils.js +41 -0
  84. package/dist/modules/search/frontend/utils.js.map +7 -0
  85. package/dist/modules/search/i18n/de.json +61 -0
  86. package/dist/modules/search/i18n/en.json +72 -0
  87. package/dist/modules/search/i18n/es.json +61 -0
  88. package/dist/modules/search/i18n/pl.json +61 -0
  89. package/dist/modules/search/index.js +11 -0
  90. package/dist/modules/search/index.js.map +7 -0
  91. package/dist/modules/search/lib/auto-indexing.js +29 -0
  92. package/dist/modules/search/lib/auto-indexing.js.map +7 -0
  93. package/dist/modules/search/lib/embedding-config.js +131 -0
  94. package/dist/modules/search/lib/embedding-config.js.map +7 -0
  95. package/dist/modules/search/lib/global-search-config.js +45 -0
  96. package/dist/modules/search/lib/global-search-config.js.map +7 -0
  97. package/dist/modules/search/lib/reindex-lock.js +99 -0
  98. package/dist/modules/search/lib/reindex-lock.js.map +7 -0
  99. package/dist/modules/search/subscribers/fulltext_upsert.js +64 -0
  100. package/dist/modules/search/subscribers/fulltext_upsert.js.map +7 -0
  101. package/dist/modules/search/subscribers/vector_delete.js +58 -0
  102. package/dist/modules/search/subscribers/vector_delete.js.map +7 -0
  103. package/dist/modules/search/subscribers/vector_purge.js +142 -0
  104. package/dist/modules/search/subscribers/vector_purge.js.map +7 -0
  105. package/dist/modules/search/subscribers/vector_upsert.js +58 -0
  106. package/dist/modules/search/subscribers/vector_upsert.js.map +7 -0
  107. package/dist/modules/search/workers/fulltext-index.worker.js +240 -0
  108. package/dist/modules/search/workers/fulltext-index.worker.js.map +7 -0
  109. package/dist/modules/search/workers/vector-index.worker.js +234 -0
  110. package/dist/modules/search/workers/vector-index.worker.js.map +7 -0
  111. package/dist/queue/fulltext-indexing.js +15 -0
  112. package/dist/queue/fulltext-indexing.js.map +7 -0
  113. package/dist/queue/index.js +3 -0
  114. package/dist/queue/index.js.map +7 -0
  115. package/dist/queue/vector-indexing.js +15 -0
  116. package/dist/queue/vector-indexing.js.map +7 -0
  117. package/dist/service.js +286 -0
  118. package/dist/service.js.map +7 -0
  119. package/dist/strategies/fulltext.strategy.js +116 -0
  120. package/dist/strategies/fulltext.strategy.js.map +7 -0
  121. package/dist/strategies/index.js +12 -0
  122. package/dist/strategies/index.js.map +7 -0
  123. package/dist/strategies/token.strategy.js +80 -0
  124. package/dist/strategies/token.strategy.js.map +7 -0
  125. package/dist/strategies/vector.strategy.js +137 -0
  126. package/dist/strategies/vector.strategy.js.map +7 -0
  127. package/dist/types.js +1 -0
  128. package/dist/types.js.map +7 -0
  129. package/dist/vector/drivers/chromadb/index.js +44 -0
  130. package/dist/vector/drivers/chromadb/index.js.map +7 -0
  131. package/dist/vector/drivers/index.js +9 -0
  132. package/dist/vector/drivers/index.js.map +7 -0
  133. package/dist/vector/drivers/pgvector/index.js +509 -0
  134. package/dist/vector/drivers/pgvector/index.js.map +7 -0
  135. package/dist/vector/drivers/qdrant/index.js +44 -0
  136. package/dist/vector/drivers/qdrant/index.js.map +7 -0
  137. package/dist/vector/index.js +4 -0
  138. package/dist/vector/index.js.map +7 -0
  139. package/dist/vector/lib/vector-logs.js +33 -0
  140. package/dist/vector/lib/vector-logs.js.map +7 -0
  141. package/dist/vector/services/checksum.js +20 -0
  142. package/dist/vector/services/checksum.js.map +7 -0
  143. package/dist/vector/services/embedding.js +222 -0
  144. package/dist/vector/services/embedding.js.map +7 -0
  145. package/dist/vector/services/index.js +4 -0
  146. package/dist/vector/services/index.js.map +7 -0
  147. package/dist/vector/services/vector-index.service.js +960 -0
  148. package/dist/vector/services/vector-index.service.js.map +7 -0
  149. package/dist/vector/types/pg.d.js +1 -0
  150. package/dist/vector/types/pg.d.js.map +7 -0
  151. package/dist/vector/types.js +75 -0
  152. package/dist/vector/types.js.map +7 -0
  153. package/jest.config.cjs +19 -0
  154. package/package.json +142 -0
  155. package/src/__tests__/queue.test.ts +148 -0
  156. package/src/__tests__/service.test.ts +345 -0
  157. package/src/__tests__/workers.test.ts +319 -0
  158. package/src/di.ts +291 -0
  159. package/src/fulltext/drivers/index.ts +41 -0
  160. package/src/fulltext/drivers/meilisearch/index.ts +410 -0
  161. package/src/fulltext/index.ts +13 -0
  162. package/src/fulltext/types.ts +115 -0
  163. package/src/index.ts +36 -0
  164. package/src/indexer/index.ts +13 -0
  165. package/src/indexer/search-indexer.ts +1141 -0
  166. package/src/indexer/subscribers/delete.ts +49 -0
  167. package/src/lib/debug.ts +46 -0
  168. package/src/lib/fallback-presenter.ts +106 -0
  169. package/src/lib/field-policy.ts +169 -0
  170. package/src/lib/index.ts +13 -0
  171. package/src/lib/merger.ts +159 -0
  172. package/src/lib/presenter-enricher.ts +323 -0
  173. package/src/modules/search/README.md +694 -0
  174. package/src/modules/search/acl.ts +10 -0
  175. package/src/modules/search/ai-tools.ts +467 -0
  176. package/src/modules/search/api/embeddings/reindex/cancel/route.ts +77 -0
  177. package/src/modules/search/api/embeddings/reindex/route.ts +197 -0
  178. package/src/modules/search/api/embeddings/route.ts +304 -0
  179. package/src/modules/search/api/index/route.ts +297 -0
  180. package/src/modules/search/api/reindex/cancel/route.ts +77 -0
  181. package/src/modules/search/api/reindex/route.ts +419 -0
  182. package/src/modules/search/api/search/global/route.ts +120 -0
  183. package/src/modules/search/api/search/route.ts +121 -0
  184. package/src/modules/search/api/settings/fulltext/route.ts +82 -0
  185. package/src/modules/search/api/settings/global-search/route.ts +91 -0
  186. package/src/modules/search/api/settings/route.ts +187 -0
  187. package/src/modules/search/api/settings/vector-store/route.ts +105 -0
  188. package/src/modules/search/backend/config/search/page.meta.ts +22 -0
  189. package/src/modules/search/backend/config/search/page.tsx +12 -0
  190. package/src/modules/search/cli.ts +818 -0
  191. package/src/modules/search/di.ts +50 -0
  192. package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +436 -0
  193. package/src/modules/search/frontend/components/HybridSearchTable.tsx +418 -0
  194. package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +476 -0
  195. package/src/modules/search/frontend/components/sections/FulltextSearchSection.tsx +624 -0
  196. package/src/modules/search/frontend/components/sections/GlobalSearchSection.tsx +124 -0
  197. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +943 -0
  198. package/src/modules/search/frontend/index.ts +3 -0
  199. package/src/modules/search/frontend/utils.ts +82 -0
  200. package/src/modules/search/i18n/de.json +61 -0
  201. package/src/modules/search/i18n/en.json +72 -0
  202. package/src/modules/search/i18n/es.json +61 -0
  203. package/src/modules/search/i18n/pl.json +61 -0
  204. package/src/modules/search/index.ts +9 -0
  205. package/src/modules/search/lib/auto-indexing.ts +35 -0
  206. package/src/modules/search/lib/embedding-config.ts +161 -0
  207. package/src/modules/search/lib/global-search-config.ts +69 -0
  208. package/src/modules/search/lib/reindex-lock.ts +201 -0
  209. package/src/modules/search/subscribers/fulltext_upsert.ts +83 -0
  210. package/src/modules/search/subscribers/vector_delete.ts +75 -0
  211. package/src/modules/search/subscribers/vector_purge.ts +161 -0
  212. package/src/modules/search/subscribers/vector_upsert.ts +75 -0
  213. package/src/modules/search/workers/fulltext-index.worker.ts +318 -0
  214. package/src/modules/search/workers/vector-index.worker.ts +292 -0
  215. package/src/queue/fulltext-indexing.ts +87 -0
  216. package/src/queue/index.ts +2 -0
  217. package/src/queue/vector-indexing.ts +66 -0
  218. package/src/service.ts +397 -0
  219. package/src/strategies/fulltext.strategy.ts +155 -0
  220. package/src/strategies/index.ts +17 -0
  221. package/src/strategies/token.strategy.ts +153 -0
  222. package/src/strategies/vector.strategy.ts +234 -0
  223. package/src/types.ts +38 -0
  224. package/src/vector/drivers/chromadb/index.ts +49 -0
  225. package/src/vector/drivers/index.ts +4 -0
  226. package/src/vector/drivers/pgvector/index.ts +627 -0
  227. package/src/vector/drivers/qdrant/index.ts +49 -0
  228. package/src/vector/index.ts +3 -0
  229. package/src/vector/lib/vector-logs.ts +46 -0
  230. package/src/vector/services/checksum.ts +18 -0
  231. package/src/vector/services/embedding.ts +275 -0
  232. package/src/vector/services/index.ts +3 -0
  233. package/src/vector/services/vector-index.service.ts +1234 -0
  234. package/src/vector/types/pg.d.ts +1 -0
  235. package/src/vector/types.ts +220 -0
  236. package/tsconfig.json +9 -0
  237. package/watch.mjs +6 -0
@@ -0,0 +1,46 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'
3
+ import type { VectorIndexOperationResult } from '../services/vector-index.service'
4
+
5
+ type LogArgs = {
6
+ em?: EntityManager | null
7
+ handler: string
8
+ entityType: string
9
+ recordId: string
10
+ result: VectorIndexOperationResult | null | undefined
11
+ source?: 'vector' | 'query_index'
12
+ }
13
+
14
+ export async function logVectorOperation({
15
+ em,
16
+ handler,
17
+ entityType,
18
+ recordId,
19
+ result,
20
+ source = 'vector',
21
+ }: LogArgs): Promise<void> {
22
+ if (!result) return
23
+ const action = result.action
24
+ if (action !== 'indexed' && action !== 'deleted') return
25
+ if (action === 'deleted' && !result.existed) return
26
+
27
+ const op =
28
+ action === 'indexed'
29
+ ? result.created ? 'created' : 'updated'
30
+ : 'removed'
31
+ const message = `Vector embedding ${op} for ${entityType}#${recordId}`
32
+
33
+ await recordIndexerLog(
34
+ { em: em ?? undefined },
35
+ {
36
+ source,
37
+ handler,
38
+ message,
39
+ entityType,
40
+ recordId,
41
+ tenantId: result.tenantId ?? null,
42
+ organizationId: result.organizationId ?? null,
43
+ details: result,
44
+ },
45
+ ).catch(() => undefined)
46
+ }
@@ -0,0 +1,18 @@
1
+ import crypto from 'node:crypto'
2
+
3
+ function stableStringify(value: unknown): string {
4
+ if (value === null || value === undefined) return 'null'
5
+ if (typeof value === 'string') return JSON.stringify(value)
6
+ if (typeof value === 'number' || typeof value === 'boolean') return JSON.stringify(value)
7
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(',')}]`
8
+ if (typeof value === 'object') {
9
+ const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b))
10
+ return `{${entries.map(([key, val]) => `${JSON.stringify(key)}:${stableStringify(val)}`).join(',')}}`
11
+ }
12
+ return JSON.stringify(value)
13
+ }
14
+
15
+ export function computeChecksum(source: unknown): string {
16
+ const serialized = stableStringify(source)
17
+ return crypto.createHash('sha256').update(serialized).digest('hex')
18
+ }
@@ -0,0 +1,275 @@
1
+ import { embed } from 'ai'
2
+ import type { EmbeddingModel } from 'ai'
3
+ import { createOpenAI } from '@ai-sdk/openai'
4
+
5
+ // Local type definition to avoid @ai-sdk/provider version conflicts
6
+ // Matches SharedV3ProviderOptions = Record<string, JSONObject>
7
+ type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }
8
+ type JSONObject = { [key: string]: JSONValue }
9
+ type ProviderOptions = Record<string, JSONObject>
10
+ import { createGoogleGenerativeAI } from '@ai-sdk/google'
11
+ import { createMistral } from '@ai-sdk/mistral'
12
+ import { createCohere } from '@ai-sdk/cohere'
13
+ import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'
14
+ import { createOllama } from 'ai-sdk-ollama'
15
+ import type { EmbeddingProviderId, EmbeddingProviderConfig } from '../types'
16
+ import { EMBEDDING_PROVIDERS, DEFAULT_EMBEDDING_CONFIG } from '../types'
17
+
18
+ export type EmbeddingServiceOptions = {
19
+ apiKey?: string
20
+ model?: string
21
+ config?: EmbeddingProviderConfig
22
+ }
23
+
24
+ type OllamaClient = ReturnType<typeof createOllama>
25
+
26
+ type ProviderClient = ReturnType<typeof createOpenAI>
27
+ | ReturnType<typeof createGoogleGenerativeAI>
28
+ | ReturnType<typeof createMistral>
29
+ | ReturnType<typeof createCohere>
30
+ | ReturnType<typeof createAmazonBedrock>
31
+ | OllamaClient
32
+
33
+ export class EmbeddingService {
34
+ private config: EmbeddingProviderConfig
35
+ private clientCache: Map<EmbeddingProviderId, ProviderClient> = new Map()
36
+
37
+ constructor(private readonly opts: EmbeddingServiceOptions = {}) {
38
+ if (opts.config) {
39
+ this.config = opts.config
40
+ } else {
41
+ this.config = {
42
+ providerId: 'openai',
43
+ model: opts.model ?? DEFAULT_EMBEDDING_CONFIG.model,
44
+ dimension: DEFAULT_EMBEDDING_CONFIG.dimension,
45
+ updatedAt: new Date().toISOString(),
46
+ }
47
+ }
48
+ }
49
+
50
+ updateConfig(config: EmbeddingProviderConfig): void {
51
+ this.config = config
52
+ this.clientCache.clear()
53
+ }
54
+
55
+ get currentConfig(): EmbeddingProviderConfig {
56
+ return { ...this.config }
57
+ }
58
+
59
+ get dimension(): number {
60
+ return this.config.outputDimensionality ?? this.config.dimension
61
+ }
62
+
63
+ get available(): boolean {
64
+ return this.isProviderConfigured(this.config.providerId)
65
+ }
66
+
67
+ private isProviderConfigured(providerId: EmbeddingProviderId): boolean {
68
+ switch (providerId) {
69
+ case 'openai':
70
+ return Boolean(this.opts.apiKey ?? process.env.OPENAI_API_KEY)
71
+ case 'google':
72
+ return Boolean(process.env.GOOGLE_GENERATIVE_AI_API_KEY)
73
+ case 'mistral':
74
+ return Boolean(process.env.MISTRAL_API_KEY)
75
+ case 'cohere':
76
+ return Boolean(process.env.COHERE_API_KEY)
77
+ case 'bedrock':
78
+ return Boolean(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)
79
+ case 'ollama':
80
+ return true
81
+ default:
82
+ return false
83
+ }
84
+ }
85
+
86
+ private getClient(providerId: EmbeddingProviderId): ProviderClient {
87
+ const cached = this.clientCache.get(providerId)
88
+ if (cached) {
89
+ return cached
90
+ }
91
+
92
+ let client: ProviderClient
93
+ switch (providerId) {
94
+ case 'openai': {
95
+ const apiKey = this.opts.apiKey ?? process.env.OPENAI_API_KEY
96
+ if (!apiKey) {
97
+ throw new Error('[vector.embedding] Missing OPENAI_API_KEY environment variable')
98
+ }
99
+ client = createOpenAI({ apiKey })
100
+ break
101
+ }
102
+ case 'google': {
103
+ const apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY
104
+ if (!apiKey) {
105
+ throw new Error('[vector.embedding] Missing GOOGLE_GENERATIVE_AI_API_KEY environment variable')
106
+ }
107
+ client = createGoogleGenerativeAI({ apiKey })
108
+ break
109
+ }
110
+ case 'mistral': {
111
+ const apiKey = process.env.MISTRAL_API_KEY
112
+ if (!apiKey) {
113
+ throw new Error('[vector.embedding] Missing MISTRAL_API_KEY environment variable')
114
+ }
115
+ client = createMistral({ apiKey })
116
+ break
117
+ }
118
+ case 'cohere': {
119
+ const apiKey = process.env.COHERE_API_KEY
120
+ if (!apiKey) {
121
+ throw new Error('[vector.embedding] Missing COHERE_API_KEY environment variable')
122
+ }
123
+ client = createCohere({ apiKey })
124
+ break
125
+ }
126
+ case 'bedrock': {
127
+ const accessKeyId = process.env.AWS_ACCESS_KEY_ID
128
+ const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY
129
+ if (!accessKeyId || !secretAccessKey) {
130
+ throw new Error('[vector.embedding] Missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment variables')
131
+ }
132
+ client = createAmazonBedrock({
133
+ accessKeyId,
134
+ secretAccessKey,
135
+ region: process.env.AWS_REGION ?? 'us-east-1',
136
+ })
137
+ break
138
+ }
139
+ case 'ollama': {
140
+ const baseURL = this.config.baseUrl ?? process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434'
141
+ client = createOllama({ baseURL })
142
+ break
143
+ }
144
+ default:
145
+ throw new Error(`[vector.embedding] Unknown provider: ${providerId}`)
146
+ }
147
+
148
+ this.clientCache.set(providerId, client)
149
+ return client
150
+ }
151
+
152
+ private getEmbeddingModel() {
153
+ const client = this.getClient(this.config.providerId)
154
+ const { providerId, model, outputDimensionality } = this.config
155
+
156
+ switch (providerId) {
157
+ case 'openai':
158
+ return (client as ReturnType<typeof createOpenAI>).embedding(model)
159
+ case 'google':
160
+ return (client as ReturnType<typeof createGoogleGenerativeAI>).textEmbeddingModel(model)
161
+ case 'mistral':
162
+ return (client as ReturnType<typeof createMistral>).textEmbeddingModel(model)
163
+ case 'cohere':
164
+ return (client as ReturnType<typeof createCohere>).textEmbeddingModel(model)
165
+ case 'bedrock':
166
+ return (client as ReturnType<typeof createAmazonBedrock>).embedding(model)
167
+ case 'ollama':
168
+ return (client as OllamaClient).embedding(model)
169
+ default:
170
+ throw new Error(`[vector.embedding] Unknown provider: ${providerId}`)
171
+ }
172
+ }
173
+
174
+ private getProviderOptions(): ProviderOptions | undefined {
175
+ const { providerId, outputDimensionality, model } = this.config
176
+
177
+ if (!outputDimensionality) {
178
+ if (providerId === 'cohere') {
179
+ return { cohere: { inputType: 'search_document' } }
180
+ }
181
+ return undefined
182
+ }
183
+
184
+ switch (providerId) {
185
+ case 'openai':
186
+ if (model === 'text-embedding-3-large' || model === 'text-embedding-3-small') {
187
+ return { openai: { dimensions: outputDimensionality } }
188
+ }
189
+ return undefined
190
+ case 'google':
191
+ return { google: { outputDimensionality } }
192
+ case 'bedrock':
193
+ return { bedrock: { dimensions: outputDimensionality } }
194
+ case 'cohere':
195
+ return { cohere: { inputType: 'search_document' } }
196
+ default:
197
+ return undefined
198
+ }
199
+ }
200
+
201
+ async createEmbedding(input: string | string[]): Promise<number[]> {
202
+ const merged = Array.isArray(input)
203
+ ? input.map((part) => String(part ?? '')).filter((part) => part.length > 0).join('\n\n')
204
+ : String(input ?? '')
205
+ if (!merged.length) {
206
+ throw new Error('[vector.embedding] Refusing to embed empty payload')
207
+ }
208
+
209
+ if (!this.available) {
210
+ const providerInfo = EMBEDDING_PROVIDERS[this.config.providerId]
211
+ throw new Error(`[vector.embedding] Provider ${providerInfo.name} is not configured. Set ${providerInfo.envKeyRequired} environment variable.`)
212
+ }
213
+
214
+ const model = this.getEmbeddingModel() as EmbeddingModel
215
+ const providerOptions = this.getProviderOptions()
216
+
217
+ try {
218
+ const result = await embed({
219
+ model,
220
+ value: merged,
221
+ ...(providerOptions && { providerOptions }),
222
+ })
223
+ const emb = Array.isArray(result.embedding)
224
+ ? result.embedding
225
+ : Array.from(result.embedding as ArrayLike<number>)
226
+ return emb.map((n) => Number.isFinite(n) ? Number(n) : 0)
227
+ } catch (err: unknown) {
228
+ const error = err as { statusCode?: number; status?: number; response?: { status?: number; statusCode?: number; data?: { error?: { message?: string; code?: string }; message?: string } }; data?: { error?: { message?: string; code?: string } }; body?: { error?: { message?: string; code?: string } }; message?: string }
229
+ const statusCandidate =
230
+ error?.statusCode ?? error?.status ?? error?.response?.status ?? error?.response?.statusCode
231
+ const status =
232
+ typeof statusCandidate === 'number'
233
+ ? Number.isFinite(statusCandidate) ? statusCandidate : undefined
234
+ : typeof statusCandidate === 'string'
235
+ ? Number.parseInt(statusCandidate, 10)
236
+ : undefined
237
+ const apiError = error?.data?.error ?? error?.body?.error ?? error?.response?.data?.error
238
+ const apiMessage = apiError?.message ?? error?.response?.data?.message
239
+ const apiCode = typeof apiError?.code === 'string' ? apiError.code : undefined
240
+ const rawMessage = typeof apiMessage === 'string'
241
+ ? apiMessage
242
+ : (typeof error?.message === 'string' ? error.message : 'Embedding request failed')
243
+
244
+ const providerInfo = EMBEDDING_PROVIDERS[this.config.providerId]
245
+ let guidance: string
246
+ switch (apiCode) {
247
+ case 'insufficient_quota':
248
+ guidance = `${providerInfo.name} usage quota exceeded. Please review your plan and billing.`
249
+ break
250
+ case 'invalid_api_key':
251
+ guidance = `Invalid ${providerInfo.name} API key. Update the key and retry.`
252
+ break
253
+ case 'account_deactivated':
254
+ guidance = `${providerInfo.name} account is disabled. Contact support or provide a different key.`
255
+ break
256
+ default:
257
+ guidance = rawMessage.includes('https://')
258
+ ? rawMessage
259
+ : `${rawMessage}. Check ${providerInfo.envKeyRequired}.`
260
+ }
261
+ const wrapped = new Error(`[vector.embedding] ${guidance}`) as Error & { status?: number; code?: string; cause?: unknown }
262
+ if (typeof status === 'number' && Number.isFinite(status)) {
263
+ const normalizedStatus = status === 401 || status === 403 ? 502 : status
264
+ if (normalizedStatus >= 400 && normalizedStatus < 600) {
265
+ wrapped.status = normalizedStatus
266
+ }
267
+ }
268
+ if (apiCode) {
269
+ wrapped.code = apiCode
270
+ }
271
+ wrapped.cause = err
272
+ throw wrapped
273
+ }
274
+ }
275
+ }
@@ -0,0 +1,3 @@
1
+ export * from './embedding'
2
+ export * from './checksum'
3
+ export * from './vector-index.service'