@jeffreycao/copilot-api 1.14.1 → 1.14.3

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
@@ -288,7 +288,6 @@ Add the following `[model_providers.copilot_api]` section to your Codex `~/.code
288
288
  ```toml
289
289
  model_provider = "copilot_api"
290
290
  model_reasoning_summary = "auto"
291
- model_verbosity = "medium"
292
291
  model_context_window = 272000
293
292
  model_auto_compact_token_limit = 244800
294
293
 
@@ -311,7 +310,7 @@ enabled = false
311
310
  ```
312
311
 
313
312
  > [!NOTE]
314
- > This configuration is specific to Codex and the GitHub Copilot provider. `name` must be set to `"OpenAI"`. It can help mitigate Codex local compact cache miss issues. If you enable `contextManagement.responses` (Responses API context management compaction), `remote_compaction_v2` or local compact is generally not triggered, but it may still occur when tool results return a large number of tokens. Before enabling it for native Responses API traffic, check that your client supports context management compaction.
313
+ > This configuration is specific to Codex and the GitHub Copilot provider. `name` must be set to `"OpenAI"`. It can help mitigate Codex local compact cache miss issues.
315
314
 
316
315
  ## GPT Tool Search
317
316
 
@@ -615,7 +614,10 @@ Use `copilot-api auth login --provider custom` to add or update another third-pa
615
614
  - **smallModel:** Fallback model used for tool-less warmup messages (e.g., Claude Code probe requests); defaults to gpt-5-mini.
616
615
  - **contextManagement:** Controls whether the proxy adds Responses API `context_management` compaction instructions. `messages` applies when Anthropic-style `/v1/messages` requests are translated to Responses API, including `openai-responses` provider message routes, and defaults to `true`. `responses` applies to native `/v1/responses` traffic, including `provider/model` aliases and the built-in `codex` provider, and defaults to `false`. Enable `responses` only after checking that your client supports context management compaction. When enabled, the request includes `context_management` in the body and keeps only the latest compaction carrier on follow-up turns. **Note:** Context management is forcibly disabled for GPT-5.6 and above models (e.g. `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`) because enabling it breaks prompt cache hits on those models. This override takes precedence over the `contextManagement` and `modelResponsesApiCompactThresholds` settings.
617
616
  - **modelResponsesApiCompactThresholds:** Per-model Responses API `compact_threshold` overrides used when the proxy adds `context_management`. These values take precedence over the fallback threshold from `resolveResponsesCompactThreshold` (`max_prompt_tokens * ratio`, or the default fallback). Defaults set `gpt-5.4` and `gpt-5.5` to `217600` (`272000 * 0.8`), and `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` to `231200` (`272000 * 0.85`). Models not listed continue to use the normal fallback logic.
618
- - **modelReasoningEfforts:** Per-model reasoning effort applied to `/v1/messages` requests. When routed to the Copilot native Messages API it sets `output_config.effort`; when translated to the Responses API it sets `reasoning.effort`. Allowed values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. If a model isn't listed, `high` is used by default; GPT-5.3+ models fall back to `xhigh` when not explicitly configured.
617
+ - **modelReasoningEfforts:** Per-model fallback reasoning effort for `/v1/messages` requests. It is used only when the request does not provide `output_config.effort`.
618
+ - **Priority:** request `output_config.effort` > `modelReasoningEfforts[model]` > built-in default (`xhigh` for GPT-5.3+ models, otherwise `high`).
619
+ - **Forwarding:** the resolved value remains `output_config.effort` for the Copilot native Messages API and becomes `reasoning.effort` when translated to the Responses API.
620
+ - **Configuration values:** `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
619
621
  - **useMessagesApi:** When `true`, Claude-family models that support Copilot's native `/v1/messages` endpoint will use the Messages API; otherwise they fall back to `/chat/completions`. Set to `false` to disable Messages API routing and always use `/chat/completions`. Defaults to `true`.
620
622
  - **useResponsesApiWebSocket:** When `true`, Responses API requests use Copilot's websocket transport for models that advertise `ws:/responses`; models that only advertise `/responses` continue to use HTTP. Set to `false` to disable websocket routing and use HTTP `/responses` whenever the selected model supports it. Defaults to `true`.
621
623
  - **useResponsesApiWebSearch:** When `true`, the server keeps Responses API tools with `type: "web_search"` and forwards them upstream. Set to `false` to strip those tools from `/responses` payloads. Defaults to `true`.
