opencode-codebase-index 0.5.0 → 0.5.1
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/README.md +38 -3
- package/dist/cli.cjs +161 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +161 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +160 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +160 -6
- package/dist/index.js.map +1 -1
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -857,7 +857,32 @@ function parseConfig(raw) {
|
|
|
857
857
|
};
|
|
858
858
|
let embeddingProvider;
|
|
859
859
|
let embeddingModel = void 0;
|
|
860
|
-
|
|
860
|
+
let customProvider = void 0;
|
|
861
|
+
if (input.embeddingProvider === "custom") {
|
|
862
|
+
embeddingProvider = "custom";
|
|
863
|
+
const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
|
|
864
|
+
if (rawCustom && typeof rawCustom.baseUrl === "string" && rawCustom.baseUrl.trim().length > 0 && typeof rawCustom.model === "string" && rawCustom.model.trim().length > 0 && typeof rawCustom.dimensions === "number" && Number.isInteger(rawCustom.dimensions) && rawCustom.dimensions > 0) {
|
|
865
|
+
customProvider = {
|
|
866
|
+
baseUrl: rawCustom.baseUrl.trim().replace(/\/+$/, ""),
|
|
867
|
+
model: rawCustom.model,
|
|
868
|
+
dimensions: rawCustom.dimensions,
|
|
869
|
+
apiKey: typeof rawCustom.apiKey === "string" ? rawCustom.apiKey : void 0,
|
|
870
|
+
maxTokens: typeof rawCustom.maxTokens === "number" ? rawCustom.maxTokens : void 0,
|
|
871
|
+
timeoutMs: typeof rawCustom.timeoutMs === "number" ? Math.max(1e3, rawCustom.timeoutMs) : void 0,
|
|
872
|
+
concurrency: typeof rawCustom.concurrency === "number" ? Math.max(1, Math.floor(rawCustom.concurrency)) : void 0,
|
|
873
|
+
requestIntervalMs: typeof rawCustom.requestIntervalMs === "number" ? Math.max(0, Math.floor(rawCustom.requestIntervalMs)) : void 0
|
|
874
|
+
};
|
|
875
|
+
if (!/\/v\d+\/?$/.test(customProvider.baseUrl)) {
|
|
876
|
+
console.warn(
|
|
877
|
+
`[codebase-index] Warning: customProvider.baseUrl ("${customProvider.baseUrl}") does not end with an API version path like /v1. The plugin appends /embeddings automatically, so the full URL will be "${customProvider.baseUrl}/embeddings". If your provider expects /v1/embeddings, set baseUrl to "${customProvider.baseUrl}/v1".`
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
} else {
|
|
881
|
+
throw new Error(
|
|
882
|
+
"embeddingProvider is 'custom' but customProvider config is missing or invalid. Required fields: baseUrl (string), model (string), dimensions (positive integer)."
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
} else if (isValidProvider(input.embeddingProvider)) {
|
|
861
886
|
embeddingProvider = input.embeddingProvider;
|
|
862
887
|
if (input.embeddingModel) {
|
|
863
888
|
embeddingModel = isValidModel(input.embeddingModel, embeddingProvider) ? input.embeddingModel : DEFAULT_PROVIDER_MODELS[embeddingProvider];
|
|
@@ -868,6 +893,7 @@ function parseConfig(raw) {
|
|
|
868
893
|
return {
|
|
869
894
|
embeddingProvider,
|
|
870
895
|
embeddingModel,
|
|
896
|
+
customProvider,
|
|
871
897
|
scope: isValidScope(input.scope) ? input.scope : "project",
|
|
872
898
|
include: isStringArray(input.include) ? input.include : DEFAULT_INCLUDE,
|
|
873
899
|
exclude: isStringArray(input.exclude) ? input.exclude : DEFAULT_EXCLUDE,
|
|
@@ -2033,12 +2059,39 @@ function getProviderDisplayName(provider) {
|
|
|
2033
2059
|
return "Google (Gemini)";
|
|
2034
2060
|
case "ollama":
|
|
2035
2061
|
return "Ollama (Local)";
|
|
2062
|
+
case "custom":
|
|
2063
|
+
return "Custom (OpenAI-compatible)";
|
|
2036
2064
|
default:
|
|
2037
2065
|
return provider;
|
|
2038
2066
|
}
|
|
2039
2067
|
}
|
|
2068
|
+
function createCustomProviderInfo(config) {
|
|
2069
|
+
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
2070
|
+
return {
|
|
2071
|
+
provider: "custom",
|
|
2072
|
+
credentials: {
|
|
2073
|
+
provider: "custom",
|
|
2074
|
+
baseUrl,
|
|
2075
|
+
apiKey: config.apiKey
|
|
2076
|
+
},
|
|
2077
|
+
modelInfo: {
|
|
2078
|
+
provider: "custom",
|
|
2079
|
+
model: config.model,
|
|
2080
|
+
dimensions: config.dimensions,
|
|
2081
|
+
maxTokens: config.maxTokens ?? 8192,
|
|
2082
|
+
costPer1MTokens: 0,
|
|
2083
|
+
timeoutMs: config.timeoutMs ?? 3e4
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2040
2087
|
|
|
2041
2088
|
// src/embeddings/provider.ts
|
|
2089
|
+
var CustomProviderNonRetryableError = class extends Error {
|
|
2090
|
+
constructor(message) {
|
|
2091
|
+
super(message);
|
|
2092
|
+
this.name = "CustomProviderNonRetryableError";
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2042
2095
|
function createEmbeddingProvider(configuredProviderInfo) {
|
|
2043
2096
|
switch (configuredProviderInfo.provider) {
|
|
2044
2097
|
case "github-copilot":
|
|
@@ -2049,6 +2102,8 @@ function createEmbeddingProvider(configuredProviderInfo) {
|
|
|
2049
2102
|
return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2050
2103
|
case "ollama":
|
|
2051
2104
|
return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2105
|
+
case "custom":
|
|
2106
|
+
return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2052
2107
|
default: {
|
|
2053
2108
|
const _exhaustive = configuredProviderInfo;
|
|
2054
2109
|
throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
|
|
@@ -2283,6 +2338,90 @@ var OllamaEmbeddingProvider = class {
|
|
|
2283
2338
|
return this.modelInfo;
|
|
2284
2339
|
}
|
|
2285
2340
|
};
|
|
2341
|
+
var CustomEmbeddingProvider = class {
|
|
2342
|
+
constructor(credentials, modelInfo) {
|
|
2343
|
+
this.credentials = credentials;
|
|
2344
|
+
this.modelInfo = modelInfo;
|
|
2345
|
+
}
|
|
2346
|
+
async embedQuery(query) {
|
|
2347
|
+
const result = await this.embedBatch([query]);
|
|
2348
|
+
return {
|
|
2349
|
+
embedding: result.embeddings[0],
|
|
2350
|
+
tokensUsed: result.totalTokensUsed
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
async embedDocument(document) {
|
|
2354
|
+
const result = await this.embedBatch([document]);
|
|
2355
|
+
return {
|
|
2356
|
+
embedding: result.embeddings[0],
|
|
2357
|
+
tokensUsed: result.totalTokensUsed
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
async embedBatch(texts) {
|
|
2361
|
+
const headers = {
|
|
2362
|
+
"Content-Type": "application/json"
|
|
2363
|
+
};
|
|
2364
|
+
if (this.credentials.apiKey) {
|
|
2365
|
+
headers["Authorization"] = `Bearer ${this.credentials.apiKey}`;
|
|
2366
|
+
}
|
|
2367
|
+
const baseUrl = this.credentials.baseUrl ?? "";
|
|
2368
|
+
const timeoutMs = this.modelInfo.timeoutMs;
|
|
2369
|
+
const controller = new AbortController();
|
|
2370
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2371
|
+
let response;
|
|
2372
|
+
try {
|
|
2373
|
+
response = await fetch(`${baseUrl}/embeddings`, {
|
|
2374
|
+
method: "POST",
|
|
2375
|
+
headers,
|
|
2376
|
+
body: JSON.stringify({
|
|
2377
|
+
model: this.modelInfo.model,
|
|
2378
|
+
input: texts
|
|
2379
|
+
}),
|
|
2380
|
+
signal: controller.signal
|
|
2381
|
+
});
|
|
2382
|
+
} catch (error) {
|
|
2383
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2384
|
+
throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${baseUrl}/embeddings`);
|
|
2385
|
+
}
|
|
2386
|
+
throw error;
|
|
2387
|
+
} finally {
|
|
2388
|
+
clearTimeout(timeout);
|
|
2389
|
+
}
|
|
2390
|
+
if (!response.ok) {
|
|
2391
|
+
const errorText = await response.text();
|
|
2392
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
2393
|
+
throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
|
|
2394
|
+
}
|
|
2395
|
+
throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
|
|
2396
|
+
}
|
|
2397
|
+
const data = await response.json();
|
|
2398
|
+
if (data.data && Array.isArray(data.data)) {
|
|
2399
|
+
if (data.data.length > 0) {
|
|
2400
|
+
const actualDims = data.data[0].embedding.length;
|
|
2401
|
+
if (actualDims !== this.modelInfo.dimensions) {
|
|
2402
|
+
throw new Error(
|
|
2403
|
+
`Dimension mismatch: customProvider.dimensions is ${this.modelInfo.dimensions}, but the API returned vectors with ${actualDims} dimensions. Update your config to match the model's actual output dimensions.`
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
if (data.data.length !== texts.length) {
|
|
2408
|
+
throw new Error(
|
|
2409
|
+
`Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
return {
|
|
2413
|
+
embeddings: data.data.map((d) => d.embedding),
|
|
2414
|
+
// Rough estimate: ~4 chars per token. Used as fallback when the server
|
|
2415
|
+
// doesn't return usage.total_tokens (e.g. llama.cpp, some vLLM configs).
|
|
2416
|
+
totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
|
|
2420
|
+
}
|
|
2421
|
+
getModelInfo() {
|
|
2422
|
+
return this.modelInfo;
|
|
2423
|
+
}
|
|
2424
|
+
};
|
|
2286
2425
|
|
|
2287
2426
|
// src/utils/files.ts
|
|
2288
2427
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
@@ -3468,19 +3607,33 @@ var Indexer = class {
|
|
|
3468
3607
|
return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
3469
3608
|
case "ollama":
|
|
3470
3609
|
return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
|
|
3610
|
+
case "custom": {
|
|
3611
|
+
const customConfig = this.config.customProvider;
|
|
3612
|
+
return {
|
|
3613
|
+
concurrency: customConfig?.concurrency ?? 3,
|
|
3614
|
+
intervalMs: customConfig?.requestIntervalMs ?? 1e3,
|
|
3615
|
+
minRetryMs: 1e3,
|
|
3616
|
+
maxRetryMs: 3e4
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3471
3619
|
default:
|
|
3472
3620
|
return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
3473
3621
|
}
|
|
3474
3622
|
}
|
|
3475
3623
|
async initialize() {
|
|
3476
|
-
if (this.config.embeddingProvider === "
|
|
3624
|
+
if (this.config.embeddingProvider === "custom") {
|
|
3625
|
+
if (!this.config.customProvider) {
|
|
3626
|
+
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
3627
|
+
}
|
|
3628
|
+
this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
|
|
3629
|
+
} else if (this.config.embeddingProvider === "auto") {
|
|
3477
3630
|
this.configuredProviderInfo = await tryDetectProvider();
|
|
3478
3631
|
} else {
|
|
3479
3632
|
this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
|
|
3480
3633
|
}
|
|
3481
3634
|
if (!this.configuredProviderInfo) {
|
|
3482
3635
|
throw new Error(
|
|
3483
|
-
"No embedding provider available. Configure GitHub, OpenAI, Google, or
|
|
3636
|
+
"No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
|
|
3484
3637
|
);
|
|
3485
3638
|
}
|
|
3486
3639
|
this.logger.info("Initializing indexer", {
|
|
@@ -3490,9 +3643,6 @@ var Indexer = class {
|
|
|
3490
3643
|
});
|
|
3491
3644
|
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
3492
3645
|
await import_fs4.promises.mkdir(this.indexPath, { recursive: true });
|
|
3493
|
-
if (this.checkForInterruptedIndexing()) {
|
|
3494
|
-
await this.recoverFromInterruptedIndexing();
|
|
3495
|
-
}
|
|
3496
3646
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
3497
3647
|
const storePath = path5.join(this.indexPath, "vectors");
|
|
3498
3648
|
this.store = new VectorStore(storePath, dimensions);
|
|
@@ -3513,6 +3663,9 @@ var Indexer = class {
|
|
|
3513
3663
|
const dbPath = path5.join(this.indexPath, "codebase.db");
|
|
3514
3664
|
const dbIsNew = !(0, import_fs4.existsSync)(dbPath);
|
|
3515
3665
|
this.database = new Database(dbPath);
|
|
3666
|
+
if (this.checkForInterruptedIndexing()) {
|
|
3667
|
+
await this.recoverFromInterruptedIndexing();
|
|
3668
|
+
}
|
|
3516
3669
|
if (dbIsNew && this.store.count() > 0) {
|
|
3517
3670
|
this.migrateFromLegacyIndex();
|
|
3518
3671
|
}
|
|
@@ -3933,6 +4086,7 @@ var Indexer = class {
|
|
|
3933
4086
|
minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
|
|
3934
4087
|
maxTimeout: providerRateLimits.maxRetryMs,
|
|
3935
4088
|
factor: 2,
|
|
4089
|
+
shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
|
|
3936
4090
|
onFailedAttempt: (error) => {
|
|
3937
4091
|
const message = getErrorMessage(error);
|
|
3938
4092
|
if (isRateLimitError(error)) {
|