opencode-see-image 0.9.0 → 0.9.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/.claude/settings.local.json +12 -0
- package/README.md +4 -3
- package/index.ts +43 -29
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(bun build *)",
|
|
5
|
+
"Bash(npm view *)",
|
|
6
|
+
"Bash(FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --msg-filter 'sed \"/Co-Authored-By: Claude/d\" | sed -e :a -e \"/^\\\\n*$/{\\\\$d;N;ba\" -e \"}\"' HEAD~2..HEAD)",
|
|
7
|
+
"Bash(echo \"--- created, exit $? ---\")",
|
|
8
|
+
"Bash(node -p \"require\\('./package.json'\\).version\")",
|
|
9
|
+
"Bash(echo \"local package.json version: $\\(node -p \"require\\('./package.json'\\).version\" \\)\")"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
}
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ You need a connected vision-capable provider. The plugin auto-detects whichever
|
|
|
42
42
|
2. Select **opencode** (OpenCode Zen)
|
|
43
43
|
3. Paste your API key from [opencode.ai/auth](https://opencode.ai/auth)
|
|
44
44
|
|
|
45
|
-
The plugin falls back to **
|
|
45
|
+
The plugin falls back to **mimo-v2.5-free**. No subscription needed.
|
|
46
46
|
|
|
47
47
|
### Paid, w/ OpenCode Go
|
|
48
48
|
1. Run `/connect` in opencode
|
|
@@ -55,7 +55,7 @@ The plugin prefers **minimax-m3** via opencode-go (~3000ms) when available.
|
|
|
55
55
|
|
|
56
56
|
Set the `SEE_IMAGE_*` env vars to point at any Anthropic-Messages-compatible endpoint. See [Configuration](#configuration) below.
|
|
57
57
|
|
|
58
|
-
**Resolution order:** explicit `SEE_IMAGE_API_KEY` env → configured `SEE_IMAGE_PROVIDER` → `opencode-go` (MiniMax M3) → `opencode` (
|
|
58
|
+
**Resolution order:** explicit `SEE_IMAGE_API_KEY` env → configured `SEE_IMAGE_PROVIDER` → `opencode-go` (MiniMax M3) → `opencode` (mimo-v2.5-free, free).
|
|
59
59
|
|
|
60
60
|
## How it works
|
|
61
61
|
|
|
@@ -126,7 +126,8 @@ export SEE_IMAGE_MODEL="kimi-k2.7-code"
|
|
|
126
126
|
|
|
127
127
|
| Model | Speed | Notes |
|
|
128
128
|
|---|---|---|
|
|
129
|
-
| `
|
|
129
|
+
| `mimo-v2.5-free` | — | Free. Default fallback when only Zen is connected (routed via CLI). |
|
|
130
|
+
| `big-pickle` | ~12000ms | Free. Accurate. Alternative Zen fallback. |
|
|
130
131
|
|
|
131
132
|
**Paid (OpenCode Go):**
|
|
132
133
|
|
package/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ const ENDPOINT =
|
|
|
11
11
|
"https://opencode.ai/zen/go/v1/messages"
|
|
12
12
|
const MODEL = process.env.SEE_IMAGE_MODEL || "minimax-m3"
|
|
13
13
|
const PROVIDER_ID = process.env.SEE_IMAGE_PROVIDER || "opencode-go"
|
|
14
|
-
const TIMEOUT = parseInt(process.env.SEE_IMAGE_TIMEOUT || "
|
|
14
|
+
const TIMEOUT = parseInt(process.env.SEE_IMAGE_TIMEOUT || "30000", 10)
|
|
15
15
|
const API_VERSION = process.env.SEE_IMAGE_API_VERSION || "2023-06-01"
|
|
16
16
|
const USER_AGENT =
|
|
17
17
|
process.env.SEE_IMAGE_USER_AGENT ||
|
|
@@ -47,8 +47,9 @@ function resolveFromDb(
|
|
|
47
47
|
const dbPath = opencodeDbPath()
|
|
48
48
|
if (!fs.existsSync(dbPath)) return null
|
|
49
49
|
|
|
50
|
+
let db: Database | undefined
|
|
50
51
|
try {
|
|
51
|
-
|
|
52
|
+
db = new Database(dbPath, { readonly: true })
|
|
52
53
|
let rows: Array<{ data: string }>
|
|
53
54
|
|
|
54
55
|
if (!filename || filename === "clipboard") {
|
|
@@ -98,8 +99,6 @@ function resolveFromDb(
|
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
db.close()
|
|
102
|
-
|
|
103
102
|
if (!rows.length) return null
|
|
104
103
|
const part = JSON.parse(rows[0].data)
|
|
105
104
|
const url: string = part.url || ""
|
|
@@ -112,6 +111,8 @@ function resolveFromDb(
|
|
|
112
111
|
}
|
|
113
112
|
} catch {
|
|
114
113
|
return null
|
|
114
|
+
} finally {
|
|
115
|
+
db?.close()
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
@@ -225,21 +226,28 @@ async function seeImageViaSDK(
|
|
|
225
226
|
): Promise<{ text: string; model: string; provider: string }> {
|
|
226
227
|
const errors: string[] = []
|
|
227
228
|
|
|
228
|
-
// Write image to a temp file so the server can read it directly
|
|
229
|
+
// Write image to a temp file so the server can read it directly. Use the
|
|
230
|
+
// real extension so the CLI can sniff the type correctly.
|
|
229
231
|
const b64 = dataUrl.split(",")[1] || ""
|
|
230
|
-
const
|
|
232
|
+
const ext =
|
|
233
|
+
Object.entries(EXT_MEDIA).find(([, m]) => m === mediaType)?.[0] || "png"
|
|
234
|
+
const tmpPath = path.join(os.tmpdir(), `see-image-${Date.now()}.${ext}`)
|
|
231
235
|
try {
|
|
232
236
|
fs.writeFileSync(tmpPath, Buffer.from(b64, "base64"))
|
|
233
237
|
} catch {}
|
|
234
238
|
|
|
235
|
-
// For free opencode models, use CLI instead of SDK (SDK returns empty)
|
|
239
|
+
// For free opencode models, use CLI instead of SDK (SDK returns empty).
|
|
240
|
+
// Bun's $ doesn't accept an AbortSignal, so race the output against a
|
|
241
|
+
// timeout to actually bound how long a slow model can hang us.
|
|
236
242
|
const freeFallback = async (modelID: string, userPrompt: string): Promise<string | null> => {
|
|
237
243
|
try {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
244
|
+
const proc = $`opencode run -f ${tmpPath} -m opencode/${modelID} ${userPrompt} --format json --dangerously-skip-permissions`.nothrow()
|
|
245
|
+
const out = await Promise.race([
|
|
246
|
+
proc.text(),
|
|
247
|
+
new Promise<never>((_, reject) =>
|
|
248
|
+
setTimeout(() => reject(new Error(`timed out after ${TIMEOUT}ms`)), TIMEOUT),
|
|
249
|
+
),
|
|
250
|
+
])
|
|
243
251
|
for (const line of out.split("\n").filter(Boolean)) {
|
|
244
252
|
try {
|
|
245
253
|
const parsed = JSON.parse(line)
|
|
@@ -252,7 +260,6 @@ async function seeImageViaSDK(
|
|
|
252
260
|
return null
|
|
253
261
|
}
|
|
254
262
|
|
|
255
|
-
const fileUrl = tmpPath
|
|
256
263
|
let result: { text: string; model: string; provider: string } | undefined
|
|
257
264
|
|
|
258
265
|
try {
|
|
@@ -287,22 +294,29 @@ async function seeImageViaSDK(
|
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
const controller = new AbortController()
|
|
297
|
+
const onAbort = () => controller.abort()
|
|
298
|
+
abort?.addEventListener("abort", onAbort)
|
|
290
299
|
const timer = setTimeout(() => controller.abort(), TIMEOUT)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
let res
|
|
301
|
+
try {
|
|
302
|
+
res = await client.session.prompt({
|
|
303
|
+
path: { id: sessionID },
|
|
304
|
+
body: {
|
|
305
|
+
model: { providerID, modelID },
|
|
306
|
+
parts: [
|
|
307
|
+
{ type: "file", mime: mediaType, url: dataUrl },
|
|
308
|
+
{ type: "text", text: prompt },
|
|
309
|
+
],
|
|
310
|
+
tools: {},
|
|
311
|
+
system:
|
|
312
|
+
"You are a vision assistant. Describe the image accurately and concisely. Answer with text only.",
|
|
313
|
+
},
|
|
314
|
+
signal: controller.signal,
|
|
315
|
+
})
|
|
316
|
+
} finally {
|
|
317
|
+
clearTimeout(timer)
|
|
318
|
+
abort?.removeEventListener("abort", onAbort)
|
|
319
|
+
}
|
|
306
320
|
|
|
307
321
|
const parts = res.data?.parts ?? []
|
|
308
322
|
const text = (parts as any[])
|
|
@@ -344,7 +358,7 @@ async function seeImageViaSDK(
|
|
|
344
358
|
|
|
345
359
|
const errMsg = errors.join("; ")
|
|
346
360
|
const hint = errMsg.includes("usage limit")
|
|
347
|
-
? ` Enable usage from your balance at https://opencode.ai/workspace
|
|
361
|
+
? ` Enable usage from your balance in your opencode workspace at https://opencode.ai/workspace`
|
|
348
362
|
: ""
|
|
349
363
|
throw new Error(
|
|
350
364
|
`see_image: SDK vision call failed for all candidates. ${errMsg}.${hint}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-see-image",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Give non-vision opencode models the ability to see images/screenshots by routing them to a vision-capable model (MiniMax M3 via opencode-go by default).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|