mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] allow image generation with provider auth (#29513)
## Summary - allow the native Responses API `image_generation` tool when the active provider carries CCA's non-empty `x-openai-actor-authorization` header - preserve the Codex-managed ChatGPT auth path, scoped to providers that actually require OpenAI auth - keep generic custom providers excluded, including when unrelated ChatGPT credentials are cached - retain the existing feature, provider-capability, and image-input-modality gates ## Why CCA authenticates its inference requests through the active provider's `x-openai-actor-authorization` and `ChatGPT-Account-ID` headers, so it does not have a Codex-managed login session. The previous gate therefore hid the native hosted image-generation tool despite an authenticated codex-backend path. This change is intentionally limited to the native hosted tool. It adds no extension, MCP, plugin-service, session-source, token plumbing, or new provider configuration surface. ## Tests - `cargo test -p codex-core hosted_tools_follow_provider_auth_model_and_config_gates` - `cargo fmt --all -- --check` - `git diff --check origin/main`
This commit is contained in:
committed by
GitHub
Unverified
parent
18fe1d9fe3
commit
c660e2b644
@@ -96,6 +96,7 @@ use tracing::warn;
|
||||
const MULTI_AGENT_V2_NAMESPACE_DESCRIPTION: &str = "Tools for spawning and managing sub-agents.";
|
||||
const IMAGE_GEN_NAMESPACE: &str = "image_gen";
|
||||
const IMAGEGEN_TOOL_NAME: &str = "imagegen";
|
||||
const ACTOR_AUTHORIZATION_HEADER: &str = "x-openai-actor-authorization";
|
||||
|
||||
type PlannedRuntime = Arc<dyn CoreToolRuntime>;
|
||||
|
||||
@@ -383,10 +384,12 @@ fn image_generation_tool_enabled(turn_context: &TurnContext) -> bool {
|
||||
}
|
||||
|
||||
fn image_generation_runtime_enabled(turn_context: &TurnContext) -> bool {
|
||||
turn_context
|
||||
.auth_manager
|
||||
.as_deref()
|
||||
.is_some_and(AuthManager::current_auth_uses_codex_backend)
|
||||
(provider_uses_actor_authorization(turn_context)
|
||||
|| (turn_context.provider.info().requires_openai_auth
|
||||
&& turn_context
|
||||
.auth_manager
|
||||
.as_deref()
|
||||
.is_some_and(AuthManager::current_auth_uses_codex_backend)))
|
||||
&& turn_context.provider.capabilities().image_generation
|
||||
&& turn_context
|
||||
.model_info
|
||||
@@ -394,6 +397,16 @@ fn image_generation_runtime_enabled(turn_context: &TurnContext) -> bool {
|
||||
.contains(&InputModality::Image)
|
||||
}
|
||||
|
||||
fn provider_uses_actor_authorization(turn_context: &TurnContext) -> bool {
|
||||
let provider_info = turn_context.provider.info();
|
||||
!provider_info.requires_openai_auth
|
||||
&& provider_info.http_headers.as_ref().is_some_and(|headers| {
|
||||
headers.iter().any(|(name, value)| {
|
||||
name.eq_ignore_ascii_case(ACTOR_AUTHORIZATION_HEADER) && !value.trim().is_empty()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn standalone_image_generation_model_visible(turn_context: &TurnContext) -> bool {
|
||||
if !image_generation_runtime_enabled(turn_context) || !namespace_tools_enabled(turn_context) {
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_features::Feature;
|
||||
@@ -270,6 +271,29 @@ fn use_bedrock_provider(turn: &mut TurnContext) {
|
||||
turn.provider = create_model_provider(provider_info, turn.auth_manager.clone());
|
||||
}
|
||||
|
||||
fn use_provider_auth(
|
||||
turn: &mut TurnContext,
|
||||
requires_openai_auth: bool,
|
||||
actor_header: Option<(&str, &str)>,
|
||||
) {
|
||||
let mut provider_info = turn.config.model_provider.clone();
|
||||
provider_info.requires_openai_auth = requires_openai_auth;
|
||||
provider_info.http_headers = actor_header.map(|(name, value)| {
|
||||
HashMap::from([
|
||||
(name.to_string(), value.to_string()),
|
||||
(
|
||||
"ChatGPT-Account-ID".to_string(),
|
||||
"test-account-id".to_string(),
|
||||
),
|
||||
])
|
||||
});
|
||||
turn.auth_manager = None;
|
||||
update_config(turn, |config| {
|
||||
config.model_provider = provider_info.clone();
|
||||
});
|
||||
turn.provider = create_model_provider(provider_info, /*auth_manager*/ None);
|
||||
}
|
||||
|
||||
struct WebRunExtensionTool;
|
||||
|
||||
impl ToolExecutor<ExtensionToolCall> for WebRunExtensionTool {
|
||||
@@ -1455,6 +1479,110 @@ async fn hosted_tools_follow_provider_auth_model_and_config_gates() {
|
||||
.await;
|
||||
api_key_auth.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let unrelated_chatgpt_auth = probe(|turn| {
|
||||
use_chatgpt_auth(turn);
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
let mut provider_info = turn.provider.info().clone();
|
||||
provider_info.requires_openai_auth = false;
|
||||
provider_info.http_headers = None;
|
||||
update_config(turn, |config| {
|
||||
config.model_provider = provider_info.clone();
|
||||
});
|
||||
turn.provider = create_model_provider(provider_info, turn.auth_manager.clone());
|
||||
turn.model_info.input_modalities = vec![InputModality::Image];
|
||||
})
|
||||
.await;
|
||||
unrelated_chatgpt_auth.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let provider_without_actor_auth = probe(|turn| {
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
use_provider_auth(
|
||||
turn, /*requires_openai_auth*/ false, /*actor_header*/ None,
|
||||
);
|
||||
turn.model_info.input_modalities = vec![InputModality::Image];
|
||||
})
|
||||
.await;
|
||||
provider_without_actor_auth.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let provider_authenticated = probe(|turn| {
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
use_provider_auth(
|
||||
turn,
|
||||
/*requires_openai_auth*/ false,
|
||||
Some(("X-OpenAI-Actor-Authorization", "test-actor-authorization")),
|
||||
);
|
||||
turn.model_info.input_modalities = vec![InputModality::Image];
|
||||
})
|
||||
.await;
|
||||
provider_authenticated.assert_visible_contains(&["image_generation"]);
|
||||
|
||||
let empty_actor_auth = probe(|turn| {
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
use_provider_auth(
|
||||
turn,
|
||||
/*requires_openai_auth*/ false,
|
||||
Some(("x-openai-actor-authorization", " ")),
|
||||
);
|
||||
turn.model_info.input_modalities = vec![InputModality::Image];
|
||||
})
|
||||
.await;
|
||||
empty_actor_auth.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let feature_disabled = probe(|turn| {
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ false);
|
||||
use_provider_auth(
|
||||
turn,
|
||||
/*requires_openai_auth*/ false,
|
||||
Some(("x-openai-actor-authorization", "test-actor-authorization")),
|
||||
);
|
||||
turn.model_info.input_modalities = vec![InputModality::Image];
|
||||
})
|
||||
.await;
|
||||
feature_disabled.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let text_only_model = probe(|turn| {
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
use_provider_auth(
|
||||
turn,
|
||||
/*requires_openai_auth*/ false,
|
||||
Some(("x-openai-actor-authorization", "test-actor-authorization")),
|
||||
);
|
||||
turn.model_info.input_modalities = vec![];
|
||||
})
|
||||
.await;
|
||||
text_only_model.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let unsupported_image_generation_provider = probe(|turn| {
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
use_bedrock_provider(turn);
|
||||
let mut provider_info = turn.provider.info().clone();
|
||||
provider_info.requires_openai_auth = false;
|
||||
provider_info.http_headers = Some(HashMap::from([(
|
||||
"x-openai-actor-authorization".to_string(),
|
||||
"test-actor-authorization".to_string(),
|
||||
)]));
|
||||
turn.auth_manager = None;
|
||||
update_config(turn, |config| {
|
||||
config.model_provider = provider_info.clone();
|
||||
});
|
||||
turn.provider = create_model_provider(provider_info, /*auth_manager*/ None);
|
||||
turn.model_info.input_modalities = vec![InputModality::Image];
|
||||
})
|
||||
.await;
|
||||
unsupported_image_generation_provider.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let codex_managed_auth_provider = probe(|turn| {
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
use_provider_auth(
|
||||
turn,
|
||||
/*requires_openai_auth*/ true,
|
||||
Some(("x-openai-actor-authorization", "test-actor-authorization")),
|
||||
);
|
||||
turn.model_info.input_modalities = vec![InputModality::Image];
|
||||
})
|
||||
.await;
|
||||
codex_managed_auth_provider.assert_visible_lacks(&["image_generation"]);
|
||||
|
||||
let image_generation = probe(|turn| {
|
||||
use_chatgpt_auth(turn);
|
||||
set_feature(turn, Feature::ImageGeneration, /*enabled*/ true);
|
||||
|
||||
Reference in New Issue
Block a user