mirror of
https://github.com/delibae/claude-prism.git
synced 2026-10-07 02:58:00 +00:00
Support legacy function calls for compatible providers
This commit is contained in:
parent
3a45b0a4ad
commit
5620b37b90
1 changed files with 379 additions and 36 deletions
|
|
@ -1955,6 +1955,18 @@ fn direct_provider_tools() -> serde_json::Value {
|
|||
])
|
||||
}
|
||||
|
||||
fn direct_provider_functions() -> serde_json::Value {
|
||||
let Some(tools) = direct_provider_tools().as_array().cloned() else {
|
||||
return json!([]);
|
||||
};
|
||||
json!(
|
||||
tools
|
||||
.into_iter()
|
||||
.filter_map(|tool| tool.get("function").cloned())
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
}
|
||||
|
||||
fn openai_chat_completions_url(base_url: &str) -> String {
|
||||
let clean = base_url.trim_end_matches('/');
|
||||
if clean.ends_with("/chat/completions") {
|
||||
|
|
@ -1986,6 +1998,7 @@ struct DirectToolCall {
|
|||
id: String,
|
||||
name: String,
|
||||
input: serde_json::Value,
|
||||
legacy_function_call: bool,
|
||||
}
|
||||
|
||||
struct DirectToolOutput {
|
||||
|
|
@ -2007,6 +2020,7 @@ struct DirectStreamingToolCall {
|
|||
id: Option<String>,
|
||||
name: String,
|
||||
arguments: String,
|
||||
legacy_function_call: bool,
|
||||
}
|
||||
|
||||
struct DirectStreamFailure {
|
||||
|
|
@ -2015,6 +2029,13 @@ struct DirectStreamFailure {
|
|||
can_retry_without_tools: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum DirectToolRequestMode {
|
||||
Tools,
|
||||
Functions,
|
||||
None,
|
||||
}
|
||||
|
||||
fn emit_direct_output(window: &WebviewWindow, tab_id: &str, event: &serde_json::Value) {
|
||||
let _ = window.emit(
|
||||
"claude-output",
|
||||
|
|
@ -2782,7 +2803,7 @@ fn parse_direct_tool_arguments(arguments: &serde_json::Value) -> serde_json::Val
|
|||
}
|
||||
|
||||
fn parse_direct_tool_calls(response: &serde_json::Value) -> Vec<DirectToolCall> {
|
||||
response
|
||||
let calls: Vec<DirectToolCall> = response
|
||||
.pointer("/choices/0/message/tool_calls")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|calls| {
|
||||
|
|
@ -2800,10 +2821,35 @@ fn parse_direct_tool_calls(response: &serde_json::Value) -> Vec<DirectToolCall>
|
|||
.pointer("/function/arguments")
|
||||
.map(parse_direct_tool_arguments)
|
||||
.unwrap_or_else(|| json!({}));
|
||||
Some(DirectToolCall { id, name, input })
|
||||
Some(DirectToolCall {
|
||||
id,
|
||||
name,
|
||||
input,
|
||||
legacy_function_call: false,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if !calls.is_empty() {
|
||||
return calls;
|
||||
}
|
||||
|
||||
response
|
||||
.pointer("/choices/0/message/function_call")
|
||||
.and_then(|call| {
|
||||
let name = call.get("name")?.as_str()?.to_string();
|
||||
let input = call
|
||||
.get("arguments")
|
||||
.map(parse_direct_tool_arguments)
|
||||
.unwrap_or_else(|| json!({}));
|
||||
Some(vec![DirectToolCall {
|
||||
id: "function_call_1".to_string(),
|
||||
name,
|
||||
input,
|
||||
legacy_function_call: true,
|
||||
}])
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
|
@ -2975,6 +3021,7 @@ fn direct_tool_calls_from_stream(
|
|||
id,
|
||||
name: call.name,
|
||||
input,
|
||||
legacy_function_call: call.legacy_function_call,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -2998,7 +3045,17 @@ fn direct_message_from_parts(
|
|||
object.insert("reasoning_content".to_string(), json!(reasoning));
|
||||
}
|
||||
}
|
||||
if !tool_calls.is_empty() {
|
||||
if let Some(tool_call) = tool_calls.iter().find(|tool_call| tool_call.legacy_function_call) {
|
||||
if let Some(object) = message.as_object_mut() {
|
||||
object.insert(
|
||||
"function_call".to_string(),
|
||||
json!({
|
||||
"name": tool_call.name,
|
||||
"arguments": tool_call.input.to_string(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else if !tool_calls.is_empty() {
|
||||
if let Some(object) = message.as_object_mut() {
|
||||
object.insert(
|
||||
"tool_calls".to_string(),
|
||||
|
|
@ -3038,21 +3095,112 @@ fn provider_error_allows_toolless_retry(status: reqwest::StatusCode, body: &str)
|
|||
|| body.contains("unknown parameter")
|
||||
}
|
||||
|
||||
fn add_direct_request_tooling(request_body: &mut serde_json::Value, use_tools: bool) {
|
||||
if !use_tools {
|
||||
fn add_direct_request_tooling(request_body: &mut serde_json::Value, mode: DirectToolRequestMode) {
|
||||
let Some(object) = request_body.as_object_mut() else {
|
||||
return;
|
||||
};
|
||||
match mode {
|
||||
DirectToolRequestMode::Tools => {
|
||||
object.insert("tools".to_string(), direct_provider_tools());
|
||||
object.insert("tool_choice".to_string(), json!("auto"));
|
||||
}
|
||||
DirectToolRequestMode::Functions => {
|
||||
object.insert("functions".to_string(), direct_provider_functions());
|
||||
object.insert("function_call".to_string(), json!("auto"));
|
||||
}
|
||||
DirectToolRequestMode::None => {}
|
||||
}
|
||||
if let Some(object) = request_body.as_object_mut() {
|
||||
object.insert("tools".to_string(), direct_provider_tools());
|
||||
object.insert("tool_choice".to_string(), json!("auto"));
|
||||
}
|
||||
|
||||
fn direct_messages_for_legacy_functions(messages: Vec<serde_json::Value>) -> Vec<serde_json::Value> {
|
||||
let mut tool_names: HashMap<String, String> = HashMap::new();
|
||||
let mut converted = Vec::new();
|
||||
|
||||
for mut message in messages {
|
||||
match message.get("role").and_then(|v| v.as_str()) {
|
||||
Some("assistant") => {
|
||||
if let Some(tool_calls) = message.get("tool_calls").and_then(|v| v.as_array()) {
|
||||
for tool_call in tool_calls {
|
||||
if let (Some(id), Some(name)) = (
|
||||
tool_call.get("id").and_then(|v| v.as_str()),
|
||||
tool_call.pointer("/function/name").and_then(|v| v.as_str()),
|
||||
) {
|
||||
tool_names.insert(id.to_string(), name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((name, arguments)) = tool_calls.first().map(|first| {
|
||||
let name = first
|
||||
.pointer("/function/name")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| "tool".to_string());
|
||||
let arguments = first
|
||||
.pointer("/function/arguments")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
first
|
||||
.pointer("/function/arguments")
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| "{}".to_string())
|
||||
});
|
||||
(name, arguments)
|
||||
}) {
|
||||
if let Some(object) = message.as_object_mut() {
|
||||
object.remove("tool_calls");
|
||||
object.insert(
|
||||
"function_call".to_string(),
|
||||
json!({
|
||||
"name": name,
|
||||
"arguments": arguments,
|
||||
}),
|
||||
);
|
||||
if object.get("content").is_none() {
|
||||
object.insert("content".to_string(), serde_json::Value::Null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
converted.push(message);
|
||||
}
|
||||
Some("tool") => {
|
||||
let tool_call_id = message
|
||||
.get("tool_call_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let name = tool_names
|
||||
.get(tool_call_id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "tool_result".to_string());
|
||||
let content = message
|
||||
.get("content")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
message
|
||||
.get("content")
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default()
|
||||
});
|
||||
converted.push(json!({
|
||||
"role": "function",
|
||||
"name": name,
|
||||
"content": content,
|
||||
}));
|
||||
}
|
||||
_ => converted.push(message),
|
||||
}
|
||||
}
|
||||
|
||||
converted
|
||||
}
|
||||
|
||||
fn direct_messages_for_tool_capability(
|
||||
messages: &[serde_json::Value],
|
||||
use_tools: bool,
|
||||
mode: DirectToolRequestMode,
|
||||
) -> Vec<serde_json::Value> {
|
||||
if use_tools {
|
||||
if mode == DirectToolRequestMode::Tools {
|
||||
return messages.to_vec();
|
||||
}
|
||||
|
||||
|
|
@ -3061,10 +3209,12 @@ fn direct_messages_for_tool_capability(
|
|||
let role = message.get("role").and_then(|v| v.as_str());
|
||||
if role == Some("system") {
|
||||
if let Some(object) = message.as_object_mut() {
|
||||
object.insert(
|
||||
"content".to_string(),
|
||||
json!(direct_provider_no_tools_system_prompt()),
|
||||
);
|
||||
let prompt = if mode == DirectToolRequestMode::Functions {
|
||||
direct_provider_system_prompt()
|
||||
} else {
|
||||
direct_provider_no_tools_system_prompt()
|
||||
};
|
||||
object.insert("content".to_string(), json!(prompt));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -3078,11 +3228,19 @@ fn direct_messages_for_tool_capability(
|
|||
0,
|
||||
json!({
|
||||
"role": "system",
|
||||
"content": direct_provider_no_tools_system_prompt(),
|
||||
"content": if mode == DirectToolRequestMode::Functions {
|
||||
direct_provider_system_prompt()
|
||||
} else {
|
||||
direct_provider_no_tools_system_prompt()
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if mode == DirectToolRequestMode::Functions {
|
||||
return direct_messages_for_legacy_functions(messages);
|
||||
}
|
||||
|
||||
let mut sanitized = Vec::new();
|
||||
for mut message in messages {
|
||||
match message.get("role").and_then(|v| v.as_str()) {
|
||||
|
|
@ -3138,7 +3296,14 @@ async fn send_openai_compatible_chat_request(
|
|||
process_key: &str,
|
||||
) -> Result<DirectChatResponse, String> {
|
||||
match send_openai_compatible_streaming_chat_request(
|
||||
client, credential, messages, window, tab_id, state, process_key, true,
|
||||
client,
|
||||
credential,
|
||||
messages,
|
||||
window,
|
||||
tab_id,
|
||||
state,
|
||||
process_key,
|
||||
DirectToolRequestMode::Tools,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
|
@ -3149,14 +3314,80 @@ async fn send_openai_compatible_chat_request(
|
|||
err.message
|
||||
);
|
||||
match send_openai_compatible_non_streaming_chat_request(
|
||||
client, credential, messages, true,
|
||||
client,
|
||||
credential,
|
||||
messages,
|
||||
DirectToolRequestMode::Tools,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => Ok(response),
|
||||
Err(err) if err.can_retry_without_tools => {
|
||||
eprintln!(
|
||||
"[direct-provider] provider rejected tools, retrying without tools: {}",
|
||||
"[direct-provider] provider rejected modern tools, retrying legacy functions: {}",
|
||||
err.message
|
||||
);
|
||||
send_openai_compatible_with_legacy_functions(
|
||||
client, credential, messages, window, tab_id, state, process_key,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(err) => Err(err.message),
|
||||
}
|
||||
}
|
||||
Err(err) if err.can_retry_without_tools => {
|
||||
eprintln!(
|
||||
"[direct-provider] provider rejected streaming tools, retrying legacy functions: {}",
|
||||
err.message
|
||||
);
|
||||
send_openai_compatible_with_legacy_functions(
|
||||
client, credential, messages, window, tab_id, state, process_key,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(err) => Err(err.message),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_openai_compatible_with_legacy_functions(
|
||||
client: &reqwest::Client,
|
||||
credential: &StoredOpenAiCompatibleCredential,
|
||||
messages: &[serde_json::Value],
|
||||
window: &WebviewWindow,
|
||||
tab_id: &str,
|
||||
state: &ClaudeProcessState,
|
||||
process_key: &str,
|
||||
) -> Result<DirectChatResponse, String> {
|
||||
match send_openai_compatible_streaming_chat_request(
|
||||
client,
|
||||
credential,
|
||||
messages,
|
||||
window,
|
||||
tab_id,
|
||||
state,
|
||||
process_key,
|
||||
DirectToolRequestMode::Functions,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => Ok(response),
|
||||
Err(err) if err.can_retry_non_streaming => {
|
||||
eprintln!(
|
||||
"[direct-provider] legacy function streaming failed, retrying non-streaming: {}",
|
||||
err.message
|
||||
);
|
||||
match send_openai_compatible_non_streaming_chat_request(
|
||||
client,
|
||||
credential,
|
||||
messages,
|
||||
DirectToolRequestMode::Functions,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => Ok(response),
|
||||
Err(err) if err.can_retry_without_tools => {
|
||||
eprintln!(
|
||||
"[direct-provider] provider rejected legacy functions, retrying without tools: {}",
|
||||
err.message
|
||||
);
|
||||
send_openai_compatible_without_tools(
|
||||
|
|
@ -3169,7 +3400,7 @@ async fn send_openai_compatible_chat_request(
|
|||
}
|
||||
Err(err) if err.can_retry_without_tools => {
|
||||
eprintln!(
|
||||
"[direct-provider] provider rejected streaming tools, retrying without tools: {}",
|
||||
"[direct-provider] provider rejected legacy function streaming, retrying without tools: {}",
|
||||
err.message
|
||||
);
|
||||
send_openai_compatible_without_tools(
|
||||
|
|
@ -3191,15 +3422,27 @@ async fn send_openai_compatible_without_tools(
|
|||
process_key: &str,
|
||||
) -> Result<DirectChatResponse, String> {
|
||||
match send_openai_compatible_streaming_chat_request(
|
||||
client, credential, messages, window, tab_id, state, process_key, false,
|
||||
client,
|
||||
credential,
|
||||
messages,
|
||||
window,
|
||||
tab_id,
|
||||
state,
|
||||
process_key,
|
||||
DirectToolRequestMode::None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => Ok(response),
|
||||
Err(err) if err.can_retry_non_streaming => {
|
||||
send_openai_compatible_non_streaming_chat_request(client, credential, messages, false)
|
||||
.await
|
||||
.map_err(|err| err.message)
|
||||
send_openai_compatible_non_streaming_chat_request(
|
||||
client,
|
||||
credential,
|
||||
messages,
|
||||
DirectToolRequestMode::None,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| err.message)
|
||||
}
|
||||
Err(err) => Err(err.message),
|
||||
}
|
||||
|
|
@ -3214,15 +3457,15 @@ async fn send_openai_compatible_non_streaming_chat_request(
|
|||
client: &reqwest::Client,
|
||||
credential: &StoredOpenAiCompatibleCredential,
|
||||
messages: &[serde_json::Value],
|
||||
use_tools: bool,
|
||||
mode: DirectToolRequestMode,
|
||||
) -> Result<DirectChatResponse, DirectProviderRequestFailure> {
|
||||
let request_messages = direct_messages_for_tool_capability(messages, use_tools);
|
||||
let request_messages = direct_messages_for_tool_capability(messages, mode);
|
||||
let mut request_body = json!({
|
||||
"model": credential.model.clone(),
|
||||
"messages": request_messages,
|
||||
"stream": false,
|
||||
});
|
||||
add_direct_request_tooling(&mut request_body, use_tools);
|
||||
add_direct_request_tooling(&mut request_body, mode);
|
||||
|
||||
let response = client
|
||||
.post(openai_chat_completions_url(&credential.base_url))
|
||||
|
|
@ -3269,15 +3512,15 @@ async fn send_openai_compatible_streaming_chat_request(
|
|||
tab_id: &str,
|
||||
state: &ClaudeProcessState,
|
||||
process_key: &str,
|
||||
use_tools: bool,
|
||||
mode: DirectToolRequestMode,
|
||||
) -> Result<DirectChatResponse, DirectStreamFailure> {
|
||||
let request_messages = direct_messages_for_tool_capability(messages, use_tools);
|
||||
let request_messages = direct_messages_for_tool_capability(messages, mode);
|
||||
let mut request_body = json!({
|
||||
"model": credential.model.clone(),
|
||||
"messages": request_messages,
|
||||
"stream": true,
|
||||
});
|
||||
add_direct_request_tooling(&mut request_body, use_tools);
|
||||
add_direct_request_tooling(&mut request_body, mode);
|
||||
|
||||
let mut response = client
|
||||
.post(openai_chat_completions_url(&credential.base_url))
|
||||
|
|
@ -3391,6 +3634,21 @@ async fn send_openai_compatible_streaming_chat_request(
|
|||
}
|
||||
}
|
||||
}
|
||||
if let Some(function_call) = delta.get("function_call") {
|
||||
let entry = tool_calls.entry(0).or_default();
|
||||
entry.legacy_function_call = true;
|
||||
if entry.id.is_none() {
|
||||
entry.id = Some("function_call_1".to_string());
|
||||
}
|
||||
if let Some(name) = function_call.get("name").and_then(|v| v.as_str()) {
|
||||
entry.name.push_str(name);
|
||||
}
|
||||
if let Some(arguments) =
|
||||
function_call.get("arguments").and_then(|v| v.as_str())
|
||||
{
|
||||
entry.arguments.push_str(arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3593,11 +3851,19 @@ async fn execute_openai_compatible_provider(
|
|||
"content": output.content,
|
||||
"is_error": output.is_error,
|
||||
}));
|
||||
let tool_message = json!({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": output.content,
|
||||
});
|
||||
let tool_message = if tool_call.legacy_function_call {
|
||||
json!({
|
||||
"role": "function",
|
||||
"name": tool_call.name,
|
||||
"content": output.content,
|
||||
})
|
||||
} else {
|
||||
json!({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": output.content,
|
||||
})
|
||||
};
|
||||
request_messages.push(tool_message.clone());
|
||||
messages.push(tool_message);
|
||||
}
|
||||
|
|
@ -4551,6 +4817,30 @@ mod tests {
|
|||
assert_eq!(calls[1].input["pattern"], "Intro");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direct_provider_parses_legacy_function_call() {
|
||||
let response = json!({
|
||||
"choices": [{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": null,
|
||||
"function_call": {
|
||||
"name": "Read",
|
||||
"arguments": "{\"file_path\":\"main.tex\"}"
|
||||
}
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
let calls = parse_direct_tool_calls(&response);
|
||||
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert_eq!(calls[0].id, "function_call_1");
|
||||
assert_eq!(calls[0].name, "Read");
|
||||
assert_eq!(calls[0].input["file_path"], "main.tex");
|
||||
assert!(calls[0].legacy_function_call);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direct_provider_reconstructs_streamed_tool_calls() {
|
||||
let mut calls = HashMap::new();
|
||||
|
|
@ -4560,6 +4850,7 @@ mod tests {
|
|||
id: Some("call-1".to_string()),
|
||||
name: "Read".to_string(),
|
||||
arguments: "{\"file_path\":\"main.tex\"}".to_string(),
|
||||
legacy_function_call: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -4574,6 +4865,32 @@ mod tests {
|
|||
assert_eq!(message["tool_calls"][0]["function"]["name"], "Read");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direct_provider_reconstructs_streamed_legacy_function_call() {
|
||||
let mut calls = HashMap::new();
|
||||
calls.insert(
|
||||
0,
|
||||
DirectStreamingToolCall {
|
||||
id: Some("function_call_1".to_string()),
|
||||
name: "Read".to_string(),
|
||||
arguments: "{\"file_path\":\"main.tex\"}".to_string(),
|
||||
legacy_function_call: true,
|
||||
},
|
||||
);
|
||||
|
||||
let calls = direct_tool_calls_from_stream(calls);
|
||||
let message = direct_message_from_parts("", "", &calls);
|
||||
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert!(calls[0].legacy_function_call);
|
||||
assert!(message.get("tool_calls").is_none());
|
||||
assert_eq!(message["function_call"]["name"], "Read");
|
||||
assert_eq!(
|
||||
message["function_call"]["arguments"],
|
||||
"{\"file_path\":\"main.tex\"}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direct_provider_extracts_reasoning_content() {
|
||||
let response = json!({
|
||||
|
|
@ -4627,7 +4944,7 @@ mod tests {
|
|||
json!({ "role": "user", "content": "Please edit main.tex" }),
|
||||
];
|
||||
|
||||
let no_tools = direct_messages_for_tool_capability(&messages, false);
|
||||
let no_tools = direct_messages_for_tool_capability(&messages, DirectToolRequestMode::None);
|
||||
let content = no_tools[0]["content"].as_str().unwrap();
|
||||
|
||||
assert!(content.contains("does not support tool calls"));
|
||||
|
|
@ -4651,7 +4968,7 @@ mod tests {
|
|||
json!({ "role": "tool", "tool_call_id": "call-1", "content": "main.tex contents" }),
|
||||
];
|
||||
|
||||
let no_tools = direct_messages_for_tool_capability(&messages, false);
|
||||
let no_tools = direct_messages_for_tool_capability(&messages, DirectToolRequestMode::None);
|
||||
|
||||
assert!(no_tools[1].get("tool_calls").is_none());
|
||||
assert_eq!(no_tools[1]["role"], "assistant");
|
||||
|
|
@ -4662,6 +4979,32 @@ mod tests {
|
|||
.contains("Previous tool result call-1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direct_provider_legacy_function_messages_convert_tool_history() {
|
||||
let messages = vec![
|
||||
json!({ "role": "system", "content": direct_provider_system_prompt() }),
|
||||
json!({
|
||||
"role": "assistant",
|
||||
"content": null,
|
||||
"tool_calls": [{
|
||||
"id": "call-1",
|
||||
"type": "function",
|
||||
"function": { "name": "Read", "arguments": "{\"file_path\":\"main.tex\"}" }
|
||||
}]
|
||||
}),
|
||||
json!({ "role": "tool", "tool_call_id": "call-1", "content": "main.tex contents" }),
|
||||
];
|
||||
|
||||
let function_messages =
|
||||
direct_messages_for_tool_capability(&messages, DirectToolRequestMode::Functions);
|
||||
|
||||
assert!(function_messages[1].get("tool_calls").is_none());
|
||||
assert_eq!(function_messages[1]["function_call"]["name"], "Read");
|
||||
assert_eq!(function_messages[2]["role"], "function");
|
||||
assert_eq!(function_messages[2]["name"], "Read");
|
||||
assert_eq!(function_messages[2]["content"], "main.tex contents");
|
||||
}
|
||||
|
||||
// --- claude_required_dirs ---
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue