@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.
- package/README.md +1 -0
- package/dist/main.mjs +241 -31
- 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.
|
|
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
|
-
|
|
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
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
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
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
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
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
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
|
});
|