diff --git a/.jp/config.toml b/.jp/config.toml index 73e173e1..d618ea62 100644 --- a/.jp/config.toml +++ b/.jp/config.toml @@ -33,10 +33,14 @@ opus6 = "anthropic/claude-opus-4-6" haiku = "anthropic/claude-haiku-4-5" zai = "cerebras/zai-glm-4.7" # OpenAI aliases -openai = "openai/gpt-5.5" -chatgpt = "openai/gpt-5.5" -gpt = "openai/gpt-5.5" -gpt5 = "openai/gpt-5.5" +openai = "openai/gpt-5.6-sol" +chatgpt = "openai/gpt-5.6-sol" +gpt = "openai/gpt-5.6-sol" +gpt5 = "openai/gpt-5.6-sol" +gpt55 = "openai/gpt-5.5" +sol = "openai/gpt-5.6-sol" +terra = "openai/gpt-5.6-terra" +luna = "openai/gpt-5.6-luna" gpt5-pro = "openai/gpt-5.5-pro" gpt-pro = "openai/gpt-5.5-pro" gpt5-mini = "openai/gpt-5.4-mini" diff --git a/Cargo.lock b/Cargo.lock index 905699a3..f9870b17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1013,7 +1013,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -1147,7 +1147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -3003,7 +3003,7 @@ checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "openai_responses" version = "0.1.6" -source = "git+https://github.com/JeanMertz/openai-responses-rs#2d462d32680e5fc0c52fdbc48e8ac5fb9d63029d" +source = "git+https://github.com/JeanMertz/openai-responses-rs#dac7d6992a213ca4ec13d41527ddd7f5c54e9885" dependencies = [ "async-fn-stream", "chrono", @@ -3735,7 +3735,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -4464,7 +4464,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -5290,7 +5290,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] diff --git a/crates/jp_llm/src/model.rs b/crates/jp_llm/src/model.rs index d0839d13..cff65171 100644 --- a/crates/jp_llm/src/model.rs +++ b/crates/jp_llm/src/model.rs @@ -147,12 +147,13 @@ impl ModelDetails { medium, high, xhigh, + max, }) => match config { // Off, so disabled. Some(ReasoningConfig::Off) => None, // Auto configured, so use medium effort if the model supports - // it, otherwise high or low. + // it, otherwise the nearest supported level. None | Some(ReasoningConfig::Auto) => Some(CustomReasoningConfig { effort: if medium { ReasoningEffort::Medium @@ -162,6 +163,8 @@ impl ModelDetails { ReasoningEffort::XHigh } else if low { ReasoningEffort::Low + } else if max { + ReasoningEffort::Max } else { ReasoningEffort::Xlow }, @@ -262,6 +265,10 @@ pub enum ReasoningDetails { /// Whether the model supports extremely high effort reasoning. xhigh: bool, + + /// Whether the model supports maximum effort reasoning, with no + /// constraints on token spending. + max: bool, }, /// Adaptive reasoning support. @@ -298,6 +305,7 @@ impl ReasoningDetails { medium: bool, high: bool, xhigh: bool, + max: bool, ) -> Self { Self::Leveled { none, @@ -306,6 +314,7 @@ impl ReasoningDetails { medium, high, xhigh, + max, } } @@ -352,6 +361,7 @@ impl ReasoningDetails { medium, high, xhigh, + max, } => { if *none { Some(ReasoningEffort::None) @@ -365,6 +375,8 @@ impl ReasoningDetails { Some(ReasoningEffort::High) } else if *xhigh { Some(ReasoningEffort::XHigh) + } else if *max { + Some(ReasoningEffort::Max) } else { None } @@ -373,6 +385,15 @@ impl ReasoningDetails { } } + /// Returns `true` if the model supports the `max` reasoning effort level. + #[must_use] + pub fn supports_max_effort(&self) -> bool { + matches!( + self, + Self::Leveled { max: true, .. } | Self::Adaptive { max: true, .. } + ) + } + #[must_use] pub fn is_unsupported(&self) -> bool { matches!(self, Self::Unsupported) @@ -393,3 +414,7 @@ impl ReasoningDetails { matches!(self, Self::Adaptive { .. }) } } + +#[cfg(test)] +#[path = "model_tests.rs"] +mod tests; diff --git a/crates/jp_llm/src/model_tests.rs b/crates/jp_llm/src/model_tests.rs new file mode 100644 index 00000000..14953f94 --- /dev/null +++ b/crates/jp_llm/src/model_tests.rs @@ -0,0 +1,43 @@ +use jp_config::model::parameters::{ReasoningConfig, ReasoningEffort}; + +use super::{ModelDetails, ReasoningDetails}; + +mod custom_reasoning_config { + use super::*; + + fn model(reasoning: ReasoningDetails) -> ModelDetails { + let mut details = ModelDetails::empty("openai/test-model".parse().unwrap()); + details.reasoning = Some(reasoning); + details + } + + /// A leveled model whose only supported level is `max` resolves `Auto` to + /// `max` instead of falling through to an unsupported level. + #[test] + fn auto_on_max_only_model_selects_max() { + let details = model(ReasoningDetails::leveled( + false, false, false, false, false, false, true, + )); + + let config = details + .custom_reasoning_config(Some(ReasoningConfig::Auto)) + .unwrap(); + + assert_eq!(config.effort, ReasoningEffort::Max); + } + + /// `max` is a last resort: any lower supported level wins in the `Auto` + /// selection. + #[test] + fn auto_prefers_lower_levels_over_max() { + let details = model(ReasoningDetails::leveled( + false, false, true, false, false, false, true, + )); + + let config = details + .custom_reasoning_config(Some(ReasoningConfig::Auto)) + .unwrap(); + + assert_eq!(config.effort, ReasoningEffort::Low); + } +} diff --git a/crates/jp_llm/src/provider/cerebras.rs b/crates/jp_llm/src/provider/cerebras.rs index ced80d4d..11628fac 100644 --- a/crates/jp_llm/src/provider/cerebras.rs +++ b/crates/jp_llm/src/provider/cerebras.rs @@ -228,7 +228,7 @@ fn map_model(id: &str) -> Result { context_window: Some(131_072), max_output_tokens: Some(40_960), reasoning: Some(ReasoningDetails::leveled( - false, false, true, true, true, false, + false, false, true, true, true, false, false, )), knowledge_cutoff: None, deprecated: None, @@ -242,7 +242,7 @@ fn map_model(id: &str) -> Result { max_output_tokens: Some(40_960), // Reasoning is enabled by default; only `none` disables it. reasoning: Some(ReasoningDetails::leveled( - true, false, false, false, false, false, + true, false, false, false, false, false, false, )), knowledge_cutoff: None, deprecated: None, diff --git a/crates/jp_llm/src/provider/google.rs b/crates/jp_llm/src/provider/google.rs index 294f0c7e..b1ef5909 100644 --- a/crates/jp_llm/src/provider/google.rs +++ b/crates/jp_llm/src/provider/google.rs @@ -245,6 +245,7 @@ fn create_request( medium, high, xhigh: _, + max: _, } => { let level = config .effort @@ -408,7 +409,7 @@ fn map_model(model: types::Model) -> ModelDetails { context_window, max_output_tokens, reasoning: Some(ReasoningDetails::leveled( - false, false, true, true, true, false, + false, false, true, true, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -422,7 +423,7 @@ fn map_model(model: types::Model) -> ModelDetails { context_window, max_output_tokens, reasoning: Some(ReasoningDetails::leveled( - false, false, true, false, true, false, + false, false, true, false, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -435,7 +436,7 @@ fn map_model(model: types::Model) -> ModelDetails { context_window, max_output_tokens, reasoning: Some(ReasoningDetails::leveled( - false, true, true, true, true, false, + false, true, true, true, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), diff --git a/crates/jp_llm/src/provider/openai.rs b/crates/jp_llm/src/provider/openai.rs index 40ec116e..e47eb2b6 100644 --- a/crates/jp_llm/src/provider/openai.rs +++ b/crates/jp_llm/src/provider/openai.rs @@ -11,7 +11,7 @@ use jp_config::{ conversation::tool::{OneOrManyTypes, ToolParameterConfig}, model::{ id::{Name, ProviderId}, - parameters::{CustomReasoningConfig, ReasoningEffort}, + parameters::{CustomReasoningConfig, ReasoningConfig, ReasoningEffort}, }, providers::llm::openai::OpenaiConfig, }; @@ -57,6 +57,24 @@ const TEMP_REQUIRES_NO_REASONING: &str = "temp_requires_no_reasoning"; /// Feature flag: the model only supports non-streaming Responses API requests. const STREAMING_UNSUPPORTED: &str = "streaming_unsupported"; +/// Feature flag: the model accepts `reasoning.mode: "pro"` in the Responses +/// API. +/// Models without this flag reject the field, so pro mode is skipped (with a +/// warning) when configured. +const REASONING_PRO_MODE: &str = "reasoning_pro_mode"; + +/// Feature flag: the model supports persisted reasoning via +/// `reasoning.context`. +/// When set, requests ask for `all_turns` so the model renders the replayed +/// encrypted reasoning items from earlier turns into the next sample. +const PERSISTED_REASONING: &str = "persisted_reasoning"; + +/// Feature flag: the model accepts explicit prompt-cache fields +/// (`prompt_cache_options`, `prompt_cache_breakpoint`). +/// Models without this flag reject the fields with a 400, so they are only sent +/// when the flag is present. +const EXPLICIT_PROMPT_CACHING: &str = "explicit_prompt_caching"; + /// How often to inject a synthetic keep-alive while a tool call is streaming. /// /// OpenAI emits the `function_call_arguments` deltas for a large tool call as a @@ -316,7 +334,16 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo tool_choice, } = query; - let parameters = thread.events.config()?.assistant.model.parameters; + let config = thread.events.config()?; + let cache_policy = config.assistant.request.cache; + let parameters = config.assistant.model.parameters; + + // Stable cache identity for this conversation. On load the stream's + // creation timestamp is derived from the conversation ID, so every + // request in a conversation produces the same key. A fork is a new + // conversation with its own timestamp: its first request misses the + // parent's warm cache and starts a cache lineage of its own. + let conversation_created_at = thread.events.created_at; // Parse verbosity from the catch-all parameters map. let verbosity = parameters @@ -333,6 +360,15 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo } }); + // Parse the OpenAI-specific reasoning execution mode from the catch-all + // parameters map. `pro` performs more model work before returning a + // single final answer, at the cost of latency and token usage. + let reasoning_mode = parameters + .other + .get("reasoning_mode") + .and_then(|v| v.as_str()) + .and_then(|s| parse_reasoning_mode(s, model)); + // Build the text config from structured output schema and/or verbosity. // Transform the schema for OpenAI's strict structured output mode. let text = match thread.events.schema() { @@ -356,29 +392,44 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo }; let is_structured = text.is_some(); + + // Models with unknown reasoning support (absent from the catalog, e.g. + // released after this binary was built) are assumed to accept reasoning + // request configuration, so an explicit `reasoning = "off"` sends + // `effort: none` rather than silently accepting the model's reasoning-on + // default. Without explicit configuration the field is omitted entirely: + // a non-reasoning deployment (e.g. a fine-tuned chat model) may reject it. + // Conversation replay is independent of this flag: namespaced OpenAI item + // metadata determines whether a stored event has a native representation. let supports_reasoning = model .reasoning - .is_some_and(|v| !matches!(v, ReasoningDetails::Unsupported)); - let reasoning = match model.custom_reasoning_config(parameters.reasoning) { - Some(r) => Some(convert_reasoning(r, model.max_output_tokens)), + .is_none_or(|v| !matches!(v, ReasoningDetails::Unsupported)); + let mut reasoning = match model.custom_reasoning_config(parameters.reasoning) { + Some(r) => Some(convert_reasoning(r, model)), // Explicitly disable reasoning for models that support it when the // user has turned it off. Sending `null` lets the model use its // default (which may include reasoning). // - // For leveled models, use their lowest supported effort. For all - // others (budgetted), fall back to `minimal` which is universally - // supported across OpenAI reasoning models. - None if supports_reasoning => { - let effort = model - .reasoning - .and_then(|r| r.lowest_effort()) - .unwrap_or(ReasoningEffort::Xlow); + // For leveled models, use their lowest supported effort. Budgetted + // models fall back to `minimal`, which every cataloged OpenAI + // reasoning model accepts. Unknown models are newer than this binary, + // and every OpenAI flagship since GPT-5.1 accepts `none`, so honor + // "off" literally rather than spending reasoning tokens at the + // model's default effort. + None if supports_reasoning + && (model.reasoning.is_some() + || matches!(parameters.reasoning, Some(ReasoningConfig::Off))) => + { + let effort = match model.reasoning { + None => ReasoningEffort::None, + Some(r) => r.lowest_effort().unwrap_or(ReasoningEffort::Xlow), + }; Some(convert_reasoning( CustomReasoningConfig { effort, exclude: true, }, - model.max_output_tokens, + model, )) } None => None, @@ -386,17 +437,35 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo let reasoning_enabled = model .custom_reasoning_config(parameters.reasoning) .is_some(); + + if reasoning_enabled && let Some(r) = reasoning.as_mut() { + r.mode = reasoning_mode; + + // JP replays the complete event history — including encrypted + // reasoning items — on every request, so ask supporting models to + // render reasoning from earlier turns into the next sample. + if model.features.contains(&PERSISTED_REASONING) { + r.context = Some(types::ReasoningContext::AllTurns); + } + } + + let cache_enabled = !cache_policy.is_off(); + let explicit_cache = cache_enabled && model.features.contains(&EXPLICIT_PROMPT_CACHING); + let parts = thread.into_parts(); let mut messages = vec![]; - messages.push(to_system_messages(parts.system_parts).0); + messages.push(to_system_messages(parts.system_parts, explicit_cache).0); // All attachments go in a user message before conversation events. let mut attachment_items = vec![]; // Text attachments as XML to preserve source metadata. if let Some(xml) = text_attachments_to_xml(&parts.attachments)? { - attachment_items.push(types::ContentItem::Text { text: xml }); + attachment_items.push(types::ContentItem::Text { + text: xml, + prompt_cache_breakpoint: None, + }); } // Binary attachments, each preceded by a label. @@ -406,6 +475,7 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo attachment_items.push(types::ContentItem::Text { text: format!("[Attached file: {}]", attachment.source), + prompt_cache_breakpoint: None, }); if media_type.starts_with("image/") { @@ -413,12 +483,14 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo detail: types::ImageDetail::Auto, file_id: None, image_url: Some(format!("data:{media_type};base64,{b64}")), + prompt_cache_breakpoint: None, }); } else if media_type == "application/pdf" { attachment_items.push(types::ContentItem::File { file_data: Some(format!("data:{media_type};base64,{b64}")), file_id: None, filename: Some(attachment.source.clone()), + prompt_cache_breakpoint: None, }); } else { warn!( @@ -431,6 +503,12 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo } if !attachment_items.is_empty() { + // Extend the cached stable prefix (system prompt + attachments) up to + // the last attachment block. + if explicit_cache && let Some(item) = attachment_items.last_mut() { + set_cache_breakpoint(item); + } + messages.push(types::InputListItem::Message(types::InputMessage { role: types::Role::User, content: types::ContentInput::List(attachment_items), @@ -439,8 +517,11 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo } // GPT-5 family models reject temperature/top_p when reasoning is active - // (any effort other than `none`). Strip them and warn if configured. - let strip_temp = model.features.contains(&TEMP_REQUIRES_NO_REASONING) + // (any effort other than `none`). Models absent from the catalog are + // newer than this binary and assumed to share the constraint. Strip the + // parameters and warn if configured. + let strip_temp = (model.features.contains(&TEMP_REQUIRES_NO_REASONING) + || model.reasoning.is_none()) && reasoning .as_ref() .is_some_and(|r| !matches!(r.effort, Some(types::ReasoningEffort::None))); @@ -467,7 +548,7 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo parameters.top_p }; - messages.extend(convert_events(supports_reasoning)(parts.events)); + messages.extend(convert_events(parts.events)); let request = Request { model: types::Model::Other(model.id.name.to_string()), input: types::Input::List(messages), @@ -481,6 +562,25 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo truncation: Some(types::Truncation::Auto), top_p, text, + // OpenAI routes requests by prompt prefix; a stable per-conversation + // key improves cache-hit rates, and GPT-5.6+ models require it for + // reliable cache matching. + prompt_cache_key: cache_enabled.then(|| { + format!( + "jp:conversation:{}", + conversation_created_at.timestamp_micros() + ) + }), + // Explicit mode with no marked breakpoints disables cache reads and + // writes; the only way to opt out of caching on models that bill + // cache writes. Models without the feature flag cache automatically + // and at no extra cost, so there is nothing to disable for them. + prompt_cache_options: (cache_policy.is_off() + && model.features.contains(&EXPLICIT_PROMPT_CACHING)) + .then_some(types::PromptCacheOptions { + mode: Some(types::PromptCacheMode::Explicit), + ttl: None, + }), ..Default::default() }; @@ -496,13 +596,68 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo #[expect(clippy::too_many_lines)] fn map_model(model: ModelResponse) -> Result { let details = match model.id.as_str() { + "gpt-5.6" | "gpt-5.6-sol" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.6 Sol".to_owned()), + context_window: Some(1_050_000), + max_output_tokens: Some(128_000), + // Reasoning.effort supports: none, low, medium, high, xhigh, max. + reasoning: Some(ReasoningDetails::leveled( + true, false, true, true, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2026, 2, 16).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![ + TEMP_REQUIRES_NO_REASONING, + REASONING_PRO_MODE, + PERSISTED_REASONING, + EXPLICIT_PROMPT_CACHING, + ], + }, + "gpt-5.6-terra" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.6 Terra".to_owned()), + context_window: Some(1_050_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + true, false, true, true, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2026, 2, 16).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![ + TEMP_REQUIRES_NO_REASONING, + REASONING_PRO_MODE, + PERSISTED_REASONING, + EXPLICIT_PROMPT_CACHING, + ], + }, + "gpt-5.6-luna" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.6 Luna".to_owned()), + context_window: Some(1_050_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + true, false, true, true, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2026, 2, 16).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![ + TEMP_REQUIRES_NO_REASONING, + REASONING_PRO_MODE, + PERSISTED_REASONING, + EXPLICIT_PROMPT_CACHING, + ], + }, "gpt-5.5" | "gpt-5.5-2026-04-23" => ModelDetails { id: (PROVIDER, model.id).try_into()?, display_name: Some("GPT-5.5".to_owned()), context_window: Some(1_050_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, true, + true, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 12, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -515,7 +670,7 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(1_050_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - false, false, false, true, true, true, + false, false, false, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 12, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -528,7 +683,7 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(1_050_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, true, + true, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -541,7 +696,7 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(1_050_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - false, false, false, true, true, true, + false, false, false, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -554,7 +709,7 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(400_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, true, + true, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -567,7 +722,7 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(400_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, true, + true, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -580,7 +735,7 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(400_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - false, false, true, true, true, true, + false, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -593,10 +748,13 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(128_000), max_output_tokens: Some(16_384), reasoning: Some(ReasoningDetails::leveled( - false, false, true, true, true, true, + false, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 8, 10).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -607,10 +765,13 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), // Reasoning.effort supports: low, medium, high, xhigh (no none) reasoning: Some(ReasoningDetails::leveled( - false, false, true, true, true, true, + false, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -620,7 +781,7 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(400_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - false, false, false, true, true, true, + false, false, false, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -634,7 +795,7 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), // Reasoning.effort supports: none (default), low, medium, high, xhigh reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, true, + true, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -647,10 +808,13 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(128_000), max_output_tokens: Some(16_384), reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, true, + true, false, true, true, true, true, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 8, 10).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -661,7 +825,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -672,7 +839,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -683,7 +853,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.4-mini", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -694,7 +867,7 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), // Reasoning.effort supports: none (default), low, medium, high reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, false, + true, false, true, true, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), deprecated: Some(ModelDeprecation::Active), @@ -707,10 +880,13 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(128_000), max_output_tokens: Some(16_384), reasoning: Some(ReasoningDetails::leveled( - true, false, true, true, true, false, + true, false, true, true, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -721,34 +897,62 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, - "gpt-5" | "gpt-5-2025-08-07" => ModelDetails { + "gpt-5" => ModelDetails { id: (PROVIDER, model.id).try_into()?, display_name: Some("GPT-5".to_owned()), context_window: Some(400_000), max_output_tokens: Some(128_000), // Reasoning.effort supports: minimal, low, medium, high reasoning: Some(ReasoningDetails::leveled( - false, true, true, true, true, false, + false, true, true, true, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + // Deprecated without an announced retirement date; only the + // 2025-08-07 snapshot has a scheduled shutdown (2026-12-11). + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + None, + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, - "gpt-5-pro" => ModelDetails { + "gpt-5-2025-08-07" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5".to_owned()), + context_window: Some(400_000), + max_output_tokens: Some(128_000), + // Reasoning.effort supports: minimal, low, medium, high + reasoning: Some(ReasoningDetails::leveled( + false, true, true, true, true, false, false, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 12, 11).unwrap()), + )), + structured_output: None, + features: vec![TEMP_REQUIRES_NO_REASONING], + }, + "gpt-5-pro" | "gpt-5-pro-2025-10-06" => ModelDetails { id: (PROVIDER, model.id).try_into()?, display_name: Some("GPT-5 pro".to_owned()), context_window: Some(400_000), max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::leveled( - false, false, false, false, true, false, + false, false, false, false, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5-pro", + Some(NaiveDate::from_ymd_opt(2026, 12, 11).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -758,10 +962,13 @@ fn map_model(model: ModelResponse) -> Result { context_window: Some(128_000), max_output_tokens: Some(16_384), reasoning: Some(ReasoningDetails::leveled( - false, true, true, true, true, false, + false, true, true, true, true, false, false, )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 9, 30).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -772,7 +979,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 5, 31).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.4-mini", + Some(NaiveDate::from_ymd_opt(2026, 12, 11).unwrap()), + )), structured_output: None, features: vec![TEMP_REQUIRES_NO_REASONING], }, @@ -783,7 +993,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(128_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 5, 31).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.4-nano", + Some(NaiveDate::from_ymd_opt(2026, 12, 11).unwrap()), + )), structured_output: None, features: vec![], }, @@ -794,7 +1007,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.4-mini", + Some(NaiveDate::from_ymd_opt(2026, 10, 23).unwrap()), + )), structured_output: None, features: vec![], }, @@ -805,20 +1021,9 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2023, 10, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), - structured_output: None, - features: vec![], - }, - "o1-mini" | "o1-mini-2024-09-12" => ModelDetails { - id: (PROVIDER, model.id).try_into()?, - display_name: Some("o1-mini".to_owned()), - context_window: Some(128_000), - max_output_tokens: Some(65_536), - reasoning: Some(ReasoningDetails::budgetted(0, None)), - knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2023, 10, 1).unwrap()), deprecated: Some(ModelDeprecation::deprecated( - &"recommended replacement: o4-mini", - Some(NaiveDate::from_ymd_opt(2025, 10, 27).unwrap()), + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 10, 23).unwrap()), )), structured_output: None, features: vec![], @@ -830,7 +1035,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 12, 11).unwrap()), + )), structured_output: None, features: vec![], }, @@ -841,7 +1049,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5-pro", + Some(NaiveDate::from_ymd_opt(2026, 12, 11).unwrap()), + )), structured_output: None, features: vec![], }, @@ -852,7 +1063,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2023, 10, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + Some(NaiveDate::from_ymd_opt(2026, 10, 23).unwrap()), + )), structured_output: None, features: vec![], }, @@ -863,7 +1077,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2023, 10, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5-pro", + Some(NaiveDate::from_ymd_opt(2026, 10, 23).unwrap()), + )), structured_output: None, features: vec![], }, @@ -878,27 +1095,18 @@ fn map_model(model: ModelResponse) -> Result { structured_output: None, features: vec![], }, - "gpt-4o" | "gpt-4o-2024-08-06" => ModelDetails { + "gpt-4o" | "gpt-4o-2024-08-06" | "gpt-4o-2024-11-20" => ModelDetails { id: (PROVIDER, model.id).try_into()?, display_name: Some("GPT-4o".to_owned()), context_window: Some(128_000), max_output_tokens: Some(16_384), reasoning: Some(ReasoningDetails::unsupported()), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2023, 10, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), - structured_output: None, - features: vec![], - }, - "chatgpt-4o" | "chatgpt-4o-latest" => ModelDetails { - id: (PROVIDER, model.id).try_into()?, - display_name: Some("ChatGPT-4o".to_owned()), - context_window: Some(128_000), - max_output_tokens: Some(16_384), - reasoning: Some(ReasoningDetails::unsupported()), - knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2023, 10, 1).unwrap()), + // Deprecated without an announced retirement date; only the + // 2024-05-13 snapshot has a scheduled shutdown (2026-10-23). deprecated: Some(ModelDeprecation::deprecated( - &"recommended replacement: gpt-5.1-chat-latest", - Some(NaiveDate::from_ymd_opt(2026, 2, 11).unwrap()), + &"recommended replacement: gpt-5.5", + None, )), structured_output: None, features: vec![], @@ -910,7 +1118,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(32_768), reasoning: Some(ReasoningDetails::unsupported()), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.4-nano", + Some(NaiveDate::from_ymd_opt(2026, 10, 23).unwrap()), + )), structured_output: None, features: vec![], }, @@ -965,7 +1176,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5-pro", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![], }, @@ -976,7 +1190,10 @@ fn map_model(model: ModelResponse) -> Result { max_output_tokens: Some(100_000), reasoning: Some(ReasoningDetails::budgetted(0, None)), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()), - deprecated: Some(ModelDeprecation::Active), + deprecated: Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5-pro", + Some(NaiveDate::from_ymd_opt(2026, 7, 23).unwrap()), + )), structured_output: None, features: vec![], }, @@ -1668,20 +1885,53 @@ fn convert_tools(tools: Vec) -> Vec { .collect() } +/// Parse an OpenAI reasoning execution mode value (`standard` or `pro`). +/// +/// `pro` is returned only when the model supports it; unsupported models fall +/// back to standard mode, with a warning. +/// `standard` returns `None`, since it is the API default. +fn parse_reasoning_mode(value: &str, model: &ModelDetails) -> Option { + match value { + "standard" => None, + "pro" if model.features.contains(&REASONING_PRO_MODE) => Some(types::ReasoningMode::Pro), + "pro" => { + warn!( + model = %model.id, + "Model does not support pro reasoning mode; using standard mode." + ); + None + } + _ => { + warn!( + reasoning_mode = value, + "Unknown reasoning_mode value, ignoring." + ); + None + } + } +} + +/// Convert the reasoning configuration to the OpenAI wire format. +/// +/// The `max` effort is only sent to models that support it; others degrade to +/// `xhigh`. fn convert_reasoning( reasoning: CustomReasoningConfig, - max_tokens: Option, + model: &ModelDetails, ) -> types::ReasoningConfig { + let supports_max = model.reasoning.is_some_and(|r| r.supports_max_effort()); + // Always request reasoning summaries so they're captured in the // conversation. The display layer handles visibility. types::ReasoningConfig { summary: Some(SummaryConfig::Auto), effort: match reasoning .effort - .abs_to_rel(max_tokens) + .abs_to_rel(model.max_output_tokens) .unwrap_or(ReasoningEffort::Auto) { ReasoningEffort::None => Some(types::ReasoningEffort::None), + ReasoningEffort::Max if supports_max => Some(types::ReasoningEffort::Max), ReasoningEffort::Max | ReasoningEffort::XHigh => Some(types::ReasoningEffort::XHigh), ReasoningEffort::High => Some(types::ReasoningEffort::High), ReasoningEffort::Auto | ReasoningEffort::Medium => Some(types::ReasoningEffort::Medium), @@ -1692,6 +1942,8 @@ fn convert_reasoning( None } }, + mode: None, + context: None, } } @@ -1706,19 +1958,52 @@ impl IntoIterator for ListItem { } } -fn to_system_messages(parts: Vec) -> ListItem { +fn to_system_messages(parts: Vec, cache_breakpoint: bool) -> ListItem { + let mut items: Vec<_> = parts + .into_iter() + .map(|text| types::ContentItem::Text { + text, + prompt_cache_breakpoint: None, + }) + .collect(); + + // Cache the system-prompt prefix. The growing conversation tail is + // covered by the implicit breakpoint on the latest message. + if cache_breakpoint && let Some(item) = items.last_mut() { + set_cache_breakpoint(item); + } + ListItem(types::InputListItem::Message(types::InputMessage { role: types::Role::System, - content: types::ContentInput::List( - parts - .into_iter() - .map(|text| types::ContentItem::Text { text }) - .collect(), - ), + content: types::ContentInput::List(items), phase: None, })) } +/// Mark a content block as the end of a cacheable prompt prefix. +/// +/// The breakpoint covers the block itself and all prompt content rendered +/// before it; content after it can change without invalidating the cached +/// prefix. +fn set_cache_breakpoint(item: &mut types::ContentItem) { + let (types::ContentItem::Text { + prompt_cache_breakpoint, + .. + } + | types::ContentItem::Image { + prompt_cache_breakpoint, + .. + } + | types::ContentItem::File { + prompt_cache_breakpoint, + .. + }) = item; + + *prompt_cache_breakpoint = Some(types::PromptCacheBreakpoint { + mode: types::PromptCacheBreakpointMode::Explicit, + }); +} + /// Parse a phase string from metadata into the API type. fn parse_phase(metadata: &mut Map) -> Option { metadata @@ -1732,117 +2017,119 @@ fn parse_phase(metadata: &mut Map) -> Option { } #[expect(clippy::too_many_lines)] -fn convert_events( - supports_reasoning: bool, -) -> impl Fn(ConversationStream) -> Vec { - move |events| { - events - .into_iter() - .flat_map(|event| { - let ConversationEvent { - kind, mut metadata, .. - } = event.event; - - match kind { - EventKind::ChatRequest(request) => { - vec![types::InputListItem::Message(types::InputMessage { - role: types::Role::User, - content: types::ContentInput::Text(request.content), - phase: None, - })] - } - EventKind::ChatResponse(response) => { - let id = metadata - .remove(ITEM_ID_KEY) - .and_then(|v| v.as_str().map(str::to_owned)); - - let encrypted_content = metadata - .remove(ENCRYPTED_CONTENT_KEY) - .and_then(|v| v.as_str().map(str::to_owned)); - - let phase = parse_phase(&mut metadata); - - match response { - ChatResponse::Reasoning { reasoning } => { - if supports_reasoning && let Some(id) = id { - vec![types::InputListItem::Item(types::InputItem::Reasoning( - types::Reasoning { - id, - summary: vec![types::ReasoningSummary::Text { - text: reasoning, - }], - encrypted_content, - status: None, - }, - ))] - } else { - // Unsupported reasoning content - wrap in XML tags - vec![types::InputListItem::Message(types::InputMessage { - role: types::Role::Assistant, - content: types::ContentInput::Text(format!( - "\n{reasoning}\n\n\n", - )), - phase, - })] - } +fn convert_events(events: ConversationStream) -> Vec { + events + .into_iter() + .flat_map(|event| { + let ConversationEvent { + kind, mut metadata, .. + } = event.event; + + match kind { + EventKind::ChatRequest(request) => { + vec![types::InputListItem::Message(types::InputMessage { + role: types::Role::User, + content: types::ContentInput::Text(request.content), + phase: None, + })] + } + EventKind::ChatResponse(response) => { + let id = metadata + .remove(ITEM_ID_KEY) + .and_then(|v| v.as_str().map(str::to_owned)); + + let encrypted_content = metadata + .remove(ENCRYPTED_CONTENT_KEY) + .and_then(|v| v.as_str().map(str::to_owned)); + + let phase = parse_phase(&mut metadata); + + match response { + ChatResponse::Reasoning { reasoning } => { + if let Some(id) = id { + // The namespaced OpenAI item id is proof that + // this event originated as a native reasoning + // item. Preserve that representation and let + // the Responses API decide whether it is + // compatible with the target model. + vec![types::InputListItem::Item(types::InputItem::Reasoning( + types::Reasoning { + id, + summary: vec![types::ReasoningSummary::Text { + text: reasoning, + }], + encrypted_content, + status: None, + }, + ))] + } else { + // Reasoning from another provider has no + // OpenAI-native representation. + vec![types::InputListItem::Message(types::InputMessage { + role: types::Role::Assistant, + content: types::ContentInput::Text(format!( + "\n{reasoning}\n\n\n", + )), + phase, + })] } - ChatResponse::Message { message } => { - if let Some(id) = id { - vec![types::InputListItem::Item( - types::InputItem::OutputMessage(types::OutputMessage { - id, - role: types::Role::Assistant, - content: vec![types::OutputContent::Text { - text: message, - annotations: vec![], - }], - status: types::MessageStatus::Completed, - phase, - }), - )] - } else { - vec![types::InputListItem::Message(types::InputMessage { + } + ChatResponse::Message { message } => { + if let Some(id) = id { + vec![types::InputListItem::Item(types::InputItem::OutputMessage( + types::OutputMessage { + id, role: types::Role::Assistant, - content: types::ContentInput::Text(message), + content: vec![types::OutputContent::Text { + text: message, + annotations: vec![], + }], + status: types::MessageStatus::Completed, phase, - })] - } - } - ChatResponse::Structured { data } => { + }, + ))] + } else { vec![types::InputListItem::Message(types::InputMessage { role: types::Role::Assistant, - content: types::ContentInput::Text(data.to_string()), + content: types::ContentInput::Text(message), phase, })] } } + ChatResponse::Structured { data } => { + vec![types::InputListItem::Message(types::InputMessage { + role: types::Role::Assistant, + content: types::ContentInput::Text(data.to_string()), + phase, + })] + } } - EventKind::ToolCallRequest(request) => vec![types::InputListItem::Item( - types::InputItem::FunctionCall(types::FunctionCall { - call_id: request.id, - name: request.name, - arguments: Value::Object(request.arguments).to_string(), - status: None, + } + EventKind::ToolCallRequest(request) => vec![types::InputListItem::Item( + types::InputItem::FunctionCall(types::FunctionCall { + call_id: request.id, + name: request.name, + arguments: Value::Object(request.arguments).to_string(), + status: None, + id: None, + }), + )], + EventKind::ToolCallResponse(ToolCallResponse { id, result }) => { + vec![types::InputListItem::Item( + types::InputItem::FunctionCallOutput(types::FunctionCallOutput { + call_id: id, + output: match result { + Ok(content) | Err(content) => content, + }, id: None, + status: None, }), - )], - EventKind::ToolCallResponse(ToolCallResponse { id, result }) => { - vec![types::InputListItem::Item( - types::InputItem::FunctionCallOutput(types::FunctionCallOutput { - call_id: id, - output: match result { - Ok(content) | Err(content) => content, - }, - id: None, - status: None, - }), - )] - } - _ => vec![], + )] } - }) - .collect() - } + _ => vec![], + } + }) + .collect() } impl From for Error { diff --git a/crates/jp_llm/src/provider/openai_tests.rs b/crates/jp_llm/src/provider/openai_tests.rs index 4a5e9f87..b0b0eb23 100644 --- a/crates/jp_llm/src/provider/openai_tests.rs +++ b/crates/jp_llm/src/provider/openai_tests.rs @@ -742,7 +742,8 @@ mod map_model { use chrono::{TimeZone as _, Utc}; use super::super::{ - ModelResponse, STREAMING_UNSUPPORTED, TEMP_REQUIRES_NO_REASONING, map_model, + EXPLICIT_PROMPT_CACHING, ModelResponse, PERSISTED_REASONING, REASONING_PRO_MODE, + STREAMING_UNSUPPORTED, TEMP_REQUIRES_NO_REASONING, map_model, }; use crate::model::{ModelDeprecation, ReasoningDetails}; @@ -755,6 +756,83 @@ mod map_model { } } + #[test] + fn gpt_5_6_sol_uses_latest_metadata() { + let details = map_model(model("gpt-5.6-sol")).unwrap(); + + assert_eq!(details.display_name.as_deref(), Some("GPT-5.6 Sol")); + assert_eq!(details.context_window, Some(1_050_000)); + assert_eq!(details.max_output_tokens, Some(128_000)); + assert_eq!( + details.reasoning, + Some(ReasoningDetails::leveled( + true, false, true, true, true, true, true, + )) + ); + assert_eq!( + details.knowledge_cutoff, + chrono::NaiveDate::from_ymd_opt(2026, 2, 16) + ); + assert_eq!(details.deprecated, Some(ModelDeprecation::Active)); + assert_eq!(details.features, vec![ + TEMP_REQUIRES_NO_REASONING, + REASONING_PRO_MODE, + PERSISTED_REASONING, + EXPLICIT_PROMPT_CACHING + ]); + } + + #[test] + fn gpt_5_6_alias_resolves_to_sol_metadata() { + let details = map_model(model("gpt-5.6")).unwrap(); + + assert_eq!(details.display_name.as_deref(), Some("GPT-5.6 Sol")); + } + + #[test] + fn gpt_5_6_terra_uses_latest_metadata() { + let details = map_model(model("gpt-5.6-terra")).unwrap(); + + assert_eq!(details.display_name.as_deref(), Some("GPT-5.6 Terra")); + assert_eq!(details.context_window, Some(1_050_000)); + assert_eq!(details.max_output_tokens, Some(128_000)); + assert_eq!( + details.reasoning, + Some(ReasoningDetails::leveled( + true, false, true, true, true, true, true, + )) + ); + assert_eq!(details.deprecated, Some(ModelDeprecation::Active)); + assert_eq!(details.features, vec![ + TEMP_REQUIRES_NO_REASONING, + REASONING_PRO_MODE, + PERSISTED_REASONING, + EXPLICIT_PROMPT_CACHING + ]); + } + + #[test] + fn gpt_5_6_luna_uses_latest_metadata() { + let details = map_model(model("gpt-5.6-luna")).unwrap(); + + assert_eq!(details.display_name.as_deref(), Some("GPT-5.6 Luna")); + assert_eq!(details.context_window, Some(1_050_000)); + assert_eq!(details.max_output_tokens, Some(128_000)); + assert_eq!( + details.reasoning, + Some(ReasoningDetails::leveled( + true, false, true, true, true, true, true, + )) + ); + assert_eq!(details.deprecated, Some(ModelDeprecation::Active)); + assert_eq!(details.features, vec![ + TEMP_REQUIRES_NO_REASONING, + REASONING_PRO_MODE, + PERSISTED_REASONING, + EXPLICIT_PROMPT_CACHING + ]); + } + #[test] fn gpt_5_5_uses_latest_metadata() { let details = map_model(model("gpt-5.5")).unwrap(); @@ -765,7 +843,7 @@ mod map_model { assert_eq!( details.reasoning, Some(ReasoningDetails::leveled( - true, false, true, true, true, true, + true, false, true, true, true, true, false, )) ); assert_eq!( @@ -786,7 +864,7 @@ mod map_model { assert_eq!( details.reasoning, Some(ReasoningDetails::leveled( - false, false, false, true, true, true, + false, false, false, true, true, true, false, )) ); assert_eq!( @@ -799,6 +877,373 @@ mod map_model { STREAMING_UNSUPPORTED ]); } + + /// The retirement notice names only the dated snapshot: the rolling `gpt-5` + /// alias carries the migration note without a date, while the snapshot + /// carries the announced shutdown date. + #[test] + fn gpt_5_alias_deprecated_without_retirement_date() { + let alias = map_model(model("gpt-5")).unwrap(); + assert_eq!( + alias.deprecated, + Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + None, + )) + ); + + let snapshot = map_model(model("gpt-5-2025-08-07")).unwrap(); + assert_eq!( + snapshot.deprecated, + Some(ModelDeprecation::deprecated( + &"recommended replacement: gpt-5.5", + chrono::NaiveDate::from_ymd_opt(2026, 12, 11), + )) + ); + } +} + +mod unknown_model { + use chrono::{TimeZone as _, Utc}; + use jp_config::{ + AppConfig, + model::{ + id::{ModelIdConfig, ModelIdOrAliasConfig, ProviderId}, + parameters::ReasoningConfig, + }, + providers::llm::LlmProviderConfig, + }; + use jp_conversation::{ + ConversationStream, + event::{ChatRequest, ChatResponse, ConversationEvent, TurnStart}, + thread::ThreadBuilder, + }; + + use super::super::{ENCRYPTED_CONTENT_KEY, ITEM_ID_KEY}; + use crate::{ + model::{ModelDetails, ReasoningDetails}, + provider::build_request_value, + query::ChatQuery, + }; + + /// A model absent from the catalog (e.g. released after this binary was + /// built) replays stored reasoning events that carry OpenAI item ids as + /// native reasoning items — not as `` fallback text, which the + /// model would mimic in its visible output. + #[test] + fn replays_reasoning_natively() { + let ts = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + + let mut config = AppConfig::new_test(); + config.assistant.model.id = ModelIdOrAliasConfig::Id(ModelIdConfig { + provider: ProviderId::Openai, + name: "gpt-9".parse().unwrap(), + }); + + let mut stream = ConversationStream::new(config.into()).with_created_at(ts); + stream.extend([ + ConversationEvent::new(TurnStart, ts), + ConversationEvent::new(ChatRequest::from("First question"), ts), + ConversationEvent::new(ChatResponse::reasoning("Earlier reasoning."), ts) + .with_metadata_field(ITEM_ID_KEY, "rs_123") + .with_metadata_field(ENCRYPTED_CONTENT_KEY, "encrypted-blob"), + ConversationEvent::new(ChatResponse::message("First answer."), ts), + ConversationEvent::new(TurnStart, ts), + ConversationEvent::new(ChatRequest::from("Second question"), ts), + ]); + + let thread = ThreadBuilder::new().with_events(stream).build().unwrap(); + + // Dummy API key env var, mirroring the VCR harness. + let env = if cfg!(windows) { "USERNAME" } else { "USER" }.to_owned(); + let mut providers = LlmProviderConfig::default(); + providers.openai.api_key_env = env; + + let model = ModelDetails::empty("openai/gpt-9".parse().unwrap()); + let request = build_request_value( + ProviderId::Openai, + &providers, + &model, + ChatQuery::from(thread), + ) + .unwrap() + .to_string(); + + assert!( + !request.contains(""), + "reasoning replayed as fallback text: {request}" + ); + assert!( + request.contains(r#""type":"reasoning""#) && request.contains("encrypted-blob"), + "native reasoning item missing from request: {request}" + ); + } + + /// OpenAI item provenance wins over target-model capability metadata. + /// A native reasoning item remains native even when continuing with a model + /// cataloged as not supporting reasoning; the API owns compatibility. + #[test] + fn native_reasoning_replay_ignores_target_capability() { + let ts = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + + let mut config = AppConfig::new_test(); + config.assistant.model.id = ModelIdOrAliasConfig::Id(ModelIdConfig { + provider: ProviderId::Openai, + name: "gpt-9".parse().unwrap(), + }); + + let mut stream = ConversationStream::new(config.into()).with_created_at(ts); + stream.extend([ + ConversationEvent::new(TurnStart, ts), + ConversationEvent::new(ChatRequest::from("First question"), ts), + ConversationEvent::new(ChatResponse::reasoning("Earlier reasoning."), ts) + .with_metadata_field(ITEM_ID_KEY, "rs_123") + .with_metadata_field(ENCRYPTED_CONTENT_KEY, "encrypted-blob"), + ConversationEvent::new(ChatResponse::message("First answer."), ts), + ConversationEvent::new(TurnStart, ts), + ConversationEvent::new(ChatRequest::from("Second question"), ts), + ]); + + let thread = ThreadBuilder::new().with_events(stream).build().unwrap(); + let env = if cfg!(windows) { "USERNAME" } else { "USER" }.to_owned(); + let mut providers = LlmProviderConfig::default(); + providers.openai.api_key_env = env; + + let mut model = ModelDetails::empty("openai/gpt-4o".parse().unwrap()); + model.reasoning = Some(ReasoningDetails::unsupported()); + let request = build_request_value( + ProviderId::Openai, + &providers, + &model, + ChatQuery::from(thread), + ) + .unwrap() + .to_string(); + + assert!( + request.contains(r#""type":"reasoning""#) && request.contains("encrypted-blob"), + "native reasoning item missing from request: {request}" + ); + assert!(!request.contains("")); + } + + /// Active reasoning on a model absent from the catalog strips `temperature` + /// and `top_p`: unknown models are newer than this binary, and every + /// GPT-5-era model rejects sampling parameters alongside active reasoning. + #[test] + fn active_reasoning_strips_temperature() { + let ts = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + + let mut config = AppConfig::new_test(); + config.assistant.model.id = ModelIdOrAliasConfig::Id(ModelIdConfig { + provider: ProviderId::Openai, + name: "gpt-9".parse().unwrap(), + }); + config.assistant.model.parameters.reasoning = Some(ReasoningConfig::Auto); + config.assistant.model.parameters.temperature = Some(0.7); + config.assistant.model.parameters.top_p = Some(0.9); + + let mut stream = ConversationStream::new(config.into()).with_created_at(ts); + stream.extend([ + ConversationEvent::new(TurnStart, ts), + ConversationEvent::new(ChatRequest::from("A question"), ts), + ]); + + let thread = ThreadBuilder::new().with_events(stream).build().unwrap(); + + // Dummy API key env var, mirroring the VCR harness. + let env = if cfg!(windows) { "USERNAME" } else { "USER" }.to_owned(); + let mut providers = LlmProviderConfig::default(); + providers.openai.api_key_env = env; + + let model = ModelDetails::empty("openai/gpt-9".parse().unwrap()); + let request = build_request_value( + ProviderId::Openai, + &providers, + &model, + ChatQuery::from(thread), + ) + .unwrap() + .to_string(); + + assert!( + request.contains(r#""temperature":null"#), + "temperature not stripped alongside active reasoning: {request}" + ); + assert!( + request.contains(r#""top_p":null"#), + "top_p not stripped alongside active reasoning: {request}" + ); + } + + /// `reasoning = "off"` on a model absent from the catalog disables + /// reasoning explicitly with `effort: none`. + /// Omitting the field would let the model reason (hidden, and billed) at + /// its default effort. + #[test] + fn reasoning_off_sends_none_effort() { + let ts = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + + let mut config = AppConfig::new_test(); + config.assistant.model.id = ModelIdOrAliasConfig::Id(ModelIdConfig { + provider: ProviderId::Openai, + name: "gpt-9".parse().unwrap(), + }); + config.assistant.model.parameters.reasoning = Some(ReasoningConfig::Off); + + let mut stream = ConversationStream::new(config.into()).with_created_at(ts); + stream.extend([ + ConversationEvent::new(TurnStart, ts), + ConversationEvent::new(ChatRequest::from("A question"), ts), + ]); + + let thread = ThreadBuilder::new().with_events(stream).build().unwrap(); + + // Dummy API key env var, mirroring the VCR harness. + let env = if cfg!(windows) { "USERNAME" } else { "USER" }.to_owned(); + let mut providers = LlmProviderConfig::default(); + providers.openai.api_key_env = env; + + let model = ModelDetails::empty("openai/gpt-9".parse().unwrap()); + let request = build_request_value( + ProviderId::Openai, + &providers, + &model, + ChatQuery::from(thread), + ) + .unwrap() + .to_string(); + + assert!( + request.contains(r#""effort":"none""#), + "reasoning not explicitly disabled: {request}" + ); + } + + /// Unconfigured reasoning on a model absent from the catalog omits the + /// `reasoning` field entirely, keeping the model's default behavior. + /// A non-reasoning deployment (e.g. a fine-tuned chat model) may reject the + /// field, so it is only sent when the user explicitly configures reasoning. + #[test] + fn unconfigured_reasoning_omits_field() { + let ts = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + + let mut config = AppConfig::new_test(); + config.assistant.model.id = ModelIdOrAliasConfig::Id(ModelIdConfig { + provider: ProviderId::Openai, + name: "gpt-9".parse().unwrap(), + }); + config.assistant.model.parameters.reasoning = None; + + let mut stream = ConversationStream::new(config.into()).with_created_at(ts); + stream.extend([ + ConversationEvent::new(TurnStart, ts), + ConversationEvent::new(ChatRequest::from("A question"), ts), + ]); + + let thread = ThreadBuilder::new().with_events(stream).build().unwrap(); + + // Dummy API key env var, mirroring the VCR harness. + let env = if cfg!(windows) { "USERNAME" } else { "USER" }.to_owned(); + let mut providers = LlmProviderConfig::default(); + providers.openai.api_key_env = env; + + let model = ModelDetails::empty("openai/gpt-9".parse().unwrap()); + let request = build_request_value( + ProviderId::Openai, + &providers, + &model, + ChatQuery::from(thread), + ) + .unwrap() + .to_string(); + + assert!( + request.contains(r#""reasoning":null"#), + "reasoning field sent without explicit configuration: {request}" + ); + } +} + +mod convert_reasoning { + use jp_config::model::parameters::{CustomReasoningConfig, ReasoningEffort}; + use openai_responses::types; + + use super::super::convert_reasoning; + use crate::model::{ModelDetails, ReasoningDetails}; + + fn model(reasoning: ReasoningDetails) -> ModelDetails { + let mut details = ModelDetails::empty("openai/test-model".parse().unwrap()); + details.max_output_tokens = Some(128_000); + details.reasoning = Some(reasoning); + details + } + + #[test] + fn max_effort_is_sent_when_supported() { + let details = model(ReasoningDetails::leveled( + true, false, true, true, true, true, true, + )); + let config = convert_reasoning( + CustomReasoningConfig { + effort: ReasoningEffort::Max, + exclude: false, + }, + &details, + ); + + assert_eq!(config.effort, Some(types::ReasoningEffort::Max)); + } + + #[test] + fn max_effort_degrades_to_xhigh_when_unsupported() { + let details = model(ReasoningDetails::leveled( + true, false, true, true, true, true, false, + )); + let config = convert_reasoning( + CustomReasoningConfig { + effort: ReasoningEffort::Max, + exclude: false, + }, + &details, + ); + + assert_eq!(config.effort, Some(types::ReasoningEffort::XHigh)); + } +} + +mod parse_reasoning_mode { + use openai_responses::types; + + use super::super::{REASONING_PRO_MODE, parse_reasoning_mode}; + use crate::model::ModelDetails; + + fn model(features: Vec<&'static str>) -> ModelDetails { + let mut details = ModelDetails::empty("openai/test-model".parse().unwrap()); + details.features = features; + details + } + + #[test] + fn pro_is_sent_when_supported() { + assert_eq!( + parse_reasoning_mode("pro", &model(vec![REASONING_PRO_MODE])), + Some(types::ReasoningMode::Pro) + ); + } + + #[test] + fn pro_is_skipped_when_unsupported() { + assert_eq!(parse_reasoning_mode("pro", &model(vec![])), None); + } + + #[test] + fn standard_and_unknown_values_are_ignored() { + let details = model(vec![REASONING_PRO_MODE]); + + assert_eq!(parse_reasoning_mode("standard", &details), None); + assert_eq!(parse_reasoning_mode("turbo", &details), None); + } } mod synthesize_non_streaming_output_item_events { @@ -1052,6 +1497,250 @@ mod map_non_streaming_finish_reason { } } +/// Tests recorded against the real OpenAI API. +/// +/// Record with a valid `OPENAI_API_KEY`: +/// +/// ```sh +/// RECORD=1 cargo test -p jp_llm recorded:: +/// ``` +/// +/// Until the cassettes exist, these tests fail on replay. +mod recorded { + use std::sync::Arc; + + use chrono::{TimeZone as _, Utc}; + use jp_attachment::Attachment; + use jp_config::assistant::request::CachePolicy; + use jp_conversation::{ConversationStream, event::ChatResponse}; + use jp_test::{Result, function_name}; + use test_log::test; + + use super::super::{ModelResponse, PROVIDER, map_model}; + use crate::{ + model::ModelDetails, + test::{TestRequest, run_test}, + }; + + /// Catalog `ModelDetails` for a real OpenAI model name, via `map_model` so + /// the test cannot drift from the catalog entry. + fn catalog_details(name: &str) -> ModelDetails { + map_model(ModelResponse { + id: name.to_owned(), + _object: "model".to_owned(), + _created: Utc.with_ymd_and_hms(2026, 4, 23, 0, 0, 0).unwrap(), + _owned_by: "openai".to_owned(), + }) + .unwrap() + } + + /// Target the request at a real cataloged model: sets the config model id + /// and replaces the test-default `ModelDetails` with the catalog entry. + fn on_model(request: TestRequest, name: &str) -> TestRequest { + let mut request = request.model(format!("openai/{name}").parse().unwrap()); + if let Some(details) = request.as_model_details_mut() { + *details = catalog_details(name); + } + request + } + + /// Set a catch-all parameter (`assistant.model.parameters.`) on the + /// request's base config. + fn with_parameter(mut request: TestRequest, key: &str, value: &str) -> TestRequest { + let Some(thread) = request.as_thread_mut() else { + return request; + }; + + let mut base = (*thread.events.base_config()).clone(); + base.assistant + .model + .parameters + .other + .insert(key.to_owned(), serde_json::Value::from(value).into()); + + let placeholder = ConversationStream::new(thread.events.base_config()); + let stream = std::mem::replace(&mut thread.events, placeholder); + thread.events = stream.with_base_config(Arc::new(base)); + + request + } + + /// Give the request a stable system prompt and text attachment, so the + /// recorded body carries both explicit prompt-cache breakpoints: one at the + /// end of the system prompt, one at the end of the attachments. + fn with_cacheable_prefix(mut request: TestRequest) -> TestRequest { + if let Some(thread) = request.as_thread_mut() { + thread.system_prompt = Some("You are a concise assistant.".to_owned()); + } + + request.attachment(Attachment::text( + "file:///notes.txt", + "The magic number is 42.", + )) + } + + /// Disable prompt caching (`assistant.request.cache = off`) on the + /// request's base config. + fn with_cache_off(mut request: TestRequest) -> TestRequest { + let Some(thread) = request.as_thread_mut() else { + return request; + }; + + let mut base = (*thread.events.base_config()).clone(); + base.assistant.request.cache = CachePolicy::Off; + + let placeholder = ConversationStream::new(thread.events.base_config()); + let stream = std::mem::replace(&mut thread.events, placeholder); + thread.events = stream.with_base_config(Arc::new(base)); + + request + } + + /// GPT-5.6 wire features against the real API: `reasoning.mode: "pro"`, + /// `reasoning.context: "all_turns"`, explicit prompt-cache breakpoints + /// (after the system prompt and after the attachments), and + /// `prompt_cache_key`. + /// The second turn replays the first turn's reasoning items natively. + /// + /// The cassette pins the exact request bodies; the history assertion proves + /// the model returned a native reasoning item (`openai_item_id`) that + /// survives the round-trip. + #[test(tokio::test)] + async fn test_gpt_5_6_pro_reasoning_and_explicit_caching() -> Result { + let first = with_parameter( + with_cacheable_prefix(on_model( + TestRequest::chat(PROVIDER) + .enable_reasoning() + .chat_request("What is 7 * 191? Reason it through step by step."), + "gpt-5.6", + )), + "reasoning_mode", + "pro", + ); + + let second = with_parameter( + with_cacheable_prefix(on_model( + TestRequest::chat(PROVIDER) + .enable_reasoning() + .chat_request("Repeat your previous answer."), + "gpt-5.6", + )), + "reasoning_mode", + "pro", + ) + .assert_history(|history| { + assert!( + history.iter().any(|e| { + e.event + .as_chat_response() + .is_some_and(|r| matches!(r, ChatResponse::Reasoning { .. })) + && e.event.metadata.contains_key("openai_item_id") + }), + "expected a native reasoning item (openai_item_id) in history" + ); + }); + + run_test(PROVIDER, function_name!(), vec![first, second]).await + } + + /// Prompt caching with a stable prefix above the 1024-token cacheable + /// minimum: turn 1 writes the breakpoint-marked prefix to cache, turn 2 + /// sends the identical prefix and reads it back. + /// + /// JP discards `response.usage`, so cache activity cannot be asserted in + /// code; the cassette captures the API's usage numbers instead. + /// When (re-)recording, verify `cache_write_tokens > 0` in turn 1's + /// `response.completed` event and `cached_tokens > 0` in turn 2's. + #[test(tokio::test)] + async fn test_gpt_5_6_prompt_cache_read_after_write() -> Result { + // ~4000 tokens of stable attachment content, well above the minimum. + let prefix = |mut request: TestRequest| { + if let Some(thread) = request.as_thread_mut() { + thread.system_prompt = Some("You are a concise assistant.".to_owned()); + } + + request.attachment(Attachment::text( + "file:///novel.txt", + "The quick brown fox jumps over the lazy dog. ".repeat(400), + )) + }; + + let first = prefix(on_model( + TestRequest::chat(PROVIDER).chat_request("What animal jumps over the dog?"), + "gpt-5.6", + )); + + let second = prefix(on_model( + TestRequest::chat(PROVIDER).chat_request("And what animal gets jumped over?"), + "gpt-5.6", + )); + + run_test(PROVIDER, function_name!(), vec![first, second]).await + } + + /// `assistant.request.cache = off` on a model that bills cache writes: the + /// request carries `prompt_cache_options.mode = "explicit"` with no + /// breakpoint markers and no `prompt_cache_key` — the only cache opt-out + /// for these models. + /// + /// The request includes a system prompt and a text attachment, so the + /// cassette pins that the markers are absent even when cacheable content is + /// present. + #[test(tokio::test)] + async fn test_gpt_5_6_cache_off_sends_explicit_optout() -> Result { + let request = with_cache_off(with_cacheable_prefix(on_model( + TestRequest::chat(PROVIDER) + .enable_reasoning() + .chat_request("What is 7 * 191?"), + "gpt-5.6", + ))); + + run_test(PROVIDER, function_name!(), vec![request]).await + } + + /// Whether the Responses API accepts native reasoning items when the target + /// model is cataloged as reasoning-unsupported (a conversation switched + /// from a reasoning model to gpt-4o mid-conversation). + /// + /// Replay is provenance-driven: a stored `openai_item_id` always replays as + /// a native reasoning item, regardless of the target model's catalog entry. + /// If recording fails with a 400 on the second request, the API rejects + /// that shape and replay needs a known-unsupported fallback. + #[test(tokio::test)] + async fn test_reasoning_history_replayed_to_reasoning_unsupported_model() -> Result { + // Turn 1 on the default reasoning test model produces native + // reasoning items (openai_item_id + encrypted content) in history. + let first = TestRequest::chat(PROVIDER) + .enable_reasoning() + .chat_request("What is 7 * 191?"); + + // Turn 2 targets gpt-4o, whose catalog entry says + // `ReasoningDetails::Unsupported`. The stored reasoning items are + // still sent natively. + let second = on_model( + TestRequest::chat(PROVIDER).chat_request("Repeat your previous answer."), + "gpt-4o", + ) + .assert_history(|history| { + let last_message = history + .iter() + .rev() + .filter_map(|e| e.event.as_chat_response()) + .find_map(|r| match r { + ChatResponse::Message { message } => Some(message.clone()), + _ => None, + }); + + assert!( + last_message.is_some_and(|m| !m.trim().is_empty()), + "expected gpt-4o to answer after receiving replayed native reasoning items" + ); + }); + + run_test(PROVIDER, function_name!(), vec![first, second]).await + } +} + mod classify_stream_error { use std::time::Duration; diff --git a/crates/jp_llm/src/test.rs b/crates/jp_llm/src/test.rs index 02691592..942a1632 100644 --- a/crates/jp_llm/src/test.rs +++ b/crates/jp_llm/src/test.rs @@ -705,7 +705,7 @@ pub(crate) fn test_model_details(id: ProviderId) -> ModelDetails { context_window: Some(131_072), max_output_tokens: Some(40_960), reasoning: Some(ReasoningDetails::leveled( - false, false, true, true, true, false, + false, false, true, true, true, false, false, )), knowledge_cutoff: None, deprecated: None, diff --git a/crates/jp_llm/tests/fixtures/cerebras/test_model_details__model_details.snap b/crates/jp_llm/tests/fixtures/cerebras/test_model_details__model_details.snap index eecdc370..a429377d 100644 --- a/crates/jp_llm/tests/fixtures/cerebras/test_model_details__model_details.snap +++ b/crates/jp_llm/tests/fixtures/cerebras/test_model_details__model_details.snap @@ -27,6 +27,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: None, diff --git a/crates/jp_llm/tests/fixtures/cerebras/test_models__models.snap b/crates/jp_llm/tests/fixtures/cerebras/test_models__models.snap index f83df511..589e97fc 100644 --- a/crates/jp_llm/tests/fixtures/cerebras/test_models__models.snap +++ b/crates/jp_llm/tests/fixtures/cerebras/test_models__models.snap @@ -27,6 +27,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: None, @@ -112,6 +113,7 @@ expression: v medium: false, high: false, xhigh: false, + max: false, }, ), knowledge_cutoff: None, diff --git a/crates/jp_llm/tests/fixtures/google/test_models__models.snap b/crates/jp_llm/tests/fixtures/google/test_models__models.snap index 627e3bc0..e129da05 100644 --- a/crates/jp_llm/tests/fixtures/google/test_models__models.snap +++ b/crates/jp_llm/tests/fixtures/google/test_models__models.snap @@ -421,6 +421,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( @@ -494,6 +495,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( @@ -627,6 +629,7 @@ expression: v medium: false, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( @@ -662,6 +665,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( @@ -697,6 +701,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( @@ -732,6 +737,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/baseline.snap b/crates/jp_llm/tests/fixtures/openai/compaction/baseline.snap index 98eec5e9..ec0452c0 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/baseline.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/baseline.snap @@ -72,6 +72,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/reasoning_strip.snap b/crates/jp_llm/tests/fixtures/openai/compaction/reasoning_strip.snap index b1f0d617..d637bb9d 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/reasoning_strip.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/reasoning_strip.snap @@ -67,6 +67,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/summary.snap b/crates/jp_llm/tests/fixtures/openai/compaction/summary.snap index fa1c6b3d..22650282 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/summary.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/summary.snap @@ -32,6 +32,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/summary_overlap.snap b/crates/jp_llm/tests/fixtures/openai/compaction/summary_overlap.snap index 9fc77a72..7d1419f3 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/summary_overlap.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/summary_overlap.snap @@ -42,6 +42,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/tool_omit.snap b/crates/jp_llm/tests/fixtures/openai/compaction/tool_omit.snap index d52e1f8a..1ce15d21 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/tool_omit.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/tool_omit.snap @@ -57,6 +57,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_both.snap b/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_both.snap index 5b0cada0..0cb2db3e 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_both.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_both.snap @@ -72,6 +72,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_request.snap b/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_request.snap index a0f08da4..713674bf 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_request.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_request.snap @@ -72,6 +72,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_response.snap b/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_response.snap index f351a108..975a6bf7 100644 --- a/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_response.snap +++ b/crates/jp_llm/tests/fixtures/openai/compaction/tool_strip_response.snap @@ -72,6 +72,7 @@ expression: request "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_chat_completion_stream.yml b/crates/jp_llm/tests/fixtures/openai/test_chat_completion_stream.yml index 88380b93..14cab824 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_chat_completion_stream.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_chat_completion_stream.yml @@ -24,6 +24,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "low", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout.snap new file mode 100644 index 00000000..36aaddc5 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout.snap @@ -0,0 +1,25 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Message { + message: "1337", + }, + ), + metadata: { + "openai_item_id": String("msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c"), + "openai_phase": String("final_answer"), + }, + }, + ), + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout.yml b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout.yml new file mode 100644 index 00000000..8d8de072 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout.yml @@ -0,0 +1,96 @@ +when: + path: /v1/responses + method: POST + json_body_str: >- + { + "model": "gpt-5.6", + "input": [ + { + "type": "message", + "role": "system", + "content": [ + { + "type": "input_text", + "text": "You are a concise assistant." + } + ] + }, + { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "\n \n 0\n file:///notes.txt\n The magic number is 42.\n \n" + } + ] + }, + { + "type": "message", + "role": "user", + "content": "What is 7 * 191?" + } + ], + "include": [ + "reasoning.encrypted_content" + ], + "instructions": null, + "max_output_tokens": null, + "metadata": null, + "parallel_tool_calls": null, + "previous_response_id": null, + "prompt_cache_options": { + "mode": "explicit" + }, + "reasoning": { + "effort": "low", + "summary": "auto", + "context": "all_turns" + }, + "service_tier": null, + "store": false, + "stream": true, + "temperature": null, + "text": null, + "tool_choice": "auto", + "tools": [], + "top_p": null, + "truncation": "auto", + "user": null + } +then: + status: 200 + header: + - name: content-type + value: text/event-stream; charset=utf-8 + body: |+ + event: response.created + data: {"type":"response.created","response":{"id":"resp_0f159240d69d819e016a53caa2ebe0819480370b600195355c","object":"response","created_at":1783876258,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_options":{"mode":"explicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + event: response.in_progress + data: {"type":"response.in_progress","response":{"id":"resp_0f159240d69d819e016a53caa2ebe0819480370b600195355c","object":"response","created_at":1783876258,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_options":{"mode":"explicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","type":"message","status":"in_progress","content":[],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":2} + + event: response.content_part.added + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"133","item_id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","logprobs":[],"obfuscation":"3nh6RDQ2lKQ49","output_index":0,"sequence_number":4} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"7","item_id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","logprobs":[],"obfuscation":"IVlk1pVEbsG1wIx","output_index":0,"sequence_number":5} + + event: response.output_text.done + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","logprobs":[],"output_index":0,"sequence_number":6,"text":"1337"} + + event: response.content_part.done + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"},"sequence_number":7} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"}],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":8} + + event: response.completed + data: {"type":"response.completed","response":{"id":"resp_0f159240d69d819e016a53caa2ebe0819480370b600195355c","object":"response","created_at":1783876258,"status":"completed","background":false,"completed_at":1783876259,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[{"id":"msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"}],"phase":"final_answer","role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_options":{"mode":"explicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":{"input_tokens":73,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":6,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":79},"user":null,"metadata":{}},"sequence_number":9} + diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout__conversation_stream.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout__conversation_stream.snap new file mode 100644 index 00000000..c9d2b1d9 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout__conversation_stream.snap @@ -0,0 +1,222 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +{ + "base_config": { + "inherit": false, + "config_load_paths": [], + "extends": [ + "config.d/**/*" + ], + "assistant": { + "system_prompt": "You are a helpful assistant.", + "system_prompt_sections": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "instructions": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "tool_choice": "auto", + "model": { + "id": { + "provider": "openai", + "name": "test" + }, + "parameters": { + "reasoning": { + "effort": "low", + "exclude": false + }, + "stop_words": [], + "other": {} + } + }, + "request": { + "max_retries": 5, + "base_backoff_ms": 1000, + "max_backoff_secs": 60, + "stream_idle_timeout_secs": 60, + "cache": false + } + }, + "conversation": { + "title": { + "generate": { + "auto": false + }, + "from_heading": true + }, + "tools": { + "*": { + "run": "ask", + "result": "unattended", + "cancellation_response": "This tool request was intentionally rejected by the user. Please evaluate and either ask the user why it was rejected, or infer the reason by looking at the historical messages in the conversation.", + "style": { + "hidden": false, + "inline_results": { + "truncate": { + "lines": 10 + } + }, + "results_file_link": "full", + "parameters": "json" + } + } + }, + "compaction": { + "rules": { + "value": [ + { + "keep_first": "1", + "keep_last": "1", + "reasoning": "strip", + "tool_calls": "strip" + } + ], + "strategy": "replace", + "discard_when_merged": false + } + }, + "attachments": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "start_local": false + }, + "style": { + "code": { + "color": true, + "line_numbers": false, + "file_link": "osc8", + "copy_link": "off" + }, + "markdown": { + "wrap_width": 80, + "table_max_column_width": 40, + "theme": "gruvbox-dark", + "hr_style": "line" + }, + "reasoning": { + "display": "full", + "background": 236, + "extend_across_tool_calls": true + }, + "streaming": { + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "lock_wait": { + "show": true, + "delay_secs": 1, + "interval_ms": 100, + "timeout_secs": 10 + }, + "tool_call": { + "show": true, + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + }, + "preparing": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "typewriter": { + "text_delay": "3ms", + "code_delay": "500us", + "max_latency": "0s" + } + }, + "interrupt": { + "escalation_cooldown_secs": 2, + "streaming": { + "action": "prompt", + "compose_in_editor": false + }, + "tool_call": { + "action": "prompt", + "compose_in_editor": false + } + }, + "editor": { + "envs": [ + "JP_EDITOR", + "VISUAL", + "EDITOR" + ], + "inline": { + "edit_mode": "emacs" + } + }, + "providers": { + "llm": { + "anthropic": { + "api_key_env": "ANTHROPIC_API_KEY", + "base_url": "https://api.anthropic.com", + "chain_on_max_tokens": true, + "beta_headers": [] + }, + "cerebras": { + "api_key_env": "CEREBRAS_API_KEY", + "base_url": "https://api.cerebras.ai" + }, + "deepseek": { + "api_key_env": "DEEPSEEK_API_KEY", + "base_url": "https://api.deepseek.com" + }, + "google": { + "api_key_env": "GEMINI_API_KEY", + "base_url": "https://generativelanguage.googleapis.com/v1beta" + }, + "llamacpp": { + "base_url": "http://127.0.0.1:8080" + }, + "ollama": { + "base_url": "http://localhost:11434" + }, + "openai": { + "api_key_env": "OPENAI_API_KEY", + "base_url": "https://api.openai.com", + "base_url_env": "OPENAI_BASE_URL" + }, + "openrouter": { + "api_key_env": "OPENROUTER_API_KEY", + "app_name": "JP", + "base_url": "https://openrouter.ai" + } + } + }, + "plugins": { + "auto_install": true, + "shutdown_timeout_secs": 5 + } + }, + "events": [ + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_request", + "content": "What is 7 * 191?" + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "message": "1337", + "metadata": { + "openai_item_id": "bXNnXzBmMTU5MjQwZDY5ZDgxOWUwMTZhNTNjYWEzYzA3ODgxOTQ5OWZkZThlNmYxNWM0NjFj", + "openai_phase": "ZmluYWxfYW5zd2Vy" + } + } + ] +} diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout__raw_events.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout__raw_events.snap new file mode 100644 index 00000000..fac4e067 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_cache_off_sends_explicit_optout__raw_events.snap @@ -0,0 +1,39 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Part { + index: 0, + part: Message( + "", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + "133", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + "7", + ), + metadata: {}, + }, + Flush { + index: 0, + metadata: { + "openai_item_id": String("msg_0f159240d69d819e016a53caa3c078819499fde8e6f15c461c"), + "openai_phase": String("final_answer"), + }, + }, + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching.snap new file mode 100644 index 00000000..b97fab2c --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching.snap @@ -0,0 +1,58 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Reasoning { + reasoning: "**Calculating step by step**\n\nAlright, I need to approach this systematically. First things first, I want to ignore any irrelevant documents that might clutter my thinking. I’ll focus on the calculation: 191 multiplied by 7 equals 1337. Keeping it concise helps me deliver the answer more clearly. So, I’ll summarize it: the calculation is straightforward and yields the result of 1337.", + }, + ), + metadata: { + "openai_item_id": String("rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717"), + "openai_encrypted_content": String("gAAAAABqU8qodvILz8NAylGDzyKtHddMXWdR7oBjIxLlKYazeZ8hhYPUf6J71xN2LkJlNbqVCr5fG4cCLZEEJs91_2gO5QmgADN-0Z6t_BD23PFUMPOExUyUCgy7exNVFLOKGpRsLq31qaM5xMy_G8oPQ0dyk--x2si2RTIG7pzowDBM9o3TJ-2mtl58tkQ1nK1JNaI056LnPYqmLbuS_G4gsN97ShAOXfLdJqIdn39My9U3BzpHvyWkOjojMDy6_6Hl7B-jrsW4OeMlzlW97iCeq3qAJQ44C4hhHeD2_r2aIqu0eMCzX5U7sqbOc931_CVdjK7_OsqLPyTffRkBy7AuhqbDlrHaCePOV-Eq5rfxQAqU6fUXvsZQ-85W8PF63s9XHPL6dlF89qXr-l75P9JB0lpljs6N-5X5KzHROoxptYHGPhTAisooTL8xgaJzYiypZmI-U-Sk5GB97c4jNsUgBtI7uDlB-4LSUK2Ns5yhTCMDDON9vzsXG71t9dSjscgPVQ67wf3ACqk263zi4tyr42_tJF_aCX6NaOIfqfVoziiQsGvAZfEsbTZmHxxFRq4nM4GEA3VStKYNzFPuuOCe814378q-eTQyCqtNEchDuY87lPRwbO_IFRWp6Y-RnfuxBsAcyQDRs4WNI80wSYRJBeZW_Hgenr8gByztNGxWUxhPPMVSTKj-KtMnB6mdrcyCACVsM-Gy4HDeGU2Sz2_j6_2lPYa4g-EPnA23Fz5n6GRJmWcuohKEESZMvZIWBmBoZUAp_hnAywbPIbdY0e3K8gZ-ClGzi9Pt98nyMF9skW8u_PPx50gxSHb1ombNo5cHhO3WbdCYTzJSK9N6AiqP2WLhQAK3gpM_q1jsm-Y8JN8Jgmrm6io00rhLPl9kdORPBd_cDMjB-LDlwMXsTkkoUeJQmhbmbVKktY3kt5pJ4odcESfRmLoMOxmpTN5VUbos-Z4MmPgXBM1DQHYwt0om12PSo-ivEdlGO10HEpJv6npGL16JIaUoiBHUxklYN_Z9piCyOwCI"), + }, + }, + ), + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Message { + message: "\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**", + }, + ), + metadata: { + "openai_item_id": String("msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d"), + "openai_phase": String("final_answer"), + }, + }, + ), + Finished( + Completed, + ), + ], + [ + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Message { + message: "\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**", + }, + ), + metadata: { + "openai_item_id": String("msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571"), + "openai_phase": String("final_answer"), + }, + }, + ), + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching.yml b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching.yml new file mode 100644 index 00000000..596f5d55 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching.yml @@ -0,0 +1,455 @@ +when: + path: /v1/responses + method: POST + json_body_str: >- + { + "model": "gpt-5.6", + "input": [ + { + "type": "message", + "role": "system", + "content": [ + { + "type": "input_text", + "text": "You are a concise assistant.", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "\n \n 0\n file:///notes.txt\n The magic number is 42.\n \n", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": "What is 7 * 191? Reason it through step by step." + } + ], + "include": [ + "reasoning.encrypted_content" + ], + "instructions": null, + "max_output_tokens": null, + "metadata": null, + "parallel_tool_calls": null, + "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", + "reasoning": { + "effort": "low", + "summary": "auto", + "mode": "pro", + "context": "all_turns" + }, + "service_tier": null, + "store": false, + "stream": true, + "temperature": null, + "text": null, + "tool_choice": "auto", + "tools": [], + "top_p": null, + "truncation": "auto", + "user": null + } +then: + status: 200 + header: + - name: content-type + value: text/event-stream; charset=utf-8 + body: |+ + event: response.created + data: {"type":"response.created","response":{"id":"resp_06b7a713fe9fcf8e016a53caa2ec6881979a55ce2e4cd53bb1","object":"response","created_at":1783876258,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"pro","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + event: response.in_progress + data: {"type":"response.in_progress","response":{"id":"resp_06b7a713fe9fcf8e016a53caa2ec6881979a55ce2e4cd53bb1","object":"response","created_at":1783876258,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"pro","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","type":"reasoning","content":[],"encrypted_content":"gAAAAABqU8qn0O42KwKiqNP92rmDREYJx-v9j0CoY9cfJLNe240oh8GSSZSi2I4vcSgN7jTkkRj66iHrXt51DJceexKndAaQw4-AOyrw4YjyP8I-ZDkhgBfldFhZPnqapvSBREP1elTrl_gD2SDFmjoFy6HF6gSSMohyAcfrx_jZXJTcwPpvviEamL0Ib61f-wszWQ2I0ZhNIIeOmCMuu_EqVRPX7B6iE8AxDMa2YRBMOrB631AYYvsyg6xCooAt_l7Co6vRtySJiLugpBENWTorjtlEozAVXrr9nH3rO65W6v0poCTHWpWSkdNtzdiaJg5bQipRrVxr0Dwh8-RulutnpdjuC_p8gXu91dkd0amrwhfHblN08oi6Ba385IryaPOMcvHH-0N1fUENOeXr1Y2M8fCXVPhf2ei87euEizrZthLI7zDSa-JJsBa9WN0mvoz_tqf8QrA2obntvHqagYRkFzsSZaY4O4drusgjq4xuRVhzHAaZhSjAA3BamSDIO9E8ezffMzODuX5JDgwfVITAxFCbCWdT2mwerM7oZQ6KmDGkp_9lzoLsWY2FGbwJ-7ES6KR80JJAxTCqYHSjOGC5addm6hfK-FKHP8KuQ65PxavXzqtxoKseILHKzRh0PuN3J8oB3624gsBNoS599H7-SozW7e8Z3TEvdUFK6AVtVrdf0iialw91j8C1p5nWCdviwOxsyLCuEvzqrEDZB7GqS33gIZ8t1cSwcyGcDFxowABDmCohCxDAZdbgDqsWaxOMjHvkDOsGX1TzI7kmjFBqMMNROH1AOW3Yi0o4LW8TvpRjeHpXbHI2Bw1ddi3kPlJ04EgOePN0XgBnYLkH7BvmaqxNRZ-TqxgNOmr7X61obJ7OzEHu6WaNpxLsQ8deK0sTX8gEmAzZZl2PyrYMmWvZTUFjrtQnn7TnVTZLZa7kYkS4j4O2GczqDkL4wzFLWNqMTGZWoDd-qKclkg7JddBI8FrrlioHuuVDJQd7qXTjUYMOcHpXXn850aT27Y5MdtzPCy4bGOi-","summary":[]},"output_index":0,"sequence_number":2} + + event: response.reasoning_summary_part.added + data: {"type":"response.reasoning_summary_part.added","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","output_index":0,"part":{"type":"summary_text","text":""},"sequence_number":3,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"**Calculating step by step**\n\nAlright","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"If869ThESjW","output_index":0,"sequence_number":4,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":",","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"vL7Fgdg9VY58lGe","output_index":0,"sequence_number":5,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" I","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"U1EewHvDWByA0v","output_index":0,"sequence_number":6,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" need","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"0uJVVKMLBx5","output_index":0,"sequence_number":7,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" to","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"M6qApCaObzJ9A","output_index":0,"sequence_number":8,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" approach","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"fkhntcR","output_index":0,"sequence_number":9,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" this","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"PTMcHz51Hiv","output_index":0,"sequence_number":10,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" systematically","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"T","output_index":0,"sequence_number":11,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"82QWUGdqaJRMCfW","output_index":0,"sequence_number":12,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" First","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"S7WpzlRcII","output_index":0,"sequence_number":13,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" things","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"HUbnTTxjJ","output_index":0,"sequence_number":14,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" first","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"9KsewCSXi5","output_index":0,"sequence_number":15,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":",","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"8PRjhgdu4wH766L","output_index":0,"sequence_number":16,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" I","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"D0ZofDZfOPCpyf","output_index":0,"sequence_number":17,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" want","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"mK46LZnLZit","output_index":0,"sequence_number":18,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" to","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"Wo5lQwBLZC3dO","output_index":0,"sequence_number":19,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" ignore","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"DKqYo5MKn","output_index":0,"sequence_number":20,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" any","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"9FYPn5SMM5cZ","output_index":0,"sequence_number":21,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" irrelevant","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"QWomN","output_index":0,"sequence_number":22,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" documents","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"HRygwS","output_index":0,"sequence_number":23,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" that","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"opPZ4Mom3ve","output_index":0,"sequence_number":24,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" might","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"1BzDUnjWIt","output_index":0,"sequence_number":25,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" clutter","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"8dTQcOBj","output_index":0,"sequence_number":26,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" my","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"BSFbzo2tjqVBQ","output_index":0,"sequence_number":27,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" thinking","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"lEI13X9","output_index":0,"sequence_number":28,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"glhEeKwnYEEZjn4","output_index":0,"sequence_number":29,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" I","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"L4l5HpXYvZc9WL","output_index":0,"sequence_number":30,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"’ll","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"PVnRHaj79Z5X4","output_index":0,"sequence_number":31,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" focus","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"DK8x2wvEM6","output_index":0,"sequence_number":32,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" on","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"OxupTBthDm6Wo","output_index":0,"sequence_number":33,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"bo1B1NWCvLmA","output_index":0,"sequence_number":34,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" calculation","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"tBVN","output_index":0,"sequence_number":35,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":":","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"5oyV6MVhKxZdu2L","output_index":0,"sequence_number":36,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 191","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"ML18xuFX6amR","output_index":0,"sequence_number":37,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" multiplied","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"lGop5","output_index":0,"sequence_number":38,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" by","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"ut3EyWzkGBJUq","output_index":0,"sequence_number":39,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 7","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"eKZsWKFYdoq8wm","output_index":0,"sequence_number":40,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" equals","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"JDBT2AQif","output_index":0,"sequence_number":41,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 133","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"LJ9tYmqNJRix","output_index":0,"sequence_number":42,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"7","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"xyK72AzFpGK0BMu","output_index":0,"sequence_number":43,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"87UUsg42AVT48uF","output_index":0,"sequence_number":44,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" Keeping","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"L7KknqPB","output_index":0,"sequence_number":45,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" it","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"SO06IwCW8oJTB","output_index":0,"sequence_number":46,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" concise","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"MsCVh9eO","output_index":0,"sequence_number":47,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" helps","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"WsbTaGqaXG","output_index":0,"sequence_number":48,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" me","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"ZWWazo3XCn56Y","output_index":0,"sequence_number":49,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" deliver","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"QBnmE5gf","output_index":0,"sequence_number":50,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"u2kbDLCOJyHR","output_index":0,"sequence_number":51,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" answer","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"vylYjyAOM","output_index":0,"sequence_number":52,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" more","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"3xRkSxIZXUo","output_index":0,"sequence_number":53,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" clearly","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"EgF3pFnq","output_index":0,"sequence_number":54,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"5Va6PlgGZ2aSeJQ","output_index":0,"sequence_number":55,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" So","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"Ho0cmrFVd4JZf","output_index":0,"sequence_number":56,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":",","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"JFuslDG9ZQwWHEm","output_index":0,"sequence_number":57,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" I","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"un6on3iuPwkT59","output_index":0,"sequence_number":58,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"’ll","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"DJnOlHdSlGNye","output_index":0,"sequence_number":59,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" summarize","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"eTdZbA","output_index":0,"sequence_number":60,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" it","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"EKsLnbMPtiJ2y","output_index":0,"sequence_number":61,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":":","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"6yBIrkeJa1C5f8r","output_index":0,"sequence_number":62,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"rdGXwSsYKS65","output_index":0,"sequence_number":63,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" calculation","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"wOTk","output_index":0,"sequence_number":64,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" is","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"BxNwOQX06fa5G","output_index":0,"sequence_number":65,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" straightforward","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"","output_index":0,"sequence_number":66,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" and","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"nLNk9PhrMVzy","output_index":0,"sequence_number":67,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" yields","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"lbGxNn2h8","output_index":0,"sequence_number":68,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"mt1b4cLAb3jd","output_index":0,"sequence_number":69,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" result","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"gysUfmlRq","output_index":0,"sequence_number":70,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" of","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"lihn4Ric7xqVw","output_index":0,"sequence_number":71,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 133","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"8K1P31w1co37","output_index":0,"sequence_number":72,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"7","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"lkB5T42NNlDGEMo","output_index":0,"sequence_number":73,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","obfuscation":"w4YthEU096krgMM","output_index":0,"sequence_number":74,"summary_index":0} + + event: response.reasoning_summary_text.done + data: {"type":"response.reasoning_summary_text.done","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","output_index":0,"sequence_number":75,"summary_index":0,"text":"**Calculating step by step**\n\nAlright, I need to approach this systematically. First things first, I want to ignore any irrelevant documents that might clutter my thinking. I’ll focus on the calculation: 191 multiplied by 7 equals 1337. Keeping it concise helps me deliver the answer more clearly. So, I’ll summarize it: the calculation is straightforward and yields the result of 1337."} + + event: response.reasoning_summary_part.done + data: {"type":"response.reasoning_summary_part.done","item_id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","output_index":0,"part":{"type":"summary_text","text":"**Calculating step by step**\n\nAlright, I need to approach this systematically. First things first, I want to ignore any irrelevant documents that might clutter my thinking. I’ll focus on the calculation: 191 multiplied by 7 equals 1337. Keeping it concise helps me deliver the answer more clearly. So, I’ll summarize it: the calculation is straightforward and yields the result of 1337."},"sequence_number":76,"summary_index":0} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","type":"reasoning","content":[],"encrypted_content":"gAAAAABqU8qodvILz8NAylGDzyKtHddMXWdR7oBjIxLlKYazeZ8hhYPUf6J71xN2LkJlNbqVCr5fG4cCLZEEJs91_2gO5QmgADN-0Z6t_BD23PFUMPOExUyUCgy7exNVFLOKGpRsLq31qaM5xMy_G8oPQ0dyk--x2si2RTIG7pzowDBM9o3TJ-2mtl58tkQ1nK1JNaI056LnPYqmLbuS_G4gsN97ShAOXfLdJqIdn39My9U3BzpHvyWkOjojMDy6_6Hl7B-jrsW4OeMlzlW97iCeq3qAJQ44C4hhHeD2_r2aIqu0eMCzX5U7sqbOc931_CVdjK7_OsqLPyTffRkBy7AuhqbDlrHaCePOV-Eq5rfxQAqU6fUXvsZQ-85W8PF63s9XHPL6dlF89qXr-l75P9JB0lpljs6N-5X5KzHROoxptYHGPhTAisooTL8xgaJzYiypZmI-U-Sk5GB97c4jNsUgBtI7uDlB-4LSUK2Ns5yhTCMDDON9vzsXG71t9dSjscgPVQ67wf3ACqk263zi4tyr42_tJF_aCX6NaOIfqfVoziiQsGvAZfEsbTZmHxxFRq4nM4GEA3VStKYNzFPuuOCe814378q-eTQyCqtNEchDuY87lPRwbO_IFRWp6Y-RnfuxBsAcyQDRs4WNI80wSYRJBeZW_Hgenr8gByztNGxWUxhPPMVSTKj-KtMnB6mdrcyCACVsM-Gy4HDeGU2Sz2_j6_2lPYa4g-EPnA23Fz5n6GRJmWcuohKEESZMvZIWBmBoZUAp_hnAywbPIbdY0e3K8gZ-ClGzi9Pt98nyMF9skW8u_PPx50gxSHb1ombNo5cHhO3WbdCYTzJSK9N6AiqP2WLhQAK3gpM_q1jsm-Y8JN8Jgmrm6io00rhLPl9kdORPBd_cDMjB-LDlwMXsTkkoUeJQmhbmbVKktY3kt5pJ4odcESfRmLoMOxmpTN5VUbos-Z4MmPgXBM1DQHYwt0om12PSo-ivEdlGO10HEpJv6npGL16JIaUoiBHUxklYN_Z9piCyOwCI","summary":[{"type":"summary_text","text":"**Calculating step by step**\n\nAlright, I need to approach this systematically. First things first, I want to ignore any irrelevant documents that might clutter my thinking. I’ll focus on the calculation: 191 multiplied by 7 equals 1337. Keeping it concise helps me deliver the answer more clearly. So, I’ll summarize it: the calculation is straightforward and yields the result of 1337."}]},"output_index":0,"sequence_number":77} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d","type":"message","status":"completed","content":[],"phase":"final_answer","role":"assistant"},"output_index":1,"sequence_number":78} + + event: response.content_part.added + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":79} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**","item_id":"msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d","logprobs":[],"obfuscation":"TrLdqFr","output_index":1,"sequence_number":80} + + event: response.output_text.done + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d","logprobs":[],"output_index":1,"sequence_number":81,"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"} + + event: response.content_part.done + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"},"sequence_number":82} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"}],"phase":"final_answer","role":"assistant"},"output_index":1,"sequence_number":83} + + event: response.completed + data: {"type":"response.completed","response":{"id":"resp_06b7a713fe9fcf8e016a53caa2ec6881979a55ce2e4cd53bb1","object":"response","created_at":1783876258,"status":"completed","background":false,"completed_at":1783876265,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[{"id":"rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717","type":"reasoning","content":[],"encrypted_content":"gAAAAABqU8qpaAuITZV6jdWzcuznbyN8qw2iH1zxLiDOPR9wHinHyPpiUEauxqKwnW5potTSuUrhDgAEOEp6rbKrw7miJp-TnPINg9ZuHj7soqAYe-iUuIDPEeJnRjNOaNAqTEFadrz1VcQLhVLozgibOd5MnU0o3RKuC3G92lz4bKuF9U9HHXaLzjyj-t2oJ7PNjTGLIueiaBm9T98gNQCBP-WvJ976J6WwpQg-YEdL5oOi8eldupOrQddKUwOGKFUS2Ve2MTr9DoLaUt1k-XTFUj6331zWknGo5CApPPQdI7uh25dZZaxdqhPEcc9MQNGoX5cpblzchGmXb_MVDct_WVzoklxIM_-voUs_vVvkD_fq82dg5OCtMeTjdqWcfP9eU8gvCuaRGkSfxYaKxpJ6-6XvJbpSGjWUPJIYQcK_t0G1Vi_fKNYIZqtKBjG29EZ8WLnunBrXV2jIRv7HOrOStvpAAq0bxs_1oHpIcjUhTrJJFlFo5aLqY2Svs5Jz1ygn2sL3T-VR2r0x8SaVTjHzZGiZ6JN_V5qyRmLNNkK2WLar4upqugrmNGvXeTccvHAZsTZYOxCVklEKVPs8_cW6jOnhmDtxlCYlL98ixgwlHpHaaWzjD3GCiUO02dLGCtgobdNsJi5z0DmmnQhpE4YaazaqEU3jNaCLjGeQM-xlQv7Rp2PQFCdRBW_fxeQ_nR6RrM8Vn0z5HcfS_JFX1u2Tl9Y_X4QuXK8NPMoP19npCw-f5VxbjrW47o3OwmkhuxsQjte92BCxfW6kAvX696sURWIlImIOPHdRiRQ5FWejiRhdgGnW4OL2gU3OuaIKk9DPOuiyLsnTpSkB7GKzDIOQdahW7Iv6JJjTbdhVXzwRPp1Jh8UJswcCMpVLnzj8n9jmFcl0p-HFhahGH9IS7k7CjBvyBL7Xlmqbuyme6Uzo2owMoZbf_EMnvWlHw6burVXUsJ-QTcbuFm4eGKgeHTIAzk3lUwEFlTDo5VA2FSFYDe2dheAJ4A1qApNDBuJhYYH97PjpzK6f","summary":[{"type":"summary_text","text":"**Calculating step by step**\n\nAlright, I need to approach this systematically. First things first, I want to ignore any irrelevant documents that might clutter my thinking. I’ll focus on the calculation: 191 multiplied by 7 equals 1337. Keeping it concise helps me deliver the answer more clearly. So, I’ll summarize it: the calculation is straightforward and yields the result of 1337."}]},{"id":"msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"}],"phase":"final_answer","role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"pro","summary":"detailed"},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":{"input_tokens":2010,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":310,"output_tokens_details":{"reasoning_tokens":158},"total_tokens":2320},"user":null,"metadata":{}},"sequence_number":84} + +--- +when: + path: /v1/responses + method: POST + json_body_str: >- + { + "model": "gpt-5.6", + "input": [ + { + "type": "message", + "role": "system", + "content": [ + { + "type": "input_text", + "text": "You are a concise assistant.", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "\n \n 0\n file:///notes.txt\n The magic number is 42.\n \n", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": "What is 7 * 191? Reason it through step by step." + }, + { + "type": "reasoning", + "id": "rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717", + "summary": [ + { + "type": "summary_text", + "text": "**Calculating step by step**\n\nAlright, I need to approach this systematically. First things first, I want to ignore any irrelevant documents that might clutter my thinking. I’ll focus on the calculation: 191 multiplied by 7 equals 1337. Keeping it concise helps me deliver the answer more clearly. So, I’ll summarize it: the calculation is straightforward and yields the result of 1337." + } + ], + "encrypted_content": "gAAAAABqU8qodvILz8NAylGDzyKtHddMXWdR7oBjIxLlKYazeZ8hhYPUf6J71xN2LkJlNbqVCr5fG4cCLZEEJs91_2gO5QmgADN-0Z6t_BD23PFUMPOExUyUCgy7exNVFLOKGpRsLq31qaM5xMy_G8oPQ0dyk--x2si2RTIG7pzowDBM9o3TJ-2mtl58tkQ1nK1JNaI056LnPYqmLbuS_G4gsN97ShAOXfLdJqIdn39My9U3BzpHvyWkOjojMDy6_6Hl7B-jrsW4OeMlzlW97iCeq3qAJQ44C4hhHeD2_r2aIqu0eMCzX5U7sqbOc931_CVdjK7_OsqLPyTffRkBy7AuhqbDlrHaCePOV-Eq5rfxQAqU6fUXvsZQ-85W8PF63s9XHPL6dlF89qXr-l75P9JB0lpljs6N-5X5KzHROoxptYHGPhTAisooTL8xgaJzYiypZmI-U-Sk5GB97c4jNsUgBtI7uDlB-4LSUK2Ns5yhTCMDDON9vzsXG71t9dSjscgPVQ67wf3ACqk263zi4tyr42_tJF_aCX6NaOIfqfVoziiQsGvAZfEsbTZmHxxFRq4nM4GEA3VStKYNzFPuuOCe814378q-eTQyCqtNEchDuY87lPRwbO_IFRWp6Y-RnfuxBsAcyQDRs4WNI80wSYRJBeZW_Hgenr8gByztNGxWUxhPPMVSTKj-KtMnB6mdrcyCACVsM-Gy4HDeGU2Sz2_j6_2lPYa4g-EPnA23Fz5n6GRJmWcuohKEESZMvZIWBmBoZUAp_hnAywbPIbdY0e3K8gZ-ClGzi9Pt98nyMF9skW8u_PPx50gxSHb1ombNo5cHhO3WbdCYTzJSK9N6AiqP2WLhQAK3gpM_q1jsm-Y8JN8Jgmrm6io00rhLPl9kdORPBd_cDMjB-LDlwMXsTkkoUeJQmhbmbVKktY3kt5pJ4odcESfRmLoMOxmpTN5VUbos-Z4MmPgXBM1DQHYwt0om12PSo-ivEdlGO10HEpJv6npGL16JIaUoiBHUxklYN_Z9piCyOwCI" + }, + { + "type": "message", + "content": [ + { + "type": "output_text", + "text": "\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**", + "annotations": [] + } + ], + "id": "msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d", + "role": "assistant", + "status": "completed", + "phase": "final_answer" + }, + { + "type": "message", + "role": "user", + "content": "Repeat your previous answer." + } + ], + "include": [ + "reasoning.encrypted_content" + ], + "instructions": null, + "max_output_tokens": null, + "metadata": null, + "parallel_tool_calls": null, + "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", + "reasoning": { + "effort": "low", + "summary": "auto", + "mode": "pro", + "context": "all_turns" + }, + "service_tier": null, + "store": false, + "stream": true, + "temperature": null, + "text": null, + "tool_choice": "auto", + "tools": [], + "top_p": null, + "truncation": "auto", + "user": null + } +then: + status: 200 + header: + - name: content-type + value: text/event-stream; charset=utf-8 + body: |+ + event: response.created + data: {"type":"response.created","response":{"id":"resp_06b7a713fe9fcf8e016a53caa951708197baeeec34b770aa09","object":"response","created_at":1783876265,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"pro","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + event: response.in_progress + data: {"type":"response.in_progress","response":{"id":"resp_06b7a713fe9fcf8e016a53caa951708197baeeec34b770aa09","object":"response","created_at":1783876265,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"pro","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571","type":"message","status":"completed","content":[],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":2} + + event: response.content_part.added + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**","item_id":"msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571","logprobs":[],"obfuscation":"EAfHEa9","output_index":0,"sequence_number":4} + + event: response.output_text.done + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571","logprobs":[],"output_index":0,"sequence_number":5,"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"} + + event: response.content_part.done + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"},"sequence_number":6} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"}],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":7} + + event: response.completed + data: {"type":"response.completed","response":{"id":"resp_06b7a713fe9fcf8e016a53caa951708197baeeec34b770aa09","object":"response","created_at":1783876265,"status":"completed","background":false,"completed_at":1783876267,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[{"id":"msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**"}],"phase":"final_answer","role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"low","mode":"pro","summary":"detailed"},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":{"input_tokens":2377,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":186,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":2563},"user":null,"metadata":{}},"sequence_number":8} + diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching__conversation_stream.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching__conversation_stream.snap new file mode 100644 index 00000000..f7f34cd7 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching__conversation_stream.snap @@ -0,0 +1,273 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +{ + "base_config": { + "inherit": false, + "config_load_paths": [], + "extends": [ + "config.d/**/*" + ], + "assistant": { + "system_prompt": "You are a helpful assistant.", + "system_prompt_sections": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "instructions": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "tool_choice": "auto", + "model": { + "id": { + "provider": "openai", + "name": "test" + }, + "parameters": { + "reasoning": { + "effort": "low", + "exclude": false + }, + "stop_words": [], + "other": { + "reasoning_mode": "pro" + } + } + }, + "request": { + "max_retries": 5, + "base_backoff_ms": 1000, + "max_backoff_secs": 60, + "stream_idle_timeout_secs": 60, + "cache": true + } + }, + "conversation": { + "title": { + "generate": { + "auto": false + }, + "from_heading": true + }, + "tools": { + "*": { + "run": "ask", + "result": "unattended", + "cancellation_response": "This tool request was intentionally rejected by the user. Please evaluate and either ask the user why it was rejected, or infer the reason by looking at the historical messages in the conversation.", + "style": { + "hidden": false, + "inline_results": { + "truncate": { + "lines": 10 + } + }, + "results_file_link": "full", + "parameters": "json" + } + } + }, + "compaction": { + "rules": { + "value": [ + { + "keep_first": "1", + "keep_last": "1", + "reasoning": "strip", + "tool_calls": "strip" + } + ], + "strategy": "replace", + "discard_when_merged": false + } + }, + "attachments": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "start_local": false + }, + "style": { + "code": { + "color": true, + "line_numbers": false, + "file_link": "osc8", + "copy_link": "off" + }, + "markdown": { + "wrap_width": 80, + "table_max_column_width": 40, + "theme": "gruvbox-dark", + "hr_style": "line" + }, + "reasoning": { + "display": "full", + "background": 236, + "extend_across_tool_calls": true + }, + "streaming": { + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "lock_wait": { + "show": true, + "delay_secs": 1, + "interval_ms": 100, + "timeout_secs": 10 + }, + "tool_call": { + "show": true, + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + }, + "preparing": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "typewriter": { + "text_delay": "3ms", + "code_delay": "500us", + "max_latency": "0s" + } + }, + "interrupt": { + "escalation_cooldown_secs": 2, + "streaming": { + "action": "prompt", + "compose_in_editor": false + }, + "tool_call": { + "action": "prompt", + "compose_in_editor": false + } + }, + "editor": { + "envs": [ + "JP_EDITOR", + "VISUAL", + "EDITOR" + ], + "inline": { + "edit_mode": "emacs" + } + }, + "providers": { + "llm": { + "anthropic": { + "api_key_env": "ANTHROPIC_API_KEY", + "base_url": "https://api.anthropic.com", + "chain_on_max_tokens": true, + "beta_headers": [] + }, + "cerebras": { + "api_key_env": "CEREBRAS_API_KEY", + "base_url": "https://api.cerebras.ai" + }, + "deepseek": { + "api_key_env": "DEEPSEEK_API_KEY", + "base_url": "https://api.deepseek.com" + }, + "google": { + "api_key_env": "GEMINI_API_KEY", + "base_url": "https://generativelanguage.googleapis.com/v1beta" + }, + "llamacpp": { + "base_url": "http://127.0.0.1:8080" + }, + "ollama": { + "base_url": "http://localhost:11434" + }, + "openai": { + "api_key_env": "OPENAI_API_KEY", + "base_url": "https://api.openai.com", + "base_url_env": "OPENAI_BASE_URL" + }, + "openrouter": { + "api_key_env": "OPENROUTER_API_KEY", + "app_name": "JP", + "base_url": "https://openrouter.ai" + } + } + }, + "plugins": { + "auto_install": true, + "shutdown_timeout_secs": 5 + } + }, + "events": [ + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_request", + "content": "What is 7 * 191? Reason it through step by step." + }, + { + "type": "config_delta", + "timestamp": "2020-01-01 00:00:00.0", + "delta": { + "assistant": { + "model": { + "id": { + "name": "gpt-5.6" + } + } + } + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "reasoning": "**Calculating step by step**\n\nAlright, I need to approach this systematically. First things first, I want to ignore any irrelevant documents that might clutter my thinking. I’ll focus on the calculation: 191 multiplied by 7 equals 1337. Keeping it concise helps me deliver the answer more clearly. So, I’ll summarize it: the calculation is straightforward and yields the result of 1337.", + "metadata": { + "openai_item_id": "cnNfMDZiN2E3MTNmZTlmY2Y4ZTAxNmE1M2NhYTdiNDU0ODE5NzhiMDIxYTdhYzU4MTI3MTc=", + "openai_encrypted_content": "Z0FBQUFBQnFVOHFvZHZJTHo4TkF5bEdEenlLdEhkZE1YV2RSN29Cakl4TGxLWWF6ZVo4aGhZUFVmNko3MXhOMkxrSmxOYnFWQ3I1Zkc0Y0NMWkVFSnM5MV8yZ081UW1nQUROLTBaNnRfQkQyM1BGVU1QT0V4VXlVQ2d5N2V4TlZGTE9LR3BSc0xxMzFxYU01eE15X0c4b1BRMGR5ay0teDJzaTJSVElHN3B6b3dEQk05bzNUSi0ybXRsNTh0a1ExbksxSk5hSTA1NkxuUFlxbUxidVNfRzRnc045N1NoQU9YZkxkSnFJZG4zOU15OVUzQnpwSHZ5V2tPam9qTUR5Nl82SGw3Qi1qcnNXNE9lTWx6bFc5N2lDZXEzcUFKUTQ0QzRoaEhlRDJfcjJhSXF1MGVNQ3pYNVU3c3FiT2M5MzFfQ1Zkaks3X09zcUxQeVRmZlJrQnk3QXVocWJEbHJIYUNlUE9WLUVxNXJmeFFBcVU2ZlVYdnNaUS04NVc4UEY2M3M5WEhQTDZkbEY4OXFYci1sNzVQOUpCMGxwbGpzNk4tNVg1S3pIUk9veHB0WUhHUGhUQWlzb29UTDh4Z2FKellpeXBabUktVS1TazVHQjk3YzRqTnNVZ0J0STd1RGxCLTRMU1VLMk5zNXloVENNRERPTjl2enNYRzcxdDlkU2pzY2dQVlE2N3dmM0FDcWsyNjN6aTR0eXI0Ml90SkZfYUNYNk5hT0lmcWZWb3ppaVFzR3ZBWmZFc2JUWm1IeHhGUnE0bk00R0VBM1ZTdEtZTnpGUHV1T0NlODE0Mzc4cS1lVFF5Q3F0TkVjaER1WTg3bFBSd2JPX0lGUldwNlktUm5mdXhCc0FjeVFEUnM0V05JODB3U1lSSkJlWldfSGdlbnI4Z0J5enROR3hXVXhoUFBNVlNUS2otS3RNbkI2bWRyY3lDQUNWc00tR3k0SERlR1UyU3oyX2o2XzJsUFlhNGctRVBuQTIzRno1bjZHUkptV2N1b2hLRUVTWk12WklXQm1Cb1pVQXBfaG5BeXdiUEliZFkwZTNLOGdaLUNsR3ppOVB0OThueU1GOXNrVzh1X1BQeDUwZ3hTSGIxb21iTm81Y0hoTzNXYmRDWVR6SlNLOU42QWlxUDJXTGhRQUszZ3BNX3ExanNtLVk4Sk44Smdtcm02aW8wMHJoTFBsOWtkT1JQQmRfY0RNakItTERsd01Yc1Rra29VZUpRbWhibWJWS2t0WTNrdDVwSjRvZGNFU2ZSbUxvTU94bXBUTjVWVWJvcy1aNE1tUGdYQk0xRFFIWXd0MG9tMTJQU28taXZFZGxHTzEwSEVwSnY2bnBHTDE2SklhVW9pQkhVeGtsWU5fWjlwaUN5T3dDSQ==" + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "message": "\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**", + "metadata": { + "openai_item_id": "bXNnXzA2YjdhNzEzZmU5ZmNmOGUwMTZhNTNjYWE4Zjk2NDgxOTdhMDIwYTc2ZWQ4MDM2Yzhk", + "openai_phase": "ZmluYWxfYW5zd2Vy" + } + }, + { + "type": "config_delta", + "timestamp": "2020-01-01 00:00:00.0", + "delta": { + "assistant": { + "model": { + "id": { + "name": "test" + } + } + } + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_request", + "content": "Repeat your previous answer." + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "message": "\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**", + "metadata": { + "openai_item_id": "bXNnXzA2YjdhNzEzZmU5ZmNmOGUwMTZhNTNjYWFiMWE5YzgxOTdhYmU5Zjk3YWU5MmZkNTcx", + "openai_phase": "ZmluYWxfYW5zd2Vy" + } + } + ] +} diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching__raw_events.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching__raw_events.snap new file mode 100644 index 00000000..49651ba0 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_pro_reasoning_and_explicit_caching__raw_events.snap @@ -0,0 +1,569 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Part { + index: 0, + part: Reasoning( + "", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "**Calculating step by step**\n\nAlright", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ",", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " I", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " need", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " to", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " approach", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " this", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " systematically", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " First", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " things", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " first", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ",", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " I", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " want", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " to", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " ignore", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " any", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " irrelevant", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " documents", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " that", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " might", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " clutter", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " my", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " thinking", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " I", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "’ll", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " focus", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " on", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " calculation", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ":", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 191", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " multiplied", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " by", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " equals", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 133", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " Keeping", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " it", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " concise", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " helps", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " me", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " deliver", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " answer", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " more", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " clearly", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " So", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ",", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " I", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "’ll", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " summarize", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " it", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ":", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " calculation", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " is", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " straightforward", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " and", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " yields", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " result", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " of", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 133", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Flush { + index: 0, + metadata: { + "openai_item_id": String("rs_06b7a713fe9fcf8e016a53caa7b45481978b021a7ac5812717"), + "openai_encrypted_content": String("gAAAAABqU8qodvILz8NAylGDzyKtHddMXWdR7oBjIxLlKYazeZ8hhYPUf6J71xN2LkJlNbqVCr5fG4cCLZEEJs91_2gO5QmgADN-0Z6t_BD23PFUMPOExUyUCgy7exNVFLOKGpRsLq31qaM5xMy_G8oPQ0dyk--x2si2RTIG7pzowDBM9o3TJ-2mtl58tkQ1nK1JNaI056LnPYqmLbuS_G4gsN97ShAOXfLdJqIdn39My9U3BzpHvyWkOjojMDy6_6Hl7B-jrsW4OeMlzlW97iCeq3qAJQ44C4hhHeD2_r2aIqu0eMCzX5U7sqbOc931_CVdjK7_OsqLPyTffRkBy7AuhqbDlrHaCePOV-Eq5rfxQAqU6fUXvsZQ-85W8PF63s9XHPL6dlF89qXr-l75P9JB0lpljs6N-5X5KzHROoxptYHGPhTAisooTL8xgaJzYiypZmI-U-Sk5GB97c4jNsUgBtI7uDlB-4LSUK2Ns5yhTCMDDON9vzsXG71t9dSjscgPVQ67wf3ACqk263zi4tyr42_tJF_aCX6NaOIfqfVoziiQsGvAZfEsbTZmHxxFRq4nM4GEA3VStKYNzFPuuOCe814378q-eTQyCqtNEchDuY87lPRwbO_IFRWp6Y-RnfuxBsAcyQDRs4WNI80wSYRJBeZW_Hgenr8gByztNGxWUxhPPMVSTKj-KtMnB6mdrcyCACVsM-Gy4HDeGU2Sz2_j6_2lPYa4g-EPnA23Fz5n6GRJmWcuohKEESZMvZIWBmBoZUAp_hnAywbPIbdY0e3K8gZ-ClGzi9Pt98nyMF9skW8u_PPx50gxSHb1ombNo5cHhO3WbdCYTzJSK9N6AiqP2WLhQAK3gpM_q1jsm-Y8JN8Jgmrm6io00rhLPl9kdORPBd_cDMjB-LDlwMXsTkkoUeJQmhbmbVKktY3kt5pJ4odcESfRmLoMOxmpTN5VUbos-Z4MmPgXBM1DQHYwt0om12PSo-ivEdlGO10HEpJv6npGL16JIaUoiBHUxklYN_Z9piCyOwCI"), + }, + }, + Part { + index: 1, + part: Message( + "", + ), + metadata: {}, + }, + Part { + index: 1, + part: Message( + "\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**", + ), + metadata: {}, + }, + Flush { + index: 1, + metadata: { + "openai_item_id": String("msg_06b7a713fe9fcf8e016a53caa8f9648197a020a76ed8036c8d"), + "openai_phase": String("final_answer"), + }, + }, + Finished( + Completed, + ), + ], + [ + Part { + index: 0, + part: Message( + "", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + "\\(191 \\times 7\\):\n\n- \\(190 \\times 7 = 1330\\)\n- \\(1 \\times 7 = 7\\)\n- \\(1330 + 7 = 1337\\)\n\n**Answer: 1337**", + ), + metadata: {}, + }, + Flush { + index: 0, + metadata: { + "openai_item_id": String("msg_06b7a713fe9fcf8e016a53caab1a9c8197abe9f97ae92fd571"), + "openai_phase": String("final_answer"), + }, + }, + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write.snap new file mode 100644 index 00000000..53ea2fea --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write.snap @@ -0,0 +1,44 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Message { + message: "The fox.", + }, + ), + metadata: { + "openai_item_id": String("msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a"), + "openai_phase": String("final_answer"), + }, + }, + ), + Finished( + Completed, + ), + ], + [ + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Message { + message: "The dog.", + }, + ), + metadata: { + "openai_item_id": String("msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd"), + "openai_phase": String("final_answer"), + }, + }, + ), + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write.yml b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write.yml new file mode 100644 index 00000000..5287ad0a --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write.yml @@ -0,0 +1,220 @@ +when: + path: /v1/responses + method: POST + json_body_str: >- + { + "model": "gpt-5.6", + "input": [ + { + "type": "message", + "role": "system", + "content": [ + { + "type": "input_text", + "text": "You are a concise assistant.", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "\n \n 0\n file:///novel.txt\n The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. \n \n", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": "What animal jumps over the dog?" + } + ], + "include": null, + "instructions": null, + "max_output_tokens": null, + "metadata": null, + "parallel_tool_calls": null, + "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", + "reasoning": { + "effort": "none", + "summary": "auto" + }, + "service_tier": null, + "store": false, + "stream": true, + "temperature": null, + "text": null, + "tool_choice": "auto", + "tools": [], + "top_p": null, + "truncation": "auto", + "user": null + } +then: + status: 200 + header: + - name: content-type + value: text/event-stream; charset=utf-8 + body: |+ + event: response.created + data: {"type":"response.created","response":{"id":"resp_041a23d55da6b2b6016a549a20dcb08194b8978ea9fb505ceb","object":"response","created_at":1783929376,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"none","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + event: response.in_progress + data: {"type":"response.in_progress","response":{"id":"resp_041a23d55da6b2b6016a549a20dcb08194b8978ea9fb505ceb","object":"response","created_at":1783929376,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"none","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","type":"message","status":"in_progress","content":[],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":2} + + event: response.content_part.added + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","logprobs":[],"obfuscation":"FCM8i1b7Jqhqv","output_index":0,"sequence_number":4} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":" fox","item_id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","logprobs":[],"obfuscation":"Kf5UvVC8r2Ta","output_index":0,"sequence_number":5} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","logprobs":[],"obfuscation":"FJ5G4VYpMXue8rs","output_index":0,"sequence_number":6} + + event: response.output_text.done + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","logprobs":[],"output_index":0,"sequence_number":7,"text":"The fox."} + + event: response.content_part.done + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"The fox."},"sequence_number":8} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The fox."}],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":9} + + event: response.completed + data: {"type":"response.completed","response":{"id":"resp_041a23d55da6b2b6016a549a20dcb08194b8978ea9fb505ceb","object":"response","created_at":1783929376,"status":"completed","background":false,"completed_at":1783929378,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[{"id":"msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The fox."}],"phase":"final_answer","role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"none","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":{"input_tokens":4067,"input_tokens_details":{"cache_write_tokens":4064,"cached_tokens":0},"output_tokens":7,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":4074},"user":null,"metadata":{}},"sequence_number":10} + +--- +when: + path: /v1/responses + method: POST + json_body_str: >- + { + "model": "gpt-5.6", + "input": [ + { + "type": "message", + "role": "system", + "content": [ + { + "type": "input_text", + "text": "You are a concise assistant.", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "\n \n 0\n file:///novel.txt\n The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. \n \n", + "prompt_cache_breakpoint": { + "mode": "explicit" + } + } + ] + }, + { + "type": "message", + "role": "user", + "content": "What animal jumps over the dog?" + }, + { + "type": "message", + "content": [ + { + "type": "output_text", + "text": "The fox.", + "annotations": [] + } + ], + "id": "msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a", + "role": "assistant", + "status": "completed", + "phase": "final_answer" + }, + { + "type": "message", + "role": "user", + "content": "And what animal gets jumped over?" + } + ], + "include": null, + "instructions": null, + "max_output_tokens": null, + "metadata": null, + "parallel_tool_calls": null, + "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", + "reasoning": { + "effort": "none", + "summary": "auto" + }, + "service_tier": null, + "store": false, + "stream": true, + "temperature": null, + "text": null, + "tool_choice": "auto", + "tools": [], + "top_p": null, + "truncation": "auto", + "user": null + } +then: + status: 200 + header: + - name: content-type + value: text/event-stream; charset=utf-8 + body: |+ + event: response.created + data: {"type":"response.created","response":{"id":"resp_041a23d55da6b2b6016a549a22c04c8194b3c4539aa31b6bc0","object":"response","created_at":1783929378,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"none","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + event: response.in_progress + data: {"type":"response.in_progress","response":{"id":"resp_041a23d55da6b2b6016a549a22c04c8194b3c4539aa31b6bc0","object":"response","created_at":1783929378,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"none","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","type":"message","status":"in_progress","content":[],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":2} + + event: response.content_part.added + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","logprobs":[],"obfuscation":"Mjc8A5O5GFoPG","output_index":0,"sequence_number":4} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":" dog","item_id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","logprobs":[],"obfuscation":"Gh4Z6eD6gKyd","output_index":0,"sequence_number":5} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","logprobs":[],"obfuscation":"FKWdV7hHkYVtyjx","output_index":0,"sequence_number":6} + + event: response.output_text.done + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","logprobs":[],"output_index":0,"sequence_number":7,"text":"The dog."} + + event: response.content_part.done + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"The dog."},"sequence_number":8} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The dog."}],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":9} + + event: response.completed + data: {"type":"response.completed","response":{"id":"resp_041a23d55da6b2b6016a549a22c04c8194b3c4539aa31b6bc0","object":"response","created_at":1783929378,"status":"completed","background":false,"completed_at":1783929380,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.6-sol","moderation":null,"output":[{"id":"msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The dog."}],"phase":"final_answer","role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_options":{"mode":"implicit","ttl":"30m"},"prompt_cache_retention":"24h","reasoning":{"context":"all_turns","effort":"none","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"auto","usage":{"input_tokens":4087,"input_tokens_details":{"cache_write_tokens":20,"cached_tokens":4064},"output_tokens":7,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":4094},"user":null,"metadata":{}},"sequence_number":10} + diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write__conversation_stream.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write__conversation_stream.snap new file mode 100644 index 00000000..9530ff5a --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write__conversation_stream.snap @@ -0,0 +1,259 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +{ + "base_config": { + "inherit": false, + "config_load_paths": [], + "extends": [ + "config.d/**/*" + ], + "assistant": { + "system_prompt": "You are a helpful assistant.", + "system_prompt_sections": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "instructions": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "tool_choice": "auto", + "model": { + "id": { + "provider": "openai", + "name": "test" + }, + "parameters": { + "reasoning": "off", + "stop_words": [], + "other": {} + } + }, + "request": { + "max_retries": 5, + "base_backoff_ms": 1000, + "max_backoff_secs": 60, + "stream_idle_timeout_secs": 60, + "cache": true + } + }, + "conversation": { + "title": { + "generate": { + "auto": false + }, + "from_heading": true + }, + "tools": { + "*": { + "run": "ask", + "result": "unattended", + "cancellation_response": "This tool request was intentionally rejected by the user. Please evaluate and either ask the user why it was rejected, or infer the reason by looking at the historical messages in the conversation.", + "style": { + "hidden": false, + "inline_results": { + "truncate": { + "lines": 10 + } + }, + "results_file_link": "full", + "parameters": "json" + } + } + }, + "compaction": { + "rules": { + "value": [ + { + "keep_first": "1", + "keep_last": "1", + "reasoning": "strip", + "tool_calls": "strip" + } + ], + "strategy": "replace", + "discard_when_merged": false + } + }, + "attachments": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "start_local": false + }, + "style": { + "code": { + "color": true, + "line_numbers": false, + "file_link": "osc8", + "copy_link": "off" + }, + "markdown": { + "wrap_width": 80, + "table_max_column_width": 40, + "theme": "gruvbox-dark", + "hr_style": "line" + }, + "reasoning": { + "display": "full", + "background": 236, + "extend_across_tool_calls": true + }, + "streaming": { + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "lock_wait": { + "show": true, + "delay_secs": 1, + "interval_ms": 100, + "timeout_secs": 10 + }, + "tool_call": { + "show": true, + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + }, + "preparing": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "typewriter": { + "text_delay": "3ms", + "code_delay": "500us", + "max_latency": "0s" + } + }, + "interrupt": { + "escalation_cooldown_secs": 2, + "streaming": { + "action": "prompt", + "compose_in_editor": false + }, + "tool_call": { + "action": "prompt", + "compose_in_editor": false + } + }, + "editor": { + "envs": [ + "JP_EDITOR", + "VISUAL", + "EDITOR" + ], + "inline": { + "edit_mode": "emacs" + } + }, + "providers": { + "llm": { + "anthropic": { + "api_key_env": "ANTHROPIC_API_KEY", + "base_url": "https://api.anthropic.com", + "chain_on_max_tokens": true, + "beta_headers": [] + }, + "cerebras": { + "api_key_env": "CEREBRAS_API_KEY", + "base_url": "https://api.cerebras.ai" + }, + "deepseek": { + "api_key_env": "DEEPSEEK_API_KEY", + "base_url": "https://api.deepseek.com" + }, + "google": { + "api_key_env": "GEMINI_API_KEY", + "base_url": "https://generativelanguage.googleapis.com/v1beta" + }, + "llamacpp": { + "base_url": "http://127.0.0.1:8080" + }, + "ollama": { + "base_url": "http://localhost:11434" + }, + "openai": { + "api_key_env": "OPENAI_API_KEY", + "base_url": "https://api.openai.com", + "base_url_env": "OPENAI_BASE_URL" + }, + "openrouter": { + "api_key_env": "OPENROUTER_API_KEY", + "app_name": "JP", + "base_url": "https://openrouter.ai" + } + } + }, + "plugins": { + "auto_install": true, + "shutdown_timeout_secs": 5 + } + }, + "events": [ + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_request", + "content": "What animal jumps over the dog?" + }, + { + "type": "config_delta", + "timestamp": "2020-01-01 00:00:00.0", + "delta": { + "assistant": { + "model": { + "id": { + "name": "gpt-5.6" + } + } + } + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "message": "The fox.", + "metadata": { + "openai_item_id": "bXNnXzA0MWEyM2Q1NWRhNmIyYjYwMTZhNTQ5YTIyNDY3ODgxOTQ4MWI1OWIxNzhiYmI0MzZh", + "openai_phase": "ZmluYWxfYW5zd2Vy" + } + }, + { + "type": "config_delta", + "timestamp": "2020-01-01 00:00:00.0", + "delta": { + "assistant": { + "model": { + "id": { + "name": "test" + } + } + } + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_request", + "content": "And what animal gets jumped over?" + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "message": "The dog.", + "metadata": { + "openai_item_id": "bXNnXzA0MWEyM2Q1NWRhNmIyYjYwMTZhNTQ5YTIzZmZlNDgxOTRiOTM3ZjIwZDA5YmU3ZmZk", + "openai_phase": "ZmluYWxfYW5zd2Vy" + } + } + ] +} diff --git a/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write__raw_events.snap b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write__raw_events.snap new file mode 100644 index 00000000..d9f48e0b --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_gpt_5_6_prompt_cache_read_after_write__raw_events.snap @@ -0,0 +1,86 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Part { + index: 0, + part: Message( + "", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + "The", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + " fox", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + ".", + ), + metadata: {}, + }, + Flush { + index: 0, + metadata: { + "openai_item_id": String("msg_041a23d55da6b2b6016a549a224678819481b59b178bbb436a"), + "openai_phase": String("final_answer"), + }, + }, + Finished( + Completed, + ), + ], + [ + Part { + index: 0, + part: Message( + "", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + "The", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + " dog", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + ".", + ), + metadata: {}, + }, + Flush { + index: 0, + metadata: { + "openai_item_id": String("msg_041a23d55da6b2b6016a549a23ffe48194b937f20d09be7ffd"), + "openai_phase": String("final_answer"), + }, + }, + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_image_attachment.yml b/crates/jp_llm/tests/fixtures/openai/test_image_attachment.yml index 3f8dab14..50238982 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_image_attachment.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_image_attachment.yml @@ -38,6 +38,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap b/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap index f8109b5d..b9292b28 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap +++ b/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap @@ -29,7 +29,12 @@ expression: v 2024-05-31, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-mini", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [ diff --git a/crates/jp_llm/tests/fixtures/openai/test_models.yml b/crates/jp_llm/tests/fixtures/openai/test_models.yml index 19b002f0..8a5abd70 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_models.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_models.yml @@ -11,16 +11,16 @@ then: "object": "list", "data": [ { - "id": "gpt-4-0613", + "id": "text-embedding-ada-002", "object": "model", - "created": 1686588896, - "owned_by": "openai" + "created": 1671217299, + "owned_by": "openai-internal" }, { - "id": "gpt-4", + "id": "whisper-1", "object": "model", - "created": 1687882411, - "owned_by": "openai" + "created": 1677532384, + "owned_by": "openai-internal" }, { "id": "gpt-3.5-turbo", @@ -29,34 +29,28 @@ then: "owned_by": "openai" }, { - "id": "gpt-5.2-codex", - "object": "model", - "created": 1766164985, - "owned_by": "system" - }, - { - "id": "gpt-4o-mini-tts-2025-12-15", + "id": "tts-1", "object": "model", - "created": 1765610837, - "owned_by": "system" + "created": 1681940951, + "owned_by": "openai-internal" }, { - "id": "gpt-realtime-mini-2025-12-15", + "id": "gpt-3.5-turbo-16k", "object": "model", - "created": 1765612007, - "owned_by": "system" + "created": 1683758102, + "owned_by": "openai-internal" }, { - "id": "gpt-audio-mini-2025-12-15", + "id": "gpt-4-0613", "object": "model", - "created": 1765760008, - "owned_by": "system" + "created": 1686588896, + "owned_by": "openai" }, { - "id": "chatgpt-image-latest", + "id": "gpt-4", "object": "model", - "created": 1765925279, - "owned_by": "system" + "created": 1687882411, + "owned_by": "openai" }, { "id": "davinci-002", @@ -82,24 +76,6 @@ then: "created": 1694122472, "owned_by": "system" }, - { - "id": "dall-e-3", - "object": "model", - "created": 1698785189, - "owned_by": "system" - }, - { - "id": "dall-e-2", - "object": "model", - "created": 1698798177, - "owned_by": "system" - }, - { - "id": "gpt-4-1106-preview", - "object": "model", - "created": 1698957206, - "owned_by": "system" - }, { "id": "gpt-3.5-turbo-1106", "object": "model", @@ -136,18 +112,6 @@ then: "created": 1705953180, "owned_by": "system" }, - { - "id": "gpt-4-0125-preview", - "object": "model", - "created": 1706037612, - "owned_by": "system" - }, - { - "id": "gpt-4-turbo-preview", - "object": "model", - "created": 1706037777, - "owned_by": "system" - }, { "id": "gpt-3.5-turbo-0125", "object": "model", @@ -196,18 +160,6 @@ then: "created": 1722814719, "owned_by": "system" }, - { - "id": "gpt-4o-audio-preview", - "object": "model", - "created": 1727460443, - "owned_by": "system" - }, - { - "id": "gpt-4o-realtime-preview", - "object": "model", - "created": 1727659998, - "owned_by": "system" - }, { "id": "omni-moderation-latest", "object": "model", @@ -220,30 +172,6 @@ then: "created": 1732734466, "owned_by": "system" }, - { - "id": "gpt-4o-realtime-preview-2024-12-17", - "object": "model", - "created": 1733945430, - "owned_by": "system" - }, - { - "id": "gpt-4o-audio-preview-2024-12-17", - "object": "model", - "created": 1734034239, - "owned_by": "system" - }, - { - "id": "gpt-4o-mini-realtime-preview-2024-12-17", - "object": "model", - "created": 1734112601, - "owned_by": "system" - }, - { - "id": "gpt-4o-mini-audio-preview-2024-12-17", - "object": "model", - "created": 1734115920, - "owned_by": "system" - }, { "id": "o1-2024-12-17", "object": "model", @@ -256,18 +184,6 @@ then: "created": 1734375816, "owned_by": "system" }, - { - "id": "gpt-4o-mini-realtime-preview", - "object": "model", - "created": 1734387380, - "owned_by": "system" - }, - { - "id": "gpt-4o-mini-audio-preview", - "object": "model", - "created": 1734387424, - "owned_by": "system" - }, { "id": "computer-use-preview", "object": "model", @@ -298,18 +214,6 @@ then: "created": 1741377021, "owned_by": "system" }, - { - "id": "gpt-4o-search-preview-2025-03-11", - "object": "model", - "created": 1741388170, - "owned_by": "system" - }, - { - "id": "gpt-4o-search-preview", - "object": "model", - "created": 1741388720, - "owned_by": "system" - }, { "id": "gpt-4o-mini-search-preview-2025-03-11", "object": "model", @@ -424,18 +328,6 @@ then: "created": 1748475349, "owned_by": "system" }, - { - "id": "gpt-4o-realtime-preview-2025-06-03", - "object": "model", - "created": 1748907838, - "owned_by": "system" - }, - { - "id": "gpt-4o-audio-preview-2025-06-03", - "object": "model", - "created": 1748908498, - "owned_by": "system" - }, { "id": "o3-pro-2025-06-10", "object": "model", @@ -701,28 +593,208 @@ then: "owned_by": "system" }, { - "id": "gpt-3.5-turbo-16k", + "id": "gpt-4o-mini-tts-2025-12-15", "object": "model", - "created": 1683758102, - "owned_by": "openai-internal" + "created": 1765610837, + "owned_by": "system" }, { - "id": "tts-1", + "id": "gpt-realtime-mini-2025-12-15", "object": "model", - "created": 1681940951, - "owned_by": "openai-internal" + "created": 1765612007, + "owned_by": "system" }, { - "id": "whisper-1", + "id": "gpt-audio-mini-2025-12-15", "object": "model", - "created": 1677532384, - "owned_by": "openai-internal" + "created": 1765760008, + "owned_by": "system" }, { - "id": "text-embedding-ada-002", + "id": "chatgpt-image-latest", "object": "model", - "created": 1671217299, - "owned_by": "openai-internal" + "created": 1765925279, + "owned_by": "system" + }, + { + "id": "gpt-5.2-codex", + "object": "model", + "created": 1766164985, + "owned_by": "system" + }, + { + "id": "gpt-5.3-codex", + "object": "model", + "created": 1770537915, + "owned_by": "system" + }, + { + "id": "gpt-realtime-1.5", + "object": "model", + "created": 1771461469, + "owned_by": "system" + }, + { + "id": "gpt-audio-1.5", + "object": "model", + "created": 1771550885, + "owned_by": "system" + }, + { + "id": "gpt-4o-search-preview", + "object": "model", + "created": 1771905534, + "owned_by": "system" + }, + { + "id": "gpt-4o-search-preview-2025-03-11", + "object": "model", + "created": 1771905621, + "owned_by": "system" + }, + { + "id": "gpt-5.3-chat-latest", + "object": "model", + "created": 1772236571, + "owned_by": "system" + }, + { + "id": "gpt-5.4-2026-03-05", + "object": "model", + "created": 1772654062, + "owned_by": "system" + }, + { + "id": "gpt-5.4-pro", + "object": "model", + "created": 1772659601, + "owned_by": "system" + }, + { + "id": "gpt-5.4-pro-2026-03-05", + "object": "model", + "created": 1772659657, + "owned_by": "system" + }, + { + "id": "gpt-5.4", + "object": "model", + "created": 1772691852, + "owned_by": "system" + }, + { + "id": "gpt-5.4-nano-2026-03-17", + "object": "model", + "created": 1773450837, + "owned_by": "system" + }, + { + "id": "gpt-5.4-nano", + "object": "model", + "created": 1773450870, + "owned_by": "system" + }, + { + "id": "gpt-5.4-mini-2026-03-17", + "object": "model", + "created": 1773451076, + "owned_by": "system" + }, + { + "id": "gpt-5.4-mini", + "object": "model", + "created": 1773451123, + "owned_by": "system" + }, + { + "id": "gpt-image-2", + "object": "model", + "created": 1776399795, + "owned_by": "system" + }, + { + "id": "gpt-image-2-2026-04-21", + "object": "model", + "created": 1776399994, + "owned_by": "system" + }, + { + "id": "gpt-5.5", + "object": "model", + "created": 1776824847, + "owned_by": "system" + }, + { + "id": "gpt-5.5-2026-04-23", + "object": "model", + "created": 1776839241, + "owned_by": "system" + }, + { + "id": "gpt-5.5-pro", + "object": "model", + "created": 1776894349, + "owned_by": "system" + }, + { + "id": "gpt-5.5-pro-2026-04-23", + "object": "model", + "created": 1776894470, + "owned_by": "system" + }, + { + "id": "chat-latest", + "object": "model", + "created": 1777704602, + "owned_by": "system" + }, + { + "id": "gpt-realtime-translate", + "object": "model", + "created": 1777950216, + "owned_by": "system" + }, + { + "id": "gpt-realtime-2", + "object": "model", + "created": 1778006032, + "owned_by": "system" + }, + { + "id": "gpt-realtime-whisper", + "object": "model", + "created": 1778012060, + "owned_by": "system" + }, + { + "id": "gpt-5.6-sol", + "object": "model", + "created": 1782228018, + "owned_by": "system" + }, + { + "id": "gpt-5.6-terra", + "object": "model", + "created": 1782228459, + "owned_by": "system" + }, + { + "id": "gpt-5.6-luna", + "object": "model", + "created": 1782228658, + "owned_by": "system" + }, + { + "id": "gpt-realtime-2.1", + "object": "model", + "created": 1782254687, + "owned_by": "system" + }, + { + "id": "gpt-realtime-2.1-mini", + "object": "model", + "created": 1782254706, + "owned_by": "system" } ] } diff --git a/crates/jp_llm/tests/fixtures/openai/test_models__models.snap b/crates/jp_llm/tests/fixtures/openai/test_models__models.snap index e01fcb10..a1e88e69 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_models__models.snap +++ b/crates/jp_llm/tests/fixtures/openai/test_models__models.snap @@ -7,7 +7,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "gpt-4-0613", + "text-embedding-ada-002", ), }, display_name: None, @@ -23,7 +23,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "gpt-4", + "whisper-1", ), }, display_name: None, @@ -55,44 +55,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "gpt-5.2-codex", - ), - }, - display_name: Some( - "GPT-5.2 Codex", - ), - context_window: Some( - 400000, - ), - max_output_tokens: Some( - 128000, - ), - reasoning: Some( - Leveled { - none: false, - xlow: false, - low: true, - medium: true, - high: true, - xhigh: true, - }, - ), - knowledge_cutoff: Some( - 2025-08-31, - ), - deprecated: Some( - Active, - ), - structured_output: None, - features: [ - "temp_requires_no_reasoning", - ], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-mini-tts-2025-12-15", + "tts-1", ), }, display_name: None, @@ -108,7 +71,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "gpt-realtime-mini-2025-12-15", + "gpt-3.5-turbo-16k", ), }, display_name: None, @@ -124,7 +87,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "gpt-audio-mini-2025-12-15", + "gpt-4-0613", ), }, display_name: None, @@ -140,7 +103,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "chatgpt-image-latest", + "gpt-4", ), }, display_name: None, @@ -216,54 +179,6 @@ expression: v structured_output: None, features: [], }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "dall-e-3", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "dall-e-2", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4-1106-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, ModelDetails { id: ModelIdConfig { provider: Openai, @@ -360,38 +275,6 @@ expression: v structured_output: None, features: [], }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4-0125-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4-turbo-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, ModelDetails { id: ModelIdConfig { provider: Openai, @@ -463,7 +346,10 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: None, + }, ), structured_output: None, features: [], @@ -563,43 +449,14 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: None, + }, ), structured_output: None, features: [], }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-audio-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-realtime-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, ModelDetails { id: ModelIdConfig { provider: Openai, @@ -632,70 +489,6 @@ expression: v structured_output: None, features: [], }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-realtime-preview-2024-12-17", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-audio-preview-2024-12-17", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-mini-realtime-preview-2024-12-17", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-mini-audio-preview-2024-12-17", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, ModelDetails { id: ModelIdConfig { provider: Openai, @@ -722,7 +515,12 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -753,43 +551,16 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-mini-realtime-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-mini-audio-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, ModelDetails { id: ModelIdConfig { provider: Openai, @@ -832,7 +603,12 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -863,7 +639,12 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -875,12 +656,27 @@ expression: v "gpt-4o-2024-11-20", ), }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, + display_name: Some( + "GPT-4o", + ), + context_window: Some( + 128000, + ), + max_output_tokens: Some( + 16384, + ), + reasoning: Some( + Unsupported, + ), + knowledge_cutoff: Some( + 2023-10-01, + ), + deprecated: Some( + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: None, + }, + ), structured_output: None, features: [], }, @@ -900,38 +696,6 @@ expression: v structured_output: None, features: [], }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-search-preview-2025-03-11", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-search-preview", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, ModelDetails { id: ModelIdConfig { provider: Openai, @@ -1022,7 +786,12 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -1053,7 +822,12 @@ expression: v 2023-10-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -1100,7 +874,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [], @@ -1131,7 +910,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-mini", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -1162,7 +946,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [], @@ -1193,7 +982,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-mini", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -1333,7 +1127,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-nano", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -1361,7 +1160,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-nano", + retire_at: Some( + 2026-10-23, + ), + }, ), structured_output: None, features: [], @@ -1408,43 +1212,16 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [], }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-realtime-preview-2025-06-03", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, - ModelDetails { - id: ModelIdConfig { - provider: Openai, - name: Name( - "gpt-4o-audio-preview-2025-06-03", - ), - }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, - structured_output: None, - features: [], - }, ModelDetails { id: ModelIdConfig { provider: Openai, @@ -1471,7 +1248,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [], @@ -1502,7 +1284,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [], @@ -1533,7 +1320,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [], @@ -1580,7 +1372,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [], @@ -1611,7 +1408,12 @@ expression: v 2024-06-01, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [], @@ -1640,13 +1442,19 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [ @@ -1677,13 +1485,19 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [ @@ -1714,13 +1528,17 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: None, + }, ), structured_output: None, features: [ @@ -1753,7 +1571,12 @@ expression: v 2024-05-31, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-mini", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [ @@ -1786,7 +1609,12 @@ expression: v 2024-05-31, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-mini", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [ @@ -1819,7 +1647,12 @@ expression: v 2024-05-31, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-nano", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [], @@ -1850,7 +1683,12 @@ expression: v 2024-05-31, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-nano", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [], @@ -1945,7 +1783,12 @@ expression: v 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [ @@ -1975,14 +1818,41 @@ expression: v "gpt-5-pro-2025-10-06", ), }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, + display_name: Some( + "GPT-5 pro", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: false, + medium: false, + high: true, + xhigh: false, + max: false, + }, + ), + knowledge_cutoff: Some( + 2024-09-30, + ), + deprecated: Some( + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-12-11, + ), + }, + ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2008,13 +1878,19 @@ expression: v medium: false, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5-pro", + retire_at: Some( + 2026-12-11, + ), + }, ), structured_output: None, features: [ @@ -2173,13 +2049,19 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [ @@ -2210,6 +2092,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( @@ -2247,6 +2130,7 @@ expression: v medium: true, high: true, xhigh: false, + max: false, }, ), knowledge_cutoff: Some( @@ -2286,7 +2170,12 @@ expression: v 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [ @@ -2319,7 +2208,12 @@ expression: v 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.4-mini", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [ @@ -2352,7 +2246,12 @@ expression: v 2024-09-30, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-07-23, + ), + }, ), structured_output: None, features: [ @@ -2399,6 +2298,7 @@ expression: v medium: true, high: true, xhigh: true, + max: false, }, ), knowledge_cutoff: Some( @@ -2436,6 +2336,7 @@ expression: v medium: true, high: true, xhigh: true, + max: false, }, ), knowledge_cutoff: Some( @@ -2473,6 +2374,7 @@ expression: v medium: true, high: true, xhigh: true, + max: false, }, ), knowledge_cutoff: Some( @@ -2510,6 +2412,7 @@ expression: v medium: true, high: true, xhigh: true, + max: false, }, ), knowledge_cutoff: Some( @@ -2547,13 +2450,19 @@ expression: v medium: true, high: true, xhigh: true, + max: false, }, ), knowledge_cutoff: Some( 2025-08-31, ), deprecated: Some( - Active, + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-08-10, + ), + }, ), structured_output: None, features: [ @@ -2612,7 +2521,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "gpt-3.5-turbo-16k", + "gpt-4o-mini-tts-2025-12-15", ), }, display_name: None, @@ -2628,7 +2537,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "tts-1", + "gpt-realtime-mini-2025-12-15", ), }, display_name: None, @@ -2644,7 +2553,7 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "whisper-1", + "gpt-audio-mini-2025-12-15", ), }, display_name: None, @@ -2660,7 +2569,904 @@ expression: v id: ModelIdConfig { provider: Openai, name: Name( - "text-embedding-ada-002", + "chatgpt-image-latest", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.2-codex", + ), + }, + display_name: Some( + "GPT-5.2 Codex", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-07-23, + ), + }, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.3-codex", + ), + }, + display_name: Some( + "GPT-5.3 Codex", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-realtime-1.5", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-audio-1.5", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-4o-search-preview", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-4o-search-preview-2025-03-11", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.3-chat-latest", + ), + }, + display_name: Some( + "GPT-5.3 Chat", + ), + context_window: Some( + 128000, + ), + max_output_tokens: Some( + 16384, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Deprecated { + note: "recommended replacement: gpt-5.5", + retire_at: Some( + 2026-08-10, + ), + }, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4-2026-03-05", + ), + }, + display_name: Some( + "GPT-5.4", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4-pro", + ), + }, + display_name: Some( + "GPT-5.4 pro", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: false, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4-pro-2026-03-05", + ), + }, + display_name: Some( + "GPT-5.4 pro", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: false, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4", + ), + }, + display_name: Some( + "GPT-5.4", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4-nano-2026-03-17", + ), + }, + display_name: Some( + "GPT-5.4 nano", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4-nano", + ), + }, + display_name: Some( + "GPT-5.4 nano", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4-mini-2026-03-17", + ), + }, + display_name: Some( + "GPT-5.4 mini", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.4-mini", + ), + }, + display_name: Some( + "GPT-5.4 mini", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-08-31, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-image-2", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-image-2-2026-04-21", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.5", + ), + }, + display_name: Some( + "GPT-5.5", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-12-01, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.5-2026-04-23", + ), + }, + display_name: Some( + "GPT-5.5", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-12-01, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.5-pro", + ), + }, + display_name: Some( + "GPT-5.5 pro", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: false, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-12-01, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + "streaming_unsupported", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.5-pro-2026-04-23", + ), + }, + display_name: Some( + "GPT-5.5 pro", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: false, + medium: true, + high: true, + xhigh: true, + max: false, + }, + ), + knowledge_cutoff: Some( + 2025-12-01, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + "streaming_unsupported", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "chat-latest", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-realtime-translate", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-realtime-2", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-realtime-whisper", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.6-sol", + ), + }, + display_name: Some( + "GPT-5.6 Sol", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: true, + }, + ), + knowledge_cutoff: Some( + 2026-02-16, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + "reasoning_pro_mode", + "persisted_reasoning", + "explicit_prompt_caching", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.6-terra", + ), + }, + display_name: Some( + "GPT-5.6 Terra", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: true, + }, + ), + knowledge_cutoff: Some( + 2026-02-16, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + "reasoning_pro_mode", + "persisted_reasoning", + "explicit_prompt_caching", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-5.6-luna", + ), + }, + display_name: Some( + "GPT-5.6 Luna", + ), + context_window: Some( + 1050000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: true, + xlow: false, + low: true, + medium: true, + high: true, + xhigh: true, + max: true, + }, + ), + knowledge_cutoff: Some( + 2026-02-16, + ), + deprecated: Some( + Active, + ), + structured_output: None, + features: [ + "temp_requires_no_reasoning", + "reasoning_pro_mode", + "persisted_reasoning", + "explicit_prompt_caching", + ], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-realtime-2.1", + ), + }, + display_name: None, + context_window: None, + max_output_tokens: None, + reasoning: None, + knowledge_cutoff: None, + deprecated: None, + structured_output: None, + features: [], + }, + ModelDetails { + id: ModelIdConfig { + provider: Openai, + name: Name( + "gpt-realtime-2.1-mini", ), }, display_name: None, diff --git a/crates/jp_llm/tests/fixtures/openai/test_multi_turn_conversation.yml b/crates/jp_llm/tests/fixtures/openai/test_multi_turn_conversation.yml index c09527e0..a8c990e5 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_multi_turn_conversation.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_multi_turn_conversation.yml @@ -22,6 +22,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" @@ -166,6 +167,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "low", "summary": "auto" @@ -544,6 +546,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" @@ -746,6 +749,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" @@ -973,6 +977,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "low", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model.snap b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model.snap new file mode 100644 index 00000000..3db3d517 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model.snap @@ -0,0 +1,56 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Reasoning { + reasoning: "**Calculating multiplication**\n\nI'm computing 7 times 191. To simplify, I think of it as 7 times (200 minus 9). That gives me 1400 minus 63, which equals 1337. So the result is 1337! It might be nice to illustrate the quick multiplication, but for now, I'll keep it straightforward and provide the answer succinctly: the final answer is 1337.", + }, + ), + metadata: { + "openai_item_id": String("rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef"), + "openai_encrypted_content": String("gAAAAABqU8qmH1D8RKxqAQ-pimWzfAWXQkcEPnhs3vTH8POrxRHAKBD-oHjN5C_TDv5TYFEHO_WTk7XKt0xHVAuTtfHawxtgcl3WJOSPv-B-ePZ9jnn1uqAjg_wONkC0uoohN1kZkT3DXoj06JgMruyx9TpuMT_vkIdvpKwlQiRxbgQjXNgisnD4odVI54PYG6HdbvRXjUk3slcWuXcqkOiQlhJ9wkqpOOGzsRHDnwUB1tTB9v3Zifu4sCaIdXsrhV8AUpXZmj26nvpnuz5jCiS5wYs46bF7RY6BJpcwAPUWf6MA0J7Am_bww1LokPcXnXZDG7jyKogyDNPDNPjd3nsup7SN3YnXn-3oUMfIaI3pXrck_K3bRdrix5NdFCL_nabUQPg9ULHEq7WsKOPad0hdZyhqLeLvsMdXwqp7wM8IToPh6NQamVq9ChBRAdTHw6jbmIvtISsxjOiZ1Ph_Ys4x1PZjlv8MrTXtNf_srfEt-C_KeX9M1VXmVq2ba5-mjNbOSrcEWAp2zy-zyMi3pnavicIVDRSU5yVbZzhvBtXhz8udW8iXPTl6Zn1juakdqiwtdBl4BEaostbsyx_9AzaDyzG6O4RCWmWH9VP3iepvSJKOu1g_XehiiXsGzvWWO01qlkPEIqGo_XLmdU4Rg0x61-N7QaFvy11Xtww4cBYAaBlogs1BZekAP6_-gf6skikOvO8w_XkeH_VRaGCvEjfA2NtLFTYss_OlLoKR2Q6y-Q91b4hU9nJMYyiKHdq9C8Umn0eKTps4vhqimISyPp2wMMOXIBCH-CK8tHlJ6LeWr4FIMdRNm-ObRvLSwY2vtSbph4iItR2dTmcr5OomX_2jZyy6W8AOACjoBxAzoOZHqTPLBy-k01XKjf-Z5rCwB5uERSYEQVVxxckPTFztOxQe6V1eF1uJj4OCv1oytZNxrkKi7ssxGKUqW-VZyZ9R6gYBJTv_2a4845R-fL3dXCJq_LCq-HXK1b8XfKZt0-eWZuX8xcogajU3zzVefJy5Tot-IJCpnroXrERXnZ2MnZBkZApTceKxp-Q4exSxB4hsSOEIK-zeRGMs_6JF-dHVZ3WJqQkGxAxn3Aol7O2d71kRk-QP6xt3jw=="), + }, + }, + ), + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Message { + message: "1337", + }, + ), + metadata: { + "openai_item_id": String("msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8"), + }, + }, + ), + Finished( + Completed, + ), + ], + [ + Flushed( + ConversationEvent { + timestamp: 2020-01-01 0:00:00.0 +00, + kind: ChatResponse( + Message { + message: "1337", + }, + ), + metadata: { + "openai_item_id": String("msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6"), + }, + }, + ), + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model.yml b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model.yml new file mode 100644 index 00000000..5ed286a8 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model.yml @@ -0,0 +1,415 @@ +when: + path: /v1/responses + method: POST + json_body_str: >- + { + "model": "gpt-5-mini", + "input": [ + { + "type": "message", + "role": "system", + "content": [] + }, + { + "type": "message", + "role": "user", + "content": "What is 7 * 191?" + } + ], + "include": [ + "reasoning.encrypted_content" + ], + "instructions": null, + "max_output_tokens": null, + "metadata": null, + "parallel_tool_calls": null, + "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", + "reasoning": { + "effort": "low", + "summary": "auto" + }, + "service_tier": null, + "store": false, + "stream": true, + "temperature": null, + "text": null, + "tool_choice": "auto", + "tools": [], + "top_p": null, + "truncation": "auto", + "user": null + } +then: + status: 200 + header: + - name: content-type + value: text/event-stream; charset=utf-8 + body: |+ + event: response.created + data: {"type":"response.created","response":{"id":"resp_01698470604a0ea3016a53caa345088197b3ca205951f546a3","object":"response","created_at":1783876259,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5-mini-2025-08-07","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_retention":"in_memory","reasoning":{"context":"current_turn","effort":"low","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + event: response.in_progress + data: {"type":"response.in_progress","response":{"id":"resp_01698470604a0ea3016a53caa345088197b3ca205951f546a3","object":"response","created_at":1783876259,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5-mini-2025-08-07","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_retention":"in_memory","reasoning":{"context":"current_turn","effort":"low","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","type":"reasoning","content":[],"encrypted_content":"gAAAAABqU8qjZXrmRl2zX416laRiOdrtmSKo911XPqH75TIjX9njrwaD95y-sPo1U1zcObzDofDx_X7ZhwKRqb7-CTj8lX1Qdn0Hqi0MxUzYM0jqhJ8vA67SPLw7ZBESzRZtN_4twtisHdyTsqU6qW9ccJmfZED8Urma1S_PcBV9UvMSmcte4uRyW0eKtKztVZCm8B0R3AYlHKL6WAaUu39lx0TbG2nSifh6ACVQiB8sLE0KDUjBrz_04ck9S4IMe2Et9Usy6tHE6-yrhrgkvWjn2uReXNhbfsFxVKI29ceYRXFav-32IezKzDH11kw7aXZKNlQsHWXubXbgUNpRcy-TQkhYgVBpK6Cgix7rCyaInhl7q65YPlGeovdFsN1VGIoY_rmZxtBjJkuvQWoBsVEXEs1yd5F_4hUGhEYY1al3g6LXquxpy9O22GIgXo3Mn6VhuNcXXqkZGShzJlMttLMtj8SWvnJX-7p5TVZPWdCG4a0KCgvDkLG7rngRqy0mIVHwtqfx452HAEKGPXrp6bGVggb-a4mLBrSZntDGqb9mils0jdC1H6Z0k4SBlH2PZsM-DmCnqa0lM8m2otOyHXdyZOpO0Pdtyli08eN0gWNhCxcx5MYDye9Uu74pMkox6yrbJcb2fYJ0E-jc6SzcWcOrY1f9x-vH5gMMCAgUoZuM6HWacEs-4WTCgE9vq8xnkcPgrkqFM9ttzXFDDo9N0VK_QmkXfVoGyeUSJxxNU6rtp9TUX7hGLVIgehpsxiCHmNjFpOjiPSwGkvhQH_Pejyo_FCtUn9O3ksn9_MkhfYxbp8d9kdYZjNbLJk-vv5xSoiu2ih3ERFFdVJcryjbKuH5x8tBaaUyjVVzN104DOpAx6pICNlYXabDavcR_CmNa-qVu32Z1kbupJz62VLaI-LfR_aK1-pQ2xGN-6mFbl9KQJG_fHpiUPA0=","summary":[]},"output_index":0,"sequence_number":2} + + event: response.reasoning_summary_part.added + data: {"type":"response.reasoning_summary_part.added","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","output_index":0,"part":{"type":"summary_text","text":""},"sequence_number":3,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"**Calculating multiplication**\n\nI'm","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"0wc1zkxkTboqm","output_index":0,"sequence_number":4,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" computing","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"YrNdYv","output_index":0,"sequence_number":5,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 7","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"TsHUTKKFhu8BLD","output_index":0,"sequence_number":6,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" times","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"SvPHf3rR84","output_index":0,"sequence_number":7,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 191","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"gqeHie7PT16p","output_index":0,"sequence_number":8,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"Ly2Hz9HDkVuktsB","output_index":0,"sequence_number":9,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" To","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"J65IQm3URgeJk","output_index":0,"sequence_number":10,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" simplify","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"TejIsUT","output_index":0,"sequence_number":11,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":",","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"ZDFcazHJXSYENNc","output_index":0,"sequence_number":12,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" I","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"GK6IEB76Y7Ukpm","output_index":0,"sequence_number":13,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" think","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"Hd0vh12tK8","output_index":0,"sequence_number":14,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" of","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"fLDAK4P5CMVnu","output_index":0,"sequence_number":15,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" it","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"Dt0XimmeBTB0O","output_index":0,"sequence_number":16,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" as","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"fA39nlkIwi0lV","output_index":0,"sequence_number":17,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 7","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"A18c8OqUDeNlJe","output_index":0,"sequence_number":18,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" times","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"2Biqdyvm95","output_index":0,"sequence_number":19,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" (","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"CkBsfZHF1bueKy","output_index":0,"sequence_number":20,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"200","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"X5w0xF31saSqo","output_index":0,"sequence_number":21,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" minus","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"Kg2Iq7aaBJ","output_index":0,"sequence_number":22,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 9","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"tpSF3cJCrcETrC","output_index":0,"sequence_number":23,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":").","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"sUji9lTU6FluVT","output_index":0,"sequence_number":24,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" That","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"E2STYOnpsXV","output_index":0,"sequence_number":25,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" gives","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"3hEiRzh6Xf","output_index":0,"sequence_number":26,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" me","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"4X5sMssayVUQs","output_index":0,"sequence_number":27,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 140","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"qUwDN3SPQOY9","output_index":0,"sequence_number":28,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"0","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"mIeQ6kHCfEk3hFz","output_index":0,"sequence_number":29,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" minus","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"fxeUxwSRYT","output_index":0,"sequence_number":30,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 63","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"w4DcFuyhKVqai","output_index":0,"sequence_number":31,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":",","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"OdpENP0ojPjSCGk","output_index":0,"sequence_number":32,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" which","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"OX3TIpRFkS","output_index":0,"sequence_number":33,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" equals","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"Odlyx77qT","output_index":0,"sequence_number":34,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 133","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"fpi5jjZnZTo8","output_index":0,"sequence_number":35,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"7","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"CkGDYQJc2vO9mLB","output_index":0,"sequence_number":36,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"SnCuOKAt6Ij28ML","output_index":0,"sequence_number":37,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" So","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"e9Z4v95Dd2HWX","output_index":0,"sequence_number":38,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"z8PJB4B63Gvz","output_index":0,"sequence_number":39,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" result","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"3lCwuam1e","output_index":0,"sequence_number":40,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" is","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"LIXp3RA8vb1fQ","output_index":0,"sequence_number":41,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 133","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"UCwTLSXsJI6d","output_index":0,"sequence_number":42,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"7","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"oIoxUybGm7U0kFZ","output_index":0,"sequence_number":43,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"!","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"fvCcNkYpwyq2qPz","output_index":0,"sequence_number":44,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" It","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"8EppDHYrZsAud","output_index":0,"sequence_number":45,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" might","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"ZZhABPbcpm","output_index":0,"sequence_number":46,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" be","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"0iqK3Y98Dvm4h","output_index":0,"sequence_number":47,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" nice","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"Zp4htidPIaE","output_index":0,"sequence_number":48,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" to","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"GwniOTObGlqrg","output_index":0,"sequence_number":49,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" illustrate","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"tJaL5","output_index":0,"sequence_number":50,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"mrFW8UtKuGBD","output_index":0,"sequence_number":51,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" quick","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"Ru1yuhZrgO","output_index":0,"sequence_number":52,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" multiplication","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"X","output_index":0,"sequence_number":53,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":",","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"VQRmNHNRyVcOPKo","output_index":0,"sequence_number":54,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" but","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"r2CiQCzTC7jO","output_index":0,"sequence_number":55,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" for","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"FFxGfcTFoX3r","output_index":0,"sequence_number":56,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" now","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"pCnLvMNOo51N","output_index":0,"sequence_number":57,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":",","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"J3rVZnbeV9WsA8g","output_index":0,"sequence_number":58,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" I'll","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"E68BtG4xnf9","output_index":0,"sequence_number":59,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" keep","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"oShKFynW4qa","output_index":0,"sequence_number":60,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" it","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"kBVqm4IGntEVh","output_index":0,"sequence_number":61,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" straightforward","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"","output_index":0,"sequence_number":62,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" and","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"AIJ97e9NGtg5","output_index":0,"sequence_number":63,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" provide","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"iPSSgIBt","output_index":0,"sequence_number":64,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"HyVS4wyYLaa2","output_index":0,"sequence_number":65,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" answer","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"zbv81nYo8","output_index":0,"sequence_number":66,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" succinct","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"4HYyTcH","output_index":0,"sequence_number":67,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"ly","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"xBdDJtZceU8MwX","output_index":0,"sequence_number":68,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":":","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"aessRuZTCYziFjj","output_index":0,"sequence_number":69,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" the","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"JdauUYddvcOK","output_index":0,"sequence_number":70,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" final","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"sssFbz6bIo","output_index":0,"sequence_number":71,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" answer","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"yN0hUaHP7","output_index":0,"sequence_number":72,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" is","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"vCgwjByMwMl7C","output_index":0,"sequence_number":73,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":" 133","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"BMJc8PwUPV6x","output_index":0,"sequence_number":74,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":"7","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"0xZ8rlmTf8lV6Wc","output_index":0,"sequence_number":75,"summary_index":0} + + event: response.reasoning_summary_text.delta + data: {"type":"response.reasoning_summary_text.delta","delta":".","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","obfuscation":"aantm3is74siBNe","output_index":0,"sequence_number":76,"summary_index":0} + + event: response.reasoning_summary_text.done + data: {"type":"response.reasoning_summary_text.done","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","output_index":0,"sequence_number":77,"summary_index":0,"text":"**Calculating multiplication**\n\nI'm computing 7 times 191. To simplify, I think of it as 7 times (200 minus 9). That gives me 1400 minus 63, which equals 1337. So the result is 1337! It might be nice to illustrate the quick multiplication, but for now, I'll keep it straightforward and provide the answer succinctly: the final answer is 1337."} + + event: response.reasoning_summary_part.done + data: {"type":"response.reasoning_summary_part.done","item_id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","output_index":0,"part":{"type":"summary_text","text":"**Calculating multiplication**\n\nI'm computing 7 times 191. To simplify, I think of it as 7 times (200 minus 9). That gives me 1400 minus 63, which equals 1337. So the result is 1337! It might be nice to illustrate the quick multiplication, but for now, I'll keep it straightforward and provide the answer succinctly: the final answer is 1337."},"sequence_number":78,"summary_index":0} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","type":"reasoning","content":[],"encrypted_content":"gAAAAABqU8qmH1D8RKxqAQ-pimWzfAWXQkcEPnhs3vTH8POrxRHAKBD-oHjN5C_TDv5TYFEHO_WTk7XKt0xHVAuTtfHawxtgcl3WJOSPv-B-ePZ9jnn1uqAjg_wONkC0uoohN1kZkT3DXoj06JgMruyx9TpuMT_vkIdvpKwlQiRxbgQjXNgisnD4odVI54PYG6HdbvRXjUk3slcWuXcqkOiQlhJ9wkqpOOGzsRHDnwUB1tTB9v3Zifu4sCaIdXsrhV8AUpXZmj26nvpnuz5jCiS5wYs46bF7RY6BJpcwAPUWf6MA0J7Am_bww1LokPcXnXZDG7jyKogyDNPDNPjd3nsup7SN3YnXn-3oUMfIaI3pXrck_K3bRdrix5NdFCL_nabUQPg9ULHEq7WsKOPad0hdZyhqLeLvsMdXwqp7wM8IToPh6NQamVq9ChBRAdTHw6jbmIvtISsxjOiZ1Ph_Ys4x1PZjlv8MrTXtNf_srfEt-C_KeX9M1VXmVq2ba5-mjNbOSrcEWAp2zy-zyMi3pnavicIVDRSU5yVbZzhvBtXhz8udW8iXPTl6Zn1juakdqiwtdBl4BEaostbsyx_9AzaDyzG6O4RCWmWH9VP3iepvSJKOu1g_XehiiXsGzvWWO01qlkPEIqGo_XLmdU4Rg0x61-N7QaFvy11Xtww4cBYAaBlogs1BZekAP6_-gf6skikOvO8w_XkeH_VRaGCvEjfA2NtLFTYss_OlLoKR2Q6y-Q91b4hU9nJMYyiKHdq9C8Umn0eKTps4vhqimISyPp2wMMOXIBCH-CK8tHlJ6LeWr4FIMdRNm-ObRvLSwY2vtSbph4iItR2dTmcr5OomX_2jZyy6W8AOACjoBxAzoOZHqTPLBy-k01XKjf-Z5rCwB5uERSYEQVVxxckPTFztOxQe6V1eF1uJj4OCv1oytZNxrkKi7ssxGKUqW-VZyZ9R6gYBJTv_2a4845R-fL3dXCJq_LCq-HXK1b8XfKZt0-eWZuX8xcogajU3zzVefJy5Tot-IJCpnroXrERXnZ2MnZBkZApTceKxp-Q4exSxB4hsSOEIK-zeRGMs_6JF-dHVZ3WJqQkGxAxn3Aol7O2d71kRk-QP6xt3jw==","summary":[{"type":"summary_text","text":"**Calculating multiplication**\n\nI'm computing 7 times 191. To simplify, I think of it as 7 times (200 minus 9). That gives me 1400 minus 63, which equals 1337. So the result is 1337! It might be nice to illustrate the quick multiplication, but for now, I'll keep it straightforward and provide the answer succinctly: the final answer is 1337."}]},"output_index":0,"sequence_number":79} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":1,"sequence_number":80} + + event: response.content_part.added + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":81} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"133","item_id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","logprobs":[],"obfuscation":"Uc8JIVGC8Evwf","output_index":1,"sequence_number":82} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"7","item_id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","logprobs":[],"obfuscation":"aVrIRvUqq4HcKnl","output_index":1,"sequence_number":83} + + event: response.output_text.done + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","logprobs":[],"output_index":1,"sequence_number":84,"text":"1337"} + + event: response.content_part.done + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"},"sequence_number":85} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"}],"role":"assistant"},"output_index":1,"sequence_number":86} + + event: response.completed + data: {"type":"response.completed","response":{"id":"resp_01698470604a0ea3016a53caa345088197b3ca205951f546a3","object":"response","created_at":1783876259,"status":"completed","background":false,"completed_at":1783876262,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5-mini-2025-08-07","moderation":null,"output":[{"id":"rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef","type":"reasoning","content":[],"encrypted_content":"gAAAAABqU8qmwOsgZjOTWg2nN5AqY9O20qs8xq_FiyVBbsKdf5sf7EmRYBwmAcghlDKJf83WEkDyruMv2BpYVmyTrpsOkmd-sGKJ_HWB6T1NITnv31xujiMTlePA168HsjW3tTWB-s3l1mrK2g2JiLzxAGhXg0Lyp61JpAx6eXm2kqve6RaqdWQ3oVBzFVDriAGJeSZXoYwxMqwAw6XoewPAN4hN4-9BECKEdASqkkHfmqrPvjDHDpK_bwvRUhPFUtL-X6OVW5KgYRNBMjEp9z5tpQq5OuTzOGKuykribsPdx_qEV9Nn-CFc5YmfFKD8rEbQJazHUTagHKBJRUSTlXpSKlKVahBr3jfX--zmMFWJdxj5BGBmb2W1m0eFKo8XtRaNFqkVacTFGosXe0W4ywgwdOINqeW3I6ON6Ze9f6AgXeYkv2xM0SVtCw-W09to3HuzevguNnrORLp5oCufsj0xlpDx21f21QEernZbAkhoWHYLhzaEZI74Gh36x_37UtdmrkdCkNxpOCOhaGYrbn36TgO8Ldn_HsPQHQCTKD-4mWPOOKtOzn67uQe98x0KSulHd4qQkhXlsYEBbBx0s10IkP2DA_lK2JWcUWpFB6sPv1YJBNC3k31z_uk_m7KMuQ9cWBK4EJirb92tHVjGuKZNmQlK3IKsTtiBpxGwXds7BwcxXSRLJM6k6xEAhF7OFjK1yiFzJpQorZPZVo9uevsbCfScp9cPWfy4U4yrnzg2bneiqOkdtXbyNkWaXjlQ0Enwo1mhk6Jop3SOGLjPOadDpjapg9LYd9z6Jpx1EPNXCx-Z-lLNWBRrp2ICB6om8Y1fntT7fpp5dhHxJ51QCikHzRi_jY9_tAOLzM5rnScORH_0ukxdh6rXB9M0UX7PGCYHw7D6rqRo1IRO7bSFCBXE1474Cx110dOqok3iHyH-bEwVTr-BWeALoAwvv34vfNBu1RKH3K4p1d6EcX3BzZT-lEePic4hEj1V3LA1plgFYcpkWimTyjQeootzpj41JJ2s90uvVu5vIS_sLH2q7eb49xrcv-jRpR_btNgb_rGinyGfTOi-sDSC0H9K16aUdnQQDr-buMm7f9r0OvBHwC4N2D5c9iguEg==","summary":[{"type":"summary_text","text":"**Calculating multiplication**\n\nI'm computing 7 times 191. To simplify, I think of it as 7 times (200 minus 9). That gives me 1400 minus 63, which equals 1337. So the result is 1337! It might be nice to illustrate the quick multiplication, but for now, I'll keep it straightforward and provide the answer succinctly: the final answer is 1337."}]},{"id":"msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_retention":"in_memory","reasoning":{"context":"current_turn","effort":"low","mode":"standard","summary":"detailed"},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"auto","usage":{"input_tokens":14,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":51,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":65},"user":null,"metadata":{}},"sequence_number":87} + +--- +when: + path: /v1/responses + method: POST + json_body_str: >- + { + "model": "gpt-4o", + "input": [ + { + "type": "message", + "role": "system", + "content": [] + }, + { + "type": "message", + "role": "user", + "content": "What is 7 * 191?" + }, + { + "type": "reasoning", + "id": "rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef", + "summary": [ + { + "type": "summary_text", + "text": "**Calculating multiplication**\n\nI'm computing 7 times 191. To simplify, I think of it as 7 times (200 minus 9). That gives me 1400 minus 63, which equals 1337. So the result is 1337! It might be nice to illustrate the quick multiplication, but for now, I'll keep it straightforward and provide the answer succinctly: the final answer is 1337." + } + ], + "encrypted_content": "gAAAAABqU8qmH1D8RKxqAQ-pimWzfAWXQkcEPnhs3vTH8POrxRHAKBD-oHjN5C_TDv5TYFEHO_WTk7XKt0xHVAuTtfHawxtgcl3WJOSPv-B-ePZ9jnn1uqAjg_wONkC0uoohN1kZkT3DXoj06JgMruyx9TpuMT_vkIdvpKwlQiRxbgQjXNgisnD4odVI54PYG6HdbvRXjUk3slcWuXcqkOiQlhJ9wkqpOOGzsRHDnwUB1tTB9v3Zifu4sCaIdXsrhV8AUpXZmj26nvpnuz5jCiS5wYs46bF7RY6BJpcwAPUWf6MA0J7Am_bww1LokPcXnXZDG7jyKogyDNPDNPjd3nsup7SN3YnXn-3oUMfIaI3pXrck_K3bRdrix5NdFCL_nabUQPg9ULHEq7WsKOPad0hdZyhqLeLvsMdXwqp7wM8IToPh6NQamVq9ChBRAdTHw6jbmIvtISsxjOiZ1Ph_Ys4x1PZjlv8MrTXtNf_srfEt-C_KeX9M1VXmVq2ba5-mjNbOSrcEWAp2zy-zyMi3pnavicIVDRSU5yVbZzhvBtXhz8udW8iXPTl6Zn1juakdqiwtdBl4BEaostbsyx_9AzaDyzG6O4RCWmWH9VP3iepvSJKOu1g_XehiiXsGzvWWO01qlkPEIqGo_XLmdU4Rg0x61-N7QaFvy11Xtww4cBYAaBlogs1BZekAP6_-gf6skikOvO8w_XkeH_VRaGCvEjfA2NtLFTYss_OlLoKR2Q6y-Q91b4hU9nJMYyiKHdq9C8Umn0eKTps4vhqimISyPp2wMMOXIBCH-CK8tHlJ6LeWr4FIMdRNm-ObRvLSwY2vtSbph4iItR2dTmcr5OomX_2jZyy6W8AOACjoBxAzoOZHqTPLBy-k01XKjf-Z5rCwB5uERSYEQVVxxckPTFztOxQe6V1eF1uJj4OCv1oytZNxrkKi7ssxGKUqW-VZyZ9R6gYBJTv_2a4845R-fL3dXCJq_LCq-HXK1b8XfKZt0-eWZuX8xcogajU3zzVefJy5Tot-IJCpnroXrERXnZ2MnZBkZApTceKxp-Q4exSxB4hsSOEIK-zeRGMs_6JF-dHVZ3WJqQkGxAxn3Aol7O2d71kRk-QP6xt3jw==" + }, + { + "type": "message", + "content": [ + { + "type": "output_text", + "text": "1337", + "annotations": [] + } + ], + "id": "msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8", + "role": "assistant", + "status": "completed" + }, + { + "type": "message", + "role": "user", + "content": "Repeat your previous answer." + } + ], + "include": null, + "instructions": null, + "max_output_tokens": null, + "metadata": null, + "parallel_tool_calls": null, + "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", + "reasoning": null, + "service_tier": null, + "store": false, + "stream": true, + "temperature": null, + "text": null, + "tool_choice": "auto", + "tools": [], + "top_p": null, + "truncation": "auto", + "user": null + } +then: + status: 200 + header: + - name: content-type + value: text/event-stream; charset=utf-8 + body: |+ + event: response.created + data: {"type":"response.created","response":{"id":"resp_01698470604a0ea3016a53caa6832081979dcbb57fdfc7f4b5","object":"response","created_at":1783876262,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + event: response.in_progress + data: {"type":"response.in_progress","response":{"id":"resp_01698470604a0ea3016a53caa6832081979dcbb57fdfc7f4b5","object":"response","created_at":1783876262,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"auto","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + event: response.output_item.added + data: {"type":"response.output_item.added","item":{"id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + event: response.content_part.added + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"133","item_id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","logprobs":[],"obfuscation":"veP0aJ4yN7LPp","output_index":0,"sequence_number":4} + + event: response.output_text.delta + data: {"type":"response.output_text.delta","content_index":0,"delta":"7","item_id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","logprobs":[],"obfuscation":"nNDuhGHgn5IV6L3","output_index":0,"sequence_number":5} + + event: response.output_text.done + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","logprobs":[],"output_index":0,"sequence_number":6,"text":"1337"} + + event: response.content_part.done + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"},"sequence_number":7} + + event: response.output_item.done + data: {"type":"response.output_item.done","item":{"id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"}],"role":"assistant"},"output_index":0,"sequence_number":8} + + event: response.completed + data: {"type":"response.completed","response":{"id":"resp_01698470604a0ea3016a53caa6832081979dcbb57fdfc7f4b5","object":"response","created_at":1783876262,"status":"completed","background":false,"completed_at":1783876263,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[{"id":"msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"1337"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":"jp:conversation:1577836800000000","prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"auto","usage":{"input_tokens":30,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":3,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":33},"user":null,"metadata":{}},"sequence_number":9} + diff --git a/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model__conversation_stream.snap b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model__conversation_stream.snap new file mode 100644 index 00000000..3fabe9b6 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model__conversation_stream.snap @@ -0,0 +1,269 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +{ + "base_config": { + "inherit": false, + "config_load_paths": [], + "extends": [ + "config.d/**/*" + ], + "assistant": { + "system_prompt": "You are a helpful assistant.", + "system_prompt_sections": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "instructions": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "tool_choice": "auto", + "model": { + "id": { + "provider": "openai", + "name": "test" + }, + "parameters": { + "reasoning": "off", + "stop_words": [], + "other": {} + } + }, + "request": { + "max_retries": 5, + "base_backoff_ms": 1000, + "max_backoff_secs": 60, + "stream_idle_timeout_secs": 60, + "cache": true + } + }, + "conversation": { + "title": { + "generate": { + "auto": false + }, + "from_heading": true + }, + "tools": { + "*": { + "run": "ask", + "result": "unattended", + "cancellation_response": "This tool request was intentionally rejected by the user. Please evaluate and either ask the user why it was rejected, or infer the reason by looking at the historical messages in the conversation.", + "style": { + "hidden": false, + "inline_results": { + "truncate": { + "lines": 10 + } + }, + "results_file_link": "full", + "parameters": "json" + } + } + }, + "compaction": { + "rules": { + "value": [ + { + "keep_first": "1", + "keep_last": "1", + "reasoning": "strip", + "tool_calls": "strip" + } + ], + "strategy": "replace", + "discard_when_merged": false + } + }, + "attachments": { + "value": [], + "strategy": "replace", + "discard_when_merged": false + }, + "start_local": false + }, + "style": { + "code": { + "color": true, + "line_numbers": false, + "file_link": "osc8", + "copy_link": "off" + }, + "markdown": { + "wrap_width": 80, + "table_max_column_width": 40, + "theme": "gruvbox-dark", + "hr_style": "line" + }, + "reasoning": { + "display": "full", + "background": 236, + "extend_across_tool_calls": true + }, + "streaming": { + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "lock_wait": { + "show": true, + "delay_secs": 1, + "interval_ms": 100, + "timeout_secs": 10 + }, + "tool_call": { + "show": true, + "progress": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + }, + "preparing": { + "show": true, + "delay_secs": 3, + "interval_ms": 100 + } + }, + "typewriter": { + "text_delay": "3ms", + "code_delay": "500us", + "max_latency": "0s" + } + }, + "interrupt": { + "escalation_cooldown_secs": 2, + "streaming": { + "action": "prompt", + "compose_in_editor": false + }, + "tool_call": { + "action": "prompt", + "compose_in_editor": false + } + }, + "editor": { + "envs": [ + "JP_EDITOR", + "VISUAL", + "EDITOR" + ], + "inline": { + "edit_mode": "emacs" + } + }, + "providers": { + "llm": { + "anthropic": { + "api_key_env": "ANTHROPIC_API_KEY", + "base_url": "https://api.anthropic.com", + "chain_on_max_tokens": true, + "beta_headers": [] + }, + "cerebras": { + "api_key_env": "CEREBRAS_API_KEY", + "base_url": "https://api.cerebras.ai" + }, + "deepseek": { + "api_key_env": "DEEPSEEK_API_KEY", + "base_url": "https://api.deepseek.com" + }, + "google": { + "api_key_env": "GEMINI_API_KEY", + "base_url": "https://generativelanguage.googleapis.com/v1beta" + }, + "llamacpp": { + "base_url": "http://127.0.0.1:8080" + }, + "ollama": { + "base_url": "http://localhost:11434" + }, + "openai": { + "api_key_env": "OPENAI_API_KEY", + "base_url": "https://api.openai.com", + "base_url_env": "OPENAI_BASE_URL" + }, + "openrouter": { + "api_key_env": "OPENROUTER_API_KEY", + "app_name": "JP", + "base_url": "https://openrouter.ai" + } + } + }, + "plugins": { + "auto_install": true, + "shutdown_timeout_secs": 5 + } + }, + "events": [ + { + "type": "config_delta", + "timestamp": "2020-01-01 00:00:00.0", + "delta": { + "assistant": { + "model": { + "parameters": { + "reasoning": { + "effort": "low", + "exclude": false + } + } + } + } + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_request", + "content": "What is 7 * 191?" + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "reasoning": "**Calculating multiplication**\n\nI'm computing 7 times 191. To simplify, I think of it as 7 times (200 minus 9). That gives me 1400 minus 63, which equals 1337. So the result is 1337! It might be nice to illustrate the quick multiplication, but for now, I'll keep it straightforward and provide the answer succinctly: the final answer is 1337.", + "metadata": { + "openai_item_id": "cnNfMDE2OTg0NzA2MDRhMGVhMzAxNmE1M2NhYTNmN2JjODE5N2IyOWUyZjQ2Y2NiMWNlZWY=", + "openai_encrypted_content": "Z0FBQUFBQnFVOHFtSDFEOFJLeHFBUS1waW1XemZBV1hRa2NFUG5oczN2VEg4UE9yeFJIQUtCRC1vSGpONUNfVER2NVRZRkVIT19XVGs3WEt0MHhIVkF1VHRmSGF3eHRnY2wzV0pPU1B2LUItZVBaOWpubjF1cUFqZ193T05rQzB1b29oTjFrWmtUM0RYb2owNkpnTXJ1eXg5VHB1TVRfdmtJZHZwS3dsUWlSeGJnUWpYTmdpc25ENG9kVkk1NFBZRzZIZGJ2UlhqVWszc2xjV3VYY3FrT2lRbGhKOXdrcXBPT0d6c1JIRG53VUIxdFRCOXYzWmlmdTRzQ2FJZFhzcmhWOEFVcFhabWoyNm52cG51ejVqQ2lTNXdZczQ2YkY3Ulk2QkpwY3dBUFVXZjZNQTBKN0FtX2J3dzFMb2tQY1huWFpERzdqeUtvZ3lETlBETlBqZDNuc3VwN1NOM1luWG4tM29VTWZJYUkzcFhyY2tfSzNiUmRyaXg1TmRGQ0xfbmFiVVFQZzlVTEhFcTdXc0tPUGFkMGhkWnlocUxlTHZzTWRYd3FwN3dNOElUb1BoNk5RYW1WcTlDaEJSQWRUSHc2amJtSXZ0SVNzeGpPaVoxUGhfWXM0eDFQWmpsdjhNclRYdE5mX3NyZkV0LUNfS2VYOU0xVlhtVnEyYmE1LW1qTmJPU3JjRVdBcDJ6eS16eU1pM3BuYXZpY0lWRFJTVTV5VmJaemh2QnRYaHo4dWRXOGlYUFRsNlpuMWp1YWtkcWl3dGRCbDRCRWFvc3Ric3l4XzlBemFEeXpHNk80UkNXbVdIOVZQM2llcHZTSktPdTFnX1hlaGlpWHNHenZXV08wMXFsa1BFSXFHb19YTG1kVTRSZzB4NjEtTjdRYUZ2eTExWHR3dzRjQllBYUJsb2dzMUJaZWtBUDZfLWdmNnNraWtPdk84d19Ya2VIX1ZSYUdDdkVqZkEyTnRMRlRZc3NfT2xMb0tSMlE2eS1ROTFiNGhVOW5KTVl5aUtIZHE5QzhVbW4wZUtUcHM0dmhxaW1JU3lQcDJ3TU1PWElCQ0gtQ0s4dEhsSjZMZVdyNEZJTWRSTm0tT2JSdkxTd1kydnRTYnBoNGlJdFIyZFRtY3I1T29tWF8yalp5eTZXOEFPQUNqb0J4QXpvT1pIcVRQTEJ5LWswMVhLamYtWjVyQ3dCNXVFUlNZRVFWVnh4Y2tQVEZ6dE94UWU2VjFlRjF1Smo0T0N2MW95dFpOeHJrS2k3c3N4R0tVcVctVlp5WjlSNmdZQkpUdl8yYTQ4NDVSLWZMM2RYQ0pxX0xDcS1IWEsxYjhYZktadDAtZVdadVg4eGNvZ2FqVTN6elZlZkp5NVRvdC1JSkNwbnJvWHJFUlhuWjJNblpCa1pBcFRjZUt4cC1RNGV4U3hCNGhzU09FSUstemVSR01zXzZKRi1kSFZaM1dKcVFrR3hBeG4zQW9sN08yZDcxa1JrLVFQNnh0M2p3PT0=" + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "message": "1337", + "metadata": { + "openai_item_id": "bXNnXzAxNjk4NDcwNjA0YTBlYTMwMTZhNTNjYWE2MjUxYzgxOTc5NTZhNmMyYTU0YTc4ZGU4" + } + }, + { + "type": "config_delta", + "timestamp": "2020-01-01 00:00:00.0", + "delta": { + "assistant": { + "model": { + "parameters": { + "reasoning": "off" + } + } + } + } + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_request", + "content": "Repeat your previous answer." + }, + { + "timestamp": "2020-01-01 00:00:00.0", + "type": "chat_response", + "message": "1337", + "metadata": { + "openai_item_id": "bXNnXzAxNjk4NDcwNjA0YTBlYTMwMTZhNTNjYWE3YmQzYzgxOTc4MTZjZjUyODExZTQ3MGQ2" + } + } + ] +} diff --git a/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model__raw_events.snap b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model__raw_events.snap new file mode 100644 index 00000000..bf095603 --- /dev/null +++ b/crates/jp_llm/tests/fixtures/openai/test_reasoning_history_replayed_to_reasoning_unsupported_model__raw_events.snap @@ -0,0 +1,595 @@ +--- +source: crates/jp_test/src/mock.rs +expression: v +--- +[ + [ + Part { + index: 0, + part: Reasoning( + "", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "**Calculating multiplication**\n\nI'm", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " computing", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " times", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 191", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " To", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " simplify", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ",", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " I", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " think", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " of", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " it", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " as", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " times", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " (", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "200", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " minus", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 9", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ").", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " That", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " gives", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " me", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 140", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "0", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " minus", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 63", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ",", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " which", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " equals", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 133", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " So", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " result", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " is", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 133", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "!", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " It", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " might", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " be", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " nice", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " to", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " illustrate", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " quick", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " multiplication", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ",", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " but", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " for", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " now", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ",", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " I'll", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " keep", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " it", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " straightforward", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " and", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " provide", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " answer", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " succinct", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "ly", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ":", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " the", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " final", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " answer", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " is", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + " 133", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + "7", + ), + metadata: {}, + }, + Part { + index: 0, + part: Reasoning( + ".", + ), + metadata: {}, + }, + Flush { + index: 0, + metadata: { + "openai_item_id": String("rs_01698470604a0ea3016a53caa3f7bc8197b29e2f46ccb1ceef"), + "openai_encrypted_content": String("gAAAAABqU8qmH1D8RKxqAQ-pimWzfAWXQkcEPnhs3vTH8POrxRHAKBD-oHjN5C_TDv5TYFEHO_WTk7XKt0xHVAuTtfHawxtgcl3WJOSPv-B-ePZ9jnn1uqAjg_wONkC0uoohN1kZkT3DXoj06JgMruyx9TpuMT_vkIdvpKwlQiRxbgQjXNgisnD4odVI54PYG6HdbvRXjUk3slcWuXcqkOiQlhJ9wkqpOOGzsRHDnwUB1tTB9v3Zifu4sCaIdXsrhV8AUpXZmj26nvpnuz5jCiS5wYs46bF7RY6BJpcwAPUWf6MA0J7Am_bww1LokPcXnXZDG7jyKogyDNPDNPjd3nsup7SN3YnXn-3oUMfIaI3pXrck_K3bRdrix5NdFCL_nabUQPg9ULHEq7WsKOPad0hdZyhqLeLvsMdXwqp7wM8IToPh6NQamVq9ChBRAdTHw6jbmIvtISsxjOiZ1Ph_Ys4x1PZjlv8MrTXtNf_srfEt-C_KeX9M1VXmVq2ba5-mjNbOSrcEWAp2zy-zyMi3pnavicIVDRSU5yVbZzhvBtXhz8udW8iXPTl6Zn1juakdqiwtdBl4BEaostbsyx_9AzaDyzG6O4RCWmWH9VP3iepvSJKOu1g_XehiiXsGzvWWO01qlkPEIqGo_XLmdU4Rg0x61-N7QaFvy11Xtww4cBYAaBlogs1BZekAP6_-gf6skikOvO8w_XkeH_VRaGCvEjfA2NtLFTYss_OlLoKR2Q6y-Q91b4hU9nJMYyiKHdq9C8Umn0eKTps4vhqimISyPp2wMMOXIBCH-CK8tHlJ6LeWr4FIMdRNm-ObRvLSwY2vtSbph4iItR2dTmcr5OomX_2jZyy6W8AOACjoBxAzoOZHqTPLBy-k01XKjf-Z5rCwB5uERSYEQVVxxckPTFztOxQe6V1eF1uJj4OCv1oytZNxrkKi7ssxGKUqW-VZyZ9R6gYBJTv_2a4845R-fL3dXCJq_LCq-HXK1b8XfKZt0-eWZuX8xcogajU3zzVefJy5Tot-IJCpnroXrERXnZ2MnZBkZApTceKxp-Q4exSxB4hsSOEIK-zeRGMs_6JF-dHVZ3WJqQkGxAxn3Aol7O2d71kRk-QP6xt3jw=="), + }, + }, + Part { + index: 1, + part: Message( + "", + ), + metadata: {}, + }, + Part { + index: 1, + part: Message( + "133", + ), + metadata: {}, + }, + Part { + index: 1, + part: Message( + "7", + ), + metadata: {}, + }, + Flush { + index: 1, + metadata: { + "openai_item_id": String("msg_01698470604a0ea3016a53caa6251c8197956a6c2a54a78de8"), + }, + }, + Finished( + Completed, + ), + ], + [ + Part { + index: 0, + part: Message( + "", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + "133", + ), + metadata: {}, + }, + Part { + index: 0, + part: Message( + "7", + ), + metadata: {}, + }, + Flush { + index: 0, + metadata: { + "openai_item_id": String("msg_01698470604a0ea3016a53caa7bd3c8197816cf52811e470d6"), + }, + }, + Finished( + Completed, + ), + ], +] diff --git a/crates/jp_llm/tests/fixtures/openai/test_structured_output.yml b/crates/jp_llm/tests/fixtures/openai/test_structured_output.yml index 347a56a2..4405e600 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_structured_output.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_structured_output.yml @@ -22,6 +22,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_tool_call_auto.yml b/crates/jp_llm/tests/fixtures/openai/test_tool_call_auto.yml index 665bb737..bd84674f 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_tool_call_auto.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_tool_call_auto.yml @@ -22,6 +22,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" @@ -150,6 +151,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_tool_call_function.yml b/crates/jp_llm/tests/fixtures/openai/test_tool_call_function.yml index 86bf4a08..0ebbcad7 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_tool_call_function.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_tool_call_function.yml @@ -22,6 +22,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" @@ -177,6 +178,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_tool_call_reasoning.yml b/crates/jp_llm/tests/fixtures/openai/test_tool_call_reasoning.yml index 649098c9..f76901db 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_tool_call_reasoning.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_tool_call_reasoning.yml @@ -24,6 +24,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "low", "summary": "auto" @@ -187,6 +188,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_no_reasoning.yml b/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_no_reasoning.yml index f418b6a0..896b93d9 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_no_reasoning.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_no_reasoning.yml @@ -22,6 +22,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" @@ -180,6 +181,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_reasoning.yml b/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_reasoning.yml index 241eed29..6fb4e727 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_reasoning.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_tool_call_required_reasoning.yml @@ -24,6 +24,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "low", "summary": "auto" @@ -481,6 +482,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" diff --git a/crates/jp_llm/tests/fixtures/openai/test_tool_call_stream.yml b/crates/jp_llm/tests/fixtures/openai/test_tool_call_stream.yml index ae49fe7b..ecea6688 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_tool_call_stream.yml +++ b/crates/jp_llm/tests/fixtures/openai/test_tool_call_stream.yml @@ -22,6 +22,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto" @@ -174,6 +175,7 @@ when: "metadata": null, "parallel_tool_calls": null, "previous_response_id": null, + "prompt_cache_key": "jp:conversation:1577836800000000", "reasoning": { "effort": "minimal", "summary": "auto"