@open-mercato/search 0.6.6-develop.6296.1.5e8bdfee17 → 0.6.6-develop.6299.1.29224e2d86

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 (47) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +16 -2
  3. package/dist/modules/search/__integration__/TC-SEARCH-010.spec.js +45 -0
  4. package/dist/modules/search/__integration__/TC-SEARCH-010.spec.js.map +7 -0
  5. package/dist/modules/search/__integration__/TC-SEARCH-011.spec.js +25 -0
  6. package/dist/modules/search/__integration__/TC-SEARCH-011.spec.js.map +7 -0
  7. package/dist/modules/search/api/embeddings/route.js +50 -6
  8. package/dist/modules/search/api/embeddings/route.js.map +2 -2
  9. package/dist/modules/search/api/search/global/route.js +1 -1
  10. package/dist/modules/search/api/search/global/route.js.map +2 -2
  11. package/dist/modules/search/api/settings/global-search/route.js +8 -5
  12. package/dist/modules/search/api/settings/global-search/route.js.map +2 -2
  13. package/dist/modules/search/di.js +3 -1
  14. package/dist/modules/search/di.js.map +2 -2
  15. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +6 -3
  16. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
  17. package/dist/modules/search/i18n/de.json +3 -0
  18. package/dist/modules/search/i18n/en.json +3 -0
  19. package/dist/modules/search/i18n/es.json +3 -0
  20. package/dist/modules/search/i18n/pl.json +3 -0
  21. package/dist/modules/search/lib/auto-indexing.js +4 -1
  22. package/dist/modules/search/lib/auto-indexing.js.map +2 -2
  23. package/dist/modules/search/lib/embedding-config.js +40 -3
  24. package/dist/modules/search/lib/embedding-config.js.map +2 -2
  25. package/dist/modules/search/lib/global-search-config.js +25 -3
  26. package/dist/modules/search/lib/global-search-config.js.map +2 -2
  27. package/dist/modules/search/lib/provider-probe.js +108 -0
  28. package/dist/modules/search/lib/provider-probe.js.map +7 -0
  29. package/package.json +4 -4
  30. package/src/modules/search/__integration__/TC-SEARCH-010.spec.ts +74 -0
  31. package/src/modules/search/__integration__/TC-SEARCH-011.spec.ts +46 -0
  32. package/src/modules/search/api/embeddings/__tests__/route.ollama-base-url.test.ts +3 -0
  33. package/src/modules/search/api/embeddings/route.ts +62 -6
  34. package/src/modules/search/api/search/global/route.ts +2 -2
  35. package/src/modules/search/api/settings/global-search/route.ts +9 -4
  36. package/src/modules/search/di.ts +2 -0
  37. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +21 -2
  38. package/src/modules/search/i18n/de.json +3 -0
  39. package/src/modules/search/i18n/en.json +3 -0
  40. package/src/modules/search/i18n/es.json +3 -0
  41. package/src/modules/search/i18n/pl.json +3 -0
  42. package/src/modules/search/lib/__tests__/provider-probe.test.ts +137 -0
  43. package/src/modules/search/lib/__tests__/search-settings-scope.test.ts +152 -0
  44. package/src/modules/search/lib/auto-indexing.ts +6 -3
  45. package/src/modules/search/lib/embedding-config.ts +58 -5
  46. package/src/modules/search/lib/global-search-config.ts +36 -4
  47. package/src/modules/search/lib/provider-probe.ts +143 -0
