Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bindings/typescript/src/generated/openai/ConnectorId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

/**
* Identifier for service connectors, like those available in ChatGPT. One of
* `server_url` or `connector_id` must be provided. Learn more about service
* connectors [here](/docs/guides/tools-remote-mcp#connectors).
* `server_url`, `connector_id`, or `tunnel_id` must be provided. Learn more
* about service connectors [here](/docs/guides/tools-remote-mcp#connectors).
*
* Currently supported `connector_id` values are:
*
Expand Down
13 changes: 9 additions & 4 deletions bindings/typescript/src/generated/openai/InputItemTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ search_context_size: WebSearchContextSize | null, user_location: ApproximateLoca
authorization: string | null,
/**
* Identifier for service connectors, like those available in ChatGPT. One of
* `server_url` or `connector_id` must be provided. Learn more about service
* connectors [here](/docs/guides/tools-remote-mcp#connectors).
* `server_url`, `connector_id`, or `tunnel_id` must be provided. Learn more
* about service connectors [here](/docs/guides/tools-remote-mcp#connectors).
*
* Currently supported `connector_id` values are:
*
Expand All @@ -206,10 +206,15 @@ server_description: string | null,
*/
server_label: string | null,
/**
* The URL for the MCP server. One of `server_url` or `connector_id` must be
* provided.
* The URL for the MCP server. One of `server_url`, `connector_id`, or
* `tunnel_id` must be provided.
*/
server_url: string | null,
/**
* The Secure MCP Tunnel ID to use instead of a direct server URL. One of
* `server_url`, `connector_id`, or `tunnel_id` must be provided.
*/
tunnel_id: string | null,
/**
* The code interpreter container. Can be a container ID or an object that
* specifies uploaded file IDs to make available to your code, along with an
Expand Down
25 changes: 22 additions & 3 deletions crates/generate-types/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ fn generate_openai_types_with_quicktype(
let quicktype_output = match output {
Ok(output) => {
if output.status.success() {
String::from_utf8(output.stdout)?
strip_quicktype_preamble(&String::from_utf8(output.stdout)?)
} else {
return Err(format!(
"quicktype failed: {}",
Expand Down Expand Up @@ -481,7 +481,7 @@ fn generate_anthropic_types_with_quicktype(
let quicktype_output = match output {
Ok(output) => {
if output.status.success() {
String::from_utf8(output.stdout)?
strip_quicktype_preamble(&String::from_utf8(output.stdout)?)
} else {
return Err(format!(
"quicktype failed: {}",
Expand Down Expand Up @@ -871,6 +871,23 @@ fn ensure_serde_json_imports(content: &str) -> String {
new_lines.join("\n")
}

fn strip_quicktype_preamble(raw_output: &str) -> String {
let lines: Vec<&str> = raw_output.lines().collect();
let first_rust_line = lines
.iter()
.position(|line| {
let trimmed = line.trim();
trimmed.starts_with("//")
|| trimmed.starts_with("extern")
|| trimmed.starts_with("use ")
|| trimmed.starts_with("#[")
|| trimmed.starts_with("#![")
|| trimmed.starts_with("pub ")
})
.unwrap_or(0);
lines[first_rust_line..].join("\n")
}

fn post_process_quicktype_output_for_anthropic(quicktype_output: &str) -> String {
let mut processed = quicktype_output.to_string();

Expand Down Expand Up @@ -1360,7 +1377,9 @@ fn generate_google_types_with_quicktype(spec: &serde_json::Value) {
let quicktype_output = match output {
Ok(output) => {
if output.status.success() {
String::from_utf8(output.stdout).expect("Invalid UTF-8 from quicktype")
strip_quicktype_preamble(
&String::from_utf8(output.stdout).expect("Invalid UTF-8 from quicktype"),
)
} else {
println!(
"❌ quicktype failed: {}",
Expand Down
72 changes: 50 additions & 22 deletions crates/lingua/src/providers/openai/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1819,16 +1819,12 @@ pub struct CreateResponseClass {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt: Option<Prompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<ResponseTextParam>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<CreateResponseToolChoiceParam>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<Truncation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_management: Option<Vec<ContextManagementParam>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation: Option<ConversationParam>,
Expand All @@ -1845,11 +1841,15 @@ pub struct CreateResponseClass {
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream_options: Option<ResponseStreamOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<Truncation>,
}

/// Context management configuration for this request.
Expand Down Expand Up @@ -3742,8 +3742,8 @@ pub struct InputItemTool {
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization: Option<String>,
/// Identifier for service connectors, like those available in ChatGPT. One of
/// `server_url` or `connector_id` must be provided. Learn more about service
/// connectors [here](/docs/guides/tools-remote-mcp#connectors).
/// `server_url`, `connector_id`, or `tunnel_id` must be provided. Learn more
/// about service connectors [here](/docs/guides/tools-remote-mcp#connectors).
///
/// Currently supported `connector_id` values are:
///
Expand All @@ -3767,10 +3767,14 @@ pub struct InputItemTool {
/// A label for this MCP server, used to identify it in tool calls.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_label: Option<String>,
/// The URL for the MCP server. One of `server_url` or `connector_id` must be
/// provided.
/// The URL for the MCP server. One of `server_url`, `connector_id`, or
/// `tunnel_id` must be provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_url: Option<String>,
/// The Secure MCP Tunnel ID to use instead of a direct server URL. One of
/// `server_url`, `connector_id`, or `tunnel_id` must be provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub tunnel_id: Option<String>,
/// The code interpreter container. Can be a container ID or an object that
/// specifies uploaded file IDs to make available to your code, along with an
/// optional `memory_limit` setting.
Expand Down Expand Up @@ -3884,8 +3888,8 @@ pub enum Background {
}

/// Identifier for service connectors, like those available in ChatGPT. One of
/// `server_url` or `connector_id` must be provided. Learn more about service
/// connectors [here](/docs/guides/tools-remote-mcp#connectors).
/// `server_url`, `connector_id`, or `tunnel_id` must be provided. Learn more
/// about service connectors [here](/docs/guides/tools-remote-mcp#connectors).
///
/// Currently supported `connector_id` values are:
///
Expand Down Expand Up @@ -4563,6 +4567,8 @@ pub struct Input {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export_to = "openai/")]
pub struct Reasoning {
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<Context>,
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -4571,6 +4577,20 @@ pub struct Reasoning {
pub summary: Option<Summary>,
}

/// Controls which reasoning items are rendered back to the model on later turns.
/// When returned on a response, this is the effective reasoning context mode
/// used for the response.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "openai/")]
pub enum Context {
#[serde(rename = "all_turns")]
AllTurns,
Auto,
#[serde(rename = "current_turn")]
CurrentTurn,
}

/// **Deprecated:** use `summary` instead.
///
/// A summary of the reasoning performed by the model. This can be
Expand Down Expand Up @@ -4974,8 +4994,8 @@ pub struct MCPTool {
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization: Option<String>,
/// Identifier for service connectors, like those available in ChatGPT. One of
/// `server_url` or `connector_id` must be provided. Learn more about service
/// connectors [here](/docs/guides/tools-remote-mcp#connectors).
/// `server_url`, `connector_id`, or `tunnel_id` must be provided. Learn more
/// about service connectors [here](/docs/guides/tools-remote-mcp#connectors).
///
/// Currently supported `connector_id` values are:
///
Expand Down Expand Up @@ -5003,10 +5023,14 @@ pub struct MCPTool {
pub server_description: Option<String>,
/// A label for this MCP server, used to identify it in tool calls.
pub server_label: String,
/// The URL for the MCP server. One of `server_url` or `connector_id` must be
/// provided.
/// The URL for the MCP server. One of `server_url`, `connector_id`, or
/// `tunnel_id` must be provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_url: Option<String>,
/// The Secure MCP Tunnel ID to use instead of a direct server URL. One of
/// `server_url`, `connector_id`, or `tunnel_id` must be provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub tunnel_id: Option<String>,
}

/// A tool that runs Python code to help generate a response to a prompt.
Expand Down Expand Up @@ -5243,14 +5267,10 @@ pub struct TheResponseObject {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt: Option<Prompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<ResponseTextParam>,
pub tool_choice: TheResponseObjectToolChoiceParam,
pub tools: Vec<Tool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<Truncation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation: Option<Conversation>,
Expand Down Expand Up @@ -5283,11 +5303,15 @@ pub struct TheResponseObject {
pub output_text: Option<String>,
/// Whether to allow the model to run tool calls in parallel.
pub parallel_tool_calls: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
/// The status of the response generation. One of `completed`, `failed`,
/// `in_progress`, `cancelled`, `queued`, or `incomplete`.
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<StatusEnum>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<Truncation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<ResponseUsage>,
}

Expand Down Expand Up @@ -6515,8 +6539,8 @@ pub struct OutputItemTool {
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization: Option<String>,
/// Identifier for service connectors, like those available in ChatGPT. One of
/// `server_url` or `connector_id` must be provided. Learn more about service
/// connectors [here](/docs/guides/tools-remote-mcp#connectors).
/// `server_url`, `connector_id`, or `tunnel_id` must be provided. Learn more
/// about service connectors [here](/docs/guides/tools-remote-mcp#connectors).
///
/// Currently supported `connector_id` values are:
///
Expand All @@ -6540,10 +6564,14 @@ pub struct OutputItemTool {
/// A label for this MCP server, used to identify it in tool calls.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_label: Option<String>,
/// The URL for the MCP server. One of `server_url` or `connector_id` must be
/// provided.
/// The URL for the MCP server. One of `server_url`, `connector_id`, or
/// `tunnel_id` must be provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub server_url: Option<String>,
/// The Secure MCP Tunnel ID to use instead of a direct server URL. One of
/// `server_url`, `connector_id`, or `tunnel_id` must be provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub tunnel_id: Option<String>,
/// The code interpreter container. Can be a container ID or an object that
/// specifies uploaded file IDs to make available to your code, along with an
/// optional `memory_limit` setting.
Expand Down
8 changes: 7 additions & 1 deletion crates/lingua/src/providers/openai/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ These structs use `#[serde(flatten)]` to automatically capture unknown fields,
eliminating the need for explicit KNOWN_KEYS arrays.
*/

