Show images read by agents; keep tool-result images with Claude Code
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
92a99c6a65
commit
99ba3835f3
4 files changed
+36
-3
No files matched your search
@@ -3,6 +3,12 @@
|
||||
All notable changes to CodeCraft. Versions follow [Semantic Versioning](https://semver.org/);
|
||||
until 1.0, minor versions may contain breaking changes.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Images read by an agent (Read tool) are shown right away; with Claude Code they were dropped.
|
||||
|
||||
## [v202611-00030] – 2026-11-15
|
||||
|
||||
### Added
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::config::ModelConfig;
|
||||
use crate::permission::{Decision, Evaluator, ToolInfo, bash_prefix, is_dangerous, split_command};
|
||||
use crate::provider::{ChatRequest, Provider, StreamEvent};
|
||||
use crate::store::UsageRecord;
|
||||
use crate::types::{Event, Message, Part, Role, Session, Usage, new_id, now_ms};
|
||||
use crate::types::{Event, ImageData, Message, Part, Role, Session, Usage, new_id, now_ms};
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::{Value, json};
|
||||
@@ -286,6 +286,20 @@ fn result_text(content: &Value) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Images inside a tool result (the CLI's Read of a picture): base64 blocks.
|
||||
fn result_images(content: &Value) -> Vec<ImageData> {
|
||||
content
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|b| b["type"] == "image" && b["source"]["type"] == "base64")
|
||||
.filter_map(|b| {
|
||||
let data = b["source"]["data"].as_str().filter(|d| !d.is_empty())?;
|
||||
Some(ImageData { media_type: b["source"]["media_type"].as_str().unwrap_or("image/png").to_string(), data: data.to_string(), blob: None, size: None })
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Images of the prompt as files the CLI can read (it has no inline image input in this mode).
|
||||
fn save_images(cwd: &str, parts: &[Part]) -> Vec<String> {
|
||||
use base64::Engine;
|
||||
@@ -458,7 +472,7 @@ pub async fn run(app: Arc<App>, session_id: &str, cancel: CancellationToken) ->
|
||||
let id = b["tool_use_id"].as_str().unwrap_or("").to_string();
|
||||
let is_error = b["is_error"].as_bool().unwrap_or(false);
|
||||
let meta = if is_error { None } else { diffs.remove(&id).map(|d| json!({"diff": d})) };
|
||||
parts.push(Part::ToolResult { name: calls.get(&id).cloned().unwrap_or_default(), call_id: id, content: result_text(&b["content"]), images: vec![], is_error, meta });
|
||||
parts.push(Part::ToolResult { name: calls.get(&id).cloned().unwrap_or_default(), call_id: id, content: result_text(&b["content"]), images: result_images(&b["content"]), is_error, meta });
|
||||
}
|
||||
if !parts.is_empty() {
|
||||
let m = Message::new(Role::User, parts);
|
||||
@@ -852,6 +866,15 @@ pub async fn update() -> Result<String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn tool_result_images_are_kept() {
|
||||
let content = json!([{"type": "text", "text": "ok"}, {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "QUJD"}}]);
|
||||
let imgs = result_images(&content);
|
||||
assert_eq!(imgs.len(), 1);
|
||||
assert_eq!((imgs[0].media_type.as_str(), imgs[0].data.as_str()), ("image/jpeg", "QUJD"));
|
||||
assert!(result_images(&json!("plain text")).is_empty());
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -121,7 +121,7 @@ export function ToolCard({ call, result, running, baseDir }: { call: ToolCall; r
|
||||
const diffLines = typeof meta?.diff === "string" ? meta.diff.split("\n").length : 0;
|
||||
const openByDefault =
|
||||
(status === "err" && !!result) ||
|
||||
(!!result && !result.is_error && ((diffLines > 0 && diffLines <= 40) || call.name === "generate_image" || call.name === "generate_3d"));
|
||||
(!!result && !result.is_error && ((diffLines > 0 && diffLines <= 40) || (result.images?.length ?? 0) > 0 || call.name === "generate_image" || call.name === "generate_3d"));
|
||||
const open = toggled ?? openByDefault;
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
* under "unreleased"; scripts/release.sh renames the key to the release version.
|
||||
*/
|
||||
export const WHATS_NEW: Record<string, { en: string[]; de: string[] }> = {
|
||||
unreleased: {
|
||||
en: ["Images an agent reads are now shown in the chat (also with Claude Code)"],
|
||||
de: ["Bilder, die ein Agent liest, werden jetzt im Chat angezeigt (auch mit Claude Code)"],
|
||||
},
|
||||
"202611-00030": {
|
||||
en: ["/clear: the model forgets the conversation so far, the chat stays visible"],
|
||||
de: ["/clear: das Modell vergisst das bisherige Gespräch, der Chat bleibt sichtbar"],
|
||||
|
||||
Reference in new issue
Block a user