package/README.zh-CN.md CHANGED
@@ -290,7 +290,6 @@ npx @jeffreycao/copilot-api@latest start
290
290
  ```toml
291
291
  model_provider = "copilot_api"
292
292
  model_reasoning_summary = "auto"
293
- model_verbosity = "medium"
294
293
  model_context_window = 272000
295
294
  model_auto_compact_token_limit = 244800
296
295
 
@@ -313,7 +312,7 @@ enabled = false
313
312
  ```
314
313
 
315
314
  > [!NOTE]
316
- > 此配置仅限于 Codex 与 GitHub Copilot provider。`name` 一定要配置为 `"OpenAI"`。它可以缓解 Codex local compact 不命中缓存的问题。如果你开启了 `contextManagement.responses`(Responses API context management 压缩),通常不会走到 `remote_compaction_v2` 或者 local compact,但如果工具返回 tokens 过大,仍有可能触发。在 native Responses API 流量下启用前,请先确认客户端支持 context management compaction。
315
+ > 此配置仅限于 Codex 与 GitHub Copilot provider。`name` 一定要配置为 `"OpenAI"`。它可以缓解 Codex local compact 不命中缓存的问题。
317
316
 
318
317
  ## GPT Tool Search
319
318
 
@@ -621,7 +620,10 @@ Copilot API 现在使用子命令结构,主要命令包括:
621
620
  - **smallModel:** 无工具预热消息的回退模型(例如 Claude Code 的探测请求);默认是 `gpt-5-mini`。
622
621
  - **contextManagement:** 控制代理是否为 Responses API 附加 `context_management` 压缩指令。`messages` 作用于被翻译成 Responses API 的 Anthropic 风格 `/v1/messages` 请求,包括 `openai-responses` provider 的 Messages 路由,默认值为 `true`。`responses` 作用于 native `/v1/responses` 流量,包括 `provider/model` 别名和内置 `codex` provider,默认值为 `false`。只有在确认客户端支持 context management compaction 后,才建议在 Responses API 下启用 `responses`。启用后,请求体会带上 `context_management`,并在后续轮次中仅保留最新的压缩承载内容。**注意:** 对于 GPT-5.6 及以上模型(如 `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna`),context management 功能会被强制禁用,因为开启后会破坏这些模型的 prompt 缓存命中。此强制覆盖优先于 `contextManagement` 和 `modelResponsesApiCompactThresholds` 配置。
623
622
  - **modelResponsesApiCompactThresholds:** 按模型覆盖 Responses API 的 `compact_threshold`,仅在代理自动附加 `context_management` 时使用。它的优先级高于 `resolveResponsesCompactThreshold` 基于 `max_prompt_tokens * ratio` 的兜底阈值。默认将 `gpt-5.4` 和 `gpt-5.5` 设为 `217600`(`272000 * 0.8`),将 `gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna` 设为 `231200`(`272000 * 0.85`)。未列出的模型继续使用原有兜底逻辑。
