OpenCode Codex Auth Plugin

Access GPT-5 Codex through your ChatGPT Plus/Pro subscription

Download as .zip Download as .tar.gz View on GitHub

OpenCode Config Flow: Complete Guide

This document explains how OpenCode configuration flows from user files through the plugin system to the Codex API.

Table of Contents


Config Loading Order

OpenCode loads and merges configuration from multiple sources in this order (last wins):

1. Global Config

~/.opencode/config.json
~/.opencode/opencode.json
~/.opencode/opencode.jsonc

2. Project Configs (traversed upward from cwd)

<project>/.opencode/opencode.json
<parent>/.opencode/opencode.json
... (up to worktree root)

3. Custom Config (via flags)

OPENCODE_CONFIG=/path/to/config.json opencode
# or
OPENCODE_CONFIG_CONTENT='{"model":"openai/gpt-5"}' opencode

4. Auth Configs

# From .well-known/opencode endpoints (for OAuth providers)
https://auth.example.com/.well-known/opencode

Source: tmp/opencode/packages/opencode/src/config/config.ts:26-51


Provider Options Flow

Options are merged at multiple stages before reaching the plugin:

Stage 1: Database Defaults

Models.dev provides baseline capabilities for each provider/model.

Stage 2: Environment Variables

export OPENAI_API_KEY="sk-..."

Stage 3: Custom Loaders

Plugins can inject options via the loader() function.

Stage 4: User Config (HIGHEST PRIORITY)

{
  "provider": {
    "openai": {
      "options": {
        "reasoningEffort": "medium",
        "textVerbosity": "low"
      }
    }
  }
}

Result: User config overrides everything else.

Source: tmp/opencode/packages/opencode/src/provider/provider.ts:236-339


Model Selection & Persistence

Display Names vs Internal IDs

Your Config (config/full-opencode.json):

{
  "provider": {
    "openai": {
      "models": {
        "GPT 5 Codex Medium (ChatGPT Subscription)": {
          "id": "gpt-5-codex",
          "options": {
            "reasoningEffort": "medium"
          }
        }
      }
    }
  }
}

What OpenCode Uses:

TUI Persistence

The TUI stores recently used models in ~/.opencode/tui:

[[recently_used_models]]
provider_id = "openai"
model_id = "gpt-5-codex"
last_used = 2025-10-12T10:30:00Z

Key Point: Custom display names are UI-only. The underlying id field is what gets persisted and sent to APIs.

Source: tmp/opencode/packages/tui/internal/app/state.go:54-79


Plugin Configuration

How This Plugin Receives Config

Plugin Entry Point (index.ts:64-86):

async loader(getAuth: () => Promise<Auth>, provider: unknown) {
  const providerConfig = provider as {
    options?: Record<string, unknown>;
    models?: UserConfig["models"]
  };

  const userConfig: UserConfig = {
    global: providerConfig?.options || {},  // Global options
    models: providerConfig?.models || {},   // Per-model options
  };

  // ... use userConfig in custom fetch()
}

Config Structure

type UserConfig = {
  global: {
    // Applied to ALL models
    reasoningEffort?: "minimal" | "low" | "medium" | "high";
    textVerbosity?: "low" | "medium" | "high";
    include?: string[];
  };
  models: {
    [modelName: string]: {
      options?: {
        // Override global for specific model
        reasoningEffort?: "minimal" | "low" | "medium" | "high";
        textVerbosity?: "low" | "medium" | "high";
      };
    };
  };
};

Option Precedence

For a given model, options are merged:

  1. Global options (provider.openai.options)
  2. Model-specific options (provider.openai.models[modelName].options) ← WINS

Implementation: lib/request/request-transformer.ts:getModelConfig()


Examples

Example 1: Global Options Only

{
  "plugin": ["opencode-openai-codex-auth"],
  "provider": {
    "openai": {
      "options": {
        "reasoningEffort": "medium",
        "textVerbosity": "medium",
        "include": ["reasoning.encrypted_content"]
      }
    }
  }
}

Result: All OpenAI models use these options.

Example 2: Per-Model Override

