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/cli.js
CHANGED
|
@@ -854,7 +854,32 @@ function parseConfig(raw) {
|
|
|
854
854
|
};
|
|
855
855
|
let embeddingProvider;
|
|
856
856
|
let embeddingModel = void 0;
|
|
857
|
-
|
|
857
|
+
let customProvider = void 0;
|
|
858
|
+
if (input.embeddingProvider === "custom") {
|
|
859
|
+
embeddingProvider = "custom";
|
|
860
|
+
const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
|
|
861
|
+
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) {
|
|
862
|
+
customProvider = {
|
|
863
|
+
baseUrl: rawCustom.baseUrl.trim().replace(/\/+$/, ""),
|
|
864
|
+
model: rawCustom.model,
|
|
865
|
+
dimensions: rawCustom.dimensions,
|
|
866
|
+
apiKey: typeof rawCustom.apiKey === "string" ? rawCustom.apiKey : void 0,
|
|
867
|
+
maxTokens: typeof rawCustom.maxTokens === "number" ? rawCustom.maxTokens : void 0,
|
|
868
|
+
timeoutMs: typeof rawCustom.timeoutMs === "number" ? Math.max(1e3, rawCustom.timeoutMs) : void 0,
|
|
869
|
+
concurrency: typeof rawCustom.concurrency === "number" ? Math.max(1, Math.floor(rawCustom.concurrency)) : void 0,
|
|
870
|
+
requestIntervalMs: typeof rawCustom.requestIntervalMs === "number" ? Math.max(0, Math.floor(rawCustom.requestIntervalMs)) : void 0
|
|
871
|
+
};
|
|
872
|
+
if (!/\/v\d+\/?$/.test(customProvider.baseUrl)) {
|
|
873
|
+
console.warn(
|
|
874
|
+
`[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".`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
} else {
|
|
878
|
+
throw new Error(
|
|
879
|
+
"embeddingProvider is 'custom' but customProvider config is missing or invalid. Required fields: baseUrl (string), model (string), dimensions (positive integer)."
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
} else if (isValidProvider(input.embeddingProvider)) {
|
|
858
883
|
embeddingProvider = input.embeddingProvider;
|
|
859
884
|
if (input.embeddingModel) {
|
|
860
885
|
embeddingModel = isValidModel(input.embeddingModel, embeddingProvider) ? input.embeddingModel : DEFAULT_PROVIDER_MODELS[embeddingProvider];
|
|
@@ -865,6 +890,7 @@ function parseConfig(raw) {
|
|
|
865
890
|
return {
|
|
866
891
|
embeddingProvider,
|
|
867
892
|
embeddingModel,
|
|
893
|
+
customProvider,
|
|
868
894
|
scope: isValidScope(input.scope) ? input.scope : "project",
|
|
869
895
|
include: isStringArray(input.include) ? input.include : DEFAULT_INCLUDE,
|
|
870
896
|
exclude: isStringArray(input.exclude) ? input.exclude : DEFAULT_EXCLUDE,
|
|
@@ -2034,12 +2060,39 @@ function getProviderDisplayName(provider) {
|
|
|
2034
2060
|
return "Google (Gemini)";
|
|
2035
2061
|
case "ollama":
|
|
2036
2062
|
return "Ollama (Local)";
|
|
2063
|
+
case "custom":
|
|
2064
|
+
return "Custom (OpenAI-compatible)";
|
|
2037
2065
|
default:
|
|
2038
2066
|
return provider;
|
|
2039
2067
|
}
|
|
2040
2068
|
}
|
|
2069
|
+
function createCustomProviderInfo(config) {
|
|
2070
|
+
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
2071
|
+
return {
|
|
2072
|
+
provider: "custom",
|
|
2073
|
+
credentials: {
|
|
2074
|
+
provider: "custom",
|
|
2075
|
+
baseUrl,
|
|
2076
|
+
apiKey: config.apiKey
|
|
2077
|
+
},
|
|
2078
|
+
modelInfo: {
|
|
2079
|
+
provider: "custom",
|
|
2080
|
+
model: config.model,
|
|
2081
|
+
dimensions: config.dimensions,
|
|
2082
|
+
maxTokens: config.maxTokens ?? 8192,
|
|
2083
|
+
costPer1MTokens: 0,
|
|
2084
|
+
timeoutMs: config.timeoutMs ?? 3e4
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2041
2088
|
|
|
2042
2089
|
// src/embeddings/provider.ts
|
|
2090
|
+
var CustomProviderNonRetryableError = class extends Error {
|
|
2091
|
+
constructor(message) {
|
|
2092
|
+
super(message);
|
|
2093
|
+
this.name = "CustomProviderNonRetryableError";
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2043
2096
|
function createEmbeddingProvider(configuredProviderInfo) {
|
|
2044
2097
|
switch (configuredProviderInfo.provider) {
|
|
2045
2098
|
case "github-copilot":
|
|
@@ -2050,6 +2103,8 @@ function createEmbeddingProvider(configuredProviderInfo) {
|
|
|
2050
2103
|
return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2051
2104
|
case "ollama":
|
|
2052
2105
|
return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2106
|
+
case "custom":
|
|
2107
|
+
return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2053
2108
|
default: {
|
|
2054
2109
|
const _exhaustive = configuredProviderInfo;
|
|
2055
2110
|
throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
|
|
@@ -2284,6 +2339,90 @@ var OllamaEmbeddingProvider = class {
|
|
|
2284
2339
|
return this.modelInfo;
|
|
2285
2340
|
}
|
|
2286
2341
|
};
|
|
2342
|
+
var CustomEmbeddingProvider = class {
|
|
2343
|
+
constructor(credentials, modelInfo) {
|
|
2344
|
+
this.credentials = credentials;
|
|
2345
|
+
this.modelInfo = modelInfo;
|
|
2346
|
+
}
|
|
2347
|
+
async embedQuery(query) {
|
|
2348
|
+
const result = await this.embedBatch([query]);
|
|
2349
|
+
return {
|
|
2350
|
+
embedding: result.embeddings[0],
|
|
2351
|
+
tokensUsed: result.totalTokensUsed
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
async embedDocument(document) {
|
|
2355
|
+
const result = await this.embedBatch([document]);
|
|
2356
|
+
return {
|
|
2357
|
+
embedding: result.embeddings[0],
|
|
2358
|
+
tokensUsed: result.totalTokensUsed
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
async embedBatch(texts) {
|
|
2362
|
+
const headers = {
|
|
2363
|
+
"Content-Type": "application/json"
|
|
2364
|
+
};
|
|
2365
|
+
if (this.credentials.apiKey) {
|
|
2366
|
+
headers["Authorization"] = `Bearer ${this.credentials.apiKey}`;
|
|
2367
|
+
}
|
|
2368
|
+
const baseUrl = this.credentials.baseUrl ?? "";
|
|
2369
|
+
const timeoutMs = this.modelInfo.timeoutMs;
|
|
2370
|
+
const controller = new AbortController();
|
|
2371
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2372
|
+
let response;
|
|
2373
|
+
try {
|
|
2374
|
+
response = await fetch(`${baseUrl}/embeddings`, {
|
|
2375
|
+
method: "POST",
|
|
2376
|
+
headers,
|
|
2377
|
+
body: JSON.stringify({
|
|
2378
|
+
model: this.modelInfo.model,
|
|
2379
|
+
input: texts
|
|
2380
|
+
}),
|
|
2381
|
+
signal: controller.signal
|
|
2382
|
+
});
|
|
2383
|
+
} catch (error) {
|
|
2384
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2385
|
+
throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${baseUrl}/embeddings`);
|
|
2386
|
+
}
|
|
2387
|
+
throw error;
|
|
2388
|
+
} finally {
|
|
2389
|
+
clearTimeout(timeout);
|
|
2390
|
+
}
|
|
2391
|
+
if (!response.ok) {
|
|
2392
|
+
const errorText = await response.text();
|
|
2393
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
2394
|
+
throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
|
|
2395
|
+
}
|
|
2396
|
+
throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
|
|
2397
|
+
}
|
|
2398
|
+
const data = await response.json();
|
|
2399
|
+
if (data.data && Array.isArray(data.data)) {
|
|
2400
|
+
if (data.data.length > 0) {
|
|
2401
|
+
const actualDims = data.data[0].embedding.length;
|
|
2402
|
+
if (actualDims !== this.modelInfo.dimensions) {
|
|
2403
|
+
throw new Error(
|
|
2404
|
+
`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.`
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
if (data.data.length !== texts.length) {
|
|
2409
|
+
throw new Error(
|
|
2410
|
+
`Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
return {
|
|
2414
|
+
embeddings: data.data.map((d) => d.embedding),
|
|
2415
|
+
// Rough estimate: ~4 chars per token. Used as fallback when the server
|
|
2416
|
+
// doesn't return usage.total_tokens (e.g. llama.cpp, some vLLM configs).
|
|
2417
|
+
totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
|
|
2421
|
+
}
|
|
2422
|
+
getModelInfo() {
|
|
2423
|
+
return this.modelInfo;
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2287
2426
|
|
|
2288
2427
|
// src/utils/files.ts
|
|
2289
2428
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
@@ -3420,19 +3559,33 @@ var Indexer = class {
|
|
|
3420
3559
|
return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
3421
3560
|
case "ollama":
|
|
3422
3561
|
return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
|
|
3562
|
+
case "custom": {
|
|
3563
|
+
const customConfig = this.config.customProvider;
|
|
3564
|
+
return {
|
|
3565
|
+
concurrency: customConfig?.concurrency ?? 3,
|
|
3566
|
+
intervalMs: customConfig?.requestIntervalMs ?? 1e3,
|
|
3567
|
+
minRetryMs: 1e3,
|
|
3568
|
+
maxRetryMs: 3e4
|
|
3569
|
+
};
|
|
3570
|
+
}
|
|
3423
3571
|
default:
|
|
3424
3572
|
return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
3425
3573
|
}
|
|
3426
3574
|
}
|
|
3427
3575
|
async initialize() {
|
|
3428
|
-
if (this.config.embeddingProvider === "
|
|
3576
|
+
if (this.config.embeddingProvider === "custom") {
|
|
3577
|
+
if (!this.config.customProvider) {
|
|
3578
|
+
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
3579
|
+
}
|
|
3580
|
+
this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
|
|
3581
|
+
} else if (this.config.embeddingProvider === "auto") {
|
|
3429
3582
|
this.configuredProviderInfo = await tryDetectProvider();
|
|
3430
3583
|
} else {
|
|
3431
3584
|
this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
|
|
3432
3585
|
}
|
|
3433
3586
|
if (!this.configuredProviderInfo) {
|
|
3434
3587
|
throw new Error(
|
|
3435
|
-
"No embedding provider available. Configure GitHub, OpenAI, Google, or
|
|
3588
|
+
"No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
|
|
3436
3589
|
);
|
|
3437
3590
|
}
|
|
3438
3591
|
this.logger.info("Initializing indexer", {
|
|
@@ -3442,9 +3595,6 @@ var Indexer = class {
|
|
|
3442
3595
|
});
|
|
3443
3596
|
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
3444
3597
|
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
3445
|
-
if (this.checkForInterruptedIndexing()) {
|
|
3446
|
-
await this.recoverFromInterruptedIndexing();
|
|
3447
|
-
}
|
|
3448
3598
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
3449
3599
|
const storePath = path5.join(this.indexPath, "vectors");
|
|
3450
3600
|
this.store = new VectorStore(storePath, dimensions);
|
|
@@ -3465,6 +3615,9 @@ var Indexer = class {
|
|
|
3465
3615
|
const dbPath = path5.join(this.indexPath, "codebase.db");
|
|
3466
3616
|
const dbIsNew = !existsSync4(dbPath);
|
|
3467
3617
|
this.database = new Database(dbPath);
|
|
3618
|
+
if (this.checkForInterruptedIndexing()) {
|
|
3619
|
+
await this.recoverFromInterruptedIndexing();
|
|
3620
|
+
}
|
|
3468
3621
|
if (dbIsNew && this.store.count() > 0) {
|
|
3469
3622
|
this.migrateFromLegacyIndex();
|
|
3470
3623
|
}
|
|
@@ -3885,6 +4038,7 @@ var Indexer = class {
|
|
|
3885
4038
|
minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
|
|
3886
4039
|
maxTimeout: providerRateLimits.maxRetryMs,
|
|
3887
4040
|
factor: 2,
|
|
4041
|
+
shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
|
|
3888
4042
|
onFailedAttempt: (error) => {
|
|
3889
4043
|
const message = getErrorMessage(error);
|
|
3890
4044
|
if (isRateLimitError(error)) {
|
|
@@ -4522,7 +4676,7 @@ var CHUNK_TYPE_ENUM = [
|
|
|
4522
4676
|
function createMcpServer(projectRoot, config) {
|
|
4523
4677
|
const server = new McpServer({
|
|
4524
4678
|
name: "opencode-codebase-index",
|
|
4525
|
-
version: "0.5.
|
|
4679
|
+
version: "0.5.1"
|
|
4526
4680
|
});
|
|
4527
4681
|
const indexer = new Indexer(projectRoot, config);
|
|
4528
4682
|
let initialized = false;
|