| use codex_code_mode_protocol::CellId; |
| use codex_code_mode_protocol::CodeModeNestedToolCall; |
| use codex_code_mode_protocol::CodeModeToolKind; |
| use codex_code_mode_protocol::ExecuteRequest; |
| use codex_code_mode_protocol::FunctionCallOutputContentItem; |
| use codex_code_mode_protocol::ImageDetail; |
| use codex_code_mode_protocol::RuntimeResponse; |
| use codex_code_mode_protocol::ToolDefinition; |
| use codex_code_mode_protocol::WaitOutcome; |
| use codex_code_mode_protocol::grpc; |
| use codex_protocol::ToolName; |
| use pretty_assertions::assert_eq; |
| use serde_json::json; |
| use std::time::Duration; |
|
|
| use super::execute_request; |
| use super::runtime_response; |
| use super::tool_call; |
| use super::wait_outcome; |
|
|
| #[test] |
| fn execute_request_preserves_tool_schemas_namespaces_and_limits() { |
| let request = ExecuteRequest { |
| tool_call_id: "outer".to_string(), |
| enabled_tools: vec![ToolDefinition { |
| name: "search".to_string(), |
| tool_name: ToolName::namespaced("work", "search"), |
| description: "search the workspace".to_string(), |
| kind: CodeModeToolKind::Freeform, |
| input_schema: Some(json!({"type": "object"})), |
| output_schema: Some(json!({"type": "string"})), |
| }], |
| source: "text('hello')".to_string(), |
| yield_time_ms: Some(25), |
| max_output_tokens: Some(128), |
| }; |
|
|
| assert_eq!( |
| execute_request("session", "execution".to_string(), request), |
| Ok(grpc::ExecuteRequest { |
| session_id: "session".to_string(), |
| execution_id: "execution".to_string(), |
| tool_call_id: "outer".to_string(), |
| source: "text('hello')".to_string(), |
| enabled_tools: vec![grpc::ToolDefinition { |
| name: "search".to_string(), |
| tool_name: Some(grpc::ToolName { |
| name: "search".to_string(), |
| namespace: Some("work".to_string()), |
| }), |
| description: "search the workspace".to_string(), |
| kind: grpc::ToolKind::Freeform as i32, |
| input_schema_json: Some(br#"{"type":"object"}"#.to_vec()), |
| output_schema_json: Some(br#"{"type":"string"}"#.to_vec()), |
| }], |
| yield_time_ms: Some(25), |
| max_output_tokens: Some(128), |
| }) |
| ); |
| } |
|
|
| #[test] |
| fn tool_call_decodes_structured_input_and_namespace() { |
| let call = grpc::ToolCall { |
| session_id: "session".to_string(), |
| execution_id: "execution".to_string(), |
| cell_id: "cell".to_string(), |
| invocation_id: "invocation".to_string(), |
| runtime_tool_call_id: "runtime-call".to_string(), |
| tool_name: Some(grpc::ToolName { |
| name: "search".to_string(), |
| namespace: Some("work".to_string()), |
| }), |
| tool_kind: grpc::ToolKind::Function as i32, |
| input_json: Some(br#"{"query":"hello"}"#.to_vec()), |
| sequence: 1, |
| traceparent: None, |
| }; |
|
|
| assert_eq!( |
| tool_call(call), |
| Ok(CodeModeNestedToolCall { |
| cell_id: CellId::new("cell".to_string()), |
| runtime_tool_call_id: "runtime-call".to_string(), |
| tool_name: ToolName::namespaced("work", "search"), |
| tool_kind: CodeModeToolKind::Function, |
| input: Some(json!({"query": "hello"})), |
| }) |
| ); |
| } |
|
|
| #[test] |
| fn runtime_response_decodes_mixed_content_items() { |
| let outcome = grpc::ExecutionOutcome { |
| code_mode_host_duration_ns: 0, |
| cell_id: "cell".to_string(), |
| content_items: vec![ |
| grpc::ContentItem { |
| item: Some(grpc::content_item::Item::Text(grpc::TextContent { |
| text: "hello".to_string(), |
| })), |
| }, |
| grpc::ContentItem { |
| item: Some(grpc::content_item::Item::Image(grpc::ImageContent { |
| image_url: "data:image/png;base64,AA==".to_string(), |
| detail: Some(grpc::ImageDetail::Original as i32), |
| })), |
| }, |
| grpc::ContentItem { |
| item: Some(grpc::content_item::Item::Audio(grpc::AudioContent { |
| audio_url: "data:audio/wav;base64,AA==".to_string(), |
| })), |
| }, |
| ], |
| outcome: Some(grpc::execution_outcome::Outcome::Completed( |
| grpc::ExecutionCompleted { |
| error_text: Some("warning".to_string()), |
| }, |
| )), |
| }; |
|
|
| assert_eq!( |
| runtime_response(outcome), |
| Ok(RuntimeResponse::Result { |
| code_mode_host_duration: Some(Duration::ZERO), |
| cell_id: CellId::new("cell".to_string()), |
| content_items: vec![ |
| FunctionCallOutputContentItem::InputText { |
| text: "hello".to_string(), |
| }, |
| FunctionCallOutputContentItem::InputImage { |
| image_url: "data:image/png;base64,AA==".to_string(), |
| detail: Some(ImageDetail::Original), |
| }, |
| FunctionCallOutputContentItem::InputAudio { |
| audio_url: "data:audio/wav;base64,AA==".to_string(), |
| }, |
| ], |
| error_text: Some("warning".to_string()), |
| }) |
| ); |
| } |
|
|
| #[test] |
| fn wait_outcome_preserves_missing_cell_state() { |
| let response = grpc::WaitResponse { |
| state: Some(grpc::wait_response::State::MissingCell( |
| grpc::ExecutionOutcome { |
| code_mode_host_duration_ns: 0, |
| cell_id: "missing".to_string(), |
| content_items: Vec::new(), |
| outcome: Some(grpc::execution_outcome::Outcome::Terminated( |
| grpc::ExecutionTerminated {}, |
| )), |
| }, |
| )), |
| }; |
|
|
| assert_eq!( |
| wait_outcome(response), |
| Ok(WaitOutcome::MissingCell(RuntimeResponse::Terminated { |
| code_mode_host_duration: Some(Duration::ZERO), |
| cell_id: CellId::new("missing".to_string()), |
| content_items: Vec::new(), |
| })) |
| ); |
| } |
|
|
| #[test] |
| fn oversized_response_cell_ids_are_rejected() { |
| let response = grpc::ExecutionOutcome { |
| code_mode_host_duration_ns: 0, |
| cell_id: "x".repeat(grpc::MAX_IDENTIFIER_BYTES + 1), |
| content_items: Vec::new(), |
| outcome: Some(grpc::execution_outcome::Outcome::Yielded( |
| grpc::ExecutionYielded {}, |
| )), |
| }; |
|
|
| assert_eq!( |
| runtime_response(response), |
| Err(format!( |
| "gRPC code-mode host returned cell ID exceeding {} bytes", |
| grpc::MAX_IDENTIFIER_BYTES |
| )) |
| ); |
| } |
|
|
| #[test] |
| fn invalid_output_enums_and_missing_oneofs_are_rejected() { |
| let invalid_image = grpc::ExecutionOutcome { |
| code_mode_host_duration_ns: 0, |
| cell_id: "cell".to_string(), |
| content_items: vec![grpc::ContentItem { |
| item: Some(grpc::content_item::Item::Image(grpc::ImageContent { |
| image_url: "image".to_string(), |
| detail: Some(grpc::ImageDetail::Unspecified as i32), |
| })), |
| }], |
| outcome: Some(grpc::execution_outcome::Outcome::Yielded( |
| grpc::ExecutionYielded {}, |
| )), |
| }; |
|
|
| assert!(runtime_response(invalid_image).is_err()); |
| assert!(wait_outcome(grpc::WaitResponse { state: None }).is_err()); |
| } |
|
|
| |
| |
| #[test] |
| fn host_timing_preserves_zero_and_nanosecond_precision() { |
| for code_mode_host_duration_ns in [0, 1_234_567, u64::MAX] { |
| let outcome = grpc::ExecutionOutcome { |
| cell_id: "cell".to_string(), |
| content_items: Vec::new(), |
| code_mode_host_duration_ns, |
| outcome: Some(grpc::execution_outcome::Outcome::Yielded( |
| grpc::ExecutionYielded {}, |
| )), |
| }; |
| let expected = RuntimeResponse::Yielded { |
| cell_id: CellId::new("cell".to_string()), |
| content_items: Vec::new(), |
| code_mode_host_duration: Some(Duration::from_nanos(code_mode_host_duration_ns)), |
| }; |
| assert_eq!(runtime_response(outcome.clone()).as_ref(), Ok(&expected)); |
| assert_eq!( |
| wait_outcome(grpc::WaitResponse { |
| state: Some(grpc::wait_response::State::LiveCell(outcome)), |
| }), |
| Ok(WaitOutcome::LiveCell(expected)) |
| ); |
| } |
| } |
|
|