{
  "plugin": ["opencode-openai-codex-auth"],
  "provider": {
    "openai": {
      "options": {
        "reasoningEffort": "medium",
        "textVerbosity": "medium"
      },
      "models": {
        "gpt-5-codex-high": {
          "name": "GPT 5 Codex High (OAuth)",
          "options": {
            "reasoningEffort": "high",
            "reasoningSummary": "detailed"
          }
        },
        "gpt-5-nano": {
          "name": "GPT 5 Nano (OAuth)",
          "options": {
            "reasoningEffort": "minimal",
            "textVerbosity": "low"
          }
        }
      }
    }
  }
}

Result:

Example 3: Full Configuration

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-openai-codex-auth"],
  "model": "openai/gpt-5-codex-medium",
  "provider": {
    "openai": {
      "options": {
        "reasoningEffort": "medium",
        "reasoningSummary": "auto",
        "textVerbosity": "medium",
        "include": ["reasoning.encrypted_content"]
      },
      "models": {
        "gpt-5-codex-low": {
          "name": "GPT 5 Codex Low (OAuth)",
          "options": {
            "reasoningEffort": "low"
          }
        },
        "gpt-5-codex-high": {
          "name": "GPT 5 Codex High (OAuth)",
          "options": {
            "reasoningEffort": "high",
            "reasoningSummary": "detailed"
          }
        }
      }
    }
  }
}

Best Practices

1. Use Per-Model Options for Variants

Instead of duplicating global options, override only what’s different:

Bad:

{
  "models": {
    "gpt-5-low": {
      "id": "gpt-5",
      "options": {
        "reasoningEffort": "low",
        "textVerbosity": "low",
        "include": ["reasoning.encrypted_content"]
      }
    },
    "gpt-5-high": {
      "id": "gpt-5",
      "options": {
        "reasoningEffort": "high",
        "textVerbosity": "high",
        "include": ["reasoning.encrypted_content"]
      }
    }
  }
}

Good:

{
  "options": {
    "include": ["reasoning.encrypted_content"]
  },
  "models": {
    "gpt-5-low": {
      "id": "gpt-5",
      "options": {
        "reasoningEffort": "low",
        "textVerbosity": "low"
      }
    },
    "gpt-5-high": {
      "id": "gpt-5",
      "options": {
        "reasoningEffort": "high",
        "textVerbosity": "high"
      }
    }
  }
}

2. Keep Display Names Meaningful

Custom model names help you remember what each variant does:

{
  "models": {
    "GPT 5 Codex - Fast & Cheap": {
      "id": "gpt-5-codex",
      "options": { "reasoningEffort": "low" }
    },
    "GPT 5 Codex - Balanced": {
      "id": "gpt-5-codex",
      "options": { "reasoningEffort": "medium" }
    },
    "GPT 5 Codex - Max Quality": {
      "id": "gpt-5-codex",
      "options": { "reasoningEffort": "high" }
    }
  }
}

3. Set Defaults at Global Level

Most common settings should be global:

{
  "options": {
    "reasoningEffort": "medium",
    "reasoningSummary": "auto",
    "textVerbosity": "medium",
    "include": ["reasoning.encrypted_content"]
  }
}

4. Use Config Files, Not Environment Variables

While you can set CODEX_MODE=0 to disable the bridge prompt, it’s better to document such settings in config files:

Bad: CODEX_MODE=0 opencode

Good: Create ~/.opencode/openai-codex-auth-config.json:

{
  "codexMode": false
}

Troubleshooting

Config Not Being Applied

  1. Check config file syntax with jq . < config.json
  2. Verify config file location (use absolute paths)
  3. Check OpenCode logs for config load errors
  4. Use OPENCODE_CONFIG_CONTENT to test minimal configs

Model Not Persisting

  1. TUI remembers the id field, not the display name
  2. Check ~/.opencode/tui for recently used models
  3. Verify your config has the correct id field

Options Not Taking Effect

  1. Model-specific options override global options
  2. Plugin receives merged config from OpenCode
  3. Add debug logging to verify what plugin receives

See Also