Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .devflow/features/cli-ux/KNOWLEDGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ Key exports: `PROVIDER_IDS`, `ProviderId`, `AliasesByProvider`, `MODEL_REGISTRY`
`parseCliArgs` returns a discriminated `CliCommand` union. Flag sets per command:

- `serve`: `verbose`, `quiet`, `port`
- `doctor`: (none — any flag on `doctor` is an error)
- `models`: `json` only
- `init`: `yes`, `dry-run`, `port`, `settings-target`
- `doctor`: `client` (defaults to `all`; reverse checks run when configured)
- `models`: `json`, `client` (defaults to `claude-code`)
- `init`: `yes`, `dry-run`, `port`, `settings-target`, `client` (defaults to `claude-code`)

Per-command flag validation walks `parseArgs` **tokens**, not `values`. The main switch is exhaustive (`default` assigns to `never` and calls `fail()`).

Expand Down Expand Up @@ -328,3 +328,20 @@ Emits to stderr. Format: `[HH:MM:SS] level=<L> event=<E> key=value …`. Fields
- PF-006: Doctor exits non-zero without live services; smoke uses `--version` not `doctor`; drives the `configuredProviders` severity split.
- `.devflow/features/codex-leg/KNOWLEDGE.md` — Full model resolution contract, `buildHeaders`, `ProviderEvents<P>` 19-field table, and the Codex handler/translator/auth side.
- `src/version.ts` — Source of `SUBSWITCH_VERSION` used by `--version`, doctor, `/__subswitch/health`, and `models --json`.

## Bidirectional ingress and configuration (2026-09-08)

`Config.codexIngress` enables native Codex HTTP/WebSocket ingress; its `claude` slice
controls Claude translation. `init --client codex|all` plans native TOML and SubSwitch
writes through `codex-init.ts` Result-returning helpers; custom upstream trust is explicit.
`doctor --client codex` checks native setup/auth/connectivity without refresh. Default
`doctor` selects `all` but skips disabled reverse checks. `models --json` stays version 1
for `claude-code`; version 2 has `client: codex|all` and separately documented shapes.

Configuration precedence is explicit `SUBSWITCH_CONFIG` (no merge), otherwise project
`subswitch.config.json` over `$XDG_CONFIG_HOME/subswitch/config.json` over defaults.
`LoadConfigResult.configPaths` lists every loaded source; legacy/unknown-provider errors
name the source containing the offending key. `configuredProviders` reflects all sources.

`src/clients.ts` owns the supported-client IDs and the `all` selector. Legacy `both`
normalizes to `all`; model JSON uses the canonical `client: "all"` discriminator.
31 changes: 31 additions & 0 deletions .devflow/features/codex-leg/KNOWLEDGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,34 @@ IncomingMessage (Anthropic wire)
- PF-012: The mutation-proof pass needs its own controls
- PF-013: The live Codex `/responses` stream sends no content-type header — the recorder cannot capture SSE without this
- `.devflow/features/cli-ux/KNOWLEDGE.md` — CLI UX layer; `subswitch models` command; doctor agent-scan; N-provider fan-out; `ProviderEvents<P>` compile-time log-injection control

## Native Codex → Claude ingress (2026-09-08)

The forward leg described above remains the default. `Config.codexIngress` adds an
opt-in reverse leg, enabled by `init --client codex|all`. Claude models and aliases
resolve by exact membership; `decideCodexRoute` consumes a typed resolution for both
HTTP and WebSockets. `CodexGateway` wires `CodexUpstream`, `CodexWebSockets`, native auth,
and `ClaudeHandler`. Both HTTP directions use `createRawHttpForwarder`; only complete
native requests with substituted credentials may refresh and retry once after 401.

Claude credential infrastructure is created in `buildDeps`. Native token substitution
is restricted to exact native endpoints and matching account IDs. `errors.ts` owns both
wire protocols' redaction. `claude-errors.ts` maps each failure code to an explicit status.
`ClaudeCache` shares the configured `codexIngress.claude.reasoningCache` budget across
continuation snapshots, thinking replay, and adaptation markers. Missing state returns
409. `content-encoding.ts` owns async codecs and the native zstd capability check.

