@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +16 -2
- package/dist/modules/search/__integration__/TC-SEARCH-010.spec.js +45 -0
- package/dist/modules/search/__integration__/TC-SEARCH-010.spec.js.map +7 -0
- package/dist/modules/search/__integration__/TC-SEARCH-011.spec.js +25 -0
- package/dist/modules/search/__integration__/TC-SEARCH-011.spec.js.map +7 -0
- package/dist/modules/search/api/embeddings/route.js +50 -6
- package/dist/modules/search/api/embeddings/route.js.map +2 -2
- package/dist/modules/search/api/search/global/route.js +1 -1
- package/dist/modules/search/api/search/global/route.js.map +2 -2
- package/dist/modules/search/api/settings/global-search/route.js +8 -5
- package/dist/modules/search/api/settings/global-search/route.js.map +2 -2
- package/dist/modules/search/di.js +3 -1
- package/dist/modules/search/di.js.map +2 -2
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +6 -3
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
- package/dist/modules/search/i18n/de.json +3 -0
- package/dist/modules/search/i18n/en.json +3 -0
- package/dist/modules/search/i18n/es.json +3 -0
- package/dist/modules/search/i18n/pl.json +3 -0
- package/dist/modules/search/lib/auto-indexing.js +4 -1
- package/dist/modules/search/lib/auto-indexing.js.map +2 -2
- package/dist/modules/search/lib/embedding-config.js +40 -3
- package/dist/modules/search/lib/embedding-config.js.map +2 -2
- package/dist/modules/search/lib/global-search-config.js +25 -3
- package/dist/modules/search/lib/global-search-config.js.map +2 -2
- package/dist/modules/search/lib/provider-probe.js +108 -0
- package/dist/modules/search/lib/provider-probe.js.map +7 -0
- package/package.json +4 -4
- package/src/modules/search/__integration__/TC-SEARCH-010.spec.ts +74 -0
- package/src/modules/search/__integration__/TC-SEARCH-011.spec.ts +46 -0
- package/src/modules/search/api/embeddings/__tests__/route.ollama-base-url.test.ts +3 -0
- package/src/modules/search/api/embeddings/route.ts +62 -6
- package/src/modules/search/api/search/global/route.ts +2 -2
- package/src/modules/search/api/settings/global-search/route.ts +9 -4
- package/src/modules/search/di.ts +2 -0
- package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +21 -2
- package/src/modules/search/i18n/de.json +3 -0
- package/src/modules/search/i18n/en.json +3 -0
- package/src/modules/search/i18n/es.json +3 -0
- package/src/modules/search/i18n/pl.json +3 -0
- package/src/modules/search/lib/__tests__/provider-probe.test.ts +137 -0
- package/src/modules/search/lib/__tests__/search-settings-scope.test.ts +152 -0
- package/src/modules/search/lib/auto-indexing.ts +6 -3
- package/src/modules/search/lib/embedding-config.ts +58 -5
- package/src/modules/search/lib/global-search-config.ts +36 -4
- 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
|
+
}
|