use crate::providers::openai::generated::{ChatCompletionRequestMessage, Instructions, Summary};
use crate::providers::openai::generated::{
ChatCompletionRequestMessage, Context as ReasoningContext, Instructions, Summary,
};
use crate::serde_json::Value;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -221,8 +223,12 @@ pub enum OpenAIReasoningEffort {
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OpenAIReasoning {
pub effort: Option<OpenAIReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<Summary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_summary: Option<Summary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<ReasoningContext>,
}

#[cfg(test)]
Expand Down
62 changes: 62 additions & 0 deletions crates/lingua/src/providers/openai/responses_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,11 @@ impl ProviderAdapter for ResponsesAdapter {
if let Some(moderation) = typed_params.moderation {
extras_map.insert("moderation".into(), moderation);
}
if let Some(ref reasoning) = typed_params.reasoning {
if let Ok(reasoning_value) = serde_json::to_value(reasoning) {
extras_map.insert("reasoning".into(), reasoning_value);
Comment on lines +420 to +421

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid injecting null reasoning effort

When a Responses request has a reasoning object without effort (for example only summary or only the new context), serializing the typed OpenAIReasoning here stores "effort": null in extras because OpenAIReasoning::effort does not have skip_serializing_if. request_from_universal then prefers this raw extras value, so a same-provider round trip changes the original payload by adding an explicit null field, breaking exact/lossless round-trip behavior; preserve the raw reasoning value or skip None for effort as well.

Useful? React with 👍 / 👎.

}
}

if !extras_map.is_empty() {
params.extras.insert(ProviderFormat::Responses, extras_map);
Expand Down Expand Up @@ -2774,4 +2779,61 @@ mod tests {
Some("{\"query\":\"studio lights\"}")
);
}

#[test]
fn test_responses_reasoning_context_roundtrips() {
let adapter = ResponsesAdapter;
let payload = json!({
"model": "gpt-5-nano",
"input": [{"role": "user", "content": "Hello"}],
"reasoning": {
"effort": "high",
"summary": "auto",
"context": "all_turns"
}
});

let universal = adapter.request_to_universal(payload).unwrap();
let reconstructed = adapter.request_from_universal(&universal).unwrap();

let reasoning = reconstructed
.get("reasoning")
.expect("reasoning should be present");
assert_eq!(
reasoning.get("context").and_then(|c| c.as_str()),
Some("all_turns"),
"reasoning.context must roundtrip through universal"
);
assert_eq!(
reasoning.get("effort").and_then(|e| e.as_str()),
Some("high")
);
assert_eq!(
reasoning.get("summary").and_then(|s| s.as_str()),
Some("auto")
);
}

#[test]
fn test_responses_reasoning_context_absent_does_not_inject() {
let adapter = ResponsesAdapter;
let payload = json!({
"model": "gpt-5-nano",
"input": [{"role": "user", "content": "Hello"}],
"reasoning": {
"effort": "medium"
}
});

let universal = adapter.request_to_universal(payload).unwrap();
let reconstructed = adapter.request_from_universal(&universal).unwrap();

let reasoning = reconstructed
.get("reasoning")
.expect("reasoning should be present");
assert!(
reasoning.get("context").is_none(),
"reasoning.context should not be injected when absent from input"
);
}
}
1 change: 1 addition & 0 deletions crates/lingua/src/universal/reasoning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ mod tests {
effort: Some(OpenAIReasoningEffortParam::High),
summary: Some(OpenAISummary::Detailed),
generate_summary: None,
context: None,
};

// Test fallback conversion (uses DEFAULT_MAX_TOKENS)
Expand Down
Loading
Loading