Skip to content

Commit b56695e

Browse files
committed
feat: add Jina as embedding provider for code indexing
- Add Jina to EmbedderProvider type and model profiles - Implement JinaEmbedder class with multi-vector embeddings support - Configure jina-embeddings-v4 model with code.query downstream task - Add UI components for Jina provider selection and API key input - Include proper error handling and rate limiting - Add localization support for Jina-related messages
1 parent cc0f9e3 commit b56695e

File tree

14 files changed

+412
-5
lines changed

14 files changed

+412
-5
lines changed

packages/types/src/codebase-index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ export const CODEBASE_INDEX_DEFAULTS = {
2121
export const codebaseIndexConfigSchema = z.object({
2222
codebaseIndexEnabled: z.boolean().optional(),
2323
codebaseIndexQdrantUrl: z.string().optional(),
24-
codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral"]).optional(),
24+
codebaseIndexEmbedderProvider: z
25+
.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "jina"])
26+
.optional(),
2527
codebaseIndexEmbedderBaseUrl: z.string().optional(),
2628
codebaseIndexEmbedderModelId: z.string().optional(),
2729
codebaseIndexEmbedderModelDimension: z.number().optional(),
@@ -48,6 +50,7 @@ export const codebaseIndexModelsSchema = z.object({
4850
"openai-compatible": z.record(z.string(), z.object({ dimension: z.number() })).optional(),
4951
gemini: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
5052
mistral: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
53+
jina: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
5154
})
5255

5356
export type CodebaseIndexModels = z.infer<typeof codebaseIndexModelsSchema>
@@ -64,6 +67,7 @@ export const codebaseIndexProviderSchema = z.object({
6467
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
6568
codebaseIndexGeminiApiKey: z.string().optional(),
6669
codebaseIndexMistralApiKey: z.string().optional(),
70+
codebaseIndexJinaApiKey: z.string().optional(),
6771
})
6872

6973
export type CodebaseIndexProvider = z.infer<typeof codebaseIndexProviderSchema>

packages/types/src/global-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export const SECRET_STATE_KEYS = [
186186
"codebaseIndexOpenAiCompatibleApiKey",
187187
"codebaseIndexGeminiApiKey",
188188
"codebaseIndexMistralApiKey",
189+
"codebaseIndexJinaApiKey",
189190
"huggingFaceApiKey",
190191
] as const satisfies readonly (keyof ProviderSettings)[]
191192
export type SecretState = Pick<ProviderSettings, (typeof SECRET_STATE_KEYS)[number]>

src/core/webview/webviewMessageHandler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,6 +2036,9 @@ export const webviewMessageHandler = async (
20362036
settings.codebaseIndexMistralApiKey,
20372037
)
20382038
}
2039+
if (settings.codebaseIndexJinaApiKey !== undefined) {
2040+
await provider.contextProxy.storeSecret("codebaseIndexJinaApiKey", settings.codebaseIndexJinaApiKey)
2041+
}
20392042

