opencode-rag-plugin 1.11.0 → 1.12.4
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 +14 -6
- package/dist/api.js +21 -82
- package/dist/api.js.map +1 -1
- package/dist/chunker/image.d.ts +14 -0
- package/dist/chunker/image.js +356 -0
- package/dist/chunker/image.js.map +1 -0
- package/dist/chunker/loader.js +5 -0
- package/dist/chunker/loader.js.map +1 -1
- package/dist/chunker/xml.js +1 -1
- package/dist/chunker/xml.js.map +1 -1
- package/dist/cli.js +123 -108
- package/dist/cli.js.map +1 -1
- package/dist/core/bootstrap.d.ts +18 -0
- package/dist/core/bootstrap.js +80 -0
- package/dist/core/bootstrap.js.map +1 -0
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.js +39 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/interfaces.d.ts +1 -0
- package/dist/core/resolve-api-key.js +3 -0
- package/dist/core/resolve-api-key.js.map +1 -1
- package/dist/core/runtime-overrides.d.ts +5 -0
- package/dist/core/runtime-overrides.js +17 -0
- package/dist/core/runtime-overrides.js.map +1 -1
- package/dist/describer/describer.js +1 -1
- package/dist/describer/describer.js.map +1 -1
- package/dist/embedder/health.d.ts +25 -0
- package/dist/embedder/health.js +346 -0
- package/dist/embedder/health.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +1 -1
- package/dist/indexer.js +192 -22
- package/dist/indexer.js.map +1 -1
- package/dist/mcp/server.js +7 -61
- package/dist/mcp/server.js.map +1 -1
- package/dist/retriever/keyword-index.js +38 -5
- package/dist/retriever/keyword-index.js.map +1 -1
- package/dist/web/api.d.ts +8 -0
- package/dist/web/api.js +81 -0
- package/dist/web/api.js.map +1 -1
- package/dist/web/ui/index.html +503 -98
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -7,12 +7,10 @@ import { spawnSync } from "node:child_process";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import chokidar from "chokidar";
|
|
9
9
|
import pc from "picocolors";
|
|
10
|
-
import { loadConfig, DEFAULT_CONFIG
|
|
11
|
-
import {
|
|
10
|
+
import { loadConfig, DEFAULT_CONFIG } from "./core/config.js";
|
|
11
|
+
import { resolveRagContext } from "./core/bootstrap.js";
|
|
12
12
|
import { appendDebugLog } from "./core/fileLogger.js";
|
|
13
|
-
import {
|
|
14
|
-
import { createEmbedder } from "./embedder/factory.js";
|
|
15
|
-
import { createDescriptionProvider } from "./describer/factory.js";
|
|
13
|
+
import { checkProviderHealth, pullOllamaModels } from "./embedder/health.js";
|
|
16
14
|
import { retrieve } from "./retriever/retriever.js";
|
|
17
15
|
import { createWatchPassScheduler, createWatchIgnore, getIndexStatusSummary, runIndexPass, } from "./indexer.js";
|
|
18
16
|
const c = {
|
|
@@ -42,55 +40,16 @@ function logCliInfo(logFilePath, scope, message) {
|
|
|
42
40
|
console.log(message);
|
|
43
41
|
//appendDebugLog(logFilePath, { scope, message });
|
|
44
42
|
}
|
|
45
|
-
async function
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const cfg = loadConfig(configPath);
|
|
51
|
-
resolveApiKey(cfg, worktree);
|
|
52
|
-
await loadChunkersFromConfig(cfg, path.dirname(configPath));
|
|
53
|
-
logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.file(configPath)}`);
|
|
54
|
-
return logConfigDetails(logFilePath, cfg);
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
logCliError(logFilePath, "config", `Could not load config from ${opt.config}, using defaults`, err);
|
|
58
|
-
console.error(c.warn(`Could not load config from ${opt.config}, using defaults`));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
for (const loc of ["opencode-rag.json", ".opencode/opencode-rag.json", ".opencode/rag.json"]) {
|
|
62
|
-
const configPath = path.resolve(loc);
|
|
63
|
-
try {
|
|
64
|
-
const cfg = loadConfig(configPath);
|
|
65
|
-
resolveApiKey(cfg, worktree);
|
|
66
|
-
await loadChunkersFromConfig(cfg, path.dirname(configPath));
|
|
67
|
-
logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.file(configPath)}`);
|
|
68
|
-
return logConfigDetails(logFilePath, cfg);
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
logCliError(logFilePath, "config", `Failed to load config from ${configPath}`, err);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.dim("using defaults (no opencode-rag.json found)")}`);
|
|
75
|
-
return logConfigDetails(logFilePath, DEFAULT_CONFIG);
|
|
76
|
-
}
|
|
77
|
-
async function loadCliKeywordIndex(storePath, logFilePath) {
|
|
78
|
-
const { KeywordIndex } = await import("./retriever/keyword-index.js");
|
|
79
|
-
try {
|
|
80
|
-
const index = await KeywordIndex.load(storePath);
|
|
81
|
-
logCliInfo(logFilePath, "keyword-index", `${c.label("Keyword index loaded")} (${c.num(index.count())} chunks)`);
|
|
82
|
-
return index;
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
logCliInfo(logFilePath, "keyword-index", c.warn("Creating keyword index"));
|
|
86
|
-
return new KeywordIndex(storePath);
|
|
87
|
-
}
|
|
43
|
+
async function resolveCliContext(opt, logFilePath) {
|
|
44
|
+
const ctx = await resolveRagContext({ configPath: opt.config });
|
|
45
|
+
logCliInfo(logFilePath, "config", `${c.label("Config:")} ${c.file(ctx.logFilePath)}`);
|
|
46
|
+
logConfigDetails(logFilePath, ctx.config);
|
|
47
|
+
return ctx;
|
|
88
48
|
}
|
|
89
49
|
function logConfigDetails(logFilePath, config) {
|
|
90
50
|
logCliInfo(logFilePath, "config", ` ${c.label("Embedding provider:")} ${c.value(config.embedding.provider)}`);
|
|
91
51
|
logCliInfo(logFilePath, "config", ` ${c.label("Embedding model:")} ${c.value(config.embedding.model)}`);
|
|
92
52
|
logCliInfo(logFilePath, "config", ` ${c.label("Vector store:")} ${c.file(config.vectorStore.path)}`);
|
|
93
|
-
return config;
|
|
94
53
|
}
|
|
95
54
|
function formatTimestamp(timestamp) {
|
|
96
55
|
if (!timestamp)
|
|
@@ -369,26 +328,13 @@ program
|
|
|
369
328
|
try {
|
|
370
329
|
const cwd = process.cwd();
|
|
371
330
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
372
|
-
const
|
|
373
|
-
|
|
331
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
332
|
+
const { config, embedder, store, storePath, keywordIndex, descriptionProvider, dimension } = ctx;
|
|
333
|
+
logFilePath = ctx.logFilePath;
|
|
374
334
|
logCliInfo(logFilePath, "index", `\n${c.heading("Indexing workspace...")}`);
|
|
375
|
-
|
|
376
|
-
// Detect actual vector dimension from the model
|
|
377
|
-
const probe = await embedder.embed(["dimension-probe"], "query");
|
|
378
|
-
let vectorDimension = 384;
|
|
379
|
-
if (probe && probe[0] && probe[0].length > 0 && typeof probe[0][0] === "number") {
|
|
380
|
-
vectorDimension = probe[0].length;
|
|
381
|
-
}
|
|
382
|
-
logCliInfo(logFilePath, "index", ` ${c.label("Vector dimension:")} ${c.num(vectorDimension)}`);
|
|
383
|
-
const { LanceDBStore } = await import("./vectorstore/lancedb.js");
|
|
384
|
-
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path), vectorDimension);
|
|
385
|
-
const keywordIndex = await loadCliKeywordIndex(path.resolve(cwd, config.vectorStore.path), logFilePath);
|
|
386
|
-
// Create description provider (enabled by default)
|
|
387
|
-
const descriptionConfig = config.description ?? { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
|
|
388
|
-
const descriptionProvider = descriptionConfig.enabled
|
|
389
|
-
? createDescriptionProvider(descriptionConfig)
|
|
390
|
-
: undefined;
|
|
335
|
+
logCliInfo(logFilePath, "index", ` ${c.label("Vector dimension:")} ${c.num(dimension)}`);
|
|
391
336
|
if (descriptionProvider) {
|
|
337
|
+
const descriptionConfig = config.description ?? { provider: "ollama", model: "qwen2.5:3b" };
|
|
392
338
|
logCliInfo(logFilePath, "index", ` ${c.label("Description LLM:")} ${c.value(descriptionConfig.model)} (${descriptionConfig.provider})`);
|
|
393
339
|
}
|
|
394
340
|
logCliInfo(logFilePath, "index", `${c.label("Scanning:")} ${c.file(cwd)}`);
|
|
@@ -396,7 +342,7 @@ program
|
|
|
396
342
|
const passStarted = Date.now();
|
|
397
343
|
const stats = await runIndexPass({
|
|
398
344
|
cwd,
|
|
399
|
-
storePath
|
|
345
|
+
storePath,
|
|
400
346
|
config,
|
|
401
347
|
store,
|
|
402
348
|
embedder,
|
|
@@ -427,7 +373,7 @@ program
|
|
|
427
373
|
logCliError(logFilePath, "watch", `\nWatch reindex failed: ${message}`, error);
|
|
428
374
|
}, 300);
|
|
429
375
|
const watcher = chokidar.watch(cwd, {
|
|
430
|
-
ignored: createWatchIgnore(cwd, config,
|
|
376
|
+
ignored: createWatchIgnore(cwd, config, storePath),
|
|
431
377
|
ignoreInitial: true,
|
|
432
378
|
persistent: true,
|
|
433
379
|
});
|
|
@@ -477,13 +423,11 @@ program
|
|
|
477
423
|
try {
|
|
478
424
|
const cwd = process.cwd();
|
|
479
425
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
480
|
-
const
|
|
481
|
-
|
|
426
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
427
|
+
const { config, embedder, store, storePath, keywordIndex } = ctx;
|
|
428
|
+
logFilePath = ctx.logFilePath;
|
|
482
429
|
logCliInfo(logFilePath, "query", `\n${c.heading("Querying:")} "${query}"`);
|
|
483
430
|
logCliInfo(logFilePath, "query", `${c.label("Top-K:")} ${c.num(parseInt(options.topK ?? "10", 10))}`);
|
|
484
|
-
const embedder = createEmbedder(config);
|
|
485
|
-
const { LanceDBStore } = await import("./vectorstore/lancedb.js");
|
|
486
|
-
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
487
431
|
const indexedCount = await store.count();
|
|
488
432
|
if (indexedCount === 0) {
|
|
489
433
|
logCliInfo(logFilePath, "query", `${c.warn("No indexed chunks found.")} Run 'opencode-rag index' first.`);
|
|
@@ -492,7 +436,6 @@ program
|
|
|
492
436
|
logCliInfo(logFilePath, "query", `${c.label("Searching")} ${c.num(indexedCount)} indexed chunks...`);
|
|
493
437
|
const topK = parseInt(options.topK ?? "10", 10);
|
|
494
438
|
const minScore = config.retrieval.minScore;
|
|
495
|
-
const keywordIndex = await loadCliKeywordIndex(path.resolve(cwd, config.vectorStore.path), logFilePath);
|
|
496
439
|
const hybridCfg = config.retrieval.hybridSearch;
|
|
497
440
|
const rawResults = await retrieve(query, embedder, store, {
|
|
498
441
|
topK,
|
|
@@ -540,10 +483,9 @@ program
|
|
|
540
483
|
try {
|
|
541
484
|
const cwd = process.cwd();
|
|
542
485
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
486
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
487
|
+
const { store } = ctx;
|
|
488
|
+
logFilePath = ctx.logFilePath;
|
|
547
489
|
const prevCount = await store.count();
|
|
548
490
|
if (prevCount === 0) {
|
|
549
491
|
logCliInfo(logFilePath, "clear", c.warn("No indexed data to clear."));
|
|
@@ -551,7 +493,7 @@ program
|
|
|
551
493
|
else {
|
|
552
494
|
logCliInfo(logFilePath, "clear", `${c.label("Clearing")} ${c.num(prevCount)} indexed chunks...`);
|
|
553
495
|
}
|
|
554
|
-
await store.
|
|
496
|
+
await store.clear();
|
|
555
497
|
logCliInfo(logFilePath, "clear", `${c.success("Done.")} vector database directory removed.`);
|
|
556
498
|
}
|
|
557
499
|
catch (err) {
|
|
@@ -569,14 +511,13 @@ program
|
|
|
569
511
|
try {
|
|
570
512
|
const cwd = process.cwd();
|
|
571
513
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
514
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
515
|
+
const { config, store, storePath, keywordIndex } = ctx;
|
|
516
|
+
logFilePath = ctx.logFilePath;
|
|
576
517
|
const count = await store.count();
|
|
577
|
-
const summary = await getIndexStatusSummary(cwd,
|
|
518
|
+
const summary = await getIndexStatusSummary(cwd, storePath, config, store);
|
|
578
519
|
logCliInfo(logFilePath, "status", `\n${c.heading("Indexed chunks:")} ${c.num(count)}`);
|
|
579
|
-
logCliInfo(logFilePath, "status", `${c.label("Store path:")} ${c.file(
|
|
520
|
+
logCliInfo(logFilePath, "status", `${c.label("Store path:")} ${c.file(storePath)}`);
|
|
580
521
|
logCliInfo(logFilePath, "status", `${c.label("Embedding provider:")} ${c.value(config.embedding.provider)}`);
|
|
581
522
|
logCliInfo(logFilePath, "status", `${c.label("Embedding model:")} ${c.value(config.embedding.model)}`);
|
|
582
523
|
logCliInfo(logFilePath, "status", `${c.label("File extensions:")} ${config.indexing.includeExtensions.join(", ")}`);
|
|
@@ -590,7 +531,7 @@ program
|
|
|
590
531
|
logCliInfo(logFilePath, "status", `${c.label("Pending files:")} ${c.num(summary.pendingFiles)}`);
|
|
591
532
|
logCliInfo(logFilePath, "status", `${c.label("Watch mode:")} ${c.dim("off")}`);
|
|
592
533
|
const kiCount = config.retrieval.hybridSearch?.enabled
|
|
593
|
-
?
|
|
534
|
+
? keywordIndex?.count() ?? 0
|
|
594
535
|
: 0;
|
|
595
536
|
logCliInfo(logFilePath, "status", `${c.label("Keyword index:")} ${config.retrieval.hybridSearch?.enabled ? c.enabled("enabled") : c.disabled("disabled")} (${c.num(kiCount)} chunks)`);
|
|
596
537
|
if (summary.rebuildRequired) {
|
|
@@ -612,10 +553,9 @@ program
|
|
|
612
553
|
try {
|
|
613
554
|
const cwd = process.cwd();
|
|
614
555
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
556
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
557
|
+
const { store } = ctx;
|
|
558
|
+
logFilePath = ctx.logFilePath;
|
|
619
559
|
const files = await store.listFiles();
|
|
620
560
|
if (files.length === 0) {
|
|
621
561
|
logCliInfo(logFilePath, "list", `${c.warn("No indexed files found.")} Run 'opencode-rag index' first.`);
|
|
@@ -641,10 +581,9 @@ program
|
|
|
641
581
|
try {
|
|
642
582
|
const cwd = process.cwd();
|
|
643
583
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
584
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
585
|
+
const { store } = ctx;
|
|
586
|
+
logFilePath = ctx.logFilePath;
|
|
648
587
|
const chunks = await store.getChunksByFilePath(file);
|
|
649
588
|
if (chunks.length === 0) {
|
|
650
589
|
logCliInfo(logFilePath, "show", `${c.warn(`No chunks found for '${file}'.`)}`);
|
|
@@ -676,10 +615,9 @@ program
|
|
|
676
615
|
try {
|
|
677
616
|
const cwd = process.cwd();
|
|
678
617
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const store = new LanceDBStore(path.resolve(cwd, config.vectorStore.path));
|
|
618
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
619
|
+
const { store, storePath } = ctx;
|
|
620
|
+
logFilePath = ctx.logFilePath;
|
|
683
621
|
const total = await store.count();
|
|
684
622
|
if (total === 0) {
|
|
685
623
|
logCliInfo(logFilePath, "dump", `${c.warn("No indexed chunks found.")} Run 'opencode-rag index' first.`);
|
|
@@ -714,12 +652,13 @@ program
|
|
|
714
652
|
try {
|
|
715
653
|
const cwd = process.cwd();
|
|
716
654
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
717
|
-
const
|
|
718
|
-
|
|
655
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
656
|
+
const { config, storePath } = ctx;
|
|
657
|
+
logFilePath = ctx.logFilePath;
|
|
719
658
|
const port = parseInt(options.port ?? String(config.ui?.port ?? 3210), 10);
|
|
720
659
|
const openBrowser = options.open !== false && (config.ui?.openBrowser ?? true);
|
|
721
660
|
const { startWebUi } = await import("./web/server.js");
|
|
722
|
-
const server = await startWebUi(
|
|
661
|
+
const server = await startWebUi(storePath, port);
|
|
723
662
|
const url = `http://127.0.0.1:${server.port}`;
|
|
724
663
|
logCliInfo(logFilePath, "ui", `\n${c.heading("OpenCodeRAG Web UI")}`);
|
|
725
664
|
logCliInfo(logFilePath, "ui", ` ${c.label("URL:")} ${c.value(url)}`);
|
|
@@ -783,8 +722,8 @@ program
|
|
|
783
722
|
try {
|
|
784
723
|
const cwd = process.cwd();
|
|
785
724
|
const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
786
|
-
const
|
|
787
|
-
const storePath =
|
|
725
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
726
|
+
const { storePath } = ctx;
|
|
788
727
|
const { listSessions } = await import("./eval/storage.js");
|
|
789
728
|
const sessions = listSessions(storePath);
|
|
790
729
|
if (sessions.length === 0) {
|
|
@@ -818,8 +757,8 @@ program
|
|
|
818
757
|
try {
|
|
819
758
|
const cwd = process.cwd();
|
|
820
759
|
const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
821
|
-
const
|
|
822
|
-
const storePath =
|
|
760
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
761
|
+
const { storePath } = ctx;
|
|
823
762
|
const { analyzeTokenUsage } = await import("./eval/token-analysis.js");
|
|
824
763
|
const analysis = analyzeTokenUsage(storePath, sessionID);
|
|
825
764
|
if (analysis.queryCount === 0) {
|
|
@@ -877,8 +816,8 @@ program
|
|
|
877
816
|
try {
|
|
878
817
|
const cwd = process.cwd();
|
|
879
818
|
const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
880
|
-
const
|
|
881
|
-
const storePath =
|
|
819
|
+
const ctx = await resolveCliContext(options, logFilePath);
|
|
820
|
+
const { storePath } = ctx;
|
|
882
821
|
const { analyzeTokenUsage, compareTokenAnalyses, formatTokenReport } = await import("./eval/token-analysis.js");
|
|
883
822
|
const a = analyzeTokenUsage(storePath, sessionA);
|
|
884
823
|
const b = analyzeTokenUsage(storePath, sessionB);
|
|
@@ -940,6 +879,24 @@ function generateDefaultConfigJson() {
|
|
|
940
879
|
contentType: DEFAULT_CONFIG.openCode.autoInject.contentType,
|
|
941
880
|
},
|
|
942
881
|
},
|
|
882
|
+
imageDescription: {
|
|
883
|
+
enabled: DEFAULT_CONFIG.imageDescription.enabled,
|
|
884
|
+
provider: DEFAULT_CONFIG.imageDescription.provider,
|
|
885
|
+
model: DEFAULT_CONFIG.imageDescription.model,
|
|
886
|
+
baseUrl: DEFAULT_CONFIG.imageDescription.baseUrl,
|
|
887
|
+
timeoutMs: DEFAULT_CONFIG.imageDescription.timeoutMs,
|
|
888
|
+
think: DEFAULT_CONFIG.imageDescription.think,
|
|
889
|
+
numCtx: DEFAULT_CONFIG.imageDescription.numCtx,
|
|
890
|
+
},
|
|
891
|
+
description: {
|
|
892
|
+
enabled: DEFAULT_CONFIG.description.enabled,
|
|
893
|
+
provider: DEFAULT_CONFIG.description.provider,
|
|
894
|
+
baseUrl: DEFAULT_CONFIG.description.baseUrl,
|
|
895
|
+
model: DEFAULT_CONFIG.description.model,
|
|
896
|
+
think: DEFAULT_CONFIG.description.think,
|
|
897
|
+
numCtx: DEFAULT_CONFIG.description.numCtx,
|
|
898
|
+
timeoutMs: DEFAULT_CONFIG.description.timeoutMs,
|
|
899
|
+
},
|
|
943
900
|
mcp: {
|
|
944
901
|
enabled: DEFAULT_CONFIG.mcp.enabled,
|
|
945
902
|
},
|
|
@@ -957,6 +914,7 @@ program
|
|
|
957
914
|
.description("Configure the current workspace for OpenCodeRAG")
|
|
958
915
|
.option("-f, --force", "overwrite existing files")
|
|
959
916
|
.option("--skip-install", "skip installing workspace-local plugin dependencies")
|
|
917
|
+
.option("--skip-health-check", "skip provider connectivity and model availability check")
|
|
960
918
|
.action(async (options) => {
|
|
961
919
|
const cwd = process.cwd();
|
|
962
920
|
const packageMetadata = getPackageMetadata();
|
|
@@ -1086,6 +1044,63 @@ program
|
|
|
1086
1044
|
else {
|
|
1087
1045
|
console.log(` ${c.exists("Exists:")} opencode-rag.json`);
|
|
1088
1046
|
}
|
|
1047
|
+
// ── Provider health check ──────────────────────────────────
|
|
1048
|
+
if (!options.skipHealthCheck) {
|
|
1049
|
+
console.log(`\n${c.heading("Checking provider connectivity...")}\n`);
|
|
1050
|
+
const logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
1051
|
+
const ragConfig = loadConfig(configPath);
|
|
1052
|
+
const results = await checkProviderHealth(ragConfig);
|
|
1053
|
+
for (const r of results) {
|
|
1054
|
+
const icon = r.status === "ok" ? c.success("✓") : r.status === "missing" ? c.warn("○") : c.error("✗");
|
|
1055
|
+
const typeLabel = r.type === "image_description" ? "image description" : r.type;
|
|
1056
|
+
const label = `${typeLabel} model`;
|
|
1057
|
+
console.log(` ${icon} ${c.value(r.model)} (${r.provider}) — ${label}: ${r.status}`);
|
|
1058
|
+
if (r.error)
|
|
1059
|
+
console.log(` ${c.dim(r.error)}`);
|
|
1060
|
+
}
|
|
1061
|
+
const missingOllama = results.filter((r) => r.status === "missing" && r.provider === "ollama");
|
|
1062
|
+
if (missingOllama.length > 0) {
|
|
1063
|
+
const pullEntries = missingOllama.map((r) => {
|
|
1064
|
+
if (r.type === "embedding") {
|
|
1065
|
+
return { model: r.model, baseUrl: ragConfig.embedding.baseUrl, proxy: ragConfig.embedding.proxy };
|
|
1066
|
+
}
|
|
1067
|
+
if (r.type === "description" && ragConfig.description) {
|
|
1068
|
+
return { model: r.model, baseUrl: ragConfig.description.baseUrl, proxy: ragConfig.description.proxy };
|
|
1069
|
+
}
|
|
1070
|
+
if (r.type === "image_description" && ragConfig.imageDescription) {
|
|
1071
|
+
return { model: r.model, baseUrl: ragConfig.imageDescription.baseUrl, proxy: ragConfig.imageDescription.proxy };
|
|
1072
|
+
}
|
|
1073
|
+
return { model: r.model, baseUrl: ragConfig.embedding.baseUrl, proxy: ragConfig.embedding.proxy };
|
|
1074
|
+
});
|
|
1075
|
+
console.log(`\n ${c.warn("Models not found:")} ${pullEntries.map((e) => e.model).join(", ")}`);
|
|
1076
|
+
const readline = await import("node:readline");
|
|
1077
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1078
|
+
const answer = await new Promise((resolve) => {
|
|
1079
|
+
rl.question(` Pull ${pullEntries.length === 1 ? "this model" : "these models"} now? (y/n) `, resolve);
|
|
1080
|
+
});
|
|
1081
|
+
rl.close();
|
|
1082
|
+
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
|
1083
|
+
console.log();
|
|
1084
|
+
try {
|
|
1085
|
+
await pullOllamaModels(pullEntries, (model, line) => {
|
|
1086
|
+
console.log(` ${c.value(model)}: ${line}`);
|
|
1087
|
+
});
|
|
1088
|
+
console.log(`\n ${c.success("Models pulled successfully.")}`);
|
|
1089
|
+
}
|
|
1090
|
+
catch (err) {
|
|
1091
|
+
console.error(`\n ${c.error("Pull failed:")} ${err.message}`);
|
|
1092
|
+
console.log(` ${c.dim("Pull manually with: ollama pull <model>")}`);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
console.log(` ${c.dim("Skipped. Pull manually with: ollama pull <model>")}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const hasErrors = results.some((r) => r.status === "error");
|
|
1100
|
+
if (hasErrors) {
|
|
1101
|
+
console.log(`\n ${c.warn("Some providers are not reachable.")} Check configuration and network, then run ${c.file("'opencode-rag index'")}.`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1089
1104
|
if (!options.skipInstall) {
|
|
1090
1105
|
console.log(`\n${c.heading("Installing workspace-local plugin dependencies...")}\n`);
|
|
1091
1106
|
installWorkspaceDependencies(opencodeDir);
|