use nanocodex_oai_api::responses::{
ContentItem, FunctionOutputBody, FunctionOutputContent, MessageRole, ResponseItem,
};
use nanocodex_tools::contract::{ToolOutputBody, ToolOutputContent};
use serde_json::Value;
use super::context::ContextSnapshot;
const PERMISSIONS_INSTRUCTIONS: &str = concat!(
"<permissions instructions>\n",
"Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is ",
"`danger-full-access`: No filesystem sandboxing - all commands are permitted. Network ",
"access is enabled.\n",
"Approval policy is currently never. Do not provide the `sandbox_permissions` for any ",
"reason, commands will be rejected.\n",
"</permissions instructions>",
);
pub(in crate::model) fn task_input(
user_content: Vec<ContentItem>,
context: &ContextSnapshot,
) -> Vec<ResponseItem> {
vec![
developer_context(),
context.full_item(),
ResponseItem::message(MessageRole::User, user_content),
]
}
pub(in crate::model) fn turn_aborted() -> ResponseItem {
ResponseItem::message(
MessageRole::User,
[ContentItem::InputText {
text: concat!(
"<turn_aborted>\n",
"The user interrupted the previous turn on purpose. Any running unified exec ",
"processes may still be running in the background. If any tools/commands were ",
"aborted, they may have partially executed.\n",
"</turn_aborted>"
)
.into(),
}],
)
}
pub(in crate::model) fn developer_context() -> ResponseItem {
ResponseItem::message(
MessageRole::Developer,
[ContentItem::InputText {
text: PERMISSIONS_INSTRUCTIONS.into(),
}],
)
}
pub(in crate::model) fn custom_tool_output(
call_id: String,
output: ToolOutputBody,
) -> ResponseItem {
ResponseItem::custom_tool_output(call_id, None, function_output(output))
}
pub(in crate::model) fn custom_tool_notification(call_id: String, text: String) -> ResponseItem {
ResponseItem::custom_tool_output(
call_id,
Some("exec".to_owned()),
FunctionOutputBody::Text(text.into_boxed_str()),
)
}
pub(in crate::model) fn function_tool_output(
call_id: String,
output: ToolOutputBody,
) -> ResponseItem {
ResponseItem::function_call_output(call_id, function_output(output))
}
pub(in crate::model) fn tool_search_output(call_id: String, tools: Vec<Value>) -> ResponseItem {
ResponseItem::ToolSearchOutput {
id: None,
call_id: Some(call_id.into_boxed_str()),
status: "completed".into(),
execution: "client".into(),
tools: tools.into_iter().map(Into::into).collect(),
internal_chat_message_metadata_passthrough: None,
}
}
fn function_output(output: ToolOutputBody) -> FunctionOutputBody {
match output {
ToolOutputBody::Text(text) => FunctionOutputBody::Text(text.into_boxed_str()),
ToolOutputBody::Content(content) => FunctionOutputBody::Content(
content
.into_iter()
.map(|item| match item {
ToolOutputContent::InputText { text } => FunctionOutputContent::InputText {
text: text.into_boxed_str(),
},
ToolOutputContent::InputImage {
image_url,
detail: _,
} => FunctionOutputContent::InputImage {
image_url: image_url.into_boxed_str(),
detail: None,
},
ToolOutputContent::InputAudio { audio_url } => {
FunctionOutputContent::InputAudio {
audio_url: audio_url.into_boxed_str(),
}
}
})
.collect(),
),
}
}
#[cfg(test)]
mod tests {
use super::*;
use nanocodex_oai_api::ImageDetail;
use nanocodex_tools::contract::ToolOutputContent;
use serde_json::json;
#[test]
fn task_input_matches_codex_context_shape() {
let context = ContextSnapshot::capture_at(
"/workspace/a&b",
"bash",
Some("Follow the project formatter."),
"2026-07-17",
"America/Los_Angeles",
);
let input = task_input(
vec![ContentItem::InputText {
text: "fix the bug".into(),
}],
&context,
);
assert_eq!(
serde_json::to_value(input).unwrap(),
json!([
json!({
"type": "message",
"role": "developer",
"content": [
{
"type": "input_text",
"text": PERMISSIONS_INSTRUCTIONS,
},
],
}),
json!({
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "# AGENTS.md instructions for /workspace/a&b\n\n<INSTRUCTIONS>\nFollow the project formatter.\n</INSTRUCTIONS>",
},
{
"type": "input_text",
"text": "<environment_context>\n <cwd>/workspace/a&b</cwd>\n <shell>bash</shell>\n <current_date>2026-07-17</current_date>\n <timezone>America/Los_Angeles</timezone>\n <filesystem><workspace_roots><root>/workspace/a&b</root></workspace_roots><permission_profile type=\"disabled\"><file_system type=\"unrestricted\" /></permission_profile></filesystem>\n</environment_context>",
},
],
}),
json!({
"type": "message",
"role": "user",
"content": [{
"type": "input_text",
"text": "fix the bug",
}],
}),
]),
);
}
#[test]
fn turn_aborted_matches_codex_context_shape() {
assert_eq!(
serde_json::to_value(turn_aborted()).unwrap(),
json!({
"type": "message",
"role": "user",
"content": [{
"type": "input_text",
"text": concat!(
"<turn_aborted>\n",
"The user interrupted the previous turn on purpose. Any running unified ",
"exec processes may still be running in the background. If any ",
"tools/commands were aborted, they may have partially executed.\n",
"</turn_aborted>"
),
}],
}),
);
}
#[test]
fn responses_lite_tool_output_omits_image_details_without_request_copy() {
let input = vec![custom_tool_output(
"call-1".to_owned(),
ToolOutputBody::Content(vec![
ToolOutputContent::InputText {
text: "before".to_owned(),
},
ToolOutputContent::InputImage {
image_url: "data:image/png;base64,a".to_owned(),
detail: ImageDetail::Original,
},
]),
)];
let request = serde_json::to_value(input).expect("tool output should serialize");
assert!(request[0]["output"][1].get("detail").is_none());
}
}