thincoder 0.12.22 → 0.12.24
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 +3 -3
- package/package.json +2 -2
- package/src/agent.mjs +2 -2
- package/src/config.mjs +6 -9
- package/src/provider/core.mjs +31 -13
- package/src/tools/file.mjs +22 -5
- package/src/tools/read_image.md +4 -3
package/README.md
CHANGED
|
@@ -15,11 +15,11 @@ Design philosophy (the entire meaning of the name): if the Node standard library
|
|
|
15
15
|
- **Fix-verify loop**: file changes without `verify` get pushed back — syntax check + tests must pass before the agent can claim completion (auto-repair up to 3 rounds)
|
|
16
16
|
- **Checkpoint system**: auto-snapshot before every user task, `list`/`create`/`rewind` tools for the model, single-file restore — rewinding itself is reversible (pre-rewind state auto-saved)
|
|
17
17
|
- **Codebase understanding** ⭐0.5.0: `repo_outline` (dependency outline, auto-injected at startup), `code_search` (source FTS5 + vectors + JSDoc extraction), `doc_search` (docs chunked by ## headings) — background indexing, auto-incremental updates on file writes, three tools guided by "structure → intent → details"
|
|
18
|
-
- **Model adaptation** ⭐: top-tier only, latest only. Built-in flagship models from
|
|
18
|
+
- **Model adaptation** ⭐: top-tier only, latest only. Built-in flagship models from seventeen providers — DeepSeek / Kimi / Kimi For Coding / GLM / Qwen / Qwen Token Plan / MiniMax / OpenAI / Claude / Gemini / Grok / Mistral / Volcengine Ark (豆包) / Hunyuan (腾讯混元) / SiliconFlow (硅基流动) / OpenRouter / Groq. No legacy model compatibility, no local model support. Auto-matched context windows, truncation-resume protocols (prefix/partial), thinking-mode APIs (thinking.type / reasoning_effort), reasoning_content echo strategies (reasoningEcho), output limits, temperature range clamping — all deeply adapted.
|
|
19
19
|
- **Toolset**: `read` / `write` / `edit` / `bash` / `glob` (supports `**`) / `grep` / `websearch` / `ls` / `fetch` + `read_image` (image/video paste) + three retrieval tools + MCP — all zero-dependency, file tools confined to the working directory
|
|
20
20
|
- **Memory system**: three layers (personal/project/team), FTS5 + vector RRF hybrid retrieval, git-friendly markdown format
|
|
21
21
|
- **Two-phase tool scheduling**: permission prompts serialized, read-only tools parallelized, side-effect tools serialized
|
|
22
|
-
- **Session persistence** ⭐0.5.0:
|
|
22
|
+
- **Session persistence** ⭐0.5.0: unlimited archive slots, `/session` to switch anytime, tool results visible after restore. Process-level isolation — multiple instances in the same directory each get their own session slot
|
|
23
23
|
- **Concurrent subagents**: three roles — `explore`/`plan`/`coder` — dispatched in parallel, streaming output visible, reports land in the conversation; per-subagent model override (`subagent` tool `model` arg or `agent.subagentModel` config — e.g. discuss with `glm-5.2`, let `deepseek-v4-flash` implement)
|
|
24
24
|
- **Plan Mode**: read-only exploration + design, implement after user approval
|
|
25
25
|
- **AUTO mode**: `/auto` full authorization, no confirmations on long tasks
|
|
@@ -183,7 +183,7 @@ src/
|
|
|
183
183
|
memory/ three-layer memory — schema.mjs (DDL/constants), core.mjs (CRUD + retrieval),
|
|
184
184
|
code-index.mjs + code-sync.mjs (code_chunks), docs.mjs (doc_chunks)
|
|
185
185
|
memory.mjs re-export shim → src/memory/*
|
|
186
|
-
session.mjs session persistence (
|
|
186
|
+
session.mjs session persistence (unlimited archive slots, isolated by project cwd, process-level isolation via sessionId + slotSessions)
|
|
187
187
|
skills.mjs skill discovery/loading (.thincoder/skills/*.md)
|
|
188
188
|
markdown.mjs entry format (frontmatter parse/serialize)
|
|
189
189
|
git/ checkpoint.mjs (git patch snapshots / rewind), gitmem.mjs (Team layer git sync)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thincoder",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.24",
|
|
4
4
|
"description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
|
30
|
-
"url": "https://
|
|
30
|
+
"url": "https://github.com/xinbo-tech/thincoder.git"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "node --test \"test/*.mjs\"",
|
package/src/agent.mjs
CHANGED
|
@@ -346,7 +346,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
346
346
|
const parsed = JSON.parse(result)
|
|
347
347
|
if (parsed.images?.length) {
|
|
348
348
|
// tool message first — closes the tool_call pairing (OpenAI API requires tool result immediately after assistant with tool_calls)
|
|
349
|
-
pushReal(agent, { role: "tool", tool_call_id: toolCall.id, content: parsed.text })
|
|
349
|
+
pushReal(agent, { role: "tool", tool_call_id: toolCall.id, name: toolCall.name, content: parsed.text })
|
|
350
350
|
if (specForModel(agent.provider.model).multimodal) {
|
|
351
351
|
// then inject multimodal user message with base64 images for the model to actually "see" them on the next turn
|
|
352
352
|
deferredUserMsgs.push({
|
|
@@ -371,7 +371,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
371
371
|
}
|
|
372
372
|
} catch { /* Parse failure doesn't affect normal tool messages */ }
|
|
373
373
|
}
|
|
374
|
-
pushReal(agent, { role: "tool", tool_call_id: toolCall.id, content: result })
|
|
374
|
+
pushReal(agent, { role: "tool", tool_call_id: toolCall.id, name: toolCall.name, content: result })
|
|
375
375
|
if (tool && ok) {
|
|
376
376
|
if (FILE_MUTATORS.has(toolCall.name)) {
|
|
377
377
|
// Direct file edit — code was changed. The prior advisor review and
|
package/src/config.mjs
CHANGED
|
@@ -18,6 +18,7 @@ export const PROVIDER_PRESETS = {
|
|
|
18
18
|
kimi: { baseURL: "https://api.moonshot.cn/v1", model: "kimi-k3", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi / Moonshot" },
|
|
19
19
|
"kimi-code": { baseURL: "https://api.kimi.com/coding/v1", model: "k3", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi For Coding (platform.kimi.com — sk-kimi- keys; NOT interchangeable with Moonshot)" },
|
|
20
20
|
glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM" },
|
|
21
|
+
"glm-code": { baseURL: "https://open.bigmodel.cn/api/coding/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM Coding Plan (coding endpoint — same key as GLM; server-forced thinking)" },
|
|
21
22
|
qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen / Alibaba" },
|
|
22
23
|
qwenplan: { baseURL: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen Token Plan (百炼套餐)" },
|
|
23
24
|
minimax: { baseURL: "https://api.minimaxi.com/v1", model: "MiniMax-M3", thinking: { type: "adaptive" }, maxTokens: 128000, chatPath: "/text/chatcompletion_v2", desc: "MiniMax" },
|
|
@@ -91,18 +92,14 @@ const DEFAULTS = {
|
|
|
91
92
|
*/
|
|
92
93
|
const MODEL_SPECS = [
|
|
93
94
|
// DeepSeek V4 series
|
|
94
|
-
["deepseek-v4-pro", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "
|
|
95
|
-
["deepseek-v4-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "
|
|
96
|
-
["deepseek-reasoner", { context: 256_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "prompt", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["high", "max"], tempRange: [0, 2] }],
|
|
97
|
-
["deepseek-chat", { context: 256_000, maxOutput: 384_000, thinking: false, prefixMode: true, cacheMode: "prompt", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["high", "max"], tempRange: [0, 2] }],
|
|
95
|
+
["deepseek-v4-pro", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
|
|
96
|
+
["deepseek-v4-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
|
|
98
97
|
// Kimi series
|
|
99
98
|
["kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
100
99
|
// Qwen router prefixes model IDs with provider namespace: kimi/kimi-k3 → kimi-k3 (IK7K4V)
|
|
101
100
|
["kimi/kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
102
101
|
// Kimi For Coding endpoint uses the short model ID "k3" (same specs as kimi-k3) — IK5VGJ
|
|
103
102
|
["k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
104
|
-
["kimi-k2", { context: 256_000, maxOutput: 128_000, thinking: false, partialMode: true, multimodal: true, cacheMode: "none" }],
|
|
105
|
-
["moonshot", { context: 128_000, maxOutput: 32_000, thinking: false, cacheMode: "none" }],
|
|
106
103
|
// GLM series
|
|
107
104
|
["glm-5.2", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
|
|
108
105
|
["glm-5", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
|
|
@@ -115,9 +112,9 @@ const MODEL_SPECS = [
|
|
|
115
112
|
// qwen3.7-max rejects image parts outright (DashScope 400 "Unexpected item type in content") — text-only
|
|
116
113
|
["qwen3.7-max", { context: 1_000_000, maxOutput: 128_000, thinking: true, partialMode: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "high"], tempRange: [0, 2] }],
|
|
117
114
|
["qwen3.8-max", { context: 1_000_000, maxOutput: 128_000, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "high"], tempRange: [0, 2] }],
|
|
118
|
-
["qwen-max", { context: 1_000_000, maxOutput:
|
|
119
|
-
["qwen-plus", { context: 1_000_000, maxOutput:
|
|
120
|
-
["qwen", { context: 1_000_000, maxOutput:
|
|
115
|
+
["qwen-max", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
116
|
+
["qwen-plus", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
117
|
+
["qwen", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
121
118
|
// MiniMax series
|
|
122
119
|
["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
|
123
120
|
["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
package/src/provider/core.mjs
CHANGED
|
@@ -40,6 +40,11 @@ export function createProvider(config) {
|
|
|
40
40
|
|
|
41
41
|
/** Send a streaming chat completion request with automatic continuation on truncation */
|
|
42
42
|
export async function chat(provider, { messages, tools, onToken, onReasoning, onWait, signal, streamRules, firedPatterns }) {
|
|
43
|
+
// Sanitize BEFORE format dispatch — image poisoning bricks anthropic/google sessions
|
|
44
|
+
// the same way it bricks OpenAI-format ones (all raster-only).
|
|
45
|
+
const spec = specForModel(provider.model)
|
|
46
|
+
messages = stripImagesForTextModel(messages, spec)
|
|
47
|
+
|
|
43
48
|
// Format dispatch: delegate to non-OpenAI transports
|
|
44
49
|
if (provider.format === "anthropic") {
|
|
45
50
|
const { chat: anthropicChat } = await import("./anthropic.mjs")
|
|
@@ -62,8 +67,7 @@ export async function chat(provider, { messages, tools, onToken, onReasoning, on
|
|
|
62
67
|
return result
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
messages = normalizeToolPairing(stripImagesForTextModel(messages, spec))
|
|
70
|
+
messages = normalizeToolPairing(messages)
|
|
67
71
|
// Compile string-pattern rules to RegExp at call time
|
|
68
72
|
const rules = compileStreamRules(streamRules)
|
|
69
73
|
const body = {
|
|
@@ -181,23 +185,37 @@ export async function chat(provider, { messages, tools, onToken, onReasoning, on
|
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
/**
|
|
184
|
-
* Replace image parts with text placeholders when
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
188
|
+
* Replace image parts with text placeholders when they would 400 the request:
|
|
189
|
+
* - the model has no vision support at all (history may carry image_url parts from a
|
|
190
|
+
* session resumed after switching from a vision model — text-only APIs like DeepSeek
|
|
191
|
+
* reject the ENTIRE request, bricking the conversation);
|
|
192
|
+
* - the model IS vision-capable but the data URL is not a raster format it can ingest
|
|
193
|
+
* (Kimi/Anthropic/OpenAI/Gemini are all raster-only — Kimi 400s "unsupported image
|
|
194
|
+
* format" on EVERY subsequent request once an svg/bmp part sits in history).
|
|
195
|
+
* Sanitize at send time — history itself is left untouched, so switching back to a
|
|
196
|
+
* capable model/format restores the images. Non-data-URL image refs (http) pass through.
|
|
189
197
|
*/
|
|
198
|
+
const RASTER_IMAGE_URL = /^data:image\/(png|jpe?g|gif|webp);base64,/
|
|
199
|
+
|
|
190
200
|
export function stripImagesForTextModel(messages, spec) {
|
|
191
|
-
if (spec.multimodal) return messages
|
|
192
201
|
let changed = false
|
|
193
202
|
const out = messages.map((m) => {
|
|
194
203
|
if (!Array.isArray(m.content) || !m.content.some((p) => p?.type === "image_url")) return m
|
|
204
|
+
let msgChanged = false
|
|
205
|
+
const parts = m.content.map((p) => {
|
|
206
|
+
if (p?.type !== "image_url") return p
|
|
207
|
+
const url = p.image_url?.url || ""
|
|
208
|
+
if (!url.startsWith("data:")) return p
|
|
209
|
+
if (spec.multimodal && RASTER_IMAGE_URL.test(url)) return p
|
|
210
|
+
msgChanged = true
|
|
211
|
+
const reason = spec.multimodal
|
|
212
|
+
? `unsupported format ${url.match(/^data:([^;,]+)/)?.[1] || "unknown"}`
|
|
213
|
+
: "this model does not support image input"
|
|
214
|
+
return { type: "text", text: `[image omitted — ${reason}]` }
|
|
215
|
+
})
|
|
216
|
+
if (!msgChanged) return m
|
|
195
217
|
changed = true
|
|
196
|
-
return {
|
|
197
|
-
...m,
|
|
198
|
-
content: m.content.map((p) =>
|
|
199
|
-
p?.type === "image_url" ? { type: "text", text: "[image omitted — this model does not support image input]" } : p),
|
|
200
|
-
}
|
|
218
|
+
return { ...m, content: parts }
|
|
201
219
|
})
|
|
202
220
|
return changed ? out : messages
|
|
203
221
|
}
|
package/src/tools/file.mjs
CHANGED
|
@@ -75,7 +75,10 @@ export const readTool = {
|
|
|
75
75
|
|
|
76
76
|
// ---------------------------------------------------------------- read_image
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
// Raster formats only — every mainstream vision API (Kimi, Anthropic, OpenAI, Gemini)
|
|
79
|
+
// rejects svg/bmp. svg is served as text source below; bmp is refused with a hint.
|
|
80
|
+
const IMAGE_EXTENSIONS = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp" }
|
|
81
|
+
const MAX_SVG_CHARS = 100_000
|
|
79
82
|
|
|
80
83
|
export const readImageTool = {
|
|
81
84
|
name: "read_image",
|
|
@@ -83,7 +86,7 @@ export const readImageTool = {
|
|
|
83
86
|
parameters: {
|
|
84
87
|
type: "object",
|
|
85
88
|
properties: {
|
|
86
|
-
path: { type: "string", description: "Path to image file (relative to cwd or absolute). Supports png, jpg, gif, webp
|
|
89
|
+
path: { type: "string", description: "Path to image file (relative to cwd or absolute). Supports png, jpg, gif, webp. svg files are returned as text source (no vision API accepts svg)." },
|
|
87
90
|
},
|
|
88
91
|
required: ["path"],
|
|
89
92
|
},
|
|
@@ -91,6 +94,19 @@ export const readImageTool = {
|
|
|
91
94
|
multimodal: true, // returns JSON { text, images } — agent loop converts to multimodal user message
|
|
92
95
|
/** Returns JSON: { text, images }, for the agent layer to convert into multimodal user messages */
|
|
93
96
|
async execute(args, ctx) {
|
|
97
|
+
const abs = resolveInCwd(ctx, args.path)
|
|
98
|
+
const ext = abs.slice(abs.lastIndexOf(".") + 1).toLowerCase()
|
|
99
|
+
|
|
100
|
+
// SVG is text markup — return the source directly. Works with text-only models too
|
|
101
|
+
// (no vision gate), and never poisons history with an image part the API will 400 on.
|
|
102
|
+
if (ext === "svg") {
|
|
103
|
+
const st = await stat(abs).catch(() => null)
|
|
104
|
+
if (st && st.size > MAX_IMAGE_BYTES) throw new Error(`Image too large: ${Math.round(st.size / 1_000_000)}MB (max 15MB)`)
|
|
105
|
+
const src = normalizeEOL(await readFile(abs, "utf8"))
|
|
106
|
+
return `[read_image: ${args.path} (svg source, ${src.length} chars — no vision API accepts image/svg+xml, showing markup instead)]\n` +
|
|
107
|
+
truncate(src, MAX_SVG_CHARS)
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
// Vision capability gate: injecting an image into a text-only model's history poisons the whole
|
|
95
111
|
// conversation (every subsequent request 400s on the image part). Refuse before reading the file.
|
|
96
112
|
const model = ctx.agent?.provider?.model
|
|
@@ -100,10 +116,11 @@ export const readImageTool = {
|
|
|
100
116
|
`Verify visual output programmatically (file size, dimensions, pixel checks via code) or ask the user to switch to a vision-capable provider.`
|
|
101
117
|
)
|
|
102
118
|
}
|
|
103
|
-
const abs = resolveInCwd(ctx, args.path)
|
|
104
|
-
const ext = abs.slice(abs.lastIndexOf(".") + 1).toLowerCase()
|
|
105
119
|
const mime = IMAGE_EXTENSIONS[ext]
|
|
106
|
-
if (!mime)
|
|
120
|
+
if (!mime) {
|
|
121
|
+
const hint = ext === "bmp" ? " Convert it to PNG first (no mainstream vision API accepts BMP)." : ""
|
|
122
|
+
throw new Error(`Unsupported image format: .${ext}. Supported: ${Object.keys(IMAGE_EXTENSIONS).join(", ")}, svg (as text source).${hint}`)
|
|
123
|
+
}
|
|
107
124
|
// Check size before reading — prevent huge images from blowing up memory (20MB base64 ≈ 15MB raw)
|
|
108
125
|
const imgStat = await stat(abs).catch(() => null)
|
|
109
126
|
if (imgStat && imgStat.size > MAX_IMAGE_BYTES) throw new Error(`Image too large: ${Math.round(imgStat.size / 1_000_000)}MB (max 15MB)`)
|
package/src/tools/read_image.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Read an image file and return it as multimodal content visible to the model. Use this to view screenshots, UI mockups, diagrams, or any visual content. The model only sees images through this tool — it cannot "see" files directly. Supports png, jpg, gif, webp
|
|
1
|
+
Read an image file and return it as multimodal content visible to the model. Use this to view screenshots, UI mockups, diagrams, or any visual content. The model only sees images through this tool — it cannot "see" files directly. Supports png, jpg, gif, webp. The image is base64-encoded and included in the response. Large images (>20MB) are rejected.
|
|
2
2
|
|
|
3
3
|
Parameters:
|
|
4
|
-
- path (required): Path to image file (relative to cwd or absolute). Supports png, jpg, gif, webp
|
|
4
|
+
- path (required): Path to image file (relative to cwd or absolute). Supports png, jpg, gif, webp; svg is returned as text source.
|
|
5
5
|
|
|
6
6
|
Notes:
|
|
7
|
-
-
|
|
7
|
+
- Raster formats only (png/jpg/gif/webp): no mainstream vision API (Kimi, Anthropic, OpenAI, Gemini) accepts svg or bmp, and an unsupported image part in history makes every subsequent request fail with 400. svg files are returned as text source instead (readable by any model); bmp is rejected — convert to PNG first.
|
|
8
|
+
- This tool only works with models that support vision/image input (Kimi K3, Qwen3.7, MiniMax M3). Pure text models (DeepSeek V4, GLM-5) will receive an error — except svg, which needs no vision support since it is read as text.
|