@peterwangze/claude-trigger-router 1.0.4 → 1.0.5
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 +360 -296
- package/config/trigger.advanced.yaml +210 -0
- package/config/trigger.example.yaml +3 -190
- package/dist/cli.js +1469 -345
- package/dist/cli.js.map +4 -4
- package/package.json +9 -1
package/dist/cli.js
CHANGED
|
@@ -224,6 +224,40 @@ function inferTransformer(protocol) {
|
|
|
224
224
|
}
|
|
225
225
|
return void 0;
|
|
226
226
|
}
|
|
227
|
+
function inferCompatibilityProfile(item, modelInterface) {
|
|
228
|
+
if (modelInterface === "anthropic") {
|
|
229
|
+
return "anthropic-native";
|
|
230
|
+
}
|
|
231
|
+
const api = getModelApi(item);
|
|
232
|
+
const vendorHint = item.metadata?.vendor_hint?.trim().toLowerCase();
|
|
233
|
+
if (vendorHint === "openrouter" || api.includes("openrouter.ai")) {
|
|
234
|
+
return "openrouter-like";
|
|
235
|
+
}
|
|
236
|
+
if (vendorHint === "qianfan" || vendorHint === "qianfan-coding" || api.includes("qianfan.baidubce.com/v2/coding")) {
|
|
237
|
+
return "qianfan-coding";
|
|
238
|
+
}
|
|
239
|
+
if (vendorHint === "minimax" || vendorHint === "minimax-chatcompletion-v2" || api.includes("/v1/text/chatcompletion_v2")) {
|
|
240
|
+
return "minimax-chatcompletion-v2";
|
|
241
|
+
}
|
|
242
|
+
return "generic-openai-compatible";
|
|
243
|
+
}
|
|
244
|
+
function getDispatchFormatForProfile(modelInterface, compatibilityProfile) {
|
|
245
|
+
if (modelInterface === "anthropic") {
|
|
246
|
+
return "anthropic_messages";
|
|
247
|
+
}
|
|
248
|
+
switch (compatibilityProfile) {
|
|
249
|
+
case "openrouter-like":
|
|
250
|
+
case "qianfan-coding":
|
|
251
|
+
case "minimax-chatcompletion-v2":
|
|
252
|
+
return "anthropic_messages";
|
|
253
|
+
case "anthropic-native":
|
|
254
|
+
return "anthropic_messages";
|
|
255
|
+
case "generic-openai-compatible":
|
|
256
|
+
return "anthropic_messages";
|
|
257
|
+
default:
|
|
258
|
+
return "anthropic_messages";
|
|
259
|
+
}
|
|
260
|
+
}
|
|
227
261
|
function buildCompiledCapabilities(item, modelInterface) {
|
|
228
262
|
const reasoningSupported = item.metadata?.supports_reasoning !== false;
|
|
229
263
|
return {
|
|
@@ -255,12 +289,15 @@ function buildModelRegistry(config) {
|
|
|
255
289
|
const modelMap2 = config.Models.reduce((result, rawItem) => {
|
|
256
290
|
const item = normalizeModelEndpointConfig(rawItem);
|
|
257
291
|
const modelInterface = getModelInterface(item) || "openai";
|
|
292
|
+
const compatibilityProfile = inferCompatibilityProfile(item, modelInterface);
|
|
258
293
|
result[item.id] = {
|
|
259
294
|
id: item.id,
|
|
260
295
|
providerName: `model__${item.id}`,
|
|
261
296
|
modelName: item.model,
|
|
262
297
|
interface: modelInterface,
|
|
263
298
|
protocol: modelInterface,
|
|
299
|
+
compatibilityProfile,
|
|
300
|
+
dispatchFormat: getDispatchFormatForProfile(modelInterface, compatibilityProfile),
|
|
264
301
|
thinking: item.thinking,
|
|
265
302
|
capabilities: buildCompiledCapabilities(item, modelInterface),
|
|
266
303
|
source: "models"
|
|
@@ -275,12 +312,23 @@ function buildModelRegistry(config) {
|
|
|
275
312
|
const providers = config.Providers ?? [];
|
|
276
313
|
const modelMap = providers.reduce((result, provider) => {
|
|
277
314
|
for (const model of provider.models ?? []) {
|
|
315
|
+
const compatibilityProfile = inferCompatibilityProfile(
|
|
316
|
+
{
|
|
317
|
+
api_base_url: provider.api_base_url,
|
|
318
|
+
metadata: {
|
|
319
|
+
vendor_hint: provider.transformer?.use?.[0]
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
"openai"
|
|
323
|
+
);
|
|
278
324
|
result[`${provider.name},${model}`] = {
|
|
279
325
|
id: `${provider.name},${model}`,
|
|
280
326
|
providerName: provider.name,
|
|
281
327
|
modelName: model,
|
|
282
328
|
interface: "openai",
|
|
283
329
|
protocol: "openai",
|
|
330
|
+
compatibilityProfile,
|
|
331
|
+
dispatchFormat: getDispatchFormatForProfile("openai", compatibilityProfile),
|
|
284
332
|
capabilities: {
|
|
285
333
|
thinking: {
|
|
286
334
|
supported: true
|
|
@@ -452,6 +500,9 @@ async function initDir() {
|
|
|
452
500
|
if (!(0, import_fs.existsSync)(CONFIG_DIR)) {
|
|
453
501
|
(0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
454
502
|
}
|
|
503
|
+
if (!(0, import_fs.existsSync)(HOME_DIR)) {
|
|
504
|
+
(0, import_fs.mkdirSync)(HOME_DIR, { recursive: true });
|
|
505
|
+
}
|
|
455
506
|
}
|
|
456
507
|
async function loadYamlConfig(path) {
|
|
457
508
|
if (!(0, import_fs.existsSync)(path)) {
|
|
@@ -930,6 +981,31 @@ async function probeServiceHealth(port, timeoutMs = 500) {
|
|
|
930
981
|
return false;
|
|
931
982
|
}
|
|
932
983
|
}
|
|
984
|
+
async function isTcpPortOccupied(port, timeoutMs = 500) {
|
|
985
|
+
return new Promise((resolve) => {
|
|
986
|
+
const socket = new import_net.Socket();
|
|
987
|
+
let settled = false;
|
|
988
|
+
const finish = (value) => {
|
|
989
|
+
if (settled) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
settled = true;
|
|
993
|
+
socket.destroy();
|
|
994
|
+
resolve(value);
|
|
995
|
+
};
|
|
996
|
+
socket.setTimeout(timeoutMs);
|
|
997
|
+
socket.once("connect", () => finish(true));
|
|
998
|
+
socket.once("timeout", () => finish(false));
|
|
999
|
+
socket.once("error", (error) => {
|
|
1000
|
+
if (error.code === "ECONNREFUSED") {
|
|
1001
|
+
finish(false);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
finish(false);
|
|
1005
|
+
});
|
|
1006
|
+
socket.connect(port, "127.0.0.1");
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
933
1009
|
async function waitForService(port, timeoutMs = 5e3) {
|
|
934
1010
|
const start = Date.now();
|
|
935
1011
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -940,10 +1016,11 @@ async function waitForService(port, timeoutMs = 5e3) {
|
|
|
940
1016
|
}
|
|
941
1017
|
return false;
|
|
942
1018
|
}
|
|
943
|
-
var SERVICE_NAME, SERVICE_HEALTH_PATH;
|
|
1019
|
+
var import_net, SERVICE_NAME, SERVICE_HEALTH_PATH;
|
|
944
1020
|
var init_service_health = __esm({
|
|
945
1021
|
"src/service-health.ts"() {
|
|
946
1022
|
"use strict";
|
|
1023
|
+
import_net = require("net");
|
|
947
1024
|
SERVICE_NAME = "claude-trigger-router";
|
|
948
1025
|
SERVICE_HEALTH_PATH = "/api/health";
|
|
949
1026
|
}
|
|
@@ -957,25 +1034,25 @@ var init_types = __esm({
|
|
|
957
1034
|
});
|
|
958
1035
|
|
|
959
1036
|
// src/governance/trace.ts
|
|
960
|
-
function createGovernanceTrace(
|
|
1037
|
+
function createGovernanceTrace(input3 = {}) {
|
|
961
1038
|
return {
|
|
962
|
-
requestId:
|
|
963
|
-
sessionKey:
|
|
964
|
-
initialModel:
|
|
965
|
-
finalModel:
|
|
966
|
-
routeReason:
|
|
967
|
-
stickyHit:
|
|
968
|
-
alignmentUsed:
|
|
969
|
-
semanticIntent:
|
|
970
|
-
cascadeTriggered:
|
|
971
|
-
cascadeEvidence:
|
|
972
|
-
cascadeNextModel:
|
|
973
|
-
shadowChecked:
|
|
974
|
-
verificationResult:
|
|
975
|
-
latencyMs:
|
|
976
|
-
estimatedCost:
|
|
977
|
-
startedAt:
|
|
978
|
-
completedAt:
|
|
1039
|
+
requestId: input3.requestId ?? (0, import_crypto.randomUUID)(),
|
|
1040
|
+
sessionKey: input3.sessionKey,
|
|
1041
|
+
initialModel: input3.initialModel,
|
|
1042
|
+
finalModel: input3.finalModel,
|
|
1043
|
+
routeReason: input3.routeReason ? [...input3.routeReason] : [],
|
|
1044
|
+
stickyHit: input3.stickyHit ?? false,
|
|
1045
|
+
alignmentUsed: input3.alignmentUsed ?? false,
|
|
1046
|
+
semanticIntent: input3.semanticIntent,
|
|
1047
|
+
cascadeTriggered: input3.cascadeTriggered ?? false,
|
|
1048
|
+
cascadeEvidence: input3.cascadeEvidence ? [...input3.cascadeEvidence] : [],
|
|
1049
|
+
cascadeNextModel: input3.cascadeNextModel,
|
|
1050
|
+
shadowChecked: input3.shadowChecked ?? false,
|
|
1051
|
+
verificationResult: input3.verificationResult,
|
|
1052
|
+
latencyMs: input3.latencyMs,
|
|
1053
|
+
estimatedCost: input3.estimatedCost,
|
|
1054
|
+
startedAt: input3.startedAt ?? Date.now(),
|
|
1055
|
+
completedAt: input3.completedAt
|
|
979
1056
|
};
|
|
980
1057
|
}
|
|
981
1058
|
function appendTraceReason(trace, reason) {
|
|
@@ -1300,16 +1377,16 @@ function normalizeContentParts(content) {
|
|
|
1300
1377
|
}
|
|
1301
1378
|
});
|
|
1302
1379
|
}
|
|
1303
|
-
function createMessageIR(
|
|
1304
|
-
const system = typeof
|
|
1305
|
-
const messages = Array.isArray(
|
|
1380
|
+
function createMessageIR(input3) {
|
|
1381
|
+
const system = typeof input3.system === "string" ? [input3.system] : Array.isArray(input3.system) ? input3.system.flatMap((item) => item?.type === "text" && typeof item.text === "string" ? [item.text] : []) : [];
|
|
1382
|
+
const messages = Array.isArray(input3.messages) ? input3.messages.filter((item) => item?.role).map((item) => ({
|
|
1306
1383
|
role: item.role,
|
|
1307
1384
|
parts: normalizeContentParts(item.content)
|
|
1308
1385
|
})) : [];
|
|
1309
|
-
const thinking =
|
|
1310
|
-
enabled:
|
|
1311
|
-
effort:
|
|
1312
|
-
budget_tokens:
|
|
1386
|
+
const thinking = input3.thinking ? {
|
|
1387
|
+
enabled: input3.thinking?.type === "enabled" || input3.thinking?.enabled === true,
|
|
1388
|
+
effort: input3.thinking?.effort,
|
|
1389
|
+
budget_tokens: input3.thinking?.budget_tokens
|
|
1313
1390
|
} : void 0;
|
|
1314
1391
|
return {
|
|
1315
1392
|
system,
|
|
@@ -1361,34 +1438,38 @@ function toAnthropicContent(parts) {
|
|
|
1361
1438
|
}
|
|
1362
1439
|
});
|
|
1363
1440
|
}
|
|
1364
|
-
function toAnthropicMessagesRequest(
|
|
1441
|
+
function toAnthropicMessagesRequest(input3) {
|
|
1365
1442
|
const body = {
|
|
1366
|
-
model:
|
|
1367
|
-
messages:
|
|
1443
|
+
model: input3.model,
|
|
1444
|
+
messages: input3.ir.messages.map((message) => ({
|
|
1368
1445
|
role: message.role,
|
|
1369
1446
|
content: toAnthropicContent(message.parts)
|
|
1370
1447
|
}))
|
|
1371
1448
|
};
|
|
1372
|
-
if (
|
|
1373
|
-
body.max_tokens =
|
|
1449
|
+
if (input3.max_tokens !== void 0) {
|
|
1450
|
+
body.max_tokens = input3.max_tokens;
|
|
1374
1451
|
}
|
|
1375
|
-
if (
|
|
1376
|
-
body.stream =
|
|
1452
|
+
if (input3.stream !== void 0) {
|
|
1453
|
+
body.stream = input3.stream;
|
|
1377
1454
|
}
|
|
1378
|
-
if (
|
|
1379
|
-
body.metadata =
|
|
1455
|
+
if (input3.metadata) {
|
|
1456
|
+
body.metadata = input3.metadata;
|
|
1380
1457
|
}
|
|
1381
|
-
if (
|
|
1382
|
-
body.tools =
|
|
1458
|
+
if (input3.tools) {
|
|
1459
|
+
body.tools = input3.tools.map((tool) => ({
|
|
1460
|
+
name: tool?.name ?? tool?.function?.name,
|
|
1461
|
+
description: tool?.description ?? tool?.function?.description,
|
|
1462
|
+
input_schema: tool?.input_schema ?? tool?.function?.parameters
|
|
1463
|
+
}));
|
|
1383
1464
|
}
|
|
1384
|
-
if (
|
|
1385
|
-
body.system =
|
|
1465
|
+
if (input3.ir.system.length) {
|
|
1466
|
+
body.system = input3.ir.system.map((text) => ({ type: "text", text }));
|
|
1386
1467
|
}
|
|
1387
|
-
if (
|
|
1468
|
+
if (input3.ir.options?.thinking?.enabled) {
|
|
1388
1469
|
body.thinking = {
|
|
1389
1470
|
type: "enabled",
|
|
1390
|
-
...
|
|
1391
|
-
...
|
|
1471
|
+
...input3.ir.options.thinking.effort ? { effort: input3.ir.options.thinking.effort } : {},
|
|
1472
|
+
...input3.ir.options.thinking.budget_tokens ? { budget_tokens: input3.ir.options.thinking.budget_tokens } : {}
|
|
1392
1473
|
};
|
|
1393
1474
|
}
|
|
1394
1475
|
return body;
|
|
@@ -2121,17 +2202,17 @@ var init_SSEParser_transform = __esm({
|
|
|
2121
2202
|
|
|
2122
2203
|
// src/governance/stream-response-governance.ts
|
|
2123
2204
|
function serializeEvent(event2) {
|
|
2124
|
-
let
|
|
2205
|
+
let output3 = "";
|
|
2125
2206
|
if (event2.event) {
|
|
2126
|
-
|
|
2207
|
+
output3 += `event: ${event2.event}
|
|
2127
2208
|
`;
|
|
2128
2209
|
}
|
|
2129
2210
|
if (event2.data !== void 0) {
|
|
2130
|
-
|
|
2211
|
+
output3 += `data: ${typeof event2.data === "string" ? event2.data : JSON.stringify(event2.data)}
|
|
2131
2212
|
`;
|
|
2132
2213
|
}
|
|
2133
|
-
|
|
2134
|
-
return new TextEncoder().encode(
|
|
2214
|
+
output3 += "\n";
|
|
2215
|
+
return new TextEncoder().encode(output3);
|
|
2135
2216
|
}
|
|
2136
2217
|
async function collectSSE(stream) {
|
|
2137
2218
|
const parser = new SSEParserTransform();
|
|
@@ -2241,17 +2322,17 @@ var init_stream_response_governance = __esm({
|
|
|
2241
2322
|
});
|
|
2242
2323
|
|
|
2243
2324
|
// src/governance/metrics.ts
|
|
2244
|
-
function normalizeAnomalyThresholds(
|
|
2325
|
+
function normalizeAnomalyThresholds(input3) {
|
|
2245
2326
|
return {
|
|
2246
|
-
minSampleSize:
|
|
2247
|
-
cascadeWarnRate:
|
|
2248
|
-
cascadeCriticalRate:
|
|
2249
|
-
shadowWarnRate:
|
|
2250
|
-
shadowCriticalRate:
|
|
2251
|
-
latencyWarnMs:
|
|
2252
|
-
latencyCriticalMs:
|
|
2253
|
-
spikeWarnRate:
|
|
2254
|
-
spikeDeltaRate:
|
|
2327
|
+
minSampleSize: input3?.minSampleSize ?? DEFAULT_ANOMALY_THRESHOLDS.minSampleSize,
|
|
2328
|
+
cascadeWarnRate: input3?.cascadeWarnRate ?? DEFAULT_ANOMALY_THRESHOLDS.cascadeWarnRate,
|
|
2329
|
+
cascadeCriticalRate: input3?.cascadeCriticalRate ?? DEFAULT_ANOMALY_THRESHOLDS.cascadeCriticalRate,
|
|
2330
|
+
shadowWarnRate: input3?.shadowWarnRate ?? DEFAULT_ANOMALY_THRESHOLDS.shadowWarnRate,
|
|
2331
|
+
shadowCriticalRate: input3?.shadowCriticalRate ?? DEFAULT_ANOMALY_THRESHOLDS.shadowCriticalRate,
|
|
2332
|
+
latencyWarnMs: input3?.latencyWarnMs ?? DEFAULT_ANOMALY_THRESHOLDS.latencyWarnMs,
|
|
2333
|
+
latencyCriticalMs: input3?.latencyCriticalMs ?? DEFAULT_ANOMALY_THRESHOLDS.latencyCriticalMs,
|
|
2334
|
+
spikeWarnRate: input3?.spikeWarnRate ?? DEFAULT_ANOMALY_THRESHOLDS.spikeWarnRate,
|
|
2335
|
+
spikeDeltaRate: input3?.spikeDeltaRate ?? DEFAULT_ANOMALY_THRESHOLDS.spikeDeltaRate
|
|
2255
2336
|
};
|
|
2256
2337
|
}
|
|
2257
2338
|
function rate(count, total) {
|
|
@@ -3262,7 +3343,7 @@ var init_server = __esm({
|
|
|
3262
3343
|
server.app.post("/api/restart", async (req, reply) => {
|
|
3263
3344
|
reply.send({ success: true, message: "Service restart initiated" });
|
|
3264
3345
|
setTimeout(() => {
|
|
3265
|
-
const { spawn:
|
|
3346
|
+
const { spawn: spawn3 } = require("child_process");
|
|
3266
3347
|
const { join: join8 } = require("path");
|
|
3267
3348
|
const cliPath = join8(__dirname, "cli.js");
|
|
3268
3349
|
const currentPort = config.initialConfig?.PORT;
|
|
@@ -3270,7 +3351,7 @@ var init_server = __esm({
|
|
|
3270
3351
|
if (currentPort) {
|
|
3271
3352
|
restartArgs.push("--port", String(currentPort));
|
|
3272
3353
|
}
|
|
3273
|
-
|
|
3354
|
+
spawn3(process.execPath, restartArgs, {
|
|
3274
3355
|
detached: true,
|
|
3275
3356
|
stdio: "ignore"
|
|
3276
3357
|
}).unref();
|
|
@@ -3576,18 +3657,18 @@ var init_SSESerializer_transform = __esm({
|
|
|
3576
3657
|
constructor() {
|
|
3577
3658
|
const transformStream = new TransformStream({
|
|
3578
3659
|
transform: (event2, controller) => {
|
|
3579
|
-
let
|
|
3660
|
+
let output3 = "";
|
|
3580
3661
|
if (event2.event) {
|
|
3581
|
-
|
|
3662
|
+
output3 += `event: ${event2.event}
|
|
3582
3663
|
`;
|
|
3583
3664
|
}
|
|
3584
3665
|
if (event2.data) {
|
|
3585
3666
|
const dataStr = typeof event2.data === "string" ? event2.data : JSON.stringify(event2.data);
|
|
3586
|
-
|
|
3667
|
+
output3 += `data: ${dataStr}
|
|
3587
3668
|
`;
|
|
3588
3669
|
}
|
|
3589
|
-
|
|
3590
|
-
controller.enqueue(new TextEncoder().encode(
|
|
3670
|
+
output3 += "\n";
|
|
3671
|
+
controller.enqueue(new TextEncoder().encode(output3));
|
|
3591
3672
|
}
|
|
3592
3673
|
});
|
|
3593
3674
|
this.readable = transformStream.readable;
|
|
@@ -4377,6 +4458,7 @@ Important:
|
|
|
4377
4458
|
method: "POST",
|
|
4378
4459
|
headers: {
|
|
4379
4460
|
"Content-Type": "application/json",
|
|
4461
|
+
"x-ctr-smart-router": "1",
|
|
4380
4462
|
...apiKey ? { "x-api-key": apiKey } : {}
|
|
4381
4463
|
},
|
|
4382
4464
|
body: JSON.stringify(
|
|
@@ -4889,6 +4971,15 @@ function toOpenAIToolResultMessages(parts) {
|
|
|
4889
4971
|
content: typeof part.content === "string" ? part.content : JSON.stringify(part.content)
|
|
4890
4972
|
}));
|
|
4891
4973
|
}
|
|
4974
|
+
function getToolName(tool) {
|
|
4975
|
+
return tool?.name ?? tool?.function?.name;
|
|
4976
|
+
}
|
|
4977
|
+
function getToolDescription(tool) {
|
|
4978
|
+
return tool?.description ?? tool?.function?.description;
|
|
4979
|
+
}
|
|
4980
|
+
function getToolInputSchema(tool) {
|
|
4981
|
+
return tool?.input_schema ?? tool?.function?.parameters;
|
|
4982
|
+
}
|
|
4892
4983
|
function toOpenAITools(tools) {
|
|
4893
4984
|
if (!Array.isArray(tools) || !tools.length) {
|
|
4894
4985
|
return void 0;
|
|
@@ -4896,9 +4987,9 @@ function toOpenAITools(tools) {
|
|
|
4896
4987
|
return tools.map((tool) => ({
|
|
4897
4988
|
type: "function",
|
|
4898
4989
|
function: {
|
|
4899
|
-
name: tool
|
|
4900
|
-
description: tool
|
|
4901
|
-
parameters: tool
|
|
4990
|
+
name: getToolName(tool),
|
|
4991
|
+
description: getToolDescription(tool),
|
|
4992
|
+
parameters: getToolInputSchema(tool)
|
|
4902
4993
|
}
|
|
4903
4994
|
}));
|
|
4904
4995
|
}
|
|
@@ -4925,10 +5016,10 @@ function toOpenAIToolChoice(toolChoice) {
|
|
|
4925
5016
|
}
|
|
4926
5017
|
return toolChoice;
|
|
4927
5018
|
}
|
|
4928
|
-
function toOpenAIChatRequest(
|
|
5019
|
+
function toOpenAIChatRequest(input3) {
|
|
4929
5020
|
const messages = [
|
|
4930
|
-
...
|
|
4931
|
-
...
|
|
5021
|
+
...input3.ir.system.map((text) => ({ role: "system", content: text })),
|
|
5022
|
+
...input3.ir.messages.flatMap((message) => {
|
|
4932
5023
|
const content = toOpenAIContent(message.parts);
|
|
4933
5024
|
const toolCalls = message.role === "assistant" ? toOpenAIToolCalls(message.parts) : [];
|
|
4934
5025
|
const toolResults = toOpenAIToolResultMessages(message.parts);
|
|
@@ -4947,26 +5038,26 @@ function toOpenAIChatRequest(input2) {
|
|
|
4947
5038
|
})
|
|
4948
5039
|
];
|
|
4949
5040
|
const body = {
|
|
4950
|
-
model:
|
|
5041
|
+
model: input3.model,
|
|
4951
5042
|
messages
|
|
4952
5043
|
};
|
|
4953
|
-
if (
|
|
4954
|
-
body.max_completion_tokens =
|
|
5044
|
+
if (input3.max_completion_tokens !== void 0) {
|
|
5045
|
+
body.max_completion_tokens = input3.max_completion_tokens;
|
|
4955
5046
|
}
|
|
4956
|
-
if (
|
|
4957
|
-
body.stream =
|
|
5047
|
+
if (input3.stream !== void 0) {
|
|
5048
|
+
body.stream = input3.stream;
|
|
4958
5049
|
}
|
|
4959
|
-
const tools = toOpenAITools(
|
|
5050
|
+
const tools = toOpenAITools(input3.tools);
|
|
4960
5051
|
if (tools) {
|
|
4961
5052
|
body.tools = tools;
|
|
4962
5053
|
}
|
|
4963
|
-
const toolChoice = toOpenAIToolChoice(
|
|
5054
|
+
const toolChoice = toOpenAIToolChoice(input3.tool_choice);
|
|
4964
5055
|
if (toolChoice !== void 0) {
|
|
4965
5056
|
body.tool_choice = toolChoice;
|
|
4966
5057
|
}
|
|
4967
|
-
if (
|
|
5058
|
+
if (input3.ir.options?.thinking?.enabled) {
|
|
4968
5059
|
body.reasoning = {
|
|
4969
|
-
...
|
|
5060
|
+
...input3.ir.options.thinking.effort ? { effort: input3.ir.options.thinking.effort } : {}
|
|
4970
5061
|
};
|
|
4971
5062
|
}
|
|
4972
5063
|
return body;
|
|
@@ -4991,19 +5082,19 @@ function stringifyFallbackContent(value) {
|
|
|
4991
5082
|
return String(value);
|
|
4992
5083
|
}
|
|
4993
5084
|
}
|
|
4994
|
-
function applyCapabilityFallbacks(
|
|
5085
|
+
function applyCapabilityFallbacks(input3) {
|
|
4995
5086
|
const diagnostics = [];
|
|
4996
|
-
const nextRequest = { ...
|
|
5087
|
+
const nextRequest = { ...input3.request };
|
|
4997
5088
|
const nextIR = {
|
|
4998
|
-
...
|
|
4999
|
-
system: [...
|
|
5000
|
-
messages:
|
|
5089
|
+
...input3.ir,
|
|
5090
|
+
system: [...input3.ir.system],
|
|
5091
|
+
messages: input3.ir.messages.map((message) => ({
|
|
5001
5092
|
...message,
|
|
5002
5093
|
parts: message.parts.map((part) => ({ ...part }))
|
|
5003
5094
|
})),
|
|
5004
|
-
options:
|
|
5095
|
+
options: input3.ir.options ? { ...input3.ir.options } : void 0
|
|
5005
5096
|
};
|
|
5006
|
-
if (
|
|
5097
|
+
if (input3.capabilities?.thinking.supported === false && nextIR.options?.thinking) {
|
|
5007
5098
|
diagnostics.push("thinking_ignored");
|
|
5008
5099
|
delete nextIR.options.thinking;
|
|
5009
5100
|
delete nextRequest.thinking;
|
|
@@ -5014,7 +5105,7 @@ function applyCapabilityFallbacks(input2) {
|
|
|
5014
5105
|
const hasImageParts = nextIR.messages.some(
|
|
5015
5106
|
(message) => message.parts.some((part) => part.type === "image")
|
|
5016
5107
|
);
|
|
5017
|
-
if (
|
|
5108
|
+
if (input3.capabilities?.images === false && hasImageParts) {
|
|
5018
5109
|
diagnostics.push("images_text_fallback");
|
|
5019
5110
|
nextIR.messages = nextIR.messages.map((message) => ({
|
|
5020
5111
|
...message,
|
|
@@ -5034,7 +5125,7 @@ function applyCapabilityFallbacks(input2) {
|
|
|
5034
5125
|
const hasToolParts = nextIR.messages.some(
|
|
5035
5126
|
(message) => message.parts.some((part) => part.type === "tool_call" || part.type === "tool_result")
|
|
5036
5127
|
);
|
|
5037
|
-
if (
|
|
5128
|
+
if (input3.capabilities?.tools === false && (Array.isArray(nextRequest.tools) && nextRequest.tools.length || hasToolParts)) {
|
|
5038
5129
|
diagnostics.push("tools_text_fallback");
|
|
5039
5130
|
delete nextRequest.tools;
|
|
5040
5131
|
delete nextRequest.tool_choice;
|
|
@@ -5080,53 +5171,58 @@ function omitRequestFields(body) {
|
|
|
5080
5171
|
} = body;
|
|
5081
5172
|
return rest;
|
|
5082
5173
|
}
|
|
5083
|
-
function
|
|
5174
|
+
function buildProviderDispatchRequestFromIR(input3) {
|
|
5084
5175
|
const fallback = applyCapabilityFallbacks({
|
|
5085
|
-
ir:
|
|
5086
|
-
request:
|
|
5087
|
-
capabilities:
|
|
5176
|
+
ir: input3.ir,
|
|
5177
|
+
request: input3.request,
|
|
5178
|
+
capabilities: input3.capabilities
|
|
5088
5179
|
});
|
|
5089
5180
|
const passthrough = omitRequestFields(fallback.request);
|
|
5090
|
-
|
|
5181
|
+
const dispatchFormat = getDispatchFormatForProfile(input3.interface, input3.compatibilityProfile);
|
|
5182
|
+
if (dispatchFormat === "openai_chat") {
|
|
5091
5183
|
return {
|
|
5184
|
+
dispatchFormat,
|
|
5092
5185
|
diagnostics: fallback.diagnostics,
|
|
5093
5186
|
...passthrough,
|
|
5094
|
-
...
|
|
5095
|
-
model:
|
|
5096
|
-
|
|
5187
|
+
...toOpenAIChatRequest({
|
|
5188
|
+
model: input3.model,
|
|
5189
|
+
max_completion_tokens: fallback.request.max_tokens ?? fallback.request.max_completion_tokens,
|
|
5097
5190
|
stream: fallback.request.stream,
|
|
5098
|
-
metadata: fallback.request.metadata,
|
|
5099
5191
|
tools: fallback.request.tools,
|
|
5192
|
+
tool_choice: fallback.request.tool_choice,
|
|
5100
5193
|
ir: fallback.ir
|
|
5101
5194
|
})
|
|
5102
5195
|
};
|
|
5103
5196
|
}
|
|
5104
5197
|
return {
|
|
5198
|
+
dispatchFormat,
|
|
5105
5199
|
diagnostics: fallback.diagnostics,
|
|
5106
5200
|
...passthrough,
|
|
5107
|
-
...
|
|
5108
|
-
model:
|
|
5109
|
-
|
|
5201
|
+
...toAnthropicMessagesRequest({
|
|
5202
|
+
model: input3.model,
|
|
5203
|
+
max_tokens: fallback.request.max_tokens,
|
|
5110
5204
|
stream: fallback.request.stream,
|
|
5205
|
+
metadata: fallback.request.metadata,
|
|
5111
5206
|
tools: fallback.request.tools,
|
|
5112
|
-
tool_choice: fallback.request.tool_choice,
|
|
5113
5207
|
ir: fallback.ir
|
|
5114
5208
|
})
|
|
5115
5209
|
};
|
|
5116
5210
|
}
|
|
5117
|
-
function
|
|
5118
|
-
const ir = createMessageIR(
|
|
5119
|
-
const { diagnostics, ...body } =
|
|
5120
|
-
model:
|
|
5121
|
-
interface:
|
|
5122
|
-
|
|
5211
|
+
function buildProviderDispatchRequest(input3) {
|
|
5212
|
+
const ir = createMessageIR(input3.request);
|
|
5213
|
+
const { diagnostics, dispatchFormat, ...body } = buildProviderDispatchRequestFromIR({
|
|
5214
|
+
model: input3.model,
|
|
5215
|
+
interface: input3.interface,
|
|
5216
|
+
compatibilityProfile: input3.compatibilityProfile,
|
|
5217
|
+
request: input3.request,
|
|
5123
5218
|
ir,
|
|
5124
|
-
capabilities:
|
|
5219
|
+
capabilities: input3.capabilities
|
|
5125
5220
|
});
|
|
5126
5221
|
return {
|
|
5127
5222
|
ir,
|
|
5128
5223
|
body,
|
|
5129
|
-
diagnostics
|
|
5224
|
+
diagnostics,
|
|
5225
|
+
dispatchFormat
|
|
5130
5226
|
};
|
|
5131
5227
|
}
|
|
5132
5228
|
var init_protocols = __esm({
|
|
@@ -5135,6 +5231,7 @@ var init_protocols = __esm({
|
|
|
5135
5231
|
init_message_ir();
|
|
5136
5232
|
init_anthropic();
|
|
5137
5233
|
init_openai();
|
|
5234
|
+
init_compile();
|
|
5138
5235
|
}
|
|
5139
5236
|
});
|
|
5140
5237
|
|
|
@@ -5202,7 +5299,7 @@ async function run(options = {}) {
|
|
|
5202
5299
|
const hour = pad(date.getHours());
|
|
5203
5300
|
const minute = pad(date.getMinutes());
|
|
5204
5301
|
const seconds = pad(date.getSeconds());
|
|
5205
|
-
return
|
|
5302
|
+
return `ctr-${month}${day}${hour}${minute}${seconds}${index ? `_${index}` : ""}.log`;
|
|
5206
5303
|
};
|
|
5207
5304
|
const loggerConfig = config.LOG !== false ? {
|
|
5208
5305
|
level: config.LOG_LEVEL || "debug",
|
|
@@ -5213,10 +5310,11 @@ async function run(options = {}) {
|
|
|
5213
5310
|
compress: "gzip"
|
|
5214
5311
|
})
|
|
5215
5312
|
} : false;
|
|
5313
|
+
const registry = buildModelRegistry(config);
|
|
5216
5314
|
const server = createServer({
|
|
5217
|
-
|
|
5315
|
+
useJsonFile: false,
|
|
5218
5316
|
initialConfig: {
|
|
5219
|
-
providers:
|
|
5317
|
+
providers: registry.providers,
|
|
5220
5318
|
HOST,
|
|
5221
5319
|
PORT: servicePort,
|
|
5222
5320
|
LOG_FILE: (0, import_path5.join)(
|
|
@@ -5252,9 +5350,10 @@ async function run(options = {}) {
|
|
|
5252
5350
|
initialModel: req.body?.model
|
|
5253
5351
|
});
|
|
5254
5352
|
appendTraceReason(req.governanceTrace, "request_received");
|
|
5255
|
-
const
|
|
5353
|
+
const bypassTriggerRouter = req.headers["x-ctr-smart-router"] === "1";
|
|
5354
|
+
const triggerResult = bypassTriggerRouter ? { matched: false, confidence: 0, analysisTime: 0 } : await triggerRouter.route(req);
|
|
5256
5355
|
req.triggerResult = triggerResult;
|
|
5257
|
-
if (triggerResult.matched && triggerResult.model) {
|
|
5356
|
+
if (!bypassTriggerRouter && triggerResult.matched && triggerResult.model) {
|
|
5258
5357
|
const previousSessionState = req.sessionId ? sessionStateStore.get(req.sessionId) : void 0;
|
|
5259
5358
|
const previousModel = previousSessionState?.lastSuccessfulModel;
|
|
5260
5359
|
const alignmentConfig = config.Governance?.sticky?.alignment;
|
|
@@ -5319,9 +5418,10 @@ async function run(options = {}) {
|
|
|
5319
5418
|
const compiledModel = getCompiledModelRef(config, req.body?.model);
|
|
5320
5419
|
if (compiledModel?.interface && req.body?.messages) {
|
|
5321
5420
|
const originalBody = cloneRequestBody(req.body);
|
|
5322
|
-
const upstream =
|
|
5421
|
+
const upstream = buildProviderDispatchRequest({
|
|
5323
5422
|
model: compiledModel.modelName,
|
|
5324
5423
|
interface: compiledModel.interface,
|
|
5424
|
+
compatibilityProfile: compiledModel.compatibilityProfile,
|
|
5325
5425
|
request: originalBody,
|
|
5326
5426
|
capabilities: compiledModel.capabilities
|
|
5327
5427
|
});
|
|
@@ -5508,7 +5608,7 @@ async function run(options = {}) {
|
|
|
5508
5608
|
event.emit("onSend", req, reply, payload);
|
|
5509
5609
|
return payload;
|
|
5510
5610
|
});
|
|
5511
|
-
server.start();
|
|
5611
|
+
await server.start();
|
|
5512
5612
|
}
|
|
5513
5613
|
var import_fs5, import_promises2, import_os2, import_path5, import_json5, import_node_events, import_rotating_file_stream, event;
|
|
5514
5614
|
var init_index = __esm({
|
|
@@ -5542,32 +5642,32 @@ var init_index = __esm({
|
|
|
5542
5642
|
});
|
|
5543
5643
|
|
|
5544
5644
|
// src/setup/service.ts
|
|
5545
|
-
function decideServiceAction(
|
|
5546
|
-
if (
|
|
5645
|
+
function decideServiceAction(input3) {
|
|
5646
|
+
if (input3.detectedService.kind === "non_self_occupied") {
|
|
5547
5647
|
throw new Error("target port is occupied by another service");
|
|
5548
5648
|
}
|
|
5549
|
-
if (
|
|
5649
|
+
if (input3.detectedService.kind === "none") {
|
|
5550
5650
|
return { kind: "start" };
|
|
5551
5651
|
}
|
|
5552
|
-
if (
|
|
5652
|
+
if (input3.detectedService.kind === "self_unhealthy") {
|
|
5553
5653
|
return { kind: "restart" };
|
|
5554
5654
|
}
|
|
5555
|
-
if (
|
|
5556
|
-
return
|
|
5655
|
+
if (input3.configChanged && input3.detectedService.kind === "self_healthy") {
|
|
5656
|
+
return input3.reloadSupported ? { kind: "reload" } : { kind: "restart" };
|
|
5557
5657
|
}
|
|
5558
5658
|
return { kind: "reuse" };
|
|
5559
5659
|
}
|
|
5560
|
-
async function applyServiceAction(
|
|
5561
|
-
if (
|
|
5562
|
-
await
|
|
5660
|
+
async function applyServiceAction(input3) {
|
|
5661
|
+
if (input3.action.kind === "start") {
|
|
5662
|
+
await input3.executeStart();
|
|
5563
5663
|
}
|
|
5564
|
-
if (
|
|
5565
|
-
await
|
|
5664
|
+
if (input3.action.kind === "reload") {
|
|
5665
|
+
await input3.executeReload();
|
|
5566
5666
|
}
|
|
5567
|
-
if (
|
|
5568
|
-
await
|
|
5667
|
+
if (input3.action.kind === "restart") {
|
|
5668
|
+
await input3.executeRestart();
|
|
5569
5669
|
}
|
|
5570
|
-
const healthy = await
|
|
5670
|
+
const healthy = await input3.verifyHealth();
|
|
5571
5671
|
if (!healthy) {
|
|
5572
5672
|
throw new Error("service health check failed");
|
|
5573
5673
|
}
|
|
@@ -5617,9 +5717,12 @@ function inferProtocolFromApiBaseUrl(apiBaseUrl) {
|
|
|
5617
5717
|
}
|
|
5618
5718
|
return "openai";
|
|
5619
5719
|
}
|
|
5720
|
+
function normalizeSegment(value) {
|
|
5721
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
5722
|
+
}
|
|
5620
5723
|
function toModelId(name, model, index) {
|
|
5621
|
-
const normalizedName = name
|
|
5622
|
-
const normalizedModel = model
|
|
5724
|
+
const normalizedName = normalizeSegment(name) || `provider_${index + 1}`;
|
|
5725
|
+
const normalizedModel = normalizeSegment(model);
|
|
5623
5726
|
return normalizedModel ? `${normalizedName}_${normalizedModel}` : normalizedName;
|
|
5624
5727
|
}
|
|
5625
5728
|
function createEmptyDraft() {
|
|
@@ -5634,66 +5737,176 @@ function createNonMigratableResult() {
|
|
|
5634
5737
|
draft: createEmptyDraft(),
|
|
5635
5738
|
skippedFields: [],
|
|
5636
5739
|
needsCompletion: true,
|
|
5637
|
-
missingFields: ["defaultModel", "apiKey"]
|
|
5740
|
+
missingFields: ["defaultModel", "apiKey", "apiBaseUrl"]
|
|
5638
5741
|
};
|
|
5639
5742
|
}
|
|
5743
|
+
function inferVendorHintFromLegacyProvider(provider) {
|
|
5744
|
+
const transformerUse = Array.isArray(provider.transformer?.use) ? provider.transformer.use.map((item) => String(item).trim().toLowerCase()) : [];
|
|
5745
|
+
const normalizedName = (provider.name ?? "").trim().toLowerCase();
|
|
5746
|
+
if (transformerUse.includes("openrouter")) {
|
|
5747
|
+
return "openrouter";
|
|
5748
|
+
}
|
|
5749
|
+
if (normalizedName.includes("qianfan")) {
|
|
5750
|
+
return "qianfan-coding";
|
|
5751
|
+
}
|
|
5752
|
+
if (normalizedName.includes("minimax")) {
|
|
5753
|
+
return "minimax-chatcompletion-v2";
|
|
5754
|
+
}
|
|
5755
|
+
return void 0;
|
|
5756
|
+
}
|
|
5640
5757
|
function isLegacyProviderInput(value) {
|
|
5641
5758
|
return typeof value === "object" && value !== null;
|
|
5642
5759
|
}
|
|
5643
|
-
function
|
|
5644
|
-
|
|
5645
|
-
|
|
5760
|
+
function isLegacyRouterInput(value) {
|
|
5761
|
+
return typeof value === "object" && value !== null;
|
|
5762
|
+
}
|
|
5763
|
+
function pushUnique(target, value) {
|
|
5764
|
+
if (!target.includes(value)) {
|
|
5765
|
+
target.push(value);
|
|
5646
5766
|
}
|
|
5647
|
-
|
|
5648
|
-
|
|
5767
|
+
}
|
|
5768
|
+
function normalizeLegacyConfig(input3) {
|
|
5769
|
+
const lowerProviders = Array.isArray(input3.providers) && input3.providers.every(isLegacyProviderInput) ? input3.providers : null;
|
|
5770
|
+
const upperProviders = Array.isArray(input3.Providers) && input3.Providers.every(isLegacyProviderInput) ? input3.Providers : null;
|
|
5771
|
+
const providerKey = lowerProviders && lowerProviders.length > 0 ? "providers" : upperProviders && upperProviders.length > 0 ? "Providers" : lowerProviders ? "providers" : upperProviders ? "Providers" : null;
|
|
5772
|
+
if (!providerKey) {
|
|
5773
|
+
return null;
|
|
5774
|
+
}
|
|
5775
|
+
const rawProviders = providerKey === "providers" ? lowerProviders : upperProviders;
|
|
5776
|
+
if (!rawProviders) {
|
|
5777
|
+
return null;
|
|
5649
5778
|
}
|
|
5650
5779
|
const skippedFields = [];
|
|
5651
|
-
const
|
|
5780
|
+
const alternateProviderKey = providerKey === "providers" ? "Providers" : "providers";
|
|
5781
|
+
const alternateDefaultKey = providerKey === "providers" ? "Router" : "default";
|
|
5782
|
+
const alternateDefaultValue = providerKey === "providers" ? input3.Router : input3.default;
|
|
5783
|
+
const alternateProviders = providerKey === "providers" ? upperProviders : lowerProviders;
|
|
5784
|
+
if (alternateProviders !== null) {
|
|
5785
|
+
pushUnique(skippedFields, alternateProviderKey);
|
|
5786
|
+
}
|
|
5787
|
+
if (alternateDefaultValue !== void 0) {
|
|
5788
|
+
pushUnique(skippedFields, alternateDefaultKey);
|
|
5789
|
+
}
|
|
5790
|
+
const consumedTopLevelFields = /* @__PURE__ */ new Set([providerKey]);
|
|
5791
|
+
const providers = rawProviders.map((provider, index) => {
|
|
5652
5792
|
if (provider.transformer !== void 0) {
|
|
5653
|
-
skippedFields
|
|
5793
|
+
pushUnique(skippedFields, `${providerKey}[${index}].transformer`);
|
|
5794
|
+
}
|
|
5795
|
+
if (provider.headers !== void 0) {
|
|
5796
|
+
pushUnique(skippedFields, `${providerKey}[${index}].headers`);
|
|
5654
5797
|
}
|
|
5655
5798
|
return {
|
|
5656
5799
|
name: provider.name ?? "",
|
|
5657
5800
|
api_base_url: provider.api_base_url,
|
|
5658
5801
|
api_key: provider.api_key ?? "",
|
|
5659
|
-
models: Array.isArray(provider.models) ? provider.models : []
|
|
5802
|
+
models: Array.isArray(provider.models) ? provider.models : [],
|
|
5803
|
+
vendor_hint: inferVendorHintFromLegacyProvider(provider)
|
|
5660
5804
|
};
|
|
5661
5805
|
});
|
|
5662
|
-
|
|
5806
|
+
let defaultRoute;
|
|
5807
|
+
if (providerKey === "providers") {
|
|
5808
|
+
consumedTopLevelFields.add("default");
|
|
5809
|
+
defaultRoute = typeof input3.default === "string" ? input3.default : void 0;
|
|
5810
|
+
} else {
|
|
5811
|
+
consumedTopLevelFields.add("Router");
|
|
5812
|
+
if (isLegacyRouterInput(input3.Router)) {
|
|
5813
|
+
defaultRoute = typeof input3.Router.default === "string" ? input3.Router.default : void 0;
|
|
5814
|
+
if (input3.Router.background !== void 0) {
|
|
5815
|
+
pushUnique(skippedFields, "Router.background");
|
|
5816
|
+
}
|
|
5817
|
+
if (input3.Router.think !== void 0) {
|
|
5818
|
+
pushUnique(skippedFields, "Router.think");
|
|
5819
|
+
}
|
|
5820
|
+
if (input3.Router.longContext !== void 0) {
|
|
5821
|
+
pushUnique(skippedFields, "Router.longContext");
|
|
5822
|
+
}
|
|
5823
|
+
if (input3.Router.longContextThreshold !== void 0) {
|
|
5824
|
+
pushUnique(skippedFields, "Router.longContextThreshold");
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
}
|
|
5828
|
+
for (const key of Object.keys(input3)) {
|
|
5829
|
+
if (consumedTopLevelFields.has(key)) {
|
|
5830
|
+
continue;
|
|
5831
|
+
}
|
|
5832
|
+
pushUnique(skippedFields, key);
|
|
5833
|
+
}
|
|
5834
|
+
return {
|
|
5835
|
+
providers,
|
|
5836
|
+
defaultRoute,
|
|
5837
|
+
skippedFields
|
|
5838
|
+
};
|
|
5839
|
+
}
|
|
5840
|
+
function migrateLegacyConfig(input3) {
|
|
5841
|
+
const normalized = normalizeLegacyConfig(input3);
|
|
5842
|
+
if (!normalized) {
|
|
5843
|
+
return createNonMigratableResult();
|
|
5844
|
+
}
|
|
5845
|
+
const rawEntries = normalized.providers.flatMap(
|
|
5663
5846
|
(provider, providerIndex) => (provider.models.length ? provider.models : [""]).map((model) => ({
|
|
5664
|
-
|
|
5847
|
+
candidateId: toModelId(provider.name, model, providerIndex),
|
|
5665
5848
|
api: provider.api_base_url,
|
|
5666
5849
|
api_base_url: provider.api_base_url,
|
|
5667
5850
|
key: provider.api_key,
|
|
5668
5851
|
api_key: provider.api_key,
|
|
5669
5852
|
interface: inferProtocolFromApiBaseUrl(provider.api_base_url),
|
|
5670
5853
|
protocol: inferProtocolFromApiBaseUrl(provider.api_base_url),
|
|
5671
|
-
model
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
const
|
|
5678
|
-
const
|
|
5679
|
-
const
|
|
5680
|
-
|
|
5854
|
+
model,
|
|
5855
|
+
providerName: provider.name,
|
|
5856
|
+
vendorHint: provider.vendor_hint
|
|
5857
|
+
})).filter((item) => item.model)
|
|
5858
|
+
);
|
|
5859
|
+
const seenIds = /* @__PURE__ */ new Map();
|
|
5860
|
+
const routeLookup = /* @__PURE__ */ new Map();
|
|
5861
|
+
const models = rawEntries.map((entry) => {
|
|
5862
|
+
const count = seenIds.get(entry.candidateId) ?? 0;
|
|
5863
|
+
seenIds.set(entry.candidateId, count + 1);
|
|
5864
|
+
const finalId = count === 0 ? entry.candidateId : `${entry.candidateId}_${count + 1}`;
|
|
5865
|
+
routeLookup.set(`${entry.providerName.trim()},${entry.model}`, finalId);
|
|
5866
|
+
return {
|
|
5867
|
+
id: finalId,
|
|
5868
|
+
api: entry.api,
|
|
5869
|
+
api_base_url: entry.api_base_url,
|
|
5870
|
+
key: entry.key,
|
|
5871
|
+
api_key: entry.api_key,
|
|
5872
|
+
interface: entry.interface,
|
|
5873
|
+
protocol: entry.protocol,
|
|
5874
|
+
model: entry.model,
|
|
5875
|
+
metadata: entry.vendorHint ? {
|
|
5876
|
+
vendor_hint: entry.vendorHint
|
|
5877
|
+
} : void 0
|
|
5878
|
+
};
|
|
5879
|
+
});
|
|
5880
|
+
const hasLegacyDefaultRoute = typeof normalized.defaultRoute === "string" && normalized.defaultRoute.length > 0;
|
|
5881
|
+
const defaultModelId = hasLegacyDefaultRoute ? (() => {
|
|
5882
|
+
const [rawProviderName, rawModelName] = String(normalized.defaultRoute).split(",");
|
|
5883
|
+
const providerName = (rawProviderName ?? "").trim();
|
|
5884
|
+
const modelName = (rawModelName ?? "").trim();
|
|
5885
|
+
const fromLookup = routeLookup.get(`${providerName},${modelName}`);
|
|
5886
|
+
if (fromLookup) return fromLookup;
|
|
5887
|
+
return models.find(
|
|
5888
|
+
(item) => item.id === toModelId(providerName, modelName, 0) || item.id.startsWith(`${normalizeSegment(providerName)}_`) && item.model === modelName
|
|
5889
|
+
)?.id;
|
|
5681
5890
|
})() : void 0;
|
|
5682
|
-
const hasMissingApiKey = providers.some((provider) => provider.api_key.length === 0);
|
|
5891
|
+
const hasMissingApiKey = normalized.providers.some((provider) => provider.api_key.length === 0);
|
|
5892
|
+
const hasMissingApiBaseUrl = normalized.providers.some((provider) => (provider.api_base_url?.trim() ?? "").length === 0);
|
|
5683
5893
|
const missingFields = [];
|
|
5684
|
-
if (!
|
|
5894
|
+
if (!defaultModelId) {
|
|
5685
5895
|
missingFields.push("defaultModel");
|
|
5686
5896
|
}
|
|
5687
5897
|
if (hasMissingApiKey) {
|
|
5688
5898
|
missingFields.push("apiKey");
|
|
5689
5899
|
}
|
|
5900
|
+
if (hasMissingApiBaseUrl) {
|
|
5901
|
+
missingFields.push("apiBaseUrl");
|
|
5902
|
+
}
|
|
5690
5903
|
return {
|
|
5691
5904
|
draft: {
|
|
5692
5905
|
Providers: [],
|
|
5693
5906
|
Models: models,
|
|
5694
|
-
Router:
|
|
5907
|
+
Router: defaultModelId ? { default: defaultModelId } : {}
|
|
5695
5908
|
},
|
|
5696
|
-
skippedFields,
|
|
5909
|
+
skippedFields: normalized.skippedFields,
|
|
5697
5910
|
needsCompletion: missingFields.length > 0,
|
|
5698
5911
|
missingFields
|
|
5699
5912
|
};
|
|
@@ -5758,11 +5971,12 @@ function getProviderPreset2(key) {
|
|
|
5758
5971
|
api: preset.api,
|
|
5759
5972
|
api_base_url: preset.api_base_url,
|
|
5760
5973
|
interface: preset.interface,
|
|
5761
|
-
protocol: preset.protocol
|
|
5974
|
+
protocol: preset.protocol,
|
|
5975
|
+
default_thinking: preset.default_thinking
|
|
5762
5976
|
};
|
|
5763
5977
|
}
|
|
5764
|
-
function buildMinimalConfig(
|
|
5765
|
-
const providers =
|
|
5978
|
+
function buildMinimalConfig(input3) {
|
|
5979
|
+
const providers = input3.providers;
|
|
5766
5980
|
const models = providers.map((p) => {
|
|
5767
5981
|
const preset = p.preset ? getProviderPreset2(p.preset) : void 0;
|
|
5768
5982
|
const modelDraft = {
|
|
@@ -5782,15 +5996,15 @@ function buildMinimalConfig(input2) {
|
|
|
5782
5996
|
}
|
|
5783
5997
|
return modelDraft;
|
|
5784
5998
|
});
|
|
5785
|
-
let defaultModel =
|
|
5786
|
-
if (
|
|
5787
|
-
const [providerName, modelName] =
|
|
5999
|
+
let defaultModel = input3.defaultModel?.trim();
|
|
6000
|
+
if (input3.defaultModel && input3.defaultModel.includes(",")) {
|
|
6001
|
+
const [providerName, modelName] = input3.defaultModel.split(",");
|
|
5788
6002
|
const matched = models.find((item) => item.id === providerName && item.model === modelName);
|
|
5789
6003
|
if (matched) {
|
|
5790
6004
|
defaultModel = matched.id;
|
|
5791
6005
|
}
|
|
5792
6006
|
}
|
|
5793
|
-
if (
|
|
6007
|
+
if (input3.defaultModel === void 0 && models.length > 0) {
|
|
5794
6008
|
const firstModelId = models[0].id;
|
|
5795
6009
|
if (firstModelId && models[0].model) {
|
|
5796
6010
|
defaultModel = firstModelId;
|
|
@@ -5804,31 +6018,68 @@ function buildMinimalConfig(input2) {
|
|
|
5804
6018
|
Router: defaultModel ? { default: defaultModel } : {}
|
|
5805
6019
|
};
|
|
5806
6020
|
}
|
|
6021
|
+
function buildUsableMinimalTemplateConfig() {
|
|
6022
|
+
const openRouterPreset = getProviderPreset("openrouter");
|
|
6023
|
+
const modelId = openRouterPreset?.suggested_id ?? "sonnet";
|
|
6024
|
+
const modelName = openRouterPreset?.default_model ?? "anthropic/claude-sonnet-4";
|
|
6025
|
+
const thinking = openRouterPreset?.default_thinking ?? "auto";
|
|
6026
|
+
const draft = buildMinimalConfig({
|
|
6027
|
+
providers: [
|
|
6028
|
+
{
|
|
6029
|
+
name: "openrouter",
|
|
6030
|
+
model_id: modelId,
|
|
6031
|
+
preset: "openrouter",
|
|
6032
|
+
api_key: "sk-xxx",
|
|
6033
|
+
models: [modelName]
|
|
6034
|
+
}
|
|
6035
|
+
],
|
|
6036
|
+
defaultModel: modelId
|
|
6037
|
+
});
|
|
6038
|
+
const primaryModel = draft.Models?.[0];
|
|
6039
|
+
return {
|
|
6040
|
+
HOST: DEFAULT_CONFIG2.HOST,
|
|
6041
|
+
PORT: DEFAULT_CONFIG2.PORT,
|
|
6042
|
+
LOG: DEFAULT_CONFIG2.LOG,
|
|
6043
|
+
LOG_LEVEL: DEFAULT_CONFIG2.LOG_LEVEL,
|
|
6044
|
+
Models: primaryModel ? [
|
|
6045
|
+
{
|
|
6046
|
+
id: primaryModel.id,
|
|
6047
|
+
api: primaryModel.api,
|
|
6048
|
+
key: primaryModel.key,
|
|
6049
|
+
interface: primaryModel.interface,
|
|
6050
|
+
model: primaryModel.model,
|
|
6051
|
+
thinking
|
|
6052
|
+
}
|
|
6053
|
+
] : [],
|
|
6054
|
+
Router: draft.Router
|
|
6055
|
+
};
|
|
6056
|
+
}
|
|
5807
6057
|
var init_templates = __esm({
|
|
5808
6058
|
"src/setup/templates.ts"() {
|
|
5809
6059
|
"use strict";
|
|
6060
|
+
init_constants();
|
|
5810
6061
|
init_provider_presets();
|
|
5811
6062
|
}
|
|
5812
6063
|
});
|
|
5813
6064
|
|
|
5814
6065
|
// src/setup/persist.ts
|
|
5815
|
-
async function persistSetupConfig(
|
|
5816
|
-
const errors =
|
|
6066
|
+
async function persistSetupConfig(input3) {
|
|
6067
|
+
const errors = input3.validateConfig(input3.config);
|
|
5817
6068
|
if (errors.length > 0) {
|
|
5818
6069
|
throw new Error("config validation failed");
|
|
5819
6070
|
}
|
|
5820
6071
|
let backupPath;
|
|
5821
|
-
if (
|
|
5822
|
-
const createdBackupPath = await
|
|
6072
|
+
if (input3.hasExistingConfig) {
|
|
6073
|
+
const createdBackupPath = await input3.backupCurrentConfig();
|
|
5823
6074
|
if (!createdBackupPath) {
|
|
5824
6075
|
throw new Error("failed to back up existing config");
|
|
5825
6076
|
}
|
|
5826
6077
|
backupPath = createdBackupPath;
|
|
5827
6078
|
}
|
|
5828
|
-
await
|
|
6079
|
+
await input3.writeConfig(input3.config);
|
|
5829
6080
|
return {
|
|
5830
6081
|
configChanged: true,
|
|
5831
|
-
configPath:
|
|
6082
|
+
configPath: input3.currentConfigPath,
|
|
5832
6083
|
backupPath
|
|
5833
6084
|
};
|
|
5834
6085
|
}
|
|
@@ -5877,8 +6128,8 @@ function ensureLegacyFlow(detection, legacyConfigAction) {
|
|
|
5877
6128
|
function invalidAction() {
|
|
5878
6129
|
return invalidCurrentAction();
|
|
5879
6130
|
}
|
|
5880
|
-
function decideSetupBranch(
|
|
5881
|
-
const { detection, currentConfigAction, legacyConfigAction } =
|
|
6131
|
+
function decideSetupBranch(input3) {
|
|
6132
|
+
const { detection, currentConfigAction, legacyConfigAction } = input3;
|
|
5882
6133
|
if (currentConfigAction === "cancel") {
|
|
5883
6134
|
ensureNoLegacyAction(legacyConfigAction);
|
|
5884
6135
|
return { kind: "cancelled" };
|
|
@@ -5931,6 +6182,28 @@ function getTargetConfigPath(detection) {
|
|
|
5931
6182
|
}
|
|
5932
6183
|
return CONFIG_FILE;
|
|
5933
6184
|
}
|
|
6185
|
+
function getLegacyProviderCount(input3) {
|
|
6186
|
+
if (typeof input3 !== "object" || input3 === null) {
|
|
6187
|
+
return 0;
|
|
6188
|
+
}
|
|
6189
|
+
const legacyConfig = input3;
|
|
6190
|
+
if (Array.isArray(legacyConfig.providers)) {
|
|
6191
|
+
return legacyConfig.providers.length;
|
|
6192
|
+
}
|
|
6193
|
+
if (Array.isArray(legacyConfig.Providers)) {
|
|
6194
|
+
return legacyConfig.Providers.length;
|
|
6195
|
+
}
|
|
6196
|
+
return 0;
|
|
6197
|
+
}
|
|
6198
|
+
function getMigratedModelCount(draft) {
|
|
6199
|
+
if (Array.isArray(draft.Models)) {
|
|
6200
|
+
return draft.Models.length;
|
|
6201
|
+
}
|
|
6202
|
+
if (Array.isArray(draft.Providers)) {
|
|
6203
|
+
return draft.Providers.reduce((total, provider) => total + (provider.models?.length ?? 0), 0);
|
|
6204
|
+
}
|
|
6205
|
+
return 0;
|
|
6206
|
+
}
|
|
5934
6207
|
async function runSetup(deps) {
|
|
5935
6208
|
const detection = await deps.detectSetupEnvironment();
|
|
5936
6209
|
const currentConfigAction = await deps.chooseCurrentConfigAction({
|
|
@@ -5938,7 +6211,7 @@ async function runSetup(deps) {
|
|
|
5938
6211
|
legacyConfig: detection.legacyConfig
|
|
5939
6212
|
});
|
|
5940
6213
|
let legacyConfigAction;
|
|
5941
|
-
if (currentConfigAction === "create" || currentConfigAction === "overwrite") {
|
|
6214
|
+
if (currentConfigAction === "create" || currentConfigAction === "overwrite" || currentConfigAction === "fresh") {
|
|
5942
6215
|
if (detection.legacyConfig.kind === "found" || detection.legacyConfig.kind === "read_error") {
|
|
5943
6216
|
legacyConfigAction = await deps.chooseLegacyConfigAction({
|
|
5944
6217
|
legacyConfig: detection.legacyConfig
|
|
@@ -6012,6 +6285,16 @@ async function runSetup(deps) {
|
|
|
6012
6285
|
throw new Error("migrate_legacy requires legacy config");
|
|
6013
6286
|
}
|
|
6014
6287
|
const migrated = deps.migrateLegacyConfig(detection.legacyConfig.config);
|
|
6288
|
+
deps.io.info(`\u5DF2\u8BC6\u522B\u65E7\u914D\u7F6E\u4E2D\u7684 ${getLegacyProviderCount(detection.legacyConfig.config)} \u4E2A provider\u3002`);
|
|
6289
|
+
deps.io.info(`\u5DF2\u4ECE\u65E7\u914D\u7F6E\u8FC1\u79FB ${getMigratedModelCount(migrated.draft)} \u4E2A\u6A21\u578B\u3002`);
|
|
6290
|
+
if (migrated.draft.Router.default) {
|
|
6291
|
+
deps.io.info(`\u8FC1\u79FB\u540E\u7684\u9ED8\u8BA4\u6A21\u578B\uFF1A${migrated.draft.Router.default}`);
|
|
6292
|
+
} else {
|
|
6293
|
+
deps.io.info("\u8FC1\u79FB\u540E\u7684\u9ED8\u8BA4\u6A21\u578B\u4ECD\u9700\u8865\u5168\u3002");
|
|
6294
|
+
}
|
|
6295
|
+
if (migrated.skippedFields.length > 0) {
|
|
6296
|
+
deps.io.info(`\u4EE5\u4E0B\u65E7\u5B57\u6BB5\u672A\u81EA\u52A8\u8FC1\u79FB\uFF1A${migrated.skippedFields.join(", ")}`);
|
|
6297
|
+
}
|
|
6015
6298
|
let finalDraft = migrated.draft;
|
|
6016
6299
|
if (migrated.needsCompletion) {
|
|
6017
6300
|
finalDraft = await deps.completeDraft({
|
|
@@ -6046,6 +6329,96 @@ var init_setup = __esm({
|
|
|
6046
6329
|
|
|
6047
6330
|
// src/setup/index.ts
|
|
6048
6331
|
function createConsoleIO() {
|
|
6332
|
+
if (process.env.CTR_SETUP_FORCE_SCRIPTED_INPUT === "1") {
|
|
6333
|
+
const scriptedInput = (0, import_fs6.readFileSync)(0, "utf-8");
|
|
6334
|
+
const answers = scriptedInput.split(/\r?\n/).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6335
|
+
let cursor = 0;
|
|
6336
|
+
const nextAnswer = async () => answers[cursor++] ?? "";
|
|
6337
|
+
return {
|
|
6338
|
+
async choose(message, options) {
|
|
6339
|
+
import_process.stdout.write(`${message}
|
|
6340
|
+
`);
|
|
6341
|
+
options.forEach((option, index) => {
|
|
6342
|
+
import_process.stdout.write(` ${index + 1}. ${option}
|
|
6343
|
+
`);
|
|
6344
|
+
});
|
|
6345
|
+
const answer = await nextAnswer();
|
|
6346
|
+
const pickedIndex = Number(answer);
|
|
6347
|
+
if (Number.isInteger(pickedIndex) && pickedIndex >= 1 && pickedIndex <= options.length) {
|
|
6348
|
+
return options[pickedIndex - 1];
|
|
6349
|
+
}
|
|
6350
|
+
const matched = options.find((option) => option === answer);
|
|
6351
|
+
if (matched) {
|
|
6352
|
+
return matched;
|
|
6353
|
+
}
|
|
6354
|
+
throw new Error(`invalid scripted answer for "${message}": ${answer || "<empty>"}`);
|
|
6355
|
+
},
|
|
6356
|
+
async input(message, defaultValue) {
|
|
6357
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
6358
|
+
import_process.stdout.write(`${message}${suffix}: `);
|
|
6359
|
+
const answer = await nextAnswer();
|
|
6360
|
+
return answer || defaultValue || "";
|
|
6361
|
+
},
|
|
6362
|
+
info(message) {
|
|
6363
|
+
import_process.stdout.write(`${message}
|
|
6364
|
+
`);
|
|
6365
|
+
},
|
|
6366
|
+
close() {
|
|
6367
|
+
}
|
|
6368
|
+
};
|
|
6369
|
+
}
|
|
6370
|
+
if (!import_process.stdin.isTTY) {
|
|
6371
|
+
let loaded = false;
|
|
6372
|
+
let answers = [];
|
|
6373
|
+
let cursor = 0;
|
|
6374
|
+
const loadAnswers = async () => {
|
|
6375
|
+
if (loaded) {
|
|
6376
|
+
return;
|
|
6377
|
+
}
|
|
6378
|
+
loaded = true;
|
|
6379
|
+
const chunks = [];
|
|
6380
|
+
for await (const chunk of import_process.stdin) {
|
|
6381
|
+
chunks.push(String(chunk));
|
|
6382
|
+
}
|
|
6383
|
+
answers = chunks.join("").split(/\r?\n/).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6384
|
+
};
|
|
6385
|
+
const nextAnswer = async () => {
|
|
6386
|
+
await loadAnswers();
|
|
6387
|
+
return answers[cursor++] ?? "";
|
|
6388
|
+
};
|
|
6389
|
+
return {
|
|
6390
|
+
async choose(message, options) {
|
|
6391
|
+
import_process.stdout.write(`${message}
|
|
6392
|
+
`);
|
|
6393
|
+
options.forEach((option, index) => {
|
|
6394
|
+
import_process.stdout.write(` ${index + 1}. ${option}
|
|
6395
|
+
`);
|
|
6396
|
+
});
|
|
6397
|
+
const answer = await nextAnswer();
|
|
6398
|
+
const pickedIndex = Number(answer);
|
|
6399
|
+
if (Number.isInteger(pickedIndex) && pickedIndex >= 1 && pickedIndex <= options.length) {
|
|
6400
|
+
return options[pickedIndex - 1];
|
|
6401
|
+
}
|
|
6402
|
+
const matched = options.find((option) => option === answer);
|
|
6403
|
+
if (matched) {
|
|
6404
|
+
return matched;
|
|
6405
|
+
}
|
|
6406
|
+
throw new Error(`invalid scripted answer for "${message}": ${answer || "<empty>"}`);
|
|
6407
|
+
},
|
|
6408
|
+
async input(message, defaultValue) {
|
|
6409
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
6410
|
+
import_process.stdout.write(`${message}${suffix}: `);
|
|
6411
|
+
const answer = await nextAnswer();
|
|
6412
|
+
return answer || defaultValue || "";
|
|
6413
|
+
},
|
|
6414
|
+
info(message) {
|
|
6415
|
+
import_process.stdout.write(`${message}
|
|
6416
|
+
`);
|
|
6417
|
+
},
|
|
6418
|
+
close() {
|
|
6419
|
+
}
|
|
6420
|
+
};
|
|
6421
|
+
}
|
|
6049
6422
|
const rl = (0, import_promises3.createInterface)({ input: import_process.stdin, output: import_process.stdout });
|
|
6050
6423
|
const ask = async (message) => {
|
|
6051
6424
|
const answer = await rl.question(message);
|
|
@@ -6080,6 +6453,9 @@ function createConsoleIO() {
|
|
|
6080
6453
|
info(message) {
|
|
6081
6454
|
import_process.stdout.write(`${message}
|
|
6082
6455
|
`);
|
|
6456
|
+
},
|
|
6457
|
+
close() {
|
|
6458
|
+
rl.close();
|
|
6083
6459
|
}
|
|
6084
6460
|
};
|
|
6085
6461
|
}
|
|
@@ -6090,14 +6466,79 @@ function readStructuredConfigFile(filePath) {
|
|
|
6090
6466
|
}
|
|
6091
6467
|
return import_js_yaml.default.load(content);
|
|
6092
6468
|
}
|
|
6469
|
+
function getCurrentRuntimeFields() {
|
|
6470
|
+
const candidates = [CONFIG_FILE, CONFIG_FILE_YML, CONFIG_FILE_JSON];
|
|
6471
|
+
const currentPath = candidates.find((filePath) => (0, import_fs6.existsSync)(filePath));
|
|
6472
|
+
if (!currentPath) {
|
|
6473
|
+
return {};
|
|
6474
|
+
}
|
|
6475
|
+
try {
|
|
6476
|
+
const config = readStructuredConfigFile(currentPath);
|
|
6477
|
+
if (!config || typeof config !== "object") {
|
|
6478
|
+
return {};
|
|
6479
|
+
}
|
|
6480
|
+
const fields = {};
|
|
6481
|
+
for (const key of ["HOST", "PORT", "LOG", "LOG_LEVEL", "API_TIMEOUT_MS"]) {
|
|
6482
|
+
if (config[key] !== void 0) {
|
|
6483
|
+
fields[key] = config[key];
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
return fields;
|
|
6487
|
+
} catch {
|
|
6488
|
+
return {};
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
function getConfiguredPortFromCurrentFiles() {
|
|
6492
|
+
const candidates = [CONFIG_FILE, CONFIG_FILE_YML, CONFIG_FILE_JSON];
|
|
6493
|
+
const currentPath = candidates.find((filePath) => (0, import_fs6.existsSync)(filePath));
|
|
6494
|
+
if (!currentPath) {
|
|
6495
|
+
return DEFAULT_CONFIG2.PORT;
|
|
6496
|
+
}
|
|
6497
|
+
try {
|
|
6498
|
+
const config = readStructuredConfigFile(currentPath);
|
|
6499
|
+
if (config && typeof config.PORT === "number" && Number.isFinite(config.PORT) && config.PORT > 0) {
|
|
6500
|
+
return config.PORT;
|
|
6501
|
+
}
|
|
6502
|
+
} catch {
|
|
6503
|
+
}
|
|
6504
|
+
return DEFAULT_CONFIG2.PORT;
|
|
6505
|
+
}
|
|
6506
|
+
async function getAvailablePort() {
|
|
6507
|
+
const server = (0, import_net2.createServer)();
|
|
6508
|
+
try {
|
|
6509
|
+
return await new Promise((resolve, reject) => {
|
|
6510
|
+
server.once("error", reject);
|
|
6511
|
+
server.listen(0, "127.0.0.1", () => {
|
|
6512
|
+
const address = server.address();
|
|
6513
|
+
if (!address || typeof address === "string") {
|
|
6514
|
+
reject(new Error("failed to resolve available port"));
|
|
6515
|
+
return;
|
|
6516
|
+
}
|
|
6517
|
+
resolve(address.port);
|
|
6518
|
+
});
|
|
6519
|
+
});
|
|
6520
|
+
} finally {
|
|
6521
|
+
if (server.listening) {
|
|
6522
|
+
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
function readLegacyConfigFile(filePath) {
|
|
6527
|
+
const content = (0, import_fs6.readFileSync)(filePath, "utf-8");
|
|
6528
|
+
if (filePath.endsWith(".json")) {
|
|
6529
|
+
return import_json52.default.parse(content);
|
|
6530
|
+
}
|
|
6531
|
+
return import_js_yaml.default.load(content);
|
|
6532
|
+
}
|
|
6093
6533
|
async function readLegacyConfig(deps = {}) {
|
|
6094
6534
|
const baseHomeDir = deps.homeDir || (0, import_os3.homedir)();
|
|
6095
6535
|
const exists = deps.exists || import_fs6.existsSync;
|
|
6096
|
-
const readConfig = deps.readConfig ||
|
|
6536
|
+
const readConfig = deps.readConfig || readLegacyConfigFile;
|
|
6097
6537
|
const overridePath = process.env.CTR_SETUP_LEGACY_CONFIG_PATH;
|
|
6098
6538
|
const candidatePaths = overridePath ? [overridePath] : [
|
|
6099
6539
|
(0, import_path6.join)(baseHomeDir, ".ccr", "config.yaml"),
|
|
6100
|
-
(0, import_path6.join)(baseHomeDir, ".claude-code-router", "config.yaml")
|
|
6540
|
+
(0, import_path6.join)(baseHomeDir, ".claude-code-router", "config.yaml"),
|
|
6541
|
+
(0, import_path6.join)(baseHomeDir, ".claude-code-router", "config.json")
|
|
6101
6542
|
];
|
|
6102
6543
|
const legacyPath = candidatePaths.find((filePath) => exists(filePath));
|
|
6103
6544
|
if (!legacyPath) {
|
|
@@ -6140,16 +6581,30 @@ async function readCurrentConfig() {
|
|
|
6140
6581
|
}
|
|
6141
6582
|
}
|
|
6142
6583
|
async function probeService() {
|
|
6143
|
-
const
|
|
6144
|
-
|
|
6584
|
+
const port = getConfiguredPortFromCurrentFiles();
|
|
6585
|
+
const healthy = await waitForService(port, 500);
|
|
6586
|
+
if (healthy) {
|
|
6587
|
+
return isServiceRunning() ? { kind: "self_healthy", port } : { kind: "non_self_occupied", port };
|
|
6588
|
+
}
|
|
6589
|
+
const occupied = await isTcpPortOccupied(port, 500);
|
|
6590
|
+
if (!occupied) {
|
|
6591
|
+
return { kind: "none" };
|
|
6592
|
+
}
|
|
6593
|
+
return isServiceRunning() ? { kind: "self_unhealthy", port } : { kind: "non_self_occupied", port };
|
|
6145
6594
|
}
|
|
6146
6595
|
async function enterClaudeCode() {
|
|
6596
|
+
if (process.env.CTR_SETUP_SKIP_ENTER_CODE === "1") {
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6147
6599
|
const cliModule = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
6148
6600
|
await cliModule.runClaudeCode();
|
|
6149
6601
|
}
|
|
6602
|
+
function shouldAutoEnterClaudeCodeAfterSetup() {
|
|
6603
|
+
return process.env.CTR_SETUP_AUTO_ENTER_CODE === "1";
|
|
6604
|
+
}
|
|
6150
6605
|
async function executeStart() {
|
|
6151
6606
|
const childProcess = await import("child_process");
|
|
6152
|
-
childProcess.spawn(process.execPath, [process.argv[1], "start"
|
|
6607
|
+
childProcess.spawn(process.execPath, [process.argv[1], "start"], {
|
|
6153
6608
|
detached: true,
|
|
6154
6609
|
stdio: "ignore",
|
|
6155
6610
|
env: { ...process.env, CTR_DAEMON: "1" }
|
|
@@ -6339,12 +6794,12 @@ async function buildFreshConfig(io) {
|
|
|
6339
6794
|
}
|
|
6340
6795
|
return draft;
|
|
6341
6796
|
}
|
|
6342
|
-
async function completeDraft(
|
|
6343
|
-
const draft = toDraftFromConfig(
|
|
6344
|
-
if (
|
|
6797
|
+
async function completeDraft(input3) {
|
|
6798
|
+
const draft = toDraftFromConfig(input3.draft);
|
|
6799
|
+
if (input3.fields.includes("defaultModel")) {
|
|
6345
6800
|
const defaultProvider = draft.Models?.[0]?.id ?? draft.Providers?.[0]?.name ?? "provider";
|
|
6346
6801
|
const defaultModel = draft.Models?.[0]?.model ?? draft.Providers?.[0]?.models?.[0] ?? "";
|
|
6347
|
-
const model = await
|
|
6802
|
+
const model = await input3.io.input("\u9ED8\u8BA4\u6A21\u578B", defaultModel);
|
|
6348
6803
|
if (draft.Models?.[0]) {
|
|
6349
6804
|
draft.Models[0].model = model;
|
|
6350
6805
|
draft.Router.default = defaultProvider;
|
|
@@ -6353,16 +6808,16 @@ async function completeDraft(input2) {
|
|
|
6353
6808
|
draft.Router.default = `${defaultProvider},${model}`;
|
|
6354
6809
|
}
|
|
6355
6810
|
}
|
|
6356
|
-
if (
|
|
6357
|
-
const apiKey = await
|
|
6811
|
+
if (input3.fields.includes("apiKey")) {
|
|
6812
|
+
const apiKey = await input3.io.input("API Key");
|
|
6358
6813
|
if (draft.Models?.length) {
|
|
6359
6814
|
draft.Models = draft.Models.map((model) => ({ ...model, key: model.key || apiKey, api_key: model.api_key || apiKey }));
|
|
6360
6815
|
} else {
|
|
6361
6816
|
draft.Providers = draft.Providers?.map((provider) => ({ ...provider, api_key: provider.api_key || apiKey }));
|
|
6362
6817
|
}
|
|
6363
6818
|
}
|
|
6364
|
-
if (
|
|
6365
|
-
const apiBaseUrl = await
|
|
6819
|
+
if (input3.fields.includes("apiBaseUrl")) {
|
|
6820
|
+
const apiBaseUrl = await input3.io.input("API Base URL");
|
|
6366
6821
|
if (draft.Models?.length) {
|
|
6367
6822
|
draft.Models = draft.Models.map((model) => ({
|
|
6368
6823
|
...model,
|
|
@@ -6376,8 +6831,8 @@ async function completeDraft(input2) {
|
|
|
6376
6831
|
}));
|
|
6377
6832
|
}
|
|
6378
6833
|
}
|
|
6379
|
-
if (
|
|
6380
|
-
await promptCapabilityMetadataForDraft(draft,
|
|
6834
|
+
if (input3.fields.includes("capabilityHints") && draft.Models?.[0]) {
|
|
6835
|
+
await promptCapabilityMetadataForDraft(draft, input3.io);
|
|
6381
6836
|
}
|
|
6382
6837
|
return draft;
|
|
6383
6838
|
}
|
|
@@ -6391,112 +6846,150 @@ function createDefaultDeps(io = createConsoleIO()) {
|
|
|
6391
6846
|
executeStart,
|
|
6392
6847
|
executeReload: executeRestart,
|
|
6393
6848
|
executeRestart,
|
|
6394
|
-
verifyHealth: () => waitForService(
|
|
6849
|
+
verifyHealth: () => waitForService(getConfiguredPortFromCurrentFiles(), 5e3),
|
|
6395
6850
|
enterClaudeCode,
|
|
6396
6851
|
io
|
|
6397
6852
|
};
|
|
6398
6853
|
}
|
|
6854
|
+
function printRoutingNextSteps(io) {
|
|
6855
|
+
io.info("\u4F60\u53EF\u4EE5\u6309\u9700\u7EE7\u7EED\u914D\u7F6E\u8DEF\u7531\u80FD\u529B\uFF1A");
|
|
6856
|
+
io.info(" - TriggerRouter\uFF1A\u9002\u5408\u9AD8\u786E\u5B9A\u6027\u4EFB\u52A1\uFF0C\u628A\u67B6\u6784\u8BBE\u8BA1\u3001\u4EE3\u7801\u5BA1\u67E5\u7B49\u8BF7\u6C42\u56FA\u5B9A\u5207\u5230\u6307\u5B9A\u6A21\u578B");
|
|
6857
|
+
io.info(" - SmartRouter\uFF1A\u9002\u5408\u6A21\u7CCA\u4EFB\u52A1\uFF0C\u5728\u5019\u9009\u6A21\u578B\u4E4B\u95F4\u81EA\u52A8\u9009\u62E9\u66F4\u5408\u9002\u7684\u6A21\u578B");
|
|
6858
|
+
io.info(" - \u914D\u7F6E\u6A21\u677F\u53C2\u8003\uFF1Aconfig/trigger.advanced.yaml");
|
|
6859
|
+
}
|
|
6399
6860
|
async function runSetupCli(customDeps) {
|
|
6400
6861
|
const defaults = createDefaultDeps(customDeps?.io);
|
|
6401
6862
|
const deps = { ...defaults, ...customDeps };
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
"\u76F4\u63A5\u4F7F\u7528\
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
if (currentConfig.kind === "invalid") {
|
|
6426
|
-
deps.io.info(`\u5F53\u524D\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A${currentConfig.errors.join("; ")}`);
|
|
6427
|
-
if (currentConfig.warnings.length > 0) {
|
|
6428
|
-
deps.io.info(`\u5F53\u524D\u914D\u7F6E\u63D0\u793A\uFF1A${currentConfig.warnings.join("; ")}`);
|
|
6863
|
+
try {
|
|
6864
|
+
await runSetup({
|
|
6865
|
+
detectSetupEnvironment: () => detectSetupEnvironment({
|
|
6866
|
+
readCurrentConfig: deps.readCurrentConfig,
|
|
6867
|
+
readLegacyConfig: deps.readLegacyConfig,
|
|
6868
|
+
probeService: deps.probeService
|
|
6869
|
+
}),
|
|
6870
|
+
chooseCurrentConfigAction: async ({ currentConfig }) => {
|
|
6871
|
+
if (currentConfig.kind === "missing") {
|
|
6872
|
+
return "create";
|
|
6873
|
+
}
|
|
6874
|
+
if (currentConfig.kind === "valid") {
|
|
6875
|
+
deps.io.info("\u68C0\u6D4B\u5230\u5F53\u524D claude-trigger-router \u914D\u7F6E\u5DF2\u53EF\u7528\u3002");
|
|
6876
|
+
if (currentConfig.warnings.length > 0) {
|
|
6877
|
+
deps.io.info(`\u5F53\u524D\u914D\u7F6E\u63D0\u793A\uFF1A${currentConfig.warnings.join("; ")}`);
|
|
6878
|
+
}
|
|
6879
|
+
return mapValidCurrentConfigChoice(
|
|
6880
|
+
await deps.io.choose("\u4F60\u60F3\u76F4\u63A5\u4F7F\u7528\u5B83\uFF0C\u8FD8\u662F\u91CD\u65B0\u8C03\u6574\uFF1F", [
|
|
6881
|
+
"\u76F4\u63A5\u4F7F\u7528\u5F53\u524D\u914D\u7F6E\uFF08\u63A8\u8350\uFF09",
|
|
6882
|
+
"\u68C0\u67E5\u5E76\u8C03\u6574\u5F53\u524D\u914D\u7F6E",
|
|
6883
|
+
"\u653E\u5F03\u5F53\u524D\u914D\u7F6E\uFF0C\u91CD\u65B0\u5F00\u59CB"
|
|
6884
|
+
])
|
|
6885
|
+
);
|
|
6429
6886
|
}
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6887
|
+
if (currentConfig.kind === "invalid") {
|
|
6888
|
+
deps.io.info(`\u5F53\u524D\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A${currentConfig.errors.join("; ")}`);
|
|
6889
|
+
if (currentConfig.warnings.length > 0) {
|
|
6890
|
+
deps.io.info(`\u5F53\u524D\u914D\u7F6E\u63D0\u793A\uFF1A${currentConfig.warnings.join("; ")}`);
|
|
6891
|
+
}
|
|
6892
|
+
return await deps.io.choose("\u9009\u62E9\u4E0B\u4E00\u6B65", ["repair", "overwrite", "cancel"]);
|
|
6893
|
+
}
|
|
6894
|
+
deps.io.info(`\u5F53\u524D\u914D\u7F6E\u65E0\u6CD5\u89E3\u6790\uFF1A${currentConfig.error}`);
|
|
6895
|
+
return await deps.io.choose("\u9009\u62E9\u4E0B\u4E00\u6B65", ["rebuild", "cancel"]);
|
|
6896
|
+
},
|
|
6897
|
+
chooseLegacyConfigAction: async ({ legacyConfig }) => {
|
|
6898
|
+
if (legacyConfig.kind === "found") {
|
|
6899
|
+
return mapLegacyConfigChoice(
|
|
6900
|
+
await deps.io.choose("\u68C0\u6D4B\u5230\u65E7 claude-code-router \u914D\u7F6E\u3002\u662F\u5426\u8FC1\u79FB\u4E3A\u5F53\u524D\u63A8\u8350\u914D\u7F6E\uFF1F", [
|
|
6901
|
+
"\u8FC1\u79FB\u65E7\u914D\u7F6E\uFF08\u63A8\u8350\uFF09",
|
|
6902
|
+
"\u8DF3\u8FC7\u8FC1\u79FB\uFF0C\u624B\u52A8\u65B0\u5EFA"
|
|
6903
|
+
])
|
|
6904
|
+
);
|
|
6905
|
+
}
|
|
6906
|
+
if (legacyConfig.kind === "read_error") {
|
|
6907
|
+
deps.io.info(`\u65E7 ccr \u914D\u7F6E\u8BFB\u53D6\u5931\u8D25\uFF1A${legacyConfig.error}`);
|
|
6908
|
+
}
|
|
6909
|
+
return "skip";
|
|
6910
|
+
},
|
|
6911
|
+
buildFreshConfig: () => buildFreshConfig(deps.io),
|
|
6912
|
+
buildRepairConfig: async ({ currentConfig }) => toDraftFromConfig(currentConfig),
|
|
6913
|
+
completeDraft: ({ draft, fields }) => completeDraft({ draft, fields, io: deps.io }),
|
|
6914
|
+
migrateLegacyConfig,
|
|
6915
|
+
mapConfigErrorsToRepairFields,
|
|
6916
|
+
io: deps.io,
|
|
6917
|
+
persistConfig: async ({ config, currentConfigPath, hasExistingConfig }) => {
|
|
6918
|
+
let normalized = normalizeAndValidateConfig({
|
|
6919
|
+
...hasExistingConfig ? getCurrentRuntimeFields() : {},
|
|
6920
|
+
...config
|
|
6921
|
+
});
|
|
6922
|
+
{
|
|
6923
|
+
const targetPort = normalized.config.PORT ?? DEFAULT_CONFIG2.PORT;
|
|
6924
|
+
const occupied = await isTcpPortOccupied(targetPort, 500);
|
|
6925
|
+
if (occupied && !isServiceRunning()) {
|
|
6926
|
+
const fallbackPort = await getAvailablePort();
|
|
6927
|
+
deps.io.info(`\u68C0\u6D4B\u5230\u9ED8\u8BA4\u7AEF\u53E3 ${targetPort} \u5DF2\u88AB\u5360\u7528\uFF0Csetup \u5DF2\u81EA\u52A8\u6539\u7528\u53EF\u7528\u7AEF\u53E3 ${fallbackPort}\u3002`);
|
|
6928
|
+
normalized = normalizeAndValidateConfig({
|
|
6929
|
+
...normalized.config,
|
|
6930
|
+
PORT: fallbackPort
|
|
6931
|
+
});
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
const persisted = await persistSetupConfig({
|
|
6935
|
+
config: normalized.config,
|
|
6936
|
+
currentConfigPath,
|
|
6937
|
+
hasExistingConfig,
|
|
6938
|
+
validateConfig: (inputConfig) => normalizeAndValidateConfig(inputConfig).errors,
|
|
6939
|
+
backupCurrentConfig: deps.backupCurrentConfig,
|
|
6940
|
+
writeConfig: deps.writeConfig
|
|
6941
|
+
});
|
|
6942
|
+
if (normalized.warnings.length > 0) {
|
|
6943
|
+
deps.io.info(`\u914D\u7F6E\u63D0\u793A\uFF1A${normalized.warnings.join("; ")}`);
|
|
6944
|
+
}
|
|
6945
|
+
return persisted;
|
|
6946
|
+
},
|
|
6947
|
+
ensureServiceReady: async ({ configChanged, detectedService, reloadSupported }) => {
|
|
6948
|
+
const effectiveDetectedService = configChanged ? await deps.probeService() : detectedService;
|
|
6949
|
+
const action = decideServiceAction({
|
|
6950
|
+
configChanged,
|
|
6951
|
+
detectedService: effectiveDetectedService,
|
|
6952
|
+
reloadSupported
|
|
6953
|
+
});
|
|
6954
|
+
await applyServiceAction({
|
|
6955
|
+
action,
|
|
6956
|
+
executeStart: deps.executeStart,
|
|
6957
|
+
executeReload: deps.executeReload,
|
|
6958
|
+
executeRestart: deps.executeRestart,
|
|
6959
|
+
verifyHealth: deps.verifyHealth
|
|
6960
|
+
});
|
|
6961
|
+
return {
|
|
6962
|
+
action: action.kind,
|
|
6963
|
+
healthChecked: true
|
|
6964
|
+
};
|
|
6965
|
+
},
|
|
6966
|
+
enterClaudeCode: async () => {
|
|
6967
|
+
printRoutingNextSteps(deps.io);
|
|
6968
|
+
if (!shouldAutoEnterClaudeCodeAfterSetup()) {
|
|
6969
|
+
deps.io.info("\u4E3A\u907F\u514D setup \u7ED3\u675F\u540E\u63A5\u7BA1\u5F53\u524D\u7EC8\u7AEF\uFF0C\u8BF7\u624B\u52A8\u8FD0\u884C\uFF1Actr code");
|
|
6970
|
+
deps.io.info("\u5982\u679C\u4F60\u660E\u786E\u9700\u8981 setup \u7ED3\u675F\u540E\u81EA\u52A8\u8FDB\u5165 Claude Code\uFF0C\u53EF\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF CTR_SETUP_AUTO_ENTER_CODE=1");
|
|
6971
|
+
return;
|
|
6972
|
+
}
|
|
6973
|
+
deps.io.close?.();
|
|
6974
|
+
await deps.enterClaudeCode();
|
|
6975
|
+
},
|
|
6976
|
+
reloadSupported: false
|
|
6977
|
+
});
|
|
6978
|
+
} finally {
|
|
6979
|
+
deps.io.close?.();
|
|
6980
|
+
}
|
|
6490
6981
|
}
|
|
6491
|
-
var import_fs6, import_os3, import_path6, import_promises3, import_process, import_js_yaml;
|
|
6982
|
+
var import_fs6, import_net2, import_os3, import_path6, import_promises3, import_process, import_json52, import_js_yaml;
|
|
6492
6983
|
var init_setup2 = __esm({
|
|
6493
6984
|
"src/setup/index.ts"() {
|
|
6494
6985
|
"use strict";
|
|
6495
6986
|
import_fs6 = require("fs");
|
|
6987
|
+
import_net2 = require("net");
|
|
6496
6988
|
import_os3 = require("os");
|
|
6497
6989
|
import_path6 = require("path");
|
|
6498
6990
|
import_promises3 = require("readline/promises");
|
|
6499
6991
|
import_process = require("process");
|
|
6992
|
+
import_json52 = __toESM(require("json5"));
|
|
6500
6993
|
import_js_yaml = __toESM(require("js-yaml"));
|
|
6501
6994
|
init_constants();
|
|
6502
6995
|
init_provider_presets();
|
|
@@ -6513,6 +7006,525 @@ var init_setup2 = __esm({
|
|
|
6513
7006
|
}
|
|
6514
7007
|
});
|
|
6515
7008
|
|
|
7009
|
+
// src/doctor/index.ts
|
|
7010
|
+
function hasArg(flag) {
|
|
7011
|
+
return process.argv.slice(2).includes(flag);
|
|
7012
|
+
}
|
|
7013
|
+
function createConsoleIO2() {
|
|
7014
|
+
if (process.env.CTR_DOCTOR_FORCE_SCRIPTED_INPUT === "1") {
|
|
7015
|
+
const scriptedInput = (0, import_fs7.readFileSync)(0, "utf-8");
|
|
7016
|
+
const answers = scriptedInput.split(/\r?\n/).map((item) => item.trim()).filter(Boolean);
|
|
7017
|
+
let cursor = 0;
|
|
7018
|
+
const nextAnswer = async () => answers[cursor++] ?? "";
|
|
7019
|
+
return {
|
|
7020
|
+
info(message) {
|
|
7021
|
+
import_process2.stdout.write(`${message}
|
|
7022
|
+
`);
|
|
7023
|
+
},
|
|
7024
|
+
error(message) {
|
|
7025
|
+
import_process2.stdout.write(`${message}
|
|
7026
|
+
`);
|
|
7027
|
+
},
|
|
7028
|
+
async choose(message, options) {
|
|
7029
|
+
import_process2.stdout.write(`${message}
|
|
7030
|
+
`);
|
|
7031
|
+
options.forEach((option, index2) => import_process2.stdout.write(` ${index2 + 1}. ${option}
|
|
7032
|
+
`));
|
|
7033
|
+
const answer = await nextAnswer();
|
|
7034
|
+
const index = Number(answer);
|
|
7035
|
+
if (Number.isInteger(index) && index >= 1 && index <= options.length) {
|
|
7036
|
+
return options[index - 1];
|
|
7037
|
+
}
|
|
7038
|
+
return options.find((option) => option === answer) ?? options[0];
|
|
7039
|
+
},
|
|
7040
|
+
async input(message, defaultValue) {
|
|
7041
|
+
import_process2.stdout.write(`${message}${defaultValue ? ` (${defaultValue})` : ""}: `);
|
|
7042
|
+
const answer = await nextAnswer();
|
|
7043
|
+
return answer || defaultValue || "";
|
|
7044
|
+
},
|
|
7045
|
+
async confirm(message, defaultValue = true) {
|
|
7046
|
+
import_process2.stdout.write(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"}
|
|
7047
|
+
`);
|
|
7048
|
+
const answer = (await nextAnswer()).toLowerCase();
|
|
7049
|
+
if (!answer) {
|
|
7050
|
+
return defaultValue;
|
|
7051
|
+
}
|
|
7052
|
+
return ["y", "yes", "1", "true"].includes(answer);
|
|
7053
|
+
},
|
|
7054
|
+
close() {
|
|
7055
|
+
}
|
|
7056
|
+
};
|
|
7057
|
+
}
|
|
7058
|
+
const rl = (0, import_promises4.createInterface)({ input: import_process2.stdin, output: import_process2.stdout });
|
|
7059
|
+
const ask = async (message) => (await rl.question(message)).trim();
|
|
7060
|
+
return {
|
|
7061
|
+
info(message) {
|
|
7062
|
+
import_process2.stdout.write(`${message}
|
|
7063
|
+
`);
|
|
7064
|
+
},
|
|
7065
|
+
error(message) {
|
|
7066
|
+
import_process2.stdout.write(`${message}
|
|
7067
|
+
`);
|
|
7068
|
+
},
|
|
7069
|
+
async choose(message, options) {
|
|
7070
|
+
import_process2.stdout.write(`${message}
|
|
7071
|
+
`);
|
|
7072
|
+
options.forEach((option, index) => import_process2.stdout.write(` ${index + 1}. ${option}
|
|
7073
|
+
`));
|
|
7074
|
+
while (true) {
|
|
7075
|
+
const answer = await ask("> ");
|
|
7076
|
+
const index = Number(answer);
|
|
7077
|
+
if (Number.isInteger(index) && index >= 1 && index <= options.length) {
|
|
7078
|
+
return options[index - 1];
|
|
7079
|
+
}
|
|
7080
|
+
const matched = options.find((option) => option === answer);
|
|
7081
|
+
if (matched) {
|
|
7082
|
+
return matched;
|
|
7083
|
+
}
|
|
7084
|
+
import_process2.stdout.write("\u8BF7\u8F93\u5165\u9009\u9879\u7F16\u53F7\u3002\n");
|
|
7085
|
+
}
|
|
7086
|
+
},
|
|
7087
|
+
async input(message, defaultValue) {
|
|
7088
|
+
const answer = await ask(`${message}${defaultValue ? ` (${defaultValue})` : ""}: `);
|
|
7089
|
+
return answer || defaultValue || "";
|
|
7090
|
+
},
|
|
7091
|
+
async confirm(message, defaultValue = true) {
|
|
7092
|
+
const answer = (await ask(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"}: `)).toLowerCase();
|
|
7093
|
+
if (!answer) {
|
|
7094
|
+
return defaultValue;
|
|
7095
|
+
}
|
|
7096
|
+
return ["y", "yes"].includes(answer);
|
|
7097
|
+
},
|
|
7098
|
+
close() {
|
|
7099
|
+
rl.close();
|
|
7100
|
+
}
|
|
7101
|
+
};
|
|
7102
|
+
}
|
|
7103
|
+
function getConfigCandidates() {
|
|
7104
|
+
return [CONFIG_FILE, CONFIG_FILE_YML, CONFIG_FILE_JSON];
|
|
7105
|
+
}
|
|
7106
|
+
function inferInterfaceFromApi(api) {
|
|
7107
|
+
const trimmed = api?.trim();
|
|
7108
|
+
if (!trimmed) {
|
|
7109
|
+
return void 0;
|
|
7110
|
+
}
|
|
7111
|
+
return trimmed.includes("/v1/messages") ? "anthropic" : "openai";
|
|
7112
|
+
}
|
|
7113
|
+
function sanitizeModelId(value) {
|
|
7114
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "model";
|
|
7115
|
+
}
|
|
7116
|
+
function tryLoadStructuredConfig(filePath, content) {
|
|
7117
|
+
if (filePath.endsWith(".json")) {
|
|
7118
|
+
try {
|
|
7119
|
+
return { config: JSON.parse(content), repairedParse: false, messages: [] };
|
|
7120
|
+
} catch {
|
|
7121
|
+
return {
|
|
7122
|
+
config: import_json53.default.parse(content),
|
|
7123
|
+
repairedParse: true,
|
|
7124
|
+
messages: ["\u68C0\u6D4B\u5230 JSON \u914D\u7F6E\u5305\u542B\u5BBD\u677E\u8BED\u6CD5\uFF0Cdoctor \u5DF2\u6309\u6807\u51C6 JSON \u7ED3\u6784\u91CD\u65B0\u5F52\u4E00\u5316\u3002"]
|
|
7125
|
+
};
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
try {
|
|
7129
|
+
return { config: import_js_yaml2.default.load(content), repairedParse: false, messages: [] };
|
|
7130
|
+
} catch (error) {
|
|
7131
|
+
const sanitized = content.replace(/\t/g, " ");
|
|
7132
|
+
if (sanitized !== content) {
|
|
7133
|
+
return {
|
|
7134
|
+
config: import_js_yaml2.default.load(sanitized),
|
|
7135
|
+
repairedParse: true,
|
|
7136
|
+
messages: ["\u68C0\u6D4B\u5230 YAML \u4F7F\u7528\u4E86 Tab \u7F29\u8FDB\uFF0Cdoctor \u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\u7A7A\u683C\u7F29\u8FDB\u3002"]
|
|
7137
|
+
};
|
|
7138
|
+
}
|
|
7139
|
+
throw error;
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
function loadCurrentConfig() {
|
|
7143
|
+
const existingPath = getConfigCandidates().find((filePath) => (0, import_fs7.existsSync)(filePath));
|
|
7144
|
+
const path = existingPath ?? CONFIG_FILE;
|
|
7145
|
+
if (!existingPath) {
|
|
7146
|
+
return {
|
|
7147
|
+
path,
|
|
7148
|
+
existed: false,
|
|
7149
|
+
repairedParse: false,
|
|
7150
|
+
messages: ["\u672A\u68C0\u6D4B\u5230\u5F53\u524D Claude Trigger Router \u914D\u7F6E\u3002"]
|
|
7151
|
+
};
|
|
7152
|
+
}
|
|
7153
|
+
const content = (0, import_fs7.readFileSync)(existingPath, "utf-8");
|
|
7154
|
+
const loaded = tryLoadStructuredConfig(existingPath, content);
|
|
7155
|
+
return {
|
|
7156
|
+
path,
|
|
7157
|
+
existed: true,
|
|
7158
|
+
repairedParse: loaded.repairedParse,
|
|
7159
|
+
messages: loaded.messages,
|
|
7160
|
+
config: loaded.config
|
|
7161
|
+
};
|
|
7162
|
+
}
|
|
7163
|
+
function getModelLookupId(model) {
|
|
7164
|
+
return model.id?.trim() || sanitizeModelId(model.model ?? "");
|
|
7165
|
+
}
|
|
7166
|
+
function repairDeterministicConfig(config) {
|
|
7167
|
+
const nextConfig = {
|
|
7168
|
+
...config,
|
|
7169
|
+
HOST: config.HOST ?? DEFAULT_CONFIG2.HOST,
|
|
7170
|
+
PORT: config.PORT ?? DEFAULT_CONFIG2.PORT,
|
|
7171
|
+
LOG: config.LOG ?? DEFAULT_CONFIG2.LOG,
|
|
7172
|
+
LOG_LEVEL: config.LOG_LEVEL ?? DEFAULT_CONFIG2.LOG_LEVEL
|
|
7173
|
+
};
|
|
7174
|
+
const changes = [];
|
|
7175
|
+
if (Array.isArray(config.Models) && config.Models.length > 0) {
|
|
7176
|
+
nextConfig.Models = config.Models.map((item, index) => {
|
|
7177
|
+
const api = getModelApi(item);
|
|
7178
|
+
const key = getModelKey(item);
|
|
7179
|
+
const inferredInterface = getModelInterface(item) ?? inferInterfaceFromApi(api);
|
|
7180
|
+
const id = item.id?.trim() || (item.model ? sanitizeModelId(item.model) : `model_${index + 1}`);
|
|
7181
|
+
if (!item.id?.trim()) {
|
|
7182
|
+
changes.push(`\u5DF2\u8865\u5168 Models[${index}].id -> ${id}`);
|
|
7183
|
+
}
|
|
7184
|
+
if (inferredInterface && !getModelInterface(item)) {
|
|
7185
|
+
changes.push(`\u5DF2\u8865\u5168 Models[${index}].interface -> ${inferredInterface}`);
|
|
7186
|
+
}
|
|
7187
|
+
if (api && item.api !== api) {
|
|
7188
|
+
changes.push(`\u5DF2\u5F52\u4E00 Models[${index}].api`);
|
|
7189
|
+
}
|
|
7190
|
+
if (key && item.key !== key) {
|
|
7191
|
+
changes.push(`\u5DF2\u5F52\u4E00 Models[${index}].key`);
|
|
7192
|
+
}
|
|
7193
|
+
return {
|
|
7194
|
+
...item,
|
|
7195
|
+
id,
|
|
7196
|
+
api: api || void 0,
|
|
7197
|
+
api_base_url: api || void 0,
|
|
7198
|
+
key: key || void 0,
|
|
7199
|
+
api_key: key || void 0,
|
|
7200
|
+
interface: inferredInterface,
|
|
7201
|
+
protocol: inferredInterface
|
|
7202
|
+
};
|
|
7203
|
+
});
|
|
7204
|
+
if (!nextConfig.Router?.default) {
|
|
7205
|
+
if (nextConfig.Models.length === 1) {
|
|
7206
|
+
nextConfig.Router = {
|
|
7207
|
+
...nextConfig.Router ?? {},
|
|
7208
|
+
default: getModelLookupId(nextConfig.Models[0])
|
|
7209
|
+
};
|
|
7210
|
+
changes.push(`\u5DF2\u8865\u5168 Router.default -> ${nextConfig.Router.default}`);
|
|
7211
|
+
} else if (typeof config.Router?.default === "string" && config.Router.default.includes(",")) {
|
|
7212
|
+
const [providerName, modelName] = config.Router.default.split(",").map((item) => item.trim());
|
|
7213
|
+
const matched = nextConfig.Models.find(
|
|
7214
|
+
(item) => item.model === modelName && (item.id === providerName || item.id.startsWith(`${sanitizeModelId(providerName)}_`))
|
|
7215
|
+
);
|
|
7216
|
+
if (matched) {
|
|
7217
|
+
nextConfig.Router = {
|
|
7218
|
+
...nextConfig.Router ?? {},
|
|
7219
|
+
default: matched.id
|
|
7220
|
+
};
|
|
7221
|
+
changes.push(`\u5DF2\u5F52\u4E00 Router.default -> ${matched.id}`);
|
|
7222
|
+
}
|
|
7223
|
+
}
|
|
7224
|
+
}
|
|
7225
|
+
} else if (Array.isArray(config.Providers) && config.Providers.length > 0) {
|
|
7226
|
+
const migrated = migrateLegacyConfig(config);
|
|
7227
|
+
nextConfig.Models = migrated.draft.Models;
|
|
7228
|
+
nextConfig.Router = {
|
|
7229
|
+
...nextConfig.Router ?? {},
|
|
7230
|
+
...migrated.draft.Router
|
|
7231
|
+
};
|
|
7232
|
+
nextConfig.Providers = [];
|
|
7233
|
+
changes.push("\u5DF2\u5C06 legacy Providers \u7ED3\u6784\u5F52\u4E00\u4E3A Models \u7ED3\u6784\u3002");
|
|
7234
|
+
}
|
|
7235
|
+
return { config: nextConfig, changes };
|
|
7236
|
+
}
|
|
7237
|
+
async function completeMissingModelFields(config, io) {
|
|
7238
|
+
const changes = [];
|
|
7239
|
+
const nextConfig = {
|
|
7240
|
+
...config,
|
|
7241
|
+
Models: Array.isArray(config.Models) ? config.Models.map((item) => ({ ...item })) : [],
|
|
7242
|
+
Router: { ...config.Router ?? {} }
|
|
7243
|
+
};
|
|
7244
|
+
for (let index = 0; index < (nextConfig.Models?.length ?? 0); index += 1) {
|
|
7245
|
+
const model = nextConfig.Models[index];
|
|
7246
|
+
const label = model.id || model.model || `Models[${index}]`;
|
|
7247
|
+
if (!model.id?.trim()) {
|
|
7248
|
+
model.id = sanitizeModelId(await io.input(`\u8865\u5168 ${label} \u7684\u6A21\u578B ID`, sanitizeModelId(model.model || `model_${index + 1}`)));
|
|
7249
|
+
changes.push(`\u5DF2\u8865\u5168 ${label} \u7684\u6A21\u578B ID -> ${model.id}`);
|
|
7250
|
+
}
|
|
7251
|
+
if (!getModelApi(model)) {
|
|
7252
|
+
const api = await io.input(`\u8865\u5168 ${label} \u7684 API Base URL`);
|
|
7253
|
+
model.api = api;
|
|
7254
|
+
model.api_base_url = api;
|
|
7255
|
+
changes.push(`\u5DF2\u8865\u5168 ${label} \u7684 API Base URL`);
|
|
7256
|
+
}
|
|
7257
|
+
if (!getModelKey(model)) {
|
|
7258
|
+
const key = await io.input(`\u8865\u5168 ${label} \u7684 API Key`);
|
|
7259
|
+
model.key = key;
|
|
7260
|
+
model.api_key = key;
|
|
7261
|
+
changes.push(`\u5DF2\u8865\u5168 ${label} \u7684 API Key`);
|
|
7262
|
+
}
|
|
7263
|
+
if (!getModelInterface(model)) {
|
|
7264
|
+
const interfaceChoice = await io.choose(`\u8865\u5168 ${label} \u7684\u63A5\u53E3\u7C7B\u578B`, ["openai", "anthropic"]);
|
|
7265
|
+
model.interface = interfaceChoice;
|
|
7266
|
+
model.protocol = model.interface;
|
|
7267
|
+
changes.push(`\u5DF2\u8865\u5168 ${label} \u7684\u63A5\u53E3\u7C7B\u578B -> ${model.interface}`);
|
|
7268
|
+
}
|
|
7269
|
+
if (!model.model?.trim()) {
|
|
7270
|
+
model.model = await io.input(`\u8865\u5168 ${label} \u7684\u4E0A\u6E38\u6A21\u578B\u540D`);
|
|
7271
|
+
changes.push(`\u5DF2\u8865\u5168 ${label} \u7684\u4E0A\u6E38\u6A21\u578B\u540D`);
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
if (!nextConfig.Router?.default) {
|
|
7275
|
+
if ((nextConfig.Models?.length ?? 0) === 1) {
|
|
7276
|
+
nextConfig.Router.default = nextConfig.Models[0].id;
|
|
7277
|
+
changes.push(`\u5DF2\u8865\u5168 Router.default -> ${nextConfig.Router.default}`);
|
|
7278
|
+
} else if ((nextConfig.Models?.length ?? 0) > 1) {
|
|
7279
|
+
const choice = await io.choose("\u8865\u5168\u9ED8\u8BA4\u6A21\u578B", nextConfig.Models.map((item) => item.id));
|
|
7280
|
+
nextConfig.Router.default = choice;
|
|
7281
|
+
changes.push(`\u5DF2\u8865\u5168 Router.default -> ${choice}`);
|
|
7282
|
+
}
|
|
7283
|
+
}
|
|
7284
|
+
return { config: nextConfig, changes };
|
|
7285
|
+
}
|
|
7286
|
+
async function probeModelAvailability(model) {
|
|
7287
|
+
const api = getModelApi(model);
|
|
7288
|
+
const key = getModelKey(model);
|
|
7289
|
+
const modelInterface = getModelInterface(model);
|
|
7290
|
+
if (!api || !key || !modelInterface || !model.model) {
|
|
7291
|
+
return {
|
|
7292
|
+
kind: "failure",
|
|
7293
|
+
category: "protocol_mismatch",
|
|
7294
|
+
message: "\u6A21\u578B\u914D\u7F6E\u7F3A\u5C11 api/key/interface/model\uFF0C\u65E0\u6CD5\u53D1\u8D77\u63A2\u6D4B\u3002"
|
|
7295
|
+
};
|
|
7296
|
+
}
|
|
7297
|
+
try {
|
|
7298
|
+
const registry = buildModelRegistry({
|
|
7299
|
+
Providers: [],
|
|
7300
|
+
Models: [model],
|
|
7301
|
+
Router: {
|
|
7302
|
+
default: model.id
|
|
7303
|
+
}
|
|
7304
|
+
});
|
|
7305
|
+
const compiledModel = registry.modelMap[model.id];
|
|
7306
|
+
const dispatchRequest = compiledModel ? buildProviderDispatchRequest({
|
|
7307
|
+
model: compiledModel.modelName,
|
|
7308
|
+
interface: compiledModel.interface ?? modelInterface,
|
|
7309
|
+
compatibilityProfile: compiledModel.compatibilityProfile,
|
|
7310
|
+
capabilities: compiledModel.capabilities,
|
|
7311
|
+
request: {
|
|
7312
|
+
model: compiledModel.id,
|
|
7313
|
+
max_tokens: 1,
|
|
7314
|
+
stream: true,
|
|
7315
|
+
messages: [
|
|
7316
|
+
{
|
|
7317
|
+
role: "user",
|
|
7318
|
+
content: [
|
|
7319
|
+
{
|
|
7320
|
+
type: "text",
|
|
7321
|
+
text: "ok"
|
|
7322
|
+
}
|
|
7323
|
+
]
|
|
7324
|
+
}
|
|
7325
|
+
]
|
|
7326
|
+
}
|
|
7327
|
+
}) : null;
|
|
7328
|
+
const response = await fetch(api, {
|
|
7329
|
+
method: "POST",
|
|
7330
|
+
signal: AbortSignal.timeout(1e4),
|
|
7331
|
+
headers: modelInterface === "anthropic" ? {
|
|
7332
|
+
"content-type": "application/json",
|
|
7333
|
+
"x-api-key": key,
|
|
7334
|
+
"anthropic-version": "2023-06-01"
|
|
7335
|
+
} : {
|
|
7336
|
+
"content-type": "application/json",
|
|
7337
|
+
authorization: `Bearer ${key}`
|
|
7338
|
+
},
|
|
7339
|
+
body: JSON.stringify(dispatchRequest?.body ?? (modelInterface === "anthropic" ? {
|
|
7340
|
+
model: model.model,
|
|
7341
|
+
max_tokens: 1,
|
|
7342
|
+
stream: true,
|
|
7343
|
+
messages: [
|
|
7344
|
+
{
|
|
7345
|
+
role: "user",
|
|
7346
|
+
content: [
|
|
7347
|
+
{
|
|
7348
|
+
type: "text",
|
|
7349
|
+
text: "ok"
|
|
7350
|
+
}
|
|
7351
|
+
]
|
|
7352
|
+
}
|
|
7353
|
+
]
|
|
7354
|
+
} : {
|
|
7355
|
+
model: model.model,
|
|
7356
|
+
max_tokens: 1,
|
|
7357
|
+
stream: true,
|
|
7358
|
+
messages: [
|
|
7359
|
+
{
|
|
7360
|
+
role: "user",
|
|
7361
|
+
content: "ok"
|
|
7362
|
+
}
|
|
7363
|
+
]
|
|
7364
|
+
}))
|
|
7365
|
+
});
|
|
7366
|
+
if (response.ok) {
|
|
7367
|
+
return { kind: "success" };
|
|
7368
|
+
}
|
|
7369
|
+
const body = await response.text();
|
|
7370
|
+
if (response.status === 401 || response.status === 403) {
|
|
7371
|
+
return { kind: "failure", category: "auth_error", message: `${response.status} ${body}` };
|
|
7372
|
+
}
|
|
7373
|
+
if (response.status === 404) {
|
|
7374
|
+
return { kind: "failure", category: "model_not_found", message: `${response.status} ${body}` };
|
|
7375
|
+
}
|
|
7376
|
+
if (response.status === 400) {
|
|
7377
|
+
return { kind: "failure", category: "protocol_mismatch", message: `${response.status} ${body}` };
|
|
7378
|
+
}
|
|
7379
|
+
return { kind: "failure", category: "remote_error", message: `${response.status} ${body}` };
|
|
7380
|
+
} catch (error) {
|
|
7381
|
+
return {
|
|
7382
|
+
kind: "failure",
|
|
7383
|
+
category: "endpoint_unreachable",
|
|
7384
|
+
message: error?.message || String(error)
|
|
7385
|
+
};
|
|
7386
|
+
}
|
|
7387
|
+
}
|
|
7388
|
+
async function ensureServiceUsable(config, deps, configChanged) {
|
|
7389
|
+
const port = config.PORT ?? DEFAULT_CONFIG2.PORT;
|
|
7390
|
+
const healthy = await deps.probeServiceHealth(port, 500);
|
|
7391
|
+
const occupied = await deps.isTcpPortOccupied(port, 500);
|
|
7392
|
+
const running = deps.isServiceRunning();
|
|
7393
|
+
if (healthy && !configChanged) {
|
|
7394
|
+
deps.io.info(`\u670D\u52A1\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7\uFF1Ahttp://127.0.0.1:${port}`);
|
|
7395
|
+
return;
|
|
7396
|
+
}
|
|
7397
|
+
if (occupied && !healthy && !running) {
|
|
7398
|
+
throw new Error(`\u7AEF\u53E3 ${port} \u5DF2\u88AB\u5176\u4ED6\u670D\u52A1\u5360\u7528\uFF0Cdoctor \u65E0\u6CD5\u81EA\u52A8\u542F\u52A8\u5F53\u524D\u670D\u52A1\u3002`);
|
|
7399
|
+
}
|
|
7400
|
+
if (running) {
|
|
7401
|
+
const info = deps.readServiceInfo();
|
|
7402
|
+
if (info) {
|
|
7403
|
+
try {
|
|
7404
|
+
deps.killProcess(info.pid);
|
|
7405
|
+
} catch {
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
7408
|
+
}
|
|
7409
|
+
await deps.startDaemon();
|
|
7410
|
+
const verified = await deps.waitForService(port, 5e3);
|
|
7411
|
+
if (!verified) {
|
|
7412
|
+
throw new Error(`doctor \u81EA\u52A8\u542F\u52A8\u540E\u5065\u5EB7\u68C0\u67E5\u4ECD\u672A\u901A\u8FC7\uFF08\u7AEF\u53E3 ${port}\uFF09\u3002`);
|
|
7413
|
+
}
|
|
7414
|
+
deps.io.info(`\u670D\u52A1\u5DF2\u5C31\u7EEA\uFF1Ahttp://127.0.0.1:${port}`);
|
|
7415
|
+
}
|
|
7416
|
+
function createDefaultDeps2(io = createConsoleIO2()) {
|
|
7417
|
+
return {
|
|
7418
|
+
readLegacyConfig,
|
|
7419
|
+
backupCurrentConfig: backupConfigFile,
|
|
7420
|
+
writeConfig: writeConfigFile,
|
|
7421
|
+
isServiceRunning,
|
|
7422
|
+
readServiceInfo,
|
|
7423
|
+
killProcess,
|
|
7424
|
+
probeServiceHealth,
|
|
7425
|
+
isTcpPortOccupied,
|
|
7426
|
+
waitForService,
|
|
7427
|
+
io,
|
|
7428
|
+
startDaemon: async () => {
|
|
7429
|
+
(0, import_child_process2.spawn)(process.execPath, [process.argv[1], "start", "--daemon"], {
|
|
7430
|
+
detached: true,
|
|
7431
|
+
stdio: "ignore",
|
|
7432
|
+
env: { ...process.env, CTR_DAEMON: "1" }
|
|
7433
|
+
}).unref();
|
|
7434
|
+
}
|
|
7435
|
+
};
|
|
7436
|
+
}
|
|
7437
|
+
async function runDoctorCli(customDeps) {
|
|
7438
|
+
const defaults = createDefaultDeps2(customDeps?.io);
|
|
7439
|
+
const deps = { ...defaults, ...customDeps };
|
|
7440
|
+
let configChanged = false;
|
|
7441
|
+
try {
|
|
7442
|
+
deps.io.info("\u5F00\u59CB\u8BCA\u65AD\u5F53\u524D Claude Trigger Router \u914D\u7F6E...");
|
|
7443
|
+
const current = loadCurrentConfig();
|
|
7444
|
+
current.messages.forEach((message) => deps.io.info(message));
|
|
7445
|
+
let workingConfig = current.config;
|
|
7446
|
+
if (!workingConfig) {
|
|
7447
|
+
const legacy = await deps.readLegacyConfig();
|
|
7448
|
+
if (legacy.kind === "found") {
|
|
7449
|
+
deps.io.info("\u672A\u68C0\u6D4B\u5230\u5F53\u524D\u914D\u7F6E\uFF0C\u4F46\u53D1\u73B0\u65E7 claude-code-router \u914D\u7F6E\uFF0Cdoctor \u5C06\u5148\u5C1D\u8BD5\u8FC1\u79FB\u3002");
|
|
7450
|
+
const migrated = migrateLegacyConfig(legacy.config);
|
|
7451
|
+
workingConfig = {
|
|
7452
|
+
...buildUsableMinimalTemplateConfig(),
|
|
7453
|
+
...migrated.draft
|
|
7454
|
+
};
|
|
7455
|
+
} else {
|
|
7456
|
+
throw new Error("\u672A\u68C0\u6D4B\u5230\u53EF\u8BCA\u65AD\u7684\u5F53\u524D\u914D\u7F6E\uFF1B\u8BF7\u5148\u8FD0\u884C ctr setup \u6216 ctr init --force\u3002");
|
|
7457
|
+
}
|
|
7458
|
+
}
|
|
7459
|
+
const deterministic = repairDeterministicConfig(workingConfig);
|
|
7460
|
+
workingConfig = deterministic.config;
|
|
7461
|
+
deterministic.changes.forEach((message) => deps.io.info(message));
|
|
7462
|
+
const completed = await completeMissingModelFields(workingConfig, deps.io);
|
|
7463
|
+
workingConfig = completed.config;
|
|
7464
|
+
completed.changes.forEach((message) => deps.io.info(message));
|
|
7465
|
+
const normalized = normalizeAndValidateConfig(workingConfig);
|
|
7466
|
+
if (normalized.errors.length > 0) {
|
|
7467
|
+
deps.io.error(`doctor \u4ECD\u53D1\u73B0\u65E0\u6CD5\u81EA\u52A8\u4FEE\u590D\u7684\u914D\u7F6E\u9519\u8BEF\uFF1A${normalized.errors.join("; ")}`);
|
|
7468
|
+
throw new Error("doctor could not fully repair config");
|
|
7469
|
+
}
|
|
7470
|
+
if (normalized.warnings.length > 0) {
|
|
7471
|
+
deps.io.info(`\u914D\u7F6E\u63D0\u793A\uFF1A${normalized.warnings.join("; ")}`);
|
|
7472
|
+
}
|
|
7473
|
+
const needWrite = current.repairedParse || deterministic.changes.length > 0 || completed.changes.length > 0 || !current.existed;
|
|
7474
|
+
if (needWrite) {
|
|
7475
|
+
if (current.existed) {
|
|
7476
|
+
const backupPath = await deps.backupCurrentConfig();
|
|
7477
|
+
if (backupPath) {
|
|
7478
|
+
deps.io.info(`\u5DF2\u5907\u4EFD\u5F53\u524D\u914D\u7F6E\uFF1A${backupPath}`);
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
await deps.writeConfig(normalized.config);
|
|
7482
|
+
deps.io.info(`\u5DF2\u5199\u56DE\u4FEE\u590D\u540E\u7684\u914D\u7F6E\uFF1A${current.path}`);
|
|
7483
|
+
configChanged = true;
|
|
7484
|
+
}
|
|
7485
|
+
await ensureServiceUsable(normalized.config, deps, configChanged);
|
|
7486
|
+
const shouldProbeModels = hasArg("--check-models") ? await deps.io.confirm(`\u5373\u5C06\u5411 ${normalized.config.Models?.length ?? 0} \u4E2A\u6A21\u578B\u53D1\u9001\u6700\u5C0F\u63A2\u6D4B\u8BF7\u6C42\uFF0C\u53EF\u80FD\u6D88\u8017\u5C11\u91CF\u989D\u5EA6\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F`, true) : await deps.io.confirm(`\u662F\u5426\u7EE7\u7EED\u63A2\u6D4B ${normalized.config.Models?.length ?? 0} \u4E2A\u6A21\u578B\u7684\u53EF\u7528\u6027\uFF1F\u8FD9\u4F1A\u6D88\u8017\u5C11\u91CF\u989D\u5EA6\u3002`, false);
|
|
7487
|
+
if (!shouldProbeModels) {
|
|
7488
|
+
deps.io.info("\u5DF2\u8DF3\u8FC7\u6A21\u578B\u63A2\u6D4B\u3002\u914D\u7F6E\u548C\u670D\u52A1\u8BCA\u65AD\u5DF2\u5B8C\u6210\u3002");
|
|
7489
|
+
return;
|
|
7490
|
+
}
|
|
7491
|
+
for (const model of normalized.config.Models ?? []) {
|
|
7492
|
+
const result = await probeModelAvailability(model);
|
|
7493
|
+
if (result.kind === "success") {
|
|
7494
|
+
deps.io.info(`\u6A21\u578B\u63A2\u6D4B\u6210\u529F\uFF1A${model.id}`);
|
|
7495
|
+
continue;
|
|
7496
|
+
}
|
|
7497
|
+
deps.io.error(`\u6A21\u578B\u63A2\u6D4B\u5931\u8D25\uFF1A${model.id} -> ${result.category} -> ${result.message}`);
|
|
7498
|
+
deps.io.info("\u8FD9\u7C7B\u8FDC\u7AEF\u5931\u8D25\u9700\u8981\u4F60\u786E\u8BA4\u5E76\u624B\u52A8\u5904\u7406\uFF1Bdoctor \u4E0D\u4F1A\u81EA\u52A8\u4FEE\u6539\u6A21\u578B\u8BED\u4E49\u6216\u8FDC\u7AEF\u8D26\u53F7\u914D\u7F6E\u3002");
|
|
7499
|
+
}
|
|
7500
|
+
deps.io.info("doctor \u8BCA\u65AD\u5B8C\u6210\u3002");
|
|
7501
|
+
} finally {
|
|
7502
|
+
deps.io.close?.();
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7505
|
+
var import_fs7, import_promises4, import_process2, import_child_process2, import_json53, import_js_yaml2;
|
|
7506
|
+
var init_doctor = __esm({
|
|
7507
|
+
"src/doctor/index.ts"() {
|
|
7508
|
+
"use strict";
|
|
7509
|
+
import_fs7 = require("fs");
|
|
7510
|
+
import_promises4 = require("readline/promises");
|
|
7511
|
+
import_process2 = require("process");
|
|
7512
|
+
import_child_process2 = require("child_process");
|
|
7513
|
+
import_json53 = __toESM(require("json5"));
|
|
7514
|
+
import_js_yaml2 = __toESM(require("js-yaml"));
|
|
7515
|
+
init_constants();
|
|
7516
|
+
init_utils();
|
|
7517
|
+
init_migrate();
|
|
7518
|
+
init_setup2();
|
|
7519
|
+
init_schema();
|
|
7520
|
+
init_compile();
|
|
7521
|
+
init_protocols();
|
|
7522
|
+
init_processCheck();
|
|
7523
|
+
init_service_health();
|
|
7524
|
+
init_templates();
|
|
7525
|
+
}
|
|
7526
|
+
});
|
|
7527
|
+
|
|
6516
7528
|
// src/cli.ts
|
|
6517
7529
|
var cli_exports = {};
|
|
6518
7530
|
__export(cli_exports, {
|
|
@@ -6522,7 +7534,7 @@ __export(cli_exports, {
|
|
|
6522
7534
|
});
|
|
6523
7535
|
module.exports = __toCommonJS(cli_exports);
|
|
6524
7536
|
function getPackageInfo() {
|
|
6525
|
-
const content = (0,
|
|
7537
|
+
const content = (0, import_fs8.readFileSync)(PACKAGE_JSON_PATH, "utf-8");
|
|
6526
7538
|
const pkg = JSON.parse(content);
|
|
6527
7539
|
return {
|
|
6528
7540
|
name: pkg.name ?? "@peterwangze/claude-trigger-router",
|
|
@@ -6535,7 +7547,7 @@ function getArgs() {
|
|
|
6535
7547
|
function getCommand() {
|
|
6536
7548
|
return getArgs()[0];
|
|
6537
7549
|
}
|
|
6538
|
-
function
|
|
7550
|
+
function hasArg2(flag, shortFlag) {
|
|
6539
7551
|
const args = getArgs();
|
|
6540
7552
|
return args.includes(flag) || (shortFlag ? args.includes(shortFlag) : false);
|
|
6541
7553
|
}
|
|
@@ -6544,23 +7556,34 @@ function getArgValue(flag, shortFlag) {
|
|
|
6544
7556
|
const index = args.indexOf(flag) !== -1 ? args.indexOf(flag) : shortFlag ? args.indexOf(shortFlag) : -1;
|
|
6545
7557
|
return index !== -1 ? args[index + 1] : void 0;
|
|
6546
7558
|
}
|
|
7559
|
+
function parsePortValue(portValue, sourceLabel) {
|
|
7560
|
+
const trimmed = portValue.trim();
|
|
7561
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
7562
|
+
throw new Error(`${sourceLabel} \u4E0D\u662F\u5408\u6CD5\u7AEF\u53E3\uFF1A${portValue}`);
|
|
7563
|
+
}
|
|
7564
|
+
const port = Number.parseInt(trimmed, 10);
|
|
7565
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
7566
|
+
throw new Error(`${sourceLabel} \u8D85\u51FA\u5408\u6CD5\u8303\u56F4\uFF081-65535\uFF09\uFF1A${portValue}`);
|
|
7567
|
+
}
|
|
7568
|
+
return port;
|
|
7569
|
+
}
|
|
6547
7570
|
function getPort() {
|
|
6548
7571
|
const portValue = getArgValue("--port", "-p");
|
|
6549
7572
|
if (portValue) {
|
|
6550
|
-
return
|
|
7573
|
+
return parsePortValue(portValue, "\u547D\u4EE4\u884C\u7AEF\u53E3\u53C2\u6570");
|
|
6551
7574
|
}
|
|
6552
7575
|
try {
|
|
6553
|
-
const
|
|
6554
|
-
if ((0,
|
|
6555
|
-
const content = (0,
|
|
6556
|
-
const config =
|
|
7576
|
+
const yaml4 = require("js-yaml");
|
|
7577
|
+
if ((0, import_fs8.existsSync)(CONFIG_FILE)) {
|
|
7578
|
+
const content = (0, import_fs8.readFileSync)(CONFIG_FILE, "utf-8");
|
|
7579
|
+
const config = yaml4.load(content);
|
|
6557
7580
|
if (config?.PORT) return config.PORT;
|
|
6558
|
-
} else if ((0,
|
|
6559
|
-
const content = (0,
|
|
6560
|
-
const config =
|
|
7581
|
+
} else if ((0, import_fs8.existsSync)(CONFIG_FILE_YML)) {
|
|
7582
|
+
const content = (0, import_fs8.readFileSync)(CONFIG_FILE_YML, "utf-8");
|
|
7583
|
+
const config = yaml4.load(content);
|
|
6561
7584
|
if (config?.PORT) return config.PORT;
|
|
6562
|
-
} else if ((0,
|
|
6563
|
-
const content = (0,
|
|
7585
|
+
} else if ((0, import_fs8.existsSync)(CONFIG_FILE_JSON)) {
|
|
7586
|
+
const content = (0, import_fs8.readFileSync)(CONFIG_FILE_JSON, "utf-8");
|
|
6564
7587
|
const config = JSON.parse(content);
|
|
6565
7588
|
if (config?.PORT) return config.PORT;
|
|
6566
7589
|
}
|
|
@@ -6569,7 +7592,7 @@ function getPort() {
|
|
|
6569
7592
|
return DEFAULT_CONFIG2.PORT;
|
|
6570
7593
|
}
|
|
6571
7594
|
function isDaemonMode() {
|
|
6572
|
-
return
|
|
7595
|
+
return hasArg2("--daemon", "-d");
|
|
6573
7596
|
}
|
|
6574
7597
|
function printHelp() {
|
|
6575
7598
|
console.log(`
|
|
@@ -6579,6 +7602,7 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6579
7602
|
|
|
6580
7603
|
\u547D\u4EE4\uFF1A
|
|
6581
7604
|
setup \u68C0\u6D4B\u5E76\u590D\u7528\u5DF2\u6709\u914D\u7F6E\uFF0C\u5FC5\u8981\u65F6\u8FC1\u79FB\u65E7\u914D\u7F6E\u6216\u65B0\u5EFA\u6700\u5C0F\u914D\u7F6E
|
|
7605
|
+
doctor \u8BCA\u65AD\u5E76\u4FEE\u590D\u5F53\u524D\u914D\u7F6E\uFF0C\u6309\u9700\u63A2\u6D4B\u6A21\u578B\u53EF\u7528\u6027
|
|
6582
7606
|
init \u521D\u59CB\u5316\u6700\u5C0F\u914D\u7F6E\u6A21\u677F
|
|
6583
7607
|
start \u542F\u52A8\u8DEF\u7531\u670D\u52A1\uFF08\u9ED8\u8BA4\u524D\u53F0\u8FD0\u884C\uFF09
|
|
6584
7608
|
stop \u505C\u6B62\u540E\u53F0\u670D\u52A1
|
|
@@ -6587,7 +7611,7 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6587
7611
|
version \u67E5\u770B\u5F53\u524D\u5B89\u88C5\u7248\u672C\u4E0E\u5305\u4FE1\u606F
|
|
6588
7612
|
upgrade \u67E5\u770B\u5347\u7EA7\u5230\u6700\u65B0 npm \u7248\u672C\u7684\u6307\u5F15
|
|
6589
7613
|
code \u901A\u8FC7\u8DEF\u7531\u5668\u8FD0\u884C Claude Code\uFF08\u9700\u5148\u542F\u52A8\u670D\u52A1\uFF09
|
|
6590
|
-
ui \u6253\u5F00\u7BA1\u7406
|
|
7614
|
+
ui \u6253\u5F00\u672C\u5730\u7BA1\u7406\u9875\uFF08\u914D\u7F6E\u9884\u89C8\u4E0E\u8C03\u8BD5\uFF09
|
|
6591
7615
|
help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
|
|
6592
7616
|
|
|
6593
7617
|
\u9009\u9879\uFF1A
|
|
@@ -6597,6 +7621,7 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6597
7621
|
|
|
6598
7622
|
\u4F7F\u7528\u793A\u4F8B\uFF1A
|
|
6599
7623
|
ctr setup # \u590D\u7528\u5F53\u524D\u914D\u7F6E / \u8FC1\u79FB\u65E7\u914D\u7F6E / \u65B0\u5EFA\u6700\u5C0F\u914D\u7F6E
|
|
7624
|
+
ctr doctor # \u8BCA\u65AD\u914D\u7F6E / \u4FEE\u590D\u683C\u5F0F\u95EE\u9898 / \u6309\u9700\u63A2\u6D4B\u6A21\u578B\u53EF\u7528\u6027
|
|
6600
7625
|
ctr init # \u521D\u59CB\u5316\u6700\u5C0F\u914D\u7F6E\u6A21\u677F
|
|
6601
7626
|
ctr version # \u67E5\u770B\u5F53\u524D\u5B89\u88C5\u7248\u672C
|
|
6602
7627
|
ctr upgrade # \u67E5\u770B\u5347\u7EA7\u5230\u6700\u65B0\u7248\u672C\u7684\u547D\u4EE4
|
|
@@ -6604,6 +7629,7 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6604
7629
|
ctr start --daemon # \u540E\u53F0\u542F\u52A8
|
|
6605
7630
|
ctr status # \u67E5\u770B\u670D\u52A1\u72B6\u6001
|
|
6606
7631
|
ctr code # \u542F\u52A8 Claude Code\uFF08\u9700\u5148\u8FD0\u884C ctr start\uFF09
|
|
7632
|
+
ctr ui # \u6253\u5F00\u672C\u5730\u7BA1\u7406\u9875\uFF08\u53EF\u9009\uFF09
|
|
6607
7633
|
ctr stop # \u505C\u6B62\u540E\u53F0\u670D\u52A1
|
|
6608
7634
|
ctr restart --daemon # \u91CD\u542F\u540E\u53F0\u670D\u52A1
|
|
6609
7635
|
|
|
@@ -6613,10 +7639,29 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6613
7639
|
|
|
6614
7640
|
\u914D\u7F6E\u76EE\u5F55\uFF1A${CONFIG_DIR}
|
|
6615
7641
|
|
|
7642
|
+
\u8865\u5145\u8BF4\u660E\uFF1A
|
|
7643
|
+
ctr restart \u5F53\u524D\u9ED8\u8BA4\u6309\u540E\u53F0\u6A21\u5F0F\u91CD\u542F\uFF1B\u53EF\u5199 ctr restart \u6216 ctr restart --daemon
|
|
7644
|
+
|
|
6616
7645
|
\u66F4\u591A\u4FE1\u606F\uFF1Ahttps://github.com/peterwangze/claude-trigger-router
|
|
6617
7646
|
`);
|
|
6618
7647
|
}
|
|
6619
|
-
|
|
7648
|
+
function getLatestPackageVersionViaNpm(packageName, timeoutMs = 5e3) {
|
|
7649
|
+
try {
|
|
7650
|
+
const result = (0, import_child_process3.spawnSync)("npm", ["view", packageName, "version", "--registry", PACKAGE_REGISTRY_URL], {
|
|
7651
|
+
encoding: "utf-8",
|
|
7652
|
+
timeout: timeoutMs,
|
|
7653
|
+
shell: process.platform === "win32"
|
|
7654
|
+
});
|
|
7655
|
+
if (result.status !== 0) {
|
|
7656
|
+
return null;
|
|
7657
|
+
}
|
|
7658
|
+
const value = result.stdout?.trim();
|
|
7659
|
+
return value ? value : null;
|
|
7660
|
+
} catch {
|
|
7661
|
+
return null;
|
|
7662
|
+
}
|
|
7663
|
+
}
|
|
7664
|
+
async function getLatestPackageVersion(packageName, timeoutMs = 4e3) {
|
|
6620
7665
|
try {
|
|
6621
7666
|
const response = await fetch(PACKAGE_REGISTRY_LATEST_URL, {
|
|
6622
7667
|
signal: AbortSignal.timeout(timeoutMs)
|
|
@@ -6625,10 +7670,12 @@ async function getLatestPackageVersion(timeoutMs = 1500) {
|
|
|
6625
7670
|
return null;
|
|
6626
7671
|
}
|
|
6627
7672
|
const payload = await response.json();
|
|
6628
|
-
|
|
7673
|
+
if (typeof payload.version === "string") {
|
|
7674
|
+
return payload.version;
|
|
7675
|
+
}
|
|
6629
7676
|
} catch {
|
|
6630
|
-
return null;
|
|
6631
7677
|
}
|
|
7678
|
+
return getLatestPackageVersionViaNpm(packageName);
|
|
6632
7679
|
}
|
|
6633
7680
|
function isNewerVersion(current, latest) {
|
|
6634
7681
|
const currentParts = current.split(".").map((part) => Number.parseInt(part, 10));
|
|
@@ -6648,7 +7695,7 @@ function isNewerVersion(current, latest) {
|
|
|
6648
7695
|
}
|
|
6649
7696
|
async function printVersion() {
|
|
6650
7697
|
const pkg = getPackageInfo();
|
|
6651
|
-
const latestVersion = await getLatestPackageVersion();
|
|
7698
|
+
const latestVersion = await getLatestPackageVersion(pkg.name);
|
|
6652
7699
|
console.log(`Package: ${pkg.name}`);
|
|
6653
7700
|
console.log(`Version: ${pkg.version}`);
|
|
6654
7701
|
console.log(`Latest: ${latestVersion ?? "unavailable"}`);
|
|
@@ -6668,30 +7715,42 @@ function printUpgradeGuidance() {
|
|
|
6668
7715
|
console.log("\u5168\u5C40\u5B89\u88C5\u5728\u67D0\u4E9B\u73AF\u5883\u4E0B\u53EF\u80FD\u9700\u8981\u7BA1\u7406\u5458/root \u6743\u9650\u3002");
|
|
6669
7716
|
console.log(`NPM: ${PACKAGE_PAGE_URL}`);
|
|
6670
7717
|
}
|
|
7718
|
+
function printRestartGuidanceHint() {
|
|
7719
|
+
console.log("\u8BF4\u660E\uFF1A`ctr restart` \u5F53\u524D\u9ED8\u8BA4\u6309\u540E\u53F0\u6A21\u5F0F\u91CD\u542F\u670D\u52A1\uFF0C`--daemon` \u53EA\u662F\u663E\u5F0F\u5199\u6CD5\u3002");
|
|
7720
|
+
}
|
|
7721
|
+
function isClaudeCommandAvailable(timeoutMs = 3e3) {
|
|
7722
|
+
try {
|
|
7723
|
+
const result = (0, import_child_process3.spawnSync)("claude", ["--version"], {
|
|
7724
|
+
encoding: "utf-8",
|
|
7725
|
+
timeout: timeoutMs,
|
|
7726
|
+
stdio: "ignore",
|
|
7727
|
+
shell: process.platform === "win32"
|
|
7728
|
+
});
|
|
7729
|
+
return result.status === 0;
|
|
7730
|
+
} catch {
|
|
7731
|
+
return false;
|
|
7732
|
+
}
|
|
7733
|
+
}
|
|
6671
7734
|
function initConfig2() {
|
|
6672
|
-
const force =
|
|
6673
|
-
const existingConfig = [CONFIG_FILE, CONFIG_FILE_YML, CONFIG_FILE_JSON].find(
|
|
7735
|
+
const force = hasArg2("--force");
|
|
7736
|
+
const existingConfig = [CONFIG_FILE, CONFIG_FILE_YML, CONFIG_FILE_JSON].find(import_fs8.existsSync);
|
|
6674
7737
|
if (existingConfig && !force) {
|
|
6675
7738
|
console.log(`\u26A0\uFE0F \u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\uFF1A${existingConfig}`);
|
|
6676
7739
|
console.log(" \u5982\u9700\u8986\u76D6\uFF0C\u8BF7\u4F7F\u7528 --force \u53C2\u6570\u3002");
|
|
6677
7740
|
return;
|
|
6678
7741
|
}
|
|
6679
|
-
if (!(0,
|
|
6680
|
-
(0,
|
|
6681
|
-
}
|
|
6682
|
-
const examplePaths = [
|
|
6683
|
-
(0, import_path7.join)(__dirname, "..", "config", "trigger.example.yaml"),
|
|
6684
|
-
(0, import_path7.join)((0, import_path7.dirname)(process.argv[1]), "..", "config", "trigger.example.yaml")
|
|
6685
|
-
];
|
|
6686
|
-
const exampleFile = examplePaths.find((p) => (0, import_fs7.existsSync)(p));
|
|
6687
|
-
if (!exampleFile) {
|
|
6688
|
-
console.error("\u274C \u627E\u4E0D\u5230\u793A\u4F8B\u914D\u7F6E\u6587\u4EF6\u3002");
|
|
6689
|
-
console.log(` \u8BF7\u624B\u52A8\u521B\u5EFA ${CONFIG_FILE}`);
|
|
6690
|
-
console.log(" \u53C2\u8003\u6587\u6863\uFF1Ahttps://github.com/peterwangze/claude-trigger-router#configuration");
|
|
6691
|
-
process.exit(1);
|
|
7742
|
+
if (!(0, import_fs8.existsSync)(CONFIG_DIR)) {
|
|
7743
|
+
(0, import_fs8.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
6692
7744
|
}
|
|
6693
7745
|
try {
|
|
6694
|
-
|
|
7746
|
+
const yaml4 = require("js-yaml");
|
|
7747
|
+
const templateConfig = buildUsableMinimalTemplateConfig();
|
|
7748
|
+
const content = yaml4.dump(templateConfig, {
|
|
7749
|
+
indent: 2,
|
|
7750
|
+
lineWidth: -1,
|
|
7751
|
+
noRefs: true
|
|
7752
|
+
});
|
|
7753
|
+
(0, import_fs8.writeFileSync)(CONFIG_FILE, content, "utf-8");
|
|
6695
7754
|
const action = force ? "\u5DF2\u8986\u76D6" : "\u5DF2\u521B\u5EFA";
|
|
6696
7755
|
console.log(`\u2705 \u914D\u7F6E\u6587\u4EF6${action}\uFF1A${CONFIG_FILE}`);
|
|
6697
7756
|
console.log("");
|
|
@@ -6707,10 +7766,22 @@ function initConfig2() {
|
|
|
6707
7766
|
}
|
|
6708
7767
|
}
|
|
6709
7768
|
async function startForeground(port) {
|
|
7769
|
+
const targetPort = port ?? getPort();
|
|
7770
|
+
const healthy = await waitForService(targetPort, 500);
|
|
7771
|
+
const occupied = await isTcpPortOccupied(targetPort, 500);
|
|
7772
|
+
if (healthy && occupied && isServiceRunning()) {
|
|
7773
|
+
console.log(`\u2705 Service is already running on port ${targetPort}.`);
|
|
7774
|
+
console.log(" Use 'ctr status' to inspect it or 'ctr stop' before starting again.");
|
|
7775
|
+
return;
|
|
7776
|
+
}
|
|
7777
|
+
if (!healthy && occupied && !isServiceRunning()) {
|
|
7778
|
+
console.error(`\u274C Port ${targetPort} is already occupied by another service.`);
|
|
7779
|
+
process.exit(1);
|
|
7780
|
+
}
|
|
6710
7781
|
console.log("\u{1F680} Starting Claude Trigger Router (foreground)...");
|
|
6711
7782
|
console.log(" Press Ctrl+C to stop");
|
|
6712
7783
|
try {
|
|
6713
|
-
await run({ port });
|
|
7784
|
+
await run({ port: targetPort });
|
|
6714
7785
|
} catch (error) {
|
|
6715
7786
|
if (error.message?.includes("Invalid configuration")) {
|
|
6716
7787
|
console.error("\n\u274C Configuration error. Run 'ctr init' to create a config file.");
|
|
@@ -6720,7 +7791,14 @@ async function startForeground(port) {
|
|
|
6720
7791
|
process.exit(1);
|
|
6721
7792
|
}
|
|
6722
7793
|
}
|
|
6723
|
-
function startDaemon(port) {
|
|
7794
|
+
async function startDaemon(port) {
|
|
7795
|
+
const targetPort = port ?? getPort();
|
|
7796
|
+
const healthy = await waitForService(targetPort, 500);
|
|
7797
|
+
const occupied = await isTcpPortOccupied(targetPort, 500);
|
|
7798
|
+
if (!healthy && occupied && !isServiceRunning()) {
|
|
7799
|
+
console.log(`\u274C Port ${targetPort} is already occupied by another service.`);
|
|
7800
|
+
return;
|
|
7801
|
+
}
|
|
6724
7802
|
if (isServiceRunning()) {
|
|
6725
7803
|
console.log("\u2705 Service is already running in the background.");
|
|
6726
7804
|
return;
|
|
@@ -6731,30 +7809,58 @@ function startDaemon(port) {
|
|
|
6731
7809
|
if (port) {
|
|
6732
7810
|
childArgs.push("--port", String(port));
|
|
6733
7811
|
}
|
|
6734
|
-
const child = (0,
|
|
7812
|
+
const child = (0, import_child_process3.spawn)(nodeExec, childArgs, {
|
|
6735
7813
|
detached: true,
|
|
6736
7814
|
stdio: "ignore",
|
|
6737
7815
|
env: { ...process.env, CTR_DAEMON: "1" }
|
|
6738
7816
|
});
|
|
6739
7817
|
child.unref();
|
|
6740
|
-
const
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
7818
|
+
const startConfirmed = await new Promise((resolve, reject) => {
|
|
7819
|
+
let settled = false;
|
|
7820
|
+
const deadline = Date.now() + 5e3;
|
|
7821
|
+
const finish = (value) => {
|
|
7822
|
+
if (settled) {
|
|
7823
|
+
return;
|
|
7824
|
+
}
|
|
7825
|
+
settled = true;
|
|
7826
|
+
resolve(value);
|
|
7827
|
+
};
|
|
7828
|
+
child.once("error", reject);
|
|
7829
|
+
child.once("exit", () => finish(false));
|
|
7830
|
+
const poll = () => {
|
|
7831
|
+
if (settled) {
|
|
7832
|
+
return;
|
|
7833
|
+
}
|
|
7834
|
+
if (isServiceRunning()) {
|
|
7835
|
+
finish(true);
|
|
7836
|
+
return;
|
|
7837
|
+
}
|
|
7838
|
+
if (Date.now() >= deadline) {
|
|
7839
|
+
finish(false);
|
|
7840
|
+
return;
|
|
7841
|
+
}
|
|
7842
|
+
setTimeout(poll, 250);
|
|
7843
|
+
};
|
|
7844
|
+
poll();
|
|
7845
|
+
});
|
|
7846
|
+
if (!startConfirmed) {
|
|
7847
|
+
console.error(`\u274C Service failed to start in background (port: ${targetPort}).`);
|
|
7848
|
+
console.error(" Run 'ctr start' (without --daemon) to inspect the startup error.");
|
|
7849
|
+
process.exit(1);
|
|
7850
|
+
}
|
|
7851
|
+
console.log(`\u2705 Service started in background (port: ${targetPort})`);
|
|
7852
|
+
console.log(` Run 'ctr stop' to stop it.`);
|
|
7853
|
+
}
|
|
7854
|
+
async function showStatus() {
|
|
6756
7855
|
const info = readServiceInfo();
|
|
6757
7856
|
if (!info || !isServiceRunning()) {
|
|
7857
|
+
const targetPort = getPort();
|
|
7858
|
+
const healthy = await waitForService(targetPort, 500);
|
|
7859
|
+
const occupied = await isTcpPortOccupied(targetPort, 500);
|
|
7860
|
+
if (!healthy && occupied) {
|
|
7861
|
+
console.log(`\u26A0\uFE0F \u7AEF\u53E3 ${targetPort} \u5DF2\u88AB\u5176\u4ED6\u670D\u52A1\u5360\u7528\uFF0C\u5F53\u524D\u4E0D\u662F claude-trigger-router\u3002`);
|
|
7862
|
+
return;
|
|
7863
|
+
}
|
|
6758
7864
|
console.log("\u23F9 \u670D\u52A1\u672A\u8FD0\u884C");
|
|
6759
7865
|
return;
|
|
6760
7866
|
}
|
|
@@ -6779,9 +7885,10 @@ function stopService() {
|
|
|
6779
7885
|
console.error("\u274C \u505C\u6B62\u670D\u52A1\u5931\u8D25:", error.message);
|
|
6780
7886
|
}
|
|
6781
7887
|
}
|
|
6782
|
-
function restartService() {
|
|
7888
|
+
async function restartService() {
|
|
6783
7889
|
stopService();
|
|
6784
|
-
|
|
7890
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
7891
|
+
await startDaemon(getPort());
|
|
6785
7892
|
}
|
|
6786
7893
|
async function runClaudeCode() {
|
|
6787
7894
|
const port = getPort();
|
|
@@ -6796,14 +7903,16 @@ async function runClaudeCode() {
|
|
|
6796
7903
|
console.log(" 1. Start service first: ctr start --daemon");
|
|
6797
7904
|
console.log(" 2. Or start interactively in another terminal: ctr start");
|
|
6798
7905
|
console.log("");
|
|
6799
|
-
|
|
6800
|
-
if (!proceed) {
|
|
6801
|
-
process.exit(1);
|
|
6802
|
-
}
|
|
7906
|
+
process.exit(1);
|
|
6803
7907
|
}
|
|
6804
7908
|
console.log(`\u{1F680} Starting Claude Code with Trigger Router (port: ${port})...`);
|
|
7909
|
+
if (!isClaudeCommandAvailable()) {
|
|
7910
|
+
console.error("\u274C \u672A\u68C0\u6D4B\u5230 Claude Code CLI\u3002");
|
|
7911
|
+
console.log(" \u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -g @anthropic-ai/claude-code");
|
|
7912
|
+
process.exit(1);
|
|
7913
|
+
}
|
|
6805
7914
|
const isWindows = process.platform === "win32";
|
|
6806
|
-
const claude = (0,
|
|
7915
|
+
const claude = (0, import_child_process3.spawn)("claude", [], {
|
|
6807
7916
|
stdio: "inherit",
|
|
6808
7917
|
shell: isWindows,
|
|
6809
7918
|
env: {
|
|
@@ -6822,10 +7931,18 @@ async function runClaudeCode() {
|
|
|
6822
7931
|
process.exit(code || 0);
|
|
6823
7932
|
});
|
|
6824
7933
|
}
|
|
6825
|
-
function openUI() {
|
|
7934
|
+
async function openUI() {
|
|
6826
7935
|
const port = getPort();
|
|
6827
7936
|
const url = `http://127.0.0.1:${port}/ui`;
|
|
7937
|
+
const healthy = await waitForService(port, 800);
|
|
6828
7938
|
console.log(`\u{1F310} Opening UI at ${url}`);
|
|
7939
|
+
if (!healthy) {
|
|
7940
|
+
console.log("\u26A0\uFE0F \u5F53\u524D UI \u670D\u52A1\u672A\u5C31\u7EEA\uFF1B\u5982\u679C\u9875\u9762\u65E0\u6CD5\u6253\u5F00\uFF0C\u8BF7\u5148\u8FD0\u884C ctr start \u6216 ctr start --daemon\u3002");
|
|
7941
|
+
}
|
|
7942
|
+
if (process.env.CTR_UI_SKIP_OPEN === "1") {
|
|
7943
|
+
console.log(" Browser launch skipped by CTR_UI_SKIP_OPEN=1");
|
|
7944
|
+
return;
|
|
7945
|
+
}
|
|
6829
7946
|
try {
|
|
6830
7947
|
(0, import_openurl.default)(url);
|
|
6831
7948
|
} catch (error) {
|
|
@@ -6838,12 +7955,15 @@ async function main() {
|
|
|
6838
7955
|
case "setup":
|
|
6839
7956
|
await runSetupCli();
|
|
6840
7957
|
break;
|
|
7958
|
+
case "doctor":
|
|
7959
|
+
await runDoctorCli();
|
|
7960
|
+
break;
|
|
6841
7961
|
case "init":
|
|
6842
7962
|
initConfig2();
|
|
6843
7963
|
break;
|
|
6844
7964
|
case "start":
|
|
6845
7965
|
if (isDaemonMode()) {
|
|
6846
|
-
startDaemon(getPort());
|
|
7966
|
+
await startDaemon(getPort());
|
|
6847
7967
|
} else {
|
|
6848
7968
|
await startForeground(getPort());
|
|
6849
7969
|
}
|
|
@@ -6852,7 +7972,7 @@ async function main() {
|
|
|
6852
7972
|
stopService();
|
|
6853
7973
|
break;
|
|
6854
7974
|
case "status":
|
|
6855
|
-
showStatus();
|
|
7975
|
+
await showStatus();
|
|
6856
7976
|
break;
|
|
6857
7977
|
case "version":
|
|
6858
7978
|
await printVersion();
|
|
@@ -6861,13 +7981,14 @@ async function main() {
|
|
|
6861
7981
|
printUpgradeGuidance();
|
|
6862
7982
|
break;
|
|
6863
7983
|
case "restart":
|
|
6864
|
-
|
|
7984
|
+
printRestartGuidanceHint();
|
|
7985
|
+
await restartService();
|
|
6865
7986
|
break;
|
|
6866
7987
|
case "code":
|
|
6867
7988
|
await runClaudeCode();
|
|
6868
7989
|
break;
|
|
6869
7990
|
case "ui":
|
|
6870
|
-
openUI();
|
|
7991
|
+
await openUI();
|
|
6871
7992
|
break;
|
|
6872
7993
|
case "help":
|
|
6873
7994
|
case "--help":
|
|
@@ -6882,21 +8003,24 @@ async function main() {
|
|
|
6882
8003
|
process.exit(command ? 1 : 0);
|
|
6883
8004
|
}
|
|
6884
8005
|
}
|
|
6885
|
-
var
|
|
8006
|
+
var import_child_process3, import_path7, import_openurl, import_fs8, PACKAGE_JSON_PATH, PACKAGE_PAGE_URL, PACKAGE_REGISTRY_LATEST_URL, PACKAGE_REGISTRY_URL;
|
|
6886
8007
|
var init_cli = __esm({
|
|
6887
8008
|
"src/cli.ts"() {
|
|
6888
|
-
|
|
8009
|
+
import_child_process3 = require("child_process");
|
|
6889
8010
|
import_path7 = require("path");
|
|
6890
8011
|
import_openurl = __toESM(require("openurl"));
|
|
6891
|
-
|
|
8012
|
+
import_fs8 = require("fs");
|
|
6892
8013
|
init_index();
|
|
6893
8014
|
init_processCheck();
|
|
6894
8015
|
init_constants();
|
|
6895
8016
|
init_service_health();
|
|
6896
8017
|
init_setup2();
|
|
8018
|
+
init_templates();
|
|
8019
|
+
init_doctor();
|
|
6897
8020
|
PACKAGE_JSON_PATH = (0, import_path7.join)(__dirname, "..", "package.json");
|
|
6898
8021
|
PACKAGE_PAGE_URL = "https://www.npmjs.com/package/@peterwangze/claude-trigger-router";
|
|
6899
8022
|
PACKAGE_REGISTRY_LATEST_URL = "https://registry.npmjs.org/@peterwangze%2Fclaude-trigger-router/latest";
|
|
8023
|
+
PACKAGE_REGISTRY_URL = "https://registry.npmjs.org/";
|
|
6900
8024
|
if (process.env.CTR_SKIP_MAIN !== "1") {
|
|
6901
8025
|
main().catch((error) => {
|
|
6902
8026
|
console.error("Error:", error);
|