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.js
CHANGED
|
@@ -853,7 +853,32 @@ function parseConfig(raw) {
|
|
|
853
853
|
};
|
|
854
854
|
let embeddingProvider;
|
|
855
855
|
let embeddingModel = void 0;
|
|
856
|
-
|
|
856
|
+
let customProvider = void 0;
|
|
857
|
+
if (input.embeddingProvider === "custom") {
|
|
858
|
+
embeddingProvider = "custom";
|
|
859
|
+
const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
|
|
860
|
+
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) {
|
|
861
|
+
customProvider = {
|
|
862
|
+
baseUrl: rawCustom.baseUrl.trim().replace(/\/+$/, ""),
|
|
863
|
+
model: rawCustom.model,
|
|
864
|
+
dimensions: rawCustom.dimensions,
|
|
865
|
+
apiKey: typeof rawCustom.apiKey === "string" ? rawCustom.apiKey : void 0,
|
|
866
|
+
maxTokens: typeof rawCustom.maxTokens === "number" ? rawCustom.maxTokens : void 0,
|
|
867
|
+
timeoutMs: typeof rawCustom.timeoutMs === "number" ? Math.max(1e3, rawCustom.timeoutMs) : void 0,
|
|
868
|
+
concurrency: typeof rawCustom.concurrency === "number" ? Math.max(1, Math.floor(rawCustom.concurrency)) : void 0,
|
|
869
|
+
requestIntervalMs: typeof rawCustom.requestIntervalMs === "number" ? Math.max(0, Math.floor(rawCustom.requestIntervalMs)) : void 0
|
|
870
|
+
};
|
|
871
|
+
if (!/\/v\d+\/?$/.test(customProvider.baseUrl)) {
|
|
872
|
+
console.warn(
|
|
873
|
+
`[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".`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
} else {
|
|
877
|
+
throw new Error(
|
|
878
|
+
"embeddingProvider is 'custom' but customProvider config is missing or invalid. Required fields: baseUrl (string), model (string), dimensions (positive integer)."
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
} else if (isValidProvider(input.embeddingProvider)) {
|
|
857
882
|
embeddingProvider = input.embeddingProvider;
|
|
858
883
|
if (input.embeddingModel) {
|
|
859
884
|
embeddingModel = isValidModel(input.embeddingModel, embeddingProvider) ? input.embeddingModel : DEFAULT_PROVIDER_MODELS[embeddingProvider];
|
|
@@ -864,6 +889,7 @@ function parseConfig(raw) {
|
|
|
864
889
|
return {
|
|
865
890
|
embeddingProvider,
|
|
866
891
|
embeddingModel,
|
|
892
|
+
customProvider,
|
|
867
893
|
scope: isValidScope(input.scope) ? input.scope : "project",
|
|
868
894
|
include: isStringArray(input.include) ? input.include : DEFAULT_INCLUDE,
|
|
869
895
|
exclude: isStringArray(input.exclude) ? input.exclude : DEFAULT_EXCLUDE,
|
|
@@ -2029,12 +2055,39 @@ function getProviderDisplayName(provider) {
|
|
|
2029
2055
|
return "Google (Gemini)";
|
|
2030
2056
|
case "ollama":
|
|
2031
2057
|
return "Ollama (Local)";
|
|
2058
|
+
case "custom":
|
|
2059
|
+
return "Custom (OpenAI-compatible)";
|
|
2032
2060
|
default:
|
|
2033
2061
|
return provider;
|
|
2034
2062
|
}
|
|
2035
2063
|
}
|
|
2064
|
+
function createCustomProviderInfo(config) {
|
|
2065
|
+
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
2066
|
+
return {
|
|
2067
|
+
provider: "custom",
|
|
2068
|
+
credentials: {
|
|
2069
|
+
provider: "custom",
|
|
2070
|
+
baseUrl,
|
|
2071
|
+
apiKey: config.apiKey
|
|
2072
|
+
},
|
|
2073
|
+
modelInfo: {
|
|
2074
|
+
provider: "custom",
|
|
2075
|
+
model: config.model,
|
|
2076
|
+
dimensions: config.dimensions,
|
|
2077
|
+
maxTokens: config.maxTokens ?? 8192,
|
|
2078
|
+
costPer1MTokens: 0,
|
|
2079
|
+
timeoutMs: config.timeoutMs ?? 3e4
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2036
2083
|
|
|
2037
2084
|
// src/embeddings/provider.ts
|
|
2085
|
+
var CustomProviderNonRetryableError = class extends Error {
|
|
2086
|
+
constructor(message) {
|
|
2087
|
+
super(message);
|
|
2088
|
+
this.name = "CustomProviderNonRetryableError";
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2038
2091
|
function createEmbeddingProvider(configuredProviderInfo) {
|
|
2039
2092
|
switch (configuredProviderInfo.provider) {
|
|
2040
2093
|
case "github-copilot":
|
|
@@ -2045,6 +2098,8 @@ function createEmbeddingProvider(configuredProviderInfo) {
|
|
|
2045
2098
|
return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2046
2099
|
case "ollama":
|
|
2047
2100
|
return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2101
|
+
case "custom":
|
|
2102
|
+
return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2048
2103
|
default: {
|
|
2049
2104
|
const _exhaustive = configuredProviderInfo;
|
|
2050
2105
|
throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
|
|
@@ -2279,6 +2334,90 @@ var OllamaEmbeddingProvider = class {
|
|
|
2279
2334
|
return this.modelInfo;
|
|
2280
2335
|
}
|
|
2281
2336
|
};
|
|
2337
|
+
var CustomEmbeddingProvider = class {
|
|
2338
|
+
constructor(credentials, modelInfo) {
|
|
2339
|
+
this.credentials = credentials;
|
|
2340
|
+
this.modelInfo = modelInfo;
|
|
2341
|
+
}
|
|
2342
|
+
async embedQuery(query) {
|
|
2343
|
+
const result = await this.embedBatch([query]);
|
|
2344
|
+
return {
|
|
2345
|
+
embedding: result.embeddings[0],
|
|
2346
|
+
tokensUsed: result.totalTokensUsed
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
async embedDocument(document) {
|
|
2350
|
+
const result = await this.embedBatch([document]);
|
|
2351
|
+
return {
|
|
2352
|
+
embedding: result.embeddings[0],
|
|
2353
|
+
tokensUsed: result.totalTokensUsed
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
async embedBatch(texts) {
|
|
2357
|
+
const headers = {
|
|
2358
|
+
"Content-Type": "application/json"
|
|
2359
|
+
};
|
|
2360
|
+
if (this.credentials.apiKey) {
|
|
2361
|
+
headers["Authorization"] = `Bearer ${this.credentials.apiKey}`;
|
|
2362
|
+
}
|
|
2363
|
+
const baseUrl = this.credentials.baseUrl ?? "";
|
|
2364
|
+
const timeoutMs = this.modelInfo.timeoutMs;
|
|
2365
|
+
const controller = new AbortController();
|
|
2366
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2367
|
+
let response;
|
|
2368
|
+
try {
|
|
2369
|
+
response = await fetch(`${baseUrl}/embeddings`, {
|
|
2370
|
+
method: "POST",
|
|
2371
|
+
headers,
|
|
2372
|
+
body: JSON.stringify({
|
|
2373
|
+
model: this.modelInfo.model,
|
|
2374
|
+
input: texts
|
|
2375
|
+
}),
|
|
2376
|
+
signal: controller.signal
|
|
2377
|
+
});
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2380
|
+
throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${baseUrl}/embeddings`);
|
|
2381
|
+
}
|
|
2382
|
+
throw error;
|
|
2383
|
+
} finally {
|
|
2384
|
+
clearTimeout(timeout);
|
|
2385
|
+
}
|
|
2386
|
+
if (!response.ok) {
|
|
2387
|
+
const errorText = await response.text();
|
|
2388
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
2389
|
+
throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
|
|
2390
|
+
}
|
|
2391
|
+
throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
|
|
2392
|
+
}
|
|
2393
|
+
const data = await response.json();
|
|
2394
|
+
if (data.data && Array.isArray(data.data)) {
|
|
2395
|
+
if (data.data.length > 0) {
|
|
2396
|
+
const actualDims = data.data[0].embedding.length;
|
|
2397
|
+
if (actualDims !== this.modelInfo.dimensions) {
|
|
2398
|
+
throw new Error(
|
|
2399
|
+
`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.`
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
if (data.data.length !== texts.length) {
|
|
2404
|
+
throw new Error(
|
|
2405
|
+
`Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
return {
|
|
2409
|
+
embeddings: data.data.map((d) => d.embedding),
|
|
2410
|
+
// Rough estimate: ~4 chars per token. Used as fallback when the server
|
|
2411
|
+
// doesn't return usage.total_tokens (e.g. llama.cpp, some vLLM configs).
|
|
2412
|
+
totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
|
|
2416
|
+
}
|
|
2417
|
+
getModelInfo() {
|
|
2418
|
+
return this.modelInfo;
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2282
2421
|
|
|
2283
2422
|
// src/utils/files.ts
|
|
2284
2423
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
@@ -3463,19 +3602,33 @@ var Indexer = class {
|
|
|
3463
3602
|
return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
3464
3603
|
case "ollama":
|
|
3465
3604
|
return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
|
|
3605
|
+
case "custom": {
|
|
3606
|
+
const customConfig = this.config.customProvider;
|
|
3607
|
+
return {
|
|
3608
|
+
concurrency: customConfig?.concurrency ?? 3,
|
|
3609
|
+
intervalMs: customConfig?.requestIntervalMs ?? 1e3,
|
|
3610
|
+
minRetryMs: 1e3,
|
|
3611
|
+
maxRetryMs: 3e4
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3466
3614
|
default:
|
|
3467
3615
|
return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
3468
3616
|
}
|
|
3469
3617
|
}
|
|
3470
3618
|
async initialize() {
|
|
3471
|
-
if (this.config.embeddingProvider === "
|
|
3619
|
+
if (this.config.embeddingProvider === "custom") {
|
|
3620
|
+
if (!this.config.customProvider) {
|
|
3621
|
+
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
3622
|
+
}
|
|
3623
|
+
this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
|
|
3624
|
+
} else if (this.config.embeddingProvider === "auto") {
|
|
3472
3625
|
this.configuredProviderInfo = await tryDetectProvider();
|
|
3473
3626
|
} else {
|
|
3474
3627
|
this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
|
|
3475
3628
|
}
|
|
3476
3629
|
if (!this.configuredProviderInfo) {
|
|
3477
3630
|
throw new Error(
|
|
3478
|
-
"No embedding provider available. Configure GitHub, OpenAI, Google, or
|
|
3631
|
+
"No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
|
|
3479
3632
|
);
|
|
3480
3633
|
}
|
|
3481
3634
|
this.logger.info("Initializing indexer", {
|
|
@@ -3485,9 +3638,6 @@ var Indexer = class {
|
|
|
3485
3638
|
});
|
|
3486
3639
|
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
3487
3640
|
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
3488
|
-
if (this.checkForInterruptedIndexing()) {
|
|
3489
|
-
await this.recoverFromInterruptedIndexing();
|
|
3490
|
-
}
|
|
3491
3641
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
3492
3642
|
const storePath = path5.join(this.indexPath, "vectors");
|
|
3493
3643
|
this.store = new VectorStore(storePath, dimensions);
|
|
@@ -3508,6 +3658,9 @@ var Indexer = class {
|
|
|
3508
3658
|
const dbPath = path5.join(this.indexPath, "codebase.db");
|
|
3509
3659
|
const dbIsNew = !existsSync4(dbPath);
|
|
3510
3660
|
this.database = new Database(dbPath);
|
|
3661
|
+
if (this.checkForInterruptedIndexing()) {
|
|
3662
|
+
await this.recoverFromInterruptedIndexing();
|
|
3663
|
+
}
|
|
3511
3664
|
if (dbIsNew && this.store.count() > 0) {
|
|
3512
3665
|
this.migrateFromLegacyIndex();
|
|
3513
3666
|
}
|
|
@@ -3928,6 +4081,7 @@ var Indexer = class {
|
|
|
3928
4081
|
minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
|
|
3929
4082
|
maxTimeout: providerRateLimits.maxRetryMs,
|
|
3930
4083
|
factor: 2,
|
|
4084
|
+
shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
|
|
3931
4085
|
onFailedAttempt: (error) => {
|
|
3932
4086
|
const message = getErrorMessage(error);
|
|
3933
4087
|
if (isRateLimitError(error)) {
|