@dianshuv/copilot-api 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +157 -29
  2. package/dist/main.mjs +1162 -1272
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -15,7 +15,6 @@
15
15
  - **Graceful shutdown**: 4-phase shutdown sequence — stops accepting requests, waits for in-flight requests to complete, sends abort signal, then force-closes. Configurable via `--shutdown-graceful-wait` and `--shutdown-abort-wait`.
16
16
  - **Stream repetition detection**: Detects when models get stuck in repetitive output loops using KMP-based pattern matching and logs a warning.
17
17
  - **Stale request reaping**: Automatically force-fails requests that exceed a configurable maximum age (default 600s) to prevent resource leaks.
18
- - **Gemini API compatibility**: `/v1beta/models` endpoints translate Gemini API requests to OpenAI format for Copilot. Enables Google Gemini CLI to use Copilot models via `GOOGLE_GEMINI_BASE_URL` environment variable.
19
18
  - **PostHog analytics**: Optional PostHog Cloud integration (`--posthog-key`) sends per-request token usage events for long-term trend analysis. Free tier (1M events/month) is more than sufficient for individual use.
20
19
 
21
20
  ## Quick Start
@@ -70,6 +69,7 @@ make down
70
69
  | `--github-token`, `-g` | Provide GitHub token directly | none |
71
70
  | `--claude-code`, `-c` | Generate Claude Code launch command | false |
72
71
  | `--show-token` | Show tokens on fetch/refresh | false |
72
+ | `--show-all-models` | Show all upstream models including the hardcoded blacklist (see "Hidden Models" below) | false |
73
73
  | `--proxy-env` | Use proxy from environment | false |
74
74
  | `--no-history` | Disable request history UI at `/history` | false |
75
75
  | `--history-limit` | Max history entries in memory | 1000 |
@@ -80,6 +80,36 @@ make down
80
80
  | `--context-editing` | Context editing mode: off, clear-thinking, clear-tooluse, clear-both | off |
81
81
  | `--timezone-offset` | Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0) | +8 |
82
82
  | `--posthog-key` | PostHog API key for token usage analytics (opt-in) | none |
