@jeffreycao/copilot-api 1.9.5 → 1.9.7

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
@@ -317,11 +317,21 @@ The following command line options are available for the `start` command:
317
317
  "apiKey": "sk-your-dashscope-key",
318
318
  "models": {
319
319
  "qwen3.6-plus": {
320
+ "temperature": 1,
321
+ "topP": 0.95,
322
+ "topK": 20,
320
323
  "extraBody": {
321
324
  "preserve_thinking": true
322
325
  },
323
- "supportPdf": false,
324
- "toolContentSupportType": []
326
+ "contextCache": true
327
+ },
328
+ "glm-5.1": {
329
+ "temperature": 0.7,
330
+ "topP": 0.95,
331
+ "contextCache": false,
332
+ "extraBody": {
333
+ "preserve_thinking": true
334
+ }
325
335
  }
326
336
  }
327
337
  }
@@ -358,6 +368,7 @@ The following command line options are available for the `start` command:
358
368
  - `topP` (optional): Default top_p value used when the request does not specify one.
359
369
  - `topK` (optional): Default top_k value used when the request does not specify one.
360
370
  - `extraBody` (optional): Dynamic fields merged into the upstream request body for that model. Request body fields with the same name take precedence. OpenAI-compatible providers can use this for fields such as `enable_thinking`, `preserve_thinking`, `reasoning_effort`.
371
+ - `contextCache` (optional): Defaults to `true` for OpenAI-compatible providers. This enables Alibaba Cloud Model Studio/DashScope explicit context cache by injecting `cache_control: { "type": "ephemeral" }` on up to 4 content blocks using the Context Cache format. The cache breakpoint strategy matches opencode's main provider flow: the first 2 system messages plus the last 2 non-system messages. Marked string content is converted to text content part arrays for `system` / `user` / `assistant` / `tool` messages; existing array content is marked on the last part. Set this to `false` when the model already supports implicit caching, or when the upstream does not accept this explicit-cache extension field.
361
372
  - `supportPdf` (optional): Controls whether the model supports PDF/document content. Defaults to `false`; unsupported PDFs are converted to a text notice. Set it to `true` to send PDF/document blocks as OpenAI Chat Completions file parts.
362
373
  - `toolContentSupportType` (optional): Tool result content capabilities for that model, as an array of `array`, `image`, and `pdf`. Provider routes default to string-only tool content when omitted. If `supportPdf` is `true` but this list does not include `pdf`, file parts in tool results are moved to user role messages. This provider default does not change the Copilot main flow, which continues to support array + image and not PDF.
363
374
  - **smallModel:** Fallback model used for tool-less warmup messages (e.g., Claude Code probe requests) to avoid spending premium requests; defaults to gpt-5-mini.
package/README.zh-CN.md CHANGED
@@ -321,11 +321,21 @@ Copilot API 现在使用子命令结构,主要命令包括:
321
321
  "apiKey": "sk-your-dashscope-key",
322
322
  "models": {
323
323
  "qwen3.6-plus": {
324
+ "temperature": 1,
325
+ "topP": 0.95,
326
+ "topK": 20,
324
327
  "extraBody": {
325
328
  "preserve_thinking": true
326
329
  },
327
- "supportPdf": false,
328
- "toolContentSupportType": []
330
+ "contextCache": true
331
+ },
332
+ "glm-5.1": {
333
+ "temperature": 0.7,
334
+ "topP": 0.95,
335
+ "contextCache": false,
336
+ "extraBody": {
337
+ "preserve_thinking": true
338
+ }
329
339
  }
330
340
  }
331
341
  }
@@ -362,6 +372,7 @@ Copilot API 现在使用子命令结构,主要命令包括:
362
372
  - `topP`:可选,当请求未指定时使用的默认 `top_p`。
363
373
  - `topK`:可选,当请求未指定时使用的默认 `top_k`。
364
374
  - `extraBody`:可选,按模型合入上游请求体的动态字段;请求体显式同名字段优先。OpenAI 兼容 provider 可用它配置 `enable_thinking`、`preserve_thinking`、`reasoning_effort` 等字段。
375
+ - `contextCache`:可选,OpenAI 兼容 provider 默认 `true`,用于启用阿里云百炼/DashScope 的显式缓存(explicit context cache),会按其 Context Cache 格式在最多 4 个 content block 上注入 `cache_control: { "type": "ephemeral" }`。缓存断点策略与 opencode 主链路保持一致:前 2 条 system 消息 + 最后 2 条非 system 消息。标记字符串 content 时会把 `system` / `user` / `assistant` / `tool` 消息转换为 text content part 数组;已有数组 content 则标记最后一个 part。如果模型本身已经支持隐式缓存,或上游不支持该显式缓存扩展字段,可在模型配置中设为 `false`。
365
376
  - `supportPdf`:可选,控制该模型是否支持 PDF/document content。默认 `false`,不支持时会把 PDF 转成提示文本;设为 `true` 时会把 PDF/document 转成 OpenAI Chat Completions 的 file part。
366
377
  - `toolContentSupportType`:可选,配置该模型的 tool result content 支持能力,值为 `array`、`image`、`pdf` 的数组。provider 侧未配置时默认只发送 string tool content。若 `supportPdf` 为 `true` 但这里不包含 `pdf`,tool result 里的 file part 会被转成 user role 消息。Copilot 主链路不使用这个 provider 默认,仍按 array + image 且不支持 PDF 的能力处理。
367
378
  - **smallModel:** 无工具预热消息的回退模型(例如 Claude Code 的探测请求),用于避免消耗 premium requests;默认是 `gpt-5-mini`。