`claude-adapter.ts` translates complete histories; `claude-stream.ts` tracks explicit
stream phases and per-block state, then commits executable tools only at a valid terminal.
Collaboration namespace adaptation applies to OpenAI turns too when Claude routing is
enabled. All `/codex` paths are reserved even when disabled. Raw TCP connect budgets do
not impose TLS, HTTP-header, WebSocket-handshake, or established-stream deadlines.

CLI `--client` defaults and merged configuration provenance are documented in the CLI UX
KB. Both directions use `allowInsecureBaseUrl` for explicit custom-host trust. Native
upgraded sockets keep a separate capacity slot until the client connection closes.

Native cancellation keeps an empty, bounded replay placeholder until a valid terminal.
The ordered `<turn_aborted>` notice and readable partial history can then continue
without replaying unfinished thinking or tools. Missing relay-side Claude credentials
return 503 so native Codex does not refresh its unrelated OpenAI login.
21 changes: 17 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,33 @@ concurrency:

jobs:
check:
name: check (${{ matrix.os }})
name: ${{ matrix.check_name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
include:
# Preserve the names required by main's branch protection.
- os: ubuntu-latest
node: "22.15.0"
check_name: check (ubuntu-latest)
- os: macos-latest
node: "22.15.0"
check_name: check (macos-latest)
- os: ubuntu-latest
node: "24"
check_name: check (ubuntu-latest, Node 24)
- os: macos-latest
node: "24"
check_name: check (macos-latest, Node 24)
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node 22
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
node-version: ${{ matrix.node }}
cache: npm

- name: Install dependencies
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ dist/
!.devflow/features/*/
.devflow/features/*/*
!.devflow/features/*/KNOWLEDGE.md
!.devflow/conventions.md
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,48 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- Opt-in native Codex → Claude routing for Sonnet, Opus and Fable, while OpenAI
models continue to OpenAI and Codex retains native agents, tools and permissions.
- Subscription authentication, credential refresh, readable collaboration messages,
streamed/non-stream responses, prompt caching and bounded process-local replay.
- `init`, `doctor` and `models --client codex|all`, native model discovery, and a
user configuration fallback. Existing Claude Code setup remains the default.
- Live native acceptance runners and shared parity follow-ups in issues #45–#48.

- `--client all` selects all supported clients; `both` remains a compatibility alias.

### Changed

- Supported Node versions are `^22.15.0 || >=24`, matching native zstd and runtime dependency requirements. Reverse routing includes the
documented Claude identity preamble. Durable restart/compaction, setup undo and
explicit API authentication for translated inference remain separate shared work.

- User-level configuration now participates in every implicit config load, including
forward-only runs. Project fields override it; explicit config selection bypasses it.
Invalid user configuration can therefore prevent startup; diagnostics show its source.
- Raw relay headers named by `Connection` are stripped on both legs in both directions.
- Native Codex setup requires explicit trust for a custom upstream host before it writes files.

### Fixed

- Native Codex conversations can continue after cancelling a Claude response; pending
replay handles and ordered cancellation notices no longer cause 409/400 failures.
- Missing relay-side Claude credentials produce Claude-specific setup guidance without
triggering native Codex to refresh its unrelated OpenAI login. Lost continuation
errors now explain that a new conversation is required.

- Scope native credential substitution to exact Codex endpoints and redact synthesized
OpenAI errors at the JSON, SSE, and WebSocket render boundaries.
- Use async decompression, one shared continuation/replay budget, bounded upgraded
sockets, and the shared raw HTTP transport with one credential refresh retry.
- Classify translation, state, internal, and upstream failures explicitly; validate
malformed history/catalog values and bound recursive protocol traversal.
- Document all ingress configuration keys, model JSON schemas, and ingress log routes.

## [0.4.0] - 2026-09-01

### Changed
Expand Down
Loading