In the provider compatibility layer for Gemini (and other providers requiring ), fully support the flow, round-trip, and placeholder fallback of thought_signature.

This commit is contained in:
sunmanbitch
2026-06-04 21:13:55 +08:00
parent 4619375c14
commit b119afcaca
11 changed files with 193 additions and 43 deletions

View File

@@ -780,6 +780,7 @@ mod tests {
id: tool_id.to_string(),
name: "search".to_string(),
input: "{\"q\":\"*.rs\"}".to_string(),
thought_signature: None,
},
]))
.unwrap();

View File

@@ -37,6 +37,7 @@ pub enum AssistantEvent {
id: String,
name: String,
input: String,
thought_signature: Option<String>,
},
Usage(TokenUsage),
PromptCache(PromptCacheEvent),
@@ -381,7 +382,7 @@ where
.blocks
.iter()
.filter_map(|block| match block {
ContentBlock::ToolUse { id, name, input } => {
ContentBlock::ToolUse { id, name, input, .. } => {
Some((id.clone(), name.clone(), input.clone()))
}
_ => None,
@@ -741,9 +742,9 @@ fn build_assistant_message(
});
}
AssistantEvent::TextDelta(delta) => text.push_str(&delta),
AssistantEvent::ToolUse { id, name, input } => {
AssistantEvent::ToolUse { id, name, input, thought_signature } => {
flush_text_block(&mut text, &mut blocks);
blocks.push(ContentBlock::ToolUse { id, name, input });
blocks.push(ContentBlock::ToolUse { id, name, input, thought_signature });
}
AssistantEvent::Usage(value) => usage = Some(value),
AssistantEvent::PromptCache(event) => prompt_cache_events.push(event),
@@ -880,6 +881,7 @@ mod tests {
id: "tool-1".to_string(),
name: "add".to_string(),
input: "2,2".to_string(),
thought_signature: None,
},
AssistantEvent::Usage(TokenUsage {
input_tokens: 20,
@@ -1046,6 +1048,7 @@ mod tests {
id: "tool-1".to_string(),
name: "blocked".to_string(),
input: "secret".to_string(),
thought_signature: None,
},
AssistantEvent::MessageStop,
])
@@ -1091,6 +1094,7 @@ mod tests {
id: "tool-1".to_string(),
name: "blocked".to_string(),
input: r#"{"path":"secret.txt"}"#.to_string(),
thought_signature: None,
},
AssistantEvent::MessageStop,
])
@@ -1153,6 +1157,7 @@ mod tests {
id: "tool-1".to_string(),
name: "blocked".to_string(),
input: r#"{"path":"secret.txt"}"#.to_string(),
thought_signature: None,
},
AssistantEvent::MessageStop,
])
@@ -1213,6 +1218,7 @@ mod tests {
id: "tool-1".to_string(),
name: "add".to_string(),
input: r#"{"lhs":2,"rhs":2}"#.to_string(),
thought_signature: None,
},
AssistantEvent::MessageStop,
]),
@@ -1288,6 +1294,7 @@ mod tests {
id: "tool-1".to_string(),
name: "fail".to_string(),
input: r#"{"path":"README.md"}"#.to_string(),
thought_signature: None,
},
AssistantEvent::MessageStop,
]),
@@ -1755,6 +1762,7 @@ mod tests {
id: "tool-1".to_string(),
name: "echo".to_string(),
input: "payload".to_string(),
thought_signature: None,
},
AssistantEvent::MessageStop,
];
@@ -1778,6 +1786,7 @@ mod tests {
id: "tool-1".to_string(),
name: "echo".to_string(),
input: "payload".to_string(),
thought_signature: None,
},
]
);
@@ -1811,6 +1820,7 @@ mod tests {
id: "tool-1".to_string(),
name: "echo".to_string(),
input: "payload".to_string(),
thought_signature: None,
},
AssistantEvent::MessageStop,
])

View File

@@ -42,6 +42,7 @@ pub enum ContentBlock {
id: String,
name: String,
input: String,
thought_signature: Option<String>,
},
ToolResult {
tool_use_id: String,
@@ -817,7 +818,7 @@ impl ContentBlock {
);
}
}
Self::ToolUse { id, name, input } => {
Self::ToolUse { id, name, input, thought_signature } => {
object.insert(
"type".to_string(),
JsonValue::String("tool_use".to_string()),
@@ -825,6 +826,12 @@ impl ContentBlock {
object.insert("id".to_string(), JsonValue::String(id.clone()));
object.insert("name".to_string(), JsonValue::String(name.clone()));
object.insert("input".to_string(), JsonValue::String(input.clone()));
if let Some(sig) = thought_signature {
object.insert(
"thought_signature".to_string(),
JsonValue::String(sig.clone()),
);
}
}
Self::ToolResult {
tool_use_id,
@@ -874,6 +881,7 @@ impl ContentBlock {
id: required_string(object, "id")?,
name: required_string(object, "name")?,
input: required_string(object, "input")?,
thought_signature: object.get("thought_signature").and_then(JsonValue::as_str).map(String::from)
}),
"tool_result" => Ok(Self::ToolResult {
tool_use_id: required_string(object, "tool_use_id")?,
@@ -1069,7 +1077,7 @@ fn persisted_block_json(block: &ContentBlock) -> JsonValue {
);
}
}
ContentBlock::ToolUse { id, name, input } => {
ContentBlock::ToolUse { id, name, input, thought_signature } => {
object.insert(
"type".to_string(),
JsonValue::String("tool_use".to_string()),
@@ -1083,6 +1091,12 @@ fn persisted_block_json(block: &ContentBlock) -> JsonValue {
"input".to_string(),
JsonValue::String(sanitize_jsonl_field(input)),
);
if let Some(sig) = thought_signature {
object.insert(
"thought_signature".to_string(),
JsonValue::String(sanitize_jsonl_field(sig)),
);
}
}
ContentBlock::ToolResult {
tool_use_id,
@@ -1433,6 +1447,7 @@ mod tests {
id: "tool-1".to_string(),
name: "bash".to_string(),
input: "echo hi".to_string(),
thought_signature: None,
},
],
Some(TokenUsage {
@@ -1596,6 +1611,7 @@ mod tests {
id: "tool-1".to_string(),
name: "bash".to_string(),
input: format!("Authorization: Bearer {secret}"),
thought_signature: None,
},
]))
.expect("tool use should append");

View File

@@ -686,6 +686,7 @@ mod tests {
id: "1".to_string(),
name: "read_file".to_string(),
input: r#"{"path":"src/main.rs"}"#.to_string(),
thought_signature: None,
}]),
ConversationMessage::tool_result(
"1",
@@ -697,6 +698,7 @@ mod tests {
id: "2".to_string(),
name: "edit_file".to_string(),
input: r#"{"path":"src/main.rs","old":"old","new":"new"}"#.to_string(),
thought_signature: None,
}]),
ConversationMessage::tool_result(
"2",
@@ -718,6 +720,7 @@ mod tests {
id: "1".to_string(),
name: "read_file".to_string(),
input: r#"{"path":"src/main.rs"}"#.to_string(),
thought_signature: None,
}]),
ConversationMessage::tool_result(
"1",
@@ -746,6 +749,7 @@ mod tests {
id: "t".to_string(),
name: "bash".to_string(),
input: r#"{"command":"ls"}"#.to_string(),
thought_signature: None,
},
]));
@@ -764,6 +768,7 @@ mod tests {
id: format!("read_{i}"),
name: "read_file".to_string(),
input: format!(r#"{{"path":"src/{i}.rs"}}"#),
thought_signature: None,
},
]));
messages.push(ConversationMessage::tool_result(
@@ -789,6 +794,7 @@ mod tests {
id: "1".to_string(),
name: "read_file".to_string(),
input: r#"{"path":"src/main.rs"}"#.to_string(),
thought_signature: None,
}]),
ConversationMessage::tool_result(
"1",
@@ -800,6 +806,7 @@ mod tests {
id: "2".to_string(),
name: "edit_file".to_string(),
input: r#"{"path":"src/main.rs","old":"buggy","new":"fixed"}"#.to_string(),
thought_signature: None,
}]),
ConversationMessage::tool_result(
"2",