Skip to content

Commit beb8f14

Browse files
committed
Added completions
1 parent 2c53e50 commit beb8f14

File tree

2 files changed

+85
-1
lines changed

2 files changed

+85
-1
lines changed

codex-rs/core/src/chat_completions.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::util::backoff;
1616
use bytes::Bytes;
1717
use codex_otel::otel_event_manager::OtelEventManager;
1818
use codex_protocol::models::ContentItem;
19+
use codex_protocol::models::FunctionCallOutputContentItem;
1920
use codex_protocol::models::ReasoningItemContent;
2021
use codex_protocol::models::ResponseItem;
2122
use eventsource_stream::Eventsource;
@@ -236,10 +237,30 @@ pub(crate) async fn stream_chat_completions(
236237
messages.push(msg);
237238
}
238239
ResponseItem::FunctionCallOutput { call_id, output } => {
240+
let content = match &output.content_items {
241+
Some(items) => serde_json::Value::Array(
242+
items
243+
.iter()
244+
.map(|item| match item {
245+
FunctionCallOutputContentItem::InputText { text } => json!({
246+
"type": "input_text",
247+
"text": text,
248+
}),
249+
FunctionCallOutputContentItem::InputImage { image_url } => {
250+
json!({
251+
"type": "input_image",
252+
"image_url": image_url,
253+
})
254+
}
255+
})
256+
.collect(),
257+
),
258+
None => serde_json::Value::String(output.content.clone()),
259+
};
239260
messages.push(json!({
240261
"role": "tool",
241262
"tool_call_id": call_id,
242-
"content": output.content,
263+
"content": content,
243264
}));
244265
}
245266
ResponseItem::CustomToolCall {

codex-rs/core/tests/chat_completions_payload.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ use codex_core::WireApi;
1313
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
1414
use codex_otel::otel_event_manager::OtelEventManager;
1515
use codex_protocol::ConversationId;
16+
use codex_protocol::models::FunctionCallOutputContentItem;
17+
use codex_protocol::models::FunctionCallOutputPayload;
1618
use codex_protocol::models::ReasoningItemContent;
1719
use core_test_support::load_default_config_for_test;
1820
use futures::StreamExt;
1921
use serde_json::Value;
22+
use serde_json::json;
2023
use tempfile::TempDir;
2124
use wiremock::Mock;
2225
use wiremock::MockServer;
@@ -254,6 +257,66 @@ async fn attaches_reasoning_to_function_call_anchor() {
254257
assert_eq!(tool_calls[0]["type"], Value::String("function".into()));
255258
}
256259

260+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
261+
async fn function_call_output_uses_content_items_for_images() {
262+
if network_disabled() {
263+
println!(
264+
"Skipping test because it cannot execute when network is disabled in a Codex sandbox."
265+
);
266+
return;
267+
}
268+
269+
let content_items = vec![
270+
FunctionCallOutputContentItem::InputText {
271+
text: "See image".to_string(),
272+
},
273+
FunctionCallOutputContentItem::InputImage {
274+
image_url: "data:image/png;base64,abc".to_string(),
275+
},
276+
];
277+
let serialized_items = serde_json::to_string(&content_items).expect("serialize content items");
278+
let payload = FunctionCallOutputPayload {
279+
content: serialized_items,
280+
content_items: Some(content_items),
281+
success: Some(true),
282+
};
283+
284+
let body = run_request(vec![
285+
user_message("u1"),
286+
function_call(),
287+
ResponseItem::FunctionCallOutput {
288+
call_id: "c1".to_string(),
289+
output: payload,
290+
},
291+
])
292+
.await;
293+
294+
let messages = messages_from(&body);
295+
let tool = messages
296+
.iter()
297+
.find(|msg| msg["role"] == "tool")
298+
.expect("tool message present");
299+
let content = tool["content"]
300+
.as_array()
301+
.expect("tool content serialized as array");
302+
303+
assert_eq!(content.len(), 2);
304+
assert_eq!(
305+
content[0],
306+
json!({
307+
"type": "input_text",
308+
"text": "See image",
309+
})
310+
);
311+
assert_eq!(
312+
content[1],
313+
json!({
314+
"type": "input_image",
315+
"image_url": "data:image/png;base64,abc",
316+
})
317+
);
318+
}
319+
257320
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
258321
async fn attaches_reasoning_to_local_shell_call() {
259322
if network_disabled() {

0 commit comments

Comments
 (0)