Files
codex/codex-rs/ext/image-generation/src/backend.rs
T
Chris Dong 346c4db7c2 Track image generation request IDs in analytics (#40714)
## What changed

- Read `x-codex-imagegen-request-id` from image generation and edit responses.
- Propagate the ID to `codex_image_generation_event` analytics.
- Keep the ID in process only, excluding it from extension item serialization, generated types, app-server wire data, and rollout history.

## Testing

- Cover response-header extraction and missing-header behavior in the images client.
- Verify end-to-end analytics emission and the unchanged image-generation item wire shape.

GitOrigin-RevId: 9b8e09b7d655c0c2faa84f0eee99aabae583668d
2026-08-25 21:38:10 +00:00

123 lines
3.5 KiB
Rust

use codex_api::ImageEditRequest;
use codex_api::ImageGenerationRequest;
use codex_api::ImageResponse;
use codex_api::ImagesClient;
use codex_api::ReqwestTransport;
use codex_api::map_api_error;
use codex_login::default_client::add_originator_header;
use codex_login::default_client::create_client;
use codex_model_provider::SharedModelProvider;
use codex_protocol::error::CodexErr;
use http::HeaderMap;
use http::HeaderValue;
const X_CODEX_IMAGE_TURN_ID_HEADER: &str = "x-codex-image-turn-id";
pub(crate) struct ImageBackendError {
message: String,
codex_error: CodexErr,
}
impl ImageBackendError {
fn from_api(error: codex_api::ApiError) -> Self {
let message = error.to_string();
Self {
message,
codex_error: map_api_error(error),
}
}
fn from_message(message: String) -> Self {
Self {
codex_error: CodexErr::Stream(message.clone()),
message,
}
}
pub(crate) fn message(&self) -> &str {
&self.message
}
pub(crate) fn codex_error(&self) -> &CodexErr {
&self.codex_error
}
}
#[derive(Clone)]
pub(crate) struct CodexImagesBackend {
provider: SharedModelProvider,
originator: Option<String>,
}
impl CodexImagesBackend {
/// Creates a backend that sends image requests through the active model provider.
pub(crate) fn new(provider: SharedModelProvider, originator: Option<String>) -> Self {
Self {
provider,
originator,
}
}
/// Resolves the provider and auth required for the current image API request.
async fn client(&self) -> Result<ImagesClient<ReqwestTransport>, ImageBackendError> {
let provider = self
.provider
.api_provider()
.await
.map_err(|err| ImageBackendError::from_message(err.to_string()))?;
let auth = self
.provider
.api_auth()
.await
.map_err(|err| ImageBackendError::from_message(err.to_string()))?;
Ok(ImagesClient::new(
ReqwestTransport::from_http_client(create_client()),
provider,
auth,
))
}
/// Sends a standalone image generation request through the configured Images client.
pub(crate) async fn generate(
&self,
request: ImageGenerationRequest,
turn_id: &str,
) -> Result<(ImageResponse, Option<String>), ImageBackendError> {
self.client()
.await?
.generate(
&request,
image_request_headers(self.originator.as_deref(), turn_id),
)
.await
.map_err(ImageBackendError::from_api)
}
/// Sends a standalone image edit request through the configured Images client.
pub(crate) async fn edit(
&self,
request: ImageEditRequest,
turn_id: &str,
) -> Result<(ImageResponse, Option<String>), ImageBackendError> {
self.client()
.await?
.edit(
&request,
image_request_headers(self.originator.as_deref(), turn_id),
)
.await
.map_err(ImageBackendError::from_api)
}
}
fn image_request_headers(originator: Option<&str>, turn_id: &str) -> HeaderMap {
let mut headers = HeaderMap::new();
if let Ok(turn_id) = HeaderValue::from_str(turn_id) {
headers.insert(X_CODEX_IMAGE_TURN_ID_HEADER, turn_id);
}
if let Some(originator) = originator {
add_originator_header(&mut headers, originator);
}
headers
}