diff --git a/AGENTS.md b/AGENTS.md index ef0f4fb..c897f8f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Kleis -Single OAuth account proxy for coding agents. One base URL that stores OAuth credentials centrally, refreshes tokens automatically, and routes requests to Copilot/Codex/Claude through provider-compatible proxy endpoints. +Single OAuth account proxy for coding agents. One base URL that stores OAuth credentials centrally, refreshes tokens automatically, and routes requests to Codex and Claude through provider-compatible proxy endpoints. ## Architecture @@ -13,7 +13,7 @@ Single OAuth account proxy for coding agents. One base URL that stores OAuth cre ## Database Schema (4 tables) -- **`provider_accounts`**: OAuth credentials per provider (copilot/codex/claude). Includes access/refresh tokens, expiry, primary flag, metadata JSON, distributed refresh lock fields. +- **`provider_accounts`**: OAuth credentials per provider (codex/claude). Includes access/refresh tokens, expiry, primary flag, metadata JSON, distributed refresh lock fields. - **`api_keys`**: Proxy auth keys (`kleis_*` format). Provider and model scope arrays. Each key gets a `modelsDiscoveryToken` for scoped model URLs. - **`oauth_states`**: Ephemeral records for in-flight OAuth flows. - **`request_usage_buckets`**: Minute-bucketed analytics. Tracks request/success/error counts and latency per key+account+provider+endpoint. @@ -26,15 +26,11 @@ All require `Authorization: Bearer `: | Route | Provider | Upstream | |---|---|---| -| `POST /copilot/v1/chat/completions` | Copilot | GitHub Copilot chat completions | -| `POST /copilot/v1/responses` | Copilot | GitHub Copilot responses API | | `POST /openai/v1/responses` | Codex | ChatGPT Codex responses API | | `POST /anthropic/v1/messages` | Claude | Anthropic messages API | ## Provider Details -**Copilot**: GitHub device flow OAuth. Derives vision/initiator headers from message content. Supports enterprise domain override. - **Codex**: OpenAI browser (PKCE) or headless (device) flow. Injects Codex-specific headers (`ChatGPT-Account-Id`, `originator`). Strips unsupported params. **Claude**: Anthropic PKCE flow (max or console mode). Full Claude Code identity emulation: system prompt rewriting, tool name prefixing/stripping (`mcp_` prefix), beta header injection, user-agent spoofing. Handles both streaming SSE transformation and non-streaming JSON rewriting. diff --git a/Caddyfile b/Caddyfile index 99f7525..571387c 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,5 +1,5 @@ kleis.rex.wf { - @streaming path /openai/v1/* /anthropic/v1/* /copilot/v1/* + @streaming path /openai/v1/* /anthropic/v1/* handle @streaming { reverse_proxy 127.0.0.1:3003 { diff --git a/README.md b/README.md index 1572dcd..28c502c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Kleis

