Skip to main content

gproxy_protocol/transform/generate/chat_responses/response/
facts.rs

1use super::ChatUsageSupplement;
2use crate::{
3    transform::TransformError,
4    wire::{
5        DeclaredFields,
6        openai::responses::{generate as g, input as i, response as r},
7    },
8};
9
10/// The original Responses request and effective settings retained during request
11/// preparation. Required settings must come from that invocation, not guesses
12/// made after receiving a Chat completion. A response conversion consumes this
13/// value and recursively removes extensions before reusing same-dialect fields.
14#[derive(Debug)]
15pub struct ResponsesResponseContext {
16    pub request: g::GenerateContentRequestBody,
17    pub effective_parallel_tool_calls: bool,
18    pub effective_tool_choice: i::ToolChoice,
19    pub usage: ChatUsageSupplement,
20    pub effective_prompt_cache_options: Option<r::ResponsePromptCacheOptions>,
21}
22
23impl ResponsesResponseContext {
24    pub(crate) fn into_response(
25        self,
26        id: String,
27        created_at: i64,
28        model: String,
29    ) -> Result<r::GenerateContentResponseBody, TransformError> {
30        let request = self.request.into_declared();
31        let choice = self.effective_tool_choice.into_declared();
32        if request
33            .parallel_tool_calls
34            .flatten()
35            .is_some_and(|v| v != self.effective_parallel_tool_calls)
36            || request.tool_choice.as_ref().is_some_and(|v| v != &choice)
37        {
38            return Err(TransformError::shape(
39                "response_context",
40                "effective settings contradict original request",
41            ));
42        }
43        let conversation = request.conversation.map(|value| {
44            value.map(|value| match value {
45                i::ConversationParam::Id(id) => i::ResponseConversationParam {
46                    id,
47                    rest: Default::default(),
48                },
49                i::ConversationParam::Object(value) => value,
50            })
51        });
52        let cache = self.effective_prompt_cache_options.into_declared();
53        if let Some(requested) = request.prompt_cache_options {
54            let actual = cache
55                .as_ref()
56                .ok_or_else(|| TransformError::missing_metadata("response.prompt_cache_options"))?;
57            if requested.mode.is_some_and(|v| v != actual.mode)
58                || requested.ttl.is_some_and(|v| v != actual.ttl)
59            {
60                return Err(TransformError::shape(
61                    "response.prompt_cache_options",
62                    "effective cache settings contradict request",
63                ));
64            }
65        }
66        Ok(r::GenerateContentResponseBody {
67            access_programs: None,
68            prompt_cache_diagnostics: None,
69            id,
70            created_at,
71            model,
72            object: r::ResponseObject::Response,
73            error: None,
74            incomplete_details: None,
75            instructions: request.instructions.flatten().map(i::Input::Text),
76            metadata: request.metadata.flatten(),
77            output: Vec::new(),
78            parallel_tool_calls: self.effective_parallel_tool_calls,
79            temperature: request.temperature.flatten(),
80            tool_choice: choice,
81            tools: request.tools.unwrap_or_default(),
82            top_p: request.top_p.flatten(),
83            background: request.background,
84            completed_at: None,
85            conversation,
86            max_output_tokens: request.max_output_tokens,
87            max_tool_calls: request.max_tool_calls,
88            moderation: None,
89            output_text: None,
90            previous_response_id: request.previous_response_id,
91            prompt: request.prompt,
92            prompt_cache_key: request.prompt_cache_key,
93            prompt_cache_options: cache,
94            prompt_cache_retention: request.prompt_cache_retention,
95            reasoning: request.reasoning,
96            safety_identifier: request.safety_identifier,
97            service_tier: None,
98            status: None,
99            text: request.text,
100            top_logprobs: request.top_logprobs,
101            truncation: request.truncation,
102            usage: None,
103            user: request.user,
104            rest: Default::default(),
105        })
106    }
107}