@ashsec/copilot-api 0.7.8 → 0.7.11
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/dist/main.js +165 -51
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -17,6 +17,85 @@ import { Hono } from "hono";
|
|
|
17
17
|
import { cors } from "hono/cors";
|
|
18
18
|
import { streamSSE } from "hono/streaming";
|
|
19
19
|
|
|
20
|
+
//#region package.json
|
|
21
|
+
var name = "@ashsec/copilot-api";
|
|
22
|
+
var version = "0.7.11";
|
|
23
|
+
var description = "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!";
|
|
24
|
+
var keywords = [
|
|
25
|
+
"proxy",
|
|
26
|
+
"github-copilot",
|
|
27
|
+
"openai-compatible"
|
|
28
|
+
];
|
|
29
|
+
var homepage = "https://github.com/ericc-ch/copilot-api";
|
|
30
|
+
var bugs = "https://github.com/ericc-ch/copilot-api/issues";
|
|
31
|
+
var repository = {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/ericc-ch/copilot-api.git"
|
|
34
|
+
};
|
|
35
|
+
var author = "Erick Christian <erickchristian48@gmail.com>";
|
|
36
|
+
var type = "module";
|
|
37
|
+
var bin = { "copilot-api": "./dist/main.js" };
|
|
38
|
+
var files = ["dist"];
|
|
39
|
+
var scripts = {
|
|
40
|
+
"build": "tsdown",
|
|
41
|
+
"dev": "bun run --watch ./src/main.ts",
|
|
42
|
+
"knip": "knip-bun",
|
|
43
|
+
"lint": "eslint --cache",
|
|
44
|
+
"lint:all": "eslint --cache .",
|
|
45
|
+
"prepack": "bun run build",
|
|
46
|
+
"prepare": "simple-git-hooks",
|
|
47
|
+
"release": "bumpp && bun publish --access public",
|
|
48
|
+
"start": "NODE_ENV=production bun run ./src/main.ts",
|
|
49
|
+
"typecheck": "tsc"
|
|
50
|
+
};
|
|
51
|
+
var simple_git_hooks = { "pre-commit": "bunx lint-staged" };
|
|
52
|
+
var lint_staged = { "*": "bun run lint --fix" };
|
|
53
|
+
var dependencies = {
|
|
54
|
+
"citty": "^0.1.6",
|
|
55
|
+
"clipboardy": "^5.0.0",
|
|
56
|
+
"consola": "^3.4.2",
|
|
57
|
+
"fetch-event-stream": "^0.1.5",
|
|
58
|
+
"gpt-tokenizer": "^3.0.1",
|
|
59
|
+
"hono": "^4.9.9",
|
|
60
|
+
"proxy-from-env": "^1.1.0",
|
|
61
|
+
"srvx": "^0.8.9",
|
|
62
|
+
"tiny-invariant": "^1.3.3",
|
|
63
|
+
"undici": "^7.16.0",
|
|
64
|
+
"zod": "^4.1.11"
|
|
65
|
+
};
|
|
66
|
+
var devDependencies = {
|
|
67
|
+
"@echristian/eslint-config": "^0.0.54",
|
|
68
|
+
"@types/bun": "^1.2.23",
|
|
69
|
+
"@types/proxy-from-env": "^1.0.4",
|
|
70
|
+
"bumpp": "^10.2.3",
|
|
71
|
+
"eslint": "^9.37.0",
|
|
72
|
+
"knip": "^5.64.1",
|
|
73
|
+
"lint-staged": "^16.2.3",
|
|
74
|
+
"prettier-plugin-packagejson": "^2.5.19",
|
|
75
|
+
"simple-git-hooks": "^2.13.1",
|
|
76
|
+
"tsdown": "^0.15.6",
|
|
77
|
+
"typescript": "^5.9.3"
|
|
78
|
+
};
|
|
79
|
+
var package_default = {
|
|
80
|
+
name,
|
|
81
|
+
version,
|
|
82
|
+
description,
|
|
83
|
+
keywords,
|
|
84
|
+
homepage,
|
|
85
|
+
bugs,
|
|
86
|
+
repository,
|
|
87
|
+
author,
|
|
88
|
+
type,
|
|
89
|
+
bin,
|
|
90
|
+
files,
|
|
91
|
+
scripts,
|
|
92
|
+
"simple-git-hooks": simple_git_hooks,
|
|
93
|
+
"lint-staged": lint_staged,
|
|
94
|
+
dependencies,
|
|
95
|
+
devDependencies
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
20
99
|
//#region src/lib/paths.ts
|
|
21
100
|
const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
|
|
22
101
|
const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
|
|
@@ -105,7 +184,7 @@ var HTTPError = class extends Error {
|
|
|
105
184
|
}
|
|
106
185
|
};
|
|
107
186
|
function isContentFilterError(obj) {
|
|
108
|
-
return typeof obj === "object" && obj !== null && "error" in obj && typeof obj.error === "object" && obj.error
|
|
187
|
+
return typeof obj === "object" && obj !== null && "error" in obj && typeof obj.error === "object" && obj.error.code === "content_filter";
|
|
109
188
|
}
|
|
110
189
|
async function forwardError(c, error) {
|
|
111
190
|
consola.error("Error occurred:", error);
|
|
@@ -575,13 +654,13 @@ const checkUsage = defineCommand({
|
|
|
575
654
|
const premiumUsed = premiumTotal - premium.remaining;
|
|
576
655
|
const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
|
|
577
656
|
const premiumPercentRemaining = premium.percent_remaining;
|
|
578
|
-
function summarizeQuota(name, snap) {
|
|
579
|
-
if (!snap) return `${name}: N/A`;
|
|
657
|
+
function summarizeQuota(name$1, snap) {
|
|
658
|
+
if (!snap) return `${name$1}: N/A`;
|
|
580
659
|
const total = snap.entitlement;
|
|
581
660
|
const used = total - snap.remaining;
|
|
582
661
|
const percentUsed = total > 0 ? used / total * 100 : 0;
|
|
583
662
|
const percentRemaining = snap.percent_remaining;
|
|
584
|
-
return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
|
|
663
|
+
return `${name$1}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
|
|
585
664
|
}
|
|
586
665
|
const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
|
|
587
666
|
const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
|
|
@@ -599,7 +678,7 @@ const checkUsage = defineCommand({
|
|
|
599
678
|
const SYSTEM_REPLACEMENTS = [{
|
|
600
679
|
id: "system-anthropic-billing",
|
|
601
680
|
name: "Remove Anthropic billing header",
|
|
602
|
-
pattern:
|
|
681
|
+
pattern: String.raw`x-anthropic-billing-header:[^\n]*\n?`,
|
|
603
682
|
replacement: "",
|
|
604
683
|
isRegex: true,
|
|
605
684
|
enabled: true,
|
|
@@ -656,11 +735,12 @@ async function getUserReplacements() {
|
|
|
656
735
|
/**
|
|
657
736
|
* Add a new user replacement rule
|
|
658
737
|
*/
|
|
659
|
-
async function addReplacement(pattern, replacement,
|
|
738
|
+
async function addReplacement(pattern, replacement, options) {
|
|
739
|
+
const { isRegex = false, name: name$1 } = options ?? {};
|
|
660
740
|
await ensureLoaded();
|
|
661
741
|
const rule = {
|
|
662
742
|
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
663
|
-
name,
|
|
743
|
+
name: name$1,
|
|
664
744
|
pattern,
|
|
665
745
|
replacement,
|
|
666
746
|
isRegex,
|
|
@@ -811,11 +891,11 @@ async function applyReplacementsToPayload(payload) {
|
|
|
811
891
|
//#region src/config.ts
|
|
812
892
|
function formatRule(rule, index) {
|
|
813
893
|
const status = rule.enabled ? "✓" : "✗";
|
|
814
|
-
const type = rule.isRegex ? "regex" : "string";
|
|
894
|
+
const type$1 = rule.isRegex ? "regex" : "string";
|
|
815
895
|
const system = rule.isSystem ? " [system]" : "";
|
|
816
|
-
const name = rule.name ? ` "${rule.name}"` : "";
|
|
896
|
+
const name$1 = rule.name ? ` "${rule.name}"` : "";
|
|
817
897
|
const replacement = rule.replacement || "(empty)";
|
|
818
|
-
return `${index + 1}. [${status}] (${type})${system}${name} "${rule.pattern}" → "${replacement}"`;
|
|
898
|
+
return `${index + 1}. [${status}] (${type$1})${system}${name$1} "${rule.pattern}" → "${replacement}"`;
|
|
819
899
|
}
|
|
820
900
|
async function listReplacements() {
|
|
821
901
|
const all = await getAllReplacements();
|
|
@@ -828,11 +908,11 @@ async function listReplacements() {
|
|
|
828
908
|
console.log();
|
|
829
909
|
}
|
|
830
910
|
async function addNewReplacement() {
|
|
831
|
-
const name = await consola.prompt("Name (optional, short description):", {
|
|
911
|
+
const name$1 = await consola.prompt("Name (optional, short description):", {
|
|
832
912
|
type: "text",
|
|
833
913
|
default: ""
|
|
834
914
|
});
|
|
835
|
-
if (typeof name === "symbol") {
|
|
915
|
+
if (typeof name$1 === "symbol") {
|
|
836
916
|
consola.info("Cancelled.");
|
|
837
917
|
return;
|
|
838
918
|
}
|
|
@@ -869,7 +949,7 @@ async function addNewReplacement() {
|
|
|
869
949
|
consola.info("Cancelled.");
|
|
870
950
|
return;
|
|
871
951
|
}
|
|
872
|
-
const rule = await addReplacement(pattern, replacement, matchType === "regex", name || void 0);
|
|
952
|
+
const rule = await addReplacement(pattern, replacement, matchType === "regex", name$1 || void 0);
|
|
873
953
|
consola.success(`Added rule: ${rule.name || rule.id}`);
|
|
874
954
|
}
|
|
875
955
|
async function editExistingReplacement() {
|
|
@@ -897,11 +977,11 @@ async function editExistingReplacement() {
|
|
|
897
977
|
}
|
|
898
978
|
consola.info(`\nEditing rule: ${rule.name || rule.id}`);
|
|
899
979
|
consola.info("Press Enter to keep current value.\n");
|
|
900
|
-
const name = await consola.prompt("Name:", {
|
|
980
|
+
const name$1 = await consola.prompt("Name:", {
|
|
901
981
|
type: "text",
|
|
902
982
|
default: rule.name || ""
|
|
903
983
|
});
|
|
904
|
-
if (typeof name === "symbol") {
|
|
984
|
+
if (typeof name$1 === "symbol") {
|
|
905
985
|
consola.info("Cancelled.");
|
|
906
986
|
return;
|
|
907
987
|
}
|
|
@@ -943,7 +1023,7 @@ async function editExistingReplacement() {
|
|
|
943
1023
|
return;
|
|
944
1024
|
}
|
|
945
1025
|
const updated = await updateReplacement(selected, {
|
|
946
|
-
name: name || void 0,
|
|
1026
|
+
name: name$1 || void 0,
|
|
947
1027
|
pattern,
|
|
948
1028
|
replacement,
|
|
949
1029
|
isRegex: matchType === "regex"
|
|
@@ -1128,9 +1208,9 @@ async function checkTokenExists() {
|
|
|
1128
1208
|
}
|
|
1129
1209
|
}
|
|
1130
1210
|
async function getDebugInfo() {
|
|
1131
|
-
const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
|
|
1211
|
+
const [version$1, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
|
|
1132
1212
|
return {
|
|
1133
|
-
version,
|
|
1213
|
+
version: version$1,
|
|
1134
1214
|
runtime: getRuntimeInfo(),
|
|
1135
1215
|
paths: {
|
|
1136
1216
|
APP_DIR: PATHS.APP_DIR,
|
|
@@ -1310,6 +1390,14 @@ function getStatusColor(status) {
|
|
|
1310
1390
|
return colors.green;
|
|
1311
1391
|
}
|
|
1312
1392
|
/**
|
|
1393
|
+
* Sanitize request body by omitting large message/prompt arrays
|
|
1394
|
+
*/
|
|
1395
|
+
function sanitizeRequestBody(parsed) {
|
|
1396
|
+
const sanitized = {};
|
|
1397
|
+
for (const [key, value] of Object.entries(parsed)) sanitized[key] = key === "messages" || key === "prompt" ? `[${Array.isArray(value) ? value.length : 1} items omitted]` : value;
|
|
1398
|
+
return sanitized;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1313
1401
|
* Log raw HTTP request details (for debug mode)
|
|
1314
1402
|
*/
|
|
1315
1403
|
async function logRawRequest(c) {
|
|
@@ -1317,9 +1405,7 @@ async function logRawRequest(c) {
|
|
|
1317
1405
|
const url = c.req.url;
|
|
1318
1406
|
const headers = Object.fromEntries(c.req.raw.headers.entries());
|
|
1319
1407
|
const lines = [];
|
|
1320
|
-
lines.push(`${colors.magenta}${colors.bold}[DEBUG] Incoming Request${colors.reset}`);
|
|
1321
|
-
lines.push(`${colors.cyan}${method}${colors.reset} ${url}`);
|
|
1322
|
-
lines.push(`${colors.dim}Headers:${colors.reset}`);
|
|
1408
|
+
lines.push(`${colors.magenta}${colors.bold}[DEBUG] Incoming Request${colors.reset}`, `${colors.cyan}${method}${colors.reset} ${url}`, `${colors.dim}Headers:${colors.reset}`);
|
|
1323
1409
|
for (const [key, value] of Object.entries(headers)) {
|
|
1324
1410
|
const displayValue = key.toLowerCase().includes("authorization") ? `${value.slice(0, 20)}...` : value;
|
|
1325
1411
|
lines.push(` ${colors.gray}${key}:${colors.reset} ${displayValue}`);
|
|
@@ -1328,11 +1414,8 @@ async function logRawRequest(c) {
|
|
|
1328
1414
|
const body = await c.req.raw.clone().text();
|
|
1329
1415
|
if (body) try {
|
|
1330
1416
|
const parsed = JSON.parse(body);
|
|
1331
|
-
const sanitized =
|
|
1332
|
-
|
|
1333
|
-
else sanitized[key] = value;
|
|
1334
|
-
lines.push(`${colors.dim}Body (sanitized):${colors.reset}`);
|
|
1335
|
-
lines.push(` ${JSON.stringify(sanitized, null, 2).split("\n").join("\n ")}`);
|
|
1417
|
+
const sanitized = sanitizeRequestBody(parsed);
|
|
1418
|
+
lines.push(`${colors.dim}Body (sanitized):${colors.reset}`, ` ${JSON.stringify(sanitized, null, 2).split("\n").join("\n ")}`);
|
|
1336
1419
|
} catch {
|
|
1337
1420
|
lines.push(`${colors.dim}Body:${colors.reset} [${body.length} bytes]`);
|
|
1338
1421
|
}
|
|
@@ -1371,7 +1454,7 @@ async function requestLogger(c, next) {
|
|
|
1371
1454
|
const statusBadge = `${statusColor}${status}${colors.reset}`;
|
|
1372
1455
|
const durationStr = `${colors.cyan}${duration}s${colors.reset}`;
|
|
1373
1456
|
lines.push(`${colors.bold}${method}${colors.reset} ${path$1} ${statusBadge} ${durationStr}`);
|
|
1374
|
-
if (ctx?.provider && ctx
|
|
1457
|
+
if (ctx?.provider && ctx.model) {
|
|
1375
1458
|
const providerColor = ctx.provider === "Azure OpenAI" ? colors.blue : colors.magenta;
|
|
1376
1459
|
lines.push(` ${colors.gray}Provider:${colors.reset} ${providerColor}${ctx.provider}${colors.reset} ${colors.gray}->${colors.reset} ${colors.white}${ctx.model}${colors.reset}`);
|
|
1377
1460
|
}
|
|
@@ -1400,7 +1483,7 @@ const awaitApproval = async () => {
|
|
|
1400
1483
|
* "gpt-5-1-codex" -> "gpt-5.1-codex"
|
|
1401
1484
|
*/
|
|
1402
1485
|
function normalizeModelName(model) {
|
|
1403
|
-
return model.
|
|
1486
|
+
return model.replaceAll(/(\d)-(\d)/g, (_, p1, p2) => `${p1}.${p2}`);
|
|
1404
1487
|
}
|
|
1405
1488
|
|
|
1406
1489
|
//#endregion
|
|
@@ -1804,8 +1887,8 @@ function translateToOpenAI(payload) {
|
|
|
1804
1887
|
};
|
|
1805
1888
|
}
|
|
1806
1889
|
function translateModelName(model) {
|
|
1807
|
-
if (
|
|
1808
|
-
else if (
|
|
1890
|
+
if (/^claude-sonnet-4-\d{8}/.test(model)) return "claude-sonnet-4";
|
|
1891
|
+
else if (/^claude-opus-4-\d{8}/.test(model)) return "claude-opus-4";
|
|
1809
1892
|
return model;
|
|
1810
1893
|
}
|
|
1811
1894
|
function translateAnthropicMessagesToOpenAI(anthropicMessages, system) {
|
|
@@ -1923,23 +2006,35 @@ function translateAnthropicToolChoiceToOpenAI(anthropicToolChoice) {
|
|
|
1923
2006
|
}
|
|
1924
2007
|
}
|
|
1925
2008
|
function translateToAnthropic(response, originalModel) {
|
|
2009
|
+
const { contentBlocks, stopReason } = extractContentFromChoices(response);
|
|
2010
|
+
return buildAnthropicResponse(response, {
|
|
2011
|
+
contentBlocks,
|
|
2012
|
+
stopReason,
|
|
2013
|
+
originalModel
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
function extractContentFromChoices(response) {
|
|
1926
2017
|
const allTextBlocks = [];
|
|
1927
2018
|
const allToolUseBlocks = [];
|
|
1928
|
-
let stopReason = null;
|
|
1929
|
-
stopReason = response.choices[0]?.finish_reason ?? stopReason;
|
|
2019
|
+
let stopReason = response.choices[0]?.finish_reason ?? null;
|
|
1930
2020
|
for (const choice of response.choices) {
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
allTextBlocks.push(...textBlocks);
|
|
1934
|
-
allToolUseBlocks.push(...toolUseBlocks);
|
|
2021
|
+
allTextBlocks.push(...getAnthropicTextBlocks(choice.message.content));
|
|
2022
|
+
allToolUseBlocks.push(...getAnthropicToolUseBlocks(choice.message.tool_calls));
|
|
1935
2023
|
if (choice.finish_reason === "tool_calls" || stopReason === "stop") stopReason = choice.finish_reason;
|
|
1936
2024
|
}
|
|
2025
|
+
return {
|
|
2026
|
+
contentBlocks: [...allTextBlocks, ...allToolUseBlocks],
|
|
2027
|
+
stopReason
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
function buildAnthropicResponse(response, options) {
|
|
2031
|
+
const { contentBlocks, stopReason, originalModel } = options;
|
|
1937
2032
|
return {
|
|
1938
2033
|
id: response.id,
|
|
1939
2034
|
type: "message",
|
|
1940
2035
|
role: "assistant",
|
|
1941
2036
|
model: originalModel ?? response.model,
|
|
1942
|
-
content:
|
|
2037
|
+
content: contentBlocks,
|
|
1943
2038
|
stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
|
|
1944
2039
|
stop_sequence: null,
|
|
1945
2040
|
usage: {
|
|
@@ -2013,10 +2108,12 @@ function isToolBlockOpen(state$1) {
|
|
|
2013
2108
|
return Object.values(state$1.toolCalls).some((tc) => tc.anthropicBlockIndex === state$1.contentBlockIndex);
|
|
2014
2109
|
}
|
|
2015
2110
|
function createMessageDeltaEvents(finishReason, usage) {
|
|
2111
|
+
const stopReason = mapOpenAIStopReasonToAnthropic(finishReason);
|
|
2112
|
+
console.log(`[stream-translation] Creating message_delta with stop_reason: ${stopReason}, finishReason: ${finishReason}`);
|
|
2016
2113
|
return [{
|
|
2017
2114
|
type: "message_delta",
|
|
2018
2115
|
delta: {
|
|
2019
|
-
stop_reason:
|
|
2116
|
+
stop_reason: stopReason,
|
|
2020
2117
|
stop_sequence: null
|
|
2021
2118
|
},
|
|
2022
2119
|
usage: {
|
|
@@ -2043,8 +2140,8 @@ function translateChunkToAnthropicEvents(chunk, state$1, originalModel) {
|
|
|
2043
2140
|
const events$1 = [];
|
|
2044
2141
|
if (chunk.usage) {
|
|
2045
2142
|
state$1.pendingUsage = {
|
|
2046
|
-
prompt_tokens: chunk.usage.prompt_tokens
|
|
2047
|
-
completion_tokens: chunk.usage.completion_tokens
|
|
2143
|
+
prompt_tokens: chunk.usage.prompt_tokens,
|
|
2144
|
+
completion_tokens: chunk.usage.completion_tokens,
|
|
2048
2145
|
cached_tokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
|
|
2049
2146
|
};
|
|
2050
2147
|
if (state$1.pendingFinishReason && !state$1.messageDeltaSent) {
|
|
@@ -2224,9 +2321,11 @@ async function handleCompletion(c) {
|
|
|
2224
2321
|
stream: true,
|
|
2225
2322
|
stream_options: { include_usage: true }
|
|
2226
2323
|
};
|
|
2227
|
-
const
|
|
2324
|
+
const azureConfig = state.azureOpenAIConfig;
|
|
2325
|
+
const eventStream = isAzureModel && azureConfig ? await createAzureOpenAIChatCompletions(azureConfig, streamPayload) : await createChatCompletions(streamPayload);
|
|
2228
2326
|
return streamSSE(c, async (stream) => {
|
|
2229
2327
|
const { chunks, usage } = await collectChunksWithUsage(eventStream);
|
|
2328
|
+
consola.debug(`[stream] Collected ${chunks.length} chunks, usage:`, usage);
|
|
2230
2329
|
if (usage) setRequestContext(c, {
|
|
2231
2330
|
inputTokens: usage.prompt_tokens,
|
|
2232
2331
|
outputTokens: usage.completion_tokens
|
|
@@ -2238,21 +2337,33 @@ async function handleCompletion(c) {
|
|
|
2238
2337
|
toolCalls: {},
|
|
2239
2338
|
pendingUsage: usage ?? void 0
|
|
2240
2339
|
};
|
|
2241
|
-
for (const chunk of chunks)
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2340
|
+
for (const chunk of chunks) {
|
|
2341
|
+
const events$1 = translateChunkToAnthropicEvents(chunk, streamState, anthropicPayload.model);
|
|
2342
|
+
for (const evt of events$1) {
|
|
2343
|
+
consola.debug(`[stream] Emitting event: ${evt.type}`);
|
|
2344
|
+
await stream.writeSSE({
|
|
2345
|
+
event: evt.type,
|
|
2346
|
+
data: JSON.stringify(evt)
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
const fallbackEvents = createFallbackMessageDeltaEvents(streamState);
|
|
2351
|
+
consola.debug(`[stream] Fallback events: ${fallbackEvents.length}, messageDeltaSent: ${streamState.messageDeltaSent}`);
|
|
2352
|
+
for (const evt of fallbackEvents) {
|
|
2353
|
+
consola.debug(`[stream] Emitting fallback event: ${evt.type}`);
|
|
2354
|
+
await stream.writeSSE({
|
|
2355
|
+
event: evt.type,
|
|
2356
|
+
data: JSON.stringify(evt)
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2249
2359
|
});
|
|
2250
2360
|
}
|
|
2251
2361
|
const nonStreamPayload = {
|
|
2252
2362
|
...openAIPayload,
|
|
2253
2363
|
stream: false
|
|
2254
2364
|
};
|
|
2255
|
-
const
|
|
2365
|
+
const azureConfigNonStream = state.azureOpenAIConfig;
|
|
2366
|
+
const response = isAzureModel && azureConfigNonStream ? await createAzureOpenAIChatCompletions(azureConfigNonStream, nonStreamPayload) : await createChatCompletions(nonStreamPayload);
|
|
2256
2367
|
if (response.usage) setRequestContext(c, {
|
|
2257
2368
|
inputTokens: response.usage.prompt_tokens,
|
|
2258
2369
|
outputTokens: response.usage.completion_tokens
|
|
@@ -2400,6 +2511,7 @@ server.route("/v1/messages", messageRoutes);
|
|
|
2400
2511
|
//#endregion
|
|
2401
2512
|
//#region src/start.ts
|
|
2402
2513
|
async function runServer(options) {
|
|
2514
|
+
consola.info(`copilot-api v${package_default.version}`);
|
|
2403
2515
|
if (options.insecure) {
|
|
2404
2516
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
2405
2517
|
consola.warn("SSL certificate verification disabled (insecure mode)");
|
|
@@ -2462,7 +2574,8 @@ async function runServer(options) {
|
|
|
2462
2574
|
consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
|
|
2463
2575
|
serve({
|
|
2464
2576
|
fetch: server.fetch,
|
|
2465
|
-
port: options.port
|
|
2577
|
+
port: options.port,
|
|
2578
|
+
bun: { idleTimeout: 255 }
|
|
2466
2579
|
});
|
|
2467
2580
|
}
|
|
2468
2581
|
const start = defineCommand({
|
|
@@ -2563,6 +2676,7 @@ const start = defineCommand({
|
|
|
2563
2676
|
const main = defineCommand({
|
|
2564
2677
|
meta: {
|
|
2565
2678
|
name: "copilot-api",
|
|
2679
|
+
version: package_default.version,
|
|
2566
2680
|
description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
|
|
2567
2681
|
},
|
|
2568
2682
|
subCommands: {
|