opencode-rag-plugin 1.22.2 → 1.23.0
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 +2 -2
- package/dist/chunker/image.d.ts +14 -0
- package/dist/chunker/image.js +26 -0
- package/dist/cli/commands/describe-image.js +10 -6
- package/dist/cli/commands/init.js +9 -1
- package/dist/core/config.d.ts +36 -1
- package/dist/core/config.js +4 -0
- package/dist/core/resolve-api-key.js +5 -0
- package/dist/embedder/health.d.ts +1 -1
- package/dist/embedder/health.js +17 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/mcp/handlers.d.ts +1 -1
- package/dist/mcp/handlers.js +7 -6
- package/dist/opencode/tools.d.ts +3 -0
- package/dist/opencode/tools.js +11 -7
- package/package.json +1 -1
package/ReadMe.md
CHANGED
|
@@ -189,7 +189,7 @@ opencode-rag mcp
|
|
|
189
189
|
| `search_semantic` | Vector + keyword hybrid search across the indexed codebase |
|
|
190
190
|
| `get_file_skeleton` | AST-based file outline (functions, classes, methods) |
|
|
191
191
|
| `find_usages` | Find all references to a symbol by name |
|
|
192
|
-
| `describe_image` |
|
|
192
|
+
| `describe_image` | Describe an image file with the configured vision model (`imageDescription.onDemand` overrides apply) |
|
|
193
193
|
|
|
194
194
|
Clients can configure the MCP server manually, or `opencode-rag init` auto-registers it.
|
|
195
195
|
|
|
@@ -211,7 +211,7 @@ OpenCodeRAG registers tools that agents can invoke directly. Agents discover the
|
|
|
211
211
|
| `search_semantic` | General-purpose code retrieval | Before any code task when you haven't read the relevant code |
|
|
212
212
|
| `get_file_skeleton` | Quick file overview via AST | Before reading a large file to decide which sections matter |
|
|
213
213
|
| `find_usages` | Symbol reference search | **Before editing** any function, variable, or class |
|
|
214
|
-
| `describe_image` |
|
|
214
|
+
| `describe_image` | Live image description via the configured vision model | When a user asks about a screenshot, diagram, or visual asset |
|
|
215
215
|
| `read` (optional) | RAG-enhanced file read | Full file contents with supplementary context chunks |
|
|
216
216
|
|
|
217
217
|
## OpenCode Integration
|
package/dist/chunker/image.d.ts
CHANGED
|
@@ -20,6 +20,20 @@ export declare function getMimeType(ext: string): string;
|
|
|
20
20
|
export interface ImageVisionProvider {
|
|
21
21
|
describeImage(imageBase64: string, mimeType: string, prompt: string, systemPrompt?: string, abort?: AbortSignal): Promise<string>;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the effective image-description configuration for on-demand
|
|
25
|
+
* `describe_image` calls (OpenCode plugin tool, MCP server, CLI
|
|
26
|
+
* `describe-image`).
|
|
27
|
+
*
|
|
28
|
+
* Applies the optional `imageDescription.onDemand` overrides on top of the
|
|
29
|
+
* indexing settings. The indexing pipeline always uses the base config and
|
|
30
|
+
* ignores `onDemand`. `null`/`undefined` override values are ignored so a
|
|
31
|
+
* partially written config cannot blank out required fields.
|
|
32
|
+
*
|
|
33
|
+
* @param config - The base image description configuration.
|
|
34
|
+
* @returns A copy with on-demand overrides applied and `onDemand` stripped.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveOnDemandImageConfig(config: ImageDescriptionConfig): ImageDescriptionConfig;
|
|
23
37
|
/**
|
|
24
38
|
* Factory function that creates the appropriate {@link ImageVisionProvider}
|
|
25
39
|
* implementation based on the `provider` field in the config.
|
package/dist/chunker/image.js
CHANGED
|
@@ -306,6 +306,32 @@ class GeminiImageVisionProvider {
|
|
|
306
306
|
throw lastError;
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Resolve the effective image-description configuration for on-demand
|
|
311
|
+
* `describe_image` calls (OpenCode plugin tool, MCP server, CLI
|
|
312
|
+
* `describe-image`).
|
|
313
|
+
*
|
|
314
|
+
* Applies the optional `imageDescription.onDemand` overrides on top of the
|
|
315
|
+
* indexing settings. The indexing pipeline always uses the base config and
|
|
316
|
+
* ignores `onDemand`. `null`/`undefined` override values are ignored so a
|
|
317
|
+
* partially written config cannot blank out required fields.
|
|
318
|
+
*
|
|
319
|
+
* @param config - The base image description configuration.
|
|
320
|
+
* @returns A copy with on-demand overrides applied and `onDemand` stripped.
|
|
321
|
+
*/
|
|
322
|
+
export function resolveOnDemandImageConfig(config) {
|
|
323
|
+
const override = config.onDemand;
|
|
324
|
+
const resolved = { ...config };
|
|
325
|
+
delete resolved.onDemand;
|
|
326
|
+
if (!override)
|
|
327
|
+
return resolved;
|
|
328
|
+
for (const [key, value] of Object.entries(override)) {
|
|
329
|
+
if (value === undefined || value === null)
|
|
330
|
+
continue;
|
|
331
|
+
resolved[key] = value;
|
|
332
|
+
}
|
|
333
|
+
return resolved;
|
|
334
|
+
}
|
|
309
335
|
/**
|
|
310
336
|
* Factory function that creates the appropriate {@link ImageVisionProvider}
|
|
311
337
|
* implementation based on the `provider` field in the config.
|
|
@@ -36,7 +36,7 @@ export function registerDescribeImageCommand(program) {
|
|
|
36
36
|
process.exit(1);
|
|
37
37
|
}
|
|
38
38
|
const ext = path.extname(resolvedPath).toLowerCase();
|
|
39
|
-
const { SUPPORTED_IMAGE_EXTENSIONS, createImageVisionProvider, getMimeType } = await import("../../chunker/image.js");
|
|
39
|
+
const { SUPPORTED_IMAGE_EXTENSIONS, createImageVisionProvider, resolveOnDemandImageConfig, getMimeType } = await import("../../chunker/image.js");
|
|
40
40
|
if (!SUPPORTED_IMAGE_EXTENSIONS.has(ext)) {
|
|
41
41
|
const exts = [...SUPPORTED_IMAGE_EXTENSIONS].join(", ");
|
|
42
42
|
logCliError(logFilePath, "describe-image", `\nUnsupported file extension "${ext}". Supported: ${exts}`, undefined);
|
|
@@ -48,16 +48,20 @@ export function registerDescribeImageCommand(program) {
|
|
|
48
48
|
process.exit(1);
|
|
49
49
|
}
|
|
50
50
|
const { resizeImage } = await import("../../content/image.js");
|
|
51
|
+
const effectiveImageConfig = resolveOnDemandImageConfig(imageDescriptionConfig);
|
|
51
52
|
logCliInfo(logFilePath, "describe-image", `\n${c.heading("Describing image:")} ${c.file(filePath)}`);
|
|
52
|
-
logCliInfo(logFilePath, "describe-image", ` ${c.label("Provider:")} ${c.value(
|
|
53
|
-
logCliInfo(logFilePath, "describe-image", ` ${c.label("Model:")} ${c.value(
|
|
53
|
+
logCliInfo(logFilePath, "describe-image", ` ${c.label("Provider:")} ${c.value(effectiveImageConfig.provider)}`);
|
|
54
|
+
logCliInfo(logFilePath, "describe-image", ` ${c.label("Model:")} ${c.value(effectiveImageConfig.model)}`);
|
|
55
|
+
if (imageDescriptionConfig.onDemand) {
|
|
56
|
+
logCliInfo(logFilePath, "describe-image", ` ${c.label("Source:")} imageDescription.onDemand override`);
|
|
57
|
+
}
|
|
54
58
|
const buffer = readFileSync(resolvedPath);
|
|
55
59
|
const mimeType = getMimeType(ext);
|
|
56
|
-
const maxDimension =
|
|
60
|
+
const maxDimension = effectiveImageConfig.resizeMaxDimension ?? 1024;
|
|
57
61
|
const sized = maxDimension > 0 ? await resizeImage(buffer, resolvedPath, maxDimension) : buffer;
|
|
58
62
|
const b64 = sized.toString("base64");
|
|
59
|
-
const provider = createImageVisionProvider(
|
|
60
|
-
const description = await provider.describeImage(b64, mimeType,
|
|
63
|
+
const provider = createImageVisionProvider(effectiveImageConfig);
|
|
64
|
+
const description = await provider.describeImage(b64, mimeType, effectiveImageConfig.prompt, options.systemPrompt);
|
|
61
65
|
logCliInfo(logFilePath, "describe-image", `\n${c.desc(description)}\n`);
|
|
62
66
|
await cleanupContext(ctx);
|
|
63
67
|
}
|
|
@@ -261,7 +261,7 @@ export function registerInitCommand(program) {
|
|
|
261
261
|
const results = await healthPromise;
|
|
262
262
|
for (const r of results) {
|
|
263
263
|
const icon = r.status === "ok" ? c.success("✓") : r.status === "missing" ? c.warn("○") : c.error("✗");
|
|
264
|
-
const typeLabel = r.type === "image_description" ? "image description" : r.type;
|
|
264
|
+
const typeLabel = r.type === "image_description" ? "image description" : r.type === "image_description_on_demand" ? "image description (on-demand)" : r.type;
|
|
265
265
|
const label = `${typeLabel} model`;
|
|
266
266
|
console.log(` ${icon} ${c.value(r.model)} (${r.provider}) — ${label}: ${r.status}`);
|
|
267
267
|
if (r.error)
|
|
@@ -280,6 +280,14 @@ export function registerInitCommand(program) {
|
|
|
280
280
|
if (r.type === "image_description" && ragConfig.imageDescription) {
|
|
281
281
|
return { model: r.model, baseUrl: ragConfig.imageDescription.baseUrl, proxy: ragConfig.imageDescription.proxy };
|
|
282
282
|
}
|
|
283
|
+
if (r.type === "image_description_on_demand" && ragConfig.imageDescription) {
|
|
284
|
+
const onDemand = ragConfig.imageDescription.onDemand;
|
|
285
|
+
return {
|
|
286
|
+
model: r.model,
|
|
287
|
+
baseUrl: onDemand?.baseUrl ?? ragConfig.imageDescription.baseUrl,
|
|
288
|
+
proxy: onDemand?.proxy ?? ragConfig.imageDescription.proxy,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
283
291
|
return { model: r.model, baseUrl: ragConfig.embedding.baseUrl, proxy: ragConfig.embedding.proxy };
|
|
284
292
|
});
|
|
285
293
|
console.log(`\n ${c.warn("Models not found:")} ${pullEntries.map((e) => e.model).join(", ")}`);
|
package/dist/core/config.d.ts
CHANGED
|
@@ -85,6 +85,36 @@ export interface DescriptionConfig {
|
|
|
85
85
|
maxContentChars?: number;
|
|
86
86
|
}
|
|
87
87
|
/** Configuration for vision-model-based image description generation. */
|
|
88
|
+
/**
|
|
89
|
+
* Optional overrides for on-demand `describe_image` calls (OpenCode plugin
|
|
90
|
+
* tool, MCP server, CLI `describe-image`). Fields omitted here fall back to
|
|
91
|
+
* the indexing settings in {@link ImageDescriptionConfig}; the indexing
|
|
92
|
+
* pipeline itself always uses the base settings.
|
|
93
|
+
*/
|
|
94
|
+
export interface ImageDescriptionOnDemandConfig {
|
|
95
|
+
/** Vision provider name ("ollama", "openai", "anthropic", "google"). */
|
|
96
|
+
provider?: string;
|
|
97
|
+
/** Vision model name. */
|
|
98
|
+
model?: string;
|
|
99
|
+
/** Base URL of the vision API. */
|
|
100
|
+
baseUrl?: string;
|
|
101
|
+
/** API key for providers that require authentication. */
|
|
102
|
+
apiKey?: string;
|
|
103
|
+
/** Request timeout in milliseconds. */
|
|
104
|
+
timeoutMs?: number;
|
|
105
|
+
/** Prompt template sent to the vision model. */
|
|
106
|
+
prompt?: string;
|
|
107
|
+
/** Whether to include chain-of-thought tokens. */
|
|
108
|
+
think?: boolean;
|
|
109
|
+
/** Context window size. */
|
|
110
|
+
numCtx?: number;
|
|
111
|
+
/** Ollama keep_alive value (e.g. "-1" for keep-in-memory) sent with /api/chat requests. */
|
|
112
|
+
keepAlive?: string;
|
|
113
|
+
/** Proxy configuration. */
|
|
114
|
+
proxy?: ProxyConfig;
|
|
115
|
+
/** Maximum image dimension (pixels) — larger images are resized before sending. */
|
|
116
|
+
resizeMaxDimension?: number;
|
|
117
|
+
}
|
|
88
118
|
export interface ImageDescriptionConfig {
|
|
89
119
|
/** Whether image description is enabled. */
|
|
90
120
|
enabled: boolean;
|
|
@@ -110,8 +140,13 @@ export interface ImageDescriptionConfig {
|
|
|
110
140
|
proxy?: ProxyConfig;
|
|
111
141
|
/** Maximum image dimension (pixels) — larger images are resized before sending. */
|
|
112
142
|
resizeMaxDimension?: number;
|
|
143
|
+
/**
|
|
144
|
+
* Optional overrides for on-demand `describe_image` calls (OpenCode plugin
|
|
145
|
+
* tool, MCP server, CLI `describe-image`). Omitted fields fall back to the
|
|
146
|
+
* indexing settings above. The indexing pipeline ignores this section.
|
|
147
|
+
*/
|
|
148
|
+
onDemand?: ImageDescriptionOnDemandConfig;
|
|
113
149
|
}
|
|
114
|
-
/** Configuration for the built-in web dashboard UI. */
|
|
115
150
|
export interface UiConfig {
|
|
116
151
|
/** HTTP port for the UI server. */
|
|
117
152
|
port: number;
|
package/dist/core/config.js
CHANGED
|
@@ -444,6 +444,10 @@ export function validateConfig(config) {
|
|
|
444
444
|
warnings.push("imageDescription.timeoutMs must be > 0");
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
|
+
const onDemand = config.imageDescription.onDemand;
|
|
448
|
+
if (onDemand?.timeoutMs !== undefined && onDemand.timeoutMs <= 0) {
|
|
449
|
+
warnings.push("imageDescription.onDemand.timeoutMs must be > 0");
|
|
450
|
+
}
|
|
447
451
|
}
|
|
448
452
|
return { valid: warnings.length === 0, warnings };
|
|
449
453
|
}
|
|
@@ -14,6 +14,11 @@ export function resolveApiKey(cfg, worktree) {
|
|
|
14
14
|
if (cfg.imageDescription?.enabled && cfg.imageDescription.provider !== "ollama") {
|
|
15
15
|
resolveForSection(cfg.imageDescription.provider, cfg.imageDescription, worktree);
|
|
16
16
|
}
|
|
17
|
+
const imageOnDemand = cfg.imageDescription?.onDemand;
|
|
18
|
+
if (imageOnDemand) {
|
|
19
|
+
const provider = imageOnDemand.provider ?? cfg.imageDescription?.provider ?? "ollama";
|
|
20
|
+
resolveForSection(provider, imageOnDemand, worktree);
|
|
21
|
+
}
|
|
17
22
|
}
|
|
18
23
|
function isPlaceholder(value) {
|
|
19
24
|
return value === "public" || value === "" || value === "PLACEHOLDER";
|
|
@@ -10,7 +10,7 @@ export interface HealthCheckResult {
|
|
|
10
10
|
/** Model identifier that was tested */
|
|
11
11
|
model: string;
|
|
12
12
|
/** Which capability was checked */
|
|
13
|
-
type: "embedding" | "description" | "image_description";
|
|
13
|
+
type: "embedding" | "description" | "image_description" | "image_description_on_demand";
|
|
14
14
|
/** Whether the provider is reachable and the model is available */
|
|
15
15
|
status: "ok" | "missing" | "error";
|
|
16
16
|
/** Human-readable error message when status is not "ok" */
|
package/dist/embedder/health.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveOnDemandImageConfig } from "../chunker/image.js";
|
|
1
2
|
import { fetchWithProxy, postJson } from "./http.js";
|
|
2
3
|
/**
|
|
3
4
|
* Check connectivity and model availability for all configured providers.
|
|
@@ -13,6 +14,10 @@ export async function checkProviderHealth(config) {
|
|
|
13
14
|
}
|
|
14
15
|
if (config.imageDescription?.enabled) {
|
|
15
16
|
checks.push(checkImageDescriptionModel(config, timeoutMs));
|
|
17
|
+
const onDemand = resolveOnDemandImageConfig(config.imageDescription);
|
|
18
|
+
if (onDemand.provider !== config.imageDescription.provider || onDemand.model !== config.imageDescription.model) {
|
|
19
|
+
checks.push(checkOnDemandImageDescriptionModel(onDemand));
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
22
|
return Promise.all(checks);
|
|
18
23
|
}
|
|
@@ -56,19 +61,27 @@ async function checkImageDescriptionModel(config, _timeoutMs) {
|
|
|
56
61
|
if (!img) {
|
|
57
62
|
return { provider: "unknown", model: "unknown", type: "image_description", status: "error", error: "Image description config is undefined" };
|
|
58
63
|
}
|
|
64
|
+
return checkVisionModel(img, "image_description");
|
|
65
|
+
}
|
|
66
|
+
/** Check the optional `imageDescription.onDemand` vision model used by the describe_image tool, MCP server, and CLI. */
|
|
67
|
+
async function checkOnDemandImageDescriptionModel(img) {
|
|
68
|
+
return checkVisionModel(img, "image_description_on_demand");
|
|
69
|
+
}
|
|
70
|
+
/** Dispatch a vision-model check (indexing or on-demand) to the correct provider-specific handler. */
|
|
71
|
+
async function checkVisionModel(img, type) {
|
|
59
72
|
const { provider, baseUrl, model, apiKey } = img;
|
|
60
73
|
const imgTimeout = img.timeoutMs ?? 60000;
|
|
61
74
|
if (provider === "ollama") {
|
|
62
|
-
return checkOllamaChat(baseUrl, model, imgTimeout, img.proxy,
|
|
75
|
+
return checkOllamaChat(baseUrl, model, imgTimeout, img.proxy, type);
|
|
63
76
|
}
|
|
64
77
|
if (provider === "anthropic") {
|
|
65
|
-
return checkAnthropicChat(baseUrl, model, apiKey, imgTimeout, img.proxy,
|
|
78
|
+
return checkAnthropicChat(baseUrl, model, apiKey, imgTimeout, img.proxy, type);
|
|
66
79
|
}
|
|
67
80
|
if (provider === "google") {
|
|
68
|
-
return checkGoogleChat(baseUrl, model, apiKey, imgTimeout, img.proxy,
|
|
81
|
+
return checkGoogleChat(baseUrl, model, apiKey, imgTimeout, img.proxy, type);
|
|
69
82
|
}
|
|
70
83
|
// OpenAI-compatible chat endpoint
|
|
71
|
-
return checkOpenAiChat(baseUrl, model, apiKey, imgTimeout, img.proxy,
|
|
84
|
+
return checkOpenAiChat(baseUrl, model, apiKey, imgTimeout, img.proxy, type);
|
|
72
85
|
}
|
|
73
86
|
/** Check whether a provider name matches a known OpenAI-compatible provider. */
|
|
74
87
|
function isOpenAiCompatible(provider) {
|
package/dist/index.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ export { optimizeContext, DEFAULT_CONTEXT_OPTIMIZATION } from "./retriever/conte
|
|
|
14
14
|
export { loadConfig, DEFAULT_CONFIG } from "./core/config.js";
|
|
15
15
|
export { createBackgroundIndexer } from "./watcher.js";
|
|
16
16
|
export { createWatchIgnore } from "./indexer.js";
|
|
17
|
-
export { ImageChunker, createImageVisionProvider, getMimeType, SUPPORTED_IMAGE_EXTENSIONS } from "./chunker/image.js";
|
|
17
|
+
export { ImageChunker, createImageVisionProvider, resolveOnDemandImageConfig, getMimeType, SUPPORTED_IMAGE_EXTENSIONS } from "./chunker/image.js";
|
|
18
18
|
export { DescriptionCache } from "./core/desc-cache.js";
|
|
19
|
-
export type { RagConfig, DescriptionConfig, ImageDescriptionConfig } from "./core/config.js";
|
|
19
|
+
export type { RagConfig, DescriptionConfig, ImageDescriptionConfig, ImageDescriptionOnDemandConfig } from "./core/config.js";
|
|
20
20
|
export type { Chunk, SearchResult, OptimizedSearchResult, Chunker, DescriptionProvider, EmbeddingProvider, VectorStore } from "./core/interfaces.js";
|
|
21
21
|
export type { ContextOptimizationConfig, ContextOptimizationOptions } from "./retriever/context-optimizer.js";
|
|
22
22
|
/**
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export { optimizeContext, DEFAULT_CONTEXT_OPTIMIZATION } from "./retriever/conte
|
|
|
14
14
|
export { loadConfig, DEFAULT_CONFIG } from "./core/config.js";
|
|
15
15
|
export { createBackgroundIndexer } from "./watcher.js";
|
|
16
16
|
export { createWatchIgnore } from "./indexer.js";
|
|
17
|
-
export { ImageChunker, createImageVisionProvider, getMimeType, SUPPORTED_IMAGE_EXTENSIONS } from "./chunker/image.js";
|
|
17
|
+
export { ImageChunker, createImageVisionProvider, resolveOnDemandImageConfig, getMimeType, SUPPORTED_IMAGE_EXTENSIONS } from "./chunker/image.js";
|
|
18
18
|
export { DescriptionCache } from "./core/desc-cache.js";
|
|
19
19
|
/**
|
|
20
20
|
* High-level convenience API — search, index, and retrieve context in a single function call.
|
package/dist/mcp/handlers.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export interface DescribeImageResult {
|
|
|
97
97
|
/** Human-readable formatted output with metadata. */
|
|
98
98
|
formatted: string;
|
|
99
99
|
}
|
|
100
|
-
/** Describe an image file using the configured vision provider (Ollama, OpenAI, Anthropic, or Gemini). */
|
|
100
|
+
/** Describe an image file using the configured vision provider (Ollama, OpenAI, Anthropic, or Gemini). Honors `imageDescription.onDemand` overrides. */
|
|
101
101
|
export declare function handleDescribeImage(params: DescribeImageParams, cfg: RagConfig, worktree: string, visionProvider?: ImageVisionProvider): Promise<DescribeImageResult>;
|
|
102
102
|
/** Find usages and references of a symbol across the indexed codebase using hybrid (keyword + vector) search. */
|
|
103
103
|
export declare function handleFindUsages(params: FindUsagesParams, embedder: EmbeddingProvider, store: VectorStore, cfg: RagConfig, keywordIndex?: KeywordIndex, retrieveFn?: typeof retrieve): Promise<FindUsagesResult>;
|
package/dist/mcp/handlers.js
CHANGED
|
@@ -227,7 +227,7 @@ export async function handleFileSkeleton(params, worktree) {
|
|
|
227
227
|
const formatted = `${skeleton.length} structural elements (${summary || "—"})\n\n${formatSkeleton(skeleton)}`;
|
|
228
228
|
return { elements: skeleton, formatted, summary };
|
|
229
229
|
}
|
|
230
|
-
/** Describe an image file using the configured vision provider (Ollama, OpenAI, Anthropic, or Gemini). */
|
|
230
|
+
/** Describe an image file using the configured vision provider (Ollama, OpenAI, Anthropic, or Gemini). Honors `imageDescription.onDemand` overrides. */
|
|
231
231
|
export async function handleDescribeImage(params, cfg, worktree, visionProvider) {
|
|
232
232
|
const { existsSync, readFileSync } = await import("node:fs");
|
|
233
233
|
const path = await import("node:path");
|
|
@@ -244,21 +244,22 @@ export async function handleDescribeImage(params, cfg, worktree, visionProvider)
|
|
|
244
244
|
if (!imageDescriptionConfig?.enabled) {
|
|
245
245
|
throw new Error("Image description is not enabled in config (imageDescription.enabled)");
|
|
246
246
|
}
|
|
247
|
-
const { getMimeType } = await import("../chunker/image.js");
|
|
247
|
+
const { getMimeType, createImageVisionProvider, resolveOnDemandImageConfig } = await import("../chunker/image.js");
|
|
248
248
|
const { resizeImage } = await import("../content/image.js");
|
|
249
|
+
const effectiveImageConfig = resolveOnDemandImageConfig(imageDescriptionConfig);
|
|
249
250
|
const buffer = readFileSync(resolvedPath);
|
|
250
251
|
const mimeType = getMimeType(ext);
|
|
251
|
-
const maxDimension =
|
|
252
|
+
const maxDimension = effectiveImageConfig.resizeMaxDimension ?? 1024;
|
|
252
253
|
const sized = maxDimension > 0 ? await resizeImage(buffer, resolvedPath, maxDimension) : buffer;
|
|
253
254
|
const b64 = sized.toString("base64");
|
|
254
|
-
const provider = visionProvider ??
|
|
255
|
-
const description = await provider.describeImage(b64, mimeType,
|
|
255
|
+
const provider = visionProvider ?? createImageVisionProvider(effectiveImageConfig);
|
|
256
|
+
const description = await provider.describeImage(b64, mimeType, effectiveImageConfig.prompt, params.systemPrompt);
|
|
256
257
|
const formatted = [
|
|
257
258
|
`**Image description** — ${params.filePath}`,
|
|
258
259
|
"",
|
|
259
260
|
description,
|
|
260
261
|
"",
|
|
261
|
-
`_Generated with ${
|
|
262
|
+
`_Generated with ${effectiveImageConfig.provider}/${effectiveImageConfig.model}_`,
|
|
262
263
|
].join("\n");
|
|
263
264
|
return { description, formatted };
|
|
264
265
|
}
|
package/dist/opencode/tools.d.ts
CHANGED
|
@@ -51,6 +51,9 @@ export interface DescribeImageToolOptions {
|
|
|
51
51
|
* for natural-language description. Supports Ollama, OpenAI, Anthropic, and
|
|
52
52
|
* Google Gemini providers with automatic resizing.
|
|
53
53
|
*
|
|
54
|
+
* On-demand calls honor the optional `imageDescription.onDemand` overrides
|
|
55
|
+
* (a different provider/model than the indexing pipeline).
|
|
56
|
+
*
|
|
54
57
|
* @param options - Tool configuration including workspace root and vision provider.
|
|
55
58
|
* @returns A tool definition suitable for OpenCode plugin registration.
|
|
56
59
|
*/
|
package/dist/opencode/tools.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
16
16
|
import { CODE_SEARCH_FILTER } from "../core/interfaces.js";
|
|
17
|
-
import { SUPPORTED_IMAGE_EXTENSIONS, createImageVisionProvider, getMimeType } from "../chunker/image.js";
|
|
17
|
+
import { SUPPORTED_IMAGE_EXTENSIONS, createImageVisionProvider, resolveOnDemandImageConfig, getMimeType } from "../chunker/image.js";
|
|
18
18
|
import { resizeImage } from "../content/image.js";
|
|
19
19
|
import { retrieve } from "../retriever/retriever.js";
|
|
20
20
|
import { Parser } from "web-tree-sitter";
|
|
@@ -247,6 +247,9 @@ export function createFileSkeletonTool(options) {
|
|
|
247
247
|
* for natural-language description. Supports Ollama, OpenAI, Anthropic, and
|
|
248
248
|
* Google Gemini providers with automatic resizing.
|
|
249
249
|
*
|
|
250
|
+
* On-demand calls honor the optional `imageDescription.onDemand` overrides
|
|
251
|
+
* (a different provider/model than the indexing pipeline).
|
|
252
|
+
*
|
|
250
253
|
* @param options - Tool configuration including workspace root and vision provider.
|
|
251
254
|
* @returns A tool definition suitable for OpenCode plugin registration.
|
|
252
255
|
*/
|
|
@@ -292,22 +295,23 @@ export function createDescribeImageTool(options) {
|
|
|
292
295
|
metadata: { tool: "describe_image", filePath: args.filePath, error: "disabled" },
|
|
293
296
|
};
|
|
294
297
|
}
|
|
298
|
+
const effectiveImageConfig = resolveOnDemandImageConfig(imageDescriptionConfig);
|
|
295
299
|
const buffer = readFileSync(resolvedPath);
|
|
296
300
|
const mimeType = getMimeType(ext);
|
|
297
|
-
const maxDimension =
|
|
301
|
+
const maxDimension = effectiveImageConfig.resizeMaxDimension ?? 1024;
|
|
298
302
|
const sized = maxDimension > 0 ? await resizeImage(buffer, resolvedPath, maxDimension) : buffer;
|
|
299
303
|
const b64 = sized.toString("base64");
|
|
300
|
-
const provider = visionProvider ?? createImageVisionProvider(
|
|
301
|
-
const description = await provider.describeImage(b64, mimeType,
|
|
304
|
+
const provider = visionProvider ?? createImageVisionProvider(effectiveImageConfig);
|
|
305
|
+
const description = await provider.describeImage(b64, mimeType, effectiveImageConfig.prompt, args.systemPrompt);
|
|
302
306
|
return {
|
|
303
307
|
title: `Image description — ${args.filePath}`,
|
|
304
|
-
output: `**${args.filePath}**\n\n${description}\n\n_Generated with ${
|
|
308
|
+
output: `**${args.filePath}**\n\n${description}\n\n_Generated with ${effectiveImageConfig.provider}/${effectiveImageConfig.model}_`,
|
|
305
309
|
metadata: {
|
|
306
310
|
tool: "describe_image",
|
|
307
311
|
filePath: args.filePath,
|
|
308
312
|
description,
|
|
309
|
-
provider:
|
|
310
|
-
model:
|
|
313
|
+
provider: effectiveImageConfig.provider,
|
|
314
|
+
model: effectiveImageConfig.model,
|
|
311
315
|
},
|
|
312
316
|
};
|
|
313
317
|
}
|