@@ -1 +1 @@
1
- {"version":3,"file":"config-BJt9unC0.js","names":["defaultConfig: AppConfig","cachedConfig: AppConfig | null"],"sources":["../src/lib/config.ts"],"sourcesContent":["import consola from \"consola\"\nimport fs from \"node:fs\"\n\nimport { PATHS } from \"./paths\"\n\nexport interface AppConfig {\n auth?: {\n apiKeys?: Array<string>\n }\n providers?: Record<string, ProviderConfig>\n extraPrompts?: Record<string, string>\n smallModel?: string\n responsesApiContextManagementModels?: Array<string>\n modelReasoningEfforts?: Record<\n string,\n \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"\n >\n useFunctionApplyPatch?: boolean\n useMessagesApi?: boolean\n anthropicApiKey?: string\n useResponsesApiWebSearch?: boolean\n claudeTokenMultiplier?: number\n}\n\nexport interface ModelConfig {\n temperature?: number\n topP?: number\n topK?: number\n extraBody?: Record<string, unknown>\n supportPdf?: boolean\n toolContentSupportType?: Array<ToolContentSupportType>\n}\n\nexport type ProviderAuthType = \"authorization\" | \"x-api-key\"\nexport type ProviderType = \"anthropic\" | \"openai-compatible\"\nexport type ToolContentSupportType = \"array\" | \"image\" | \"pdf\"\n\nexport interface ProviderConfig {\n type?: string\n enabled?: boolean\n baseUrl?: string\n apiKey?: string\n authType?: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nexport interface ResolvedProviderConfig {\n name: string\n type: ProviderType\n baseUrl: string\n apiKey: string\n authType: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nconst gpt5ExplorationPrompt = `## Exploration and reading files\n- **Think first.** Before any tool call, decide ALL files/resources you will need.\n- **Batch everything.** If you need multiple files (even from different places), read them together.\n- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.\n- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**\n- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`\n\nconst gpt5CommentaryPrompt = `# Working with the user\n\nYou interact with the user through a terminal. You have 2 ways of communicating with the users: \n- Share intermediary updates in \\`commentary\\` channel. \n- After you have completed all your work, send a message to the \\`final\\` channel. \n\n## Intermediary updates\n\n- Intermediary updates go to the \\`commentary\\` channel.\n- User updates are short updates while you are working, they are NOT final answers.\n- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.\n- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements (“Done —”, “Got it”, “Great question, ”) or framing phrases.\n- You provide user updates frequently, every 20s.\n- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as \"Got it -\" or \"Understood -\" etc.\n- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.\n- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).\n- Before performing file edits of any kind, you provide updates explaining what edits you are making.\n- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.\n- Tone of your updates MUST match your personality.`\n\nconst defaultConfig: AppConfig = {\n auth: {\n apiKeys: [],\n },\n providers: {},\n extraPrompts: {\n \"gpt-5-mini\": gpt5ExplorationPrompt,\n \"gpt-5.3-codex\": gpt5CommentaryPrompt,\n \"gpt-5.4-mini\": gpt5CommentaryPrompt,\n \"gpt-5.4\": gpt5CommentaryPrompt,\n \"gpt-5.5\": gpt5CommentaryPrompt,\n },\n smallModel: \"gpt-5-mini\",\n responsesApiContextManagementModels: [],\n modelReasoningEfforts: {\n \"gpt-5-mini\": \"low\",\n \"gpt-5.3-codex\": \"xhigh\",\n \"gpt-5.4-mini\": \"xhigh\",\n \"gpt-5.4\": \"xhigh\",\n \"gpt-5.5\": \"xhigh\",\n },\n useFunctionApplyPatch: true,\n useMessagesApi: true,\n useResponsesApiWebSearch: true,\n}\n\nlet cachedConfig: AppConfig | null = null\n\nfunction ensureConfigFile(): void {\n try {\n fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK | fs.constants.W_OK)\n } catch {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n try {\n fs.chmodSync(PATHS.CONFIG_PATH, 0o600)\n } catch {\n return\n }\n }\n}\n\nfunction readConfigFromDisk(): AppConfig {\n ensureConfigFile()\n try {\n const raw = fs.readFileSync(PATHS.CONFIG_PATH, \"utf8\")\n if (!raw.trim()) {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n return defaultConfig\n }\n return JSON.parse(raw) as AppConfig\n } catch (error) {\n consola.error(\"Failed to read config file, using default config\", error)\n return defaultConfig\n }\n}\n\nfunction mergeDefaultConfig(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const extraPrompts = config.extraPrompts ?? {}\n const defaultExtraPrompts = defaultConfig.extraPrompts ?? {}\n const modelReasoningEfforts = config.modelReasoningEfforts ?? {}\n const defaultModelReasoningEfforts = defaultConfig.modelReasoningEfforts ?? {}\n\n const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter(\n (model) => !Object.hasOwn(extraPrompts, model),\n )\n\n const missingReasoningEffortModels = Object.keys(\n defaultModelReasoningEfforts,\n ).filter((model) => !Object.hasOwn(modelReasoningEfforts, model))\n\n const hasExtraPromptChanges = missingExtraPromptModels.length > 0\n const hasReasoningEffortChanges = missingReasoningEffortModels.length > 0\n\n if (!hasExtraPromptChanges && !hasReasoningEffortChanges) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n extraPrompts: {\n ...defaultExtraPrompts,\n ...extraPrompts,\n },\n modelReasoningEfforts: {\n ...defaultModelReasoningEfforts,\n ...modelReasoningEfforts,\n },\n },\n changed: true,\n }\n}\n\nexport function mergeConfigWithDefaults(): AppConfig {\n const config = readConfigFromDisk()\n const { mergedConfig, changed } = mergeDefaultConfig(config)\n\n if (changed) {\n try {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(mergedConfig, null, 2)}\\n`,\n \"utf8\",\n )\n } catch (writeError) {\n consola.warn(\n \"Failed to write merged extraPrompts to config file\",\n writeError,\n )\n }\n }\n\n cachedConfig = mergedConfig\n return mergedConfig\n}\n\nexport function getConfig(): AppConfig {\n cachedConfig ??= readConfigFromDisk()\n return cachedConfig\n}\n\nexport function getExtraPromptForModel(model: string): string {\n const config = getConfig()\n return config.extraPrompts?.[model] ?? \"\"\n}\n\nexport function getSmallModel(): string {\n const config = getConfig()\n return config.smallModel ?? \"gpt-5-mini\"\n}\n\nexport function getResponsesApiContextManagementModels(): Array<string> {\n const config = getConfig()\n return (\n config.responsesApiContextManagementModels\n ?? defaultConfig.responsesApiContextManagementModels\n ?? []\n )\n}\n\nexport function isResponsesApiContextManagementModel(model: string): boolean {\n return getResponsesApiContextManagementModels().includes(model)\n}\n\nexport function getReasoningEffortForModel(\n model: string,\n): \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" {\n const config = getConfig()\n return config.modelReasoningEfforts?.[model] ?? \"high\"\n}\n\nexport function normalizeProviderBaseUrl(url: string): string {\n return url.trim().replace(/\\/+$/u, \"\")\n}\n\nfunction getDefaultProviderAuthType(\n providerType: ProviderType,\n): ProviderAuthType {\n return providerType === \"openai-compatible\" ? \"authorization\" : \"x-api-key\"\n}\n\nexport function resolveProviderAuthType(\n providerName: string,\n authType: string | undefined,\n providerType: ProviderType,\n): ProviderAuthType {\n if (authType === undefined) {\n return getDefaultProviderAuthType(providerType)\n }\n\n if (authType === \"x-api-key\") {\n return \"x-api-key\"\n }\n\n if (authType === \"authorization\") {\n return authType\n }\n\n consola.warn(\n `Provider ${providerName} has invalid authType '${authType}', falling back to ${getDefaultProviderAuthType(providerType)}`,\n )\n return getDefaultProviderAuthType(providerType)\n}\n\nexport function getProviderConfig(name: string): ResolvedProviderConfig | null {\n const providerName = name.trim()\n if (!providerName) {\n return null\n }\n\n const config = getConfig()\n const provider = config.providers?.[providerName]\n if (!provider) {\n return null\n }\n\n if (provider.enabled === false) {\n return null\n }\n\n const type = provider.type ?? \"anthropic\"\n if (type !== \"anthropic\" && type !== \"openai-compatible\") {\n consola.warn(\n `Provider ${providerName} is ignored because type '${type}' is not supported`,\n )\n return null\n }\n\n const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? \"\")\n const apiKey = (provider.apiKey ?? \"\").trim()\n const authType = resolveProviderAuthType(\n providerName,\n provider.authType,\n type,\n )\n if (!baseUrl || !apiKey) {\n consola.warn(\n `Provider ${providerName} is enabled but missing baseUrl or apiKey`,\n )\n return null\n }\n\n return {\n name: providerName,\n type,\n baseUrl,\n apiKey,\n authType,\n models: provider.models,\n adjustInputTokens: provider.adjustInputTokens,\n }\n}\n\nexport function listEnabledProviders(): Array<string> {\n const config = getConfig()\n const providerNames = Object.keys(config.providers ?? {})\n return providerNames.filter((name) => getProviderConfig(name) !== null)\n}\n\nexport function isMessagesApiEnabled(): boolean {\n const config = getConfig()\n return config.useMessagesApi ?? true\n}\n\nexport function getAnthropicApiKey(): string | undefined {\n const config = getConfig()\n return config.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? undefined\n}\n\nexport function isResponsesApiWebSearchEnabled(): boolean {\n const config = getConfig()\n return config.useResponsesApiWebSearch ?? true\n}\n\nexport function getClaudeTokenMultiplier(): number {\n const config = getConfig()\n return config.claudeTokenMultiplier ?? 1.15\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-BJt9unC0.js","names":["defaultConfig: AppConfig","cachedConfig: AppConfig | null"],"sources":["../src/lib/config.ts"],"sourcesContent":["import consola from \"consola\"\nimport fs from \"node:fs\"\n\nimport { PATHS } from \"./paths\"\n\nexport interface AppConfig {\n auth?: {\n apiKeys?: Array<string>\n }\n providers?: Record<string, ProviderConfig>\n extraPrompts?: Record<string, string>\n smallModel?: string\n responsesApiContextManagementModels?: Array<string>\n modelReasoningEfforts?: Record<\n string,\n \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"\n >\n useFunctionApplyPatch?: boolean\n useMessagesApi?: boolean\n anthropicApiKey?: string\n useResponsesApiWebSearch?: boolean\n claudeTokenMultiplier?: number\n}\n\nexport interface ModelConfig {\n temperature?: number\n topP?: number\n topK?: number\n extraBody?: Record<string, unknown>\n contextCache?: boolean\n supportPdf?: boolean\n toolContentSupportType?: Array<ToolContentSupportType>\n}\n\nexport type ProviderAuthType = \"authorization\" | \"x-api-key\"\nexport type ProviderType = \"anthropic\" | \"openai-compatible\"\nexport type ToolContentSupportType = \"array\" | \"image\" | \"pdf\"\n\nexport interface ProviderConfig {\n type?: string\n enabled?: boolean\n baseUrl?: string\n apiKey?: string\n authType?: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nexport interface ResolvedProviderConfig {\n name: string\n type: ProviderType\n baseUrl: string\n apiKey: string\n authType: ProviderAuthType\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nconst gpt5ExplorationPrompt = `## Exploration and reading files\n- **Think first.** Before any tool call, decide ALL files/resources you will need.\n- **Batch everything.** If you need multiple files (even from different places), read them together.\n- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.\n- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**\n- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`\n\nconst gpt5CommentaryPrompt = `# Working with the user\n\nYou interact with the user through a terminal. You have 2 ways of communicating with the users: \n- Share intermediary updates in \\`commentary\\` channel. \n- After you have completed all your work, send a message to the \\`final\\` channel. \n\n## Intermediary updates\n\n- Intermediary updates go to the \\`commentary\\` channel.\n- User updates are short updates while you are working, they are NOT final answers.\n- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.\n- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements (“Done —”, “Got it”, “Great question, ”) or framing phrases.\n- You provide user updates frequently, every 20s.\n- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as \"Got it -\" or \"Understood -\" etc.\n- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.\n- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).\n- Before performing file edits of any kind, you provide updates explaining what edits you are making.\n- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.\n- Tone of your updates MUST match your personality.`\n\nconst defaultConfig: AppConfig = {\n auth: {\n apiKeys: [],\n },\n providers: {},\n extraPrompts: {\n \"gpt-5-mini\": gpt5ExplorationPrompt,\n \"gpt-5.3-codex\": gpt5CommentaryPrompt,\n \"gpt-5.4-mini\": gpt5CommentaryPrompt,\n \"gpt-5.4\": gpt5CommentaryPrompt,\n \"gpt-5.5\": gpt5CommentaryPrompt,\n },\n smallModel: \"gpt-5-mini\",\n responsesApiContextManagementModels: [],\n modelReasoningEfforts: {\n \"gpt-5-mini\": \"low\",\n \"gpt-5.3-codex\": \"xhigh\",\n \"gpt-5.4-mini\": \"xhigh\",\n \"gpt-5.4\": \"xhigh\",\n \"gpt-5.5\": \"xhigh\",\n },\n useFunctionApplyPatch: true,\n useMessagesApi: true,\n useResponsesApiWebSearch: true,\n}\n\nlet cachedConfig: AppConfig | null = null\n\nfunction ensureConfigFile(): void {\n try {\n fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK | fs.constants.W_OK)\n } catch {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n try {\n fs.chmodSync(PATHS.CONFIG_PATH, 0o600)\n } catch {\n return\n }\n }\n}\n\nfunction readConfigFromDisk(): AppConfig {\n ensureConfigFile()\n try {\n const raw = fs.readFileSync(PATHS.CONFIG_PATH, \"utf8\")\n if (!raw.trim()) {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n return defaultConfig\n }\n return JSON.parse(raw) as AppConfig\n } catch (error) {\n consola.error(\"Failed to read config file, using default config\", error)\n return defaultConfig\n }\n}\n\nfunction mergeDefaultConfig(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const extraPrompts = config.extraPrompts ?? {}\n const defaultExtraPrompts = defaultConfig.extraPrompts ?? {}\n const modelReasoningEfforts = config.modelReasoningEfforts ?? {}\n const defaultModelReasoningEfforts = defaultConfig.modelReasoningEfforts ?? {}\n\n const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter(\n (model) => !Object.hasOwn(extraPrompts, model),\n )\n\n const missingReasoningEffortModels = Object.keys(\n defaultModelReasoningEfforts,\n ).filter((model) => !Object.hasOwn(modelReasoningEfforts, model))\n\n const hasExtraPromptChanges = missingExtraPromptModels.length > 0\n const hasReasoningEffortChanges = missingReasoningEffortModels.length > 0\n\n if (!hasExtraPromptChanges && !hasReasoningEffortChanges) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n extraPrompts: {\n ...defaultExtraPrompts,\n ...extraPrompts,\n },\n modelReasoningEfforts: {\n ...defaultModelReasoningEfforts,\n ...modelReasoningEfforts,\n },\n },\n changed: true,\n }\n}\n\nexport function mergeConfigWithDefaults(): AppConfig {\n const config = readConfigFromDisk()\n const { mergedConfig, changed } = mergeDefaultConfig(config)\n\n if (changed) {\n try {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(mergedConfig, null, 2)}\\n`,\n \"utf8\",\n )\n } catch (writeError) {\n consola.warn(\n \"Failed to write merged extraPrompts to config file\",\n writeError,\n )\n }\n }\n\n cachedConfig = mergedConfig\n return mergedConfig\n}\n\nexport function getConfig(): AppConfig {\n cachedConfig ??= readConfigFromDisk()\n return cachedConfig\n}\n\nexport function getExtraPromptForModel(model: string): string {\n const config = getConfig()\n return config.extraPrompts?.[model] ?? \"\"\n}\n\nexport function getSmallModel(): string {\n const config = getConfig()\n return config.smallModel ?? \"gpt-5-mini\"\n}\n\nexport function getResponsesApiContextManagementModels(): Array<string> {\n const config = getConfig()\n return (\n config.responsesApiContextManagementModels\n ?? defaultConfig.responsesApiContextManagementModels\n ?? []\n )\n}\n\nexport function isResponsesApiContextManagementModel(model: string): boolean {\n return getResponsesApiContextManagementModels().includes(model)\n}\n\nexport function getReasoningEffortForModel(\n model: string,\n): \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" {\n const config = getConfig()\n return config.modelReasoningEfforts?.[model] ?? \"high\"\n}\n\nexport function normalizeProviderBaseUrl(url: string): string {\n return url.trim().replace(/\\/+$/u, \"\")\n}\n\nfunction getDefaultProviderAuthType(\n providerType: ProviderType,\n): ProviderAuthType {\n return providerType === \"openai-compatible\" ? \"authorization\" : \"x-api-key\"\n}\n\nexport function resolveProviderAuthType(\n providerName: string,\n authType: string | undefined,\n providerType: ProviderType,\n): ProviderAuthType {\n if (authType === undefined) {\n return getDefaultProviderAuthType(providerType)\n }\n\n if (authType === \"x-api-key\") {\n return \"x-api-key\"\n }\n\n if (authType === \"authorization\") {\n return authType\n }\n\n consola.warn(\n `Provider ${providerName} has invalid authType '${authType}', falling back to ${getDefaultProviderAuthType(providerType)}`,\n )\n return getDefaultProviderAuthType(providerType)\n}\n\nexport function getProviderConfig(name: string): ResolvedProviderConfig | null {\n const providerName = name.trim()\n if (!providerName) {\n return null\n }\n\n const config = getConfig()\n const provider = config.providers?.[providerName]\n if (!provider) {\n return null\n }\n\n if (provider.enabled === false) {\n return null\n }\n\n const type = provider.type ?? \"anthropic\"\n if (type !== \"anthropic\" && type !== \"openai-compatible\") {\n consola.warn(\n `Provider ${providerName} is ignored because type '${type}' is not supported`,\n )\n return null\n }\n\n const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? \"\")\n const apiKey = (provider.apiKey ?? \"\").trim()\n const authType = resolveProviderAuthType(\n providerName,\n provider.authType,\n type,\n )\n if (!baseUrl || !apiKey) {\n consola.warn(\n `Provider ${providerName} is enabled but missing baseUrl or apiKey`,\n )\n return null\n }\n\n return {\n name: providerName,\n type,\n baseUrl,\n apiKey,\n authType,\n models: provider.models,\n adjustInputTokens: provider.adjustInputTokens,\n }\n}\n\nexport function listEnabledProviders(): Array<string> {\n const config = getConfig()\n const providerNames = Object.keys(config.providers ?? {})\n return providerNames.filter((name) => getProviderConfig(name) !== null)\n}\n\nexport function isMessagesApiEnabled(): boolean {\n const config = getConfig()\n return config.useMessagesApi ?? true\n}\n\nexport function getAnthropicApiKey(): string | undefined {\n const config = getConfig()\n return config.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? undefined\n}\n\nexport function isResponsesApiWebSearchEnabled(): boolean {\n const config = getConfig()\n return config.useResponsesApiWebSearch ?? true\n}\n\nexport function getClaudeTokenMultiplier(): number {\n const config = getConfig()\n return config.claudeTokenMultiplier ?? 1.15\n}\n"],"mappings":";;;;;AA0DA,MAAM,wBAAwB;;;;;;AAO9B,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;AAoB7B,MAAMA,gBAA2B;CAC/B,MAAM,EACJ,SAAS,EAAE,EACZ;CACD,WAAW,EAAE;CACb,cAAc;EACZ,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,WAAW;EACX,WAAW;EACZ;CACD,YAAY;CACZ,qCAAqC,EAAE;CACvC,uBAAuB;EACrB,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,WAAW;EACX,WAAW;EACZ;CACD,uBAAuB;CACvB,gBAAgB;CAChB,0BAA0B;CAC3B;AAED,IAAIC,eAAiC;AAErC,SAAS,mBAAyB;AAChC,KAAI;AACF,KAAG,WAAW,MAAM,aAAa,GAAG,UAAU,OAAO,GAAG,UAAU,KAAK;SACjE;AACN,KAAG,UAAU,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;AAChD,KAAG,cACD,MAAM,aACN,GAAG,KAAK,UAAU,eAAe,MAAM,EAAE,CAAC,KAC1C,OACD;AACD,MAAI;AACF,MAAG,UAAU,MAAM,aAAa,IAAM;UAChC;AACN;;;;AAKN,SAAS,qBAAgC;AACvC,mBAAkB;AAClB,KAAI;EACF,MAAM,MAAM,GAAG,aAAa,MAAM,aAAa,OAAO;AACtD,MAAI,CAAC,IAAI,MAAM,EAAE;AACf,MAAG,cACD,MAAM,aACN,GAAG,KAAK,UAAU,eAAe,MAAM,EAAE,CAAC,KAC1C,OACD;AACD,UAAO;;AAET,SAAO,KAAK,MAAM,IAAI;UACf,OAAO;AACd,UAAQ,MAAM,oDAAoD,MAAM;AACxE,SAAO;;;AAIX,SAAS,mBAAmB,QAG1B;CACA,MAAM,eAAe,OAAO,gBAAgB,EAAE;CAC9C,MAAM,sBAAsB,cAAc,gBAAgB,EAAE;CAC5D,MAAM,wBAAwB,OAAO,yBAAyB,EAAE;CAChE,MAAM,+BAA+B,cAAc,yBAAyB,EAAE;CAE9E,MAAM,2BAA2B,OAAO,KAAK,oBAAoB,CAAC,QAC/D,UAAU,CAAC,OAAO,OAAO,cAAc,MAAM,CAC/C;CAED,MAAM,+BAA+B,OAAO,KAC1C,6BACD,CAAC,QAAQ,UAAU,CAAC,OAAO,OAAO,uBAAuB,MAAM,CAAC;CAEjE,MAAM,wBAAwB,yBAAyB,SAAS;CAChE,MAAM,4BAA4B,6BAA6B,SAAS;AAExE,KAAI,CAAC,yBAAyB,CAAC,0BAC7B,QAAO;EAAE,cAAc;EAAQ,SAAS;EAAO;AAGjD,QAAO;EACL,cAAc;GACZ,GAAG;GACH,cAAc;IACZ,GAAG;IACH,GAAG;IACJ;GACD,uBAAuB;IACrB,GAAG;IACH,GAAG;IACJ;GACF;EACD,SAAS;EACV;;AAGH,SAAgB,0BAAqC;CACnD,MAAM,SAAS,oBAAoB;CACnC,MAAM,EAAE,cAAc,YAAY,mBAAmB,OAAO;AAE5D,KAAI,QACF,KAAI;AACF,KAAG,cACD,MAAM,aACN,GAAG,KAAK,UAAU,cAAc,MAAM,EAAE,CAAC,KACzC,OACD;UACM,YAAY;AACnB,UAAQ,KACN,sDACA,WACD;;AAIL,gBAAe;AACf,QAAO;;AAGT,SAAgB,YAAuB;AACrC,kBAAiB,oBAAoB;AACrC,QAAO;;AAGT,SAAgB,uBAAuB,OAAuB;AAE5D,QADe,WAAW,CACZ,eAAe,UAAU;;AAGzC,SAAgB,gBAAwB;AAEtC,QADe,WAAW,CACZ,cAAc;;AAG9B,SAAgB,yCAAwD;AAEtE,QADe,WAAW,CAEjB,uCACJ,cAAc,uCACd,EAAE;;AAIT,SAAgB,qCAAqC,OAAwB;AAC3E,QAAO,wCAAwC,CAAC,SAAS,MAAM;;AAGjE,SAAgB,2BACd,OAC0D;AAE1D,QADe,WAAW,CACZ,wBAAwB,UAAU;;AAGlD,SAAgB,yBAAyB,KAAqB;AAC5D,QAAO,IAAI,MAAM,CAAC,QAAQ,SAAS,GAAG;;AAGxC,SAAS,2BACP,cACkB;AAClB,QAAO,iBAAiB,sBAAsB,kBAAkB;;AAGlE,SAAgB,wBACd,cACA,UACA,cACkB;AAClB,KAAI,aAAa,OACf,QAAO,2BAA2B,aAAa;AAGjD,KAAI,aAAa,YACf,QAAO;AAGT,KAAI,aAAa,gBACf,QAAO;AAGT,SAAQ,KACN,YAAY,aAAa,yBAAyB,SAAS,qBAAqB,2BAA2B,aAAa,GACzH;AACD,QAAO,2BAA2B,aAAa;;AAGjD,SAAgB,kBAAkB,MAA6C;CAC7E,MAAM,eAAe,KAAK,MAAM;AAChC,KAAI,CAAC,aACH,QAAO;CAIT,MAAM,WADS,WAAW,CACF,YAAY;AACpC,KAAI,CAAC,SACH,QAAO;AAGT,KAAI,SAAS,YAAY,MACvB,QAAO;CAGT,MAAM,OAAO,SAAS,QAAQ;AAC9B,KAAI,SAAS,eAAe,SAAS,qBAAqB;AACxD,UAAQ,KACN,YAAY,aAAa,4BAA4B,KAAK,oBAC3D;AACD,SAAO;;CAGT,MAAM,UAAU,yBAAyB,SAAS,WAAW,GAAG;CAChE,MAAM,UAAU,SAAS,UAAU,IAAI,MAAM;CAC7C,MAAM,WAAW,wBACf,cACA,SAAS,UACT,KACD;AACD,KAAI,CAAC,WAAW,CAAC,QAAQ;AACvB,UAAQ,KACN,YAAY,aAAa,2CAC1B;AACD,SAAO;;AAGT,QAAO;EACL,MAAM;EACN;EACA;EACA;EACA;EACA,QAAQ,SAAS;EACjB,mBAAmB,SAAS;EAC7B;;AASH,SAAgB,uBAAgC;AAE9C,QADe,WAAW,CACZ,kBAAkB;;AAGlC,SAAgB,qBAAyC;AAEvD,QADe,WAAW,CACZ,mBAAmB,QAAQ,IAAI,qBAAqB;;AAGpE,SAAgB,iCAA0C;AAExD,QADe,WAAW,CACZ,4BAA4B;;AAG5C,SAAgB,2BAAmC;AAEjD,QADe,WAAW,CACZ,yBAAyB"}
package/dist/main.js CHANGED
@@ -44,7 +44,7 @@ bindElectronFetch();
44
44
  const { auth } = await import("./auth-CWEhhJYn.js");
45
45
  const { checkUsage } = await import("./check-usage-B5yr4fpk.js");
46
46
  const { debug } = await import("./debug-DcC7ZPH0.js");
47
- const { start } = await import("./start-DNhkQhQZ.js");
47
+ const { start } = await import("./start-BgL68ZLT.js");
48
48
  const main = defineCommand({
49
49
  meta: {
50
50
  name: "copilot-api",
@@ -800,10 +800,12 @@ function createProviderTokenUsageRecorder(options) {
800
800
  }
801
801
  function normalizeOpenAIUsage(usage) {
802
802
  const cachedTokens = normalizeToken(usage?.prompt_tokens_details?.cached_tokens);
803
+ const cacheCreationTokens = normalizeToken(usage?.prompt_tokens_details?.cache_creation_input_tokens);
803
804
  const promptTokens = normalizeToken(usage?.prompt_tokens);
804
805
  return {
806
+ cache_creation_input_tokens: cacheCreationTokens,
805
807
  cache_read_input_tokens: cachedTokens,
806
- input_tokens: Math.max(0, promptTokens - cachedTokens),
808
+ input_tokens: Math.max(0, promptTokens - cachedTokens - cacheCreationTokens),
807
809
  output_tokens: normalizeToken(usage?.completion_tokens),
808
810
  total_tokens: normalizeOptionalToken(usage?.total_tokens)
809
811
  };
@@ -1566,12 +1568,21 @@ function translateToAnthropic(response) {
1566
1568
  content: assistantContentBlocks,
1567
1569
  stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
1568
1570
  stop_sequence: null,
1569
- usage: {
1570
- input_tokens: (response.usage?.prompt_tokens ?? 0) - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0),
1571
- output_tokens: response.usage?.completion_tokens ?? 0,
1572
- ...response.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: response.usage.prompt_tokens_details.cached_tokens }
1573
- }
1571
+ usage: mapOpenAIChatCompletionUsage(response)
1572
+ };
1573
+ }
1574
+ function mapOpenAIChatCompletionUsage(response) {
1575
+ const promptDetails = response.usage?.prompt_tokens_details;
1576
+ const promptTokens = response.usage?.prompt_tokens ?? 0;
1577
+ const cachedTokens = promptDetails?.cached_tokens ?? 0;
1578
+ const cacheCreationTokens = promptDetails?.cache_creation_input_tokens ?? 0;
1579
+ const usage = {
1580
+ input_tokens: Math.max(0, promptTokens - cachedTokens - cacheCreationTokens),
1581
+ output_tokens: response.usage?.completion_tokens ?? 0
1574
1582
  };
1583
+ if (promptDetails?.cache_creation_input_tokens !== void 0) usage.cache_creation_input_tokens = cacheCreationTokens;
1584
+ if (promptDetails?.cached_tokens !== void 0) usage.cache_read_input_tokens = cachedTokens;
1585
+ return usage;
1575
1586
  }
1576
1587
  function getOpenAIReasoningText(message) {
1577
1588
  return message.reasoning_text ?? message.reasoning_content;
@@ -2989,10 +3000,22 @@ function handleFinish(choice, state$1, context) {
2989
3000
  }
2990
3001
  }
2991
3002
  function getAnthropicUsageFromOpenAIChunk(chunk) {
3003
+ const { cachedTokens, cacheCreationTokens, inputTokens } = getOpenAIChunkUsageTokens(chunk);
2992
3004
  return {
2993
- input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
3005
+ input_tokens: inputTokens,
2994
3006
  output_tokens: chunk.usage?.completion_tokens ?? 0,
2995
- ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
3007
+ ...chunk.usage?.prompt_tokens_details?.cache_creation_input_tokens !== void 0 && { cache_creation_input_tokens: cacheCreationTokens },
3008
+ ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: cachedTokens }
3009
+ };
3010
+ }
3011
+ function getOpenAIChunkUsageTokens(chunk) {
3012
+ const promptTokens = chunk.usage?.prompt_tokens ?? 0;
3013
+ const cachedTokens = chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0;
3014
+ const cacheCreationTokens = chunk.usage?.prompt_tokens_details?.cache_creation_input_tokens ?? 0;
3015
+ return {
3016
+ cacheCreationTokens,
3017
+ cachedTokens,
3018
+ inputTokens: Math.max(0, promptTokens - cachedTokens - cacheCreationTokens)
2996
3019
  };
2997
3020
  }
2998
3021
  function handleToolCalls(delta, state$1, events$1) {
@@ -3101,6 +3124,7 @@ function handleContent(delta, state$1, events$1) {
3101
3124
  }
3102
3125
  function handleMessageStart(state$1, events$1, chunk) {
3103
3126
  if (!state$1.messageStartSent) {
3127
+ const { cachedTokens, cacheCreationTokens, inputTokens } = getOpenAIChunkUsageTokens(chunk);
3104
3128
  events$1.push({
3105
3129
  type: "message_start",
3106
3130
  message: {
@@ -3112,9 +3136,10 @@ function handleMessageStart(state$1, events$1, chunk) {
3112
3136
  stop_reason: null,
3113
3137
  stop_sequence: null,
3114
3138
  usage: {
3115
- input_tokens: (chunk.usage?.prompt_tokens ?? 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0),
3139
+ input_tokens: inputTokens,
3116
3140
  output_tokens: 0,
3117
- ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: chunk.usage.prompt_tokens_details.cached_tokens }
3141
+ ...chunk.usage?.prompt_tokens_details?.cache_creation_input_tokens !== void 0 && { cache_creation_input_tokens: cacheCreationTokens },
3142
+ ...chunk.usage?.prompt_tokens_details?.cached_tokens !== void 0 && { cache_read_input_tokens: cachedTokens }
3118
3143
  }
3119
3144
  }
3120
3145
  });
@@ -3674,6 +3699,14 @@ async function forwardProviderModels(providerConfig, requestHeaders) {
3674
3699
  //#endregion
3675
3700
  //#region src/routes/provider/messages/handler.ts
3676
3701
  const logger$3 = createHandlerLogger("provider-messages-handler");
3702
+ const OPENAI_COMPATIBLE_CONTEXT_CACHE_MARKER_LIMIT = 4;
3703
+ const OPENAI_COMPATIBLE_CONTEXT_CACHE_CONTROL = { type: "ephemeral" };
3704
+ const OPENAI_COMPATIBLE_CONTEXT_CACHE_ROLES = new Set([
3705
+ "system",
3706
+ "user",
3707
+ "assistant",
3708
+ "tool"
3709
+ ]);
3677
3710
  async function handleProviderMessages(c) {
3678
3711
  const provider = c.req.param("provider");
3679
3712
  const providerConfig = getProviderConfig(provider);
@@ -3772,6 +3805,7 @@ const createOpenAICompatiblePayload = (payload, modelConfig) => {
3772
3805
  });
3773
3806
  applyMissingExtraBody(openAIPayload, { extraBody: modelConfig?.extraBody });
3774
3807
  if (!Object.hasOwn(openAIPayload, "parallel_tool_calls")) openAIPayload.parallel_tool_calls = true;
3808
+ if (modelConfig?.contextCache !== false) applyOpenAICompatibleContextCache(openAIPayload);
3775
3809
  return openAIPayload;
3776
3810
  };
3777
3811
  const normalizeOpenAICompatibleReasoningContent = (payload) => {
@@ -3786,6 +3820,40 @@ const applyOpenAICompatibleRequestOverrides = (payload, options) => {
3786
3820
  const allowedKeys = new Set(Object.keys(options.extraBody ?? {}));
3787
3821
  for (const key of allowedKeys) if (Object.hasOwn(options.source, key)) payload[key] = options.source[key];
3788
3822
  };
3823
+ const applyOpenAICompatibleContextCache = (payload) => {
3824
+ const messageIndexes = selectContextCacheMessageIndexes(payload.messages);
3825
+ for (const messageIndex of messageIndexes) applyContextCacheControl(payload.messages[messageIndex]);
3826
+ };
3827
+ const selectContextCacheMessageIndexes = (messages) => {
3828
+ const cacheableIndexes = messages.flatMap((message, index) => isContextCacheMarkerEligible(message) ? [index] : []);
3829
+ const systemIndexes = cacheableIndexes.filter((index) => messages[index]?.role === "system").slice(0, 2);
3830
+ const finalIndexes = cacheableIndexes.filter((index) => messages[index]?.role !== "system").slice(-2);
3831
+ return uniqueIndexes([...systemIndexes, ...finalIndexes]).sort((a, b) => a - b);
3832
+ };
3833
+ const uniqueIndexes = (indexes) => [...new Set(indexes)].slice(0, OPENAI_COMPATIBLE_CONTEXT_CACHE_MARKER_LIMIT);
3834
+ const isContextCacheMarkerEligible = (message) => {
3835
+ if (!OPENAI_COMPATIBLE_CONTEXT_CACHE_ROLES.has(message.role)) return false;
3836
+ if (typeof message.content === "string") return message.content.length > 0;
3837
+ return Array.isArray(message.content) && message.content.length > 0;
3838
+ };
3839
+ const applyContextCacheControl = (message) => {
3840
+ if (!message) return;
3841
+ if (typeof message.content === "string") {
3842
+ message.content = [{
3843
+ type: "text",
3844
+ text: message.content,
3845
+ cache_control: { ...OPENAI_COMPATIBLE_CONTEXT_CACHE_CONTROL }
3846
+ }];
3847
+ return;
3848
+ }
3849
+ if (!Array.isArray(message.content)) return;
3850
+ const lastPart = message.content.at(-1);
3851
+ if (!lastPart) return;
3852
+ setContextCacheControl(lastPart);
3853
+ };
3854
+ const setContextCacheControl = (part) => {
3855
+ part.cache_control = { ...OPENAI_COMPATIBLE_CONTEXT_CACHE_CONTROL };
3856
+ };
3789
3857
  const streamProviderMessages = ({ c, payload, provider, providerConfig, upstreamResponse }) => {
3790
3858
  logger$3.debug("provider.messages.streaming");
3791
3859
  const recordUsage = createProviderMessagesUsageRecorder(payload, provider);
@@ -3830,10 +3898,14 @@ const streamOpenAICompatibleProviderMessages = ({ c, payload, provider, upstream
3830
3898
  thinkingBlockOpen: false
3831
3899
  };
3832
3900
  for await (const chunk of events(upstreamResponse)) {
3833
- logger$3.debug("provider.messages.openai_compatible.raw_stream_event:", {
3834
- data: chunk.data,
3835
- event: chunk.event
3836
- });
3901
+ logger$3.debug("provider.messages.openai_compatible.raw_stream_event:", chunk.data);
3902
+ if (chunk.event === "ping") {
3903
+ await stream.writeSSE({
3904
+ event: "ping",
3905
+ data: "{\"type\":\"ping\"}"
3906
+ });
3907
+ continue;
3908
+ }
3837
3909
  if (!chunk.data || chunk.data === "[DONE]") {
3838
3910
  if (chunk.data === "[DONE]") break;
3839
3911
  continue;
@@ -4237,4 +4309,4 @@ server.route("/:provider/v1/models", providerModelRoutes);
4237
4309
 
4238
4310
  //#endregion
4239
4311
  export { server };
4240
- //# sourceMappingURL=server-MKpxApyo.js.map
4312
+ //# sourceMappingURL=server-Bpu_LXg5.js.map