Files
Aether/apps/aether-gateway/src/ai_serving/finalize/tests_stream.rs
T

1165 lines
53 KiB
Rust
Raw Normal View History

use serde_json::json;
2026-05-02 13:23:54 +08:00
use crate::ai_serving::maybe_bridge_standard_sync_json_to_stream;
use super::maybe_build_local_stream_rewriter;
fn utf8(bytes: Vec<u8>) -> String {
String::from_utf8(bytes).expect("utf8 should decode")
}
#[test]
fn same_format_claude_local_stream_rewriter_sanitizes_read_input_json_delta() {
let report_context = json!({
"provider_api_format": "claude:messages",
"client_api_format": "claude:messages",
"anthropic_compatibility_profile": "claude_code_legacy",
"needs_conversion": false,
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let mut output = rewriter
.push_chunk(
b"event: content_block_start\n\
data: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"call_read_1\",\"name\":\"Read\",\"input\":{}}}\n\n",
)
.expect("start should be accepted");
output.extend(
rewriter
.push_chunk(
b"event: content_block_delta\n\
data: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"file_path\\\":\\\"/tmp/a.txt\\\",\\\"pages\\\":\\\"\\\"}\"}}\n\n",
)
.expect("delta should be accepted"),
);
output.extend(
rewriter
.push_chunk(
b"event: content_block_stop\n\
data: {\"type\":\"content_block_stop\",\"index\":0}\n\n",
)
.expect("stop should flush sanitized delta"),
);
let output_text = utf8(output);
assert!(output_text.contains("\"name\":\"Read\""));
assert!(output_text.contains("\\\"file_path\\\":\\\"/tmp/a.txt\\\""));
assert!(!output_text.contains("\\\"pages\\\":\\\"\\\""));
}
#[test]
fn standard_sync_bridge_converts_openai_chat_sync_json_to_openai_chat_sse() {
let outcome = maybe_bridge_standard_sync_json_to_stream(
&json!({
"id": "chatcmpl_sync_123",
"object": "chat.completion",
"model": "gpt-5.4",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello from sync bridge"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 1,
"completion_tokens": 2,
"total_tokens": 3
}
}),
"openai:chat",
"openai:chat",
None,
)
.expect("bridge should succeed")
.expect("bridge should produce sse");
let output_text = utf8(outcome.sse_body);
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"role\":\"assistant\""));
assert!(output_text.contains("\"content\":\"Hello from sync bridge\""));
assert!(output_text.contains("\"finish_reason\":\"stop\""));
assert!(output_text.contains("data: [DONE]"));
let summary = outcome
.terminal_summary
.expect("terminal summary should exist");
assert_eq!(summary.response_id.as_deref(), Some("resp_sync_123"));
assert_eq!(summary.model.as_deref(), Some("gpt-5.4"));
assert_eq!(summary.finish_reason.as_deref(), Some("stop"));
assert_eq!(
summary
.standardized_usage
.as_ref()
.and_then(|usage| usage.dimensions.get("total_tokens"))
.cloned(),
Some(json!(3))
);
}
#[test]
fn standard_sync_bridge_converts_claude_sync_json_to_openai_responses_sse() {
let report_context = json!({
"mapped_model": "claude-sonnet-4-5",
});
let outcome = maybe_bridge_standard_sync_json_to_stream(
&json!({
"id": "msg_sync_123",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-5",
"content": [{
"type": "text",
"text": "Hello from Claude sync"
}],
"stop_reason": "end_turn",
"usage": {
"input_tokens": 2,
"output_tokens": 3
}
}),
2026-04-29 09:25:19 +08:00
"claude:messages",
"openai:responses",
Some(&report_context),
)
.expect("bridge should succeed")
.expect("bridge should produce sse");
let output_text = utf8(outcome.sse_body);
assert!(output_text.contains("event: response.created"));
assert!(output_text.contains("event: response.output_text.delta"));
assert!(output_text.contains("event: response.completed"));
assert!(output_text.contains("\"text\":\"Hello from Claude sync\""));
let summary = outcome
.terminal_summary
.expect("terminal summary should exist");
assert_eq!(summary.response_id.as_deref(), Some("msg_sync_123"));
assert_eq!(summary.model.as_deref(), Some("claude-sonnet-4-5"));
assert_eq!(summary.finish_reason.as_deref(), Some("stop"));
assert_eq!(
summary
.standardized_usage
.as_ref()
.and_then(|usage| usage.dimensions.get("total_tokens"))
.cloned(),
Some(json!(5))
);
}
#[test]
fn antigravity_stream_rewriter_unwraps_and_injects_tool_ids() {
let report_context = json!({
"has_envelope": true,
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "gemini:generate_content",
"envelope_name": "antigravity:v1internal",
"needs_conversion": false,
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"response\":{\"candidates\":[{\"content\":{\"parts\":[{\"functionCall\":{\"name\":\"get_weather\",\"args\":{\"city\":\"SF\"}}}],\"role\":\"model\"},\"index\":0}],\"modelVersion\":\"claude-sonnet-4-5\"},\"responseId\":\"resp_123\"}\n\n",
)
.expect("rewrite should succeed");
let output_text = String::from_utf8(output).expect("text should be utf8");
assert!(output_text.contains("\"_v1internal_response_id\":\"resp_123\""));
assert!(output_text.contains("\"id\":\"call_get_weather_0\""));
assert!(output_text.contains("\"modelVersion\":\"claude-sonnet-4-5\""));
}
#[test]
fn openai_image_stream_rewriter_emits_completed_event_for_generate() {
let report_context = json!({
"provider_api_format": "openai:image",
"client_api_format": "openai:image",
"needs_conversion": false,
"image_request": {
"operation": "generate"
}
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let first = rewriter
.push_chunk(
concat!(
"event: response.output_item.done\n",
"data: {\"type\":\"response.output_item.done\",\"output_index\":0,\"item\":{\"id\":\"ig_123\",\"type\":\"image_generation_call\",\"result\":\"aGVsbG8=\"}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
assert!(first.is_empty());
let second = rewriter
.push_chunk(
concat!(
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"response\":{\"tool_usage\":{\"image_gen\":{\"input_tokens\":1,\"output_tokens\":2,\"total_tokens\":3}}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(second);
assert!(output_text.contains("event: image_generation.completed"));
assert!(output_text.contains("\"type\":\"image_generation.completed\""));
assert!(output_text.contains("\"b64_json\":\"aGVsbG8=\""));
assert!(output_text.contains("\"input_tokens\":1"));
assert!(!output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_image_stream_rewriter_maps_responses_partial_image_events() {
let report_context = json!({
"provider_api_format": "openai:image",
"client_api_format": "openai:image",
"needs_conversion": false,
"image_request": {
"operation": "generate",
"partial_images": 1
}
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let partial = rewriter
.push_chunk(
concat!(
"event: response.image_generation_call.partial_image\n",
"data: {\"type\":\"response.image_generation_call.partial_image\",\"partial_image_index\":0,\"partial_image_b64\":\"cGFydGlhbA==\"}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let partial_text = utf8(partial);
assert!(partial_text.contains("event: image_generation.partial_image"));
assert!(partial_text.contains("\"type\":\"image_generation.partial_image\""));
assert!(partial_text.contains("\"b64_json\":\"cGFydGlhbA==\""));
assert!(partial_text.contains("\"partial_image_index\":0"));
assert!(!partial_text.contains("response.image_generation_call.partial_image"));
let done = rewriter
.push_chunk(
concat!(
"event: response.output_item.done\n",
"data: {\"type\":\"response.output_item.done\",\"output_index\":0,\"item\":{\"id\":\"ig_123\",\"type\":\"image_generation_call\",\"result\":\"ZmluYWw=\"}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
assert!(done.is_empty());
let completed = rewriter
.push_chunk(
concat!(
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"response\":{\"usage\":{\"input_tokens\":4,\"output_tokens\":5,\"total_tokens\":9}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let completed_text = utf8(completed);
assert!(completed_text.contains("event: image_generation.completed"));
assert!(completed_text.contains("\"type\":\"image_generation.completed\""));
assert!(completed_text.contains("\"b64_json\":\"ZmluYWw=\""));
assert!(completed_text.contains("\"total_tokens\":9"));
}
#[test]
fn openai_image_stream_rewriter_reads_final_image_from_completed_response_output() {
let report_context = json!({
"provider_api_format": "openai:image",
"client_api_format": "openai:image",
"needs_conversion": false,
"image_request": {
"operation": "generate"
}
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let completed = rewriter
.push_chunk(
concat!(
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"response\":{\"output\":[{\"type\":\"message\"},{\"type\":\"image_generation_call\",\"result\":\"ZnJvbV9vdXRwdXQ=\"}],\"usage\":{\"input_tokens\":1,\"output_tokens\":2,\"total_tokens\":3}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let completed_text = utf8(completed);
assert!(completed_text.contains("event: image_generation.completed"));
assert!(completed_text.contains("\"b64_json\":\"ZnJvbV9vdXRwdXQ=\""));
assert!(completed_text.contains("\"total_tokens\":3"));
}
#[test]
fn openai_image_stream_rewriter_maps_upstream_error_to_generation_failed() {
let report_context = json!({
"provider_api_format": "openai:image",
"client_api_format": "openai:image",
"needs_conversion": false,
"image_request": {
"operation": "generate"
}
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: error\n",
"data: {\"type\":\"error\",\"error\":{\"type\":\"input-images\",\"code\":\"rate_limit_exceeded\",\"message\":\"Rate limit reached for gpt-image-2\",\"param\":null}}\n\n",
"event: response.failed\n",
"data: {\"type\":\"response.failed\",\"response\":{\"status\":\"failed\",\"error\":{\"code\":\"rate_limit_exceeded\",\"message\":\"Rate limit reached for gpt-image-2\"}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: image_generation.failed"));
assert_eq!(
output_text
.matches("event: image_generation.failed")
.count(),
1
);
assert!(output_text.contains("\"type\":\"image_generation.failed\""));
assert!(output_text.contains("\"type\":\"input-images\""));
assert!(output_text.contains("\"code\":\"rate_limit_exceeded\""));
assert!(output_text.contains("\"message\":\"Rate limit reached for gpt-image-2\""));
assert!(!output_text.contains("response.failed"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_image_stream_rewriter_maps_response_failed_to_edit_failed() {
let report_context = json!({
"provider_api_format": "openai:image",
"client_api_format": "openai:image",
"needs_conversion": false,
"image_request": {
"operation": "edit"
}
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: response.failed\n",
"data: {\"type\":\"response.failed\",\"response\":{\"status\":\"failed\",\"error\":{\"code\":\"rate_limit_exceeded\",\"message\":\"slow down\"}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: image_edit.failed"));
assert!(output_text.contains("\"type\":\"image_edit.failed\""));
assert!(output_text.contains("\"type\":\"rate_limit_exceeded\""));
assert!(output_text.contains("\"code\":\"rate_limit_exceeded\""));
assert!(output_text.contains("\"message\":\"slow down\""));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_image_stream_rewriter_emits_partial_and_completed_events_for_edit() {
let report_context = json!({
"provider_api_format": "openai:image",
"client_api_format": "openai:image",
"needs_conversion": false,
"image_request": {
"operation": "edit",
"partial_images": 2
}
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let partial = rewriter
.push_chunk(
concat!(
"event: response.output_item.done\n",
"data: {\"type\":\"response.output_item.done\",\"output_index\":1,\"item\":{\"id\":\"ig_edit_123\",\"type\":\"image_generation_call\",\"result\":\"d29ybGQ=\"}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let partial_text = utf8(partial);
assert!(partial_text.contains("event: image_edit.partial_image"));
assert!(partial_text.contains("\"type\":\"image_edit.partial_image\""));
assert!(partial_text.contains("\"b64_json\":\"d29ybGQ=\""));
assert!(partial_text.contains("\"partial_image_index\":1"));
let completed = rewriter
.push_chunk(
concat!(
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"response\":{\"usage\":{\"input_tokens\":4,\"output_tokens\":5,\"total_tokens\":9}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let completed_text = utf8(completed);
assert!(completed_text.contains("event: image_edit.completed"));
assert!(completed_text.contains("\"type\":\"image_edit.completed\""));
assert!(completed_text.contains("\"b64_json\":\"d29ybGQ=\""));
assert!(completed_text.contains("\"total_tokens\":9"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn gemini_cli_v1internal_stream_rewriter_unwraps_response_object() {
let report_context = json!({
"has_envelope": true,
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "gemini:generate_content",
"envelope_name": "gemini_cli:v1internal",
"needs_conversion": false,
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"response\":{\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Hello Gemini CLI\"}],\"role\":\"model\"},\"index\":0}],\"modelVersion\":\"gemini-cli-2.5\"}}\n\n",
)
.expect("rewrite should succeed");
let output_text = String::from_utf8(output).expect("text should be utf8");
assert_eq!(
output_text,
"data: {\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Hello Gemini CLI\"}],\"role\":\"model\"},\"index\":0}],\"modelVersion\":\"gemini-cli-2.5\"}\n\n"
);
}
#[test]
fn openai_chat_error_to_openai_responses_stream_rewriter_converts_to_response_failed() {
let report_context = json!({
"provider_api_format": "openai:chat",
"client_api_format": "openai:responses",
"needs_conversion": true,
"mapped_model": "gpt-5.4",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"invalid_request\"}}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.failed"));
assert!(output_text.contains("\"sequence_number\":1"));
assert!(output_text.contains("\"message\":\"bad request\""));
assert!(output_text.contains("\"type\":\"invalid_request_error\""));
assert!(!output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn claude_error_to_openai_responses_stream_rewriter_converts_to_response_failed() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "claude:messages",
"client_api_format": "openai:responses",
"needs_conversion": true,
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: error\n",
"data: {\"type\":\"error\",\"error\":{\"type\":\"rate_limit_error\",\"message\":\"slow down\",\"code\":\"rate_limit\"}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.failed"));
assert!(output_text.contains("\"sequence_number\":1"));
assert!(output_text.contains("\"message\":\"slow down\""));
assert!(output_text.contains("\"type\":\"rate_limit_error\""));
assert!(!output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn gemini_error_to_openai_responses_stream_rewriter_converts_to_response_failed() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:responses",
"needs_conversion": true,
"mapped_model": "gemini-2.5-pro",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"error\":{\"code\":429,\"message\":\"quota exceeded\",\"status\":\"RESOURCE_EXHAUSTED\"}}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.failed"));
assert!(output_text.contains("\"sequence_number\":1"));
assert!(output_text.contains("\"message\":\"quota exceeded\""));
assert!(output_text.contains("\"type\":\"rate_limit_error\""));
assert!(!output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_chat_error_to_claude_chat_stream_rewriter_uses_error_event_line() {
let report_context = json!({
"provider_api_format": "openai:chat",
2026-04-29 09:25:19 +08:00
"client_api_format": "claude:messages",
"needs_conversion": true,
"mapped_model": "gpt-5.4",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"error\":{\"message\":\"bad request\",\"type\":\"invalid_request_error\",\"code\":\"invalid_request\"}}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: error"));
assert!(output_text.contains("\"type\":\"error\""));
assert!(output_text.contains("\"message\":\"bad request\""));
assert!(output_text.contains("\"code\":\"invalid_request\""));
assert!(!output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_chat_error_to_gemini_chat_stream_rewriter_keeps_data_only_error() {
let report_context = json!({
"provider_api_format": "openai:chat",
2026-04-29 09:25:19 +08:00
"client_api_format": "gemini:generate_content",
"needs_conversion": true,
"mapped_model": "gpt-5.4",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"error\":{\"message\":\"rate limited\",\"type\":\"rate_limit_error\",\"code\":\"rate_limit\"}}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.starts_with("data: {\"error\":"));
assert!(!output_text.contains("event: "));
assert!(output_text.contains("\"message\":\"rate limited\""));
assert!(output_text.contains("\"status\":\"RESOURCE_EXHAUSTED\""));
assert!(!output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn claude_to_openai_chat_stream_rewriter_converts_text_deltas() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "claude:messages",
"client_api_format": "openai:chat",
"needs_conversion": true,
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: message_start\n",
"data: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_123\",\"model\":\"claude-sonnet-4-5\"}}\n\n",
"event: content_block_delta\n",
"data: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello\"}}\n\n",
"event: message_delta\n",
"data: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"}}\n\n",
"event: message_stop\n",
"data: {\"type\":\"message_stop\"}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = String::from_utf8(output).expect("utf8 should decode");
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"role\":\"assistant\""));
assert!(output_text.contains("\"content\":\"Hello\""));
assert!(output_text.contains("\"finish_reason\":\"stop\""));
assert!(output_text.contains("data: [DONE]"));
}
#[test]
fn claude_to_openai_chat_stream_rewriter_converts_tool_use_to_tool_calls() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "claude:messages",
"client_api_format": "openai:chat",
"needs_conversion": true,
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: message_start\n",
"data: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_tool_claude_chat_stream_123\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-4-5\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null}}\n\n",
"event: content_block_start\n",
"data: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"Need a tool.\"}}\n\n",
"event: content_block_stop\n",
"data: {\"type\":\"content_block_stop\",\"index\":0}\n\n",
"event: content_block_start\n",
"data: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"tool_123\",\"name\":\"get_weather\",\"input\":{\"location\":\"Tokyo\"}}}\n\n",
"event: message_delta\n",
"data: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"}}\n\n",
"event: message_stop\n",
"data: {\"type\":\"message_stop\"}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = String::from_utf8(output).expect("utf8 should decode");
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"role\":\"assistant\""));
assert!(output_text.contains("\"tool_calls\":[{"));
assert!(output_text.contains("\"id\":\"tool_123\""));
assert!(output_text.contains("\"name\":\"get_weather\""));
assert!(output_text.contains("\\\"location\\\":\\\"Tokyo\\\""));
assert!(output_text.contains("\"finish_reason\":\"tool_calls\""));
assert!(output_text.contains("data: [DONE]"));
}
#[test]
fn gemini_to_openai_chat_stream_rewriter_buffers_and_converts_text() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:chat",
"needs_conversion": true,
"mapped_model": "gemini-2.5-pro",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let first = rewriter
.push_chunk(
b"data: {\"responseId\":\"resp_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Hello \"}],\"role\":\"model\"},\"index\":0}],\"modelVersion\":\"gemini-2.5-pro\"}\n\n",
)
.expect("rewrite should succeed");
let first_text = utf8(first);
assert!(first_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(first_text.contains("\"role\":\"assistant\""));
assert!(first_text.contains("\"content\":\"Hello \""));
assert!(!first_text.contains("data: [DONE]"));
let second = rewriter
.push_chunk(
b"data: {\"responseId\":\"resp_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Gemini\"}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"gemini-2.5-pro\"}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(second);
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"content\":\"Gemini\""));
assert!(output_text.contains("\"finish_reason\":\"stop\""));
assert!(output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn gemini_to_openai_chat_stream_rewriter_buffers_and_converts_function_call() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:chat",
"needs_conversion": true,
"mapped_model": "gemini-2.5-pro",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"responseId\":\"resp_tool_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Need a tool.\"},{\"functionCall\":{\"name\":\"get_weather\",\"args\":{\"city\":\"SF\"}}}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"gemini-2.5-pro\"}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"role\":\"assistant\""));
assert!(output_text.contains("\"content\":\"Need a tool.\""));
assert!(output_text.contains("\"tool_calls\":[{"));
assert!(output_text.contains("\"name\":\"get_weather\""));
assert!(output_text.contains("\\\"city\\\":\\\"SF\\\""));
assert!(output_text.contains("\"finish_reason\":\"tool_calls\""));
assert!(output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_responses_to_openai_chat_stream_rewriter_converts_text_deltas_immediately() {
let report_context = json!({
"provider_api_format": "openai:responses",
"client_api_format": "openai:chat",
"needs_conversion": true,
"mapped_model": "gpt-5.4",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let created = rewriter
.push_chunk(
concat!(
"event: response.created\n",
"data: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_cli_stream_123\",\"object\":\"response\",\"model\":\"gpt-5.4\",\"status\":\"in_progress\"}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let created_text = String::from_utf8(created).expect("utf8 should decode");
assert!(created_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(created_text.contains("\"role\":\"assistant\""));
assert!(!created_text.contains("data: [DONE]"));
let delta = rewriter
.push_chunk(
concat!(
"event: response.output_text.delta\n",
"data: {\"type\":\"response.output_text.delta\",\"delta\":\"Hello Codex\"}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let delta_text = String::from_utf8(delta).expect("utf8 should decode");
assert!(delta_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(delta_text.contains("\"content\":\"Hello Codex\""));
assert!(!delta_text.contains("data: [DONE]"));
let completed = rewriter
.push_chunk(
concat!(
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_cli_stream_123\",\"object\":\"response\",\"model\":\"gpt-5.4\",\"status\":\"completed\",\"output\":[{\"type\":\"message\",\"id\":\"msg_cli_stream_123\",\"role\":\"assistant\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello Codex\",\"annotations\":[]}]}],\"usage\":{\"input_tokens\":1,\"output_tokens\":2,\"total_tokens\":3}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let completed_text = String::from_utf8(completed).expect("utf8 should decode");
assert!(completed_text.contains("\"finish_reason\":\"stop\""));
assert!(
completed_text.contains("\"choices\":[]"),
"{completed_text}"
);
assert!(
completed_text.contains("\"prompt_tokens\":1"),
"{completed_text}"
);
assert!(
completed_text.contains("\"completion_tokens\":2"),
"{completed_text}"
);
assert!(
completed_text.contains("\"total_tokens\":3"),
"{completed_text}"
);
assert!(completed_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_responses_to_openai_chat_stream_rewriter_converts_reasoning_deltas_immediately() {
let report_context = json!({
"provider_api_format": "openai:responses",
"client_api_format": "openai:chat",
"needs_conversion": true,
"mapped_model": "gpt-5.4",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: response.reasoning_summary_text.delta\n",
"data: {\"type\":\"response.reasoning_summary_text.delta\",\"response_id\":\"resp_reasoning_stream_123\",\"item_id\":\"rs_123\",\"output_index\":0,\"summary_index\":0,\"delta\":\"Need to inspect first.\"}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = String::from_utf8(output).expect("utf8 should decode");
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"reasoning_content\":\"Need to inspect first.\""));
assert!(!output_text.contains("\"content\""));
assert!(!output_text.contains("data: [DONE]"));
}
#[test]
fn openai_responses_to_openai_chat_stream_rewriter_converts_completed_event_without_buffering() {
let report_context = json!({
"provider_api_format": "openai:responses",
"client_api_format": "openai:chat",
"needs_conversion": true,
"mapped_model": "gpt-5.4",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: response.completed\n",
"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_cli_stream_123\",\"object\":\"response\",\"model\":\"gpt-5.4\",\"status\":\"completed\",\"output\":[{\"type\":\"message\",\"id\":\"msg_cli_stream_123\",\"role\":\"assistant\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello Codex\",\"annotations\":[]}]}],\"usage\":{\"input_tokens\":1,\"output_tokens\":2,\"total_tokens\":3}}}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = String::from_utf8(output).expect("utf8 should decode");
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"role\":\"assistant\""));
assert!(output_text.contains("\"content\":\"Hello Codex\""));
assert!(output_text.contains("\"finish_reason\":\"stop\""));
assert!(output_text.contains("\"choices\":[]"), "{output_text}");
assert!(output_text.contains("\"prompt_tokens\":1"), "{output_text}");
assert!(
output_text.contains("\"completion_tokens\":2"),
"{output_text}"
);
assert!(output_text.contains("\"total_tokens\":3"), "{output_text}");
assert!(output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn antigravity_gemini_to_openai_chat_stream_rewriter_unwraps_and_converts_function_call() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:chat",
"needs_conversion": true,
"has_envelope": true,
"envelope_name": "antigravity:v1internal",
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"response\":{\"responseId\":\"resp_antigravity_chat_tool_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Need a tool.\"},{\"functionCall\":{\"name\":\"get_weather\",\"args\":{\"city\":\"SF\"}}}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"claude-sonnet-4-5\"},\"responseId\":\"resp_antigravity_chat_tool_123\"}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("\"object\":\"chat.completion.chunk\""));
assert!(output_text.contains("\"content\":\"Need a tool.\""));
assert!(output_text.contains("\"tool_calls\""));
assert!(output_text.contains("\"name\":\"get_weather\""));
assert!(output_text.contains("\"finish_reason\":\"tool_calls\""));
assert!(output_text.contains("data: [DONE]"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn antigravity_gemini_to_openai_responses_stream_rewriter_unwraps_and_converts_function_call() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:responses",
"needs_conversion": true,
"envelope_name": "antigravity:v1internal",
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"response\":{\"responseId\":\"resp_antigravity_cli_tool_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Need a tool.\"},{\"functionCall\":{\"name\":\"get_weather\",\"args\":{\"city\":\"SF\"}}}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"claude-sonnet-4-5\",\"usageMetadata\":{\"promptTokenCount\":2,\"candidatesTokenCount\":3,\"totalTokenCount\":5}},\"responseId\":\"resp_antigravity_cli_tool_123\"}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.created"));
assert!(output_text.contains("event: response.output_text.delta"));
assert!(output_text.contains("event: response.output_item.added"));
assert!(output_text.contains("event: response.function_call_arguments.delta"));
assert!(output_text.contains("event: response.completed"));
assert!(output_text.contains("\"type\":\"function_call\""));
assert!(output_text.contains("\"name\":\"get_weather\""));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn gemini_to_openai_responses_stream_rewriter_buffers_and_converts_to_completed_event() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:responses",
"needs_conversion": true,
"mapped_model": "gemini-2.5-pro",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let first = rewriter
.push_chunk(
b"data: {\"responseId\":\"resp_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Hello \"}],\"role\":\"model\"},\"index\":0}],\"modelVersion\":\"gemini-2.5-pro\"}\n\n",
)
.expect("rewrite should succeed");
let first_text = utf8(first);
assert!(first_text.contains("event: response.created"));
assert!(first_text.contains("event: response.output_text.delta"));
assert!(first_text.contains("\"delta\":\"Hello \""));
let second = rewriter
.push_chunk(
b"data: {\"responseId\":\"resp_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Gemini CLI\"}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"gemini-2.5-pro\",\"usageMetadata\":{\"promptTokenCount\":2,\"candidatesTokenCount\":3,\"totalTokenCount\":5}}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(second);
assert!(output_text.contains("event: response.output_text.delta"));
assert!(output_text.contains("event: response.completed"));
assert!(output_text.contains("\"type\":\"response.completed\""));
assert!(output_text.contains("\"object\":\"response\""));
assert!(output_text.contains("\"delta\":\"Gemini CLI\""));
assert!(output_text.contains("\"text\":\"Hello Gemini CLI\""));
assert!(output_text.contains("\"total_tokens\":5"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn claude_to_openai_responses_stream_rewriter_buffers_and_converts_to_completed_event() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "claude:messages",
"client_api_format": "openai:responses",
"needs_conversion": true,
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: message_start\n",
"data: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_123\",\"model\":\"claude-sonnet-4-5\"}}\n\n",
"event: content_block_start\n",
"data: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n",
"event: content_block_delta\n",
"data: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello Claude CLI\"}}\n\n",
"event: message_delta\n",
"data: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":2,\"output_tokens\":3}}\n\n",
"event: message_stop\n",
"data: {\"type\":\"message_stop\"}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.created"));
assert!(output_text.contains("event: response.output_text.delta"));
assert!(output_text.contains("event: response.completed"));
assert!(output_text.contains("\"type\":\"response.completed\""));
assert!(output_text.contains("\"object\":\"response\""));
assert!(output_text.contains("\"text\":\"Hello Claude CLI\""));
assert!(output_text.contains("\"total_tokens\":5"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn claude_to_openai_responses_stream_rewriter_converts_tool_use_to_function_call() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "claude:messages",
"client_api_format": "openai:responses",
"needs_conversion": true,
"mapped_model": "claude-sonnet-4-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
concat!(
"event: message_start\n",
"data: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_tool_123\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-4-5\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null}}\n\n",
"event: content_block_start\n",
"data: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"Running tool.\"}}\n\n",
"event: content_block_stop\n",
"data: {\"type\":\"content_block_stop\",\"index\":0}\n\n",
"event: content_block_start\n",
"data: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"tool_123\",\"name\":\"read_file\",\"input\":{\"path\":\"/tmp/test.txt\"}}}\n\n",
"event: content_block_stop\n",
"data: {\"type\":\"content_block_stop\",\"index\":1}\n\n",
"event: message_delta\n",
"data: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"input_tokens\":4,\"output_tokens\":6}}\n\n",
"event: message_stop\n",
"data: {\"type\":\"message_stop\"}\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.created"));
assert!(output_text.contains("event: response.output_text.delta"));
assert!(output_text.contains("\"delta\":\"Running tool.\""));
assert!(output_text.contains("event: response.output_item.added"));
assert!(output_text.contains("event: response.function_call_arguments.delta"));
assert!(output_text.contains("event: response.completed"));
assert!(output_text.contains("\"type\":\"response.completed\""));
assert!(output_text.contains("\"type\":\"function_call\""));
assert!(output_text.contains("\"call_id\":\"tool_123\""));
assert!(output_text.contains("\"name\":\"read_file\""));
assert!(output_text.contains("\\\"path\\\":\\\"/tmp/test.txt\\\""));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn gemini_to_openai_responses_stream_rewriter_converts_function_call_to_completed_event() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:responses",
"needs_conversion": true,
"mapped_model": "gemini-2.5-pro",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"responseId\":\"resp_tool_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Need a tool.\"},{\"functionCall\":{\"name\":\"get_weather\",\"args\":{\"location\":\"Tokyo\"}}}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"gemini-2.5-pro\",\"usageMetadata\":{\"promptTokenCount\":2,\"candidatesTokenCount\":3,\"totalTokenCount\":5}}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.created"));
assert!(output_text.contains("event: response.output_item.added"));
assert!(output_text.contains("event: response.function_call_arguments.delta"));
assert!(output_text.contains("event: response.completed"));
assert!(output_text.contains("\"type\":\"response.completed\""));
assert!(output_text.contains("\"type\":\"function_call\""));
assert!(output_text.contains("\"name\":\"get_weather\""));
assert!(output_text.contains("\\\"location\\\":\\\"Tokyo\\\""));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
2026-04-26 21:12:57 +08:00
fn gemini_to_openai_responses_compact_stream_rewriter_converts_function_call_to_completed_event() {
let report_context = json!({
2026-04-29 09:25:19 +08:00
"provider_api_format": "gemini:generate_content",
"client_api_format": "openai:responses:compact",
"needs_conversion": true,
"mapped_model": "gemini-2.5-pro",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let output = rewriter
.push_chunk(
b"data: {\"responseId\":\"resp_tool_compact_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Need a tool.\"},{\"functionCall\":{\"name\":\"get_weather\",\"args\":{\"location\":\"Tokyo\"}}}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"gemini-2.5-pro\",\"usageMetadata\":{\"promptTokenCount\":2,\"candidatesTokenCount\":3,\"totalTokenCount\":5}}\n\n",
)
.expect("rewrite should succeed");
let output_text = utf8(output);
assert!(output_text.contains("event: response.created"));
assert!(output_text.contains("event: response.output_item.added"));
assert!(output_text.contains("event: response.function_call_arguments.delta"));
assert!(output_text.contains("event: response.completed"));
assert!(output_text.contains("\"type\":\"response.completed\""));
assert!(output_text.contains("\"type\":\"function_call\""));
assert!(output_text.contains("\"name\":\"get_weather\""));
assert!(output_text.contains("\\\"location\\\":\\\"Tokyo\\\""));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_chat_to_claude_chat_stream_rewriter_converts_via_standard_matrix() {
let report_context = json!({
"provider_api_format": "openai:chat",
2026-04-29 09:25:19 +08:00
"client_api_format": "claude:messages",
"needs_conversion": true,
"mapped_model": "gpt-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let first = rewriter
.push_chunk(
"data: {\"id\":\"chatcmpl_std_claude_123\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gpt-5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Hello Claude\"},\"finish_reason\":null}]}\n\n"
.as_bytes(),
)
.expect("rewrite should succeed");
let first_text = utf8(first);
assert!(first_text.contains("event: message_start"));
assert!(first_text.contains("event: content_block_start"));
assert!(first_text.contains("event: content_block_delta"));
assert!(first_text.contains("\"text\":\"Hello Claude\""));
assert!(first_text.contains("\"usage\":{\"input_tokens\":0,\"output_tokens\":0}"));
let second = rewriter
.push_chunk(
concat!(
"data: {\"id\":\"chatcmpl_std_claude_123\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":1,\"completion_tokens\":2,\"total_tokens\":3}}\n\n",
"data: [DONE]\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(second);
assert!(output_text.contains("event: content_block_stop"));
assert!(output_text.contains("event: message_delta"));
assert!(output_text.contains("event: message_stop"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}
#[test]
fn openai_chat_to_claude_chat_stream_rewriter_injects_default_usage_when_finish_chunk_lacks_usage()
{
let report_context = json!({
"provider_api_format": "openai:chat",
2026-04-29 09:25:19 +08:00
"client_api_format": "claude:messages",
"needs_conversion": true,
"mapped_model": "gpt-5.4",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let first = rewriter
.push_chunk(
"data: {\"id\":\"chatcmpl_usage_missing_123\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gpt-5.4\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Hello\"},\"finish_reason\":null}]}\n\n"
.as_bytes(),
)
.expect("rewrite should succeed");
let first_text = utf8(first);
assert!(first_text.contains("event: message_start"));
let second = rewriter
.push_chunk(
concat!(
"data: {\"id\":\"chatcmpl_usage_missing_123\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-5.4\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\n",
"data: [DONE]\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(second);
assert!(output_text.is_empty(), "{output_text}");
let final_text = utf8(rewriter.finish().expect("finish should succeed"));
assert!(final_text.contains("event: message_delta"));
assert!(final_text.contains("\"stop_reason\":\"end_turn\""));
assert!(final_text.contains("\"usage\":{\"input_tokens\":0,\"output_tokens\":0}"));
assert!(final_text.contains("event: message_stop"));
}
#[test]
fn openai_chat_to_gemini_cli_stream_rewriter_converts_via_standard_matrix() {
let report_context = json!({
"provider_api_format": "openai:chat",
2026-04-29 09:25:19 +08:00
"client_api_format": "gemini:generate_content",
"needs_conversion": true,
"mapped_model": "gpt-5",
});
let mut rewriter =
maybe_build_local_stream_rewriter(Some(&report_context)).expect("rewriter should exist");
let first = rewriter
.push_chunk(
"data: {\"id\":\"chatcmpl_std_gemini_cli_123\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gpt-5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Hello Gemini CLI\"},\"finish_reason\":null}]}\n\n"
.as_bytes(),
)
.expect("rewrite should succeed");
let first_text = utf8(first);
assert!(first_text.contains("\"responseId\":\"chatcmpl_std_gemini_cli_123\""));
assert!(first_text.contains("\"candidates\""));
assert!(first_text.contains("\"text\":\"Hello Gemini CLI\""));
let second = rewriter
.push_chunk(
concat!(
"data: {\"id\":\"chatcmpl_std_gemini_cli_123\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":2,\"completion_tokens\":3,\"total_tokens\":5}}\n\n",
"data: [DONE]\n\n"
)
.as_bytes(),
)
.expect("rewrite should succeed");
let output_text = utf8(second);
assert!(output_text.contains("\"finishReason\":\"STOP\""));
assert!(output_text.contains("\"totalTokenCount\":5"));
assert!(rewriter.finish().expect("finish should succeed").is_empty());
}