Skip to main content

codex_convert_proxy/convert/
context.rs

1//! Request-level context shared between conversion paths.
2//!
3//! `ResponseRequestContext` captures the original Responses API request fields
4//! needed to reconstruct a spec-compliant response (instructions, tools, sampling
5//! params, etc.). It is consumed by both streaming and non-streaming flows, so
6//! it lives at the `convert` root rather than under `streaming::state`.
7
8use std::collections::HashMap;
9
10use serde::Serialize;
11
12use crate::types::response_api::{
13    ResponseReasoning, ResponseRequest, ResponseTextConfig, Tool, ToolChoice,
14};
15
16/// Fields from the originating Responses API request that the proxy must
17/// echo back on the synthesized Response payload (and intermediate stream
18/// stubs). Kept separate from `StreamState` so non-streaming flows can use
19/// it without dragging streaming-specific bookkeeping.
20#[derive(Debug, Clone, Serialize)]
21pub struct ResponseRequestContext {
22    pub instructions: Option<String>,
23    pub max_output_tokens: Option<u32>,
24    pub parallel_tool_calls: Option<bool>,
25    pub previous_response_id: Option<String>,
26    pub reasoning: Option<ResponseReasoning>,
27    pub store: Option<bool>,
28    pub temperature: Option<f32>,
29    pub text: Option<ResponseTextConfig>,
30    pub tool_choice: ToolChoice,
31    pub tools: Vec<Tool>,
32    pub top_p: Option<f32>,
33    pub truncation: Option<String>,
34    pub user: Option<String>,
35    pub metadata: Option<HashMap<String, serde_json::Value>>,
36}
37
38impl From<&ResponseRequest> for ResponseRequestContext {
39    fn from(req: &ResponseRequest) -> Self {
40        let mut metadata = req.metadata.clone().unwrap_or_default();
41        let tool_map: serde_json::Map<String, serde_json::Value> = req
42            .tools
43            .iter()
44            .filter_map(|t| {
45                t.name.as_ref().map(|name| {
46                    (
47                        name.clone(),
48                        serde_json::json!({
49                            "type": t.tool_type,
50                            "strict": t.strict,
51                            "extra": t.extra,
52                        }),
53                    )
54                })
55            })
56            .collect();
57        if !tool_map.is_empty() {
58            metadata.insert(
59                "x_proxy_tool_map".to_string(),
60                serde_json::Value::Object(tool_map),
61            );
62        }
63
64        Self {
65            instructions: req.instructions.clone(),
66            max_output_tokens: req.max_output_tokens.or(req.max_tokens),
67            parallel_tool_calls: req.parallel_tool_calls,
68            previous_response_id: req.previous_response_id.clone(),
69            reasoning: req.reasoning.clone(),
70            store: req.store,
71            temperature: req.temperature,
72            text: req.text.clone(),
73            tool_choice: req.tool_choice.clone(),
74            tools: req.tools.clone(),
75            top_p: req.top_p,
76            truncation: req.truncation.clone(),
77            user: req.user.clone(),
78            metadata: if metadata.is_empty() {
79                None
80            } else {
81                Some(metadata)
82            },
83        }
84    }
85}