-OAuth account proxy for [OpenCode](https://github.com/anomalyco/opencode). One base URL for Copilot, Codex, and Claude. +OAuth account proxy for [OpenCode](https://github.com/anomalyco/opencode). One base URL for Codex and Claude. > [!NOTE] > "Kleis" is named from the idea of a key that can unlock many paths with one handle. @@ -13,7 +13,7 @@ Re-authenticating OAuth accounts across clients and machines is painful. Kleis s ## How it works -Each provider has its own proxy adapter because none of them behave the same way. Copilot needs vision/initiator headers derived from message content analysis. Codex rejects certain params and requires instruction injection. Claude needs tool name prefixing, system identity rewriting, beta header merging, and streaming response transformation to strip those prefixes back out. +Each provider has its own proxy adapter because none of them behave the same way. Codex rejects certain params and requires instruction injection. Claude needs tool name prefixing, system identity rewriting, beta header merging, and streaming response transformation to strip those prefixes back out. `GET /api.json` serves a models.dev-compatible registry that merges upstream model data with Kleis routing info, so OpenCode auto-discovers everything without manual model config. @@ -27,8 +27,6 @@ There's also minute-bucketed request analytics across both API keys and provider |---|---|---| | `POST /openai/v1/responses` | Codex | Responses API | | `POST /anthropic/v1/messages` | Claude | Messages API | -| `POST /copilot/v1/chat/completions` | Copilot | Chat Completions | -| `POST /copilot/v1/responses` | Copilot | Responses API | --- @@ -62,7 +60,6 @@ Admin panel lives at `http://localhost:3003/admin/`. ## OAuth flows - Codex: browser callback code flow or headless device flow. -- Copilot: device flow. - Claude: authorization code flow (`claude.ai` or `console.anthropic.com` mode). After connecting accounts, set one primary account per provider. diff --git a/assets/hero.html b/assets/hero.html index 0d08f62..067fc72 100644 --- a/assets/hero.html +++ b/assets/hero.html @@ -21,7 +21,6 @@ --text-tertiary: #5c5853; --amber: #d4a24e; --amber-dim: #b8882e; - --copilot: #79c0ff; --codex: #a78bfa; --claude: #fb923c; --font-display: "Lexend", sans-serif; @@ -96,10 +95,6 @@ color: var(--codex); border-color: rgba(167, 139, 250, 0.2); } - .node.copilot { - color: var(--copilot); - border-color: rgba(121, 192, 255, 0.2); - } .node.claude { color: var(--claude); border-color: rgba(251, 146, 60, 0.2); @@ -120,7 +115,6 @@
kleis
-
copilot
claude
codex
...
diff --git a/assets/hero.png b/assets/hero.png index 524f2b9..4f6fe09 100644 Binary files a/assets/hero.png and b/assets/hero.png differ diff --git a/assets/social.html b/assets/social.html index 6054a5d..76921c9 100644 --- a/assets/social.html +++ b/assets/social.html @@ -21,7 +21,6 @@ --text-tertiary: #5c5853; --amber: #d4a24e; --amber-dim: #b8882e; - --copilot: #79c0ff; --codex: #a78bfa; --claude: #fb923c; --font-display: "Lexend", sans-serif; @@ -96,10 +95,6 @@ color: var(--codex); border-color: rgba(167, 139, 250, 0.2); } - .node.copilot { - color: var(--copilot); - border-color: rgba(121, 192, 255, 0.2); - } .node.claude { color: var(--claude); border-color: rgba(251, 146, 60, 0.2); @@ -120,7 +115,6 @@
kleis
-
copilot
claude
codex
...
diff --git a/assets/social.png b/assets/social.png index 7301b74..3d1a7f1 100644 Binary files a/assets/social.png and b/assets/social.png differ diff --git a/public/admin/app-data.js b/public/admin/app-data.js index b36410f..225abc1 100644 --- a/public/admin/app-data.js +++ b/public/admin/app-data.js @@ -9,7 +9,7 @@ import { const DEFAULT_KEY_USAGE_WINDOW_MS = 24 * 60 * 60 * 1000; const ADMIN_TOKEN_STORAGE_KEY = "kleis_admin_token"; -const PROVIDER_ORDER = ["copilot", "codex", "claude"]; +const PROVIDER_ORDER = ["codex", "claude"]; const readPersistedToken = () => localStorage.getItem(ADMIN_TOKEN_STORAGE_KEY) || ""; @@ -1159,7 +1159,6 @@ async function copyToClipboard(text, btn) { function updateOAuthProviderUI() { const p = $("#oauth-provider").value; - $("#oauth-copilot-opts").style.display = p === "copilot" ? "block" : "none"; $("#oauth-codex-opts").style.display = p === "codex" ? "block" : "none"; $("#oauth-claude-opts").style.display = p === "claude" ? "block" : "none"; } @@ -1172,10 +1171,7 @@ async function startOAuth() { try { const body = {}; - if (provider === "copilot") { - const ed = $("#oauth-enterprise-domain").value.trim(); - if (ed) body.options = { enterpriseDomain: ed }; - } else if (provider === "codex") { + if (provider === "codex") { body.options = { mode: $("#oauth-codex-mode").value }; } else if (provider === "claude") { body.options = { mode: $("#oauth-claude-mode").value }; diff --git a/public/admin/app-render.js b/public/admin/app-render.js index 2043877..60bde78 100644 --- a/public/admin/app-render.js +++ b/public/admin/app-render.js @@ -165,7 +165,6 @@ function claudeLimitsBody(data) { } function accountTrackingHtml(account) { - if (account.provider === "copilot") return ""; const tracking = account.quota; if (!tracking) { return `
@@ -760,7 +759,7 @@ function renderDashKpis(m, pm) { label: "cache hit", value: `${cr}%`, delta: dashDelta(cr, prevCr), - accent: "var(--copilot)", + accent: "var(--amber)", }, { label: "avg latency", @@ -864,7 +863,7 @@ function tokenSeriesExtractor(bucket) { return [ { value: m.inputTokens, color: "var(--amber)", label: "input" }, { value: m.outputTokens, color: "var(--green)", label: "output" }, - { value: m.cacheReadTokens, color: "var(--copilot)", label: "cache read" }, + { value: m.cacheReadTokens, color: "var(--amber)", label: "cache read" }, { value: m.cacheWriteTokens, color: "var(--text-secondary)", @@ -953,7 +952,7 @@ function renderDashboard(data) { html += `
request volume
${reqChart}
successproxy failupstream/other fail429
`; } if (tokChart) { - html += `
token usage
${tokChart}
inputoutputcache readcache write
`; + html += `
token usage
${tokChart}
inputoutputcache readcache write
`; } html += "
"; } diff --git a/public/admin/index.html b/public/admin/index.html index 3da7663..a427679 100644 --- a/public/admin/index.html +++ b/public/admin/index.html @@ -265,23 +265,10 @@
-
- - -
Leave blank for github.com
-
@@ -430,10 +416,6 @@ Leave empty for all providers.
-