dsh-codex-subs-plugin

August 14, 2026 · View on GitHub

中文 | English

An experimental primary-model adapter for DeepSeek Harness that uses ChatGPT OAuth to access an account's Codex subscription capabilities instead of an OpenAI Platform API key.

Warning

OpenAI officially commits only that Codex can access subscriptions through "Sign in with ChatGPT." This project follows the open-source implementations of the OpenAI Codex client and OpenCode, and depends on compatibility surfaces not documented as stable, including the auth.openai.com device-code endpoints, ChatGPT-Account-Id, and chatgpt.com/backend-api/codex. Server-side changes may break compatibility at any time. A subscription does not provide unlimited usage.

For complete installation, login, default-model, proxy, Headless, and Web UI instructions, see the USAGE guide.

For implementation details, upstream source evidence, and risk analysis, see How OpenCode uses a Codex subscription.

How it works

ChatGPT OAuth (PKCE / device code)
  → access + refresh token + account id
  → translate DSH GenerateOptions into a Responses request
  → send only to chatgpt.com/backend-api/codex/responses
  → translate Responses SSE into DSH StreamChunk
  → preserve encrypted reasoning / tool replay state

It does not read or copy credentials from Codex CLI or OpenCode, and it does not "convert" a subscription into an API key. Its credentials are stored independently at, in precedence order:

$DSH_CODEX_SUBS_AUTH_FILE
  or $DSH_HOME/codex-subs/auth.json
  or ~/.dsh/codex-subs/auth.json

Writes use a temporary file, fsync, atomic rename, and 0600 permissions. Refresh starts 60 seconds before expiry and coalesces concurrent requests. The transport exposes no custom endpoint setting; the OAuth bearer can be sent only to the fixed Codex backend encoded in the source.

Quick start

Requires Node.js ^22.19.0 or >=24.0.0, pnpm, and an installed dsh CLI.

1. Install

pnpm install
pnpm run check
dsh plugin --profile headless add .

This package's bundle is non-invasive: it only registers the codex-subscription provider and does not replace DSH's existing default model. The following optional command can only confirm that the Cordis composition contains llm-codex-subscription:

dsh --profile headless --dump-config

--dump-config does not load the runtime model selection from $DSH_HOME/settings.yaml, so it cannot prove that requests will use this plugin.

2. Log in

dsh plugin --profile headless exec dsh-codex-subs login

On a machine without a desktop browser, use the time-limited device-code flow:

Device-code login is currently a beta feature. A personal account must first enable it under ChatGPT Settings → Security; for a managed workspace, an administrator must enable it in workspace permissions. See OpenAI authentication.

dsh plugin --profile headless exec dsh-codex-subs login --headless

3. Explicitly select the default model

Edit $DSH_HOME/settings.yaml; when DSH_HOME is unset, the default path is ~/.dsh/settings.yaml:

agent-default-model:
  provider: codex-subscription
  model: gpt-5.5
  reasoningEffort: medium

The default catalog is only a current compatibility snapshot. Unlisted model IDs are still passed through unchanged, and actual model access is determined by the server.

4. Check local status

dsh plugin --profile headless exec dsh-codex-subs status
dsh plugin --profile headless exec dsh-codex-subs doctor

status reads local credential metadata. doctor neither refreshes the token nor makes a network request; it reports only whether auth exists or is expired, whether an account ID is present, profile installation state, the plugin/provider, the default provider/model declared in settings.yaml, whether proxy variables are present, the Node environment-proxy switch, and the fixed endpoint. It does not output tokens, the account ID, proxy URLs, or proxy-variable values. Neither command verifies remote entitlement or proves that DSH selected this route at runtime.

5. Complete a minimal request round trip

dsh --profile headless "Reply with exactly: codex-subscription-ok"

This is the current real runtime verification: a new headless agent reads the effective default model and sends a request through the selected adapter. A successful response jointly verifies runtime routing, OAuth, networking, and server-side access. It consumes subscription usage.

When you no longer use the plugin, you can delete its own local credential:

dsh plugin --profile headless exec dsh-codex-subs logout

logout does not remotely revoke the token or sign out of ChatGPT, Codex CLI, or OpenCode.

Proxy environment

