Skip to main content
E

dsh-oauth-openai

ekincn/dsh-oauth-openai

OpenAI Codex OAuth provider and model adapter for dsh-oauth and DeepSeek Harness

Install

dsh plugin --profile web add github:ekincn/dsh-oauth-openai

README

dsh-oauth-openai

中文 | English

OpenAI Codex OAuth provider for DeepSeek Harness: sign in to ChatGPT through dsh-oauth, register the openai-codex model route, expose an editable model catalog, and correct the one request field the ChatGPT Codex backend refuses. The whole integration installs as external third-party bundles — no app rebuild or replacement.

Responsibility boundary

  • dsh-oauth: account page, login sessions, secret storage, refresh locking, and logout.
  • dsh-oauth-openai: the OpenAI Codex OAuth protocol, account parsing, refresh, the model adapter, and the model-catalog settings page.
  • It never copies Codex CLI's auth.json, and it never treats a ChatGPT subscription token as an OpenAI Platform API key.

Features

  1. ChatGPT sign-in: browser PKCE callback when the local 1455 port is reachable, otherwise device code; credentials live in the Harness credential store and refresh automatically.
  2. openai-codex model route: pi-ai's openai-codex-responses protocol, over the installed catalog or custom model rows (id, display name, context window, output cap, input modalities).
  3. Editable model catalog: in the plugin's settings page. With no saved override the route inherits pi-ai's installed Codex catalog; a saved list replaces it wholesale and can be reset.
  4. Codex parameter compatibility fix (see below).

Install (third-party, external)

Requires DeepSeek Harness 0.2.x (verified on the official desktop 0.2.0-rc.2).

Two tarballs ship in dist/, installed in order:

PackageContents
dist/ekin_chen-dsh-oauth-0.1.2.tgz@ekin_chen/dsh-oauth — the account foundation (code identical to upstream dsh-oauth@0.1.1; manifest adapted for 0.2.x and republished under our own scope, see VENDORED.md)
dist/ekin_chen-dsh-oauth-openai-0.2.3.tgzThis plugin (@ekin_chen/dsh-oauth-openai)
dsh plugin --profile desktop add /abs/path/dist/ekin_chen-dsh-oauth-0.1.2.tgz
dsh plugin --profile desktop add /abs/path/dist/ekin_chen-dsh-oauth-openai-0.2.3.tgz

Naming: the npm names dsh-oauth / dsh-oauth-openai belong to a third party (ruihuahe, MIT). This stack is republished under our own scope @ekin_chen/* with attribution (THIRD_PARTY_NOTICES). Registry install: dsh plugin --profile desktop add @ekin_chen/dsh-oauth-openai (@ekin_chen/dsh-oauth comes along as its dependency).

add selects each bundle automatically; restart the app when the profile has no HMR.

Publishing (maintainers): npm publish dist/ekin_chen-dsh-oauth-0.1.2.tgz --access public, then npm publish dist/ekin_chen-dsh-oauth-openai-0.2.3.tgz --access public (scoped packages default to private). Never touch the third-party dsh-oauth/dsh-oauth-openai names.

Rebuild from source:

pnpm install
pnpm test
pnpm run build
pnpm run pack:check
node scripts/vendor-dsh-oauth.mjs dist   # regenerate the dsh-oauth external package

pnpm run build clears dist/; run pack:check first, then vendor-dsh-oauth.

Usage

  • Settings → OpenAI Codex: sign in (browser / device code), account status and sign-out, and the model-catalog editor (add/remove rows, adopt rows from the installed catalog, save, reset).
  • Pick openai-codex/<model id> in the model selector; custom models appear there too.

The temperature problem (Unsupported parameter: temperature)

Symptom. On the openai-codex route the experimental Auto review classifies every pending tool call with temperature: 0 (upstream packages/experimental/auto-review, classifyRisk). The ChatGPT Codex backend rejects the temperature field, so the request fails with PI_AI_ERROR: Codex error: Unsupported parameter: temperature and the tool call is blocked.

Fix. Two stripping layers, both confined to Codex-bound traffic and neither changing the call config recorded in session logs:

  1. Adapter layer — OpenAICodexAdapter.stream() drops temperature before pi-ai sees the request (a loop-assembled request keeps its loop identity on the copy).
  2. llm/stream waterfall layer — codexTemperatureListener re-dispatches a temperature-free copy through the same waterfall. This layer keeps working even while llm-pi-ai's configured profile still owns the route (see below), so Auto review and any other temperature-carrying call succeed.

Toggle: stripRequestTemperature (default true).

Evidence (scripts/smoke-request.mjs, real stack against the real backend, A/B):

strip=true   finish=stop  text="pong"                                         # fix on: succeeds
strip=false  finish=error "Codex error: Unsupported parameter: temperature"   # fix off: reproduces the bug

Host constraint (0.2.x)

On 0.2.x runtimes connection.rpc.handle cannot register custom channels: its registration path reads owner.webServer, which the Cordis inject rules reject (cannot get property "webServer" without inject), so requests fall through to the SPA static layer and get HTTP 405. Every third-party plugin using that API hits this (for example the tailscale-serve settings toggle). This plugin therefore serves its control channel as exact Fetch routes on the shared /api channel (connection.fetch.register), verified over real HTTP: 401 unauthenticated, standard server-response envelopes once admitted.

Relationship with the built-in llm-pi-ai route

The route and the Models-page directory entry are first-come registrations:

  • If the profile's cordis.patch.yml configures llm-pi-ai.providers.openai-codex, the built-in adapter keeps serving the route. This plugin backs off (logging a migration hint) while the llm/stream layer still fixes temperature.
  • To let this plugin serve the route (its accounts and catalog), delete the llm-pi-ai providers.openai-codex section and keep the model rows in Settings → OpenAI Codex, then sign in once on that page.
  • The native Models-page card for openai-codex edits llm-pi-ai's section; this plugin's catalog lives in its own settings page. Do not mix the two.

Configuration reference

- id: llm-openai-codex
  config:
    loginMethod: auto            # auto | browser | device-code
    authorizationTimeoutMs: 900000
    streamIdleTimeoutMs: 300000
    stripRequestTemperature: true
    maxRequestImageBytes: 20971520
    models:                      # omitted = inherit installed catalog; list = full replacement
      - id: gpt-5.6-luna
        name: GPT-5.6 Luna
        contextWindow: 272000
        maxTokens: 128000        # an explicit value also becomes the request default cap
        input: [text, image]     # unknown ids inherit the first image-capable installed model

Compatibility and dependency policy

  • @deepseek-ai/dsh-* peer ranges are >=0.1.0-rc.6 <0.3.0, which the plugin compatibility gate (semver.satisfies(runtime, range, { includePrerelease: true })) accepts on 0.2.x.
  • Every peer is optional: the host runtime supplies same-generation copies through the Loader's interception, so pnpm never vendors duplicate @deepseek-ai/*, pi-ai, or dsh-oauth copies into the profile.
  • The @earendil-works/pi-ai coupling is ^0.87.1 (the version the 0.2.0-rc.2 desktop ships).

Development and verification

pnpm test                      # temperature fix (both layers), catalog resolution, composition, RPC contract, config validation
pnpm run typecheck
node scripts/smoke-request.mjs           # real request (needs a Codex login on this machine)
node scripts/smoke-request.mjs --no-strip # control run: should reproduce the original error

License

MIT. Third-party components are listed in THIRD_PARTY_NOTICES.md.

Related plugins