copilot-api-plus 1.0.33 → 1.0.35

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.
package/README.md CHANGED
@@ -120,7 +120,7 @@ npx copilot-api-plus@latest start --account-type enterprise
120
120
 
121
121
  ### 2. OpenCode Zen 模式
122
122
 
123
- 使用 [OpenCode Zen](https://opencode.ai/zen) 的多模型 API 服务。
123
+ 使用 [OpenCode Zen](https://opencode.ai/zen) 的多模型 API 服务,支持 GPT-5、Claude、Gemini 等顶级编程模型。
124
124
 
125
125
  #### 前置要求
126
126
  1. 访问 https://opencode.ai/zen
@@ -143,16 +143,37 @@ npx copilot-api-plus@latest start --zen --zen-api-key YOUR_API_KEY
143
143
 
144
144
  | 模型 | ID | 说明 |
145
145
  |------|-----|------|
146
- | Claude Sonnet 4.5 | `claude-sonnet-4-5` | Anthropic Claude (200K) |
147
- | Claude Opus 4.5 | `claude-opus-4-5` | Anthropic Claude (200K) |
146
+ | GPT-5.2 | `gpt-5.2` | OpenAI 最新模型 |
147
+ | GPT-5.1 Codex Max | `gpt-5.1-codex-max` | 代码优化版 |
148
+ | GPT-5.1 Codex | `gpt-5.1-codex` | 代码专用 |
148
149
  | GPT-5 Codex | `gpt-5-codex` | OpenAI Responses API |
150
+ | Claude Opus 4.5 | `claude-opus-4-5` | Anthropic Claude (200K) |
151
+ | Claude Sonnet 4.5 | `claude-sonnet-4-5` | Anthropic Claude (200K) |
152
+ | Claude Sonnet 4 | `claude-sonnet-4` | Anthropic Claude |
149
153
  | Gemini 3 Pro | `gemini-3-pro` | Google Gemini |
150
- | Qwen3 Coder 480B | `qwen3-coder` | Alibaba Qwen |
154
+ | Qwen3 Coder | `qwen3-coder` | Alibaba Qwen |
151
155
  | Kimi K2 | `kimi-k2` | Moonshot |
152
- | Grok Code Fast 1 | `grok-code` | xAI |
156
+ | Grok Code Fast 1 | `grok-code-fast-1` | xAI |
153
157
 
154
158
  更多模型请访问 [opencode.ai/zen](https://opencode.ai/zen)
155
159
 
160
+ #### API 端点
161
+
162
+ Zen 模式支持以下 API 端点:
163
+
164
+ | 端点 | 说明 |
165
+ |------|------|
166
+ | `/v1/chat/completions` | OpenAI 兼容 Chat API |
167
+ | `/v1/messages` | Anthropic 兼容 Messages API |
168
+ | `/v1/responses` | OpenAI Responses API (GPT-5 系列) |
169
+ | `/v1/models` | 获取可用模型列表 |
170
+
171
+ 专用端点(无需 `--zen` 标志也可访问):
172
+ - `/zen/v1/chat/completions`
173
+ - `/zen/v1/messages`
174
+ - `/zen/v1/responses`
175
+ - `/zen/v1/models`
176
+
156
177
  #### 管理 API Key
157
178
 
158
179
  ```bash
package/dist/main.js CHANGED
@@ -2162,14 +2162,14 @@ function createErrorResponse(type, message, status) {
2162
2162
  * Create Anthropic-compatible message response using Antigravity
2163
2163
  * Note: Both Gemini and Claude models use the same endpoint and Gemini-style format
2164
2164
  */
2165
- const MAX_RETRIES = 5;
2165
+ const MAX_RETRIES$3 = 5;
2166
2166
  async function createAntigravityMessages(request) {
2167
2167
  const endpoint = request.stream ? ANTIGRAVITY_STREAM_URL : ANTIGRAVITY_NO_STREAM_URL;
2168
2168
  const body = buildGeminiRequest(request);
2169
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
2169
+ for (let attempt = 0; attempt <= MAX_RETRIES$3; attempt++) {
2170
2170
  const accessToken = await getValidAccessToken();
2171
2171
  if (!accessToken) return createErrorResponse("authentication_error", "No valid Antigravity access token available. Please run login first.", 401);
2172
- consola.debug(`Antigravity request to ${endpoint} (attempt ${attempt + 1}/${MAX_RETRIES + 1})`);
2172
+ consola.debug(`Antigravity request to ${endpoint} (attempt ${attempt + 1}/${MAX_RETRIES$3 + 1})`);
2173
2173
  try {
2174
2174
  const response = await fetch(endpoint, {
2175
2175
  method: "POST",
@@ -2184,7 +2184,7 @@ async function createAntigravityMessages(request) {
2184
2184
  });
2185
2185
  if (response.ok) return request.stream ? transformStreamResponse(response, request.model) : await transformNonStreamResponse(response, request.model);
2186
2186
  const errorResult = await handleApiError(response);
2187
- if (errorResult.shouldRetry && attempt < MAX_RETRIES) {
2187
+ if (errorResult.shouldRetry && attempt < MAX_RETRIES$3) {
2188
2188
  consola.info(`Rate limited, retrying in ${errorResult.retryDelayMs}ms...`);
2189
2189
  await sleep(errorResult.retryDelayMs);
2190
2190
  continue;
@@ -2192,7 +2192,7 @@ async function createAntigravityMessages(request) {
2192
2192
  return errorResult.response;
2193
2193
  } catch (error) {
2194
2194
  consola.error("Antigravity messages request error:", error);
2195
- if (attempt < MAX_RETRIES) {
2195
+ if (attempt < MAX_RETRIES$3) {
2196
2196
  await sleep(500);
2197
2197
  continue;
2198
2198
  }
@@ -2204,7 +2204,7 @@ async function createAntigravityMessages(request) {
2204
2204
  /**
2205
2205
  * Parse retry delay from error response
2206
2206
  */
2207
- function parseRetryDelay(errorText) {
2207
+ function parseRetryDelay$3(errorText) {
2208
2208
  try {
2209
2209
  const details = JSON.parse(errorText).error?.details ?? [];
2210
2210
  for (const detail of details) {
@@ -2234,7 +2234,7 @@ async function handleApiError(response) {
2234
2234
  await rotateAccount();
2235
2235
  return {
2236
2236
  shouldRetry: true,
2237
- retryDelayMs: parseRetryDelay(errorText),
2237
+ retryDelayMs: parseRetryDelay$3(errorText),
2238
2238
  response: createErrorResponse("api_error", `Antigravity API error: ${response.status}`, response.status)
2239
2239
  };
2240
2240
  }
@@ -2319,12 +2319,14 @@ function transformStreamResponse(response, model) {
2319
2319
  */
2320
2320
  async function processStream(reader, decoder, state$1, controller) {
2321
2321
  const emit = (event) => emitSSEEvent(event, controller, state$1);
2322
- while (true) {
2322
+ let finished = false;
2323
+ while (!finished) {
2323
2324
  const { done, value } = await reader.read();
2324
2325
  if (done) break;
2325
2326
  const chunk = decoder.decode(value, { stream: true });
2326
2327
  const lines = processChunk(chunk, state$1);
2327
2328
  for (const line of lines) {
2329
+ if (finished) break;
2328
2330
  const data = parseSSELine(line);
2329
2331
  if (!data) continue;
2330
2332
  const { candidates, usage } = extractFromData(data);
@@ -2335,7 +2337,11 @@ async function processStream(reader, decoder, state$1, controller) {
2335
2337
  const candidate = candidates[0];
2336
2338
  const parts = candidate?.content?.parts ?? [];
2337
2339
  for (const part of parts) processPart(part, state$1, emit);
2338
- if (candidate?.finishReason === "STOP") handleFinish(state$1, emit);
2340
+ if (candidate?.finishReason === "STOP") {
2341
+ handleFinish(state$1, emit);
2342
+ finished = true;
2343
+ break;
2344
+ }
2339
2345
  }
2340
2346
  }
2341
2347
  }
@@ -3257,6 +3263,23 @@ usageRoute.get("/", async (c) => {
3257
3263
 
3258
3264
  //#endregion
3259
3265
  //#region src/services/zen/create-chat-completions.ts
3266
+ const MAX_RETRIES$2 = 5;
3267
+ const DEFAULT_RETRY_DELAY$2 = 500;
3268
+ /**
3269
+ * Parse retry delay from error response
3270
+ */
3271
+ function parseRetryDelay$2(response, errorText) {
3272
+ const retryAfter = response.headers.get("Retry-After");
3273
+ if (retryAfter) {
3274
+ const seconds = Number.parseInt(retryAfter, 10);
3275
+ if (!Number.isNaN(seconds)) return seconds * 1e3;
3276
+ }
3277
+ try {
3278
+ const errorData = JSON.parse(errorText);
3279
+ if (errorData.error?.retry_after) return errorData.error.retry_after * 1e3;
3280
+ } catch {}
3281
+ return DEFAULT_RETRY_DELAY$2;
3282
+ }
3260
3283
  /**
3261
3284
  * Create chat completions via OpenCode Zen
3262
3285
  */
@@ -3264,21 +3287,36 @@ async function createZenChatCompletions(request, signal) {
3264
3287
  const apiKey = state.zenApiKey;
3265
3288
  if (!apiKey) throw new Error("Zen API key not configured");
3266
3289
  consola.debug(`Zen chat completions request for model: ${request.model}`);
3267
- const response = await fetch("https://opencode.ai/zen/v1/chat/completions", {
3268
- method: "POST",
3269
- headers: {
3270
- "Content-Type": "application/json",
3271
- Authorization: `Bearer ${apiKey}`
3272
- },
3273
- body: JSON.stringify(request),
3274
- signal
3275
- });
3276
- if (!response.ok) {
3290
+ for (let attempt = 0; attempt <= MAX_RETRIES$2; attempt++) try {
3291
+ const response = await fetch("https://opencode.ai/zen/v1/chat/completions", {
3292
+ method: "POST",
3293
+ headers: {
3294
+ "Content-Type": "application/json",
3295
+ Authorization: `Bearer ${apiKey}`
3296
+ },
3297
+ body: JSON.stringify(request),
3298
+ signal
3299
+ });
3300
+ if (response.ok) return response;
3277
3301
  const errorText = await response.text();
3302
+ if ((response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES$2) {
3303
+ const retryDelay = parseRetryDelay$2(response, errorText);
3304
+ consola.info(`Zen rate limited (${response.status}), retrying in ${retryDelay}ms...`);
3305
+ await sleep(retryDelay);
3306
+ continue;
3307
+ }
3278
3308
  consola.error(`Zen API error: ${response.status} ${errorText}`);
3279
3309
  throw new Error(`Zen API error: ${response.status} ${errorText}`);
3310
+ } catch (error) {
3311
+ if (error instanceof Error && error.name === "AbortError") throw error;
3312
+ if (attempt < MAX_RETRIES$2) {
3313
+ consola.warn(`Zen request failed, retrying... (${attempt + 1})`);
3314
+ await sleep(DEFAULT_RETRY_DELAY$2);
3315
+ continue;
3316
+ }
3317
+ throw error;
3280
3318
  }
3281
- return response;
3319
+ throw new Error("Max retries exceeded");
3282
3320
  }
3283
3321
 
3284
3322
  //#endregion
@@ -3313,6 +3351,23 @@ zenCompletionRoutes.post("/", async (c) => {
3313
3351
 
3314
3352
  //#endregion
3315
3353
  //#region src/services/zen/create-messages.ts
3354
+ const MAX_RETRIES$1 = 5;
3355
+ const DEFAULT_RETRY_DELAY$1 = 500;
3356
+ /**
3357
+ * Parse retry delay from error response headers or body
3358
+ */
3359
+ function parseRetryDelay$1(response, errorText) {
3360
+ const retryAfter = response.headers.get("Retry-After");
3361
+ if (retryAfter) {
3362
+ const seconds = Number.parseInt(retryAfter, 10);
3363
+ if (!Number.isNaN(seconds)) return seconds * 1e3;
3364
+ }
3365
+ try {
3366
+ const errorData = JSON.parse(errorText);
3367
+ if (errorData.error?.retry_after) return errorData.error.retry_after * 1e3;
3368
+ } catch {}
3369
+ return DEFAULT_RETRY_DELAY$1;
3370
+ }
3316
3371
  /**
3317
3372
  * Create messages via OpenCode Zen (Anthropic format)
3318
3373
  */
@@ -3320,22 +3375,37 @@ async function createZenMessages(request, signal) {
3320
3375
  const apiKey = state.zenApiKey;
3321
3376
  if (!apiKey) throw new Error("Zen API key not configured");
3322
3377
  consola.debug(`Zen messages request for model: ${request.model}`);
3323
- const response = await fetch("https://opencode.ai/zen/v1/messages", {
3324
- method: "POST",
3325
- headers: {
3326
- "Content-Type": "application/json",
3327
- "x-api-key": apiKey,
3328
- "anthropic-version": "2023-06-01"
3329
- },
3330
- body: JSON.stringify(request),
3331
- signal
3332
- });
3333
- if (!response.ok) {
3378
+ for (let attempt = 0; attempt <= MAX_RETRIES$1; attempt++) try {
3379
+ const response = await fetch("https://opencode.ai/zen/v1/messages", {
3380
+ method: "POST",
3381
+ headers: {
3382
+ "Content-Type": "application/json",
3383
+ "x-api-key": apiKey,
3384
+ "anthropic-version": "2023-06-01"
3385
+ },
3386
+ body: JSON.stringify(request),
3387
+ signal
3388
+ });
3389
+ if (response.ok) return response;
3334
3390
  const errorText = await response.text();
3391
+ if ((response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES$1) {
3392
+ const retryDelay = parseRetryDelay$1(response, errorText);
3393
+ consola.info(`Zen rate limited (${response.status}), retrying in ${retryDelay}ms...`);
3394
+ await sleep(retryDelay);
3395
+ continue;
3396
+ }
3335
3397
  consola.error(`Zen Messages API error: ${response.status} ${errorText}`);
3336
3398
  throw new Error(`Zen Messages API error: ${response.status} ${errorText}`);
3399
+ } catch (error) {
3400
+ if (error instanceof Error && error.name === "AbortError") throw error;
3401
+ if (attempt < MAX_RETRIES$1) {
3402
+ consola.warn(`Zen request failed, retrying... (${attempt + 1})`);
3403
+ await sleep(DEFAULT_RETRY_DELAY$1);
3404
+ continue;
3405
+ }
3406
+ throw error;
3337
3407
  }
3338
- return response;
3408
+ throw new Error("Max retries exceeded");
3339
3409
  }
3340
3410
 
3341
3411
  //#endregion
@@ -3391,6 +3461,94 @@ zenModelRoutes.get("/", async (c) => {
3391
3461
  }
3392
3462
  });
3393
3463
 
3464
+ //#endregion
3465
+ //#region src/services/zen/create-responses.ts
3466
+ const MAX_RETRIES = 5;
3467
+ const DEFAULT_RETRY_DELAY = 500;
3468
+ /**
3469
+ * Parse retry delay from error response
3470
+ */
3471
+ function parseRetryDelay(response, errorText) {
3472
+ const retryAfter = response.headers.get("Retry-After");
3473
+ if (retryAfter) {
3474
+ const seconds = Number.parseInt(retryAfter, 10);
3475
+ if (!Number.isNaN(seconds)) return seconds * 1e3;
3476
+ }
3477
+ try {
3478
+ const errorData = JSON.parse(errorText);
3479
+ if (errorData.error?.retry_after) return errorData.error.retry_after * 1e3;
3480
+ } catch {}
3481
+ return DEFAULT_RETRY_DELAY;
3482
+ }
3483
+ /**
3484
+ * Create responses via OpenCode Zen (OpenAI Responses API format)
3485
+ */
3486
+ async function createZenResponses(request, signal) {
3487
+ const apiKey = state.zenApiKey;
3488
+ if (!apiKey) throw new Error("Zen API key not configured");
3489
+ consola.debug(`Zen responses request for model: ${request.model}`);
3490
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) try {
3491
+ const response = await fetch("https://opencode.ai/zen/v1/responses", {
3492
+ method: "POST",
3493
+ headers: {
3494
+ "Content-Type": "application/json",
3495
+ Authorization: `Bearer ${apiKey}`
3496
+ },
3497
+ body: JSON.stringify(request),
3498
+ signal
3499
+ });
3500
+ if (response.ok) return response;
3501
+ const errorText = await response.text();
3502
+ if ((response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
3503
+ const retryDelay = parseRetryDelay(response, errorText);
3504
+ consola.info(`Zen rate limited (${response.status}), retrying in ${retryDelay}ms...`);
3505
+ await sleep(retryDelay);
3506
+ continue;
3507
+ }
3508
+ consola.error(`Zen Responses API error: ${response.status} ${errorText}`);
3509
+ throw new Error(`Zen Responses API error: ${response.status} ${errorText}`);
3510
+ } catch (error) {
3511
+ if (error instanceof Error && error.name === "AbortError") throw error;
3512
+ if (attempt < MAX_RETRIES) {
3513
+ consola.warn(`Zen request failed, retrying... (${attempt + 1})`);
3514
+ await sleep(DEFAULT_RETRY_DELAY);
3515
+ continue;
3516
+ }
3517
+ throw error;
3518
+ }
3519
+ throw new Error("Max retries exceeded");
3520
+ }
3521
+
3522
+ //#endregion
3523
+ //#region src/routes/zen/responses/route.ts
3524
+ const zenResponsesRoutes = new Hono();
3525
+ zenResponsesRoutes.post("/", async (c) => {
3526
+ if (!state.zenMode || !state.zenApiKey) return c.json({ error: "Zen mode is not enabled. Start with --zen flag." }, 400);
3527
+ try {
3528
+ const body = await c.req.json();
3529
+ consola.debug("Zen responses request:", body.model);
3530
+ const response = await createZenResponses(body);
3531
+ if (body.stream) {
3532
+ const headers = new Headers();
3533
+ headers.set("Content-Type", "text/event-stream");
3534
+ headers.set("Cache-Control", "no-cache");
3535
+ headers.set("Connection", "keep-alive");
3536
+ return new Response(response.body, {
3537
+ status: response.status,
3538
+ headers
3539
+ });
3540
+ }
3541
+ const data = await response.json();
3542
+ return c.json(data);
3543
+ } catch (error) {
3544
+ consola.error("Zen responses error:", error);
3545
+ return c.json({ error: {
3546
+ message: error instanceof Error ? error.message : "Unknown error",
3547
+ type: "zen_error"
3548
+ } }, 500);
3549
+ }
3550
+ });
3551
+
3394
3552
  //#endregion
3395
3553
  //#region src/server.ts
3396
3554
  const server = new Hono();
@@ -3467,9 +3625,20 @@ server.all("/v1/messages", async (c) => {
3467
3625
  if (state.antigravityMode) return antigravityMessagesRoute.fetch(req, c.env);
3468
3626
  return messageRoutes.fetch(req, c.env);
3469
3627
  });
3628
+ server.all("/v1/responses/*", async (c) => {
3629
+ const req = createSubRequest(c, "/v1/responses");
3630
+ if (state.zenMode) return zenResponsesRoutes.fetch(req, c.env);
3631
+ return c.json({ error: "Responses API requires Zen mode" }, 400);
3632
+ });
3633
+ server.all("/v1/responses", async (c) => {
3634
+ const req = createSubRequest(c, "/v1/responses");
3635
+ if (state.zenMode) return zenResponsesRoutes.fetch(req, c.env);
3636
+ return c.json({ error: "Responses API requires Zen mode" }, 400);
3637
+ });
3470
3638
  server.route("/zen/v1/chat/completions", zenCompletionRoutes);
3471
3639
  server.route("/zen/v1/models", zenModelRoutes);
3472
3640
  server.route("/zen/v1/messages", zenMessageRoutes);
3641
+ server.route("/zen/v1/responses", zenResponsesRoutes);
3473
3642
  server.route("/antigravity/v1/chat/completions", antigravityChatCompletionsRoute);
3474
3643
  server.route("/antigravity/v1/models", antigravityModelsRoute);
3475
3644
  server.route("/antigravity/v1/messages", antigravityMessagesRoute);