OAuth, refresh, device-code, and Responses requests share one isolated, proxy-aware transport. It reads HTTP_PROXY, HTTPS_PROXY, and NO_PROXY, including their lowercase forms; when both cases of the same variable are present, lowercase takes precedence. Example:

export HTTPS_PROXY=http://proxy.example:8080
export HTTP_PROXY=http://proxy.example:8080
export NO_PROXY=localhost,127.0.0.1
  • The current transport neither uses ALL_PROXY nor counts it toward doctor's proxy presence. Set HTTPS_PROXY/HTTP_PROXY as appropriate for the protocol.
  • When HTTPS_PROXY is not set separately, Undici falls back to HTTP_PROXY for HTTPS requests. Setting both explicitly makes diagnosis easier.
  • The authorization page opens in an external browser and uses the browser or operating system's network path. CLI token and model requests use this plugin's transport, so their egress paths may differ.
  • Node 24 provides NODE_USE_ENV_PROXY=1 / --use-env-proxy as a global HTTP(S) client capability. This is diagnostic context only: on supported Node 22 and 24 versions, the plugin uses its own unified transport and does not depend on that switch.
  • The transport refuses to follow redirects automatically and redacts network errors. Even if a proxy URL contains credentials, never paste it or a complete environment-variable value into logs, issues, or chats.

Troubleshooting unsupported_country_region_territory

This error comes from the server. The plugin preserves the stable error code UNSUPPORTED_COUNTRY_REGION_TERRITORY, but it cannot override regional, account, or workspace policies:

  1. Run status and doctor first to check auth, the default provider/model, and proxy-variable state. They do not test the remote service.
  2. Compare against OpenAI's supported countries and territories. That page describes API service availability; the private ChatGPT Codex backend still makes the final eligibility decision.
  3. Ask the network or enterprise administrator to confirm the public egress country or region of the CLI process. A successful browser login does not mean the CLI request uses the same egress path.
  4. Check for conflicting or stale uppercase and lowercase proxy variables. This implementation gives lowercase precedence. Confirm that NO_PROXY does not unintentionally bypass the proxy for auth.openai.com or chatgpt.com; setting only ALL_PROXY has no effect on this transport.
  5. Retry the minimal request after establishing a consistent, compliant network path. Log in again if the failing stage is OAuth/device/refresh or the credential has expired. If it still fails, retain the failure stage, HTTP status, and request ID, remove tokens, complete request headers, and proxy URLs, then contact OpenAI Support or the workspace administrator.

Do not try to bypass server-side controls by forging a client ID, endpoint, copying someone else's credentials, or evading regional restrictions.

Plugin configuration

Override the row inserted by the bundle in the profile's cordis.patch.yml:

- id: llm-codex-subscription
  config:
    provider: codex-subscription
    displayName: OpenAI Codex Subscription
    reasoningEffort: medium
    textVerbosity: low
    streamIdleTimeoutMs: 300000

Other optional fields are an absolute authFile path and an advisory models list. There is no baseURL or endpoint setting; this is part of the token boundary.

Implemented

  • Browser PKCE + state + 127.0.0.1-only callback, with a five-minute timeout and guaranteed cleanup.
  • Headless device flow with a 15-minute total timeout.
  • Atomic persistence of refresh-token rotation, early refresh, and single-flight coordination.
  • One redacting environment-proxy transport shared by OAuth, refresh, device, and Responses.
  • doctor safely reports local auth, installation, the default model, and proxy presence without making a network request.
  • Forced refresh followed by one safe replay after the first 401, with no retry loop.
  • Responses mappings for text, reasoning summary, function calls, usage, errors, and incomplete results.
  • Stateless replay of encrypted reasoning and tool calls under store:false.
  • DSH usage → finish ordering, abort, and stream idle-timeout contracts.

If you only need Codex as a subagent, prefer DeepSeek Harness's built-in @deepseek-ai/dsh-subagent-codex. It invokes the official codex app-server and uses native Codex authentication. This project is for connecting a Codex subscription to DSH as the primary LLM route.

Development verification

pnpm run typecheck
pnpm run test
pnpm run build
pnpm pack --dry-run

All tests use mock OAuth / Responses data. They do not read real credentials or access OpenAI.