mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 16:37:46 +08:00
fix(ai): align Gemini and Responses compatibility
This commit is contained in:
@@ -4670,8 +4670,7 @@ async fn gateway_hydrates_antigravity_project_id_from_load_code_assist_for_test_
|
|||||||
.lock()
|
.lock()
|
||||||
.expect("mutex should lock")
|
.expect("mutex should lock")
|
||||||
.push(plan.url.clone());
|
.push(plan.url.clone());
|
||||||
if plan.url == "https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
|
if plan.url == "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist" {
|
||||||
{
|
|
||||||
assert_eq!(plan.model_name.as_deref(), Some("loadCodeAssist"));
|
assert_eq!(plan.model_name.as_deref(), Some("loadCodeAssist"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plan.headers.get("authorization").map(String::as_str),
|
plan.headers.get("authorization").map(String::as_str),
|
||||||
@@ -4827,7 +4826,7 @@ async fn gateway_hydrates_antigravity_project_id_from_load_code_assist_for_test_
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
*seen_urls.lock().expect("mutex should lock"),
|
*seen_urls.lock().expect("mutex should lock"),
|
||||||
vec![
|
vec![
|
||||||
"https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist".to_string(),
|
"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist".to_string(),
|
||||||
"https://daily-cloudcode-pa.googleapis.com/v1internal:generateContent".to_string(),
|
"https://daily-cloudcode-pa.googleapis.com/v1internal:generateContent".to_string(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ use crate::{
|
|||||||
gemini_generation_config_extra, gemini_google_search_grounding,
|
gemini_generation_config_extra, gemini_google_search_grounding,
|
||||||
gemini_response_format_to_canonical, gemini_system_to_canonical_instructions,
|
gemini_response_format_to_canonical, gemini_system_to_canonical_instructions,
|
||||||
gemini_thinking_to_canonical, gemini_tool_choice_to_canonical, gemini_tools_to_canonical,
|
gemini_thinking_to_canonical, gemini_tool_choice_to_canonical, gemini_tools_to_canonical,
|
||||||
gemini_value_by_case, CanonicalContentBlock, CanonicalMessage, CanonicalRequest,
|
gemini_value_by_case, is_cross_format_tool_result, CanonicalContentBlock, CanonicalMessage,
|
||||||
CanonicalResponseFormat, CanonicalRole, CanonicalToolChoice, CanonicalToolDefinition,
|
CanonicalRequest, CanonicalResponseFormat, CanonicalRole, CanonicalToolChoice,
|
||||||
OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
CanonicalToolDefinition, OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -244,13 +244,51 @@ fn canonical_system_instruction(canonical: &CanonicalRequest) -> Option<Value> {
|
|||||||
fn canonical_messages_to_gemini_contents(messages: &[CanonicalMessage]) -> Option<Vec<Value>> {
|
fn canonical_messages_to_gemini_contents(messages: &[CanonicalMessage]) -> Option<Vec<Value>> {
|
||||||
let mut contents = Vec::new();
|
let mut contents = Vec::new();
|
||||||
let mut tool_name_by_id = BTreeMap::new();
|
let mut tool_name_by_id = BTreeMap::new();
|
||||||
for message in messages {
|
let mut pending_tool_use_ids = Vec::new();
|
||||||
let role = match message.role {
|
let mut message_index = 0;
|
||||||
|
while message_index < messages.len() {
|
||||||
|
let role = match messages[message_index].role {
|
||||||
CanonicalRole::Assistant => "model",
|
CanonicalRole::Assistant => "model",
|
||||||
CanonicalRole::System | CanonicalRole::Developer => continue,
|
CanonicalRole::System | CanonicalRole::Developer => {
|
||||||
|
message_index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
CanonicalRole::Tool | CanonicalRole::User | CanonicalRole::Unknown => "user",
|
CanonicalRole::Tool | CanonicalRole::User | CanonicalRole::Unknown => "user",
|
||||||
};
|
};
|
||||||
let parts = canonical_blocks_to_gemini_parts(&message.content, &mut tool_name_by_id)?;
|
let mut blocks = Vec::new();
|
||||||
|
while message_index < messages.len() {
|
||||||
|
let next_role = match messages[message_index].role {
|
||||||
|
CanonicalRole::Assistant => Some("model"),
|
||||||
|
CanonicalRole::Tool | CanonicalRole::User | CanonicalRole::Unknown => Some("user"),
|
||||||
|
CanonicalRole::System | CanonicalRole::Developer => None,
|
||||||
|
};
|
||||||
|
match next_role {
|
||||||
|
Some(next_role) if next_role == role => {
|
||||||
|
blocks.extend(messages[message_index].content.iter());
|
||||||
|
message_index += 1;
|
||||||
|
}
|
||||||
|
None => message_index += 1,
|
||||||
|
Some(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let blocks = if role == "user" {
|
||||||
|
let aligned = align_gemini_tool_results(blocks, &pending_tool_use_ids);
|
||||||
|
pending_tool_use_ids.clear();
|
||||||
|
aligned
|
||||||
|
} else {
|
||||||
|
pending_tool_use_ids = blocks
|
||||||
|
.iter()
|
||||||
|
.filter_map(|block| match block {
|
||||||
|
CanonicalContentBlock::ToolUse { id, .. } if !id.trim().is_empty() => {
|
||||||
|
Some(id.clone())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
blocks
|
||||||
|
};
|
||||||
|
let parts = canonical_blocks_to_gemini_parts(&blocks, &mut tool_name_by_id)?;
|
||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -262,8 +300,74 @@ fn canonical_messages_to_gemini_contents(messages: &[CanonicalMessage]) -> Optio
|
|||||||
Some(contents)
|
Some(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn align_gemini_tool_results<'a>(
|
||||||
|
blocks: Vec<&'a CanonicalContentBlock>,
|
||||||
|
pending_tool_use_ids: &[String],
|
||||||
|
) -> Vec<&'a CanonicalContentBlock> {
|
||||||
|
if pending_tool_use_ids.is_empty() {
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result_indexes = blocks
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(index, block)| match block {
|
||||||
|
CanonicalContentBlock::ToolResult { extensions, .. }
|
||||||
|
if is_cross_format_tool_result(extensions) =>
|
||||||
|
{
|
||||||
|
Some(index)
|
||||||
|
}
|
||||||
|
CanonicalContentBlock::ToolResult { .. } => None,
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if result_indexes.len() != pending_tool_use_ids.len()
|
||||||
|
|| blocks
|
||||||
|
.iter()
|
||||||
|
.filter(|block| matches!(block, CanonicalContentBlock::ToolResult { .. }))
|
||||||
|
.count()
|
||||||
|
!= result_indexes.len()
|
||||||
|
{
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ordered = Vec::with_capacity(blocks.len());
|
||||||
|
let mut used = vec![false; result_indexes.len()];
|
||||||
|
for pending_id in pending_tool_use_ids {
|
||||||
|
if pending_id.trim().is_empty() {
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
let Some((result_position, block_index)) =
|
||||||
|
result_indexes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(result_position, block_index)| {
|
||||||
|
if used[*result_position] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
matches!(
|
||||||
|
blocks[**block_index],
|
||||||
|
CanonicalContentBlock::ToolResult { ref tool_use_id, .. }
|
||||||
|
if tool_use_id == pending_id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return blocks;
|
||||||
|
};
|
||||||
|
used[result_position] = true;
|
||||||
|
ordered.push(blocks[*block_index]);
|
||||||
|
}
|
||||||
|
ordered.extend(
|
||||||
|
blocks
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|block| !matches!(block, CanonicalContentBlock::ToolResult { .. })),
|
||||||
|
);
|
||||||
|
ordered
|
||||||
|
}
|
||||||
|
|
||||||
fn canonical_blocks_to_gemini_parts(
|
fn canonical_blocks_to_gemini_parts(
|
||||||
blocks: &[CanonicalContentBlock],
|
blocks: &[&CanonicalContentBlock],
|
||||||
tool_name_by_id: &mut BTreeMap<String, String>,
|
tool_name_by_id: &mut BTreeMap<String, String>,
|
||||||
) -> Option<Vec<Value>> {
|
) -> Option<Vec<Value>> {
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
|
|||||||
@@ -1367,19 +1367,21 @@ fn mapped_namespace_request_extensions(
|
|||||||
source,
|
source,
|
||||||
FormatId::OpenAiResponses | FormatId::OpenAiResponsesCompact
|
FormatId::OpenAiResponses | FormatId::OpenAiResponsesCompact
|
||||||
) || target != FormatId::OpenAiChat
|
) || target != FormatId::OpenAiChat
|
||||||
|| !openai_chat::request::raw_tool_choice_extension_is_representable_for_openai_chat(
|
|
||||||
request,
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let remove_tool_choice =
|
||||||
|
openai_chat::request::raw_tool_choice_extension_is_representable_for_openai_chat(request);
|
||||||
for provider_namespace in ["openai_responses", "openai_cli"] {
|
for provider_namespace in ["openai_responses", "openai_cli"] {
|
||||||
let should_remove_namespace = mapped
|
let should_remove_namespace = mapped
|
||||||
.get_mut(provider_namespace)
|
.get_mut(provider_namespace)
|
||||||
.and_then(Value::as_object_mut)
|
.and_then(Value::as_object_mut)
|
||||||
.is_some_and(|fields| {
|
.is_some_and(|fields| {
|
||||||
fields.remove("tool_choice");
|
fields.remove("include");
|
||||||
|
if remove_tool_choice {
|
||||||
|
fields.remove("tool_choice");
|
||||||
|
}
|
||||||
fields.is_empty()
|
fields.is_empty()
|
||||||
});
|
});
|
||||||
if should_remove_namespace {
|
if should_remove_namespace {
|
||||||
@@ -2517,7 +2519,6 @@ fn validate_openai_responses_to_chat(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
for field in [
|
for field in [
|
||||||
"include",
|
|
||||||
"previous_response_id",
|
"previous_response_id",
|
||||||
"truncation",
|
"truncation",
|
||||||
"prompt",
|
"prompt",
|
||||||
@@ -4241,6 +4242,123 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pure_openai_chat_to_gemini_preserves_json_tool_output_as_string() {
|
||||||
|
let body = json!({
|
||||||
|
"model": "gpt-source",
|
||||||
|
"messages": [{
|
||||||
|
"role": "assistant",
|
||||||
|
"tool_calls": [{
|
||||||
|
"id": "call_1",
|
||||||
|
"type": "function",
|
||||||
|
"function": {"name": "read_file", "arguments": "{}"}
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": "call_1",
|
||||||
|
"content": "{\"key\":\"value\",\"items\":[1,2,3]}"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
let converted = convert_request_pure("openai:chat", "gemini:generate_content", &body)
|
||||||
|
.expect("pure conversion should succeed")
|
||||||
|
.value;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
converted["contents"][1]["parts"][0]["functionResponse"]["response"]["result"],
|
||||||
|
"{\"key\":\"value\",\"items\":[1,2,3]}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pure_openai_responses_to_gemini_aligns_parallel_tool_outputs() {
|
||||||
|
let body = json!({
|
||||||
|
"model": "gpt-source",
|
||||||
|
"input": [{
|
||||||
|
"type": "function_call",
|
||||||
|
"call_id": "call_1",
|
||||||
|
"name": "read_file",
|
||||||
|
"arguments": "{\"path\":\"one\"}"
|
||||||
|
}, {
|
||||||
|
"type": "function_call",
|
||||||
|
"call_id": "call_2",
|
||||||
|
"name": "read_file",
|
||||||
|
"arguments": "{\"path\":\"two\"}"
|
||||||
|
}, {
|
||||||
|
"type": "function_call_output",
|
||||||
|
"call_id": "call_2",
|
||||||
|
"output": "two"
|
||||||
|
}, {
|
||||||
|
"type": "function_call_output",
|
||||||
|
"call_id": "call_1",
|
||||||
|
"output": "one"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
let converted = convert_request_pure("openai:responses", "gemini:generate_content", &body)
|
||||||
|
.expect("pure conversion should succeed")
|
||||||
|
.value;
|
||||||
|
let parts = converted["contents"][1]["parts"]
|
||||||
|
.as_array()
|
||||||
|
.expect("tool response parts");
|
||||||
|
|
||||||
|
assert_eq!(parts[0]["functionResponse"]["id"], "call_1");
|
||||||
|
assert_eq!(parts[0]["functionResponse"]["response"]["result"], "one");
|
||||||
|
assert_eq!(parts[1]["functionResponse"]["id"], "call_2");
|
||||||
|
assert_eq!(parts[1]["functionResponse"]["response"]["result"], "two");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pure_claude_to_gemini_aligns_parallel_tool_results_before_text() {
|
||||||
|
let body = json!({
|
||||||
|
"model": "claude-source",
|
||||||
|
"max_tokens": 64,
|
||||||
|
"messages": [{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": "call_1",
|
||||||
|
"name": "read_file",
|
||||||
|
"input": {"path": "one"}
|
||||||
|
}, {
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": "call_2",
|
||||||
|
"name": "read_file",
|
||||||
|
"input": {"path": "two"}
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"role": "user",
|
||||||
|
"content": [{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Results follow."
|
||||||
|
}, {
|
||||||
|
"type": "tool_result",
|
||||||
|
"tool_use_id": "call_2",
|
||||||
|
"content": "two"
|
||||||
|
}, {
|
||||||
|
"type": "tool_result",
|
||||||
|
"tool_use_id": "call_1",
|
||||||
|
"content": "one"
|
||||||
|
}, {
|
||||||
|
"type": "text",
|
||||||
|
"text": "Continue."
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
let converted = convert_request_pure("claude:messages", "gemini:generate_content", &body)
|
||||||
|
.expect("pure conversion should succeed")
|
||||||
|
.value;
|
||||||
|
let parts = converted["contents"][1]["parts"]
|
||||||
|
.as_array()
|
||||||
|
.expect("tool response parts");
|
||||||
|
|
||||||
|
assert_eq!(parts[0]["functionResponse"]["id"], "call_1");
|
||||||
|
assert_eq!(parts[1]["functionResponse"]["id"], "call_2");
|
||||||
|
assert_eq!(parts[2]["text"], "Results follow.");
|
||||||
|
assert_eq!(parts[3]["text"], "Continue.");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pure_gemini_to_openai_responses_blocks_thought_part_loss() {
|
fn pure_gemini_to_openai_responses_blocks_thought_part_loss() {
|
||||||
let body = json!({
|
let body = json!({
|
||||||
@@ -4437,20 +4555,23 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pure_openai_responses_to_chat_blocks_responses_only_fields() {
|
fn pure_openai_responses_to_chat_drops_include() {
|
||||||
let body = json!({
|
let body = json!({
|
||||||
"model": "gpt-source",
|
"model": "gpt-source",
|
||||||
"input": [{"role": "user", "content": "hello"}],
|
"input": [{"role": "user", "content": "hello"}],
|
||||||
"include": ["reasoning.encrypted_content"]
|
"include": [
|
||||||
|
"reasoning.encrypted_content",
|
||||||
|
"file_search_call.results"
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
let error = convert_request_pure("openai:responses", "openai:chat", &body)
|
let converted = convert_request_pure("openai:responses", "openai:chat", &body)
|
||||||
.expect_err("lossy field should fail closed");
|
.expect("Responses include should be safely omitted for Chat")
|
||||||
|
.value;
|
||||||
|
|
||||||
assert!(matches!(
|
assert_eq!(converted["model"], "gpt-source");
|
||||||
error,
|
assert_eq!(converted["messages"][0]["content"], "hello");
|
||||||
super::FormatError::LossyConversionBlocked { ref field, .. } if field == "include"
|
assert!(converted.get("include").is_none());
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -6147,6 +6268,29 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runtime_openai_responses_to_chat_drops_include() {
|
||||||
|
let body = json!({
|
||||||
|
"model": "gpt-source",
|
||||||
|
"input": [{"role": "user", "content": "hello"}],
|
||||||
|
"include": ["reasoning.encrypted_content"],
|
||||||
|
"stream": true
|
||||||
|
});
|
||||||
|
|
||||||
|
let converted = convert_request(
|
||||||
|
"openai:responses",
|
||||||
|
"openai:chat",
|
||||||
|
&body,
|
||||||
|
&FormatContext::default().with_upstream_stream(true),
|
||||||
|
)
|
||||||
|
.expect("runtime conversion should safely omit Responses include");
|
||||||
|
|
||||||
|
assert_eq!(converted["model"], "gpt-source");
|
||||||
|
assert_eq!(converted["messages"][0]["content"], "hello");
|
||||||
|
assert_eq!(converted["stream"], true);
|
||||||
|
assert!(converted.get("include").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn converts_openai_embedding_to_jina_without_chat_fields() {
|
fn converts_openai_embedding_to_jina_without_chat_fields() {
|
||||||
let body = json!({
|
let body = json!({
|
||||||
|
|||||||
@@ -1410,6 +1410,14 @@ mod tests {
|
|||||||
assert!(!input_json.contains("sig_123"));
|
assert!(!input_json.contains("sig_123"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if provider_api_format == "gemini:generate_content" {
|
||||||
|
assert_eq!(
|
||||||
|
converted["contents"][2]["parts"][0]["functionResponse"]["response"]
|
||||||
|
["result"],
|
||||||
|
json!({"ok": true})
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let legacy =
|
let legacy =
|
||||||
legacy_claude_request_body(&request, provider_api_format, upstream_is_stream);
|
legacy_claude_request_body(&request, provider_api_format, upstream_is_stream);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -1817,13 +1817,7 @@ pub(crate) fn openai_message_content_blocks(
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
name: None,
|
name: None,
|
||||||
output: match message.get("content") {
|
output: message.get("content").cloned(),
|
||||||
Some(Value::String(raw)) => serde_json::from_str::<Value>(raw)
|
|
||||||
.ok()
|
|
||||||
.or_else(|| Some(Value::String(raw.clone()))),
|
|
||||||
Some(value) => Some(value.clone()),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
content_text: Some(if text.is_empty() {
|
content_text: Some(if text.is_empty() {
|
||||||
message
|
message
|
||||||
.get("content")
|
.get("content")
|
||||||
@@ -2114,7 +2108,7 @@ pub(crate) fn openai_responses_input_to_canonical_messages(
|
|||||||
generated
|
generated
|
||||||
});
|
});
|
||||||
let raw_output = item_object.get("output");
|
let raw_output = item_object.get("output");
|
||||||
let output = Some(parse_jsonish_value(raw_output));
|
let output = Some(raw_output.cloned().unwrap_or_else(|| json!({})));
|
||||||
let mut extensions = openai_responses_extensions(
|
let mut extensions = openai_responses_extensions(
|
||||||
item_object,
|
item_object,
|
||||||
&[
|
&[
|
||||||
@@ -2550,7 +2544,7 @@ pub(crate) fn openai_responses_output_to_canonical(
|
|||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.unwrap_or_else(|| format!("call_auto_{index}"));
|
.unwrap_or_else(|| format!("call_auto_{index}"));
|
||||||
let raw_output = item_object.get("output");
|
let raw_output = item_object.get("output");
|
||||||
let output = Some(parse_jsonish_value(raw_output));
|
let output = Some(raw_output.cloned().unwrap_or_else(|| json!({})));
|
||||||
let mut extensions = openai_responses_extensions(
|
let mut extensions = openai_responses_extensions(
|
||||||
item_object,
|
item_object,
|
||||||
&[
|
&[
|
||||||
@@ -3504,6 +3498,12 @@ pub(crate) fn is_claude_tool_result(extensions: &BTreeMap<String, Value>) -> boo
|
|||||||
== Some(CLAUDE_TOOL_RESULT_SOURCE_MARKER)
|
== Some(CLAUDE_TOOL_RESULT_SOURCE_MARKER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_cross_format_tool_result(extensions: &BTreeMap<String, Value>) -> bool {
|
||||||
|
is_claude_tool_result(extensions)
|
||||||
|
|| is_openai_chat_tool_result(extensions)
|
||||||
|
|| is_openai_responses_tool_result(extensions)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_openai_responses_tool_result(extensions: &BTreeMap<String, Value>) -> bool {
|
fn is_openai_responses_tool_result(extensions: &BTreeMap<String, Value>) -> bool {
|
||||||
extensions
|
extensions
|
||||||
.get(AETHER_EXTENSION_NAMESPACE)
|
.get(AETHER_EXTENSION_NAMESPACE)
|
||||||
|
|||||||
@@ -2631,14 +2631,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn antigravity_model_fetch_hydrates_project_from_daily_load_code_assist() {
|
async fn antigravity_model_fetch_hydrates_project_from_prod_load_code_assist() {
|
||||||
let executed_urls = Arc::new(Mutex::new(Vec::new()));
|
let executed_urls = Arc::new(Mutex::new(Vec::new()));
|
||||||
let runtime = OAuthRoutingTestRuntime {
|
let runtime = OAuthRoutingTestRuntime {
|
||||||
executed_urls: Arc::clone(&executed_urls),
|
executed_urls: Arc::clone(&executed_urls),
|
||||||
routes: vec![
|
routes: vec![
|
||||||
(
|
(
|
||||||
"https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
|
"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist".to_string(),
|
||||||
.to_string(),
|
|
||||||
Ok((
|
Ok((
|
||||||
200,
|
200,
|
||||||
json!({
|
json!({
|
||||||
@@ -2679,7 +2678,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
urls.as_slice(),
|
urls.as_slice(),
|
||||||
&[
|
&[
|
||||||
"https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist",
|
"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist",
|
||||||
"https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels",
|
"https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ pub async fn build_antigravity_load_code_assist_plan(
|
|||||||
transport,
|
transport,
|
||||||
ModelFetchExecutionPlanRequest {
|
ModelFetchExecutionPlanRequest {
|
||||||
method: "POST".to_string(),
|
method: "POST".to_string(),
|
||||||
url: "https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist".to_string(),
|
url: "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist".to_string(),
|
||||||
headers,
|
headers,
|
||||||
content_type: Some("application/json".to_string()),
|
content_type: Some("application/json".to_string()),
|
||||||
body: RequestBody::from_json(json!({
|
body: RequestBody::from_json(json!({
|
||||||
@@ -1212,7 +1212,7 @@ mod tests {
|
|||||||
assert_eq!(plan.method, "POST");
|
assert_eq!(plan.method, "POST");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plan.url,
|
plan.url,
|
||||||
"https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
|
"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plan.headers.get("authorization").map(String::as_str),
|
plan.headers.get("authorization").map(String::as_str),
|
||||||
|
|||||||
Reference in New Issue
Block a user