mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(inference): preserve Gemini thought_signature across tool-call turns (TAURI-RUST-4PK) (#3548) (#3553)
This commit is contained in:
@@ -26,6 +26,7 @@ fn native_dispatcher_roundtrip() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "file_read".into(),
|
name: "file_read".into(),
|
||||||
arguments: "{\"path\":\"a.txt\"}".into(),
|
arguments: "{\"path\":\"a.txt\"}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -268,6 +269,7 @@ fn assistant_tool_calls(id: &str) -> ConversationMessage {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
}
|
}
|
||||||
@@ -397,6 +399,7 @@ fn assistant_tool_calls_multi(ids: &[&str]) -> ConversationMessage {
|
|||||||
id: (*id).into(),
|
id: (*id).into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -413,6 +416,7 @@ fn native_dispatcher_serializes_reasoning_content_for_tool_call_turns() {
|
|||||||
id: "tc-1".into(),
|
id: "tc-1".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: Some("chain-of-thought replay blob".into()),
|
reasoning_content: Some("chain-of-thought replay blob".into()),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ async fn native_tool_call_decodes_json_encoded_arguments_string() {
|
|||||||
id: "c1".into(),
|
id: "c1".into(),
|
||||||
name: "captured".into(),
|
name: "captured".into(),
|
||||||
arguments: "{\"city\":\"Berlin\",\"n\":3}".to_string(),
|
arguments: "{\"city\":\"Berlin\",\"n\":3}".to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -143,6 +144,7 @@ async fn documents_silent_drop_of_non_json_arguments_string() {
|
|||||||
name: "captured".into(),
|
name: "captured".into(),
|
||||||
// Not valid JSON — the model "meant" a plain string.
|
// Not valid JSON — the model "meant" a plain string.
|
||||||
arguments: "world".to_string(),
|
arguments: "world".to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -353,6 +355,7 @@ async fn native_tool_calls_take_precedence_over_xml_in_text() {
|
|||||||
id: "c1".into(),
|
id: "c1".into(),
|
||||||
name: "tool_a".into(),
|
name: "tool_a".into(),
|
||||||
arguments: "{\"src\":\"native\"}".into(),
|
arguments: "{\"src\":\"native\"}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ fn structured_tool_call_and_history_helpers_round_trip_expected_shapes() {
|
|||||||
id: "call-1".into(),
|
id: "call-1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: "{\"value\":\"hello\"}".into(),
|
arguments: "{\"value\":\"hello\"}".into(),
|
||||||
|
extra_content: None,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let parsed = parse_structured_tool_calls(&tool_calls);
|
let parsed = parse_structured_tool_calls(&tool_calls);
|
||||||
|
|||||||
@@ -456,6 +456,8 @@ impl Agent {
|
|||||||
.unwrap_or_else(|| format!("parsed-{}-{}", iteration + 1, idx + 1)),
|
.unwrap_or_else(|| format!("parsed-{}-{}", iteration + 1, idx + 1)),
|
||||||
name: call.name.clone(),
|
name: call.name.clone(),
|
||||||
arguments: call.arguments.to_string(),
|
arguments: call.arguments.to_string(),
|
||||||
|
// Prompt-based tool calls carry no provider extra_content.
|
||||||
|
extra_content: None,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,7 @@ fn helper_paths_cover_no_overlap_native_calls_and_truncation() {
|
|||||||
id: "native-1".into(),
|
id: "native-1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}];
|
}];
|
||||||
let response = crate::openhuman::inference::provider::ChatResponse {
|
let response = crate::openhuman::inference::provider::ChatResponse {
|
||||||
text: Some(String::new()),
|
text: Some(String::new()),
|
||||||
|
|||||||
@@ -461,6 +461,7 @@ async fn turn_with_native_dispatcher_handles_tool_results_variant() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -604,6 +605,7 @@ async fn turn_dispatches_spawn_subagent_through_full_path() {
|
|||||||
"prompt": "find out about X"
|
"prompt": "find out about X"
|
||||||
})
|
})
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ fn persisted_tool_calls(
|
|||||||
id,
|
id,
|
||||||
name: c.name.clone(),
|
name: c.name.clone(),
|
||||||
arguments: c.arguments.to_string(),
|
arguments: c.arguments.to_string(),
|
||||||
|
// Prompt-parsed calls carry no provider extra_content; the
|
||||||
|
// native (Gemini) path returns early above, preserving it.
|
||||||
|
extra_content: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -414,6 +414,7 @@ fn trim_history_snaps_past_orphaned_tool_results() {
|
|||||||
id: "call_x".into(),
|
id: "call_x".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ fn tool_response(name: &str, args: &str) -> ChatResponse {
|
|||||||
id: "call-1".into(),
|
id: "call-1".into(),
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
arguments: args.into(),
|
arguments: args.into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ impl Provider for KeywordScriptedProvider {
|
|||||||
id: format!("call_{id}"),
|
id: format!("call_{id}"),
|
||||||
name: c.name.clone(),
|
name: c.name.clone(),
|
||||||
arguments: c.arguments.to_string(),
|
arguments: c.arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -372,6 +372,7 @@ mod tests {
|
|||||||
id: "1".into(),
|
id: "1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: "{\"value\":\"x\"}".into(),
|
arguments: "{\"value\":\"x\"}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
};
|
};
|
||||||
@@ -396,6 +397,7 @@ mod tests {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: "f".into(),
|
name: "f".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -398,6 +398,7 @@ async fn run_tool_call_loop_persists_native_tool_results_as_tool_messages() {
|
|||||||
id: "call-1".into(),
|
id: "call-1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -382,6 +382,7 @@ async fn turn_executes_single_tool_then_returns() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: r#"{"message": "hello from tool"}"#.into(),
|
arguments: r#"{"message": "hello from tool"}"#.into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
text_response("I ran the tool"),
|
text_response("I ran the tool"),
|
||||||
]));
|
]));
|
||||||
@@ -412,16 +413,19 @@ async fn turn_handles_multi_step_tool_chain() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "counter".into(),
|
name: "counter".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
tool_response(vec![ToolCall {
|
tool_response(vec![ToolCall {
|
||||||
id: "tc2".into(),
|
id: "tc2".into(),
|
||||||
name: "counter".into(),
|
name: "counter".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
tool_response(vec![ToolCall {
|
tool_response(vec![ToolCall {
|
||||||
id: "tc3".into(),
|
id: "tc3".into(),
|
||||||
name: "counter".into(),
|
name: "counter".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
text_response("Done after 3 calls"),
|
text_response("Done after 3 calls"),
|
||||||
]));
|
]));
|
||||||
@@ -460,6 +464,7 @@ async fn turn_emits_checkpoint_at_max_iterations() {
|
|||||||
id: format!("tc{i}"),
|
id: format!("tc{i}"),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: r#"{"message": "loop"}"#.into(),
|
arguments: r#"{"message": "loop"}"#.into(),
|
||||||
|
extra_content: None,
|
||||||
}]));
|
}]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,6 +509,7 @@ async fn turn_handles_unknown_tool_gracefully() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "nonexistent_tool".into(),
|
name: "nonexistent_tool".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
text_response("I couldn't find that tool"),
|
text_response("I couldn't find that tool"),
|
||||||
]));
|
]));
|
||||||
@@ -544,6 +550,7 @@ async fn turn_recovers_from_tool_failure() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "fail".into(),
|
name: "fail".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
text_response("Tool failed but I recovered"),
|
text_response("Tool failed but I recovered"),
|
||||||
]));
|
]));
|
||||||
@@ -568,6 +575,7 @@ async fn turn_recovers_from_tool_error() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "panicker".into(),
|
name: "panicker".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
text_response("I recovered from the error"),
|
text_response("I recovered from the error"),
|
||||||
]));
|
]));
|
||||||
@@ -794,6 +802,7 @@ async fn turn_preserves_text_alongside_tool_calls() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: r#"{"message": "hi"}"#.into(),
|
arguments: r#"{"message": "hi"}"#.into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -835,16 +844,19 @@ async fn turn_handles_multiple_tools_in_one_response() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "counter".into(),
|
name: "counter".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
},
|
},
|
||||||
ToolCall {
|
ToolCall {
|
||||||
id: "tc2".into(),
|
id: "tc2".into(),
|
||||||
name: "counter".into(),
|
name: "counter".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
},
|
},
|
||||||
ToolCall {
|
ToolCall {
|
||||||
id: "tc3".into(),
|
id: "tc3".into(),
|
||||||
name: "counter".into(),
|
name: "counter".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
text_response("All 3 done"),
|
text_response("All 3 done"),
|
||||||
@@ -982,6 +994,7 @@ async fn history_contains_all_expected_entries_after_tool_loop() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: r#"{"message": "tool-out"}"#.into(),
|
arguments: r#"{"message": "tool-out"}"#.into(),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
text_response("final answer"),
|
text_response("final answer"),
|
||||||
]));
|
]));
|
||||||
@@ -1087,6 +1100,7 @@ async fn native_dispatcher_handles_stringified_arguments() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: r#"{"message": "hello"}"#.into(),
|
arguments: r#"{"message": "hello"}"#.into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -1176,6 +1190,7 @@ fn conversation_message_serialization_roundtrip() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: Some("thinking".into()),
|
reasoning_content: Some("thinking".into()),
|
||||||
},
|
},
|
||||||
@@ -1302,6 +1317,7 @@ fn xml_dispatcher_converts_history_to_provider_messages() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
},
|
},
|
||||||
@@ -1337,11 +1353,13 @@ fn native_dispatcher_converts_tool_results_to_tool_messages() {
|
|||||||
id: "tc1".into(),
|
id: "tc1".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
},
|
},
|
||||||
ToolCall {
|
ToolCall {
|
||||||
id: "tc2".into(),
|
id: "tc2".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -500,6 +500,7 @@ fn tool_response(name: &str, arguments: serde_json::Value) -> ChatResponse {
|
|||||||
id: format!("call-{name}"),
|
id: format!("call-{name}"),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ fn call(id: &str) -> ConversationMessage {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: "t".into(),
|
name: "t".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ mod tests {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ mod tests {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: "t".into(),
|
name: "t".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ fn call(id: &str) -> ConversationMessage {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: "t".into(),
|
name: "t".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
}
|
}
|
||||||
@@ -217,6 +218,7 @@ fn transcript_renders_all_message_variants() {
|
|||||||
id: "1".into(),
|
id: "1".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: r#"{"cmd":"ls"}"#.into(),
|
arguments: r#"{"cmd":"ls"}"#.into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ impl EventMapper {
|
|||||||
id: call_id,
|
id: call_id,
|
||||||
name,
|
name,
|
||||||
arguments,
|
arguments,
|
||||||
|
// Claude Code CLI events carry no OpenAI-compat extra_content.
|
||||||
|
extra_content: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|||||||
@@ -199,6 +199,9 @@ impl OpenAiCompatibleProvider {
|
|||||||
tc.arguments,
|
tc.arguments,
|
||||||
)),
|
)),
|
||||||
}),
|
}),
|
||||||
|
// Echo Gemini's thought_signature back on
|
||||||
|
// the next turn (TAURI-RUST-4PK).
|
||||||
|
extra_content: tc.extra_content,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@@ -405,6 +408,10 @@ impl OpenAiCompatibleProvider {
|
|||||||
id: tc.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
|
id: tc.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
|
||||||
name,
|
name,
|
||||||
arguments,
|
arguments,
|
||||||
|
// Preserve Gemini's thought_signature (TAURI-RUST-4PK) so it
|
||||||
|
// can be echoed on the next turn; None for providers that
|
||||||
|
// don't send extra_content.
|
||||||
|
extra_content: tc.extra_content,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -420,6 +427,8 @@ impl OpenAiCompatibleProvider {
|
|||||||
id: uuid::Uuid::new_v4().to_string(),
|
id: uuid::Uuid::new_v4().to_string(),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
arguments: normalize_function_arguments(function.arguments.clone()),
|
arguments: normalize_function_arguments(function.arguments.clone()),
|
||||||
|
// Legacy `function_call` shape carries no extra_content.
|
||||||
|
extra_content: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ pub(crate) fn parse_provider_tool_call_from_value(
|
|||||||
arguments: normalize_function_arguments(Some(serde_json::Value::String(
|
arguments: normalize_function_arguments(Some(serde_json::Value::String(
|
||||||
call.arguments,
|
call.arguments,
|
||||||
))),
|
))),
|
||||||
|
// Preserve Gemini's thought_signature through the stored-history
|
||||||
|
// recovery path so it is echoed on the next turn (TAURI-RUST-4PK).
|
||||||
|
extra_content: call.extra_content,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +179,9 @@ pub(crate) fn parse_provider_tool_call_from_value(
|
|||||||
id,
|
id,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: normalize_function_arguments(function.get("arguments").cloned()),
|
arguments: normalize_function_arguments(function.get("arguments").cloned()),
|
||||||
|
// Carry Gemini's thought_signature if the stored value had one
|
||||||
|
// (TAURI-RUST-4PK).
|
||||||
|
extra_content: value.get("extra_content").cloned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -438,6 +438,9 @@ impl Provider for OpenAiCompatibleProvider {
|
|||||||
id: tc.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
|
id: tc.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
|
||||||
name,
|
name,
|
||||||
arguments,
|
arguments,
|
||||||
|
// Non-streaming response: preserve Gemini's thought_signature
|
||||||
|
// so it round-trips on the next turn (TAURI-RUST-4PK).
|
||||||
|
extra_content: tc.extra_content,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -254,6 +254,16 @@ impl OpenAiCompatibleProvider {
|
|||||||
let idx = tc.index.unwrap_or(0);
|
let idx = tc.index.unwrap_or(0);
|
||||||
let entry = tool_accum.entry(idx).or_default();
|
let entry = tool_accum.entry(idx).or_default();
|
||||||
|
|
||||||
|
// Capture the first non-null extra_content seen for
|
||||||
|
// this index (Gemini's thought_signature, TAURI-RUST-4PK).
|
||||||
|
if entry.extra_content.is_none() {
|
||||||
|
if let Some(ec) = tc.extra_content.as_ref() {
|
||||||
|
if !ec.is_null() {
|
||||||
|
entry.extra_content = Some(ec.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(id) = tc.id.as_ref() {
|
if let Some(id) = tc.id.as_ref() {
|
||||||
if entry.id.is_none() {
|
if entry.id.is_none() {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
@@ -380,6 +390,9 @@ impl OpenAiCompatibleProvider {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
// Carry Gemini's thought_signature through to parse_native_response
|
||||||
|
// so it lands on the harness ToolCall (TAURI-RUST-4PK).
|
||||||
|
extra_content: c.extra_content,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -1157,6 +1157,7 @@ fn parse_native_response_preserves_tool_call_id() {
|
|||||||
r#"{"command":"pwd"}"#.to_string(),
|
r#"{"command":"pwd"}"#.to_string(),
|
||||||
)),
|
)),
|
||||||
}),
|
}),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
function_call: None,
|
function_call: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -1183,6 +1184,7 @@ fn parse_native_response_captures_reasoning_content() {
|
|||||||
name: Some("shell".to_string()),
|
name: Some("shell".to_string()),
|
||||||
arguments: Some(serde_json::Value::String("{}".to_string())),
|
arguments: Some(serde_json::Value::String("{}".to_string())),
|
||||||
}),
|
}),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
function_call: None,
|
function_call: None,
|
||||||
reasoning_content: Some(" weighing the options ".to_string()),
|
reasoning_content: Some(" weighing the options ".to_string()),
|
||||||
@@ -1234,6 +1236,189 @@ fn convert_messages_for_native_maps_tool_result_payload() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── TAURI-RUST-4PK: Gemini thought_signature round-trip ──────────────────────
|
||||||
|
|
||||||
|
/// The wire `ToolCall` must capture Gemini's `extra_content` from the response
|
||||||
|
/// and re-emit it verbatim on the request, so the thought_signature survives the
|
||||||
|
/// round-trip. A tool call without it omits the field entirely (non-Gemini
|
||||||
|
/// providers stay byte-identical on the wire).
|
||||||
|
#[test]
|
||||||
|
fn tool_call_wire_round_trips_extra_content() {
|
||||||
|
let json = r#"{"id":"call_g","type":"function","function":{"name":"shell","arguments":"{}"},"extra_content":{"google":{"thought_signature":"SIG123"}}}"#;
|
||||||
|
let tc: ToolCall = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
tc.extra_content
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.pointer("/google/thought_signature"))
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("SIG123"),
|
||||||
|
"extra_content must be captured from the Gemini response"
|
||||||
|
);
|
||||||
|
let reemitted = serde_json::to_value(&tc).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
reemitted
|
||||||
|
.pointer("/extra_content/google/thought_signature")
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("SIG123"),
|
||||||
|
"extra_content must be echoed verbatim on the request body"
|
||||||
|
);
|
||||||
|
|
||||||
|
let bare: ToolCall = serde_json::from_str(
|
||||||
|
r#"{"id":"c","type":"function","function":{"name":"x","arguments":"{}"}}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(bare.extra_content.is_none());
|
||||||
|
assert!(
|
||||||
|
serde_json::to_value(&bare)
|
||||||
|
.unwrap()
|
||||||
|
.get("extra_content")
|
||||||
|
.is_none(),
|
||||||
|
"providers that don't send extra_content keep a byte-identical wire body"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `parse_native_response` lifts the tool-call `extra_content` onto the harness
|
||||||
|
/// ToolCall so it can be persisted and echoed (TAURI-RUST-4PK).
|
||||||
|
#[test]
|
||||||
|
fn parse_native_response_captures_tool_call_extra_content() {
|
||||||
|
let message = ResponseMessage {
|
||||||
|
content: None,
|
||||||
|
tool_calls: Some(vec![ToolCall {
|
||||||
|
id: Some("call_g".to_string()),
|
||||||
|
kind: Some("function".to_string()),
|
||||||
|
function: Some(Function {
|
||||||
|
name: Some("shell".to_string()),
|
||||||
|
arguments: Some(serde_json::Value::String("{}".to_string())),
|
||||||
|
}),
|
||||||
|
extra_content: Some(serde_json::json!({"google":{"thought_signature":"SIG_RESP"}})),
|
||||||
|
}]),
|
||||||
|
function_call: None,
|
||||||
|
reasoning_content: None,
|
||||||
|
};
|
||||||
|
let parsed =
|
||||||
|
OpenAiCompatibleProvider::parse_native_response(wrap_message(message), "google").unwrap();
|
||||||
|
assert_eq!(parsed.tool_calls.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
parsed.tool_calls[0]
|
||||||
|
.extra_content
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.pointer("/google/thought_signature"))
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("SIG_RESP"),
|
||||||
|
"the signature must land on the harness ToolCall"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On rebuild, a persisted assistant tool-call message whose stored JSON carries
|
||||||
|
/// `extra_content` must re-emit it on the wire tool_calls, so Gemini sees the
|
||||||
|
/// signature on the follow-up turn (TAURI-RUST-4PK).
|
||||||
|
#[test]
|
||||||
|
fn convert_messages_for_native_echoes_tool_call_extra_content() {
|
||||||
|
let input = vec![
|
||||||
|
ChatMessage::assistant(
|
||||||
|
r#"{"content":"on it","tool_calls":[{"id":"call_g","name":"shell","arguments":"{}","extra_content":{"google":{"thought_signature":"SIG_ECHO"}}}]}"#,
|
||||||
|
),
|
||||||
|
ChatMessage::tool(r#"{"tool_call_id":"call_g","content":"done"}"#),
|
||||||
|
];
|
||||||
|
let converted = OpenAiCompatibleProvider::convert_messages_for_native(&input);
|
||||||
|
let tool_calls = converted[0]
|
||||||
|
.tool_calls
|
||||||
|
.as_ref()
|
||||||
|
.expect("assistant tool_calls present");
|
||||||
|
assert_eq!(
|
||||||
|
tool_calls[0]
|
||||||
|
.extra_content
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.pointer("/google/thought_signature"))
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("SIG_ECHO"),
|
||||||
|
"stored extra_content must be echoed back on the rebuilt request"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_value(tool_calls)
|
||||||
|
.unwrap()
|
||||||
|
.pointer("/0/extra_content/google/thought_signature")
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("SIG_ECHO"),
|
||||||
|
"echoed signature must appear on the serialized wire body"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A non-Gemini stored tool call (no `extra_content`) rebuilds with the field
|
||||||
|
/// omitted — every other provider's wire body stays byte-identical.
|
||||||
|
#[test]
|
||||||
|
fn convert_messages_for_native_tool_call_without_extra_content_stays_none() {
|
||||||
|
let input = vec![
|
||||||
|
ChatMessage::assistant(
|
||||||
|
r#"{"content":"on it","tool_calls":[{"id":"call_x","name":"shell","arguments":"{}"}]}"#,
|
||||||
|
),
|
||||||
|
ChatMessage::tool(r#"{"tool_call_id":"call_x","content":"done"}"#),
|
||||||
|
];
|
||||||
|
let converted = OpenAiCompatibleProvider::convert_messages_for_native(&input);
|
||||||
|
let tool_calls = converted[0]
|
||||||
|
.tool_calls
|
||||||
|
.as_ref()
|
||||||
|
.expect("assistant tool_calls present");
|
||||||
|
assert!(tool_calls[0].extra_content.is_none());
|
||||||
|
assert!(serde_json::to_value(&tool_calls[0])
|
||||||
|
.unwrap()
|
||||||
|
.get("extra_content")
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Streaming: Gemini sends the thought_signature in the tool-call delta's
|
||||||
|
/// `extra_content` on the first chunk. The accumulator must preserve it onto the
|
||||||
|
/// aggregated tool call so it reaches history (TAURI-RUST-4PK).
|
||||||
|
#[tokio::test]
|
||||||
|
async fn streaming_tool_call_captures_extra_content() {
|
||||||
|
let mock_server = MockServer::start().await;
|
||||||
|
let body = "data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_g\",\"type\":\"function\",\"function\":{\"name\":\"shell\",\"arguments\":\"{}\"},\"extra_content\":{\"google\":{\"thought_signature\":\"SIG_STREAM\"}}}]}}]}\n\n\
|
||||||
|
data: [DONE]\n\n";
|
||||||
|
Mock::given(method("POST"))
|
||||||
|
.and(path("/chat/completions"))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_raw(body, "text/event-stream"))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let provider =
|
||||||
|
OpenAiCompatibleProvider::new("google", &mock_server.uri(), None, AuthStyle::None);
|
||||||
|
let request = NativeChatRequest {
|
||||||
|
model: "models/gemini-3.5-flash".to_string(),
|
||||||
|
messages: vec![NativeMessage {
|
||||||
|
role: "user".to_string(),
|
||||||
|
content: Some("hi".into()),
|
||||||
|
tool_call_id: None,
|
||||||
|
tool_calls: None,
|
||||||
|
reasoning_content: None,
|
||||||
|
}],
|
||||||
|
temperature: Some(0.7),
|
||||||
|
stream: Some(true),
|
||||||
|
tools: None,
|
||||||
|
tool_choice: None,
|
||||||
|
thread_id: None,
|
||||||
|
stream_options: Some(super::compatible_types::OpenAiStreamOptions {
|
||||||
|
include_usage: true,
|
||||||
|
}),
|
||||||
|
options: None,
|
||||||
|
frequency_penalty: None,
|
||||||
|
};
|
||||||
|
let (delta_tx, _delta_rx) = tokio::sync::mpsc::channel(64);
|
||||||
|
let resp = provider
|
||||||
|
.stream_native_chat(None, &request, &delta_tx, 0)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.tool_calls.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
resp.tool_calls[0]
|
||||||
|
.extra_content
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.pointer("/google/thought_signature"))
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("SIG_STREAM"),
|
||||||
|
"streaming must preserve the thought_signature onto the aggregated tool call"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper: roles in serialized order.
|
/// Helper: roles in serialized order.
|
||||||
fn roles(messages: &[NativeMessage]) -> Vec<&str> {
|
fn roles(messages: &[NativeMessage]) -> Vec<&str> {
|
||||||
messages.iter().map(|m| m.role.as_str()).collect()
|
messages.iter().map(|m| m.role.as_str()).collect()
|
||||||
@@ -1550,6 +1735,7 @@ fn enforce_invariants_clears_reasoning_when_assistant_collapses_to_text() {
|
|||||||
name: Some("web_fetch".to_string()),
|
name: Some("web_fetch".to_string()),
|
||||||
arguments: Some(serde_json::Value::String("{}".to_string())),
|
arguments: Some(serde_json::Value::String("{}".to_string())),
|
||||||
}),
|
}),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
reasoning_content: Some("deep reasoning".to_string()),
|
reasoning_content: Some("deep reasoning".to_string()),
|
||||||
},
|
},
|
||||||
@@ -1897,6 +2083,7 @@ fn response_with_tool_call_object_arguments_deserializes() {
|
|||||||
name: Some("get_weather".to_string()),
|
name: Some("get_weather".to_string()),
|
||||||
arguments: Some(serde_json::json!({"location":"London","unit":"c"})),
|
arguments: Some(serde_json::json!({"location":"London","unit":"c"})),
|
||||||
}),
|
}),
|
||||||
|
extra_content: None,
|
||||||
}]),
|
}]),
|
||||||
function_call: None,
|
function_call: None,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -411,6 +411,20 @@ pub(crate) struct ToolCall {
|
|||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub(crate) kind: Option<String>,
|
pub(crate) kind: Option<String>,
|
||||||
pub(crate) function: Option<Function>,
|
pub(crate) function: Option<Function>,
|
||||||
|
/// Provider-specific passthrough metadata attached to a tool call.
|
||||||
|
///
|
||||||
|
/// Google's Gemini OpenAI-compat endpoint returns a cryptographically
|
||||||
|
/// signed reasoning token here as
|
||||||
|
/// `extra_content.google.thought_signature`, and **requires** it echoed
|
||||||
|
/// back verbatim on the assistant tool-call turn of every subsequent
|
||||||
|
/// request — otherwise it 400s with "Function call is missing a
|
||||||
|
/// thought_signature" (TAURI-RUST-4PK). Captured on the response and
|
||||||
|
/// re-emitted on the request as an opaque value so any future
|
||||||
|
/// `extra_content.*` keys round-trip unchanged. `skip_serializing_if`
|
||||||
|
/// keeps the wire body byte-identical for every provider that doesn't
|
||||||
|
/// send it.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) extra_content: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
@@ -517,6 +531,12 @@ pub(crate) struct StreamToolCallDelta {
|
|||||||
pub(crate) kind: Option<String>,
|
pub(crate) kind: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) function: Option<StreamToolCallFunction>,
|
pub(crate) function: Option<StreamToolCallFunction>,
|
||||||
|
/// Provider passthrough metadata (Gemini's `extra_content`, carrying
|
||||||
|
/// `google.thought_signature`). Arrives on the first chunk for a given
|
||||||
|
/// tool-call index; accumulated and re-emitted so the signature survives
|
||||||
|
/// the streaming path (TAURI-RUST-4PK).
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) extra_content: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -545,4 +565,8 @@ pub(crate) struct StreamingToolCall {
|
|||||||
pub(crate) arguments: String,
|
pub(crate) arguments: String,
|
||||||
pub(crate) emitted_start: bool,
|
pub(crate) emitted_start: bool,
|
||||||
pub(crate) emitted_chars: usize,
|
pub(crate) emitted_chars: usize,
|
||||||
|
/// First non-null `extra_content` seen for this tool-call index (Gemini's
|
||||||
|
/// thought_signature). Re-emitted on the aggregated [`ToolCall`] so it can
|
||||||
|
/// be echoed on the next turn (TAURI-RUST-4PK).
|
||||||
|
pub(crate) extra_content: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ pub struct ToolCall {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub arguments: String,
|
pub arguments: String,
|
||||||
|
/// Provider-specific passthrough metadata for this call, captured from the
|
||||||
|
/// response and echoed back verbatim on the next assistant turn. Carries
|
||||||
|
/// Google Gemini's required `extra_content.google.thought_signature` so
|
||||||
|
/// multi-turn tool calling round-trips without a 400 (TAURI-RUST-4PK).
|
||||||
|
/// `None`/omitted for every provider that doesn't emit it, so non-Gemini
|
||||||
|
/// history stays byte-identical.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub extra_content: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Token usage information returned by the provider after an inference call.
|
/// Token usage information returned by the provider after an inference call.
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ fn chat_response_helpers() {
|
|||||||
id: "1".into(),
|
id: "1".into(),
|
||||||
name: "shell".into(),
|
name: "shell".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
@@ -69,6 +70,7 @@ fn tool_call_serialization() {
|
|||||||
id: "call_123".into(),
|
id: "call_123".into(),
|
||||||
name: "file_read".into(),
|
name: "file_read".into(),
|
||||||
arguments: r#"{"path":"test.txt"}"#.into(),
|
arguments: r#"{"path":"test.txt"}"#.into(),
|
||||||
|
extra_content: None,
|
||||||
};
|
};
|
||||||
let json = serde_json::to_string(&tc).unwrap();
|
let json = serde_json::to_string(&tc).unwrap();
|
||||||
assert!(json.contains("call_123"));
|
assert!(json.contains("call_123"));
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ fn tool_call(id: &str, name: &str, args: serde_json::Value) -> ChatResponse {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
arguments: args.to_string(),
|
arguments: args.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ fn tool_call_resp(id: &str, name: &str, args: serde_json::Value) -> ChatResponse
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
arguments: args.to_string(),
|
arguments: args.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ fn tool_response(name: &str, arguments: serde_json::Value) -> ChatResponse {
|
|||||||
id: "round21-call".to_string(),
|
id: "round21-call".to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: Some("scripted tool use".to_string()),
|
reasoning_content: Some("scripted tool use".to_string()),
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ fn tool_response(id: &str, name: &str, arguments: serde_json::Value) -> ChatResp
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: Some(UsageInfo {
|
usage: Some(UsageInfo {
|
||||||
input_tokens: 7,
|
input_tokens: 7,
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ fn tool_call(id: &str, name: &str, arguments: serde_json::Value) -> ToolCall {
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ fn tool_response(name: &str, arguments: serde_json::Value) -> ChatResponse {
|
|||||||
id: "round18-call".to_string(),
|
id: "round18-call".to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: Some("test reasoning".to_string()),
|
reasoning_content: Some("test reasoning".to_string()),
|
||||||
|
|||||||
@@ -487,6 +487,7 @@ fn native_tool_response(id: &str, name: &str, args: serde_json::Value) -> ChatRe
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: args.to_string(),
|
arguments: args.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: Some(UsageInfo {
|
usage: Some(UsageInfo {
|
||||||
input_tokens: 21,
|
input_tokens: 21,
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ fn native_tool_response(name: &str, arguments: serde_json::Value) -> ChatRespons
|
|||||||
id: format!("call-{name}"),
|
id: format!("call-{name}"),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: Some(UsageInfo {
|
usage: Some(UsageInfo {
|
||||||
input_tokens: 13,
|
input_tokens: 13,
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ fn native_tool_response(name: &str, arguments: &str) -> ChatResponse {
|
|||||||
id: "round20-native-1".to_string(),
|
id: "round20-native-1".to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: Some(UsageInfo {
|
usage: Some(UsageInfo {
|
||||||
input_tokens: 7_000,
|
input_tokens: 7_000,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ impl Provider for MockCalendarProvider {
|
|||||||
"timeMax": "2026-05-04T00:00:00Z"
|
"timeMax": "2026-05-04T00:00:00Z"
|
||||||
})
|
})
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ impl Provider for StubProvider {
|
|||||||
id: "call_1".into(),
|
id: "call_1".into(),
|
||||||
name: "composio_list_tools".into(),
|
name: "composio_list_tools".into(),
|
||||||
arguments: json!({ "toolkits": ["gmail"] }).to_string(),
|
arguments: json!({ "toolkits": ["gmail"] }).to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -3798,11 +3798,13 @@ fn agent_dispatchers_and_host_runtime_cover_public_edge_paths() {
|
|||||||
id: "call-ok".into(),
|
id: "call-ok".into(),
|
||||||
name: "search_docs".into(),
|
name: "search_docs".into(),
|
||||||
arguments: "{\"query\":\"native\"}".into(),
|
arguments: "{\"query\":\"native\"}".into(),
|
||||||
|
extra_content: None,
|
||||||
},
|
},
|
||||||
ToolCall {
|
ToolCall {
|
||||||
id: "call-bad-json".into(),
|
id: "call-bad-json".into(),
|
||||||
name: "search_docs".into(),
|
name: "search_docs".into(),
|
||||||
arguments: "{not-json".into(),
|
arguments: "{not-json".into(),
|
||||||
|
extra_content: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -3835,6 +3837,7 @@ fn agent_dispatchers_and_host_runtime_cover_public_edge_paths() {
|
|||||||
id: "call-1".into(),
|
id: "call-1".into(),
|
||||||
name: "search_docs".into(),
|
name: "search_docs".into(),
|
||||||
arguments: "{\"query\":\"paired\"}".into(),
|
arguments: "{\"query\":\"paired\"}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: Some("thinking".into()),
|
reasoning_content: Some("thinking".into()),
|
||||||
},
|
},
|
||||||
@@ -3848,6 +3851,7 @@ fn agent_dispatchers_and_host_runtime_cover_public_edge_paths() {
|
|||||||
id: "missing-result".into(),
|
id: "missing-result".into(),
|
||||||
name: "search_docs".into(),
|
name: "search_docs".into(),
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -671,6 +671,7 @@ impl Provider for Round22Provider {
|
|||||||
id: "round22-call".to_string(),
|
id: "round22-call".to_string(),
|
||||||
name: "round22_tool".to_string(),
|
name: "round22_tool".to_string(),
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ fn tool_response(id: &str, name: &str, arguments: serde_json::Value) -> ChatResp
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
arguments: arguments.to_string(),
|
arguments: arguments.to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
usage: None,
|
usage: None,
|
||||||
reasoning_content: Some(format!("need {name}")),
|
reasoning_content: Some(format!("need {name}")),
|
||||||
|
|||||||
@@ -725,6 +725,7 @@ async fn round16_agent_builder_turn_uses_public_harness_paths() {
|
|||||||
id: "call-round16".into(),
|
id: "call-round16".into(),
|
||||||
name: "echo".into(),
|
name: "echo".into(),
|
||||||
arguments: json!({ "message": "builder" }).to_string(),
|
arguments: json!({ "message": "builder" }).to_string(),
|
||||||
|
extra_content: None,
|
||||||
}],
|
}],
|
||||||
),
|
),
|
||||||
response(Some("builder final"), Vec::new()),
|
response(Some("builder final"), Vec::new()),
|
||||||
|
|||||||
Reference in New Issue
Block a user