@dianshuv/copilot-api 0.7.1 → 0.7.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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/dist/main.mjs +53 -16
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -32,6 +32,16 @@ copilot-api start
32
32
  ```
33
33
 
34
34
 
35
+ ## Development
36
+
37
+ ```sh
38
+ # Start the server (foreground, production mode)
39
+ make up
40
+
41
+ # Stop the server (graceful shutdown)
42
+ make down
43
+ ```
44
+
35
45
  ## Command Reference
36
46
 
37
47
  | Command | Description |
package/dist/main.mjs CHANGED
@@ -1213,7 +1213,7 @@ const patchClaude = defineCommand({
1213
1213
 
1214
1214
  //#endregion
1215
1215
  //#region package.json
1216
- var version = "0.7.1";
1216
+ var version = "0.7.3";
1217
1217
 
1218
1218
  //#endregion
1219
1219
  //#region src/lib/adaptive-rate-limiter.ts
@@ -3252,13 +3252,28 @@ const getTokenCount = async (payload, model) => {
3252
3252
 
3253
3253
  //#endregion
3254
3254
  //#region src/services/copilot/create-chat-completions.ts
3255
+ const GPT_MODEL_PATTERN = /^gpt-/i;
3255
3256
  const createChatCompletions = async (payload, options) => {
3256
3257
  if (!state.copilotToken) throw new Error("Copilot token not found");
3257
- const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
3258
- const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
3258
+ const vendor = options?.resolvedModel?.vendor;
3259
+ const isOpenAIVendor = vendor === "OpenAI" || vendor === "Azure OpenAI";
3260
+ const isLikelyGPT = !options?.resolvedModel && GPT_MODEL_PATTERN.test(payload.model);
3261
+ let wire = payload;
3262
+ if (isOpenAIVendor || isLikelyGPT) {
3263
+ const { max_tokens, max_completion_tokens, ...rest } = payload;
3264
+ const effective = max_completion_tokens ?? max_tokens;
3265
+ wire = {
3266
+ ...rest,
3267
+ ...effective !== null && effective !== void 0 && { max_completion_tokens: effective }
3268
+ };
3269
+ }
3270
+ const enableVision = wire.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
3271
+ const isAgentCall = wire.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
3272
+ const modelSupportsVision = options?.resolvedModel?.capabilities?.supports?.vision !== false;
3259
3273
  const headers = {
3260
3274
  ...copilotHeaders(state, {
3261
- vision: enableVision,
3275
+ vision: enableVision && modelSupportsVision,
3276
+ modelRequestHeaders: options?.resolvedModel?.request_headers,
3262
3277
  intent: isAgentCall ? "conversation-agent" : "conversation-panel"
3263
3278
  }),
3264
3279
  "X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
@@ -3266,7 +3281,7 @@ const createChatCompletions = async (payload, options) => {
3266
3281
  const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
3267
3282
  method: "POST",
3268
3283
  headers,
3269
- body: JSON.stringify(payload)
3284
+ body: JSON.stringify(wire)
3270
3285
  });
3271
3286
  if (!response.ok) {
3272
3287
  consola.error("Failed to create chat completions", response);
@@ -3681,10 +3696,23 @@ function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, re
3681
3696
  stopReason: analytics.stopReason
3682
3697
  });
3683
3698
  }
3699
+ function formatError(error) {
3700
+ if (error instanceof Error) return error.message || error.name;
3701
+ if (typeof error === "string") return error;
3702
+ try {
3703
+ const s = JSON.stringify(error);
3704
+ if (s && s !== "{}") return s;
3705
+ } catch {}
3706
+ try {
3707
+ return String(error);
3708
+ } catch {
3709
+ return "Unknown error";
3710
+ }
3711
+ }
3684
3712
  /** Fail TUI tracking */
3685
3713
  function failTracking(trackingId, error) {
3686
3714
  if (!trackingId) return;
3687
- requestTracker.failRequest(trackingId, error instanceof Error ? error.message : "Stream error");
3715
+ requestTracker.failRequest(trackingId, formatError(error));
3688
3716
  }
3689
3717
  /**
3690
3718
  * Create a marker to prepend to responses indicating auto-truncation occurred.
@@ -3707,7 +3735,7 @@ function recordStreamError(opts) {
3707
3735
  input_tokens: 0,
3708
3736
  output_tokens: 0
3709
3737
  },
3710
- error: error instanceof Error ? error.message : "Stream error",
3738
+ error: formatError(error),
3711
3739
  content: null
3712
3740
  }, Date.now() - ctx.startTime);
3713
3741
  }
@@ -3846,7 +3874,7 @@ async function handleCompletion$1(c) {
3846
3874
  async function executeRequest(opts) {
3847
3875
  const { c, payload, selectedModel, ctx, trackingId } = opts;
3848
3876
  try {
3849
- const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(payload));
3877
+ const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(payload, { resolvedModel: selectedModel }));
3850
3878
  ctx.queueWaitMs = queueWaitMs;
3851
3879
  if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx, payload);
3852
3880
  consola.debug("Streaming response");
@@ -4503,7 +4531,7 @@ async function handleGeminiGenerate(c, model, isStream) {
4503
4531
  trackingId,
4504
4532
  startTime
4505
4533
  };
4506
- const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(payload));
4534
+ const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(payload, { resolvedModel: selectedModel }));
4507
4535
  ctx.queueWaitMs = queueWaitMs;
4508
4536
  if (isNonStreaming(response)) return handleNonStreamResponse(c, response, model, ctx, payload);
4509
4537
  consola.debug("Streaming Gemini response");
@@ -7629,7 +7657,7 @@ async function handleDirectAnthropicStreamingResponse(opts) {
7629
7657
  toolCount: anthropicPayload.tools?.length ?? 0
7630
7658
  });
7631
7659
  } catch (error) {
7632
- consola.error("Direct Anthropic stream error:", error);
7660
+ consola.error("Direct Anthropic stream error:", formatError(error), error);
7633
7661
  recordStreamError({
7634
7662
  acc,
7635
7663
  fallbackModel: anthropicPayload.model,
@@ -7704,7 +7732,10 @@ async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOve
7704
7732
  if (truncateResult) ctx.truncateResult = truncateResult;
7705
7733
  if (state.manualApprove) await awaitApproval();
7706
7734
  try {
7707
- const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(openAIPayload, { initiator: initiatorOverride }));
7735
+ const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(openAIPayload, {
7736
+ initiator: initiatorOverride,
7737
+ resolvedModel: selectedModel
7738
+ }));
7708
7739
  ctx.queueWaitMs = queueWaitMs;
7709
7740
  if (isNonStreaming(response)) return handleNonStreamingResponse({
7710
7741
  c,
@@ -7812,7 +7843,7 @@ async function handleStreamingResponse(opts) {
7812
7843
  toolCount: anthropicPayload.tools?.length ?? 0
7813
7844
  });
7814
7845
  } catch (error) {
7815
- consola.error("Stream error:", error);
7846
+ consola.error("Stream error:", formatError(error), error);
7816
7847
  recordStreamError({
7817
7848
  acc,
7818
7849
  fallbackModel: anthropicPayload.model,
@@ -8058,10 +8089,14 @@ modelRoutes.get("/", async (c) => {
8058
8089
 
8059
8090
  //#endregion
8060
8091
  //#region src/services/copilot/create-responses.ts
8061
- const createResponses = async (payload, { vision, initiator }) => {
8092
+ const createResponses = async (payload, { vision, initiator, resolvedModel }) => {
8062
8093
  if (!state.copilotToken) throw new Error("Copilot token not found");
8094
+ const modelSupportsVision = resolvedModel?.capabilities?.supports?.vision !== false;
8063
8095
  const headers = {
8064
- ...copilotHeaders(state, { vision }),
8096
+ ...copilotHeaders(state, {
8097
+ vision: vision && modelSupportsVision,
8098
+ modelRequestHeaders: resolvedModel?.request_headers
8099
+ }),
8065
8100
  "X-Initiator": initiator
8066
8101
  };
8067
8102
  payload.service_tier = null;
@@ -8324,7 +8359,8 @@ const handleResponses = async (c) => {
8324
8359
  trackingId,
8325
8360
  startTime
8326
8361
  };
8327
- if (!(findModelById(payload.model)?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) {
8362
+ const selectedModel = findModelById(payload.model);
8363
+ if (!(selectedModel?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) {
8328
8364
  recordErrorResponse(ctx, model, /* @__PURE__ */ new Error("This model does not support the responses endpoint."));
8329
8365
  return c.json({ error: {
8330
8366
  message: "This model does not support the responses endpoint. Please choose a different model.",
@@ -8336,7 +8372,8 @@ const handleResponses = async (c) => {
8336
8372
  try {
8337
8373
  const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createResponses(payload, {
8338
8374
  vision,
8339
- initiator
8375
+ initiator,
8376
+ resolvedModel: selectedModel
8340
8377
  }));
8341
8378
  ctx.queueWaitMs = queueWaitMs;
8342
8379
  if (isStreamingRequested(payload) && isAsyncIterable(response)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  "prepare": "npm run build && (command -v bun >/dev/null 2>&1 && simple-git-hooks || true)",
21
21
  "prepublishOnly": "npm run typecheck && npm run lint:all && npm run test",
22
22
  "release": "npm publish --access public --//registry.npmjs.org/:_authToken=$NPM_TOKEN",
23
- "start": "NODE_ENV=production bun run ./src/main.ts",
23
+ "start": "NODE_ENV=production bun run ./src/main.ts start",
24
24
  "test": "bun test tests/*.test.ts",
25
25
  "test:all": "bun test tests/*.test.ts && bun test tests/integration/",
26
26
  "test:integration": "bun test tests/integration/",