624
- - **modelReasoningEfforts:** 按模型配置的推理强度,仅作用于 `/v1/messages` 请求。当请求走 Copilot 原生 Messages API 时设置 `output_config.effort`;当请求被翻译为 Responses API 时设置 `reasoning.effort`。可选值包括 `none`、`minimal`、`low`、`medium`、`high`、`xhigh` 和 `max`。若某模型未配置,则默认使用 `high`;GPT-5.3+ 模型未显式配置时回退为 `xhigh`。
623
+ - **modelReasoningEfforts:** `/v1/messages` 请求的模型级默认推理强度。仅当请求没有传入 `output_config.effort` 时,该配置才会生效。
624
+ - **优先级:** 请求中的 `output_config.effort` > `modelReasoningEfforts[model]` > 内置默认值(GPT-5.3+ 模型为 `xhigh`,其他模型为 `high`)。
625
+ - **转发字段:** 走 Copilot 原生 Messages API 时,最终值写入 `output_config.effort`;转换为 Responses API 时,最终值写入 `reasoning.effort`。
626
+ - **配置可选值:** `none`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`。
625
627
  - **useMessagesApi:** 当为 `true` 时,支持 Copilot 原生 `/v1/messages` 的 Claude 系模型会走 Messages API;否则回退到 `/chat/completions`。设为 `false` 可禁用 Messages API 路由,始终使用 `/chat/completions`。默认值为 `true`。
626
628
  - **useResponsesApiWebSocket:** 当为 `true` 时,Responses API 请求会优先对声明了 `ws:/responses` 的模型使用 Copilot websocket transport;仅声明 `/responses` 的模型仍走 HTTP。设为 `false` 可禁用 websocket 路由,并在模型支持 `/responses` 时使用 HTTP `/responses`。默认值为 `true`。
627
629
  - **useResponsesApiWebSearch:** 当为 `true` 时,服务端会保留 Responses API 中 `type: "web_search"` 的工具并透传到上游。设为 `false` 则会从 `/responses` payload 中移除这些工具。默认值为 `true`。
package/dist/main.js CHANGED
@@ -25,7 +25,7 @@ bindElectronFetch();
25
25
  const { auth } = await import("./auth-DvJ8r_tN.js");
26
26
  const { debug } = await import("./debug-Bb5hamXX.js");
27
27
  const { mcp } = await import("./mcp-BseuqgHR.js");
28
- const { start } = await import("./start-Ca-IHgWl.js");
28
+ const { start } = await import("./start-CE0CJpAo.js");
29
29
  await runMain(defineCommand({
30
30
  meta: {
31
31
  name: "copilot-api",
@@ -3568,6 +3568,7 @@ const MESSAGE_TYPE = "message";
3568
3568
  const COMPACTION_SIGNATURE_PREFIX = "cm1#";
3569
3569
  const COMPACTION_SIGNATURE_SEPARATOR = "@";
3570
3570
  const THINKING_TEXT = "Thinking...";
3571
+ const resolveReasoningEffort = (payload) => payload.output_config?.effort ?? getReasoningEffortForModel(payload.model);
3571
3572
  const buildPromptCacheKey = (basePromptCacheKey, subagentAgentId) => {
3572
3573
  if (!basePromptCacheKey) return null;
3573
3574
  const normalizedSubagentAgentId = subagentAgentId?.trim() || null;
@@ -3607,7 +3608,7 @@ const translateAnthropicMessagesToResponsesPayload = (payload, subagentAgentId)
3607
3608
  store: false,
3608
3609
  parallel_tool_calls: true,
3609
3610
  reasoning: {
3610
- effort: getReasoningEffortForModel(payload.model),
3611
+ effort: resolveReasoningEffort(payload),
3611
3612
  summary: "detailed",
3612
3613
  context: "all_turns"
3613
3614
  },
@@ -4622,6 +4623,64 @@ const stringifyToolSearchArguments = (argumentsValue) => {
4622
4623
  return;
4623
4624
  }
4624
4625
  };
4626
+ //#endregion
4627
+ //#region src/routes/messages/responses-stream-collection.ts
4628
+ const collectResponsesStreamResult = async ({ errorMessagePrefix = "Responses stream", parseEvent = parseResponsesStreamEvent$1, upstreamResponse, logger }) => {
4629
+ const state = createResponsesStreamCollection();
4630
+ for await (const chunk of upstreamResponse) {
4631
+ const result = collectResponsesStreamChunk({
4632
+ chunk,
4633
+ errorMessagePrefix,
4634
+ logger,
4635
+ parseEvent,
4636
+ state
4637
+ });
4638
+ if (result) return result;
4639
+ }
4640
+ throw new Error(`${errorMessagePrefix} ended without a terminal event`);
4641
+ };
4642
+ const collectResponsesStreamChunk = ({ chunk, errorMessagePrefix, logger, parseEvent, state }) => {
4643
+ debugJson(logger, "Received responses stream chunk:", chunk);
4644
+ if (chunk.event === "ping" || !chunk.data || chunk.data === "[DONE]") return;
4645
+ const parsed = parseEvent(chunk.data);
4646
+ if (!parsed) return;
4647
+ if (parsed.type === "error") throw new Error(getStreamErrorMessage(parsed) ?? `${errorMessagePrefix} failed`);
4648
+ if (parsed.type === "response.failed") {
4649
+ const response = parsed.response;
4650
+ if (!response) throw new Error("Responses stream ended without a response");
4651
+ throw new Error(response.error?.message ?? `${errorMessagePrefix} failed`);
4652
+ }
4653
+ return collectResponsesStreamEvent(parsed, state);
4654
+ };
4655
+ const parseResponsesStreamEvent$1 = (data) => {
4656
+ try {
4657
+ return JSON.parse(data);
4658
+ } catch {
4659
+ return null;
4660
+ }
4661
+ };
4662
+ const createResponsesStreamCollection = () => ({ outputItemsByIndex: /* @__PURE__ */ new Map() });
4663
+ const collectResponsesStreamEvent = (event, state) => {
4664
+ switch (event.type) {
4665
+ case "response.completed":
4666
+ case "response.incomplete": {
4667
+ const response = event.response;
4668
+ if (!response) throw new Error("Responses stream ended without a response");
4669
+ response.copilot_usage ??= event.copilot_usage;
4670
+ const output = [...state.outputItemsByIndex.entries()].sort(([leftIndex], [rightIndex]) => leftIndex - rightIndex).map(([, item]) => item);
4671
+ return {
4672
+ ...response,
4673
+ output: output.length > 0 ? output : response.output
4674
+ };
4675
+ }
4676
+ case "response.output_item.done":
4677
+ state.outputItemsByIndex.set(event.output_index, event.item);
4678
+ break;
4679
+ }
4680
+ };
4681
+ const getStreamErrorMessage = (event) => {
4682
+ return event.error?.message ?? event.message ?? void 0;
4683
+ };
4625
4684
  const DEFAULT_RESPONSES_COMPACT_THRESHOLD_RATIO = .85;
4626
4685
  const responsesUtilsDependencies = {
4627
4686
  getModelResponsesApiCompactThreshold: getModelResponsesApiCompactThreshold$1,
@@ -4970,53 +5029,9 @@ const reconstructWebSearchResponse = (payload, result, options) => {
4970
5029
  }
4971
5030
  };
4972
5031
  };
4973
- const collectWebSearchResponsesStreamResult = async ({ errorMessagePrefix = "Web search responses stream", parseEvent = parseResponsesStreamEvent$1, upstreamResponse, logger }) => {
4974
- const state = createWebSearchResponsesStreamCollection();
4975
- for await (const chunk of upstreamResponse) {
4976
- debugJson(logger, "Received web search responses stream chunk:", chunk);
4977
- if (chunk.event === "ping") continue;
4978
- if (!chunk.data || chunk.data === "[DONE]") continue;
4979
- const parsed = parseEvent(chunk.data);
4980
- if (!parsed) continue;
4981
- if (parsed.type === "error") throw new Error(getStreamErrorMessage(parsed) ?? `${errorMessagePrefix} failed`);
4982
- const result = collectResponsesStreamEvent(parsed, state);
4983
- if (result) return result;
4984
- }
4985
- throw new Error(`${errorMessagePrefix} ended without a terminal event`);
4986
- };
4987
- const parseResponsesStreamEvent$1 = (data) => {
4988
- try {
4989
- return JSON.parse(data);
4990
- } catch {
4991
- return null;
4992
- }
4993
- };
4994
5032
  const isWebSearchResponsesStream = (value) => {
4995
5033
  return Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
4996
5034
  };
4997
- const createWebSearchResponsesStreamCollection = () => ({ outputItemsByIndex: /* @__PURE__ */ new Map() });
4998
- const collectResponsesStreamEvent = (event, state) => {
4999
- switch (event.type) {
5000
- case "response.completed":
5001
- case "response.failed":
5002
- case "response.incomplete": {
5003
- event.response.copilot_usage ??= event.copilot_usage;
5004
- const response = event.response;
5005
- if (!response) throw new Error("Web search responses stream ended without a response");
5006
- const output = [...state.outputItemsByIndex.entries()].sort(([leftIndex], [rightIndex]) => leftIndex - rightIndex).map(([, item]) => item);
5007
- return {
5008
- ...response,
5009
- output: output.length > 0 ? output : response.output
5010
- };
5011
- }
5012
- case "response.output_item.done":
5013
- state.outputItemsByIndex.set(event.output_index, event.item);
5014
- break;
5015
- }
5016
- };
5017
- const getStreamErrorMessage = (event) => {
5018
- return event.error?.message ?? event.message ?? void 0;
5019
- };
5020
5035
  const createUsageRecorder = (payload, sessionId, webSearchModel) => webSearchFlowDependencies.createUsageRecorder(payload, sessionId, webSearchModel);
5021
5036
  /**
5022
5037
  * Entry point for web-search detection and routing on /v1/messages.
@@ -5081,7 +5096,7 @@ const handleWebSearchViaResponses = async (c, payload, options) => {
5081
5096
  sessionId: options.sessionId,
5082
5097
  compactType: options.compactType
5083
5098
  });
5084
- const result = isWebSearchResponsesStream(upstreamResult) ? await collectWebSearchResponsesStreamResult({
5099
+ const result = isWebSearchResponsesStream(upstreamResult) ? await collectResponsesStreamResult({
5085
5100
  errorMessagePrefix: "Web search responses stream",
5086
5101
  upstreamResponse: upstreamResult,
5087
5102
  logger
@@ -5393,7 +5408,7 @@ const handleOpenAIResponsesProviderWebSearchMessages = async (c, options) => {
5393
5408
  if (providerConfig.name === "codex") {
5394
5409
  const upstreamResponse = await forwardCodexResponses(responsesPayload, c.req.raw.headers, providerConfig.baseUrl);
5395
5410
  if (isResponsesStream$1(upstreamResponse)) return respondWebSearchProviderMessagesJson(c, {
5396
- body: await collectWebSearchResponsesStreamResult({
5411
+ body: await collectResponsesStreamResult({
5397
5412
  errorMessagePrefix: `${provider} web search responses stream`,
5398
5413
  parseEvent: (data) => parseResponsesProviderStreamChunk(data, providerConfig),
5399
5414
  upstreamResponse,
@@ -5421,7 +5436,7 @@ const handleOpenAIResponsesProviderWebSearchMessages = async (c, options) => {
5421
5436
  throw new HTTPError("Failed to create provider web search responses", upstreamResponse);
5422
5437
  }
5423
5438
  if ((upstreamResponse.headers.get("content-type") ?? "").includes("text/event-stream")) return respondWebSearchProviderMessagesJson(c, {
5424
- body: await collectWebSearchResponsesStreamResult({
5439
+ body: await collectResponsesStreamResult({
5425
5440
  errorMessagePrefix: `${provider} web search responses stream`,
5426
5441
  parseEvent: (data) => parseResponsesProviderStreamChunk(data, providerConfig),
5427
5442
  upstreamResponse: events(upstreamResponse),
@@ -5443,7 +5458,9 @@ const handleOpenAIResponsesProviderWebSearchMessages = async (c, options) => {
5443
5458
  const handleOpenAIResponsesProviderMessages = async (c, options) => {
5444
5459
  const { modelConfig, payload, provider, providerConfig } = options;
5445
5460
  const selectedModel = providerConfig.name === "codex" ? getModels().data.find((model) => model.id === payload.model) : void 0;
5461
+ const wantsStream = payload.stream === true;
5446
5462
  const responsesPayload = translateAnthropicMessagesToResponsesPayload(payload);
5463
+ if (providerConfig.name === "codex" && !wantsStream) responsesPayload.stream = true;
5447
5464
  if (applyResponsesApiContextManagement(responsesPayload, selectedModel?.capabilities.limits.max_prompt_tokens, { source: "messages" })) compactInputByLatestCompaction(responsesPayload);
5448
5465
  debugJson(logger$6, "provider.messages.responses.request", {
5449
5466
  payload: responsesPayload,
@@ -5451,15 +5468,30 @@ const handleOpenAIResponsesProviderMessages = async (c, options) => {
5451
5468
  });
5452
5469
  if (providerConfig.name === "codex") {
5453
5470
  const upstreamResponse = await forwardCodexResponses(responsesPayload, c.req.raw.headers, providerConfig.baseUrl);
5454
- if (responsesPayload.stream && isResponsesStream$1(upstreamResponse)) return streamResponsesProviderMessages({
5455
- c,
5456
- modelConfig,
5457
- payload,
5458
- pricingCurrency: providerConfig.pricingCurrency,
5459
- provider,
5460
- providerConfig,
5461
- upstreamResponse
5462
- });
5471
+ if (isResponsesStream$1(upstreamResponse)) {
5472
+ if (wantsStream) return streamResponsesProviderMessages({
5473
+ c,
5474
+ modelConfig,
5475
+ payload,
5476
+ pricingCurrency: providerConfig.pricingCurrency,
5477
+ provider,
5478
+ providerConfig,
5479
+ upstreamResponse
5480
+ });
5481
+ return respondResponsesProviderMessagesJson(c, {
5482
+ body: await collectResponsesStreamResult({
5483
+ errorMessagePrefix: `${provider} messages responses stream`,
5484
+ parseEvent: (data) => parseResponsesProviderStreamChunk(data, providerConfig),
5485
+ upstreamResponse,
5486
+ logger: logger$6
5487
+ }),
5488
+ modelConfig,
5489
+ payload,
5490
+ pricingCurrency: providerConfig.pricingCurrency,
5491
+ provider,
5492
+ providerConfig
5493
+ });
5494
+ }
5463
5495
  return respondResponsesProviderMessagesJson(c, {
5464
5496
  body: upstreamResponse,
5465
5497
  modelConfig,
@@ -6850,4 +6882,4 @@ server.route("/:provider/v1/models", providerModelRoutes);
6850
6882
  //#endregion
6851
6883
  export { server };
6852
6884
 
6853
- //# sourceMappingURL=server-_YRTKSO1.js.map
6885
+ //# sourceMappingURL=server-erYoRK9y.js.map