Codex can now take the Vertex AI project and location from config.toml

2 days ago

This change lets Codex reach Vertex AI with two new settings on the built-in GCP provider. Under model_providers.gcp, you can now set a project ID and a location, right beside the provider choice. Codex builds the Vertex endpoint from them, so a Google Cloud setup can live in config.toml instead of the shell environment.

Ask about this presentation

Chapters

  1. 0:00Vertex AI settings in config.toml
  2. 0:22Why: environment variables only
  3. 0:39Part 1: Resolving the endpoint
  4. 0:42Config, then environment, then default
  5. 1:07The new vertex_base_url
  6. 1:32An empty project_id fails
  7. 1:49Part 2: Carrying the fields through Codex
  8. 1:52Where project_id and location travel
  9. 2:25Managed GCP requirements
  10. 2:52Part 3: Evidence
  11. 2:55Each field resolves independently
  12. 3:20What to do with it
Show transcript
  1. This change lets Codex reach Vertex AI with two new settings on the built-in GCP provider. Under model_providers.gcp, you can now set a project ID and a location, right beside the provider choice. Codex builds the Vertex endpoint from them, so a Google Cloud setup can live in config.toml instead of the shell environment.

    Slide text
    >_ OpenAI Codex

    Codex can now take the Vertex AI project and location from config.toml

    config.toml
    model_provider = "gcp"
    
    [model_providers.gcp]
    project_id = "example-project"
    location   = "us-central1"
  2. Deriving a Vertex AI endpoint used to require environment variables. Leave GOOGLE_CLOUD_PROJECT unset, and this was the whole instruction you got. The same error now names the new config key first, then the variable, then the base URL override.

    Slide text

    Before, the project could only come from an environment variable

    before
    Missing environment variable: `GOOGLE_CLOUD_PROJECT`.
    Set GOOGLE_CLOUD_PROJECT to your Google Cloud project ID, or configure model_providers.gcp.base_url.
    after
    Missing environment variable: `GOOGLE_CLOUD_PROJECT`.
    Set model_providers.gcp.project_id or GOOGLE_CLOUD_PROJECT to your Google Cloud project ID, or configure model_providers.gcp.base_url.
  3. First, how Codex picks the endpoint now.

    Slide text
    >_ 1

    Resolving the endpoint

  4. The endpoint has two slots. A project and a location, inside the Vertex AI publishers path for OpenAI. The project comes from the configured project ID first, then GOOGLE_CLOUD_PROJECT, and with neither set you get the error from before. Location follows the same order and still lands on global by default. And an explicit base URL bypasses all of it, as it did before.

    Slide text

    Each field resolves on its own: config first, then the environment, then the default

    https://aiplatform.googleapis.com/v1
    /projects/{project}/locations/{location}/publishers/openai/v1
    project
    1 project_id config.toml
    2 GOOGLE_CLOUD_PROJECT env
    3 error nothing set
    location
    1 location config.toml
    2 GOOGLE_CLOUD_LOCATION env
    3 global default

    An explicit base_url still skips all of this.

  5. Here is the function that does it, in the GCP model provider. It now takes the provider info beside the two environment values. The project starts from the configured project ID, trimmed. Only when that field is absent does it read the environment, and only the environment value is filtered for empty strings. Location has the same shape. So an empty configured project is kept, and fails validation.

    Slide text

    vertex_base_url now reads the provider info before the environment

    codex-rs/model-provider/src/gcp/mod.rs
    fn vertex_base_url(
        info: &ModelProviderInfo,
        environment_project: Option<&str>,
        environment_location: Option<&str>,
    ) -> Result<String> {
        let project = info
            .project_id
            .as_deref()
            .map(str::trim)
            .or_else(|| {
                environment_project
                    .map(str::trim)
                    .filter(|project| !project.is_empty())
            })
            .ok_or_else(|| /* names project_id and GOOGLE_CLOUD_PROJECT */)?;
        let location = info
            .location
            // … same shape, falling back to environment_location …
            .unwrap_or(DEFAULT_GOOGLE_CLOUD_LOCATION);
  6. An invalid value you set yourself now stops Codex. Set the project ID to an empty string while the environment holds a good project, and you get this error. The message names whichever source supplied the bad value, the config key or the environment variable.

    Slide text

    An empty project_id fails, even with a valid project in the environment

    inputs
    # config.toml
    project_id = ""
    
    # environment
    GOOGLE_CLOUD_PROJECT=
      environment-project
    result
    model_providers.gcp.project_id must be non-empty and contain only ASCII letters, digits, and hyphens

    The message names whichever source supplied the bad value: the config key or the variable.

  7. Next, everywhere the two fields have to travel.

    Slide text
    >_ 2

    Carrying the fields through Codex

  8. The fields have to survive every layer a provider passes through. The config schema gains both properties, and the merge step widens the list of GCP overrides it accepts, which used to stop at base URL, auth and HTTP headers. Model provider info carries them as two optional strings. The thread config proto adds fields 20 and 21, so remote thread configuration round-trips them. The other nine files are all tests, and the change there is mechanical: each struct literal gains two None fields.

    Slide text

    Two optional strings, added at every layer a provider passes through

    Schema

    core/config.schema.json
    + project_id
    + location

    Merge

    merge_configured_model_providers
    gcp now allows project_id, location

    Provider info

    ModelProviderInfo
    project_id: Option<String>
    location: Option<String>

    Thread config

    codex.thread_config.v1.proto
    project_id = 20
    location = 21

    9 more files, all tests: struct literals gain project_id: None, location: None

  9. The app server also checks managed requirements against the running provider. That check used to merge built-in defaults only for Bedrock. Now GCP gets the same treatment. It also applies the default gcloud auth command, with a thirty second timeout and a refresh every fifty minutes. That block moved out of the core config loader into one shared function, so startup and the requirement check build the same provider, and unchanged requirements are accepted.

    Slide text

    Managed GCP requirements now get the same built-in defaults as startup config

    requirements.toml
    model_provider = "gcp"
    [model_providers.gcp]
    project_id = "managed-project"
    location = "us-central1"
    codex-rs/config/src/model_provider_defaults.rs
    pub fn apply_default_gcp_auth(provider, cwd) {
      provider.auth.get_or_insert_with(|| ModelProviderAuthInfo {
        command: if cfg!(windows) { "gcloud.cmd" }
                 else { "gcloud" }.to_string(),
        args: ["auth", "application-default",
               "print-access-token"],
        timeout_ms: 30_000,
        refresh_interval_ms: 3_000_000,
        cwd: cwd.clone(),
      });
    }

    Called from Config at startup and from ConfigManager's requirement check.

  10. Finally, what the tests pin down.

    Slide text
    >_ 3

    Evidence

  11. This test holds the environment fixed and varies the config. Set both fields and both win. Set only the project, and the location still comes from the environment. Set only the location, and the project does. Other tests cover the empty project error, the proto round trip, requirement checks for Bedrock and GCP, and an app server that lists models twice under GCP requirements and gets the same answer.

    Slide text

    With the environment fixed, each configured field overrides only its own slot

    configured_vertex_endpoint_resolves_each_field_independently
    env: environment-project · us-central1
    project_idlocationendpoint uses
    configured-projectus-east4configured-project / us-east4
    configured-projectunsetconfigured-project / us-central1
    unsetus-east4environment-project / us-east4
    empty project_id rejected provider builds from config proto round trip requirement checks bedrock ×2 · gcp_project · gcp_location · gcp_auth model list twice, same result
  12. One limit: only the built-in GCP provider reads these fields, and an explicit base URL still overrides both. So if you run Codex on Vertex AI, the block from the start can replace the two environment variables, and they keep working as a fallback. And if you build model provider info by hand in a test, add the two new fields. Codex can now take the Vertex AI project and location from config.toml.

    Slide text
    >_ OpenAI Codex

    Only the built-in gcp provider reads these fields, and base_url still wins over both.

    On Vertex AI, move the project and location into config.toml

    Building a ModelProviderInfo by hand? Add the two new fields.

    config.toml
    model_provider = "gcp"
    
    [model_providers.gcp]
    project_id = "example-project"
    location   = "us-central1"

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built in New York with ❤️