copilot-api-plus 1.0.49 → 1.0.51

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
@@ -1,5 +1,10 @@
1
1
  # Copilot API Plus
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/copilot-api-plus.svg)](https://www.npmjs.com/package/copilot-api-plus)
4
+ [![license](https://img.shields.io/npm/l/copilot-api-plus.svg)](https://github.com/imbuxiangnan-cyber/copilot-api-plus/blob/main/LICENSE)
5
+
6
+ > A proxy that converts GitHub Copilot, OpenCode Zen, and Google Antigravity into OpenAI & Anthropic compatible APIs. Works with Claude Code, opencode, and more.
7
+
3
8
  将 GitHub Copilot、OpenCode Zen、Google Antigravity 等 AI 服务转换为 **OpenAI** 和 **Anthropic** 兼容 API,支持与 [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview)、[opencode](https://github.com/sst/opencode) 等工具无缝集成。
4
9
 
5
10
  ---
@@ -673,6 +678,32 @@ Google Antigravity 模式内置了可靠性保障:
673
678
  - **按模型族速率追踪**:分别追踪 Gemini 和 Claude 模型族的速率限制状态
674
679
  - **指数退避重试**:429/503 等限流错误自动退避重试,短间隔走同端点,长间隔切换端点
675
680
 
681
+ ### 请求日志
682
+
683
+ 每次 API 请求会输出一行日志,包含模型名、耗时和 token 用量:
684
+
685
+ ```
686
+ [claude-opus-4-6] 13:13:39 --> POST /v1/messages?beta=true 200 20.1s [in:87356 out:171 cache_read:13016]
687
+ ```
688
+
689
+ - `in` — 输入 token 数(不含缓存命中部分)
690
+ - `out` — 输出 token 数
691
+ - `cache_read` — 缓存命中的 token 数(仅在有缓存时显示)
692
+
693
+ 触发上下文压缩时会额外输出一行:
694
+
695
+ ```
696
+ Truncated: 190385 -> 117537 tokens (-59 msgs)
697
+ ```
698
+
699
+ ### 网络重试
700
+
701
+ 对上游 API 的请求内置了瞬时网络错误重试(TLS 断开、连接重置等):
702
+
703
+ - 最多重试 2 次(共 3 次尝试)
704
+ - 退避间隔:1s、2s
705
+ - 仅重试网络层错误,HTTP 错误码(如 400/500)不重试
706
+
676
707
  ---
677
708
 
678
709
  ## 🐳 Docker 部署
@@ -0,0 +1,3 @@
1
+ import { HTTPError, forwardError } from "./error-SzJ4KHd8.js";
2
+
3
+ export { HTTPError };
@@ -11,7 +11,12 @@ var HTTPError = class extends Error {
11
11
  async function forwardError(c, error) {
12
12
  consola.error("Error occurred:", error);
13
13
  if (error instanceof HTTPError) {
14
- const errorText = await error.response.text();
14
+ let errorText;
15
+ try {
16
+ errorText = await error.response.text();
17
+ } catch {
18
+ errorText = error.message;
19
+ }
15
20
  let errorJson;
16
21
  try {
17
22
  errorJson = JSON.parse(errorText);
@@ -32,4 +37,4 @@ async function forwardError(c, error) {
32
37
 
33
38
  //#endregion
34
39
  export { HTTPError, forwardError };
35
- //# sourceMappingURL=error-CvU5otz-.js.map
40
+ //# sourceMappingURL=error-SzJ4KHd8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-SzJ4KHd8.js","names":["errorText: string","errorJson: unknown"],"sources":["../src/lib/error.ts"],"sourcesContent":["import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nexport class HTTPError extends Error {\n response: Response\n\n constructor(message: string, response: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport async function forwardError(c: Context, error: unknown) {\n consola.error(\"Error occurred:\", error)\n\n if (error instanceof HTTPError) {\n // Try to read error body, but it may already be consumed by the caller\n let errorText: string\n try {\n errorText = await error.response.text()\n } catch {\n // Body already read — fall back to the error message\n errorText = error.message\n }\n let errorJson: unknown\n try {\n errorJson = JSON.parse(errorText)\n } catch {\n errorJson = errorText\n }\n consola.error(\"HTTP error:\", errorJson)\n return c.json(\n {\n error: {\n message: errorText,\n type: \"error\",\n },\n },\n error.response.status as ContentfulStatusCode,\n )\n }\n\n return c.json(\n {\n error: {\n message: (error as Error).message,\n type: \"error\",\n },\n },\n 500,\n )\n}\n"],"mappings":";;;AAKA,IAAa,YAAb,cAA+B,MAAM;CACnC;CAEA,YAAY,SAAiB,UAAoB;AAC/C,QAAM,QAAQ;AACd,OAAK,WAAW;;;AAIpB,eAAsB,aAAa,GAAY,OAAgB;AAC7D,SAAQ,MAAM,mBAAmB,MAAM;AAEvC,KAAI,iBAAiB,WAAW;EAE9B,IAAIA;AACJ,MAAI;AACF,eAAY,MAAM,MAAM,SAAS,MAAM;UACjC;AAEN,eAAY,MAAM;;EAEpB,IAAIC;AACJ,MAAI;AACF,eAAY,KAAK,MAAM,UAAU;UAC3B;AACN,eAAY;;AAEd,UAAQ,MAAM,eAAe,UAAU;AACvC,SAAO,EAAE,KACP,EACE,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,EACD,MAAM,SAAS,OAChB;;AAGH,QAAO,EAAE,KACP,EACE,OAAO;EACL,SAAU,MAAgB;EAC1B,MAAM;EACP,EACF,EACD,IACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-models-uEbEgq0L.js","names":["FALLBACK_MODELS: Array<AntigravityModel>","cachedModels: Array<AntigravityModel> | null","cacheTimestamp: number","models: Array<AntigravityModel>","modelsQuota: Record<string, {\r\n remaining_fraction: number\r\n reset_time: string\r\n percent_remaining: number\r\n }>"],"sources":["../src/services/antigravity/get-models.ts"],"sourcesContent":["/**\r\n * Google Antigravity Models\r\n *\r\n * Provides list of available models from Antigravity.\r\n * Based on: https://github.com/liuw1535/antigravity2api-nodejs\r\n */\r\n\r\n/* eslint-disable require-atomic-updates */\r\n\r\nimport consola from \"consola\"\r\n\r\nimport { getValidAccessToken } from \"./auth\"\r\n\r\n// Antigravity API endpoints\r\nconst ANTIGRAVITY_API_HOST = \"daily-cloudcode-pa.sandbox.googleapis.com\"\r\nconst ANTIGRAVITY_MODELS_URL = `https://${ANTIGRAVITY_API_HOST}/v1internal:fetchAvailableModels`\r\nconst ANTIGRAVITY_USER_AGENT = \"antigravity/1.11.3 windows/amd64\"\r\n\r\nexport interface AntigravityQuotaInfo {\r\n remainingFraction: number\r\n resetTime: string\r\n}\r\n\r\nexport interface AntigravityModel {\r\n id: string\r\n object: string\r\n created: number\r\n owned_by: string\r\n quotaInfo?: AntigravityQuotaInfo\r\n}\r\n\r\nexport interface AntigravityModelsResponse {\r\n object: string\r\n data: Array<AntigravityModel>\r\n}\r\n\r\n/**\r\n * Fallback Antigravity models when API is unavailable\r\n * Updated based on actual API response (December 2024)\r\n */\r\nconst FALLBACK_MODELS: Array<AntigravityModel> = [\r\n // Gemini models\r\n {\r\n id: \"gemini-2.5-pro\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n {\r\n id: \"gemini-2.5-flash\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n {\r\n id: \"gemini-2.5-flash-lite\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n {\r\n id: \"gemini-2.5-flash-thinking\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n {\r\n id: \"gemini-3-pro-low\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n {\r\n id: \"gemini-3-pro-high\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n {\r\n id: \"gemini-3-pro-image\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n {\r\n id: \"gemini-3-flash\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"google\",\r\n },\r\n\r\n // Claude models (via Antigravity)\r\n {\r\n id: \"claude-sonnet-4-5\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"anthropic\",\r\n },\r\n {\r\n id: \"claude-sonnet-4-5-thinking\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"anthropic\",\r\n },\r\n {\r\n id: \"claude-opus-4-5-thinking\",\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: \"anthropic\",\r\n },\r\n]\r\n\r\n// Cache for fetched models\r\nlet cachedModels: Array<AntigravityModel> | null = null\r\nlet cacheTimestamp: number = 0\r\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\r\n\r\n/**\r\n * Fetch models from Antigravity API\r\n */\r\nasync function fetchModelsFromApi(): Promise<Array<AntigravityModel> | null> {\r\n const accessToken = await getValidAccessToken()\r\n\r\n if (!accessToken) {\r\n consola.debug(\"No access token available, using fallback models\")\r\n return null\r\n }\r\n\r\n try {\r\n const response = await fetch(ANTIGRAVITY_MODELS_URL, {\r\n method: \"POST\",\r\n headers: {\r\n Host: ANTIGRAVITY_API_HOST,\r\n \"User-Agent\": ANTIGRAVITY_USER_AGENT,\r\n Authorization: `Bearer ${accessToken}`,\r\n \"Content-Type\": \"application/json\",\r\n \"Accept-Encoding\": \"gzip\",\r\n },\r\n body: JSON.stringify({}),\r\n })\r\n\r\n if (!response.ok) {\r\n consola.warn(`Failed to fetch Antigravity models: ${response.status}`)\r\n return null\r\n }\r\n\r\n // API returns models as object (dictionary), not array\r\n // Format: { \"models\": { \"model-id\": { \"quotaInfo\": {...}, \"apiProvider\": \"...\", ... }, ... } }\r\n const data = (await response.json()) as {\r\n models?: Record<string, {\r\n displayName?: string\r\n maxTokens?: number\r\n apiProvider?: string\r\n model?: string\r\n quotaInfo?: {\r\n remainingFraction?: number\r\n resetTime?: string\r\n }\r\n }>\r\n }\r\n\r\n if (!data.models || typeof data.models !== \"object\") {\r\n consola.warn(\"No models object in response\")\r\n return null\r\n }\r\n\r\n // Convert object to array format\r\n const modelEntries = Object.entries(data.models)\r\n consola.debug(`Antigravity API returned ${modelEntries.length} models`)\r\n\r\n // Filter to only include Gemini and Claude models (skip internal models like chat_20706)\r\n const models: Array<AntigravityModel> = modelEntries\r\n .filter(([modelId, info]) => {\r\n // Only include gemini, learnlm, and claude models\r\n const isPublicModel = modelId.startsWith(\"gemini\") ||\r\n modelId.startsWith(\"learnlm\") ||\r\n modelId.startsWith(\"claude\")\r\n // Filter out models with no remaining quota\r\n const remaining = info.quotaInfo?.remainingFraction ?? 1\r\n return isPublicModel && remaining > 0\r\n })\r\n .map(([modelId, info]) => {\r\n const isGoogle = modelId.startsWith(\"gemini\") || modelId.startsWith(\"learnlm\")\r\n\r\n return {\r\n id: modelId,\r\n object: \"model\",\r\n created: 1700000000,\r\n owned_by: isGoogle ? \"google\" : \"anthropic\",\r\n quotaInfo: info.quotaInfo ? {\r\n remainingFraction: info.quotaInfo.remainingFraction ?? 1,\r\n resetTime: info.quotaInfo.resetTime ?? \"\",\r\n } : undefined,\r\n }\r\n })\r\n\r\n consola.debug(`Fetched ${models.length} models from Antigravity API`)\r\n return models\r\n } catch (error) {\r\n consola.warn(\"Error fetching Antigravity models:\", error)\r\n return null\r\n }\r\n}\r\n\r\n/**\r\n * Get available Antigravity models\r\n */\r\nexport async function getAntigravityModels(): Promise<AntigravityModelsResponse> {\r\n // Check cache\r\n if (cachedModels && Date.now() - cacheTimestamp < CACHE_TTL) {\r\n consola.debug(`Returning ${cachedModels.length} cached Antigravity models`)\r\n return {\r\n object: \"list\",\r\n data: cachedModels,\r\n }\r\n }\r\n\r\n // Try to fetch from API\r\n const apiModels = await fetchModelsFromApi()\r\n\r\n if (apiModels && apiModels.length > 0) {\r\n cachedModels = apiModels\r\n cacheTimestamp = Date.now()\r\n\r\n return {\r\n object: \"list\",\r\n data: apiModels,\r\n }\r\n }\r\n\r\n // Use fallback models\r\n consola.debug(\r\n `Returning ${FALLBACK_MODELS.length} fallback Antigravity models`,\r\n )\r\n\r\n return {\r\n object: \"list\",\r\n data: FALLBACK_MODELS,\r\n }\r\n}\r\n\r\n/**\r\n * Antigravity usage response format (compatible with Copilot usage viewer)\r\n */\r\nexport interface AntigravityUsageResponse {\r\n copilot_plan: string\r\n quota_reset_date: string\r\n quota_snapshots: {\r\n models: Record<string, {\r\n remaining_fraction: number\r\n reset_time: string\r\n percent_remaining: number\r\n }>\r\n }\r\n}\r\n\r\n/**\r\n * Get Antigravity usage/quota information\r\n */\r\nexport async function getAntigravityUsage(): Promise<AntigravityUsageResponse> {\r\n // Force refresh models to get latest quota\r\n cachedModels = null\r\n cacheTimestamp = 0\r\n\r\n const modelsResponse = await getAntigravityModels()\r\n\r\n // Find earliest reset time\r\n let earliestResetTime = \"\"\r\n const modelsQuota: Record<string, {\r\n remaining_fraction: number\r\n reset_time: string\r\n percent_remaining: number\r\n }> = {}\r\n\r\n let modelsWithQuota = 0\r\n for (const model of modelsResponse.data) {\r\n if (model.quotaInfo) {\r\n modelsWithQuota++\r\n const resetTime = model.quotaInfo.resetTime\r\n if (!earliestResetTime || (resetTime && resetTime < earliestResetTime)) {\r\n earliestResetTime = resetTime\r\n }\r\n\r\n modelsQuota[model.id] = {\r\n remaining_fraction: model.quotaInfo.remainingFraction,\r\n reset_time: model.quotaInfo.resetTime,\r\n percent_remaining: Math.round(model.quotaInfo.remainingFraction * 100),\r\n }\r\n }\r\n }\r\n\r\n consola.debug(`Antigravity usage: ${modelsWithQuota}/${modelsResponse.data.length} models have quota info`)\r\n\r\n return {\r\n copilot_plan: \"antigravity\",\r\n quota_reset_date: earliestResetTime,\r\n quota_snapshots: {\r\n models: modelsQuota,\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * Check if a model is a Claude model\r\n */\r\nexport function isClaudeModel(modelId: string): boolean {\r\n return modelId.startsWith(\"claude-\")\r\n}\r\n\r\n/**\r\n * Check if a model is a thinking/reasoning model\r\n */\r\nexport function isThinkingModel(modelId: string): boolean {\r\n return modelId.includes(\"thinking\")\r\n}\r\n\r\n/**\r\n * Check if a model is an image generation model\r\n */\r\nexport function isImageModel(modelId: string): boolean {\r\n return modelId.includes(\"image\")\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"get-models-uEbEgq0L.js","names":["FALLBACK_MODELS: Array<AntigravityModel>","cachedModels: Array<AntigravityModel> | null","cacheTimestamp: number","models: Array<AntigravityModel>","modelsQuota: Record<\n string,\n {\n remaining_fraction: number\n reset_time: string\n percent_remaining: number\n }\n >"],"sources":["../src/services/antigravity/get-models.ts"],"sourcesContent":["/**\n * Google Antigravity Models\n *\n * Provides list of available models from Antigravity.\n * Based on: https://github.com/liuw1535/antigravity2api-nodejs\n */\n\n/* eslint-disable require-atomic-updates */\n\nimport consola from \"consola\"\n\nimport { getValidAccessToken } from \"./auth\"\n\n// Antigravity API endpoints\nconst ANTIGRAVITY_API_HOST = \"daily-cloudcode-pa.sandbox.googleapis.com\"\nconst ANTIGRAVITY_MODELS_URL = `https://${ANTIGRAVITY_API_HOST}/v1internal:fetchAvailableModels`\nconst ANTIGRAVITY_USER_AGENT = \"antigravity/1.11.3 windows/amd64\"\n\nexport interface AntigravityQuotaInfo {\n remainingFraction: number\n resetTime: string\n}\n\nexport interface AntigravityModel {\n id: string\n object: string\n created: number\n owned_by: string\n quotaInfo?: AntigravityQuotaInfo\n}\n\nexport interface AntigravityModelsResponse {\n object: string\n data: Array<AntigravityModel>\n}\n\n/**\n * Fallback Antigravity models when API is unavailable\n * Updated based on actual API response (December 2024)\n */\nconst FALLBACK_MODELS: Array<AntigravityModel> = [\n // Gemini models\n {\n id: \"gemini-2.5-pro\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.5-flash\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.5-flash-lite\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.5-flash-thinking\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-3-pro-low\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-3-pro-high\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-3-pro-image\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-3-flash\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n\n // Claude models (via Antigravity)\n {\n id: \"claude-sonnet-4-5\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-sonnet-4-5-thinking\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-opus-4-5-thinking\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n]\n\n// Cache for fetched models\nlet cachedModels: Array<AntigravityModel> | null = null\nlet cacheTimestamp: number = 0\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\n/**\n * Fetch models from Antigravity API\n */\nasync function fetchModelsFromApi(): Promise<Array<AntigravityModel> | null> {\n const accessToken = await getValidAccessToken()\n\n if (!accessToken) {\n consola.debug(\"No access token available, using fallback models\")\n return null\n }\n\n try {\n const response = await fetch(ANTIGRAVITY_MODELS_URL, {\n method: \"POST\",\n headers: {\n Host: ANTIGRAVITY_API_HOST,\n \"User-Agent\": ANTIGRAVITY_USER_AGENT,\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n \"Accept-Encoding\": \"gzip\",\n },\n body: JSON.stringify({}),\n })\n\n if (!response.ok) {\n consola.warn(`Failed to fetch Antigravity models: ${response.status}`)\n return null\n }\n\n // API returns models as object (dictionary), not array\n // Format: { \"models\": { \"model-id\": { \"quotaInfo\": {...}, \"apiProvider\": \"...\", ... }, ... } }\n const data = (await response.json()) as {\n models?: Record<\n string,\n {\n displayName?: string\n maxTokens?: number\n apiProvider?: string\n model?: string\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n }\n >\n }\n\n if (!data.models || typeof data.models !== \"object\") {\n consola.warn(\"No models object in response\")\n return null\n }\n\n // Convert object to array format\n const modelEntries = Object.entries(data.models)\n consola.debug(`Antigravity API returned ${modelEntries.length} models`)\n\n // Filter to only include Gemini and Claude models (skip internal models like chat_20706)\n const models: Array<AntigravityModel> = modelEntries\n .filter(([modelId, info]) => {\n // Only include gemini, learnlm, and claude models\n const isPublicModel =\n modelId.startsWith(\"gemini\")\n || modelId.startsWith(\"learnlm\")\n || modelId.startsWith(\"claude\")\n // Filter out models with no remaining quota\n const remaining = info.quotaInfo?.remainingFraction ?? 1\n return isPublicModel && remaining > 0\n })\n .map(([modelId, info]) => {\n const isGoogle =\n modelId.startsWith(\"gemini\") || modelId.startsWith(\"learnlm\")\n\n return {\n id: modelId,\n object: \"model\",\n created: 1700000000,\n owned_by: isGoogle ? \"google\" : \"anthropic\",\n quotaInfo:\n info.quotaInfo ?\n {\n remainingFraction: info.quotaInfo.remainingFraction ?? 1,\n resetTime: info.quotaInfo.resetTime ?? \"\",\n }\n : undefined,\n }\n })\n\n consola.debug(`Fetched ${models.length} models from Antigravity API`)\n return models\n } catch (error) {\n consola.warn(\"Error fetching Antigravity models:\", error)\n return null\n }\n}\n\n/**\n * Get available Antigravity models\n */\nexport async function getAntigravityModels(): Promise<AntigravityModelsResponse> {\n // Check cache\n if (cachedModels && Date.now() - cacheTimestamp < CACHE_TTL) {\n consola.debug(`Returning ${cachedModels.length} cached Antigravity models`)\n return {\n object: \"list\",\n data: cachedModels,\n }\n }\n\n // Try to fetch from API\n const apiModels = await fetchModelsFromApi()\n\n if (apiModels && apiModels.length > 0) {\n cachedModels = apiModels\n cacheTimestamp = Date.now()\n\n return {\n object: \"list\",\n data: apiModels,\n }\n }\n\n // Use fallback models\n consola.debug(\n `Returning ${FALLBACK_MODELS.length} fallback Antigravity models`,\n )\n\n return {\n object: \"list\",\n data: FALLBACK_MODELS,\n }\n}\n\n/**\n * Antigravity usage response format (compatible with Copilot usage viewer)\n */\nexport interface AntigravityUsageResponse {\n copilot_plan: string\n quota_reset_date: string\n quota_snapshots: {\n models: Record<\n string,\n {\n remaining_fraction: number\n reset_time: string\n percent_remaining: number\n }\n >\n }\n}\n\n/**\n * Get Antigravity usage/quota information\n */\nexport async function getAntigravityUsage(): Promise<AntigravityUsageResponse> {\n // Force refresh models to get latest quota\n cachedModels = null\n cacheTimestamp = 0\n\n const modelsResponse = await getAntigravityModels()\n\n // Find earliest reset time\n let earliestResetTime = \"\"\n const modelsQuota: Record<\n string,\n {\n remaining_fraction: number\n reset_time: string\n percent_remaining: number\n }\n > = {}\n\n let modelsWithQuota = 0\n for (const model of modelsResponse.data) {\n if (model.quotaInfo) {\n modelsWithQuota++\n const resetTime = model.quotaInfo.resetTime\n if (!earliestResetTime || (resetTime && resetTime < earliestResetTime)) {\n earliestResetTime = resetTime\n }\n\n modelsQuota[model.id] = {\n remaining_fraction: model.quotaInfo.remainingFraction,\n reset_time: model.quotaInfo.resetTime,\n percent_remaining: Math.round(model.quotaInfo.remainingFraction * 100),\n }\n }\n }\n\n consola.debug(\n `Antigravity usage: ${modelsWithQuota}/${modelsResponse.data.length} models have quota info`,\n )\n\n return {\n copilot_plan: \"antigravity\",\n quota_reset_date: earliestResetTime,\n quota_snapshots: {\n models: modelsQuota,\n },\n }\n}\n\n/**\n * Check if a model is a Claude model\n */\nexport function isClaudeModel(modelId: string): boolean {\n return modelId.startsWith(\"claude-\")\n}\n\n/**\n * Check if a model is a thinking/reasoning model\n */\nexport function isThinkingModel(modelId: string): boolean {\n return modelId.includes(\"thinking\")\n}\n\n/**\n * Check if a model is an image generation model\n */\nexport function isImageModel(modelId: string): boolean {\n return modelId.includes(\"image\")\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { state } from "./state-CcLGr8VN.js";
2
- import { HTTPError } from "./error-CvU5otz-.js";
2
+ import { HTTPError } from "./error-SzJ4KHd8.js";
3
3
  import { randomUUID } from "node:crypto";
4
4
 
5
5
  //#region src/lib/api-config.ts
@@ -58,4 +58,4 @@ async function getGitHubUser() {
58
58
 
59
59
  //#endregion
60
60
  export { GITHUB_API_BASE_URL, GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, copilotBaseUrl, copilotHeaders, getGitHubUser, githubHeaders, standardHeaders };
61
- //# sourceMappingURL=get-user-BzIEATcF.js.map
61
+ //# sourceMappingURL=get-user-DEDD9jIs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-user-DEDD9jIs.js","names":["state","headers: Record<string, string>"],"sources":["../src/lib/api-config.ts","../src/services/github/get-user.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst COPILOT_VERSION = \"0.26.7\"\nconst EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`\nconst USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`\n\n// Updated to match latest Zed implementation - 2025-05-01 returns Claude models\nconst API_VERSION = \"2025-05-01\"\n\n// Use the API endpoint from token response if available, otherwise fall back to default\nexport const copilotBaseUrl = (state: State) => {\n if (state.copilotApiEndpoint) {\n return state.copilotApiEndpoint\n }\n return state.accountType === \"individual\" ?\n \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\n}\nexport const copilotHeaders = (state: State, vision: boolean = false) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": \"vscode-chat\",\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"openai-intent\": \"conversation-panel\",\n \"x-github-api-version\": API_VERSION,\n \"x-request-id\": randomUUID(),\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL = \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"x-github-api-version\": API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n","import { GITHUB_API_BASE_URL, standardHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport async function getGitHubUser() {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: {\n authorization: `token ${state.githubToken}`,\n ...standardHeaders(),\n },\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GithubUserResponse\n}\n\n// Trimmed for the sake of simplicity\ninterface GithubUserResponse {\n login: string\n}\n"],"mappings":";;;;;AAIA,MAAa,yBAAyB;CACpC,gBAAgB;CAChB,QAAQ;CACT;AAED,MAAM,kBAAkB;AACxB,MAAM,wBAAwB,gBAAgB;AAC9C,MAAM,aAAa,qBAAqB;AAGxC,MAAM,cAAc;AAGpB,MAAa,kBAAkB,YAAiB;AAC9C,KAAIA,QAAM,mBACR,QAAOA,QAAM;AAEf,QAAOA,QAAM,gBAAgB,eACzB,kCACA,eAAeA,QAAM,YAAY;;AAEvC,MAAa,kBAAkB,SAAc,SAAkB,UAAU;CACvE,MAAMC,UAAkC;EACtC,eAAe,UAAUD,QAAM;EAC/B,gBAAgB,iBAAiB,CAAC;EAClC,0BAA0B;EAC1B,kBAAkB,UAAUA,QAAM;EAClC,yBAAyB;EACzB,cAAc;EACd,iBAAiB;EACjB,wBAAwB;EACxB,gBAAgB,YAAY;EAC5B,uCAAuC;EACxC;AAED,KAAI,OAAQ,SAAQ,4BAA4B;AAEhD,QAAO;;AAGT,MAAa,sBAAsB;AACnC,MAAa,iBAAiB,aAAkB;CAC9C,GAAG,iBAAiB;CACpB,eAAe,SAASA,QAAM;CAC9B,kBAAkB,UAAUA,QAAM;CAClC,yBAAyB;CACzB,cAAc;CACd,wBAAwB;CACxB,uCAAuC;CACxC;AAED,MAAa,kBAAkB;AAC/B,MAAa,mBAAmB;AAChC,MAAa,oBAAoB,CAAC,YAAY,CAAC,KAAK,IAAI;;;;ACrDxD,eAAsB,gBAAgB;CACpC,MAAM,WAAW,MAAM,MAAM,GAAG,oBAAoB,QAAQ,EAC1D,SAAS;EACP,eAAe,SAAS,MAAM;EAC9B,GAAG,iBAAiB;EACrB,EACF,CAAC;AAEF,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,6BAA6B,SAAS;AAE5E,QAAQ,MAAM,SAAS,MAAM"}
@@ -0,0 +1,5 @@
1
+ import "./state-CcLGr8VN.js";
2
+ import { getGitHubUser } from "./get-user-DEDD9jIs.js";
3
+ import "./error-SzJ4KHd8.js";
4
+
5
+ export { getGitHubUser };
package/dist/main.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { PATHS, ensurePaths } from "./paths-CVYLp61D.js";
3
3
  import { state } from "./state-CcLGr8VN.js";
4
- import { GITHUB_API_BASE_URL, copilotBaseUrl, copilotHeaders, githubHeaders } from "./get-user-BzIEATcF.js";
5
- import { HTTPError, forwardError } from "./error-CvU5otz-.js";
6
- import { cacheModels, cacheVSCodeVersion, clearGithubToken, findModel, isNullish, setupCopilotToken, setupGitHubToken, sleep } from "./token-B777vbx8.js";
4
+ import { GITHUB_API_BASE_URL, copilotBaseUrl, copilotHeaders, githubHeaders } from "./get-user-DEDD9jIs.js";
5
+ import { HTTPError, forwardError } from "./error-SzJ4KHd8.js";
6
+ import { cacheModels, cacheVSCodeVersion, clearGithubToken, findModel, isNullish, refreshCopilotToken, setupCopilotToken, setupGitHubToken, sleep } from "./token-CpxbiiIw.js";
7
7
  import { clearAntigravityAuth, disableCurrentAccount, getAntigravityAuthPath, getApiKey, getCurrentProjectId, getValidAccessToken, rotateAccount } from "./auth-CWGl6kMf.js";
8
8
  import { clearZenAuth, getZenAuthPath } from "./auth-BrdL89xk.js";
9
9
  import { getAntigravityModels, getAntigravityUsage, isThinkingModel } from "./get-models-uEbEgq0L.js";
@@ -1197,12 +1197,24 @@ const apiKeyAuthMiddleware = async (c, next) => {
1197
1197
  * Global token usage store for passing usage info from handlers to logger.
1198
1198
  * Handlers call setTokenUsage() when usage is available,
1199
1199
  * logger reads and clears it after await next().
1200
+ *
1201
+ * For streaming responses, usage arrives after next() returns.
1202
+ * In that case the handler calls signalStreamDone() when the stream ends,
1203
+ * and the logger waits for it with a timeout.
1200
1204
  */
1201
1205
  let pendingTokenUsage;
1206
+ let streamDoneResolve;
1202
1207
  function setTokenUsage(usage) {
1203
1208
  pendingTokenUsage = usage;
1204
1209
  }
1205
1210
  /**
1211
+ * Notify the logger that a streaming response has finished sending.
1212
+ * Must be called at the end of streamSSE callbacks.
1213
+ */
1214
+ function signalStreamDone() {
1215
+ streamDoneResolve?.();
1216
+ }
1217
+ /**
1206
1218
  * Get timestamp string in format HH:mm:ss
1207
1219
  */
1208
1220
  function getTime() {
@@ -1252,9 +1264,16 @@ function modelLogger() {
1252
1264
  const modelPrefix = model ? `[${model}] ` : "";
1253
1265
  const startTime = getTime();
1254
1266
  pendingTokenUsage = void 0;
1267
+ const localStreamDone = new Promise((resolve) => {
1268
+ streamDoneResolve = resolve;
1269
+ });
1255
1270
  console.log(`${modelPrefix}${startTime} <-- ${method} ${fullPath}`);
1256
1271
  const start$1 = Date.now();
1257
1272
  await next();
1273
+ if (c.res.headers.get("content-type")?.includes("text/event-stream") && !pendingTokenUsage) {
1274
+ const timeout = new Promise((resolve) => setTimeout(resolve, 12e4));
1275
+ await Promise.race([localStreamDone, timeout]);
1276
+ }
1258
1277
  const duration = Date.now() - start$1;
1259
1278
  const endTime = getTime();
1260
1279
  const usage = pendingTokenUsage;
@@ -2944,10 +2963,10 @@ const createChatCompletions = async (payload) => {
2944
2963
  if (!state.copilotToken) throw new Error("Copilot token not found");
2945
2964
  const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
2946
2965
  const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
2947
- const headers = {
2966
+ const buildHeaders = () => ({
2948
2967
  ...copilotHeaders(state, enableVision),
2949
2968
  "X-Initiator": isAgentCall ? "agent" : "user"
2950
- };
2969
+ });
2951
2970
  consola.debug("Sending request to Copilot:", {
2952
2971
  model: payload.model,
2953
2972
  endpoint: `${copilotBaseUrl(state)}/chat/completions`
@@ -2957,16 +2976,16 @@ const createChatCompletions = async (payload) => {
2957
2976
  ...payload,
2958
2977
  stream_options: { include_usage: true }
2959
2978
  } : payload;
2960
- const fetchOptions = {
2961
- method: "POST",
2962
- headers,
2963
- body: JSON.stringify(body)
2964
- };
2979
+ const bodyString = JSON.stringify(body);
2965
2980
  const maxRetries = 2;
2966
2981
  let lastError;
2967
2982
  let response;
2968
2983
  for (let attempt = 0; attempt <= maxRetries; attempt++) try {
2969
- response = await fetch(url, fetchOptions);
2984
+ response = await fetch(url, {
2985
+ method: "POST",
2986
+ headers: buildHeaders(),
2987
+ body: bodyString
2988
+ });
2970
2989
  break;
2971
2990
  } catch (error) {
2972
2991
  lastError = error;
@@ -2977,6 +2996,19 @@ const createChatCompletions = async (payload) => {
2977
2996
  }
2978
2997
  }
2979
2998
  if (!response) throw lastError;
2999
+ if (response.status === 401) {
3000
+ consola.warn("Copilot token expired, refreshing and retrying...");
3001
+ try {
3002
+ await refreshCopilotToken();
3003
+ response = await fetch(url, {
3004
+ method: "POST",
3005
+ headers: buildHeaders(),
3006
+ body: bodyString
3007
+ });
3008
+ } catch (refreshError) {
3009
+ consola.error("Failed to refresh token:", refreshError);
3010
+ }
3011
+ }
2980
3012
  if (!response.ok) {
2981
3013
  const errorBody = await response.text();
2982
3014
  consola.error("Failed to create chat completions", {
@@ -3006,7 +3038,7 @@ async function processPayloadTokens(payload) {
3006
3038
  }
3007
3039
  try {
3008
3040
  const tokenCount = await getTokenCount(payload, selectedModel);
3009
- consola.info("Current token count:", tokenCount);
3041
+ consola.debug("Current token count:", tokenCount);
3010
3042
  const truncated = await truncateMessages(payload, selectedModel);
3011
3043
  if (isNullish(truncated.max_tokens)) {
3012
3044
  const withMaxTokens = {
@@ -3053,12 +3085,12 @@ async function handleCompletion$1(c) {
3053
3085
  cacheReadTokens: parsed.usage.prompt_tokens_details?.cached_tokens
3054
3086
  };
3055
3087
  setTokenUsage(usage);
3056
- console.log(`[${formatTokenUsage(usage)}]`);
3057
3088
  }
3058
3089
  }
3059
3090
  } catch {}
3060
3091
  await stream.writeSSE(chunk);
3061
3092
  }
3093
+ signalStreamDone();
3062
3094
  });
3063
3095
  }
3064
3096
  const isNonStreaming$1 = (response) => Object.hasOwn(response, "choices");
@@ -3341,7 +3373,7 @@ async function handleCountTokens(c) {
3341
3373
  let finalTokenCount = tokenCount.input + tokenCount.output;
3342
3374
  if (anthropicPayload.model.startsWith("claude")) finalTokenCount = Math.round(finalTokenCount * 1.15);
3343
3375
  else if (anthropicPayload.model.startsWith("grok")) finalTokenCount = Math.round(finalTokenCount * 1.03);
3344
- consola.info("Token count:", finalTokenCount);
3376
+ consola.debug("Token count:", finalTokenCount);
3345
3377
  return c.json({ input_tokens: finalTokenCount });
3346
3378
  } catch (error) {
3347
3379
  consola.error("Error counting tokens:", error);
@@ -3530,13 +3562,13 @@ async function handleCompletion(c) {
3530
3562
  cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens
3531
3563
  };
3532
3564
  setTokenUsage(usage);
3533
- console.log(`[${formatTokenUsage(usage)}]`);
3534
3565
  }
3535
3566
  for (const event of events$1) await stream.writeSSE({
3536
3567
  event: event.type,
3537
3568
  data: JSON.stringify(event)
3538
3569
  });
3539
3570
  }
3571
+ signalStreamDone();
3540
3572
  });
3541
3573
  }
3542
3574
  const isNonStreaming = (response) => Object.hasOwn(response, "choices");
@@ -4109,7 +4141,7 @@ async function runServer(options$1) {
4109
4141
  state.githubToken = options$1.githubToken;
4110
4142
  consola.info("Using provided GitHub token");
4111
4143
  try {
4112
- const { getGitHubUser } = await import("./get-user-CsQCc3Qx.js");
4144
+ const { getGitHubUser } = await import("./get-user-HhhC3uQr.js");
4113
4145
  const user = await getGitHubUser();
4114
4146
  consola.info(`Logged in as ${user.login}`);
4115
4147
  } catch (error) {
@@ -4120,10 +4152,10 @@ async function runServer(options$1) {
4120
4152
  try {
4121
4153
  await setupCopilotToken();
4122
4154
  } catch (error) {
4123
- const { HTTPError: HTTPError$1 } = await import("./error-CsShqJjE.js");
4155
+ const { HTTPError: HTTPError$1 } = await import("./error-DNWWcl_s.js");
4124
4156
  if (error instanceof HTTPError$1 && error.response.status === 401) {
4125
4157
  consola.error("Failed to get Copilot token - GitHub token may be invalid or Copilot access revoked");
4126
- const { clearGithubToken: clearGithubToken$1 } = await import("./token-CCg0yU7a.js");
4158
+ const { clearGithubToken: clearGithubToken$1 } = await import("./token-DkNaoDp7.js");
4127
4159
  await clearGithubToken$1();
4128
4160
  consola.info("Please restart to re-authenticate");
4129
4161
  }