跳轉到

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 透過 socks ALL_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 與 -fast main candidates 會排除。
  • Launcher 以已安裝 Codex 的 codex debug models --bundled descriptor 與同一份 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__sites namespace;使用者後續傳入的 -c 仍可在單次呼叫覆寫這兩項。
  • 有安裝 SpecStory 時預設執行 specstory run codex,並保留實際生效的 codex_cmd(project config > user config > 裸 codex),再附加 provider、 model 與使用者參數。Raw --no-specstory path 會自行維持同樣的 --ask-for-approval never --sandbox danger-full-access posture;若使用者已指定 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 與 -fast entries,再依 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,然後快取整個生命週期。 有兩種看起來一樣的壞法:

  1. 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。
  2. 節點抖一下 — 那一次請求碰上壞 hop,快取成截斷清單。

線索在啟動 banner:

ℹ Models refresh: 13 new     ← 壞掉:底下的清單裡沒有任何 claude id
ℹ Models refresh: 21 new     ← 正常

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 選單

這裡其實有兩個不同陷阱:

  1. 按 Enter 代表「設為預設」,不是「只用於本 session」(後者是 s)。Claude Code 會把 custom proxy id 寫入 ~/.claude/settings.json 的頂層 model。沒有 guard 時,之後 即使 copilot-here off、也沒有 ANTHROPIC_* env,specstory run claude 仍會顯示 GPT/Terra/Luna。
  2. 選內建 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

參見