83
+ | `--api-key` | Proxy API key for inbound authentication (see [Authentication](#authentication)). Empty = disabled | none |
84
+
85
+ ### Hidden Models
86
+
87
+ By default the proxy hides 30 stale / duplicate / unused upstream model ids from
88
+ its **listing surfaces** (`/v1/models`, the startup banner, and the
89
+ `--claude-code` interactive prompts). Pass `--show-all-models` to see the full
90
+ upstream catalogue.
91
+
92
+ **Important**: this is a *display* filter only. The remaining POST endpoints
93
+ (`/v1/chat/completions`, `/v1/messages`, `/v1/responses`) do **not** reject
94
+ hidden ids — an explicit `POST` with a hidden id is still forwarded to
95
+ upstream verbatim, even without `--show-all-models`. (The embedding and
96
+ Gemini ids in the list have no callable endpoint in this proxy at all;
97
+ they're hidden purely to denoise the `/v1/models` listing — the upstream
98
+ `/models` catalogue still returns them.)
99
+
100
+ To change the blacklist, edit `src/lib/hidden-models.ts` and re-publish (this
101
+ fork uses `/publish`).
102
+
103
+ Currently hidden (grouped):
104
+
105
+ - **Legacy GPT** — `gpt-3.5-turbo`, `gpt-3.5-turbo-0613`, `gpt-4`, `gpt-4-0613`, `gpt-4-0125-preview`
106
+ - **GPT-4o family** — `gpt-4o`, `gpt-4o-mini`, `gpt-4-o-preview`, `gpt-4o-2024-05-13`, `gpt-4o-2024-08-06`, `gpt-4o-2024-11-20`, `gpt-4o-mini-2024-07-18`
107
+ - **GPT-4.1 family** — `gpt-4.1`, `gpt-4.1-2025-04-14`, `gpt-41-copilot`
108
+ - **Older / smaller GPT-5** — `gpt-5-mini`, `gpt-5.3-codex`, `gpt-5.4`
109
+ - **All embeddings** — `text-embedding-ada-002`, `text-embedding-3-small`, `text-embedding-3-small-inference`
110
+ - **Older Gemini** — `gemini-2.5-pro`, `gemini-3-flash-preview`
111
+ - **Older / variant Claude** — `claude-opus-4.5`, `claude-opus-4.6`, `claude-opus-4.7-high`, `claude-opus-4.7-xhigh`, `claude-sonnet-4.5`
112
+ - **Special-purpose** — `mai-code-1-flash-internal`, `trajectory-compaction`
83
113
 
84
114
  ### Patch-Claude Command Options
85
115
 
@@ -98,7 +128,6 @@ make down
98
128
  |----------|--------|-------------|
99
129
  | `/v1/chat/completions` | POST | Chat completions |
100
130
  | `/v1/models` | GET | List available models |
101
- | `/v1/embeddings` | POST | Text embeddings |
102
131
  | `/v1/responses` | POST | Responses API (for codex models) |
103
132
 
104
133
  ### Anthropic Compatible
@@ -109,14 +138,6 @@ make down
109
138
  | `/v1/messages/count_tokens` | POST | Token counting |
110
139
  | `/v1/event_logging/batch` | POST | Event logging (no-op) |
111
140
 
112
- ### Gemini Compatible
113
-
114
- | Endpoint | Method | Description |
115
- |----------|--------|-------------|
116
- | `/v1beta/models/{model}:generateContent` | POST | Non-streaming generation |
117
- | `/v1beta/models/{model}:streamGenerateContent` | POST | Streaming generation (SSE) |
118
- | `/v1beta/models/{model}:countTokens` | POST | Token counting |
119
-
120
141
  ### Utility
121
142
 
122
143
  | Endpoint | Method | Description |
@@ -137,6 +158,130 @@ When enabled (default), auto-truncate automatically compacts conversation histor
137
158
  - **Smart compression**: With `--compress-tool-results`, old tool results are compressed before removing messages, preserving more conversation context.
138
159
  - **Orphan filtering**: After truncation, orphaned tool results (without matching tool calls) are automatically removed.
139
160
 
161
+ ## Authentication
162
+
163
+ By default the proxy is **unauthenticated**: every endpoint is open and any
164
+ request passes through unchanged. You can optionally protect the proxy's
165
+ *inbound* surface with a **Proxy API key** (this is your own secret for this
166
+ proxy — it is unrelated to the GitHub OAuth / Copilot token the proxy uses
167
+ upstream). When a key is configured, all endpoints **except `/` and `/health`**
168
+ require it (fail-closed: any route not on that exemption list — including future
169
+ ones — is protected). Genuine CORS preflight `OPTIONS` requests are also let
170
+ through (they carry no payload), so browser clients aren't tripped by an opaque
171
+ preflight failure.
172
+
173
+ ### Enabling it
174
+
175
+ Provide the key via either source:
176
+
177
+ - `--api-key <key>` flag
178
+ - `COPILOT_API_KEY` environment variable
179
+
180
+ Rules:
181
+
182
+ - **Precedence**: when both are set to a non-empty value, the **flag wins** and
183
+ the env value is ignored.
184
+ - **Trim + empty disables**: each source is trimmed first; a value that is empty
185
+ or whitespace-only (`--api-key ""`, `COPILOT_API_KEY=`) counts as *not
186
+ provided*. If neither source yields a non-empty value, auth stays **disabled**
187
+ (the default).
188
+
189
+ The startup banner prints whether auth is on/off and, when on, the source
190
+ (`flag` / `env`) — but **never** the key value itself.
191
+
192
+ > [!CAUTION]
193
+ > **Ambient `COPILOT_API_KEY` footgun.** Because the env var is read on every
194
+ > start, a `COPILOT_API_KEY` exported anywhere the process can see it (your
195
+ > shell profile, a `.env` sourced into the environment, a systemd unit, a
196
+ > container env) **silently enables authentication** for that instance — even
197
+ > when you did not pass `--api-key`. If a previously-open instance suddenly
198
+ > returns `401`, check for an inherited `COPILOT_API_KEY`. The startup banner's
199
+ > `source: env` line makes this visible.
200
+
201
+ ### Network binding
202
+
203
+ The proxy **binds all interfaces (`0.0.0.0`) by default**, so a key-less
204
+ instance is reachable from anywhere on your network. For a local-only,
205
+ unauthenticated instance, bind loopback explicitly:
206
+
207
+ ```sh
208
+ copilot-api start --host 127.0.0.1
209
+ ```
210
+
211
+ The startup banner reports the **real** bind address (`0.0.0.0` for an
212
+ all-interfaces bind, or the narrowed host you passed), so a wide-open bind is
213
+ never hidden.
214
+
215
+ ### Choosing a key
216
+
217
+ Use a **high-entropy** secret (e.g. `openssl rand -hex 32`), not a guessable
218
+ word. The comparison is constant-time, but a weak key can still be guessed
219
+ offline or brute-forced over the network.
220
+
221
+ ### Migrating clients
222
+
223
+ Once a key is configured, clients that previously sent a placeholder must send
224
+ the real key. Two header shapes are accepted and **either** is sufficient:
225
+
226
+ - **OpenAI-compatible clients** → `Authorization: Bearer <key>` (the scheme is
227
+ matched case-insensitively; a bare value with no `Bearer ` prefix is also
228
+ tolerated).
229
+ - **Anthropic-native clients** → `x-api-key: <key>`.
230
+
231
+ If both headers are present, a match on *either* one is accepted.
232
+
233
+ Per client:
234
+
235
+ - **opencode** (`opencode.json`): set `provider.<name>.options.apiKey` from
236
+ `"dummy"` to your real key (the `@ai-sdk/openai-compatible` provider sends it
237
+ as `Authorization: Bearer`).
238
+ - **Claude Code** (`.claude/settings.json`, see [below](#using-with-claude-code)):
239
+ change `ANTHROPIC_AUTH_TOKEN` from `"dummy"` to your real key. Claude Code
240
+ talks to the proxy's `/v1/messages` (Anthropic) surface; the auth gate accepts
241
+ the token from either header. `--claude-code` keeps emitting the `"dummy"`
242
+ placeholder on purpose (so the secret never lands in your clipboard / shell
243
+ history) and prints a reminder to replace it when auth is on.
244
+ - **Raw OpenAI SDK** → `Authorization: Bearer <key>`; **raw Anthropic SDK** →
245
+ `x-api-key: <key>`.
246
+
247
+ > [!TIP]
248
+ > **Keep the key out of shell history.** Don't paste the literal key onto a
249
+ > command line (e.g. `curl -H "Authorization: Bearer sk-real-key" …`), or it
250
+ > lands in `~/.zsh_history`. Read it from an env var or a file instead:
251
+ >
252
+ > ```sh
253
+ > # Put the key in an env var read from a non-echoed prompt or a 0600 file,
254
+ > # then reference the var — the literal secret never appears in argv/history.
255
+ > read -rs COPILOT_API_KEY # typed key is not echoed or saved
256
+ > export COPILOT_API_KEY
257
+ > curl -H "Authorization: Bearer $COPILOT_API_KEY" http://127.0.0.1:4141/v1/models
258
+ > ```
259
+
260
+ A failed auth returns `401` with a client-appropriate body: requests to the
261
+ Anthropic surface (`/v1/messages`, `/v1/messages/count_tokens`) get
262
+ `{"type":"error","error":{"type":"authentication_error", …}}`; every other
263
+ endpoint gets the OpenAI-style `{"error":{"code":"invalid_api_key", …}}`.
264
+ Missing and wrong keys return the identical body (no oracle), always with a
265
+ `WWW-Authenticate: Bearer` header.
266
+
267
+ ### Browser tools when auth is on
268
+
269
+ The request logger does **not** record the `Authorization` / `x-api-key`
270
+ headers, so your key never lands in logs. But the browser-based tools can't send
271
+ a custom header, so when auth is on they stop working:
272
+
273
+ - **History UI** (`/history`) and its live-update WebSocket (`/history/ws`) — the
274
+ page load and socket upgrade are gated like any other protected route.
275
+ - **External Usage Viewer** (the `ericc-ch.github.io` link in the banner) — it
276
+ fetches `/usage`, which is now protected, so it can't load your data.
277
+
278
+ Workarounds:
279
+
280
+ - Run a **separate local, unauthenticated instance** (`--host 127.0.0.1` with no
281
+ `--api-key`) for the browser UI, or
282
+ - Hit the history JSON API directly with a key, e.g.
283
+ `curl -H "Authorization: Bearer $COPILOT_API_KEY" http://127.0.0.1:4141/history/api/entries`.
284
+
140
285
  ## Using with Claude Code
141
286
 
142
287
  Create `.claude/settings.json` in your project:
@@ -146,8 +291,8 @@ Create `.claude/settings.json` in your project:
146
291
  "env": {
147
292
  "ANTHROPIC_BASE_URL": "http://localhost:4141",
148
293
  "ANTHROPIC_AUTH_TOKEN": "dummy",
149
- "ANTHROPIC_MODEL": "gpt-4.1",
150
- "ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1",
294
+ "ANTHROPIC_MODEL": "claude-opus-4.8",
295
+ "ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4.5",
151
296
  "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
152
297
  "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
153
298
  },
@@ -163,23 +308,6 @@ Or use the interactive setup:
163
308
  bun run start --claude-code
164
309
  ```
165
310
 
166
- ## Using with Gemini CLI
167
-
168
- ```bash
169
- # Start the proxy
170
- copilot-api start
171
-
172
- # Configure Gemini CLI to use the proxy
173
- export GEMINI_API_KEY="placeholder"
174
- export GOOGLE_GEMINI_BASE_URL="http://localhost:4141"
175
-
176
- # Basic conversation
177
- gemini -p "Explain this code"
178
-
179
- # Pipe review
180
- git diff HEAD~1 | gemini -p "Review this diff for bugs"
181
- ```
182
-
183
311
  ## Upstream Project
184
312
 
185
313
  For the original project documentation, features, and updates, see: [ericc-ch/copilot-api](https://github.com/ericc-ch/copilot-api)