@@ -0,0 +1,143 @@
1
+ import type { CacheStrategy } from '@open-mercato/cache'
2
+ import type { AppContainer } from '@open-mercato/shared/lib/di/container'
3
+ import type { EmbeddingProviderId } from '../../../vector'
4
+ import { EMBEDDING_PROVIDERS } from '../../../vector'
5
+
6
+ const CACHE_VERSION = 'v1'
7
+ const CACHE_TTL_MS = 30_000
8
+ const OLLAMA_PROBE_TIMEOUT_MS = 1500
9
+
10
+ export type ProviderAvailability = {
11
+ available: boolean
12
+ reason?: string
13
+ models?: number
14
+ }
15
+
16
+ export type ProviderAvailabilityEntry = ProviderAvailability & {
17
+ providerId: EmbeddingProviderId
18
+ }
19
+
20
+ export type EmbeddingProviderProbe = {
21
+ checkAvailability(providerId: EmbeddingProviderId, options?: { force?: boolean }): Promise<ProviderAvailability>
22
+ }
23
+
24
+ const ALL_PROVIDERS: EmbeddingProviderId[] = ['openai', 'google', 'mistral', 'cohere', 'bedrock', 'ollama']
25
+
26
+ export async function checkAllProviders(
27
+ probe: EmbeddingProviderProbe,
28
+ options?: { force?: boolean },
29
+ ): Promise<ProviderAvailabilityEntry[]> {
30
+ return Promise.all(
31
+ ALL_PROVIDERS.map(async (providerId) => ({
32
+ providerId,
33
+ ...(await probe.checkAvailability(providerId, options)),
34
+ })),
35
+ )
36
+ }
37
+
38
+ const cacheKey = (providerId: string) => `embedding-provider-probe:${CACHE_VERSION}:${providerId}`
39
+
40
+ const resolveCache = (container: AppContainer): CacheStrategy | null => {
41
+ try {
42
+ return container.resolve('cache') as CacheStrategy
43
+ } catch {
44
+ return null
45
+ }
46
+ }
47
+
48
+ const ollamaBaseUrl = () => process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434'
49
+
50
+ const keyPresence = (providerId: EmbeddingProviderId): ProviderAvailability => {
51
+ const info = EMBEDDING_PROVIDERS[providerId]
52
+ const envKey = info?.envKeyRequired
53
+ switch (providerId) {
54
+ case 'openai':
55
+ return process.env.OPENAI_API_KEY?.trim()
56
+ ? { available: true }
57
+ : { available: false, reason: `Set ${envKey} to enable ${info?.name ?? providerId}` }
58
+ case 'google':
59
+ return process.env.GOOGLE_GENERATIVE_AI_API_KEY?.trim()
60
+ ? { available: true }
61
+ : { available: false, reason: `Set ${envKey} to enable ${info?.name ?? providerId}` }
62
+ case 'mistral':
63
+ return process.env.MISTRAL_API_KEY?.trim()
64
+ ? { available: true }
65
+ : { available: false, reason: `Set ${envKey} to enable ${info?.name ?? providerId}` }
66
+ case 'cohere':
67
+ return process.env.COHERE_API_KEY?.trim()
68
+ ? { available: true }
69
+ : { available: false, reason: `Set ${envKey} to enable ${info?.name ?? providerId}` }
70
+ case 'bedrock':
71
+ return process.env.AWS_ACCESS_KEY_ID?.trim() && process.env.AWS_SECRET_ACCESS_KEY?.trim()
72
+ ? { available: true }
73
+ : { available: false, reason: 'Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to enable Bedrock' }
74
+ default:
75
+ return { available: false, reason: `Unknown provider: ${providerId}` }
76
+ }
77
+ }
78
+
79
+ export async function probeOllama(baseUrl: string): Promise<ProviderAvailability> {
80
+ const controller = new AbortController()
81
+ const timer = setTimeout(() => controller.abort(), OLLAMA_PROBE_TIMEOUT_MS)
82
+ try {
83
+ const response = await fetch(`${baseUrl.replace(/\/$/, '')}/api/tags`, {
84
+ method: 'GET',
85
+ signal: controller.signal,
86
+ })
87
+ if (!response.ok) {
88
+ return { available: false, reason: `Ollama responded ${response.status} at ${baseUrl}` }
89
+ }
90
+ let models: number | undefined
91
+ try {
92
+ const payload = (await response.json()) as { models?: unknown[] }
93
+ if (Array.isArray(payload?.models)) models = payload.models.length
94
+ } catch {}
95
+ return { available: true, models }
96
+ } catch (error) {
97
+ const aborted = error instanceof Error && error.name === 'AbortError'
98
+ return {
99
+ available: false,
100
+ reason: aborted ? `Ollama not reachable at ${baseUrl} (timed out)` : `Ollama not reachable at ${baseUrl}`,
101
+ }
102
+ } finally {
103
+ clearTimeout(timer)
104
+ }
105
+ }
106
+
107
+ async function computeAvailability(providerId: EmbeddingProviderId): Promise<ProviderAvailability> {
108
+ try {
109
+ if (providerId === 'ollama') {
110
+ return await probeOllama(ollamaBaseUrl())
111
+ }
112
+ return keyPresence(providerId)
113
+ } catch (error) {
114
+ // Fail closed: any unexpected error means the provider is treated as unavailable.
115
+ return { available: false, reason: error instanceof Error ? error.message : 'Availability check failed' }
116
+ }
117
+ }
118
+
119
+ export function createEmbeddingProviderProbe(container: AppContainer): EmbeddingProviderProbe {
120
+ const checkAvailability = async (
121
+ providerId: EmbeddingProviderId,
122
+ options?: { force?: boolean },
123
+ ): Promise<ProviderAvailability> => {
124
+ const cache = resolveCache(container)
125
+ const key = cacheKey(providerId)
126
+ if (!options?.force && cache) {
127
+ try {
128
+ const cached = await cache.get(key)
129
+ if (cached && typeof cached === 'object' && 'available' in cached) {
130
+ return cached as ProviderAvailability
131
+ }
132
+ } catch {}
133
+ }
134
+ const result = await computeAvailability(providerId)
135
+ if (cache) {
136
+ try {
137
+ await cache.set(key, result, { ttl: CACHE_TTL_MS })
138
+ } catch {}
139
+ }
140
+ return result
141
+ }
142
+ return { checkAvailability }
143
+ }