Copilot agent gateway(Claude Code + Codex)¶
Terminology rule (zh-TW pages)
技術名詞首次出現以「中文 (English original)」格式呈現,例:依賴注入
(dependency injection)。不自創翻譯——若無公認譯名直接保留英文
(如 embedding、tokenizer)。代碼、API 名、CLI flag、套件名、檔名一律不翻。
用 GitHub Copilot 訂閱目前 served 的模型來驅動
Claude Code:帳號有 Claude entitlement 時優先
用 Claude,沒有時改用有角色分層的 OpenAI fallback。本機代理使用持續維護的
caozhiyuan/copilot-api
fork(npm @jeffreycao/copilot-api)。
- Shell helpers:
~/.config/shell/43_copilot_proxy.sh(copilot-proxy、claude-copilot、codex-copilot、copilot-run、copilot-here、copilot-model) - 執行器 (runner):
@jeffreycao/copilot-api(已釘選 pinned),只安裝一次到~/.local/share/copilot-api/pkg,之後直接執行該處的 binary。刻意不在啟動時用bunx:bunx 每次啟動都會重新解析套件,而 bun 透過 socksALL_PROXY解析相依會永遠卡住 —— 詳見陷阱。 熱啟動 (warm start) 在綁定 port 之前完全不碰網路。 - 不由 ansible 安裝 —— 第一次
copilot-proxy start才安裝。update --check只檢查;只有明確指定copilot-proxy update VERSION才會改變釘選版本。
這違反 GitHub Copilot 的服務條款 (Terms of Service)
用 Copilot 訂閱去驅動非 GitHub 的 agent 是不被允許的,且 copilot-api 是逆向工程/非官方
的。copilot-api 自己的 README 就警告它可能觸發 GitHub 的 濫用偵測 (abuse detection),
導致 Copilot 存取被暫時停權 (temporary suspension)。Claude Code 很吃 token(頻繁的背景
請求、大 context);fork 本身沒有 rate limiter,所以本機 shim 以保守 queue + 自適應並行
控制 burst,另可設 COPILOT_PROXY_QUIET=1 減少背景呼叫。兩者都不是上游保證的安全上限。
風險自負;建議用個人帳號而非公司席位 (corporate seat)。
快速開始¶
copilot-proxy auth # 一次性:GitHub 裝置登入 (device login)(儲存 ghu_ token)
copilot-proxy start
copilot-model --auto # 有 Claude 就用 Claude,否則按 Sol/Terra/Luna 角色配置
claude-copilot-once # 單次 CLI 設定;自動啟動 proxy
claude-copilot --fast # 同上;catalog 有 fast sibling 時改走該模型
claude-copilot-once --specstory-mode exit # agent 退出後才匯出 history
codex-copilot-once # 單次 Codex session;auto 優先選 OpenAI
# claude-copilot / codex-copilot 都是相同的單次注入名稱
# 單次 planning preset。Codex TUI 開啟後仍需輸入 `/plan`;
# Codex 0.151.0 沒有啟動時指定 collaboration mode 的公開 flag。
codex-copilot -c 'plan_mode_reasoning_effort="ultra"' -c 'service_tier="fast"'
claude-copilot-once --fast --permission-mode plan --settings '{"ultracode":true}'
copilot-here on # 或者:釘選「這個專案」—— 之後直接跑 `claude` 就走代理
copilot-here off # 取消釘選 —— 回到真正的 Anthropic 後端
Claude 指令不要再加 --effort:啟動時的 effort pin 會阻止 session-only
ultracode 生效。Codex 的兩個 override 只影響本次啟動;開啟後輸入 /plan
才會正式進入 Plan mode。
運作原理¶
Claude Code ──Anthropic /v1/messages──▶ throttle/metrics shim (:4142) ─▶ copilot-api (:4141)
│ Claude:native Messages path
│ GPT:Anthropic → Responses 轉譯
▼
api.githubcopilot.com (你的 Copilot 訂閱)
Codex ──OpenAI /v1/responses──────────▶ 同一個 :4142 managed gateway
(provider/model 只透過本次啟動的 `-c` / `-m` 覆寫)
- Claude Code 只講 Anthropic Messages API(
/v1/messages)。 - fork 對 Claude id 使用 Copilot native Anthropic path;對 GPT id 把 Claude Code
request 轉成 Responses API。自
2.3.4起會把output_config.effort正確轉成reasoning.effort,並支援 GPT-5.6 request shape 與 reasoning state。 - Claude Code 透過
ANTHROPIC_BASE_URL被指向代理 —— 注入方式有兩種: 合併後的 CLI--settings(兩個 Claude launcher),或 gitignore 掉的./.claude/settings.local.json(copilot-here on)。見「設定分層設計」。
設定分層¶
Claude 設定優先序:user → project → local → CLI(managed policy 仍優先)。
兩個 Claude launcher 使用單一合併後的 CLI --settings;兩個 Codex launcher 使用
單次 -c。只有 copilot-here on 寫入 Claude local pin。泛用 copilot-run 仍是
環境注入,可能被 local settings 覆蓋。
Claude /model 的 Enter 可能寫入使用者頂層 model,因此 launcher 保留只針對
proxy-only model 的還原保護,其他設定改動保留。
Shell helpers¶
copilot-proxy [start|stop|status|stats|events|limiter|logs|quota|bench|update|...]¶
在 $COPILOT_PROXY_PORT(預設 4141)管理背景代理。
搭配 lazyclash v0.1.7+ 時,啟動與重啟會先檢查 endpoint 是否適合長期服務,再安裝
套件、輪替 log 或操作現有 backend。Shell/前景指令持有的臨時 SSH endpoint 會被
拒絕,複製 URL 也一樣;請選穩定的本機代理或明確的穩定 endpoint。解析歧義、認證
與 session 失效都會中止;只有確實尚未配置 proxy 時,auto 才能直連。明確穩定
覆寫會替換六個 proxy 環境變數,並移除 backend 繼承的 SSH 生命週期標記。舊版
lazyclash 保留相容 resolver,直到使用者明確升級。
整合會檢查 --consumer 能力,不只比對版本字串。固定設定
COPILOT_HTTP_PROXY=http://127.0.0.1:7897 時,生命週期檢查使用空的 target 設定,
仍讀取獨立的 SSH session registry,因此不受無關的 target/profile schema 錯誤影響。
auto 仍使用已配置的 target 探索,需要有效的 lazyclash 設定。Lazyclash 是可選依賴;
沒有相容介面時保留原本 resolver,但若繼承臨時代理 metadata,仍要求相容 CLI。
| 環境變數 | 預設 | 意義 |
|---|---|---|
COPILOT_PROXY_PORT |
4141 |
代理監聽的 port |
COPILOT_HTTP_PROXY |
auto |
GitHub /models 用的上游 HTTP proxy:auto 使用已配置代理,未配置才直連;never 清除 backend 繼承的 proxy 變數後直連;always 必須有代理;或明確 URL |
COPILOT_API_PKG |
未設定 | 最高優先的暫時套件覆寫;否則使用已保存的 exact selection,再 fallback 到內建 @jeffreycao/copilot-api@2.5.2。設著時 update/rollback 不會改 persisted state。 |
COPILOT_ASTRA_COMPACT_RATIO |
0.70 |
新 Astra session/pin 使用此比例乘以 live prompt ceiling,須滿足 0 < ratio <= 1;明確 client compact 設定優先。 |
COPILOT_PROXY_RATE |
15 |
僅舊版 copilot-api@0.7.0 使用;fork 沒有 rate limiter |
COPILOT_SHIM_MIN |
4 |
自適應並行 floor,也是啟動 limit |
COPILOT_SHIM_MAX |
8 |
自適應 ceiling;設成與 MIN 相同即可固定上限 |
COPILOT_PROXY_QUIET |
0 |
1 減少背景模型呼叫,但會犧牲部分 UX |
COPILOT_INSTALL_NOPROXY |
0 |
1 = 安裝時把 proxy 環境變數拿掉,跳過「bun 無法透過 proxy 解析」那 45 秒的卡頓 |
這些設在 ~/.shellrc.adhoc(或 per-shell secrets 檔案)。在 copilot-proxy auth
儲存 token 之前,start 會拒絕執行;啟動後最多等 ~20 秒直到代理能回應才返回。
第一次 start 會把釘選的套件安裝到 ~/.local/share/copilot-api/pkg(並寫下 spec
戳記),之後每次啟動只是直接執行那個 binary —— 不打 registry、不做啟動時解析。安裝時會
先用你當下的環境變數(在「registry 只能透過 proxy 連到」的機器上必要),卡住就改用拿掉
proxy 的重試;兩種嘗試都有 timeout 並會被殺掉,所以卡死的安裝再也不可能占住 bun 的全域
快取鎖而拖垮下一次。start 逾時的時候現在也會把自己啟動的 server 殺掉,不會像以前
那樣每重試一次就留下一個孤兒程序。
copilot-proxy quota [--json](whoami 仍是 alias)讀取即時 plan / quota;fork 上使用
與 /usage-viewer 相同的 /usage payload,代理未執行時不會假裝有離線 quota。
正常流量統計與安全 benchmark¶
managed Claude/Codex launcher 預設全部經過 shim。shim 啟用但啟動失敗時會 fail closed,
不會靜默 fallback 到 :4141;copilot-proxy shim off 是明確的 break-glass 直連模式。
在 upstream model bytes 尚未暴露前,每個進入 shim 的請求最多派送兩次:初次請求與
一次相同 body/model 的 replay。已確認的 body-read 408 user_request_timeout 及符合
條件的完整 HTTP 失敗可使用這次 replay;權限、認證、政策拒絕不重試。本地 timeout
或斷線表示上游工作狀態不明,不自動 replay。排隊或 backoff 中取消會移除請求;已派送
後取消則保留 permit,在背景讀完 backend response,直到工作結束才釋放容量。
Codex 的 request/stream retries 在 shim 模式為 0/0,明確直連模式為 3/1;
直接啟動和 SpecStory 路徑都保留後置 caller -c 的覆寫優先序。
所有成功的 streamed response(包括 grace window 內的快速回應)都必須是
SSE media type。慢 stream 在 queue/retry 期間持續收到 SSE comment;如果這段 pre-header
pipeline 最後得到 non-2xx 或不是 SSE 的 HTTP 200,shim 會依 Anthropic Messages 或
OpenAI Responses 的 terminal error shape 送出,不會把 raw JSON 接在 comment frames 後面。
Responses 會把 402 分類為 quota、429 分類為 rate limit、其他 post-commit 4xx 分類為
invalid prompt,5xx/transport failure 則分類為 server error,避免 Codex 重試不可重試的
錯誤。延遲 bare 401 只能用 invalid_prompt 作為 stopgap,因 Codex 沒有不可重試的 Responses
authentication code;只有尚未 commit 前的原始 HTTP 401 能保留 authentication semantics。
一旦 model bytes 已開始,後續 body error/stall 只會終止 stream,不再 retry。
422 cyber_policy 是 provider 的內容政策判定;shim 不 retry、不改寫,也不嘗試繞過。
Admission control 現在是自適應,而不是固定 semaphore。啟動時從
COPILOT_SHIM_MIN=4 開始,只有在確實有人排隊且持續成功時,才逐格增加到
COPILOT_SHIM_MAX=8。上游一旦回 403/429 就立刻降回 floor 並冷卻五分鐘;
Retry-After 最多尊重五分鐘。設定 MIN=MAX 可得到固定上限。live 調整不會
中斷正在跑的 stream,也不必重啟:
copilot-proxy limiter status
copilot-proxy limiter set --min 4 --max 8 --limit 6
copilot-proxy limiter reset # 回到啟動環境值;live 改動不持久化
寫入端點只接受 loopback,且要求 manager 專用 admin header。若要持久化範圍,
在 ~/.shellrc.adhoc export COPILOT_SHIM_MIN/MAX,之後再重啟 shim。
copilot-proxy stats week --model gpt-6-sol
copilot-proxy events day --limit 20 --json
copilot-proxy quota --json
copilot-proxy bench --model gpt-6-sol --runs 3 --max-output 256
stats 預設只算一般使用;benchmark 要用 --scope benchmark,兩者合併則用 all。
輸出包含 request/error/retry,並把 client cancel 與 upstream failure 分開;另有 queue、
upstream headers、first byte、stream、end-to-end 的
p50/p90/max,以及 token、AIU、output token/s。只有完成的 stream 且能對到 token event
時才計算吞吐量,否則為 null。
shim 不儲存 prompt 或 response body,只把 timing 寫到
$XDG_STATE_HOME/copilot-proxy/metrics.sqlite(WAL、保留 90 天)。fork 的 dashboard 則讀
$XDG_DATA_HOME/copilot-api/copilot-api.sqlite 的 token_usage_events;它不保存 quota
歷史,/usage 是即時 GitHub 資料。CLI 以 x-trace-id join 兩個 DB,並先彙總同一
request/model 的多筆 token event。stats、events 在代理與 shim 都停機時仍可離線讀取。
bench 會送真實 streaming Responses request,確實消耗 quota;限制為 1–10 runs、
32–2048 max output tokens、concurrency 1–4,並獨立標記,不污染正常使用統計。
套件供應與明確更新¶
warm start 只執行已安裝 binary,不接觸 npm,因此 registry 下架只影響新機、重裝或更新。 成熟 npm 套件一般下架門檻較高,但 maintainer、政策與 Copilot protocol 改變的風險仍存在; 它畢竟是非官方且 maintainer surface 小的套件。
update --check 優先查 canonical npm,失敗才帶警告使用 configured registry,且絕不安裝。
update VERSION 只接受 exact semver,驗證 SHA-512 後直接安裝同一份本地 archive,
再檢查落地版本與 help smoke test,全部通過才切換 live prefix。受信任 selection 的首次
安裝也走同一條 artifact 路徑;舊 manager lock 不再被當成其他 runtime bytes 的證據。
內建目標為 @jeffreycao/copilot-api@2.5.2,
SRI sha512-bMVpuniekbKKq0LMtmZZJKjDVpaOODAHs19akwkP/hyGfgcx+YK0X22jfB46lQb0p9EoywDrJMyTcAfLr18jEQ==。
已有 selection 的 host 仍需明確執行 copilot-proxy update 2.5.2。
更新會保留私有的 pkg.rollback.* generation,包含原套件與 selection、backend config、
一致性的 backend/metrics SQLite 快照、當時已部署的 wrapper/shim 及相關啟動設定。
selection JSON 仍在 $XDG_STATE_HOME/copilot-proxy/package.json,旁邊的 rollback
檔案指向可回復的 generation。Unix 原本正在跑的服務會重啟;candidate 啟動或 selection
寫入失敗時,自動還原 package/config。
copilot-proxy rollback 可離線執行,還原套件、selection 及原本的
responsesTransport/upstreamTransport 欄位,保留其他目前設定、新憑證與更新後用量。
被撤回的套件及更新後 config 會留作診斷;SQLite 快照不會自動蓋回目前資料庫。
指令會印出 deployment 快照位置,matching wrapper/shim/session backend 與啟動設定須另外還原並 reload
shell。快照是 update 當下磁碟上的檔案;若要回到更早的 shim,須在部署新版 wrapper
之前先保留舊檔。
Ubuntu 隔離 dogfood 使用 scripts/copilot_proxy/dogfood.py prepare,傳入 --root、
絕對路徑 --node/--bun、--shim、--archive-2-3-4、--archive-2-5-2;
run 使用同一 root 與 runtime 路徑。trial 只在 Ubuntu 本機複製其既有憑證,使用獨立
config/SQLite/metrics 並記錄 PID,不使用廣域 stop helper。預設
--expected-host David-Ubuntu 會在建立 trial 目錄、複製憑證前檢查主機;送 inference 前
也會確認 listener PID 就是所啟動的 child。總上限為 12 次邏輯請求、
24 次實際派送;host 確實需要時才加 --proxy-env。baseline/candidate 使用相同
shim/client policy,切換 immutable trial slot 可回退完整配對,不影響正式安裝或 Mac session。
初輪 client 執行失敗時,verify-candidate 只能使用原本預算尚未消耗的部分,而且只能
執行一次;預算以 exclusive create 保留,重跑不會重置。預設 client sandbox 是
workspace-write。若 host 無法初始化 bubblewrap,可明確指定
--sandbox-mode danger-full-access,沿用一般 launcher 的權限模式;它只影響這個
測試程序,但沒有 OS filesystem sandbox,trial 目錄此時只是工作範圍約定。
不同 client policy 的結果只能作功能驗證,不能當成控制條件相同的效能比較。
trial 目錄包含私密憑證與設定副本,不可整包分享或提交;詳見
實作與 Ubuntu 實測紀錄。
copilot-proxy doctor [--live](別名:test)¶
診斷整條路徑,任何一項失敗就以非零狀態結束。預設唯讀;加上 --live 會多送一個真實的
POST /v1/messages(max_tokens: 1、挑一個非 [1m] 的 chat model),會消耗一個 quota
單位,但那是唯一能驗證 streaming 的檢查。
status 不送推理請求:它顯示 shim admission,以及最近觀察到的認證結果;沒有近期
證據時顯示 unverified。token 檔案只證明已儲存憑證。doctor --live 會從有長度上限的
錯誤回應區分 shim 隔離、IDE token 過期與儲存憑證被拒絕,分別提示受控重啟、重啟後
重試,或 copilot-proxy auth 後重啟;不會印出 token。
檢查順序:前置工具(bun/curl/jq)→ 套件 (package) → token 檔案 → 代理與
throttle shim 是否存活 → 安裝殘留(stale installer)→ 模型→ 上游連線 →
本機代理 / VPN → Codex Apps → live inference probe。
套件 (package) 這一段報告釘選的 spec 是否已安裝在 prefix 裡、binary 在哪。顯示
「未安裝」不算失敗 —— 下一次 start 會自動裝一次。
安裝殘留(stale installer) 是陷阱
那個坑的保險絲:平常若還有存活的 bun add … copilot-api,就代表某次安裝卡死了(bun 透過
socks proxy 解析相依會卡住),而且正占著 bun 的全域快取鎖。現在安裝流程自己會設 timeout
並殺掉卡死的程序,所以這一項應該永遠是空的;萬一真的觸發,doctor 會印出「清掉再重啟」的
一行指令。
其中「模型」這一段才是這個指令的價值所在。它把代理目前提供的模型清單,跟 GitHub
此刻真正提供的清單相比對 —— 這是唯一能區分 400 model_not_supported 兩種成因的方法:
| 代理有 claude? | 上游有 claude? | 判定 | 修法 |
|---|---|---|---|
| 沒有 | 有 | 快取過期 (stale cache) | copilot-proxy restart |
| 沒有 | 沒有 | 組織政策停用 Anthropic | 重啟沒有用 |
它會同時驗證 main 與 Fable/Opus/Sonnet/Haiku aliases,包括 [1m] alias。某個過期的
background role id,會造成普通對話成功、workflow 卻 400。沒有 Claude entitlement 在可用的
OpenAI profile 存在時是 warning,不是整體失敗。
上游連線會同時探測 api.enterprise.githubcopilot.com 與 api.githubcopilot.com,直連
以及(若有設定)走 macOS 系統代理,所以 Clash/mihomo 規則黑洞掉其中一個 host 時會立刻
顯現。未帶認證的 400/401 算「連得到」—— 只有連線/讀取失敗才算故障。doctor 絕不會印出你的
token:憑證是透過 curl -K -(stdin)傳入,而非 argv,因為 argv 可被 ps 讀到。
使用 --live 時,Codex Apps 段落還會對
https://chatgpt.com/backend-api/wham/apps 分別做 direct 與 detected HTTP
proxy probe。這個 GET 不消耗 model inference quota,可把 codex_apps 啟動中斷與
localhost Copilot 路由拆開診斷。任何真實 HTTP status(包含 auth rejection)都代表
網路可達;timeout 與 TLS certificate failure 會分開顯示。codex_apps 是遠端
ChatGPT MCP,不是只供 Apple Silicon Codex Desktop 使用的 bridge。
claude-copilot-once / claude-copilot —— 單次啟動¶
兩個名稱完全相同:自動啟動 proxy,透過單一 CLI --settings 注入 role profile
與 live compact ceiling。需要 jq、Python 3.11+(或 uv)。既有專案 pin 只會在
本次程序被覆蓋,檔案不改寫,也不詢問更新;持久更新另用 copilot-here on。
Claude 2.1.281 實測重複 --settings 只採用最後一次,因此 helper 先讀取有效的
使用者 JSON/檔案,再與 proxy defaults 合併成一份 JSON。明確使用者值優先,
ultracode 等其他欄位保留;model/permission 先解析,再計算 context 與 bypass。
--fast 用 live fast sibling,找不到就警告並保留標準模型。Wrapper flags 必須
放在 agent 參數之前;--fast 與 -- 邊界不能合用。使用者 /model 防汙染保護仍在。
錄製方式:--specstory-mode run|exit|off¶
四個 Claude/Codex 啟動名稱都支援:
| 模式 | 行為 |
|---|---|
run(預設;--specstory) |
specstory run 即時錄製;未安裝時 raw fallback |
exit |
agent 原生 terminal;真正退出後執行同專案、同 provider 的 sync |
off(--no-specstory) |
原生 provider command,不新增錄製要求 |
exit 避免本 launcher 在 agent commit 時持續改寫 history。第一版匯出同專案、
同 provider 的所有已保存 session,不猜最新 session。同組的受管理 launcher 還在
執行時,延後到最後一個退出;同專案 sync 序列化。其他獨立 recorder 仍可能寫入。
正常退出、錯誤退出、Ctrl+C 都先等 agent 結束。Sync 不受連按 Ctrl+C 中斷,
最多等待 30 秒;失敗時留下私人 XDG state 待辦,下次同專案啟動、沒有同 provider
程序仍在執行時補做。因此 off 也可能補做先前的 pending export。Agent exit status
保留,sync 結果另外顯示。Wrapper 被 SIGKILL/主機斷電無法立即收尾,未落盤的
native history 無法補回。exit 需要 SpecStory,拒絕背景/detach、新 worktree 與
不保存 session 的參數。這些模式都不自動 commit。
run/exit 讀取專案優先、使用者其次的 SpecStory provider command;完整保留
argv、引號、空字串及 prompt 邊界。明確本次 permission 優先於 provider command,
再補齊缺少的 bypass 預設。off 不繼承 command。Sync 沿用輸出路徑、redaction
與 cloud 偏好。持久 pin 或 resume 舊 Copilot session 仍可能使用 proxy;普通新開
specstory run 在沒有 pin 時沿用原本 provider。
copilot-run <cmd...> —— 泛用環境變數注入器¶
泛用環境注入:自動啟動代理,然後帶著代理 env 執行 任意 指令。 適合其他 Anthropic 相容工具或自訂的 specstory 呼叫:
注入的 env 區塊來自 _copilot_env_json_for_model --live —— 和 copilot-here on 寫入、
_copilot_here_drift 比對的是同一份 single source of truth,兩邊不可能再對「有哪些 key」
產生分歧。--live 是唯一差別:它用 _copilot_client_base(此刻這個 process 該連的位址)
解析 ANTHROPIC_BASE_URL,而不是設定檔會記錄的 pinned base。
copilot-run specstory run claude # 環境注入;local pin 仍可覆蓋它
copilot-run claude --resume # 裸 claude,不經 specstory
codex-copilot [--no-specstory] [codex args...] —— 一次性 Codex session¶
codex-copilot 與 codex-copilot-once 是完全相同、零持久化的 launcher。
它們會視需要啟動 gateway/shim,並用 CLI -c overrides 把自訂的
copilot_api Responses provider 傳給 Codex;不會編輯 ~/.codex/config.toml
或 .codex/config.toml,所以 plain codex 仍走原本 provider。
Codex 0.162.1 的這些 command-line configuration overrides 需要 embedded
app server。啟動時會顯示:Running without the shared background server:
command-line configuration overrides (-c, --enable, --disable, or --search)
requires embedded mode. 因此 launcher 的 /daemon 會顯示沒有連上 shared
background server。跨 session 的 Command Center(codex agents / /agents)
與目前 session 的 /subagents picker 是不同介面;開啟 multi-agent 不會讓
launcher 自動加入 shared daemon。已用不送 prompt 的啟動檢查確認。
為支援 GUI 或 plain CLI 恢復舊 thread,chezmoi 會在 user config 缺少時補上
[model_providers.copilot_api]。Thread 會保存 provider ID,但不會保留 launcher
臨時傳入的完整定義。預設指向 http://localhost:4142,並使用本機 gateway 接受的
字面值 dummy bearer,因此 GUI 不必繼承 GITHUB_COPILOT_API_KEY。這只註冊
provider,不改全域預設。既有 provider 定義會保留;使用自訂 port 時,須同步
調整此表的 base_url。
Codex 預設走 localhost:4142 shim。這一層除了限流與 measurement,也會正規化
Codex mcp_list_tools Responses item 裡的空白
description。MCP server 與原生 Codex path 可以省略描述,但 GitHub Copilot 會以
Invalid 'input[0].tools[0].description': empty string 拒絕請求。shim 只補這些
tool definition 欄位,不改 prompt、schema 或 tool name。
目前 Codex 會以 zstd 壓縮這些請求;shim 只解壓需要修補的 Responses body,改以
普通 JSON 轉送,並移除已不適用的 content-encoding header。
明確執行 copilot-proxy shim off 也會略過這個相容性修補與 metrics,因此只適合診斷逃生。
這是與 Claude Code copilot-model --auto 分開的 picker:後者保持
Claude-first,只有這個 Codex launcher 是 OpenAI-first。
- 呼叫者明確傳入
-m/--model時永遠優先。否則使用與copilot-model相同的 tier-aware policy,但 Codex 的 vendor 順序是 OpenAI/Codex、Claude、grok、Gemini、 其他 chat model;embedding、disabled、picker-hidden 與-fastmain candidates 會排除。 - Launcher 以已安裝 Codex 的
codex debug models --bundleddescriptor 與同一份 live Copilot snapshot 產生 provider 專用model_catalog_json。只對精確相符的 model ID 把context_window與max_context_window更新為 live context;reasoning、tools、 prompts 及其他 metadata 全部保留 bundled 值。單傳-c model_context_window=...不夠,Codex 仍會以 descriptor 的max_context_window夾住它。 - 快取位於
$XDG_CACHE_HOME/copilot-proxy/codex-models/(預設~/.cache/copilot-proxy/codex-models/),key 包含 Codex 版本、cache schema 與 canonical live model-to-context map 的 hash;JSON 驗證後才原子發布。Prompt/output limits 不參與 此 cache key,因為每次啟動都會重算 compact override;provider 共用的~/.codex/models_cache.json不會被修改。 - 呼叫者明確傳入
-c model_catalog_json=...時跳過 managed catalog 產生,由呼叫者負責 該 catalog 的 descriptor 與上限。否則選gpt-6-sol或gpt-6-luna卻沒有精確的 bundled descriptor 時,launcher 會停止並提示升級至 Codex 0.156.1 以上,不會複製 其他模型的 reasoning/tool metadata。詳見 context 被夾住的調查。 - Claude/Gemini fallback 經 Responses Lite 轉譯;基本 tools、compaction、
multi-agent orchestration 可用,但這條路不支援 Responses
tool_search,因此 auto 會把 native Responses OpenAI models 排在 Anthropic 前。 - Launcher 會啟用 gateway-backed remote compaction,並排除依賴不可用
tool_search的mcp__codex_apps__sitesnamespace;使用者後續傳入的-c仍可在單次呼叫覆寫這兩項。 - 有安裝 SpecStory 時預設執行
specstory run codex,並保留實際生效的codex_cmd(project config > user config > 裸codex),再附加 provider、 model 與使用者參數。Raw--no-specstorypath 會自行維持同樣的--ask-for-approval never --sandbox danger-full-accessposture;若使用者已指定 approval/sandbox policy,則完全尊重該設定,不重複注入。
Codex 刻意不提供 copilot-here 對應物。官方 Codex config 允許 trusted project
使用 .codex/config.toml,但 project scope 會忽略 model_provider、
model_providers、provider auth 等 host-owned metadata。因此顯式 launcher 才能在
不改 user-wide default 的前提下,提供 project/session 邊界。參考
configuration reference 與
configuration basics。
實驗性 direct provider(正式 helper 不使用)¶
以下設定會跳過 localhost;只有帳號 credential 能直接驗證到相符 Copilot endpoint 時才可用:
[model_providers.copilot-enterprise]
name = "GitHub Copilot Enterprise"
base_url = "https://api.enterprise.githubcopilot.com"
wire_api = "responses"
http_headers = { "Copilot-Integration-Id" = "vscode-chat", "Editor-Version" = "vscode/1.99.0", "Editor-Plugin-Version" = "copilot-chat/0.0.1", "User-Agent" = "GithubCopilot/1.0" }
[model_providers.copilot-enterprise.auth]
command = "gh"
args = ["auth", "token", "--hostname", "github.com"]
timeout_ms = 5000
refresh_interval_ms = 300000
這只是範例,不是受管設定。在本次 EMU/Enterprise 帳號實測,裸
gh auth token 對 enterprise API 回 421 Misdirected Request,而 Copilot token
exchange 回 403;copilot-proxy auth 保存的 token 經正常短效 Copilot token
exchange 則可用。Endpoint 也由 exchange 結果決定,hard-code personal/enterprise
host 不具可攜性。因此正式 helper 沿用已認證的 localhost gateway。
copilot-here [claude] [on [MODEL] [--yes]|off|status]¶
明確的持久 Claude 專案 pin,會影響後續普通 claude/specstory run claude。
on:全新無衝突就建立,相同設定不改寫;已有 pin 不同時先顯示差異,詢問更新, 預設保留。--yes可供腳本明確更新;非互動且未指定時保留原檔並回傳非零。 憑證值不會出現在預覽。- 已 tracked/staged 的檔案一律拒絕寫入。既有未忽略檔案需要確認整份檔案會被
本機忽略;使用 Git 查詢
info/exclude,支援 worktree 與巢狀專案。 - 新 pin 私下保存受管理欄位的原值;
off只還原仍符合上次寫入值的欄位,保留 其他與後續手動修改。舊 pin 沒有 journal 時沿用移除 managed keys 的語意。 status顯示 drift 與 plain-Claude launch audit;保留plansDirectory等欄位。
Codex 0.156.1 實際啟動會忽略 project-local model_provider/model_providers,
所以 copilot-here codex on 只說明限制,不寫無效設定。這是版本相關的限制,
原始警告與升級後重測方法
已記為 pitfall。
copilot-model [<id>|-l|-L|-c|--auto|--why|--json]¶
切換釘選的 Copilot 模型。需要 jq。寫入目標 —— 永遠不是會 commit 的
.claude/settings.json:
- 目前專案的
copilot-here是 ON → 編輯./.claude/settings.local.json。 - 否則 → 寫入全域狀態檔
~/.local/state/copilot-proxy/model,由claude-copilot、copilot-run與下一次copilot-here on讀取。 ($COPILOT_CLAUDE_MODEL可覆寫狀態檔;最終預設是gpt-5.6-sol[1m]。)
行為:
- 模糊 id:
copilot-model opus-4-8會解析成claude-opus-4-8;點號寫法也會被 正規化(opus-4.8一樣可用)。 [1m]是只給 Claude Code 看的 context 提示。helper 會依 live/v1/models的max_context_window_tokens對每個 provider 自動決定,不再靠模型名寫死。- Auto-compact 另外使用
max_prompt_tokens,透過CLAUDE_CODE_AUTO_COMPACT_WINDOW設定真正的 prompt ceiling,再讓 Claude Code 沿用約 95% 的預設觸發比例;不會硬塞固定 percentage。缺少明確 prompt limit 時才以 context 減 maximum output 推導,copilot-model -c會顯示結果。 - 會對照即時的代理
/v1/models驗證(離線時仍提供靜態 discovery 清單供手動選擇); 打錯字與不明確的前綴會被拒絕,--auto絕不從離線清單寫入 pin。 --auto/-a必須讀到 live catalog。先排除 policy-disabled、picker-hidden、 embedding-only 與-fastentries,再依 vendor 順序 Claude > OpenAI > grok > Gemini。 同一 vendor 讀 Copilot 的model_picker_category(powerful > versatile > lightweight), 只在勝出的 tier 內比較世代。已知 id 與同世代 sibling 仍由 curated allowlist 決定; 未知但更新世代的旗艦可以不等 dotfiles 更新就勝出。舊 proxy 沒有 category metadata 時會退回既有 allowlist,不會猜測。Auto 候選只取決於該次 live catalog,不受先前的 model、project pin 或 state file 是否存在影響。方案標籤(restricted_to)與價格 只供診斷,不代表目前帳號的 entitlement。Main、衍生角色、--why、details 的 auto marker 及未指定模型的codex-copilot都使用同一組可選候選。- OpenAI 的世代與 capability tier 是兩個獨立維度。已知且 selectable 的 OpenAI main
優先順序是
gpt-6-astra>gpt-6-sol>gpt-5.6-sol;gpt-6-luna保留在 lightweight/Haiku tier,不會擠掉旗艦。GitHub 已在 2026-09-22 宣布 GPT-6 Sol 與 Luna 上線。 Terra 仍為gpt-5.6-terra;較低 role 的 Luna 從 selectable live entries 優先選gpt-6-luna,再退到gpt-5.6-luna。 向後相容的離線預設仍是gpt-5.6-sol[1m],不代表 entitlement 保證。copilot-model -L與--json提供 live plan/policy metadata,但billing.restricted_to不能證明目前 billing target 的存取資格;若後續被 entitlement 拒絕,仍需手動改選其他 served model。下方 context/compact 數值是 2026-09-23 live gateway snapshot,不是對所有帳號寫死的保證。 - 無參數 →
fzf選單;-c印出 main 來源與完整 role profile。-l是可 pipe 的裸 id 清單;-L/--details顯示 live tier、price category、context/output limits、reasoning 範圍、fast sibling、可用方案與 picker state;*是目前模型,->是具權威性的 auto pick。Rows 為方便比較而按 tier/generation 分組,顯示順序不取代 vendor/allowlist policy。 --why只解釋而不寫入;--auto --why先解釋再寫入。--json原樣輸出 live/v1/models。它與 shim 的/_shim/fast-routing、以及 Codex 專用的磁碟檔~/.cache/copilot-proxy/codex-models/內 provider 專用 catalog 是三種不同資料。- 目前 OpenAI profile:Main/Fable/Opus = Astra(或手動指定的 main)、Sonnet = Terra、 Haiku/background = GPT-6 Luna(再退到 GPT-5.6 Luna)。缺少某層時退回 main,不會寫入未 served 的 id。Grok 不維護 靜態版本 allowlist;使用其最高 tier 中最新的 served model。
- global state 仍是向後相容的單行 main id;wrapper 在啟動時依 live catalog 產生角色 profile。Local pin 會寫入完整角色組。切換後只需重開 Claude Code,不用重啟 proxy。
若舊版 auto 在已有旗艦的 catalog 下仍存入 gpt-5-mini,部署並 reload 修正後的
shell fragment,再執行一次 copilot-model --auto 即可重新選擇。不必刪除 state file 或
project settings:舊模型不再限制新的候選。--why 可以不寫入地預覽同一個選擇;
真正的 entitlement 拒絕仍由 gateway 負責。
建議順序:
copilot-proxy start
copilot-model --why # 解釋自動選擇,不寫入
copilot-model -L # 查看 live tier/price/context/plan metadata
copilot-model --auto # 儲存 main/更新目前 project pin
copilot-model -c # 檢查完整角色組
# 明確選擇:copilot-model gpt-6-sol;codex-copilot -m gpt-6-luna
copilot-here on # 黏著本專案,或改用 claude-copilot-once
claude-copilot-once 透過 CLI 注入同一組 profile,不改寫既有 pin;用
copilot-model --auto 或 copilot-here on
更新它。
注入的 env(兩層設定的內容相同)¶
範例:明確選 gpt-6-sol,較低 roles 使用 selectable 的 Terra/Luna。
{
"env": {
"ANTHROPIC_BASE_URL": "http://localhost:4141",
"ANTHROPIC_AUTH_TOKEN": "dummy",
"ANTHROPIC_MODEL": "gpt-6-sol[1m]",
"ANTHROPIC_DEFAULT_FABLE_MODEL": "gpt-6-sol[1m]",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gpt-6-sol[1m]",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-5.6-terra[1m]",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-6-luna[1m]",
"ANTHROPIC_SMALL_FAST_MODEL": "gpt-6-luna[1m]",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "872000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
}
}
ANTHROPIC_AUTH_TOKEN 會被代理忽略,但必須設定。
2026-09-23 live gateway snapshot 要分開看 provider 上限與 client compaction:
| 模型 | Provider context | Prompt ceiling | Maximum output | Launcher compact budget |
|---|---|---|---|---|
gpt-6-astra |
1,050,000 | 1,050,000 | 128,000 | 735,000(70%) |
gpt-6-sol |
1,000,000 | 872,000 | 128,000 | 872,000 |
gpt-6-luna |
1,000,000 | 872,000 | 128,000 | 872,000 |
明確的 live max_prompt_tokens 優先,即使它不等於 context 減 maximum output;只有
缺少 prompt 欄位時才做減法。[1m] 仍只給 Claude Code 作為顯示/context 提示,raw API
ID 不帶此後綴。Sol/Luna 不套用 Astra 的 70% 實驗設定。
Astra 與其 -fast sibling 使用
floor(live prompt ceiling × COPILOT_ASTRA_COMPACT_RATIO),預設 0.70,因此
1,050,000 變成 735,000。比例須滿足 0 < ratio <= 1;1 恢復完整 prompt budget。
Claude 保留 100,000-token 下限,Codex 的結果須為正數。這是減少大型 compact 上傳的
保守設定,尚不能宣稱修復遠端 body-read 408。有效的明確
CLAUDE_CODE_AUTO_COMPACT_WINDOW 與 -c model_auto_compact_token_limit=...
仍優先,但 Codex 仍會套用自身的 compact 上限。
配合 provider 專用 descriptor,Codex 0.156.1 以 context 的 95% 計算 usable input, 並以完整 context 的 90% 限制 auto-compact:
| 模型 | Codex usable context | 實際 Codex auto-compact trigger |
|---|---|---|
gpt-6-astra |
997,500 | 735,000 |
gpt-6-sol / gpt-6-luna |
950,000 | 872,000 |
Trigger 是 min(launcher compact budget, 90% × context),不是把已打折的 usable
context 再乘 90%。計算依據見 Codex 的
model 計算實作。
Claude 另把注入的 budget 乘上自身約 95% 的門檻(Astra 約 698,250,Sol/Luna 約
828,400)。換模型後請刷新 managed pin 並重新啟動 wrapper;session 內切換不會重算
啟動時的 override。既有安全 pin 與 active session history 不會被改寫,這些設定也不改
VS Code 的 context picker。
缺少 live metadata 時會警告並保留 client 原有 fallback。
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 可減少背景流量(有助於速率限制)。
不要 把這段貼進會 commit 的 .claude/settings.json —— 改用 copilot-here on。
Claude Code 功能相容性¶
關鍵分界是本機編排 (local orchestration) 與 Anthropic cloud service:
| 功能 | 透過 Copilot + GPT | 說明 |
|---|---|---|
| CLI、tools、hooks、skills、memory、plugins、MCP、checkpoints、sandboxing | 可以 | 這些是本機 Claude Code 功能;GPT 收到的是經過轉譯的 Claude prompt/tool schema,行為可能不完全一樣。 |
| subagents、dynamic workflows | 可以 | 不強制 CLAUDE_CODE_SUBAGENT_MODEL,保留 workflow/frontmatter 的 routing。Workflow 文件 |
ultracode |
2.3.4 可以 |
Ultracode 是 xhigh effort + dynamic workflows,不是單獨模型;2.3.4 會把 effort 傳到 GPT-5.6。 |
| thinking/reasoning | 轉譯後可用 | GPT 使用 Responses reasoning,不是 Anthropic-native thinking semantics。 |
| Fast inference | catalog 有提供時可用 | Codex /fast 由 shim 轉成 Copilot 的獨立 -fast sibling;Claude Code 使用 claude-copilot --fast。沒有 eligible sibling 時會警告並退回 standard。 |
| Web Search、auto mode、MCP tool search | 依 provider | 由 base-URL gateway 與 Copilot endpoint 能力決定;non-first-party tool search 可能需要額外 bridge/plugin。 |
| Ultrareview、Remote Control、Chrome、cloud Code Review、routines、web/mobile/Slack session | 不可以 | 這些需要 Claude.ai auth/cloud identity;local API gateway 無法取代。ultrareview 和 ultracode 無關。 |
官方參考:feature availability、 model configuration、 gateway protocol、 Ultrareview。
陷阱 (gotchas)(這些都花了實際 debug 時間)¶
start used to hang at "Resolving dependencies" behind a socks proxy¶
(start 曾經卡在「Resolving dependencies」—— socks proxy 造成)
已修正(runner 啟動時不再解析套件),但仍值得理解,因為它的失敗樣態極具誤導性 ——
而且在有 proxy 的機器上,任何其他用 bunx 的工具都會踩到同一個坑。
start 以前跑的是 bunx <pkg> start,而 bunx 每次啟動都會重新解析套件。
bun 透過 socks ALL_PROXY 解析相依會無限期卡住 —— 但 curl 走同一個 proxy 連
registry 卻不到 0.5 秒就回來。所以每一項明顯的檢查都會過,沒有任何線索指向安裝程序。
你只會看到:
copilot-proxy: did not come up in time — check 'copilot-proxy logs'.
$ copilot-proxy logs
nohup: ignoring input
Resolving dependencies
有兩件事把「一次卡住」變成「永久卡死」:卡死的 bun add 會占住 bun 的全域安裝快取鎖,
所以下一次 start 也卡在那把鎖上;而 start 逾時後直接 return、沒有殺掉自己啟動的程序,
於是每重試一次就多留一個孤兒(實際上疊了 5 個),而且沒有任何一個綁上 port。
現在 runner 會把釘選的套件只安裝一次到 ~/.local/share/copilot-api/pkg 並直接執行該
binary,所以熱啟動完全不碰網路。安裝本身有 timeout 且逾時會被殺掉,先退回「拿掉 proxy」
的重試;若 Bun 明確回 UNKNOWN_CERTIFICATE_VERIFICATION_ERROR,最後再改用 npm 的 CA stack。
TLS 驗證仍保持開啟,這是在繞過已觀察到的 Bun/Node CA-store 差異,不是關閉憑證檢查。
注意這裡的 registry 是 npmmirror(國內鏡像,為了 GFW 速度設在 ~/.bunfig.toml)
—— 把它繞進 proxy 一點好處也沒有,而那正是弄壞 bun 的原因。
完整事後檢討與可 grep 的原始症狀:
pitfalls/copilot-proxy-start-hangs-at-resolving-dependencies.md。
模型清單只在啟動時抓一次 —— 直連被 geo-filter / 一次抓壞,整個 session 就毀了¶
copilot-api 只在行程啟動時向 GitHub 抓一次 /models,然後快取整個生命週期。
有兩種看起來一樣的壞法:
- Egress geo-filter(離開 TUN 後最常見) — 同一組 token,直連/CN 出口 Claude = 0,
走海外 Clash 節點 Claude = 8。Node / OpenCode 不讀 macOS System Proxy;以前
Clash for Windows 的 TUN / Mixin 把所有 TCP 抓進去所以沒感覺,換成只有 System Proxy
(Clash Verge)就會啟動成「沒有 Claude」的清單。預設
COPILOT_HTTP_PROXY=auto會在 偵測到本機 proxy 時自動帶--proxy-env。 - 節點抖一下 — 那一次請求碰上壞 hop,快取成截斷清單。
線索在啟動 banner:
copilot-proxy doctor 會做 直連 vs 走 proxy 的上游 A/B,並對照代理已服務的清單,
避免再把 geo-filter 誤判成「組織停用 Claude」。完整說明:
pitfalls/copilot-api-caches-degraded-model-list-at-startup.md。
settings.local.json 的 env 蓋過 shell env¶
泛用 copilot-run 只有繼承的環境變數,因此可能被 Claude local settings 覆蓋。
兩個 Claude launcher 改用單一 CLI --settings(Claude 2.1.281 已驗證),可以
只對本次程序覆蓋 pin。持久設定更新另用 copilot-here on。
OpenAI 模型思考時整條線是靜默的 —— shim 用心跳把 socket 撐住¶
copilot-api 不會提早開啟 SSE stream:對 :4141 用 gpt-5.6-sol 實測,
response headers 在 8.11s 才到、第一個 body chunk 在 8.12s,也就是 reasoning
model 的整段思考時間都耗在同一個 fetch() 裡,線上一個 byte 都沒有。真正的
Anthropic stream 會用週期性的 ping event 蓋住這段,這個 gateway 一個都不送。
再加上 shim 的併發佇列(自適應範圍 4..8;超過當下 live limit 的請求會排隊),
路徑上任何一個 idle timer 都可以合法回收一條其實健康的連線。agent 這端
不會收到錯誤、只會一直等,最後只能 Esc + continue。
因此對「client 要求 streaming」的請求,shim 會在靜默達
COPILOT_SHIM_PING_AFTER_MS 後自己先 commit text/event-stream response,
並每 COPILOT_SHIM_PING_MS 送出 SSE comment frame
(: copilot-shim keepalive,符合規範的 parser 一律丟棄,所以對 Anthropic 與
OpenAI SDK 都是隱形的),直到真正的 bytes 出現 —— 排隊時間、思考時間、串流中途
的 reasoning 間隔都一併涵蓋。Backend 擁有正常的 300 秒 header/inactivity deadline,
shim 使用 330 秒有限 fallback。本地 timeout 或 backend 連線中斷會保留 unknown
permit 並回報需要 recovery,不自動重播可能仍在執行的工作。Health 會顯示
active/draining/unknown、artifact 版本與 deadline 順序;全數容量為 unknown 時會立即
回報失敗。未完成 lease 寫入 metrics.sqlite.admission.json,單獨重啟 shim 不能清除。
等 tracked work 結束後,copilot-proxy restart 會確認 shim/backend 都已退出才清除
marker。PID 檔遺失時也會盤點相符的程序,不會把缺少 PID 檔當成程序已退出。
start、status、doctor 會明示 admission 隔離,避免把仍監聽但無法推理的 shim
當成健康。不要手動刪除 marker。Codex 在本機 503 後顯示的「high demand」是通用訊息;
shim 回應中的 admission is quarantined 才是這次故障的識別訊息。
只有部分容量為 unknown 時,health 仍可能回報 admission_available=true;
status/doctor 會標示 degraded,而非 blocked。受控重啟可回收這些容量。
Doctor 使用與 start/restart 相同的 service proxy resolver,lazyclash 設定錯誤
會回報失敗,不再顯示「none detected」。408 user_request_timeout 表示上游讀取
request body 逾時;重啟清除本機保留狀態,不代表上游上傳問題已修復。
| Env | 預設 | 意義 |
|---|---|---|
COPILOT_SHIM_MIN |
4 |
自適應並行 floor,也是啟動 limit |
COPILOT_SHIM_MAX |
8 |
自適應 ceiling;設成與 MIN 相同即可固定上限 |
COPILOT_SHIM_PING_MS |
15000 |
心跳間隔;0 關閉 |
COPILOT_SHIM_PING_AFTER_MS |
10000 |
提早 commit SSE 前容忍的靜默時間 |
COPILOT_SHIM_STALL_MS |
330000 |
正數、有限的外層 header/inactivity fallback,須大於 backend deadline |
COPILOT_SHIM_RETRIES |
1 |
最多 replay 一次,每個 ingress 最多派送兩次 |
COPILOT_SHIM_HOST |
127.0.0.1 |
監聽位址;預設只綁 loopback |
快速 non-2xx 回應保留真正的 status code 與 headers,因此
400 model_not_supported、401 IDE token expired 仍能正確回報。對 stream:true
的快速 HTTP 2xx,只有 SSE media type 才會接受;headers 尚未 commit 時,shim 仍可直接
拒絕 protocol mismatch。SSE response 一旦 commit,之後才出現的 non-2xx 只能以
protocol-native terminal event 送出,所以不要把 COPILOT_SHIM_PING_AFTER_MS 調成 0。
對 /responses,只有 response.completed 算 SSE 成功;response.failed、
response.incomplete 與缺少終止事件各自分類,即使 HTTP 200 也不算成功。
非串流 JSON 依協定結果分類。Metrics 記錄 compact 分類與來源、request bytes、attempt、
timeout owner、drain outcome,不儲存正文或憑證,也不偽造完成事件。
若 fork log 同時出現 WebSocket error,請把 data dir config.json 裡的
useResponsesApiWebSocket 設成 false 後 restart;fork 的 WebSocket 失敗不會自動
fallback 到 HTTP。0ms stream 的指紋與已驗證 A/B 步驟見
pitfalls/copilot-responses-stream-closed-before-completed.md。
FleetView 顯示 0 tok 不是這個 bug 的證據 —— 那個計數器只在 agent 結束時才
回填。先去看 agent-<id>.jsonl 的 assistant 筆數有沒有在長。完整診斷:
pitfalls/copilot-proxy-openai-model-silent-stall.md。
Fast mode 是 catalog model route,不是轉送 Copilot tier¶
OpenAI Responses API 以 service_tier="fast"(歷史上也用 priority)表示 Fast Mode;
目前釘選的 Copilot fork 會移除這個欄位,而 GitHub Copilot 則把 fast inference
advertise 成另一個 model id,例如 eligible 的 <standard>-fast sibling。前置 shim
現在會銜接這兩種表示法:每五分鐘由 live /v1/models catalog 推導 standard-to-fast
pair,把 Codex /fast request 改寫到 sibling,再於 fork 收到前移除不支援的 tier。
參考 OpenAI Fast Mode guide。
Claude Code 原生 /fast 在這個 custom Anthropic gateway 仍不可用,因此要使用
claude-copilot --fast(也可經 claude-copilot-once 傳入)。Wrapper 會用 session-only
--model override 選同一份 live-catalog sibling。Discovery 失敗時保留 last-good cache;
完全沒有 eligible sibling 時,兩條路徑都退回 standard model 並送出去重警告。
copilot-proxy status 與 doctor 會顯示 routing state;明確執行
copilot-proxy shim off 也會關掉這項轉譯。不會為了測試 Fast 可用性自動送出會消耗
quota 的 inference probe。
舊版 shim 會卡住 port,而不是被換掉¶
_copilot_shim_alive 探測的是 /_shim/health 而不是單純連得上,這樣任意程序 ——
或舊版本的 shim —— 才不會被誤認成健康的 metrics shim。但舊版 shim 會把這個路徑
轉發到上游,於是 :4141 回 404,探測正確地判定「死了」,而作業系統仍然認為
「port 被佔用」。過去啟動流程把「死了」當成「port 是空的」,spawn 出來的程序立刻
EADDRINUSE 死掉,結果每個 managed launcher 都對著一個其實正在跑的 shim fail closed。
現在 _copilot_shim_start 直接讀 port(lsof -tiTCP -sTCP:LISTEN):command line 命中
copilot-throttle-shim.js 的就是我們自己的,直接回收;其他一律印出 PID 與 command 後
拒絕啟動 —— 在一個眾所周知的 port 上砍掉無關程序不是它該做的決定,要讓路請改用
COPILOT_SHIM_PORT。判讀特徵是代理的 log 裡一整片 GET /_shim/health 404
(pitfall)。
fork 沒有速率限制器¶
fork 的 start 拿掉了 --rate-limit/--wait。它 README 給的緩解方式是改成減少
Claude Code 的背景雜訊 —— 那正是 COPILOT_PROXY_QUIET=1 注入的東西(這裡預設關閉;
我們優先保 UX 而不是 Copilot quota)。前置 shim 現在提供並行 admission 與 transient
retry,但它不是按時間間隔限制請求的 rate limiter。真的需要後者時,退回原始套件:
COPILOT_API_PKG=copilot-api@0.7.0。
Context management 是轉譯,不是 Anthropic-native¶
舊 fork 可能把 Claude Code 的 context_management 原樣送到會回 400 的 endpoint
(caozhiyuan#305)。自 2.3.4 起,
GPT-5.6 path 會壓掉這個不相容欄位,改依 Responses reasoning/context handling。這避開 400,
但不會完整重現 Anthropic 的 context editing 與 prompt-cache semantics;長 session 仍需觀察。
不要使用 Claude Code 內建的 /model 選單¶
這裡其實有兩個不同陷阱:
- 按 Enter 代表「設為預設」,不是「只用於本 session」(後者是
s)。Claude Code 會把 custom proxy id 寫入~/.claude/settings.json的頂層model。沒有 guard 時,之後 即使copilot-here off、也沒有ANTHROPIC_*env,specstory run claude仍會顯示 GPT/Terra/Luna。 - 選內建 Anthropic 項目時可能送出 官方 帶日期 id(例如
claude-opus-4-8-YYYYMMDD),會被 Copilot 後端拒絕:
API Error: 400 {"error":{"message":"The requested model is not supported.",
"code":"model_not_supported", ...}}
改用 copilot-model 釘選代理模型 —— 不帶日期的連字號 id(claude-opus-4-8)可以正常
運作,只有選單送出的帶日期 id 會失敗。不用啟動 Claude Code,也可以用
copilot-here status 看目前各層與實際生效的 backend/model。
點號 id 會造成「Opus 4 retired」警告與 >100% 的 context HUD¶
歷史陷阱,已由連字號預設值修掉。使用 點號 id(claude-opus-4.8,也就是舊版原始代理
唯一接受的形狀)時,Claude Code 無法對應到它內建的模型表,於是:
- 顯示
[Opus 4]並警告 "Claude Opus 4 was retired"(退回最接近的、已退役的名稱),且 - 假設 context window 是 200k,但 Copilot 實際上以 1M 提供 opus-5 / opus-4-8 /
sonnet-5(
/v1/models裡的max_context_window_tokens: 1000000)—— 所以 HUD/statusline 的 context 可能顯示超過 100%,compaction 也會用錯誤的預算觸發。
helper 會使用連字號 id,並依每個 live model 的 context metadata 自動加 [1m];GPT id 也適用,
例如 gpt-6-sol[1m]。此外會從 max_prompt_tokens 獨立設定精確的 auto-compact capacity;
[1m] 本身不是精確的 prompt limit。Claude Code 送出前會剝掉 suffix;raw API client 必須使用
plain id。
Token 陷阱:gho_ vs ghu_¶
有兩個不同的 GitHub token,且 不可 互換:
| 來源 | 前綴 | copilot_internal/v2/token 交換 |
|---|---|---|
| OpenCode 儲存的 auth | gho_ |
失敗 (404) |
copilot-proxy auth(裝置登入) |
ghu_ |
成功 |
OpenCode 的 gho_ token(OAuth App)只有在 直接 當 Bearer 打 api.githubcopilot.com
時能用;它無法完成 copilot-api 的傳統 token 交換步驟。讓 copilot-proxy auth 產生它自己的
ghu_ token —— 不要重用 OpenCode 的。 Token 儲存在
~/.local/share/copilot-api/github_token。
可用模型與角色 discovery¶
以 GET /v1/models 為 live truth:GitHub 會依帳號、組織政策、rollout 與 egress 改變 catalog。
Claude Code 現在有 Fable、Opus、Sonnet、Haiku 角色;copilot-model --auto 只會映射 catalog
裡實際存在的 id。copilot-model -l 顯示 raw served ids,copilot-model -c 顯示生效中的
角色 profile。不要複製帶日期的 Anthropic id,也不要手猜哪些模型有 1M context。
實用指令¶
claude-copilot # 一次性代理 session(specstory 包裝)
claude-copilot-once # 合併 CLI 設定,單次生效
copilot-here status # 這個專案有釘在代理上嗎?
copilot-model -c # 目前模型 + 來自哪一層
copilot-proxy status # 有沒有在跑?提供哪些 Claude 模型?
copilot-proxy limiter status # live limit / active / queued
copilot-proxy limiter set --limit 6 # 暫時調整,不重啟或中斷 stream
copilot-proxy whoami # 驗證 token → 帳號 / plan / quota
copilot-proxy logs 60 # tail 代理的 log
copilot-proxy logs -f 60 # 跨 restart / rotation 持續 follow
copilot-proxy logs shim -f # follow limiter 與 keepalive 事件
# 用量儀表板 (dashboard)(fork 已在本地內建):
# http://localhost:4141/usage-viewer?endpoint=http://localhost:4141/usage
參見¶
- Copilot embeddings → 語意搜尋 —— 同一個 proxy 的
/v1/embeddings端點,接成copilot-embed+semsearch(本機語意搜尋) - Raycast AI 自帶模型 (BYOK) → 本機 Copilot 代理 (proxy)
—— 同一個 proxy 接進 Raycast 的 Quick AI / AI Chat / AI Commands(
copilot-raycast), 以及那個分辨「真的能用」與「/v1/models只是宣稱有」的零額度探針 - copilot-api —— 代理本體
- Copilot agent gateway 指令
- Claude Code 設定優先序 —— 為什麼
settings.local.json/ 環境變數才是正確的注入層 - Agent overlays —— 本設計刻意避開的
chezmoi 管理
~/.claude/settings.json - OpenCode 原生的 GitHub Copilot provider(OpenCode 本身不需要代理)