xiaodcs-copilot-api 2.3.9-public.3 → 2.5.1-public.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 +46 -17
- package/README.zh-CN.md +46 -17
- package/dist/{auth-BiVetBYt.js → auth-D-M3qCeV.js} +79 -16
- package/dist/auth-DIGdDJx3.js +2 -0
- package/dist/{config-SytZjLq8.js → config-DnMKoKrg.js} +43 -29
- package/dist/{debug-BFadhEB4.js → debug-CuLfQpTq.js} +3 -3
- package/dist/main.js +6 -6
- package/dist/{mcp-fpSlKZxK.js → mcp-DqslOG3W.js} +1 -1
- package/dist/{mcp-server-BeNu_Edl.js → mcp-server-B-_dWuXe.js} +1 -1
- package/dist/mcp-server-Byq5DomJ.js +2 -0
- package/dist/protocol-BTFLbI8m.js +257 -0
- package/dist/provider-proxy-BbLwvtD_.js +263 -0
- package/dist/runtime-CWU5BNBf.js +442 -0
- package/dist/server-DmKNMBLd.js +13628 -0
- package/dist/{start-FFVCi8su.js → start-WdcRgBSL.js} +110 -11
- package/dist/{token-D9svRIYW.js → token-CQftvMU4.js} +48 -31
- package/dist/transport-DE8Q0wU-.js +252 -0
- package/dist/transport-HOWgkXsv.js +2 -0
- package/package.json +20 -8
- package/pages/index.html +106 -32
- package/qoder-bridge/README.md +112 -0
- package/dist/auth-Bu4MXadr.js +0 -2
- package/dist/mcp-server-DQ4r-fAy.js +0 -2
- package/dist/models-YMUf33c-.js +0 -88
- package/dist/server-CFQmvoAJ.js +0 -11710
- /package/dist/{electron-fetch-BRX-ug5E.js → electron-fetch-D5uOld7F.js} +0 -0
- /package/dist/{tls-Aq1Dd8E2.js → tls-B5VJAJJM.js} +0 -0
- /package/dist/{tool-search-Ds1vbmGG.js → tool-search-DCb3HbWj.js} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "xiaodcs-copilot-api",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.5.1-public.2",
|
|
5
5
|
"description": "XiaoDcs copilot-api package built from the same Edge source with Responses recovery and Codex integrations.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"github-copilot",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
|
-
"pages"
|
|
27
|
+
"pages",
|
|
28
|
+
"qoder-bridge/README.md"
|
|
28
29
|
],
|
|
29
30
|
"publishConfig": {
|
|
30
31
|
"access": "public"
|
|
@@ -35,25 +36,32 @@
|
|
|
35
36
|
"dev": "NODE_USE_SYSTEM_CA=1 bun run --watch ./src/main.ts",
|
|
36
37
|
"knip": "knip-bun",
|
|
37
38
|
"lint": "eslint --cache",
|
|
38
|
-
"lint:all": "
|
|
39
|
+
"lint:all": "bun run lint && bun run --cwd desktop lint",
|
|
40
|
+
"lint:desktop": "bun run --cwd desktop lint",
|
|
39
41
|
"prepack": "bun run build",
|
|
40
42
|
"prepare": "simple-git-hooks",
|
|
41
43
|
"release": "bumpp && bun publish --access public",
|
|
42
|
-
"start": "NODE_USE_SYSTEM_CA=1 NODE_ENV=production
|
|
44
|
+
"start": "bun run build && NODE_USE_SYSTEM_CA=1 NODE_ENV=production node ./dist/main.js",
|
|
43
45
|
"test:ci": "bash scripts/test-ci.sh",
|
|
44
|
-
"typecheck": "tsc"
|
|
46
|
+
"typecheck": "tsc",
|
|
47
|
+
"typecheck:all": "bun run typecheck && bun run --cwd desktop typecheck",
|
|
48
|
+
"typecheck:desktop": "bun run --cwd desktop typecheck"
|
|
45
49
|
},
|
|
46
50
|
"simple-git-hooks": {
|
|
47
51
|
"pre-commit": "bunx lint-staged"
|
|
48
52
|
},
|
|
49
53
|
"lint-staged": {
|
|
50
|
-
"*": "bun run lint --fix"
|
|
54
|
+
"*": "bun run lint --fix",
|
|
55
|
+
"desktop/**/*": "eslint --config desktop/eslint.config.mjs --cache --fix"
|
|
51
56
|
},
|
|
52
57
|
"overrides": {
|
|
53
58
|
"@hono/node-server": "^1.19.15",
|
|
54
59
|
"rolldown": "1.0.0-rc.18"
|
|
55
60
|
},
|
|
56
61
|
"dependencies": {
|
|
62
|
+
"@azure/identity": "^4.13.2",
|
|
63
|
+
"@clack/prompts": "^1.7.0",
|
|
64
|
+
"@fastify/busboy": "^3.2.2",
|
|
57
65
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
58
66
|
"citty": "^0.1.6",
|
|
59
67
|
"clipboardy": "^5.0.0",
|
|
@@ -67,7 +75,8 @@
|
|
|
67
75
|
"tiny-invariant": "^1.3.3",
|
|
68
76
|
"undici": "^7.29.0",
|
|
69
77
|
"winreg": "^1.2.5",
|
|
70
|
-
"zod": "^4.1.11"
|
|
78
|
+
"zod": "^4.1.11",
|
|
79
|
+
"@qoder-ai/qoder-agent-sdk": "1.0.31"
|
|
71
80
|
},
|
|
72
81
|
"devDependencies": {
|
|
73
82
|
"@eslint/js": "^9.37.0",
|
|
@@ -93,5 +102,8 @@
|
|
|
93
102
|
"engines": {
|
|
94
103
|
"node": ">=20"
|
|
95
104
|
},
|
|
96
|
-
"
|
|
105
|
+
"trustedDependencies": [
|
|
106
|
+
"@qoder-ai/qoder-agent-sdk"
|
|
107
|
+
],
|
|
108
|
+
"gitHead": "489f9ba568de8258057d74c52b9b74d12439ec5b"
|
|
97
109
|
}
|
package/pages/index.html
CHANGED
|
@@ -423,9 +423,12 @@
|
|
|
423
423
|
id="token-usage-period"
|
|
424
424
|
class="select-control input-focus"
|
|
425
425
|
>
|
|
426
|
-
<option value="
|
|
427
|
-
<option value="
|
|
428
|
-
<option value="
|
|
426
|
+
<option value="today">Today</option>
|
|
427
|
+
<option value="this_week">This week</option>
|
|
428
|
+
<option value="last_7_days">Last 7 days</option>
|
|
429
|
+
<option value="this_month">This month</option>
|
|
430
|
+
<option value="last_30_days">Last 30 days</option>
|
|
431
|
+
<option value="lifetime">Lifetime</option>
|
|
429
432
|
</select>
|
|
430
433
|
</div>
|
|
431
434
|
<button id="fetch-button" type="submit" class="primary-action">
|
|
@@ -463,13 +466,21 @@
|
|
|
463
466
|
});
|
|
464
467
|
|
|
465
468
|
const DEFAULT_ENDPOINT = "http://localhost:4141/usage";
|
|
466
|
-
const DEFAULT_TOKEN_USAGE_PERIOD = "
|
|
469
|
+
const DEFAULT_TOKEN_USAGE_PERIOD = "today";
|
|
467
470
|
const DEFAULT_TOKEN_USAGE_EVENTS_PAGE_SIZE = 20;
|
|
471
|
+
const MAX_LIFETIME_TREND_POINTS = 180;
|
|
468
472
|
const ALL_METRICS_VALUE = "__all__";
|
|
469
473
|
const ALL_MODELS_VALUE = "__all__";
|
|
470
474
|
const LEGACY_API_KEY_STORAGE_KEY = "copilot-api.usage-viewer.x-api-key";
|
|
471
475
|
const CREDENTIAL_STORAGE_PREFIX = "copilot-api.usage-viewer.credential.";
|
|
472
|
-
const VALID_PERIODS = new Set([
|
|
476
|
+
const VALID_PERIODS = new Set([
|
|
477
|
+
"today",
|
|
478
|
+
"this_week",
|
|
479
|
+
"last_7_days",
|
|
480
|
+
"this_month",
|
|
481
|
+
"last_30_days",
|
|
482
|
+
"lifetime",
|
|
483
|
+
]);
|
|
473
484
|
const EMPTY_TOKEN_USAGE_TOTALS = {
|
|
474
485
|
cache_creation_input_tokens: 0,
|
|
475
486
|
cache_read_input_tokens: 0,
|
|
@@ -495,7 +506,7 @@
|
|
|
495
506
|
tokenUsageSummaryError: null,
|
|
496
507
|
tokenUsageEventsError: null,
|
|
497
508
|
tokenUsagePeriod: DEFAULT_TOKEN_USAGE_PERIOD,
|
|
498
|
-
|
|
509
|
+
tokenUsageTrendDay: null,
|
|
499
510
|
tokenUsageTrendMetric: ALL_METRICS_VALUE,
|
|
500
511
|
tokenUsageTrendModel: ALL_MODELS_VALUE,
|
|
501
512
|
};
|
|
@@ -536,10 +547,17 @@
|
|
|
536
547
|
);
|
|
537
548
|
}
|
|
538
549
|
|
|
550
|
+
const LEGACY_PERIODS = {
|
|
551
|
+
day: "today",
|
|
552
|
+
week: "last_7_days",
|
|
553
|
+
month: "last_30_days",
|
|
554
|
+
};
|
|
555
|
+
|
|
539
556
|
function normalizePeriod(value) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
557
|
+
if (VALID_PERIODS.has(value)) {
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
return LEGACY_PERIODS[value] || DEFAULT_TOKEN_USAGE_PERIOD;
|
|
543
561
|
}
|
|
544
562
|
|
|
545
563
|
function getSelectedPeriod() {
|
|
@@ -895,20 +913,40 @@
|
|
|
895
913
|
return trimmed || "—";
|
|
896
914
|
}
|
|
897
915
|
|
|
916
|
+
function formatRangeDate(value) {
|
|
917
|
+
const date = new Date(value);
|
|
918
|
+
if (!Number.isFinite(date.getTime())) {
|
|
919
|
+
return "N/A";
|
|
920
|
+
}
|
|
921
|
+
const options = { day: "numeric", month: "short" };
|
|
922
|
+
if (date.getFullYear() !== new Date().getFullYear()) {
|
|
923
|
+
options.year = "numeric";
|
|
924
|
+
}
|
|
925
|
+
return date.toLocaleDateString("en-US", options);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function formatRangeText(range) {
|
|
929
|
+
const start = formatRangeDate(range.start_ms);
|
|
930
|
+
const end = formatRangeDate(range.end_ms - 1);
|
|
931
|
+
return start === end ? start : `${start} - ${end}`;
|
|
932
|
+
}
|
|
933
|
+
|
|
898
934
|
function renderTokenUsageRangeText(period, range) {
|
|
899
935
|
const labels = {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
936
|
+
today: "Today",
|
|
937
|
+
this_week: "This week",
|
|
938
|
+
last_7_days: "Last 7 days",
|
|
939
|
+
this_month: "This month",
|
|
940
|
+
last_30_days: "Last 30 days",
|
|
941
|
+
lifetime: "Lifetime",
|
|
903
942
|
};
|
|
943
|
+
const label = labels[period] || labels.today;
|
|
904
944
|
|
|
905
|
-
if (!range) {
|
|
906
|
-
return
|
|
945
|
+
if (!range || range.end_ms <= range.start_ms) {
|
|
946
|
+
return label;
|
|
907
947
|
}
|
|
908
948
|
|
|
909
|
-
return `${
|
|
910
|
-
range.start_ms
|
|
911
|
-
)} - ${formatDateTime(range.end_ms)}`;
|
|
949
|
+
return `${label}: ${formatRangeText(range)}`;
|
|
912
950
|
}
|
|
913
951
|
|
|
914
952
|
/**
|
|
@@ -1334,8 +1372,46 @@
|
|
|
1334
1372
|
return parts.length === 3 ? `${parts[1]}/${parts[2]}` : value;
|
|
1335
1373
|
}
|
|
1336
1374
|
|
|
1375
|
+
function getTokenUsageTrendDays(dailySummary, selectedModel) {
|
|
1376
|
+
const days = dailySummary?.days || [];
|
|
1377
|
+
if (
|
|
1378
|
+
state.tokenUsagePeriod !== "lifetime"
|
|
1379
|
+
|| days.length <= MAX_LIFETIME_TREND_POINTS
|
|
1380
|
+
) {
|
|
1381
|
+
return days;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const usageIndexes = days.reduce((indexes, day, index) => {
|
|
1385
|
+
if (getDailyTrendTotals(day, selectedModel).request_count > 0) {
|
|
1386
|
+
indexes.push(index);
|
|
1387
|
+
}
|
|
1388
|
+
return indexes;
|
|
1389
|
+
}, []);
|
|
1390
|
+
if (usageIndexes.length >= MAX_LIFETIME_TREND_POINTS) {
|
|
1391
|
+
const step =
|
|
1392
|
+
(usageIndexes.length - 1) / (MAX_LIFETIME_TREND_POINTS - 1);
|
|
1393
|
+
return Array.from(
|
|
1394
|
+
{ length: MAX_LIFETIME_TREND_POINTS },
|
|
1395
|
+
(_, index) => days[usageIndexes[Math.round(index * step)]]
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
const indexes = new Set(usageIndexes);
|
|
1399
|
+
const sampleCount = Math.max(
|
|
1400
|
+
1,
|
|
1401
|
+
MAX_LIFETIME_TREND_POINTS - usageIndexes.length
|
|
1402
|
+
);
|
|
1403
|
+
const step = (days.length - 1) / (sampleCount - 1 || 1);
|
|
1404
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
1405
|
+
indexes.add(Math.round(index * step));
|
|
1406
|
+
}
|
|
1407
|
+
return [...indexes]
|
|
1408
|
+
.sort((left, right) => left - right)
|
|
1409
|
+
.slice(0, MAX_LIFETIME_TREND_POINTS)
|
|
1410
|
+
.map((index) => days[index]);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1337
1413
|
function renderTokenUsageDailyTrend(dailySummary, error) {
|
|
1338
|
-
if (state.tokenUsagePeriod === "
|
|
1414
|
+
if (state.tokenUsagePeriod === "today") {
|
|
1339
1415
|
return "";
|
|
1340
1416
|
}
|
|
1341
1417
|
|
|
@@ -1435,7 +1511,6 @@
|
|
|
1435
1511
|
}
|
|
1436
1512
|
|
|
1437
1513
|
function renderTokenUsageTrendChart(dailySummary, selectedModel) {
|
|
1438
|
-
const days = dailySummary?.days || [];
|
|
1439
1514
|
const metrics = getTokenUsageTrendMetrics();
|
|
1440
1515
|
const visibleMetrics =
|
|
1441
1516
|
state.tokenUsageTrendMetric === ALL_METRICS_VALUE
|
|
@@ -1443,6 +1518,7 @@
|
|
|
1443
1518
|
: metrics.filter(
|
|
1444
1519
|
(metric) => metric.key === state.tokenUsageTrendMetric
|
|
1445
1520
|
);
|
|
1521
|
+
const days = getTokenUsageTrendDays(dailySummary, selectedModel);
|
|
1446
1522
|
const hasUsage = days.some(
|
|
1447
1523
|
(day) => getDailyTrendTotals(day, selectedModel).request_count > 0
|
|
1448
1524
|
);
|
|
@@ -1451,11 +1527,12 @@
|
|
|
1451
1527
|
? index
|
|
1452
1528
|
: latest;
|
|
1453
1529
|
}, -1);
|
|
1530
|
+
const selectedDayIndex = days.findIndex(
|
|
1531
|
+
(day) => day.date === state.tokenUsageTrendDay
|
|
1532
|
+
);
|
|
1454
1533
|
const activeIndex =
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
&& state.tokenUsageTrendDayIndex < days.length
|
|
1458
|
-
? state.tokenUsageTrendDayIndex
|
|
1534
|
+
selectedDayIndex >= 0
|
|
1535
|
+
? selectedDayIndex
|
|
1459
1536
|
: latestUsageIndex >= 0
|
|
1460
1537
|
? latestUsageIndex
|
|
1461
1538
|
: Math.max(0, days.length - 1);
|
|
@@ -1544,7 +1621,7 @@
|
|
|
1544
1621
|
value
|
|
1545
1622
|
)}" r="2.5" fill="${
|
|
1546
1623
|
metric.color
|
|
1547
|
-
}" data-trend-day
|
|
1624
|
+
}" data-trend-day="${escapeHtml(day.date)}">
|
|
1548
1625
|
<title>${escapeHtml(
|
|
1549
1626
|
`${formatChartDate(day.date)} ${metric.label}: ${formatNumber(
|
|
1550
1627
|
value
|
|
@@ -1588,7 +1665,7 @@
|
|
|
1588
1665
|
].join("\n");
|
|
1589
1666
|
return `<rect x="${hitStart}" y="${padding.top}" width="${
|
|
1590
1667
|
hitEnd - hitStart
|
|
1591
|
-
}" height="${plotHeight}" fill="transparent" style="cursor: pointer;" data-trend-day
|
|
1668
|
+
}" height="${plotHeight}" fill="transparent" style="cursor: pointer;" data-trend-day="${escapeHtml(day.date)}">
|
|
1592
1669
|
<title>${escapeHtml(title)}</title>
|
|
1593
1670
|
</rect>`;
|
|
1594
1671
|
})
|
|
@@ -2153,14 +2230,11 @@
|
|
|
2153
2230
|
return;
|
|
2154
2231
|
}
|
|
2155
2232
|
|
|
2156
|
-
const trendPoint = event.target.closest("[data-trend-day
|
|
2233
|
+
const trendPoint = event.target.closest("[data-trend-day]");
|
|
2157
2234
|
if (trendPoint) {
|
|
2158
|
-
const
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
);
|
|
2162
|
-
if (Number.isFinite(index) && index >= 0) {
|
|
2163
|
-
state.tokenUsageTrendDayIndex = index;
|
|
2235
|
+
const day = trendPoint.getAttribute("data-trend-day");
|
|
2236
|
+
if (day) {
|
|
2237
|
+
state.tokenUsageTrendDay = day;
|
|
2164
2238
|
render();
|
|
2165
2239
|
}
|
|
2166
2240
|
return;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Qoder SDK → copilot-api
|
|
2
|
+
|
|
3
|
+
Qoder 已内置到 npm 包中,支持文本与图片输入的 Chat Completions、SSE、模型目录和 Credits 查询。账号额度由 Qoder 计量,不会转移到 GitHub Copilot。
|
|
4
|
+
|
|
5
|
+
## npm 安装与配置
|
|
6
|
+
|
|
7
|
+
推荐 Node 24 LTS。普通包使用:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g xiaodcs-copilot-api
|
|
11
|
+
copilot-api auth login --provider qoder
|
|
12
|
+
copilot-api auth keys --add <你生成的网关密钥>
|
|
13
|
+
copilot-api start
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Edge 包对应 `npm install -g xiaodcs-copilot-api-edge`,命令替换为 `copilot-api-edge`。这些步骤适用于包含本次内置适配的版本;本次代码修改尚未发布到 npm。
|
|
17
|
+
|
|
18
|
+
登录命令会提示输入 [Qoder PAT](https://qoder.com/account/integrations),并通过现有配置写入机制保存到 `~/.local/share/copilot-api/config.json`(文件权限 0600)。指定 `--api-home` 或 `COPILOT_API_HOME` 时使用对应目录,配置不会随 npm 包升级丢失。交互终端中的 PAT 输入会隐藏,不会打印到日志。
|
|
19
|
+
|
|
20
|
+
也可以把以下条目合并进已有配置;`apiKey` 是 **Qoder PAT**,与客户端访问网关的密钥分开:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"providers": {
|
|
25
|
+
"qoder": {
|
|
26
|
+
"enabled": true,
|
|
27
|
+
"apiKey": "<Qoder PAT>"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Qoder 默认使用 `openai-compatible` 协议和内置 SDK。登录命令写入的 `baseUrl: "qoder://sdk"` 是内部标记,不是需要启动的网络地址。用户不必安装单独的 bridge、创建 `.env.local`、配置 bridge key 或监听额外端口。主进程按需创建 SDK 会话,正常启动一个网关即可。
|
|
34
|
+
|
|
35
|
+
npm 会安装固定版本的官方 SDK 及其匹配 Worker runtime;需要允许依赖的安装脚本和下载网络访问。使用 Bun 安装时,仓库已信任 SDK 的安装脚本。禁用安装脚本的环境需在允许脚本后重新安装包。无需预先安装全局 Qoder CLI。`QODER_PROXY` 可为 SDK 指定代理,否则使用 `HTTPS_PROXY` / `HTTP_PROXY`。
|
|
36
|
+
|
|
37
|
+
## 调用
|
|
38
|
+
|
|
39
|
+
客户端 Base URL 使用网关的 `http://127.0.0.1:4141/v1`,API Key 使用网关密钥,模型使用 `qoder/<模型 ID>`。
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
curl -H "Authorization: Bearer $COPILOT_API_KEY" http://127.0.0.1:4141/qoder/v1/models
|
|
43
|
+
curl -H "Authorization: Bearer $COPILOT_API_KEY" http://127.0.0.1:4141/qoder/usage
|
|
44
|
+
curl -N http://127.0.0.1:4141/v1/chat/completions \
|
|
45
|
+
-H "Authorization: Bearer $COPILOT_API_KEY" \
|
|
46
|
+
-H 'Content-Type: application/json' \
|
|
47
|
+
-d '{"model":"qoder/performance","messages":[{"role":"user","content":"Reply with OK only."}],"stream":true,"stream_options":{"include_usage":true}}'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`performance` 以账号实际模型目录为准。`/qoder/usage` 返回账号 Credits 快照;Chat Completions 的 `qoder_usage.total_credits` 返回本次会话用量。SDK 未提供有效 token 计数时,OpenAI `usage` 省略或为 null,不估算计费数据。
|
|
51
|
+
|
|
52
|
+
每个请求使用独立临时目录与会话,禁用内置本地工具、技能和插件;仅注册请求提供的客户端 MCP 工具。最多并发 2 个 SDK 会话,每个请求 120 秒;客户端断开会取消对应请求。历史消息以 JSON 文字加图片编号重建,system/developer 通过 SDK systemPrompt 提供。
|
|
53
|
+
|
|
54
|
+
支持客户端 function 工具调用:SDK 请求工具时将调用返回给客户端执行,下一次请求携带工具结果继续。集成网关通过现有 Responses → Messages → Chat 转换支持 Codex;独立 bridge 仍提供 Chat Completions 端点。已实测 Codex 终端工具往返、JSON 和 SSE 工具结果回传。
|
|
55
|
+
|
|
56
|
+
Qoder 路由自动移除 Codex 默认附带的 provider-native web search 声明并告知模型不可用;显式强制搜索仍报错,客户端提供的搜索工具不受影响。暂不支持图片生成/编辑、结构化输出、强制指定工具、parallel_tool_calls=false(带工具时)及 temperature/max_tokens 等采样参数。max_completion_tokens 仅接受不小于 SDK 原生 32000 上限的值,不伪装可配置更小的生成上限。工具交接时 SDK 会话中止,因此可能没有最终 usage;缺失不表示免费。
|
|
57
|
+
|
|
58
|
+
模型目录公开 SDK 默认窗口、可选窗口、输入上限和输出上限。provider 的 extraBody.context_window 可以选择 SDK 实际支持的窗口;未指定则保留默认。Codex 客户端预算应从默认总窗口扣除 32000 输出预留并受 max_input_tokens 约束,不能直接按模型最大可选 1M 宣传。默认未声明的 auto/efficient/lite 按最小可选 128000 保守计算。
|
|
59
|
+
|
|
60
|
+
## 图片输入
|
|
61
|
+
|
|
62
|
+
用户消息的 `content` 支持 OpenAI `image_url`,包括 Base64 data URL 和公网 HTTPS 图片。图片作为 SDK 原生图片块发送,历史消息中的图片用编号保留与文字的对应关系。支持 PNG、JPEG、WebP、GIF;每次请求最多 4 张、每张 5 MiB、图片合计 15 MiB。`detail` 可省略或使用 `auto`;不支持 `high`/`low` 映射。
|
|
63
|
+
|
|
64
|
+
例如发送到网关 `/v1/chat/completions`:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"model": "qoder/performance",
|
|
69
|
+
"messages": [{
|
|
70
|
+
"role": "user",
|
|
71
|
+
"content": [
|
|
72
|
+
{"type": "text", "text": "描述这张图片"},
|
|
73
|
+
{"type": "image_url", "image_url": {"url": "data:image/png;base64,<图片的 Base64>"}}
|
|
74
|
+
]
|
|
75
|
+
}],
|
|
76
|
+
"stream": true
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
HTTPS 下载仅接受公网 IP、443 端口,最多 3 次重定向;每跳重新检查地址并固定连接 IP,不向图片网站发送 PAT、网关密钥或 Cookie。下载使用直接 HTTPS 连接,不采用 SDK 的 `QODER_PROXY`。如果本机代理的 Fake-IP DNS 把域名解析为 `198.18.*` 等保留地址,请改用 Base64,或为图片域名配置真实 DNS 解析。本机实测 HTTPS 链接因这一 DNS 条件被拒绝;Base64 经真实网关和 Qoder 的流式识图已通过。
|
|
81
|
+
|
|
82
|
+
## 从源码启动
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
bun install --frozen-lockfile
|
|
86
|
+
bun run start --api-home=/你的配置目录 start --port 4141
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`bun run start` 先构建再使用 Node 运行。推荐 Node 24 LTS。不要用 `bun run src/main.ts` 启动 Qoder:本机 Bun 1.3.6 的 SDK Worker 会立即关闭 transport,导致模型目录返回 502。npm 安装后的命令默认使用 Node,不受这个源码启动问题影响。
|
|
90
|
+
|
|
91
|
+
同时启动两个配置时,先执行一次 `bun run build`,再分别运行 `node dist/main.js --api-home=... start --port 4141` 和 `node dist/main.js start --port 4142`,避免并发构建同一个 dist 目录。启动会单独打印 `Qoder models (数量)`;发现失败会打印警告,不影响其他 provider 启动。
|
|
92
|
+
|
|
93
|
+
## 从旧实验配置迁移
|
|
94
|
+
|
|
95
|
+
重新运行 `auth login --provider qoder` 并输入原来的 PAT 即可。它会替换旧的本地 HTTP bridge 配置,保留其他 provider 和已有模型配置。原 `.env.local` 不会自动导入;主网关的内置模式只使用 `providers.qoder.apiKey`,不读取实验 PAT 环境变量。
|
|
96
|
+
|
|
97
|
+
旧的 `baseUrl: "http://127.0.0.1:4343"` 配置仍保留 HTTP 转发兼容性。源码中的独立 sidecar 是开发调试入口,不是 npm 用户的必需步骤。
|
|
98
|
+
|
|
99
|
+
## 开发验证
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
bun install --frozen-lockfile
|
|
103
|
+
bun run lint --fix qoder-bridge/*.ts src/services/qoder/*.ts
|
|
104
|
+
bun run typecheck
|
|
105
|
+
bun test --coverage tests/qoder-transport.test.ts qoder-bridge/bridge.test.ts qoder-bridge/images.test.ts
|
|
106
|
+
bun qoder-bridge/integration-integrated.ts
|
|
107
|
+
bun run test:ci
|
|
108
|
+
bun run build
|
|
109
|
+
npm pack --ignore-scripts
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
集成脚本使用随机端口和模拟 SDK,不调用真实模型。npm 安装产物的真实 PAT 验证记录见 [验证报告](../docs/sync-v2.5.1-qoder-validation.md)。
|
package/dist/auth-Bu4MXadr.js
DELETED
package/dist/models-YMUf33c-.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { K as state } from "./token-D9svRIYW.js";
|
|
2
|
-
//#region src/lib/models.ts
|
|
3
|
-
/**
|
|
4
|
-
* Converts a Copilot upstream model ID to a client-friendly ID that Claude Code
|
|
5
|
-
* and Claude Desktop recognize (dots in version replaced with hyphens).
|
|
6
|
-
* e.g. "claude-sonnet-4.6" -> "claude-sonnet-4-6"
|
|
7
|
-
* Non-Claude models are returned unchanged.
|
|
8
|
-
*/
|
|
9
|
-
const toClientModelId = (modelId) => {
|
|
10
|
-
const normalized = normalizeSdkModelId(modelId);
|
|
11
|
-
if (!normalized) return modelId;
|
|
12
|
-
const versionHyphenated = normalized.version.replaceAll(".", "-");
|
|
13
|
-
return `claude-${normalized.family}-${versionHyphenated}`;
|
|
14
|
-
};
|
|
15
|
-
const findEndpointModel = (sdkModelId) => {
|
|
16
|
-
const models = state.models?.data ?? [];
|
|
17
|
-
const exactMatch = models.find((m) => m.id === sdkModelId);
|
|
18
|
-
if (exactMatch) return exactMatch;
|
|
19
|
-
const normalized = normalizeSdkModelId(sdkModelId);
|
|
20
|
-
if (!normalized) return;
|
|
21
|
-
const modelName = `claude-${normalized.family}-${normalized.version}`;
|
|
22
|
-
const model = models.find((m) => m.id === modelName);
|
|
23
|
-
if (model) return model;
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Finds the latest available model for a given Claude family (e.g. "opus",
|
|
27
|
-
* "sonnet", "haiku") among the models currently cached in `state.models`.
|
|
28
|
-
* "Latest" is determined by the highest semantic version parsed from the model
|
|
29
|
-
* ID. Returns `undefined` when no model of that family is available.
|
|
30
|
-
*/
|
|
31
|
-
const getLatestModelForFamily = (family) => {
|
|
32
|
-
const models = state.models?.data ?? [];
|
|
33
|
-
let best;
|
|
34
|
-
for (const model of models) {
|
|
35
|
-
const normalized = normalizeSdkModelId(model.id);
|
|
36
|
-
if (!normalized || normalized.family !== family) continue;
|
|
37
|
-
const [majorPart, minorPart = "0"] = normalized.version.split(".");
|
|
38
|
-
const major = Number.parseInt(majorPart, 10);
|
|
39
|
-
const minor = Number.parseInt(minorPart, 10);
|
|
40
|
-
if (Number.isNaN(major) || Number.isNaN(minor)) continue;
|
|
41
|
-
if (!best || major > best.major || major === best.major && minor > best.minor) best = {
|
|
42
|
-
model,
|
|
43
|
-
major,
|
|
44
|
-
minor
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return best?.model;
|
|
48
|
-
};
|
|
49
|
-
/**
|
|
50
|
-
* Normalizes an SDK model ID to extract the model family and version.
|
|
51
|
-
* this method from github copilot extension
|
|
52
|
-
* Examples:
|
|
53
|
-
* - "claude-opus-4-5-20251101" -> { family: "opus", version: "4.5" }
|
|
54
|
-
* - "claude-3-5-sonnet-20241022" -> { family: "sonnet", version: "3.5" }
|
|
55
|
-
* - "claude-sonnet-4-20250514" -> { family: "sonnet", version: "4" }
|
|
56
|
-
* - "claude-haiku-3-5-20250514" -> { family: "haiku", version: "3.5" }
|
|
57
|
-
* - "claude-haiku-4.5" -> { family: "haiku", version: "4.5" }
|
|
58
|
-
*/
|
|
59
|
-
const normalizeSdkModelId = (sdkModelId) => {
|
|
60
|
-
const withoutDate = sdkModelId.toLowerCase().replace(/-\d{8}$/, "");
|
|
61
|
-
const pattern1 = withoutDate.match(/^claude-(\w+)-(\d+)\.(\d+)$/);
|
|
62
|
-
if (pattern1) return {
|
|
63
|
-
family: pattern1[1],
|
|
64
|
-
version: `${pattern1[2]}.${pattern1[3]}`
|
|
65
|
-
};
|
|
66
|
-
const pattern2 = withoutDate.match(/^claude-(\w+)-(\d+)-(\d+)$/);
|
|
67
|
-
if (pattern2) return {
|
|
68
|
-
family: pattern2[1],
|
|
69
|
-
version: `${pattern2[2]}.${pattern2[3]}`
|
|
70
|
-
};
|
|
71
|
-
const pattern3 = withoutDate.match(/^claude-(\d+)-(\d+)-(\w+)$/);
|
|
72
|
-
if (pattern3) return {
|
|
73
|
-
family: pattern3[3],
|
|
74
|
-
version: `${pattern3[1]}.${pattern3[2]}`
|
|
75
|
-
};
|
|
76
|
-
const pattern4 = withoutDate.match(/^claude-(\w+)-(\d+)$/);
|
|
77
|
-
if (pattern4) return {
|
|
78
|
-
family: pattern4[1],
|
|
79
|
-
version: pattern4[2]
|
|
80
|
-
};
|
|
81
|
-
const pattern5 = withoutDate.match(/^claude-(\d+)-(\w+)$/);
|
|
82
|
-
if (pattern5) return {
|
|
83
|
-
family: pattern5[2],
|
|
84
|
-
version: pattern5[1]
|
|
85
|
-
};
|
|
86
|
-
};
|
|
87
|
-
//#endregion
|
|
88
|
-
export { toClientModelId as i, getLatestModelForFamily as n, normalizeSdkModelId as r, findEndpointModel as t };
|