20402043
// Send success response first - settings are saved regardless of validation
20412044
await provider.postMessageToWebview({
@@ -2157,6 +2160,7 @@ export const webviewMessageHandler = async (
21572160
))
21582161
const hasGeminiApiKey = !!(await provider.context.secrets.get("codebaseIndexGeminiApiKey"))
21592162
const hasMistralApiKey = !!(await provider.context.secrets.get("codebaseIndexMistralApiKey"))
2163+
const hasJinaApiKey = !!(await provider.context.secrets.get("codebaseIndexJinaApiKey"))
21602164

21612165
provider.postMessageToWebview({
21622166
type: "codeIndexSecretStatus",
@@ -2166,6 +2170,7 @@ export const webviewMessageHandler = async (
21662170
hasOpenAiCompatibleApiKey,
21672171
hasGeminiApiKey,
21682172
hasMistralApiKey,
2173+
hasJinaApiKey,
21692174
},
21702175
})
21712176
break

src/i18n/locales/en/embeddings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"openAiCompatibleConfigMissing": "OpenAI Compatible configuration missing for embedder creation",
4848
"geminiConfigMissing": "Gemini configuration missing for embedder creation",
4949
"mistralConfigMissing": "Mistral configuration missing for embedder creation",
50+
"jinaConfigMissing": "Jina configuration missing for embedder creation",
5051
"invalidEmbedderType": "Invalid embedder type configured: {{embedderProvider}}",
5152
"vectorDimensionNotDeterminedOpenAiCompatible": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Please ensure the 'Embedding Dimension' is correctly set in the OpenAI-Compatible provider settings.",
5253
"vectorDimensionNotDetermined": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Check model profiles or configuration.",

src/services/code-index/config-manager.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class CodeIndexConfigManager {
1919
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
2020
private geminiOptions?: { apiKey: string }
2121
private mistralOptions?: { apiKey: string }
22+
private jinaOptions?: { apiKey: string }
2223
private qdrantUrl?: string = "http://localhost:6333"
2324
private qdrantApiKey?: string
2425
private searchMinScore?: number
@@ -69,6 +70,7 @@ export class CodeIndexConfigManager {
6970
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
7071
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
7172
const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? ""
73+
const jinaApiKey = this.contextProxy?.getSecret("codebaseIndexJinaApiKey") ?? ""
7274

7375
// Update instance variables with configuration
7476
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
@@ -104,6 +106,8 @@ export class CodeIndexConfigManager {
104106
this.embedderProvider = "gemini"
105107
} else if (codebaseIndexEmbedderProvider === "mistral") {
106108
this.embedderProvider = "mistral"
109+
} else if (codebaseIndexEmbedderProvider === "jina") {
110+
this.embedderProvider = "jina"
107111
} else {
108112
this.embedderProvider = "openai"
109113
}
@@ -124,6 +128,7 @@ export class CodeIndexConfigManager {
124128

125129
this.geminiOptions = geminiApiKey ? { apiKey: geminiApiKey } : undefined
126130
this.mistralOptions = mistralApiKey ? { apiKey: mistralApiKey } : undefined
131+
this.jinaOptions = jinaApiKey ? { apiKey: jinaApiKey } : undefined
127132
}
128133

129134
/**
@@ -141,6 +146,7 @@ export class CodeIndexConfigManager {
141146
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
142147
geminiOptions?: { apiKey: string }
143148
mistralOptions?: { apiKey: string }
149+
jinaOptions?: { apiKey: string }
144150
qdrantUrl?: string
145151
qdrantApiKey?: string
146152
searchMinScore?: number
@@ -160,6 +166,7 @@ export class CodeIndexConfigManager {
160166
openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "",
161167
geminiApiKey: this.geminiOptions?.apiKey ?? "",
162168
mistralApiKey: this.mistralOptions?.apiKey ?? "",
169+
jinaApiKey: this.jinaOptions?.apiKey ?? "",
163170
qdrantUrl: this.qdrantUrl ?? "",
164171
qdrantApiKey: this.qdrantApiKey ?? "",
165172
}
@@ -184,6 +191,7 @@ export class CodeIndexConfigManager {
184191
openAiCompatibleOptions: this.openAiCompatibleOptions,
185192
geminiOptions: this.geminiOptions,
186193
mistralOptions: this.mistralOptions,
194+
jinaOptions: this.jinaOptions,
187195
qdrantUrl: this.qdrantUrl,
188196
qdrantApiKey: this.qdrantApiKey,
189197
searchMinScore: this.currentSearchMinScore,
@@ -221,6 +229,11 @@ export class CodeIndexConfigManager {
221229
const qdrantUrl = this.qdrantUrl
222230
const isConfigured = !!(apiKey && qdrantUrl)
223231
return isConfigured
232+
} else if (this.embedderProvider === "jina") {
233+
const apiKey = this.jinaOptions?.apiKey
234+
const qdrantUrl = this.qdrantUrl
235+
const isConfigured = !!(apiKey && qdrantUrl)
236+
return isConfigured
224237
}
225238
return false // Should not happen if embedderProvider is always set correctly
226239
}
@@ -292,6 +305,7 @@ export class CodeIndexConfigManager {
292305
const currentModelDimension = this.modelDimension
293306
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
294307
const currentMistralApiKey = this.mistralOptions?.apiKey ?? ""
308+
const currentJinaApiKey = this.jinaOptions?.apiKey ?? ""
295309
const currentQdrantUrl = this.qdrantUrl ?? ""
296310
const currentQdrantApiKey = this.qdrantApiKey ?? ""
297311

@@ -318,6 +332,11 @@ export class CodeIndexConfigManager {
318332
return true
319333
}
320334

335+
const prevJinaApiKey = prev?.jinaApiKey ?? ""
336+
if (prevJinaApiKey !== currentJinaApiKey) {
337+
return true
338+
}
339+
321340
// Check for model dimension changes (generic for all providers)
322341
if (prevModelDimension !== currentModelDimension) {
323342
return true
@@ -375,6 +394,7 @@ export class CodeIndexConfigManager {
375394
openAiCompatibleOptions: this.openAiCompatibleOptions,
376395
geminiOptions: this.geminiOptions,
377396
mistralOptions: this.mistralOptions,
397+
jinaOptions: this.jinaOptions,
378398
qdrantUrl: this.qdrantUrl,
379399
qdrantApiKey: this.qdrantApiKey,
380400
searchMinScore: this.currentSearchMinScore,

0 commit comments

Comments
 (0)