-
Notifications
You must be signed in to change notification settings - Fork 15k
Add feature-gated standalone image generation extension #24723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,8 @@ use std::sync::Arc; | |
| use tracing::warn; | ||
|
|
||
| const MULTI_AGENT_V2_NAMESPACE_DESCRIPTION: &str = "Tools for spawning and managing sub-agents."; | ||
| const IMAGE_GEN_NAMESPACE: &str = "image_gen"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the plan to drop this? (and drop the old tool)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. once the standalone path is ready to replace the hosted tool, I plan to remove the hosted tool and this replacement detection together |
||
| const IMAGEGEN_TOOL_NAME: &str = "imagegen"; | ||
|
|
||
| type PlannedRuntime = Arc<dyn CoreToolRuntime>; | ||
|
|
||
|
|
@@ -257,7 +259,9 @@ fn hosted_model_tool_specs(context: &CoreToolPlanContext<'_>) -> Vec<ToolSpec> { | |
| }) { | ||
| specs.push(web_search_tool); | ||
| } | ||
| if image_generation_tool_enabled(turn_context) { | ||
| if image_generation_tool_enabled(turn_context) | ||
| && !standalone_image_generation_available(turn_context, context.extension_tool_executors) | ||
| { | ||
| specs.push(create_image_generation_tool("png")); | ||
| } | ||
| specs | ||
|
|
@@ -316,21 +320,41 @@ fn agent_jobs_worker_tools_enabled(turn_context: &TurnContext) -> bool { | |
| } | ||
|
|
||
| fn image_generation_tool_enabled(turn_context: &TurnContext) -> bool { | ||
| image_generation_runtime_enabled(turn_context) | ||
| && turn_context | ||
| .features | ||
| .get() | ||
| .enabled(Feature::ImageGeneration) | ||
| } | ||
|
|
||
| fn image_generation_runtime_enabled(turn_context: &TurnContext) -> bool { | ||
| turn_context | ||
| .auth_manager | ||
| .as_deref() | ||
| .is_some_and(AuthManager::current_auth_uses_codex_backend) | ||
| && turn_context.provider.capabilities().image_generation | ||
| && turn_context | ||
| .features | ||
| .get() | ||
| .enabled(Feature::ImageGeneration) | ||
| && turn_context | ||
| .model_info | ||
| .input_modalities | ||
| .contains(&InputModality::Image) | ||
| } | ||
|
|
||
| fn standalone_image_generation_model_visible(turn_context: &TurnContext) -> bool { | ||
| image_generation_runtime_enabled(turn_context) | ||
| && turn_context.features.get().enabled(Feature::ImageGenExt) | ||
| && namespace_tools_enabled(turn_context) | ||
|
Comment on lines
+342
to
+345
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a user disables Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| fn standalone_image_generation_available( | ||
| turn_context: &TurnContext, | ||
| extension_tools: &[Arc<dyn ToolExecutor<ExtensionToolCall>>], | ||
| ) -> bool { | ||
| standalone_image_generation_model_visible(turn_context) | ||
| && extension_tools.iter().any(|executor| { | ||
| executor.tool_name() == ToolName::namespaced(IMAGE_GEN_NAMESPACE, IMAGEGEN_TOOL_NAME) | ||
| }) | ||
| } | ||
|
|
||
| fn wait_agent_timeout_options(turn_context: &TurnContext) -> WaitAgentTimeoutOptions { | ||
| if multi_agent_v2_enabled(turn_context) { | ||
| return WaitAgentTimeoutOptions { | ||
|
|
@@ -839,6 +863,11 @@ fn append_extension_tool_executors( | |
|
|
||
| for executor in executors.iter().cloned() { | ||
| let tool_name = executor.tool_name(); | ||
| if tool_name == ToolName::namespaced(IMAGE_GEN_NAMESPACE, IMAGEGEN_TOOL_NAME) | ||
| && !standalone_image_generation_model_visible(turn_context) | ||
| { | ||
| continue; | ||
| } | ||
| if !reserved_tool_names.insert(tool_name.clone()) { | ||
| warn!("Skipping extension tool `{tool_name}`: tool already registered"); | ||
| continue; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -960,6 +960,16 @@ async fn hosted_tools_follow_provider_auth_model_and_config_gates() { | |
| .await; | ||
| image_generation.assert_visible_contains(&["image_generation"]); | ||
|
|
||
| let extension_flag_without_imagegen_tool = probe(|turn| { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This only covers the fallback when imagegenext is on but the executor is absent. The replacement path itself is untested now
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: doing as follow-up / as the need comes to invest time in making sure we are at parity |
||
| use_chatgpt_auth(turn); | ||
| set_feature(turn, Feature::ImageGeneration, /*enabled*/ true); | ||
| set_feature(turn, Feature::ImageGenExt, /*enabled*/ true); | ||
| turn.model_info.input_modalities = vec![InputModality::Image]; | ||
| }) | ||
| .await; | ||
| extension_flag_without_imagegen_tool.assert_visible_contains(&["image_generation"]); | ||
| extension_flag_without_imagegen_tool.assert_visible_lacks(&["image_gen"]); | ||
|
|
||
| let live_web_search = probe(|turn| { | ||
| set_web_search_mode(turn, WebSearchMode::Live); | ||
| turn.model_info.web_search_tool_type = WebSearchToolType::TextAndImage; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| load("//:defs.bzl", "codex_rust_crate") | ||
|
|
||
| codex_rust_crate( | ||
| name = "image-generation", | ||
| crate_name = "codex_image_generation_extension", | ||
| compile_data = [ | ||
| "imagegen_description.md", | ||
| ], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| [package] | ||
| edition.workspace = true | ||
| license.workspace = true | ||
| name = "codex-image-generation-extension" | ||
| version.workspace = true | ||
|
|
||
| [lib] | ||
| name = "codex_image_generation_extension" | ||
| path = "src/lib.rs" | ||
| doctest = false | ||
|
|
||
| [lints] | ||
| workspace = true | ||
|
|
||
| [dependencies] | ||
| async-trait = { workspace = true } | ||
| base64 = { workspace = true } | ||
| codex-api = { workspace = true } | ||
| codex-core = { workspace = true } | ||
| codex-extension-api = { workspace = true } | ||
| codex-features = { workspace = true } | ||
| codex-login = { workspace = true } | ||
| codex-model-provider = { workspace = true } | ||
| codex-model-provider-info = { workspace = true } | ||
| codex-protocol = { workspace = true } | ||
| codex-tools = { workspace = true } | ||
| http = { workspace = true } | ||
| schemars = { workspace = true } | ||
| serde = { workspace = true, features = ["derive"] } | ||
| serde_json = { workspace = true } | ||
| tokio = { workspace = true, features = ["fs"] } | ||
| tracing = { workspace = true } | ||
|
|
||
| [dev-dependencies] | ||
| pretty_assertions = { workspace = true } | ||
| tempfile = { workspace = true } | ||
| tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| The `image_gen.imagegen` tool enables image generation from descriptions and editing of existing images based on specific instructions. Use it when: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. placeholder, subject to change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly not sure we need a doc file then... just drop it
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. placeholder as in we need to change the guidelines later depending on how we want the model to use this image gen tool, we should still give it guidelines on generate and edit etc bc the model isn't trained to do it |
||
|
|
||
| - The user requests an image based on a scene description, such as a diagram, portrait, comic, meme, or any other visual. | ||
| - The user wants to modify an attached or previously generated image with specific changes, including adding or removing elements, altering colors, improving quality/resolution, or transforming the style (e.g., cartoon, oil painting). | ||
|
|
||
| Guidelines: | ||
| - Set `action` to `generate` when the user asks for a brand new image. | ||
| - Set `action` to `edit` when the user asks to modify an existing image from the conversation history. | ||
| - Directly generate the image without reconfirmation or clarification. | ||
| - After each image generation, do not mention anything related to download. Do not summarize the image. Do not ask followup question. Do not say ANYTHING after you generate an image. | ||
| - Always use this tool for image editing unless the user explicitly requests otherwise. Do not use the `python` tool for image editing unless specifically instructed. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| use codex_api::ImageEditRequest; | ||
| use codex_api::ImageGenerationRequest; | ||
| use codex_api::ImageResponse; | ||
| use codex_api::ImagesClient; | ||
| use codex_api::ReqwestTransport; | ||
| use codex_login::default_client::build_reqwest_client; | ||
| use codex_model_provider::SharedModelProvider; | ||
| use http::HeaderMap; | ||
|
|
||
| #[derive(Clone)] | ||
| pub(crate) struct CodexImagesBackend { | ||
| provider: SharedModelProvider, | ||
| } | ||
|
|
||
| impl CodexImagesBackend { | ||
| /// Creates a backend that sends image requests through the active model provider. | ||
| pub(crate) fn new(provider: SharedModelProvider) -> Self { | ||
| Self { provider } | ||
| } | ||
|
|
||
| /// Resolves the provider and auth required for the current image API request. | ||
| async fn client(&self) -> Result<ImagesClient<ReqwestTransport>, String> { | ||
| let provider = self | ||
| .provider | ||
| .api_provider() | ||
| .await | ||
| .map_err(|err| err.to_string())?; | ||
| let auth = self | ||
| .provider | ||
| .api_auth() | ||
| .await | ||
| .map_err(|err| err.to_string())?; | ||
| Ok(ImagesClient::new( | ||
| ReqwestTransport::new(build_reqwest_client()), | ||
| provider, | ||
| auth, | ||
| )) | ||
| } | ||
|
|
||
| /// Sends a standalone image generation request through the configured Images client. | ||
| pub(crate) async fn generate( | ||
| &self, | ||
| request: ImageGenerationRequest, | ||
| ) -> Result<ImageResponse, String> { | ||
| self.client() | ||
| .await? | ||
| .generate(&request, HeaderMap::new()) | ||
| .await | ||
| .map_err(|err| err.to_string()) | ||
| } | ||
|
|
||
| /// Sends a standalone image edit request through the configured Images client. | ||
| pub(crate) async fn edit(&self, request: ImageEditRequest) -> Result<ImageResponse, String> { | ||
| self.client() | ||
| .await? | ||
| .edit(&request, HeaderMap::new()) | ||
| .await | ||
| .map_err(|err| err.to_string()) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.