@guanghechen/kit-copilot
v0.14.1
Published
AI API proxy server routing through GitHub Copilot
Maintainers
Readme
@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:
- CLI entry passes
process.envto@guanghechen/commander/node. - Commander merges user envs with preset envs into
ctx.envs. - Runtime logic consumes
ctx.envsonly.
There is no runtime fallback from business logic to global process.env for critical keys such as:
KIT_COPILOT_GITHUB_TOKENKIT_COPILOT_CODEX_UPSTREAM_TIMEOUT_MSKIT_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/copilotwhenXDG_CONFIG_HOMEis set~/.config/kit/copilototherwise
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:
{
"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 profileenvFile GH_COPILOT_TOKENis 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
kit-copilot auth
kit-copilot start --port=4141The 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:
KIT_COPILOT_DOWNSTREAM_TOKEN="$(openssl rand -hex 32)" \
kit-copilot start --host=0.0.0.0 --allowRemoteRemote 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:
KIT_COPILOT_GITHUB_TOKEN=ghu_xxx
# Optional: Codex Responses upstream response-establishment timeout, in milliseconds
KIT_COPILOT_CODEX_UPSTREAM_TIMEOUT_MS=60000Realtime 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:
[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_KEYKIT_STT_AZURE_API_ENDPOINT
Local whisper.cpp STT runtime envs:
KIT_STT_LOCAL_WHISPER_BINKIT_STT_LOCAL_WHISPER_MODELKIT_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 emitserrorwithinput_audio_buffer_overflowand aborts the active utterance. - Each Realtime WebSocket message is limited to
8MiB; oversized messages close with code1009. input_audio_buffer.append.audiomust 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:
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_generationLocal whisper.cpp example:
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_generationThe 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:
pnpm --filter @guanghechen/kit-copilot test:codex-contractSet 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.
