@dianshuv/copilot-api 0.4.0 → 0.4.2

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 +1 -0
  2. package/dist/main.mjs +241 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -65,6 +65,7 @@ copilot-api start
65
65
  | `--compress-tool-results` | Compress old tool results before truncating | false |
66
66
  | `--redirect-anthropic` | Force Anthropic through OpenAI translation | false |
67
67
  | `--no-rewrite-anthropic-tools` | Don't rewrite server-side tools | false |
68
+ | `--timezone-offset` | Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0) | +8 |
68
69
 
69
70
  ### Patch-Claude Command Options
70
71
 
package/dist/main.mjs CHANGED
@@ -52,6 +52,7 @@ const state = {
52
52
  redirectAnthropic: false,
53
53
  rewriteAnthropicTools: true,
54
54
  staleRequestMaxAge: 600,
55
+ timezoneOffset: 8,
55
56
  shutdownGracefulWait: 60,
56
57
  shutdownAbortWait: 120
57
58
  };
@@ -1020,7 +1021,7 @@ const patchClaude = defineCommand({
1020
1021
 
1021
1022
  //#endregion
1022
1023
  //#region package.json
1023
- var version = "0.4.0";
1024
+ var version = "0.4.2";
1024
1025
 
1025
1026
  //#endregion
1026
1027
  //#region src/lib/adaptive-rate-limiter.ts
@@ -2199,7 +2200,8 @@ function setupShutdownHandlers() {
2199
2200
  //#region src/lib/tui/console-renderer.ts
2200
2201
  const CLEAR_LINE = "\x1B[2K\r";
2201
2202
  function formatTime(date = /* @__PURE__ */ new Date()) {
2202
- return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}`;
2203
+ const adjusted = new Date(date.getTime() + state.timezoneOffset * 60 * 60 * 1e3);
2204
+ return `${String(adjusted.getUTCHours()).padStart(2, "0")}:${String(adjusted.getUTCMinutes()).padStart(2, "0")}:${String(adjusted.getUTCSeconds()).padStart(2, "0")}`;
2203
2205
  }
2204
2206
  function formatDuration(ms) {
2205
2207
  if (ms < 1e3) return `${ms}ms`;
@@ -7445,50 +7447,229 @@ const containsVisionContent = (value) => {
7445
7447
  if (Array.isArray(record.content)) return record.content.some((entry) => containsVisionContent(entry));
7446
7448
  return false;
7447
7449
  };
7450
+ /** Convert Responses API input to history MessageContent format */
7451
+ function convertResponsesInputToMessages(input) {
7452
+ if (!input) return [];
7453
+ if (typeof input === "string") return [{
7454
+ role: "user",
7455
+ content: input
7456
+ }];
7457
+ const messages = [];
7458
+ for (const item of input) {
7459
+ const record = item;
7460
+ switch (record.type) {
7461
+ case "function_call": {
7462
+ const fc = item;
7463
+ messages.push({
7464
+ role: "assistant",
7465
+ content: "",
7466
+ tool_calls: [{
7467
+ id: fc.call_id,
7468
+ type: "function",
7469
+ function: {
7470
+ name: fc.name,
7471
+ arguments: fc.arguments
7472
+ }
7473
+ }]
7474
+ });
7475
+ break;
7476
+ }
7477
+ case "function_call_output": {
7478
+ const fco = item;
7479
+ messages.push({
7480
+ role: "tool",
7481
+ content: typeof fco.output === "string" ? fco.output : JSON.stringify(fco.output),
7482
+ tool_call_id: fco.call_id
7483
+ });
7484
+ break;
7485
+ }
7486
+ case "reasoning": break;
7487
+ default: if ("role" in record) {
7488
+ const msg = item;
7489
+ messages.push({
7490
+ role: msg.role,
7491
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
7492
+ });
7493
+ }
7494
+ }
7495
+ }
7496
+ return messages;
7497
+ }
7498
+ /** Convert Responses API tools to history ToolDefinition format */
7499
+ function convertResponsesToolsToDefinitions(tools) {
7500
+ if (!tools) return [];
7501
+ return tools.filter((t) => t.type === "function").map((t) => {
7502
+ const ft = t;
7503
+ const def = { name: ft.name };
7504
+ if (ft.description) def.description = ft.description;
7505
+ return def;
7506
+ });
7507
+ }
7508
+ /** Extract response content and tool calls from ResponsesResult output in a single pass */
7509
+ function extractResponseData(result) {
7510
+ if (result.output.length === 0) return {
7511
+ content: null,
7512
+ toolCalls: void 0
7513
+ };
7514
+ let text = "";
7515
+ const contentToolCalls = [];
7516
+ const historyToolCalls = [];
7517
+ for (const item of result.output) if (item.type === "message" && "content" in item && item.content) {
7518
+ for (const block of item.content) if ("text" in block && typeof block.text === "string") text += block.text;
7519
+ else if ("refusal" in block && typeof block.refusal === "string") text += block.refusal;
7520
+ } else if (item.type === "function_call") {
7521
+ const fc = item;
7522
+ contentToolCalls.push({
7523
+ id: fc.call_id,
7524
+ type: "function",
7525
+ function: {
7526
+ name: fc.name,
7527
+ arguments: fc.arguments
7528
+ }
7529
+ });
7530
+ historyToolCalls.push({
7531
+ id: fc.call_id,
7532
+ name: fc.name,
7533
+ input: fc.arguments
7534
+ });
7535
+ }
7536
+ if (!text && contentToolCalls.length === 0) return {
7537
+ content: null,
7538
+ toolCalls: void 0
7539
+ };
7540
+ const content = {
7541
+ role: "assistant",
7542
+ content: text
7543
+ };
7544
+ if (contentToolCalls.length > 0) content.tool_calls = contentToolCalls;
7545
+ return {
7546
+ content,
7547
+ toolCalls: historyToolCalls.length > 0 ? historyToolCalls : void 0
7548
+ };
7549
+ }
7550
+ /** Map ResponsesResult.status to a stop_reason string */
7551
+ function extractResponseStopReason(result) {
7552
+ switch (result.status) {
7553
+ case "completed": return "stop";
7554
+ case "incomplete": return "length";
7555
+ case "failed": return "error";
7556
+ default: return result.status;
7557
+ }
7558
+ }
7448
7559
 
7449
7560
  //#endregion
7450
7561
  //#region src/routes/responses/handler.ts
7451
7562
  const RESPONSES_ENDPOINT = "/responses";
7563
+ const TERMINAL_EVENTS = new Set([
7564
+ "response.completed",
7565
+ "response.incomplete",
7566
+ "response.failed",
7567
+ "error"
7568
+ ]);
7452
7569
  const handleResponses = async (c) => {
7453
7570
  const payload = await c.req.json();
7454
7571
  consola.debug("Responses request payload:", JSON.stringify(payload));
7455
7572
  const trackingId = c.get("trackingId");
7573
+ const startTime = (trackingId ? requestTracker.getRequest(trackingId) : void 0)?.startTime ?? Date.now();
7456
7574
  updateTrackerModel(trackingId, payload.model);
7457
7575
  useFunctionApplyPatch(payload);
7458
7576
  removeWebSearchTool(payload);
7459
- if (!((state.models?.data.find((model) => model.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) return c.json({ error: {
7460
- message: "This model does not support the responses endpoint. Please choose a different model.",
7461
- type: "invalid_request_error"
7462
- } }, 400);
7577
+ const model = payload.model;
7578
+ const stream = payload.stream ?? false;
7579
+ const tools = convertResponsesToolsToDefinitions(payload.tools);
7580
+ const historyId = recordRequest("openai", {
7581
+ model,
7582
+ messages: convertResponsesInputToMessages(payload.input),
7583
+ stream,
7584
+ tools: tools.length > 0 ? tools : void 0,
7585
+ max_tokens: payload.max_output_tokens ?? void 0,
7586
+ temperature: payload.temperature ?? void 0,
7587
+ system: payload.instructions ?? void 0
7588
+ });
7589
+ const ctx = {
7590
+ historyId,
7591
+ trackingId,
7592
+ startTime
7593
+ };
7594
+ if (!((state.models?.data.find((m) => m.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) {
7595
+ recordErrorResponse(ctx, model, /* @__PURE__ */ new Error("This model does not support the responses endpoint."));
7596
+ return c.json({ error: {
7597
+ message: "This model does not support the responses endpoint. Please choose a different model.",
7598
+ type: "invalid_request_error"
7599
+ } }, 400);
7600
+ }
7463
7601
  const { vision, initiator } = getResponsesRequestOptions(payload);
7464
7602
  if (state.manualApprove) await awaitApproval();
7465
- const { result: response } = await executeWithAdaptiveRateLimit(() => createResponses(payload, {
7466
- vision,
7467
- initiator
7468
- }));
7469
- if (isStreamingRequested(payload) && isAsyncIterable(response)) {
7470
- consola.debug("Forwarding native Responses stream");
7471
- return streamSSE(c, async (stream) => {
7472
- const idTracker = createStreamIdTracker();
7473
- try {
7474
- for await (const chunk of response) {
7475
- consola.debug("Responses stream chunk:", JSON.stringify(chunk));
7476
- const processedData = fixStreamIds(chunk.data ?? "", chunk.event, idTracker);
7477
- await stream.writeSSE({
7478
- id: chunk.id,
7479
- event: chunk.event,
7480
- data: processedData
7603
+ try {
7604
+ const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createResponses(payload, {
7605
+ vision,
7606
+ initiator
7607
+ }));
7608
+ ctx.queueWaitMs = queueWaitMs;
7609
+ if (isStreamingRequested(payload) && isAsyncIterable(response)) {
7610
+ consola.debug("Forwarding native Responses stream");
7611
+ updateTrackerStatus(trackingId, "streaming");
7612
+ return streamSSE(c, async (stream) => {
7613
+ const idTracker = createStreamIdTracker();
7614
+ let finalResult;
7615
+ let streamErrorMessage;
7616
+ try {
7617
+ for await (const chunk of response) {
7618
+ consola.debug("Responses stream chunk:", JSON.stringify(chunk));
7619
+ const eventType = chunk.event;
7620
+ const rawData = chunk.data ?? "";
7621
+ if (eventType && TERMINAL_EVENTS.has(eventType)) try {
7622
+ const parsed = JSON.parse(rawData);
7623
+ if ("response" in parsed) finalResult = parsed.response;
7624
+ else if (eventType === "error" && "message" in parsed) streamErrorMessage = parsed.message;
7625
+ } catch {}
7626
+ const processedData = fixStreamIds(rawData, eventType, idTracker);
7627
+ await stream.writeSSE({
7628
+ id: chunk.id,
7629
+ event: eventType,
7630
+ data: processedData
7631
+ });
7632
+ }
7633
+ if (finalResult) {
7634
+ recordResponseResult(finalResult, model, historyId, startTime);
7635
+ const usage = finalResult.usage;
7636
+ completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, queueWaitMs);
7637
+ } else if (streamErrorMessage) {
7638
+ recordResponse(historyId, {
7639
+ success: false,
7640
+ model,
7641
+ usage: {
7642
+ input_tokens: 0,
7643
+ output_tokens: 0
7644
+ },
7645
+ error: streamErrorMessage,
7646
+ content: null
7647
+ }, Date.now() - startTime);
7648
+ completeTracking(trackingId, 0, 0, queueWaitMs);
7649
+ } else completeTracking(trackingId, 0, 0, queueWaitMs);
7650
+ } catch (error) {
7651
+ recordStreamError({
7652
+ acc: { model: finalResult?.model || model },
7653
+ fallbackModel: model,
7654
+ ctx,
7655
+ error
7481
7656
  });
7657
+ failTracking(trackingId, error);
7658
+ throw error;
7482
7659
  }
7483
- completeTracking(trackingId, 0, 0);
7484
- } catch (error) {
7485
- failTracking(trackingId, error);
7486
- throw error;
7487
- }
7488
- });
7660
+ });
7661
+ }
7662
+ const result = response;
7663
+ const usage = result.usage;
7664
+ recordResponseResult(result, model, historyId, startTime);
7665
+ completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, ctx.queueWaitMs);
7666
+ consola.debug("Forwarding native Responses result:", JSON.stringify(result).slice(-400));
7667
+ return c.json(result);
7668
+ } catch (error) {
7669
+ recordErrorResponse(ctx, model, error);
7670
+ failTracking(trackingId, error);
7671
+ throw error;
7489
7672
  }
7490
- consola.debug("Forwarding native Responses result:", JSON.stringify(response).slice(-400));
7491
- return c.json(response);
7492
7673
  };
7493
7674
  const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
7494
7675
  const isStreamingRequested = (payload) => Boolean(payload.stream);
@@ -7523,6 +7704,22 @@ const removeWebSearchTool = (payload) => {
7523
7704
  return t.type !== "web_search";
7524
7705
  });
7525
7706
  };
7707
+ /** Record a ResponsesResult to history */
7708
+ function recordResponseResult(result, fallbackModel, historyId, startTime) {
7709
+ const usage = result.usage;
7710
+ const { content, toolCalls } = extractResponseData(result);
7711
+ recordResponse(historyId, {
7712
+ success: result.status !== "failed",
7713
+ model: result.model || fallbackModel,
7714
+ usage: {
7715
+ input_tokens: usage?.input_tokens ?? 0,
7716
+ output_tokens: usage?.output_tokens ?? 0
7717
+ },
7718
+ stop_reason: extractResponseStopReason(result),
7719
+ content,
7720
+ toolCalls
7721
+ }, Date.now() - startTime);
7722
+ }
7526
7723
 
7527
7724
  //#endregion
7528
7725
  //#region src/routes/responses/route.ts
@@ -7620,6 +7817,7 @@ async function runServer(options) {
7620
7817
  state.compressToolResults = options.compressToolResults;
7621
7818
  state.redirectAnthropic = options.redirectAnthropic;
7622
7819
  state.rewriteAnthropicTools = options.rewriteAnthropicTools;
7820
+ state.timezoneOffset = options.timezoneOffset;
7623
7821
  if (options.rateLimit) initAdaptiveRateLimiter({
7624
7822
  baseRetryIntervalSeconds: options.retryInterval,
7625
7823
  requestIntervalSeconds: options.requestInterval,
@@ -7684,6 +7882,12 @@ async function runServer(options) {
7684
7882
  hostname: options.host
7685
7883
  }));
7686
7884
  }
7885
+ function parseTimezoneOffset(value) {
7886
+ if (typeof value !== "string") return 8;
7887
+ const n = Number(value);
7888
+ if (!Number.isFinite(n)) return 8;
7889
+ return n;
7890
+ }
7687
7891
  const start = defineCommand({
7688
7892
  meta: {
7689
7893
  name: "start",
@@ -7793,6 +7997,11 @@ const start = defineCommand({
7793
7997
  type: "boolean",
7794
7998
  default: false,
7795
7999
  description: "Don't rewrite Anthropic server-side tools (web_search, etc.) to custom tool format"
8000
+ },
8001
+ "timezone-offset": {
8002
+ type: "string",
8003
+ default: "+8",
8004
+ description: "Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0)"
7796
8005
  }
7797
8006
  },
7798
8007
  run({ args }) {
@@ -7816,7 +8025,8 @@ const start = defineCommand({
7816
8025
  autoTruncate: !args["no-auto-truncate"],
7817
8026
  compressToolResults: args["compress-tool-results"],
7818
8027
  redirectAnthropic: args["redirect-anthropic"],
7819
- rewriteAnthropicTools: !args["no-rewrite-anthropic-tools"]
8028
+ rewriteAnthropicTools: !args["no-rewrite-anthropic-tools"],
8029
+ timezoneOffset: parseTimezoneOffset(args["timezone-offset"])
7820
8030
  });
7821
8031
  }
7822
8032
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",