Add a validation

This commit is contained in:
jif-oai
2025-12-01 10:50:16 +00:00
parent ca8f63f771
commit cac05eddc1
3 changed files with 84 additions and 16 deletions

View File

@@ -112,6 +112,8 @@ impl ModelClient {
/// For Chat providers, the underlying stream is optionally aggregated
/// based on the `show_raw_agent_reasoning` flag in the config.
pub async fn stream(&self, prompt: &PromptBuilder) -> Result<ResponseStream> {
self.validate_prompt_wire_api(prompt)?;
match self.provider.wire_api {
WireApi::Responses => self.stream_responses_api(prompt).await,
WireApi::Chat => {
@@ -132,6 +134,17 @@ impl ModelClient {
}
}
fn validate_prompt_wire_api(&self, prompt: &PromptBuilder) -> Result<()> {
if prompt.wire_api != self.provider.wire_api {
return Err(CodexErr::UnsupportedOperation(format!(
"prompt configured for {:?} wire API but provider {} expects {:?}",
prompt.wire_api, self.provider.name, self.provider.wire_api
)));
}
Ok(())
}
/// Streams a turn via the OpenAI Chat Completions API.
///
/// This path is only used when the provider is configured with

View File

@@ -5,6 +5,8 @@ use crate::model_family::ModelFamily;
use crate::model_provider_info::WireApi;
use crate::tools::spec::create_tools_json_for_chat_completions_api;
use crate::tools::spec::create_tools_json_for_responses_api;
use codex_api::CompactionInput;
use codex_api::Prompt;
pub use codex_api::common::ResponseEvent;
use codex_apply_patch::APPLY_PATCH_TOOL_INSTRUCTIONS;
use codex_protocol::models::ResponseItem;
@@ -18,7 +20,6 @@ use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
use tokio::sync::mpsc;
use codex_api::{CompactionInput, Prompt};
/// Review thread system prompt. Edit `core/src/review_prompt.md` to customize.
pub const REVIEW_PROMPT: &str = include_str!("../review_prompt.md");

View File

@@ -10,6 +10,7 @@ use codex_core::ModelProviderInfo;
use codex_core::PromptBuilder;
use codex_core::ResponseItem;
use codex_core::WireApi;
use codex_core::error::CodexErr;
use codex_otel::otel_event_manager::OtelEventManager;
use codex_protocol::ConversationId;
use codex_protocol::models::ReasoningItemContent;
@@ -41,21 +42,7 @@ async fn run_request(input: Vec<ResponseItem>) -> Value {
.mount(&server)
.await;
let provider = ModelProviderInfo {
name: "mock".into(),
base_url: Some(format!("{}/v1", server.uri())),
env_key: None,
env_key_instructions: None,
experimental_bearer_token: None,
wire_api: WireApi::Chat,
query_params: None,
http_headers: None,
env_http_headers: None,
request_max_retries: Some(0),
stream_max_retries: Some(0),
stream_idle_timeout_ms: Some(5_000),
requires_openai_auth: false,
};
let provider = chat_provider(format!("{}/v1", server.uri()));
let codex_home = match TempDir::new() {
Ok(dir) => dir,
@@ -184,6 +171,24 @@ fn first_assistant(messages: &[Value]) -> &Value {
}
}
fn chat_provider(base_url: String) -> ModelProviderInfo {
ModelProviderInfo {
name: "mock".into(),
base_url: Some(base_url),
env_key: None,
env_key_instructions: None,
experimental_bearer_token: None,
wire_api: WireApi::Chat,
query_params: None,
http_headers: None,
env_http_headers: None,
request_max_retries: Some(0),
stream_max_retries: Some(0),
stream_idle_timeout_ms: Some(5_000),
requires_openai_auth: false,
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn omits_reasoning_when_none_present() {
skip_if_no_network!();
@@ -316,3 +321,52 @@ async fn suppresses_duplicate_assistant_messages() {
Value::String("dup".into())
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn errors_on_mismatched_prompt_wire_api() {
let provider = chat_provider("https://example.com/v1".into());
let codex_home = match TempDir::new() {
Ok(dir) => dir,
Err(e) => panic!("failed to create TempDir: {e}"),
};
let mut config = load_default_config_for_test(&codex_home);
config.model_provider_id = provider.name.clone();
config.model_provider = provider.clone();
config.show_raw_agent_reasoning = true;
let effort = config.model_reasoning_effort;
let summary = config.model_reasoning_summary;
let config = Arc::new(config);
let conversation_id = ConversationId::new();
let otel_event_manager = OtelEventManager::new(
conversation_id,
config.model.as_str(),
config.model_family.slug.as_str(),
None,
Some("test@test.com".to_string()),
Some(AuthMode::ChatGPT),
false,
"test".to_string(),
);
let client = ModelClient::new(
Arc::clone(&config),
None,
otel_event_manager,
provider,
effort,
summary,
conversation_id,
codex_protocol::protocol::SessionSource::Exec,
);
let prompt = PromptBuilder::new().with_input(vec![user_message("u1")]);
let err = match client.stream(&prompt).await {
Ok(_) => panic!("wire API mismatch should error before sending request"),
Err(e) => e,
};
assert!(matches!(err, CodexErr::UnsupportedOperation(_)));
}