# @guanghechen/kit-copilot

GitHub Copilot proxy server for `Claude`, `Codex`, and `Gemini` compatible endpoints.

Runtime requirement: Node `^22.15.0 || >=24.0.0`. Codex enables zstd request compression by default;
the server requires Node's native zstd decoder and fails startup when that capability is absent.

## Environment Model

`kit-copilot` follows a strict environment flow:

1. CLI entry passes `process.env` to `@guanghechen/commander/node`.
2. Commander merges user envs with preset envs into `ctx.envs`.
3. Runtime logic consumes `ctx.envs` only.

There is no runtime fallback from business logic to global `process.env` for critical keys such as:

- `KIT_COPILOT_GITHUB_TOKEN`
- `KIT_COPILOT_CODEX_UPSTREAM_TIMEOUT_MS`
- `KIT_COPILOT_NATIVE_UPSTREAM_TIMEOUT_MS`

Codex protocol headers such as `x-codex-beta-features` and `OpenAI-Beta` are forwarded by the
Responses route contract itself; there is no runtime beta-header allowlist.

## Preset File (`preset.json`)

Config root:

- `$XDG_CONFIG_HOME/kit/copilot` when `XDG_CONFIG_HOME` is set
- `~/.config/kit/copilot` otherwise

Default preset file:

- `<config-root>/preset.json`

Commander loads preset data from `preset.json` (`preset.file`) and merges selected profile into `ctx.envs`.
If `<config-root>/preset.json` does not exist, command runs without preset envs.

Example `preset.json`:

```json
{
  "version": 1,
  "defaults": { "profile": "default" },
  "profiles": {
    "default": {
      "envFile": ".env.local"
    }
  }
}
```

## Token Persistence

`kit-copilot auth` persists `KIT_COPILOT_GITHUB_TOKEN` to preset env storage.

- Persist target resolution: `variant.envFile` > `profile.envFile` > `<config-root>/.env.local`
- Persist target path is resolved from commander public preset metadata (`meta.file/profile/variant`) and preset profile `envFile`
- `GH_COPILOT_TOKEN` is not persisted

Note: persist target is selected from the active preset profile/variant; default fallback is `<config-root>/.env.local` when no `envFile` is configured or path resolution fails.

## Examples

```bash
kit-copilot auth
kit-copilot start --port=4141
```

The default listener is loopback-only. It rejects browser `Origin` requests and non-loopback `Host`
values. Remote binding is explicit and requires a downstream bearer token:

```bash
KIT_COPILOT_DOWNSTREAM_TOKEN="$(openssl rand -hex 32)" \
kit-copilot start --host=0.0.0.0 --allowRemote
```

Remote clients must send the same value as `Authorization: Bearer <token>` (for Codex, configure a
custom provider `env_key`). Remote mode does not provide TLS; use a trusted network or TLS tunnel.

With preset env file:

```dotenv
KIT_COPILOT_GITHUB_TOKEN=ghu_xxx
# Optional: Codex Responses upstream response-establishment timeout, in milliseconds
KIT_COPILOT_CODEX_UPSTREAM_TIMEOUT_MS=60000
```

## Realtime Voice (Codex)

`kit-copilot` supports Codex Realtime voice input via WebSocket handoff events.

Codex must use websocket realtime transport for local STT. The default WebRTC transport is not
implemented by `kit-copilot` because WebRTC requires SDP negotiation and a media channel in addition
to STT. Configure Codex with:

```toml
[realtime]
transport = "websocket"
version = "v2"
type = "conversational"
```

Supported WS paths:

- `/api/codex`
- `/api/codex/` (equivalent custom-provider root)
- `/api/codex/realtime`
- `/api/codex/v1/realtime`

`POST /api/codex/realtime/calls` returns `400 realtime_webrtc_unsupported` with the same websocket
transport guidance.

Path matching is exact by design:

- `/api/codex/` is explicitly supported because Codex preserves the custom provider root slash.
- Other trailing slash variants, such as `/api/codex/realtime/`, are rejected.

STT selector envs:

- `KIT_COPILOT_REALTIME_STT_PROVIDER` (optional, default: `aoai`)
- `KIT_COPILOT_REALTIME_STT_MODEL` (optional, default: `whisper`)
- `KIT_COPILOT_REALTIME_STT_LANGUAGE` (optional, BCP-47 language tag)

Realtime transcripts are handed off as returned by the selected STT provider. Local whisper.cpp uses
`zh` for Chinese and does not expose a Simplified/Traditional script selector.

Azure STT runtime envs:

- `KIT_STT_AZURE_API_KEY`
- `KIT_STT_AZURE_API_ENDPOINT`

Local whisper.cpp STT runtime envs:

- `KIT_STT_LOCAL_WHISPER_BIN`
- `KIT_STT_LOCAL_WHISPER_MODEL`
- `KIT_STT_LOCAL_WHISPER_THREADS` (optional)
- `KIT_STT_LOCAL_TMP_DIR` (optional)
- `KIT_STT_LOCAL_FFMPEG_BIN` (optional)

Audio buffering policy:

- Idle debounce (`600ms`) completes the utterance; max segment duration (`4000ms`) only splits STT
  work and does not emit an early handoff.
- Per-connection buffered, queued, and in-flight audio has a hard cap of `20MiB`; overflow emits
  `error` with `input_audio_buffer_overflow` and aborts the active utterance.
- Each Realtime WebSocket message is limited to `8MiB`; oversized messages close with code `1009`.
- `input_audio_buffer.append.audio` must be canonical RFC4648 base64 (decode+re-encode must exactly match input).

The GitHub Copilot backend does not expose Codex standalone Images APIs. Run Codex with
`--disable image_generation`, or persist `[features] image_generation = false` in Codex config.
This does not disable vision input through Responses `input_image`.

Example:

```bash
KIT_STT_AZURE_API_KEY=... \
KIT_STT_AZURE_API_ENDPOINT=https://<resource>.openai.azure.com \
OPENAI_BASE_URL=http://127.0.0.1:4141/api/codex \
codex --disable image_generation
```

Local whisper.cpp example:

```bash
KIT_COPILOT_REALTIME_STT_PROVIDER=local \
KIT_COPILOT_REALTIME_STT_MODEL=whisper-cpp \
KIT_COPILOT_REALTIME_STT_LANGUAGE=zh-CN \
KIT_STT_LOCAL_WHISPER_BIN=/path/to/whisper.cpp/build/bin/whisper-cli \
KIT_STT_LOCAL_WHISPER_MODEL=/path/to/whisper.cpp/models/ggml-large-v3-turbo-q5_0.bin \
OPENAI_BASE_URL=http://127.0.0.1:4141/api/codex \
codex --disable image_generation
```

The Codex config still needs `[realtime].transport = "websocket"`; otherwise Codex will attempt the
WebRTC call creation path and receive the explicit unsupported response.

### Current Codex binary contract

Run the opt-in contract harness against the `codex` binary on `PATH`:

```bash
pnpm --filter @guanghechen/kit-copilot test:codex-contract
```

Set `CODEX_BIN=/absolute/path/to/codex` to verify a specific build. The harness uses an isolated
temporary `CODEX_HOME`, placeholder credentials, local auth fixtures, and does not target real
GitHub/OpenAI endpoints. It is intentionally separate from default unit and coverage tests because
the binary version is machine-controlled.
