@peterwangze/claude-trigger-router 1.0.3 → 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 +4 -191
- package/dist/cli.js +1494 -363
- package/dist/cli.js.map +4 -4
- package/package.json +9 -1
package/dist/cli.js
CHANGED
|
@@ -32,7 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
34
|
// src/constants.ts
|
|
35
|
-
var import_os, import_path, CONFIG_DIR, CONFIG_FILE, CONFIG_FILE_JSON, CONFIG_FILE_YML, HOME_DIR, PID_FILE, GOVERNANCE_TRACE_FILE, GOVERNANCE_TRACE_ARCHIVE_DIR, GOVERNANCE_EXPORT_HISTORY_FILE, GOVERNANCE_SNAPSHOT_DIR, GOVERNANCE_SCHEDULE_FILE,
|
|
35
|
+
var import_os, import_path, CONFIG_DIR, CONFIG_FILE, CONFIG_FILE_JSON, CONFIG_FILE_YML, HOME_DIR, PID_FILE, GOVERNANCE_TRACE_FILE, GOVERNANCE_TRACE_ARCHIVE_DIR, GOVERNANCE_EXPORT_HISTORY_FILE, GOVERNANCE_SNAPSHOT_DIR, GOVERNANCE_SCHEDULE_FILE, DEFAULT_CONFIG2, DEFAULT_TRIGGER_CONFIG, DEFAULT_SMART_ROUTER_CONFIG, DEFAULT_GOVERNANCE_CONFIG;
|
|
36
36
|
var init_constants = __esm({
|
|
37
37
|
"src/constants.ts"() {
|
|
38
38
|
"use strict";
|
|
@@ -49,9 +49,9 @@ var init_constants = __esm({
|
|
|
49
49
|
GOVERNANCE_EXPORT_HISTORY_FILE = (0, import_path.join)(CONFIG_DIR, "governance-metrics-export-history.json");
|
|
50
50
|
GOVERNANCE_SNAPSHOT_DIR = (0, import_path.join)(CONFIG_DIR, "governance-metric-snapshots");
|
|
51
51
|
GOVERNANCE_SCHEDULE_FILE = (0, import_path.join)(CONFIG_DIR, "governance-metric-schedules.json");
|
|
52
|
-
|
|
52
|
+
DEFAULT_CONFIG2 = {
|
|
53
53
|
HOST: "127.0.0.1",
|
|
54
|
-
PORT:
|
|
54
|
+
PORT: 5678,
|
|
55
55
|
LOG: true,
|
|
56
56
|
LOG_LEVEL: "debug",
|
|
57
57
|
API_TIMEOUT_MS: 6e5,
|
|
@@ -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)) {
|
|
@@ -775,7 +826,7 @@ function validateConfig(config) {
|
|
|
775
826
|
function normalizeAndValidateConfig(config = {}) {
|
|
776
827
|
const normalizedConfig = deepMerge(
|
|
777
828
|
{
|
|
778
|
-
...
|
|
829
|
+
...DEFAULT_CONFIG2,
|
|
779
830
|
Router: {
|
|
780
831
|
default: ""
|
|
781
832
|
},
|
|
@@ -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;
|
|
@@ -1404,6 +1485,7 @@ var CONTEXT_ALIGNMENT_PROMPT, ContextAlignmentService, contextAlignmentService;
|
|
|
1404
1485
|
var init_context_alignment = __esm({
|
|
1405
1486
|
"src/governance/context-alignment.ts"() {
|
|
1406
1487
|
"use strict";
|
|
1488
|
+
init_constants();
|
|
1407
1489
|
init_log();
|
|
1408
1490
|
init_message_ir();
|
|
1409
1491
|
init_anthropic();
|
|
@@ -1432,7 +1514,7 @@ Do not include markdown fences.`;
|
|
|
1432
1514
|
buildPrompt(text, previousModel, nextModel) {
|
|
1433
1515
|
return CONTEXT_ALIGNMENT_PROMPT.replace("{previousModel}", previousModel).replace("{nextModel}", nextModel).replace("{request}", text);
|
|
1434
1516
|
}
|
|
1435
|
-
async summarizeTransition(text, previousModel, nextModel, config, port =
|
|
1517
|
+
async summarizeTransition(text, previousModel, nextModel, config, port = DEFAULT_CONFIG2.PORT, fetchFn, apiKey, timeoutMs) {
|
|
1436
1518
|
if (!config.enabled || !config.summarizer_model || !text.trim()) {
|
|
1437
1519
|
return null;
|
|
1438
1520
|
}
|
|
@@ -1656,6 +1738,7 @@ var SemanticRouter, semanticRouter;
|
|
|
1656
1738
|
var init_semantic_router = __esm({
|
|
1657
1739
|
"src/governance/semantic-router.ts"() {
|
|
1658
1740
|
"use strict";
|
|
1741
|
+
init_constants();
|
|
1659
1742
|
init_log();
|
|
1660
1743
|
init_message_ir();
|
|
1661
1744
|
init_anthropic();
|
|
@@ -1714,7 +1797,7 @@ Return JSON only:
|
|
|
1714
1797
|
analyze(text, config) {
|
|
1715
1798
|
return this.analyzeEmbedding(text, config);
|
|
1716
1799
|
}
|
|
1717
|
-
async analyzeWithClassifier(text, config, port =
|
|
1800
|
+
async analyzeWithClassifier(text, config, port = DEFAULT_CONFIG2.PORT, fetchFn, apiKey, timeoutMs) {
|
|
1718
1801
|
if (!config?.enabled || config.mode !== "classifier" || !config.prototypes || Object.keys(config.prototypes).length === 0) {
|
|
1719
1802
|
return this.analyzeEmbedding(text, config);
|
|
1720
1803
|
}
|
|
@@ -1778,6 +1861,7 @@ var SHADOW_VERIFIER_PROMPT, ShadowSupervisor, shadowSupervisor;
|
|
|
1778
1861
|
var init_shadow_supervisor = __esm({
|
|
1779
1862
|
"src/governance/shadow-supervisor.ts"() {
|
|
1780
1863
|
"use strict";
|
|
1864
|
+
init_constants();
|
|
1781
1865
|
init_log();
|
|
1782
1866
|
init_message_ir();
|
|
1783
1867
|
init_anthropic();
|
|
@@ -1852,7 +1936,7 @@ If no issue is found, return:
|
|
|
1852
1936
|
}
|
|
1853
1937
|
});
|
|
1854
1938
|
}
|
|
1855
|
-
async inspectWithVerifier(payload, config, port =
|
|
1939
|
+
async inspectWithVerifier(payload, config, port = DEFAULT_CONFIG2.PORT, fetchFn, apiKey, timeoutMs) {
|
|
1856
1940
|
const text = extractText(payload).trim();
|
|
1857
1941
|
if (!config.enabled || !config.verifier_model || !text) {
|
|
1858
1942
|
return this.inspect(payload, config);
|
|
@@ -2118,17 +2202,17 @@ var init_SSEParser_transform = __esm({
|
|
|
2118
2202
|
|
|
2119
2203
|
// src/governance/stream-response-governance.ts
|
|
2120
2204
|
function serializeEvent(event2) {
|
|
2121
|
-
let
|
|
2205
|
+
let output3 = "";
|
|
2122
2206
|
if (event2.event) {
|
|
2123
|
-
|
|
2207
|
+
output3 += `event: ${event2.event}
|
|
2124
2208
|
`;
|
|
2125
2209
|
}
|
|
2126
2210
|
if (event2.data !== void 0) {
|
|
2127
|
-
|
|
2211
|
+
output3 += `data: ${typeof event2.data === "string" ? event2.data : JSON.stringify(event2.data)}
|
|
2128
2212
|
`;
|
|
2129
2213
|
}
|
|
2130
|
-
|
|
2131
|
-
return new TextEncoder().encode(
|
|
2214
|
+
output3 += "\n";
|
|
2215
|
+
return new TextEncoder().encode(output3);
|
|
2132
2216
|
}
|
|
2133
2217
|
async function collectSSE(stream) {
|
|
2134
2218
|
const parser = new SSEParserTransform();
|
|
@@ -2238,17 +2322,17 @@ var init_stream_response_governance = __esm({
|
|
|
2238
2322
|
});
|
|
2239
2323
|
|
|
2240
2324
|
// src/governance/metrics.ts
|
|
2241
|
-
function normalizeAnomalyThresholds(
|
|
2325
|
+
function normalizeAnomalyThresholds(input3) {
|
|
2242
2326
|
return {
|
|
2243
|
-
minSampleSize:
|
|
2244
|
-
cascadeWarnRate:
|
|
2245
|
-
cascadeCriticalRate:
|
|
2246
|
-
shadowWarnRate:
|
|
2247
|
-
shadowCriticalRate:
|
|
2248
|
-
latencyWarnMs:
|
|
2249
|
-
latencyCriticalMs:
|
|
2250
|
-
spikeWarnRate:
|
|
2251
|
-
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
|
|
2252
2336
|
};
|
|
2253
2337
|
}
|
|
2254
2338
|
function rate(count, total) {
|
|
@@ -3259,7 +3343,7 @@ var init_server = __esm({
|
|
|
3259
3343
|
server.app.post("/api/restart", async (req, reply) => {
|
|
3260
3344
|
reply.send({ success: true, message: "Service restart initiated" });
|
|
3261
3345
|
setTimeout(() => {
|
|
3262
|
-
const { spawn:
|
|
3346
|
+
const { spawn: spawn3 } = require("child_process");
|
|
3263
3347
|
const { join: join8 } = require("path");
|
|
3264
3348
|
const cliPath = join8(__dirname, "cli.js");
|
|
3265
3349
|
const currentPort = config.initialConfig?.PORT;
|
|
@@ -3267,7 +3351,7 @@ var init_server = __esm({
|
|
|
3267
3351
|
if (currentPort) {
|
|
3268
3352
|
restartArgs.push("--port", String(currentPort));
|
|
3269
3353
|
}
|
|
3270
|
-
|
|
3354
|
+
spawn3(process.execPath, restartArgs, {
|
|
3271
3355
|
detached: true,
|
|
3272
3356
|
stdio: "ignore"
|
|
3273
3357
|
}).unref();
|
|
@@ -3527,7 +3611,7 @@ function isServiceRunning() {
|
|
|
3527
3611
|
function savePid(pid, port) {
|
|
3528
3612
|
const info = {
|
|
3529
3613
|
pid,
|
|
3530
|
-
port: port ??
|
|
3614
|
+
port: port ?? DEFAULT_CONFIG2.PORT,
|
|
3531
3615
|
startTime: (/* @__PURE__ */ new Date()).toISOString()
|
|
3532
3616
|
};
|
|
3533
3617
|
(0, import_fs4.writeFileSync)(PID_FILE, JSON.stringify(info, null, 2), "utf-8");
|
|
@@ -3537,7 +3621,7 @@ function readServiceInfo() {
|
|
|
3537
3621
|
try {
|
|
3538
3622
|
const content = (0, import_fs4.readFileSync)(PID_FILE, "utf-8").trim();
|
|
3539
3623
|
if (/^\d+$/.test(content)) {
|
|
3540
|
-
return { pid: parseInt(content, 10), port:
|
|
3624
|
+
return { pid: parseInt(content, 10), port: DEFAULT_CONFIG2.PORT, startTime: "" };
|
|
3541
3625
|
}
|
|
3542
3626
|
return JSON.parse(content);
|
|
3543
3627
|
} catch {
|
|
@@ -3573,18 +3657,18 @@ var init_SSESerializer_transform = __esm({
|
|
|
3573
3657
|
constructor() {
|
|
3574
3658
|
const transformStream = new TransformStream({
|
|
3575
3659
|
transform: (event2, controller) => {
|
|
3576
|
-
let
|
|
3660
|
+
let output3 = "";
|
|
3577
3661
|
if (event2.event) {
|
|
3578
|
-
|
|
3662
|
+
output3 += `event: ${event2.event}
|
|
3579
3663
|
`;
|
|
3580
3664
|
}
|
|
3581
3665
|
if (event2.data) {
|
|
3582
3666
|
const dataStr = typeof event2.data === "string" ? event2.data : JSON.stringify(event2.data);
|
|
3583
|
-
|
|
3667
|
+
output3 += `data: ${dataStr}
|
|
3584
3668
|
`;
|
|
3585
3669
|
}
|
|
3586
|
-
|
|
3587
|
-
controller.enqueue(new TextEncoder().encode(
|
|
3670
|
+
output3 += "\n";
|
|
3671
|
+
controller.enqueue(new TextEncoder().encode(output3));
|
|
3588
3672
|
}
|
|
3589
3673
|
});
|
|
3590
3674
|
this.readable = transformStream.readable;
|
|
@@ -4130,6 +4214,7 @@ var init_intent = __esm({
|
|
|
4130
4214
|
"src/trigger/intent.ts"() {
|
|
4131
4215
|
"use strict";
|
|
4132
4216
|
import_lru_cache6 = require("lru-cache");
|
|
4217
|
+
init_constants();
|
|
4133
4218
|
init_log();
|
|
4134
4219
|
intentCache = new import_lru_cache6.LRUCache({
|
|
4135
4220
|
max: 500,
|
|
@@ -4198,7 +4283,7 @@ Important: Respond ONLY with the JSON, no additional text.`;
|
|
|
4198
4283
|
* @param fetchFn fetch 函数(用于发起 API 请求)
|
|
4199
4284
|
* @returns 意图识别结果
|
|
4200
4285
|
*/
|
|
4201
|
-
async detectIntent(text, config, port =
|
|
4286
|
+
async detectIntent(text, config, port = DEFAULT_CONFIG2.PORT, fetchFn, apiKey, timeoutMs) {
|
|
4202
4287
|
if (!config.llm_intent_recognition) {
|
|
4203
4288
|
return {
|
|
4204
4289
|
intent: "general",
|
|
@@ -4286,6 +4371,7 @@ var init_smart_router = __esm({
|
|
|
4286
4371
|
"src/trigger/smart-router.ts"() {
|
|
4287
4372
|
"use strict";
|
|
4288
4373
|
import_lru_cache7 = require("lru-cache");
|
|
4374
|
+
init_constants();
|
|
4289
4375
|
init_log();
|
|
4290
4376
|
init_message_ir();
|
|
4291
4377
|
init_anthropic();
|
|
@@ -4349,11 +4435,11 @@ Important:
|
|
|
4349
4435
|
*
|
|
4350
4436
|
* @param text 请求文本
|
|
4351
4437
|
* @param config SmartRouter 配置
|
|
4352
|
-
* @param port 本地服务端口(默认
|
|
4438
|
+
* @param port 本地服务端口(默认 5678)
|
|
4353
4439
|
* @param fetchFn 可注入的 fetch 函数(用于测试)
|
|
4354
4440
|
* @returns 选择结果,失败时返回 null
|
|
4355
4441
|
*/
|
|
4356
|
-
async selectModel(text, config, port =
|
|
4442
|
+
async selectModel(text, config, port = DEFAULT_CONFIG2.PORT, fetchFn, apiKey, timeoutMs) {
|
|
4357
4443
|
if (!config.enabled) {
|
|
4358
4444
|
return null;
|
|
4359
4445
|
}
|
|
@@ -4372,6 +4458,7 @@ Important:
|
|
|
4372
4458
|
method: "POST",
|
|
4373
4459
|
headers: {
|
|
4374
4460
|
"Content-Type": "application/json",
|
|
4461
|
+
"x-ctr-smart-router": "1",
|
|
4375
4462
|
...apiKey ? { "x-api-key": apiKey } : {}
|
|
4376
4463
|
},
|
|
4377
4464
|
body: JSON.stringify(
|
|
@@ -4428,6 +4515,7 @@ var init_selector = __esm({
|
|
|
4428
4515
|
init_intent();
|
|
4429
4516
|
init_smart_router();
|
|
4430
4517
|
init_log();
|
|
4518
|
+
init_constants();
|
|
4431
4519
|
init_session_store();
|
|
4432
4520
|
init_semantic_router();
|
|
4433
4521
|
init_compile();
|
|
@@ -4484,7 +4572,7 @@ var init_selector = __esm({
|
|
|
4484
4572
|
* @param config 触发配置
|
|
4485
4573
|
* @returns 分析结果
|
|
4486
4574
|
*/
|
|
4487
|
-
async selectModel(req, config, port =
|
|
4575
|
+
async selectModel(req, config, port = DEFAULT_CONFIG2.PORT, smartRouterConfig, governanceConfig, apiKey, timeoutMs) {
|
|
4488
4576
|
const startTime = Date.now();
|
|
4489
4577
|
const appConfig = req.appConfig;
|
|
4490
4578
|
if (!config.enabled) {
|
|
@@ -4683,10 +4771,11 @@ var init_trigger = __esm({
|
|
|
4683
4771
|
init_selector();
|
|
4684
4772
|
init_analyzer();
|
|
4685
4773
|
init_log();
|
|
4774
|
+
init_constants();
|
|
4686
4775
|
TriggerRouter = class {
|
|
4687
4776
|
config = null;
|
|
4688
4777
|
appConfig = null;
|
|
4689
|
-
port =
|
|
4778
|
+
port = DEFAULT_CONFIG2.PORT;
|
|
4690
4779
|
smartRouterConfig = void 0;
|
|
4691
4780
|
governanceConfig = void 0;
|
|
4692
4781
|
apiKey;
|
|
@@ -4699,7 +4788,7 @@ var init_trigger = __esm({
|
|
|
4699
4788
|
init(appConfig) {
|
|
4700
4789
|
this.appConfig = appConfig;
|
|
4701
4790
|
this.config = appConfig.TriggerRouter || this.getDefaultConfig();
|
|
4702
|
-
this.port = appConfig.PORT ||
|
|
4791
|
+
this.port = appConfig.PORT || DEFAULT_CONFIG2.PORT;
|
|
4703
4792
|
this.smartRouterConfig = appConfig.SmartRouter;
|
|
4704
4793
|
this.governanceConfig = appConfig.Governance;
|
|
4705
4794
|
this.apiKey = appConfig.APIKEY;
|
|
@@ -4882,6 +4971,15 @@ function toOpenAIToolResultMessages(parts) {
|
|
|
4882
4971
|
content: typeof part.content === "string" ? part.content : JSON.stringify(part.content)
|
|
4883
4972
|
}));
|
|
4884
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
|
+
}
|
|
4885
4983
|
function toOpenAITools(tools) {
|
|
4886
4984
|
if (!Array.isArray(tools) || !tools.length) {
|
|
4887
4985
|
return void 0;
|
|
@@ -4889,9 +4987,9 @@ function toOpenAITools(tools) {
|
|
|
4889
4987
|
return tools.map((tool) => ({
|
|
4890
4988
|
type: "function",
|
|
4891
4989
|
function: {
|
|
4892
|
-
name: tool
|
|
4893
|
-
description: tool
|
|
4894
|
-
parameters: tool
|
|
4990
|
+
name: getToolName(tool),
|
|
4991
|
+
description: getToolDescription(tool),
|
|
4992
|
+
parameters: getToolInputSchema(tool)
|
|
4895
4993
|
}
|
|
4896
4994
|
}));
|
|
4897
4995
|
}
|
|
@@ -4918,10 +5016,10 @@ function toOpenAIToolChoice(toolChoice) {
|
|
|
4918
5016
|
}
|
|
4919
5017
|
return toolChoice;
|
|
4920
5018
|
}
|
|
4921
|
-
function toOpenAIChatRequest(
|
|
5019
|
+
function toOpenAIChatRequest(input3) {
|
|
4922
5020
|
const messages = [
|
|
4923
|
-
...
|
|
4924
|
-
...
|
|
5021
|
+
...input3.ir.system.map((text) => ({ role: "system", content: text })),
|
|
5022
|
+
...input3.ir.messages.flatMap((message) => {
|
|
4925
5023
|
const content = toOpenAIContent(message.parts);
|
|
4926
5024
|
const toolCalls = message.role === "assistant" ? toOpenAIToolCalls(message.parts) : [];
|
|
4927
5025
|
const toolResults = toOpenAIToolResultMessages(message.parts);
|
|
@@ -4940,26 +5038,26 @@ function toOpenAIChatRequest(input2) {
|
|
|
4940
5038
|
})
|
|
4941
5039
|
];
|
|
4942
5040
|
const body = {
|
|
4943
|
-
model:
|
|
5041
|
+
model: input3.model,
|
|
4944
5042
|
messages
|
|
4945
5043
|
};
|
|
4946
|
-
if (
|
|
4947
|
-
body.max_completion_tokens =
|
|
5044
|
+
if (input3.max_completion_tokens !== void 0) {
|
|
5045
|
+
body.max_completion_tokens = input3.max_completion_tokens;
|
|
4948
5046
|
}
|
|
4949
|
-
if (
|
|
4950
|
-
body.stream =
|
|
5047
|
+
if (input3.stream !== void 0) {
|
|
5048
|
+
body.stream = input3.stream;
|
|
4951
5049
|
}
|
|
4952
|
-
const tools = toOpenAITools(
|
|
5050
|
+
const tools = toOpenAITools(input3.tools);
|
|
4953
5051
|
if (tools) {
|
|
4954
5052
|
body.tools = tools;
|
|
4955
5053
|
}
|
|
4956
|
-
const toolChoice = toOpenAIToolChoice(
|
|
5054
|
+
const toolChoice = toOpenAIToolChoice(input3.tool_choice);
|
|
4957
5055
|
if (toolChoice !== void 0) {
|
|
4958
5056
|
body.tool_choice = toolChoice;
|
|
4959
5057
|
}
|
|
4960
|
-
if (
|
|
5058
|
+
if (input3.ir.options?.thinking?.enabled) {
|
|
4961
5059
|
body.reasoning = {
|
|
4962
|
-
...
|
|
5060
|
+
...input3.ir.options.thinking.effort ? { effort: input3.ir.options.thinking.effort } : {}
|
|
4963
5061
|
};
|
|
4964
5062
|
}
|
|
4965
5063
|
return body;
|
|
@@ -4984,19 +5082,19 @@ function stringifyFallbackContent(value) {
|
|
|
4984
5082
|
return String(value);
|
|
4985
5083
|
}
|
|
4986
5084
|
}
|
|
4987
|
-
function applyCapabilityFallbacks(
|
|
5085
|
+
function applyCapabilityFallbacks(input3) {
|
|
4988
5086
|
const diagnostics = [];
|
|
4989
|
-
const nextRequest = { ...
|
|
5087
|
+
const nextRequest = { ...input3.request };
|
|
4990
5088
|
const nextIR = {
|
|
4991
|
-
...
|
|
4992
|
-
system: [...
|
|
4993
|
-
messages:
|
|
5089
|
+
...input3.ir,
|
|
5090
|
+
system: [...input3.ir.system],
|
|
5091
|
+
messages: input3.ir.messages.map((message) => ({
|
|
4994
5092
|
...message,
|
|
4995
5093
|
parts: message.parts.map((part) => ({ ...part }))
|
|
4996
5094
|
})),
|
|
4997
|
-
options:
|
|
5095
|
+
options: input3.ir.options ? { ...input3.ir.options } : void 0
|
|
4998
5096
|
};
|
|
4999
|
-
if (
|
|
5097
|
+
if (input3.capabilities?.thinking.supported === false && nextIR.options?.thinking) {
|
|
5000
5098
|
diagnostics.push("thinking_ignored");
|
|
5001
5099
|
delete nextIR.options.thinking;
|
|
5002
5100
|
delete nextRequest.thinking;
|
|
@@ -5007,7 +5105,7 @@ function applyCapabilityFallbacks(input2) {
|
|
|
5007
5105
|
const hasImageParts = nextIR.messages.some(
|
|
5008
5106
|
(message) => message.parts.some((part) => part.type === "image")
|
|
5009
5107
|
);
|
|
5010
|
-
if (
|
|
5108
|
+
if (input3.capabilities?.images === false && hasImageParts) {
|
|
5011
5109
|
diagnostics.push("images_text_fallback");
|
|
5012
5110
|
nextIR.messages = nextIR.messages.map((message) => ({
|
|
5013
5111
|
...message,
|
|
@@ -5027,7 +5125,7 @@ function applyCapabilityFallbacks(input2) {
|
|
|
5027
5125
|
const hasToolParts = nextIR.messages.some(
|
|
5028
5126
|
(message) => message.parts.some((part) => part.type === "tool_call" || part.type === "tool_result")
|
|
5029
5127
|
);
|
|
5030
|
-
if (
|
|
5128
|
+
if (input3.capabilities?.tools === false && (Array.isArray(nextRequest.tools) && nextRequest.tools.length || hasToolParts)) {
|
|
5031
5129
|
diagnostics.push("tools_text_fallback");
|
|
5032
5130
|
delete nextRequest.tools;
|
|
5033
5131
|
delete nextRequest.tool_choice;
|
|
@@ -5073,53 +5171,58 @@ function omitRequestFields(body) {
|
|
|
5073
5171
|
} = body;
|
|
5074
5172
|
return rest;
|
|
5075
5173
|
}
|
|
5076
|
-
function
|
|
5174
|
+
function buildProviderDispatchRequestFromIR(input3) {
|
|
5077
5175
|
const fallback = applyCapabilityFallbacks({
|
|
5078
|
-
ir:
|
|
5079
|
-
request:
|
|
5080
|
-
capabilities:
|
|
5176
|
+
ir: input3.ir,
|
|
5177
|
+
request: input3.request,
|
|
5178
|
+
capabilities: input3.capabilities
|
|
5081
5179
|
});
|
|
5082
5180
|
const passthrough = omitRequestFields(fallback.request);
|
|
5083
|
-
|
|
5181
|
+
const dispatchFormat = getDispatchFormatForProfile(input3.interface, input3.compatibilityProfile);
|
|
5182
|
+
if (dispatchFormat === "openai_chat") {
|
|
5084
5183
|
return {
|
|
5184
|
+
dispatchFormat,
|
|
5085
5185
|
diagnostics: fallback.diagnostics,
|
|
5086
5186
|
...passthrough,
|
|
5087
|
-
...
|
|
5088
|
-
model:
|
|
5089
|
-
|
|
5187
|
+
...toOpenAIChatRequest({
|
|
5188
|
+
model: input3.model,
|
|
5189
|
+
max_completion_tokens: fallback.request.max_tokens ?? fallback.request.max_completion_tokens,
|
|
5090
5190
|
stream: fallback.request.stream,
|
|
5091
|
-
metadata: fallback.request.metadata,
|
|
5092
5191
|
tools: fallback.request.tools,
|
|
5192
|
+
tool_choice: fallback.request.tool_choice,
|
|
5093
5193
|
ir: fallback.ir
|
|
5094
5194
|
})
|
|
5095
5195
|
};
|
|
5096
5196
|
}
|
|
5097
5197
|
return {
|
|
5198
|
+
dispatchFormat,
|
|
5098
5199
|
diagnostics: fallback.diagnostics,
|
|
5099
5200
|
...passthrough,
|
|
5100
|
-
...
|
|
5101
|
-
model:
|
|
5102
|
-
|
|
5201
|
+
...toAnthropicMessagesRequest({
|
|
5202
|
+
model: input3.model,
|
|
5203
|
+
max_tokens: fallback.request.max_tokens,
|
|
5103
5204
|
stream: fallback.request.stream,
|
|
5205
|
+
metadata: fallback.request.metadata,
|
|
5104
5206
|
tools: fallback.request.tools,
|
|
5105
|
-
tool_choice: fallback.request.tool_choice,
|
|
5106
5207
|
ir: fallback.ir
|
|
5107
5208
|
})
|
|
5108
5209
|
};
|
|
5109
5210
|
}
|
|
5110
|
-
function
|
|
5111
|
-
const ir = createMessageIR(
|
|
5112
|
-
const { diagnostics, ...body } =
|
|
5113
|
-
model:
|
|
5114
|
-
interface:
|
|
5115
|
-
|
|
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,
|
|
5116
5218
|
ir,
|
|
5117
|
-
capabilities:
|
|
5219
|
+
capabilities: input3.capabilities
|
|
5118
5220
|
});
|
|
5119
5221
|
return {
|
|
5120
5222
|
ir,
|
|
5121
5223
|
body,
|
|
5122
|
-
diagnostics
|
|
5224
|
+
diagnostics,
|
|
5225
|
+
dispatchFormat
|
|
5123
5226
|
};
|
|
5124
5227
|
}
|
|
5125
5228
|
var init_protocols = __esm({
|
|
@@ -5128,6 +5231,7 @@ var init_protocols = __esm({
|
|
|
5128
5231
|
init_message_ir();
|
|
5129
5232
|
init_anthropic();
|
|
5130
5233
|
init_openai();
|
|
5234
|
+
init_compile();
|
|
5131
5235
|
}
|
|
5132
5236
|
});
|
|
5133
5237
|
|
|
@@ -5171,7 +5275,7 @@ async function run(options = {}) {
|
|
|
5171
5275
|
HOST = "127.0.0.1";
|
|
5172
5276
|
logWarn("\u26A0\uFE0F API key is not set. HOST is forced to 127.0.0.1.");
|
|
5173
5277
|
}
|
|
5174
|
-
const port = options.port ?? config.PORT ??
|
|
5278
|
+
const port = options.port ?? config.PORT ?? DEFAULT_CONFIG.PORT;
|
|
5175
5279
|
savePid(process.pid, port);
|
|
5176
5280
|
process.on("SIGINT", () => {
|
|
5177
5281
|
log("Received SIGINT, cleaning up...");
|
|
@@ -5195,7 +5299,7 @@ async function run(options = {}) {
|
|
|
5195
5299
|
const hour = pad(date.getHours());
|
|
5196
5300
|
const minute = pad(date.getMinutes());
|
|
5197
5301
|
const seconds = pad(date.getSeconds());
|
|
5198
|
-
return
|
|
5302
|
+
return `ctr-${month}${day}${hour}${minute}${seconds}${index ? `_${index}` : ""}.log`;
|
|
5199
5303
|
};
|
|
5200
5304
|
const loggerConfig = config.LOG !== false ? {
|
|
5201
5305
|
level: config.LOG_LEVEL || "debug",
|
|
@@ -5206,10 +5310,11 @@ async function run(options = {}) {
|
|
|
5206
5310
|
compress: "gzip"
|
|
5207
5311
|
})
|
|
5208
5312
|
} : false;
|
|
5313
|
+
const registry = buildModelRegistry(config);
|
|
5209
5314
|
const server = createServer({
|
|
5210
|
-
|
|
5315
|
+
useJsonFile: false,
|
|
5211
5316
|
initialConfig: {
|
|
5212
|
-
providers:
|
|
5317
|
+
providers: registry.providers,
|
|
5213
5318
|
HOST,
|
|
5214
5319
|
PORT: servicePort,
|
|
5215
5320
|
LOG_FILE: (0, import_path5.join)(
|
|
@@ -5245,9 +5350,10 @@ async function run(options = {}) {
|
|
|
5245
5350
|
initialModel: req.body?.model
|
|
5246
5351
|
});
|
|
5247
5352
|
appendTraceReason(req.governanceTrace, "request_received");
|
|
5248
|
-
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);
|
|
5249
5355
|
req.triggerResult = triggerResult;
|
|
5250
|
-
if (triggerResult.matched && triggerResult.model) {
|
|
5356
|
+
if (!bypassTriggerRouter && triggerResult.matched && triggerResult.model) {
|
|
5251
5357
|
const previousSessionState = req.sessionId ? sessionStateStore.get(req.sessionId) : void 0;
|
|
5252
5358
|
const previousModel = previousSessionState?.lastSuccessfulModel;
|
|
5253
5359
|
const alignmentConfig = config.Governance?.sticky?.alignment;
|
|
@@ -5312,9 +5418,10 @@ async function run(options = {}) {
|
|
|
5312
5418
|
const compiledModel = getCompiledModelRef(config, req.body?.model);
|
|
5313
5419
|
if (compiledModel?.interface && req.body?.messages) {
|
|
5314
5420
|
const originalBody = cloneRequestBody(req.body);
|
|
5315
|
-
const upstream =
|
|
5421
|
+
const upstream = buildProviderDispatchRequest({
|
|
5316
5422
|
model: compiledModel.modelName,
|
|
5317
5423
|
interface: compiledModel.interface,
|
|
5424
|
+
compatibilityProfile: compiledModel.compatibilityProfile,
|
|
5318
5425
|
request: originalBody,
|
|
5319
5426
|
capabilities: compiledModel.capabilities
|
|
5320
5427
|
});
|
|
@@ -5501,7 +5608,7 @@ async function run(options = {}) {
|
|
|
5501
5608
|
event.emit("onSend", req, reply, payload);
|
|
5502
5609
|
return payload;
|
|
5503
5610
|
});
|
|
5504
|
-
server.start();
|
|
5611
|
+
await server.start();
|
|
5505
5612
|
}
|
|
5506
5613
|
var import_fs5, import_promises2, import_os2, import_path5, import_json5, import_node_events, import_rotating_file_stream, event;
|
|
5507
5614
|
var init_index = __esm({
|
|
@@ -5535,32 +5642,32 @@ var init_index = __esm({
|
|
|
5535
5642
|
});
|
|
5536
5643
|
|
|
5537
5644
|
// src/setup/service.ts
|
|
5538
|
-
function decideServiceAction(
|
|
5539
|
-
if (
|
|
5645
|
+
function decideServiceAction(input3) {
|
|
5646
|
+
if (input3.detectedService.kind === "non_self_occupied") {
|
|
5540
5647
|
throw new Error("target port is occupied by another service");
|
|
5541
5648
|
}
|
|
5542
|
-
if (
|
|
5649
|
+
if (input3.detectedService.kind === "none") {
|
|
5543
5650
|
return { kind: "start" };
|
|
5544
5651
|
}
|
|
5545
|
-
if (
|
|
5652
|
+
if (input3.detectedService.kind === "self_unhealthy") {
|
|
5546
5653
|
return { kind: "restart" };
|
|
5547
5654
|
}
|
|
5548
|
-
if (
|
|
5549
|
-
return
|
|
5655
|
+
if (input3.configChanged && input3.detectedService.kind === "self_healthy") {
|
|
5656
|
+
return input3.reloadSupported ? { kind: "reload" } : { kind: "restart" };
|
|
5550
5657
|
}
|
|
5551
5658
|
return { kind: "reuse" };
|
|
5552
5659
|
}
|
|
5553
|
-
async function applyServiceAction(
|
|
5554
|
-
if (
|
|
5555
|
-
await
|
|
5660
|
+
async function applyServiceAction(input3) {
|
|
5661
|
+
if (input3.action.kind === "start") {
|
|
5662
|
+
await input3.executeStart();
|
|
5556
5663
|
}
|
|
5557
|
-
if (
|
|
5558
|
-
await
|
|
5664
|
+
if (input3.action.kind === "reload") {
|
|
5665
|
+
await input3.executeReload();
|
|
5559
5666
|
}
|
|
5560
|
-
if (
|
|
5561
|
-
await
|
|
5667
|
+
if (input3.action.kind === "restart") {
|
|
5668
|
+
await input3.executeRestart();
|
|
5562
5669
|
}
|
|
5563
|
-
const healthy = await
|
|
5670
|
+
const healthy = await input3.verifyHealth();
|
|
5564
5671
|
if (!healthy) {
|
|
5565
5672
|
throw new Error("service health check failed");
|
|
5566
5673
|
}
|
|
@@ -5610,9 +5717,12 @@ function inferProtocolFromApiBaseUrl(apiBaseUrl) {
|
|
|
5610
5717
|
}
|
|
5611
5718
|
return "openai";
|
|
5612
5719
|
}
|
|
5720
|
+
function normalizeSegment(value) {
|
|
5721
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
5722
|
+
}
|
|
5613
5723
|
function toModelId(name, model, index) {
|
|
5614
|
-
const normalizedName = name
|
|
5615
|
-
const normalizedModel = model
|
|
5724
|
+
const normalizedName = normalizeSegment(name) || `provider_${index + 1}`;
|
|
5725
|
+
const normalizedModel = normalizeSegment(model);
|
|
5616
5726
|
return normalizedModel ? `${normalizedName}_${normalizedModel}` : normalizedName;
|
|
5617
5727
|
}
|
|
5618
5728
|
function createEmptyDraft() {
|
|
@@ -5627,66 +5737,176 @@ function createNonMigratableResult() {
|
|
|
5627
5737
|
draft: createEmptyDraft(),
|
|
5628
5738
|
skippedFields: [],
|
|
5629
5739
|
needsCompletion: true,
|
|
5630
|
-
missingFields: ["defaultModel", "apiKey"]
|
|
5740
|
+
missingFields: ["defaultModel", "apiKey", "apiBaseUrl"]
|
|
5631
5741
|
};
|
|
5632
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
|
+
}
|
|
5633
5757
|
function isLegacyProviderInput(value) {
|
|
5634
5758
|
return typeof value === "object" && value !== null;
|
|
5635
5759
|
}
|
|
5636
|
-
function
|
|
5637
|
-
|
|
5638
|
-
|
|
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);
|
|
5639
5766
|
}
|
|
5640
|
-
|
|
5641
|
-
|
|
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;
|
|
5642
5778
|
}
|
|
5643
5779
|
const skippedFields = [];
|
|
5644
|
-
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) => {
|
|
5645
5792
|
if (provider.transformer !== void 0) {
|
|
5646
|
-
skippedFields
|
|
5793
|
+
pushUnique(skippedFields, `${providerKey}[${index}].transformer`);
|
|
5794
|
+
}
|
|
5795
|
+
if (provider.headers !== void 0) {
|
|
5796
|
+
pushUnique(skippedFields, `${providerKey}[${index}].headers`);
|
|
5647
5797
|
}
|
|
5648
5798
|
return {
|
|
5649
5799
|
name: provider.name ?? "",
|
|
5650
5800
|
api_base_url: provider.api_base_url,
|
|
5651
5801
|
api_key: provider.api_key ?? "",
|
|
5652
|
-
models: Array.isArray(provider.models) ? provider.models : []
|
|
5802
|
+
models: Array.isArray(provider.models) ? provider.models : [],
|
|
5803
|
+
vendor_hint: inferVendorHintFromLegacyProvider(provider)
|
|
5653
5804
|
};
|
|
5654
5805
|
});
|
|
5655
|
-
|
|
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(
|
|
5656
5846
|
(provider, providerIndex) => (provider.models.length ? provider.models : [""]).map((model) => ({
|
|
5657
|
-
|
|
5847
|
+
candidateId: toModelId(provider.name, model, providerIndex),
|
|
5658
5848
|
api: provider.api_base_url,
|
|
5659
5849
|
api_base_url: provider.api_base_url,
|
|
5660
5850
|
key: provider.api_key,
|
|
5661
5851
|
api_key: provider.api_key,
|
|
5662
5852
|
interface: inferProtocolFromApiBaseUrl(provider.api_base_url),
|
|
5663
5853
|
protocol: inferProtocolFromApiBaseUrl(provider.api_base_url),
|
|
5664
|
-
model
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
const
|
|
5671
|
-
const
|
|
5672
|
-
const
|
|
5673
|
-
|
|
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;
|
|
5674
5890
|
})() : void 0;
|
|
5675
|
-
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);
|
|
5676
5893
|
const missingFields = [];
|
|
5677
|
-
if (!
|
|
5894
|
+
if (!defaultModelId) {
|
|
5678
5895
|
missingFields.push("defaultModel");
|
|
5679
5896
|
}
|
|
5680
5897
|
if (hasMissingApiKey) {
|
|
5681
5898
|
missingFields.push("apiKey");
|
|
5682
5899
|
}
|
|
5900
|
+
if (hasMissingApiBaseUrl) {
|
|
5901
|
+
missingFields.push("apiBaseUrl");
|
|
5902
|
+
}
|
|
5683
5903
|
return {
|
|
5684
5904
|
draft: {
|
|
5685
5905
|
Providers: [],
|
|
5686
5906
|
Models: models,
|
|
5687
|
-
Router:
|
|
5907
|
+
Router: defaultModelId ? { default: defaultModelId } : {}
|
|
5688
5908
|
},
|
|
5689
|
-
skippedFields,
|
|
5909
|
+
skippedFields: normalized.skippedFields,
|
|
5690
5910
|
needsCompletion: missingFields.length > 0,
|
|
5691
5911
|
missingFields
|
|
5692
5912
|
};
|
|
@@ -5751,11 +5971,12 @@ function getProviderPreset2(key) {
|
|
|
5751
5971
|
api: preset.api,
|
|
5752
5972
|
api_base_url: preset.api_base_url,
|
|
5753
5973
|
interface: preset.interface,
|
|
5754
|
-
protocol: preset.protocol
|
|
5974
|
+
protocol: preset.protocol,
|
|
5975
|
+
default_thinking: preset.default_thinking
|
|
5755
5976
|
};
|
|
5756
5977
|
}
|
|
5757
|
-
function buildMinimalConfig(
|
|
5758
|
-
const providers =
|
|
5978
|
+
function buildMinimalConfig(input3) {
|
|
5979
|
+
const providers = input3.providers;
|
|
5759
5980
|
const models = providers.map((p) => {
|
|
5760
5981
|
const preset = p.preset ? getProviderPreset2(p.preset) : void 0;
|
|
5761
5982
|
const modelDraft = {
|
|
@@ -5775,15 +5996,15 @@ function buildMinimalConfig(input2) {
|
|
|
5775
5996
|
}
|
|
5776
5997
|
return modelDraft;
|
|
5777
5998
|
});
|
|
5778
|
-
let defaultModel =
|
|
5779
|
-
if (
|
|
5780
|
-
const [providerName, modelName] =
|
|
5999
|
+
let defaultModel = input3.defaultModel?.trim();
|
|
6000
|
+
if (input3.defaultModel && input3.defaultModel.includes(",")) {
|
|
6001
|
+
const [providerName, modelName] = input3.defaultModel.split(",");
|
|
5781
6002
|
const matched = models.find((item) => item.id === providerName && item.model === modelName);
|
|
5782
6003
|
if (matched) {
|
|
5783
6004
|
defaultModel = matched.id;
|
|
5784
6005
|
}
|
|
5785
6006
|
}
|
|
5786
|
-
if (
|
|
6007
|
+
if (input3.defaultModel === void 0 && models.length > 0) {
|
|
5787
6008
|
const firstModelId = models[0].id;
|
|
5788
6009
|
if (firstModelId && models[0].model) {
|
|
5789
6010
|
defaultModel = firstModelId;
|
|
@@ -5797,31 +6018,68 @@ function buildMinimalConfig(input2) {
|
|
|
5797
6018
|
Router: defaultModel ? { default: defaultModel } : {}
|
|
5798
6019
|
};
|
|
5799
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
|
+
}
|
|
5800
6057
|
var init_templates = __esm({
|
|
5801
6058
|
"src/setup/templates.ts"() {
|
|
5802
6059
|
"use strict";
|
|
6060
|
+
init_constants();
|
|
5803
6061
|
init_provider_presets();
|
|
5804
6062
|
}
|
|
5805
6063
|
});
|
|
5806
6064
|
|
|
5807
6065
|
// src/setup/persist.ts
|
|
5808
|
-
async function persistSetupConfig(
|
|
5809
|
-
const errors =
|
|
6066
|
+
async function persistSetupConfig(input3) {
|
|
6067
|
+
const errors = input3.validateConfig(input3.config);
|
|
5810
6068
|
if (errors.length > 0) {
|
|
5811
6069
|
throw new Error("config validation failed");
|
|
5812
6070
|
}
|
|
5813
6071
|
let backupPath;
|
|
5814
|
-
if (
|
|
5815
|
-
const createdBackupPath = await
|
|
6072
|
+
if (input3.hasExistingConfig) {
|
|
6073
|
+
const createdBackupPath = await input3.backupCurrentConfig();
|
|
5816
6074
|
if (!createdBackupPath) {
|
|
5817
6075
|
throw new Error("failed to back up existing config");
|
|
5818
6076
|
}
|
|
5819
6077
|
backupPath = createdBackupPath;
|
|
5820
6078
|
}
|
|
5821
|
-
await
|
|
6079
|
+
await input3.writeConfig(input3.config);
|
|
5822
6080
|
return {
|
|
5823
6081
|
configChanged: true,
|
|
5824
|
-
configPath:
|
|
6082
|
+
configPath: input3.currentConfigPath,
|
|
5825
6083
|
backupPath
|
|
5826
6084
|
};
|
|
5827
6085
|
}
|
|
@@ -5870,8 +6128,8 @@ function ensureLegacyFlow(detection, legacyConfigAction) {
|
|
|
5870
6128
|
function invalidAction() {
|
|
5871
6129
|
return invalidCurrentAction();
|
|
5872
6130
|
}
|
|
5873
|
-
function decideSetupBranch(
|
|
5874
|
-
const { detection, currentConfigAction, legacyConfigAction } =
|
|
6131
|
+
function decideSetupBranch(input3) {
|
|
6132
|
+
const { detection, currentConfigAction, legacyConfigAction } = input3;
|
|
5875
6133
|
if (currentConfigAction === "cancel") {
|
|
5876
6134
|
ensureNoLegacyAction(legacyConfigAction);
|
|
5877
6135
|
return { kind: "cancelled" };
|
|
@@ -5924,6 +6182,28 @@ function getTargetConfigPath(detection) {
|
|
|
5924
6182
|
}
|
|
5925
6183
|
return CONFIG_FILE;
|
|
5926
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
|
+
}
|
|
5927
6207
|
async function runSetup(deps) {
|
|
5928
6208
|
const detection = await deps.detectSetupEnvironment();
|
|
5929
6209
|
const currentConfigAction = await deps.chooseCurrentConfigAction({
|
|
@@ -5931,7 +6211,7 @@ async function runSetup(deps) {
|
|
|
5931
6211
|
legacyConfig: detection.legacyConfig
|
|
5932
6212
|
});
|
|
5933
6213
|
let legacyConfigAction;
|
|
5934
|
-
if (currentConfigAction === "create" || currentConfigAction === "overwrite") {
|
|
6214
|
+
if (currentConfigAction === "create" || currentConfigAction === "overwrite" || currentConfigAction === "fresh") {
|
|
5935
6215
|
if (detection.legacyConfig.kind === "found" || detection.legacyConfig.kind === "read_error") {
|
|
5936
6216
|
legacyConfigAction = await deps.chooseLegacyConfigAction({
|
|
5937
6217
|
legacyConfig: detection.legacyConfig
|
|
@@ -6005,6 +6285,16 @@ async function runSetup(deps) {
|
|
|
6005
6285
|
throw new Error("migrate_legacy requires legacy config");
|
|
6006
6286
|
}
|
|
6007
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
|
+
}
|
|
6008
6298
|
let finalDraft = migrated.draft;
|
|
6009
6299
|
if (migrated.needsCompletion) {
|
|
6010
6300
|
finalDraft = await deps.completeDraft({
|
|
@@ -6039,6 +6329,96 @@ var init_setup = __esm({
|
|
|
6039
6329
|
|
|
6040
6330
|
// src/setup/index.ts
|
|
6041
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
|
+
}
|
|
6042
6422
|
const rl = (0, import_promises3.createInterface)({ input: import_process.stdin, output: import_process.stdout });
|
|
6043
6423
|
const ask = async (message) => {
|
|
6044
6424
|
const answer = await rl.question(message);
|
|
@@ -6073,6 +6453,9 @@ function createConsoleIO() {
|
|
|
6073
6453
|
info(message) {
|
|
6074
6454
|
import_process.stdout.write(`${message}
|
|
6075
6455
|
`);
|
|
6456
|
+
},
|
|
6457
|
+
close() {
|
|
6458
|
+
rl.close();
|
|
6076
6459
|
}
|
|
6077
6460
|
};
|
|
6078
6461
|
}
|
|
@@ -6083,14 +6466,79 @@ function readStructuredConfigFile(filePath) {
|
|
|
6083
6466
|
}
|
|
6084
6467
|
return import_js_yaml.default.load(content);
|
|
6085
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
|
+
}
|
|
6086
6533
|
async function readLegacyConfig(deps = {}) {
|
|
6087
6534
|
const baseHomeDir = deps.homeDir || (0, import_os3.homedir)();
|
|
6088
6535
|
const exists = deps.exists || import_fs6.existsSync;
|
|
6089
|
-
const readConfig = deps.readConfig ||
|
|
6536
|
+
const readConfig = deps.readConfig || readLegacyConfigFile;
|
|
6090
6537
|
const overridePath = process.env.CTR_SETUP_LEGACY_CONFIG_PATH;
|
|
6091
6538
|
const candidatePaths = overridePath ? [overridePath] : [
|
|
6092
6539
|
(0, import_path6.join)(baseHomeDir, ".ccr", "config.yaml"),
|
|
6093
|
-
(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")
|
|
6094
6542
|
];
|
|
6095
6543
|
const legacyPath = candidatePaths.find((filePath) => exists(filePath));
|
|
6096
6544
|
if (!legacyPath) {
|
|
@@ -6133,16 +6581,30 @@ async function readCurrentConfig() {
|
|
|
6133
6581
|
}
|
|
6134
6582
|
}
|
|
6135
6583
|
async function probeService() {
|
|
6136
|
-
const
|
|
6137
|
-
|
|
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 };
|
|
6138
6594
|
}
|
|
6139
6595
|
async function enterClaudeCode() {
|
|
6596
|
+
if (process.env.CTR_SETUP_SKIP_ENTER_CODE === "1") {
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6140
6599
|
const cliModule = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
6141
6600
|
await cliModule.runClaudeCode();
|
|
6142
6601
|
}
|
|
6602
|
+
function shouldAutoEnterClaudeCodeAfterSetup() {
|
|
6603
|
+
return process.env.CTR_SETUP_AUTO_ENTER_CODE === "1";
|
|
6604
|
+
}
|
|
6143
6605
|
async function executeStart() {
|
|
6144
6606
|
const childProcess = await import("child_process");
|
|
6145
|
-
childProcess.spawn(process.execPath, [process.argv[1], "start"
|
|
6607
|
+
childProcess.spawn(process.execPath, [process.argv[1], "start"], {
|
|
6146
6608
|
detached: true,
|
|
6147
6609
|
stdio: "ignore",
|
|
6148
6610
|
env: { ...process.env, CTR_DAEMON: "1" }
|
|
@@ -6332,12 +6794,12 @@ async function buildFreshConfig(io) {
|
|
|
6332
6794
|
}
|
|
6333
6795
|
return draft;
|
|
6334
6796
|
}
|
|
6335
|
-
async function completeDraft(
|
|
6336
|
-
const draft = toDraftFromConfig(
|
|
6337
|
-
if (
|
|
6797
|
+
async function completeDraft(input3) {
|
|
6798
|
+
const draft = toDraftFromConfig(input3.draft);
|
|
6799
|
+
if (input3.fields.includes("defaultModel")) {
|
|
6338
6800
|
const defaultProvider = draft.Models?.[0]?.id ?? draft.Providers?.[0]?.name ?? "provider";
|
|
6339
6801
|
const defaultModel = draft.Models?.[0]?.model ?? draft.Providers?.[0]?.models?.[0] ?? "";
|
|
6340
|
-
const model = await
|
|
6802
|
+
const model = await input3.io.input("\u9ED8\u8BA4\u6A21\u578B", defaultModel);
|
|
6341
6803
|
if (draft.Models?.[0]) {
|
|
6342
6804
|
draft.Models[0].model = model;
|
|
6343
6805
|
draft.Router.default = defaultProvider;
|
|
@@ -6346,16 +6808,16 @@ async function completeDraft(input2) {
|
|
|
6346
6808
|
draft.Router.default = `${defaultProvider},${model}`;
|
|
6347
6809
|
}
|
|
6348
6810
|
}
|
|
6349
|
-
if (
|
|
6350
|
-
const apiKey = await
|
|
6811
|
+
if (input3.fields.includes("apiKey")) {
|
|
6812
|
+
const apiKey = await input3.io.input("API Key");
|
|
6351
6813
|
if (draft.Models?.length) {
|
|
6352
6814
|
draft.Models = draft.Models.map((model) => ({ ...model, key: model.key || apiKey, api_key: model.api_key || apiKey }));
|
|
6353
6815
|
} else {
|
|
6354
6816
|
draft.Providers = draft.Providers?.map((provider) => ({ ...provider, api_key: provider.api_key || apiKey }));
|
|
6355
6817
|
}
|
|
6356
6818
|
}
|
|
6357
|
-
if (
|
|
6358
|
-
const apiBaseUrl = await
|
|
6819
|
+
if (input3.fields.includes("apiBaseUrl")) {
|
|
6820
|
+
const apiBaseUrl = await input3.io.input("API Base URL");
|
|
6359
6821
|
if (draft.Models?.length) {
|
|
6360
6822
|
draft.Models = draft.Models.map((model) => ({
|
|
6361
6823
|
...model,
|
|
@@ -6369,8 +6831,8 @@ async function completeDraft(input2) {
|
|
|
6369
6831
|
}));
|
|
6370
6832
|
}
|
|
6371
6833
|
}
|
|
6372
|
-
if (
|
|
6373
|
-
await promptCapabilityMetadataForDraft(draft,
|
|
6834
|
+
if (input3.fields.includes("capabilityHints") && draft.Models?.[0]) {
|
|
6835
|
+
await promptCapabilityMetadataForDraft(draft, input3.io);
|
|
6374
6836
|
}
|
|
6375
6837
|
return draft;
|
|
6376
6838
|
}
|
|
@@ -6384,112 +6846,150 @@ function createDefaultDeps(io = createConsoleIO()) {
|
|
|
6384
6846
|
executeStart,
|
|
6385
6847
|
executeReload: executeRestart,
|
|
6386
6848
|
executeRestart,
|
|
6387
|
-
verifyHealth: () => waitForService(
|
|
6849
|
+
verifyHealth: () => waitForService(getConfiguredPortFromCurrentFiles(), 5e3),
|
|
6388
6850
|
enterClaudeCode,
|
|
6389
6851
|
io
|
|
6390
6852
|
};
|
|
6391
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
|
+
}
|
|
6392
6860
|
async function runSetupCli(customDeps) {
|
|
6393
6861
|
const defaults = createDefaultDeps(customDeps?.io);
|
|
6394
6862
|
const deps = { ...defaults, ...customDeps };
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
"\u76F4\u63A5\u4F7F\u7528\
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
if (currentConfig.kind === "invalid") {
|
|
6419
|
-
deps.io.info(`\u5F53\u524D\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A${currentConfig.errors.join("; ")}`);
|
|
6420
|
-
if (currentConfig.warnings.length > 0) {
|
|
6421
|
-
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
|
+
);
|
|
6422
6886
|
}
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
6483
6981
|
}
|
|
6484
|
-
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;
|
|
6485
6983
|
var init_setup2 = __esm({
|
|
6486
6984
|
"src/setup/index.ts"() {
|
|
6487
6985
|
"use strict";
|
|
6488
6986
|
import_fs6 = require("fs");
|
|
6987
|
+
import_net2 = require("net");
|
|
6489
6988
|
import_os3 = require("os");
|
|
6490
6989
|
import_path6 = require("path");
|
|
6491
6990
|
import_promises3 = require("readline/promises");
|
|
6492
6991
|
import_process = require("process");
|
|
6992
|
+
import_json52 = __toESM(require("json5"));
|
|
6493
6993
|
import_js_yaml = __toESM(require("js-yaml"));
|
|
6494
6994
|
init_constants();
|
|
6495
6995
|
init_provider_presets();
|
|
@@ -6506,6 +7006,525 @@ var init_setup2 = __esm({
|
|
|
6506
7006
|
}
|
|
6507
7007
|
});
|
|
6508
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
|
+
|
|
6509
7528
|
// src/cli.ts
|
|
6510
7529
|
var cli_exports = {};
|
|
6511
7530
|
__export(cli_exports, {
|
|
@@ -6515,7 +7534,7 @@ __export(cli_exports, {
|
|
|
6515
7534
|
});
|
|
6516
7535
|
module.exports = __toCommonJS(cli_exports);
|
|
6517
7536
|
function getPackageInfo() {
|
|
6518
|
-
const content = (0,
|
|
7537
|
+
const content = (0, import_fs8.readFileSync)(PACKAGE_JSON_PATH, "utf-8");
|
|
6519
7538
|
const pkg = JSON.parse(content);
|
|
6520
7539
|
return {
|
|
6521
7540
|
name: pkg.name ?? "@peterwangze/claude-trigger-router",
|
|
@@ -6528,7 +7547,7 @@ function getArgs() {
|
|
|
6528
7547
|
function getCommand() {
|
|
6529
7548
|
return getArgs()[0];
|
|
6530
7549
|
}
|
|
6531
|
-
function
|
|
7550
|
+
function hasArg2(flag, shortFlag) {
|
|
6532
7551
|
const args = getArgs();
|
|
6533
7552
|
return args.includes(flag) || (shortFlag ? args.includes(shortFlag) : false);
|
|
6534
7553
|
}
|
|
@@ -6537,32 +7556,43 @@ function getArgValue(flag, shortFlag) {
|
|
|
6537
7556
|
const index = args.indexOf(flag) !== -1 ? args.indexOf(flag) : shortFlag ? args.indexOf(shortFlag) : -1;
|
|
6538
7557
|
return index !== -1 ? args[index + 1] : void 0;
|
|
6539
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
|
+
}
|
|
6540
7570
|
function getPort() {
|
|
6541
7571
|
const portValue = getArgValue("--port", "-p");
|
|
6542
7572
|
if (portValue) {
|
|
6543
|
-
return
|
|
7573
|
+
return parsePortValue(portValue, "\u547D\u4EE4\u884C\u7AEF\u53E3\u53C2\u6570");
|
|
6544
7574
|
}
|
|
6545
7575
|
try {
|
|
6546
|
-
const
|
|
6547
|
-
if ((0,
|
|
6548
|
-
const content = (0,
|
|
6549
|
-
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);
|
|
6550
7580
|
if (config?.PORT) return config.PORT;
|
|
6551
|
-
} else if ((0,
|
|
6552
|
-
const content = (0,
|
|
6553
|
-
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);
|
|
6554
7584
|
if (config?.PORT) return config.PORT;
|
|
6555
|
-
} else if ((0,
|
|
6556
|
-
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");
|
|
6557
7587
|
const config = JSON.parse(content);
|
|
6558
7588
|
if (config?.PORT) return config.PORT;
|
|
6559
7589
|
}
|
|
6560
7590
|
} catch {
|
|
6561
7591
|
}
|
|
6562
|
-
return
|
|
7592
|
+
return DEFAULT_CONFIG2.PORT;
|
|
6563
7593
|
}
|
|
6564
7594
|
function isDaemonMode() {
|
|
6565
|
-
return
|
|
7595
|
+
return hasArg2("--daemon", "-d");
|
|
6566
7596
|
}
|
|
6567
7597
|
function printHelp() {
|
|
6568
7598
|
console.log(`
|
|
@@ -6572,6 +7602,7 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6572
7602
|
|
|
6573
7603
|
\u547D\u4EE4\uFF1A
|
|
6574
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
|
|
6575
7606
|
init \u521D\u59CB\u5316\u6700\u5C0F\u914D\u7F6E\u6A21\u677F
|
|
6576
7607
|
start \u542F\u52A8\u8DEF\u7531\u670D\u52A1\uFF08\u9ED8\u8BA4\u524D\u53F0\u8FD0\u884C\uFF09
|
|
6577
7608
|
stop \u505C\u6B62\u540E\u53F0\u670D\u52A1
|
|
@@ -6580,16 +7611,17 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6580
7611
|
version \u67E5\u770B\u5F53\u524D\u5B89\u88C5\u7248\u672C\u4E0E\u5305\u4FE1\u606F
|
|
6581
7612
|
upgrade \u67E5\u770B\u5347\u7EA7\u5230\u6700\u65B0 npm \u7248\u672C\u7684\u6307\u5F15
|
|
6582
7613
|
code \u901A\u8FC7\u8DEF\u7531\u5668\u8FD0\u884C Claude Code\uFF08\u9700\u5148\u542F\u52A8\u670D\u52A1\uFF09
|
|
6583
|
-
ui \u6253\u5F00\u7BA1\u7406
|
|
7614
|
+
ui \u6253\u5F00\u672C\u5730\u7BA1\u7406\u9875\uFF08\u914D\u7F6E\u9884\u89C8\u4E0E\u8C03\u8BD5\uFF09
|
|
6584
7615
|
help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
|
|
6585
7616
|
|
|
6586
7617
|
\u9009\u9879\uFF1A
|
|
6587
|
-
--port, -p \u6307\u5B9A\u76D1\u542C\u7AEF\u53E3\uFF08\u9ED8\u8BA4\
|
|
7618
|
+
--port, -p \u6307\u5B9A\u76D1\u542C\u7AEF\u53E3\uFF08\u9ED8\u8BA4\uFF1A5678\uFF09
|
|
6588
7619
|
--daemon, -d \u4EE5\u540E\u53F0\u65B9\u5F0F\u8FD0\u884C\uFF08\u914D\u5408 start/restart \u4F7F\u7528\uFF09
|
|
6589
7620
|
--force \u5F3A\u5236\u8986\u76D6\u5DF2\u6709\u914D\u7F6E\uFF08\u914D\u5408 init \u4F7F\u7528\uFF09
|
|
6590
7621
|
|
|
6591
7622
|
\u4F7F\u7528\u793A\u4F8B\uFF1A
|
|
6592
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
|
|
6593
7625
|
ctr init # \u521D\u59CB\u5316\u6700\u5C0F\u914D\u7F6E\u6A21\u677F
|
|
6594
7626
|
ctr version # \u67E5\u770B\u5F53\u524D\u5B89\u88C5\u7248\u672C
|
|
6595
7627
|
ctr upgrade # \u67E5\u770B\u5347\u7EA7\u5230\u6700\u65B0\u7248\u672C\u7684\u547D\u4EE4
|
|
@@ -6597,6 +7629,7 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6597
7629
|
ctr start --daemon # \u540E\u53F0\u542F\u52A8
|
|
6598
7630
|
ctr status # \u67E5\u770B\u670D\u52A1\u72B6\u6001
|
|
6599
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
|
|
6600
7633
|
ctr stop # \u505C\u6B62\u540E\u53F0\u670D\u52A1
|
|
6601
7634
|
ctr restart --daemon # \u91CD\u542F\u540E\u53F0\u670D\u52A1
|
|
6602
7635
|
|
|
@@ -6606,10 +7639,29 @@ Claude Trigger Router - \u667A\u80FD\u89E6\u53D1\u8DEF\u7531\u5668
|
|
|
6606
7639
|
|
|
6607
7640
|
\u914D\u7F6E\u76EE\u5F55\uFF1A${CONFIG_DIR}
|
|
6608
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
|
+
|
|
6609
7645
|
\u66F4\u591A\u4FE1\u606F\uFF1Ahttps://github.com/peterwangze/claude-trigger-router
|
|
6610
7646
|
`);
|
|
6611
7647
|
}
|
|
6612
|
-
|
|
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) {
|
|
6613
7665
|
try {
|
|
6614
7666
|
const response = await fetch(PACKAGE_REGISTRY_LATEST_URL, {
|
|
6615
7667
|
signal: AbortSignal.timeout(timeoutMs)
|
|
@@ -6618,10 +7670,12 @@ async function getLatestPackageVersion(timeoutMs = 1500) {
|
|
|
6618
7670
|
return null;
|
|
6619
7671
|
}
|
|
6620
7672
|
const payload = await response.json();
|
|
6621
|
-
|
|
7673
|
+
if (typeof payload.version === "string") {
|
|
7674
|
+
return payload.version;
|
|
7675
|
+
}
|
|
6622
7676
|
} catch {
|
|
6623
|
-
return null;
|
|
6624
7677
|
}
|
|
7678
|
+
return getLatestPackageVersionViaNpm(packageName);
|
|
6625
7679
|
}
|
|
6626
7680
|
function isNewerVersion(current, latest) {
|
|
6627
7681
|
const currentParts = current.split(".").map((part) => Number.parseInt(part, 10));
|
|
@@ -6641,7 +7695,7 @@ function isNewerVersion(current, latest) {
|
|
|
6641
7695
|
}
|
|
6642
7696
|
async function printVersion() {
|
|
6643
7697
|
const pkg = getPackageInfo();
|
|
6644
|
-
const latestVersion = await getLatestPackageVersion();
|
|
7698
|
+
const latestVersion = await getLatestPackageVersion(pkg.name);
|
|
6645
7699
|
console.log(`Package: ${pkg.name}`);
|
|
6646
7700
|
console.log(`Version: ${pkg.version}`);
|
|
6647
7701
|
console.log(`Latest: ${latestVersion ?? "unavailable"}`);
|
|
@@ -6661,30 +7715,42 @@ function printUpgradeGuidance() {
|
|
|
6661
7715
|
console.log("\u5168\u5C40\u5B89\u88C5\u5728\u67D0\u4E9B\u73AF\u5883\u4E0B\u53EF\u80FD\u9700\u8981\u7BA1\u7406\u5458/root \u6743\u9650\u3002");
|
|
6662
7716
|
console.log(`NPM: ${PACKAGE_PAGE_URL}`);
|
|
6663
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
|
+
}
|
|
6664
7734
|
function initConfig2() {
|
|
6665
|
-
const force =
|
|
6666
|
-
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);
|
|
6667
7737
|
if (existingConfig && !force) {
|
|
6668
7738
|
console.log(`\u26A0\uFE0F \u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\uFF1A${existingConfig}`);
|
|
6669
7739
|
console.log(" \u5982\u9700\u8986\u76D6\uFF0C\u8BF7\u4F7F\u7528 --force \u53C2\u6570\u3002");
|
|
6670
7740
|
return;
|
|
6671
7741
|
}
|
|
6672
|
-
if (!(0,
|
|
6673
|
-
(0,
|
|
6674
|
-
}
|
|
6675
|
-
const examplePaths = [
|
|
6676
|
-
(0, import_path7.join)(__dirname, "..", "config", "trigger.example.yaml"),
|
|
6677
|
-
(0, import_path7.join)((0, import_path7.dirname)(process.argv[1]), "..", "config", "trigger.example.yaml")
|
|
6678
|
-
];
|
|
6679
|
-
const exampleFile = examplePaths.find((p) => (0, import_fs7.existsSync)(p));
|
|
6680
|
-
if (!exampleFile) {
|
|
6681
|
-
console.error("\u274C \u627E\u4E0D\u5230\u793A\u4F8B\u914D\u7F6E\u6587\u4EF6\u3002");
|
|
6682
|
-
console.log(` \u8BF7\u624B\u52A8\u521B\u5EFA ${CONFIG_FILE}`);
|
|
6683
|
-
console.log(" \u53C2\u8003\u6587\u6863\uFF1Ahttps://github.com/peterwangze/claude-trigger-router#configuration");
|
|
6684
|
-
process.exit(1);
|
|
7742
|
+
if (!(0, import_fs8.existsSync)(CONFIG_DIR)) {
|
|
7743
|
+
(0, import_fs8.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
6685
7744
|
}
|
|
6686
7745
|
try {
|
|
6687
|
-
|
|
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");
|
|
6688
7754
|
const action = force ? "\u5DF2\u8986\u76D6" : "\u5DF2\u521B\u5EFA";
|
|
6689
7755
|
console.log(`\u2705 \u914D\u7F6E\u6587\u4EF6${action}\uFF1A${CONFIG_FILE}`);
|
|
6690
7756
|
console.log("");
|
|
@@ -6700,10 +7766,22 @@ function initConfig2() {
|
|
|
6700
7766
|
}
|
|
6701
7767
|
}
|
|
6702
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
|
+
}
|
|
6703
7781
|
console.log("\u{1F680} Starting Claude Trigger Router (foreground)...");
|
|
6704
7782
|
console.log(" Press Ctrl+C to stop");
|
|
6705
7783
|
try {
|
|
6706
|
-
await run({ port });
|
|
7784
|
+
await run({ port: targetPort });
|
|
6707
7785
|
} catch (error) {
|
|
6708
7786
|
if (error.message?.includes("Invalid configuration")) {
|
|
6709
7787
|
console.error("\n\u274C Configuration error. Run 'ctr init' to create a config file.");
|
|
@@ -6713,7 +7791,14 @@ async function startForeground(port) {
|
|
|
6713
7791
|
process.exit(1);
|
|
6714
7792
|
}
|
|
6715
7793
|
}
|
|
6716
|
-
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
|
+
}
|
|
6717
7802
|
if (isServiceRunning()) {
|
|
6718
7803
|
console.log("\u2705 Service is already running in the background.");
|
|
6719
7804
|
return;
|
|
@@ -6724,30 +7809,58 @@ function startDaemon(port) {
|
|
|
6724
7809
|
if (port) {
|
|
6725
7810
|
childArgs.push("--port", String(port));
|
|
6726
7811
|
}
|
|
6727
|
-
const child = (0,
|
|
7812
|
+
const child = (0, import_child_process3.spawn)(nodeExec, childArgs, {
|
|
6728
7813
|
detached: true,
|
|
6729
7814
|
stdio: "ignore",
|
|
6730
7815
|
env: { ...process.env, CTR_DAEMON: "1" }
|
|
6731
7816
|
});
|
|
6732
7817
|
child.unref();
|
|
6733
|
-
const
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
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() {
|
|
6749
7855
|
const info = readServiceInfo();
|
|
6750
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
|
+
}
|
|
6751
7864
|
console.log("\u23F9 \u670D\u52A1\u672A\u8FD0\u884C");
|
|
6752
7865
|
return;
|
|
6753
7866
|
}
|
|
@@ -6772,9 +7885,10 @@ function stopService() {
|
|
|
6772
7885
|
console.error("\u274C \u505C\u6B62\u670D\u52A1\u5931\u8D25:", error.message);
|
|
6773
7886
|
}
|
|
6774
7887
|
}
|
|
6775
|
-
function restartService() {
|
|
7888
|
+
async function restartService() {
|
|
6776
7889
|
stopService();
|
|
6777
|
-
|
|
7890
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
7891
|
+
await startDaemon(getPort());
|
|
6778
7892
|
}
|
|
6779
7893
|
async function runClaudeCode() {
|
|
6780
7894
|
const port = getPort();
|
|
@@ -6789,14 +7903,16 @@ async function runClaudeCode() {
|
|
|
6789
7903
|
console.log(" 1. Start service first: ctr start --daemon");
|
|
6790
7904
|
console.log(" 2. Or start interactively in another terminal: ctr start");
|
|
6791
7905
|
console.log("");
|
|
6792
|
-
|
|
6793
|
-
if (!proceed) {
|
|
6794
|
-
process.exit(1);
|
|
6795
|
-
}
|
|
7906
|
+
process.exit(1);
|
|
6796
7907
|
}
|
|
6797
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
|
+
}
|
|
6798
7914
|
const isWindows = process.platform === "win32";
|
|
6799
|
-
const claude = (0,
|
|
7915
|
+
const claude = (0, import_child_process3.spawn)("claude", [], {
|
|
6800
7916
|
stdio: "inherit",
|
|
6801
7917
|
shell: isWindows,
|
|
6802
7918
|
env: {
|
|
@@ -6815,10 +7931,18 @@ async function runClaudeCode() {
|
|
|
6815
7931
|
process.exit(code || 0);
|
|
6816
7932
|
});
|
|
6817
7933
|
}
|
|
6818
|
-
function openUI() {
|
|
7934
|
+
async function openUI() {
|
|
6819
7935
|
const port = getPort();
|
|
6820
7936
|
const url = `http://127.0.0.1:${port}/ui`;
|
|
7937
|
+
const healthy = await waitForService(port, 800);
|
|
6821
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
|
+
}
|
|
6822
7946
|
try {
|
|
6823
7947
|
(0, import_openurl.default)(url);
|
|
6824
7948
|
} catch (error) {
|
|
@@ -6831,12 +7955,15 @@ async function main() {
|
|
|
6831
7955
|
case "setup":
|
|
6832
7956
|
await runSetupCli();
|
|
6833
7957
|
break;
|
|
7958
|
+
case "doctor":
|
|
7959
|
+
await runDoctorCli();
|
|
7960
|
+
break;
|
|
6834
7961
|
case "init":
|
|
6835
7962
|
initConfig2();
|
|
6836
7963
|
break;
|
|
6837
7964
|
case "start":
|
|
6838
7965
|
if (isDaemonMode()) {
|
|
6839
|
-
startDaemon(getPort());
|
|
7966
|
+
await startDaemon(getPort());
|
|
6840
7967
|
} else {
|
|
6841
7968
|
await startForeground(getPort());
|
|
6842
7969
|
}
|
|
@@ -6845,7 +7972,7 @@ async function main() {
|
|
|
6845
7972
|
stopService();
|
|
6846
7973
|
break;
|
|
6847
7974
|
case "status":
|
|
6848
|
-
showStatus();
|
|
7975
|
+
await showStatus();
|
|
6849
7976
|
break;
|
|
6850
7977
|
case "version":
|
|
6851
7978
|
await printVersion();
|
|
@@ -6854,13 +7981,14 @@ async function main() {
|
|
|
6854
7981
|
printUpgradeGuidance();
|
|
6855
7982
|
break;
|
|
6856
7983
|
case "restart":
|
|
6857
|
-
|
|
7984
|
+
printRestartGuidanceHint();
|
|
7985
|
+
await restartService();
|
|
6858
7986
|
break;
|
|
6859
7987
|
case "code":
|
|
6860
7988
|
await runClaudeCode();
|
|
6861
7989
|
break;
|
|
6862
7990
|
case "ui":
|
|
6863
|
-
openUI();
|
|
7991
|
+
await openUI();
|
|
6864
7992
|
break;
|
|
6865
7993
|
case "help":
|
|
6866
7994
|
case "--help":
|
|
@@ -6875,21 +8003,24 @@ async function main() {
|
|
|
6875
8003
|
process.exit(command ? 1 : 0);
|
|
6876
8004
|
}
|
|
6877
8005
|
}
|
|
6878
|
-
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;
|
|
6879
8007
|
var init_cli = __esm({
|
|
6880
8008
|
"src/cli.ts"() {
|
|
6881
|
-
|
|
8009
|
+
import_child_process3 = require("child_process");
|
|
6882
8010
|
import_path7 = require("path");
|
|
6883
8011
|
import_openurl = __toESM(require("openurl"));
|
|
6884
|
-
|
|
8012
|
+
import_fs8 = require("fs");
|
|
6885
8013
|
init_index();
|
|
6886
8014
|
init_processCheck();
|
|
6887
8015
|
init_constants();
|
|
6888
8016
|
init_service_health();
|
|
6889
8017
|
init_setup2();
|
|
8018
|
+
init_templates();
|
|
8019
|
+
init_doctor();
|
|
6890
8020
|
PACKAGE_JSON_PATH = (0, import_path7.join)(__dirname, "..", "package.json");
|
|
6891
8021
|
PACKAGE_PAGE_URL = "https://www.npmjs.com/package/@peterwangze/claude-trigger-router";
|
|
6892
8022
|
PACKAGE_REGISTRY_LATEST_URL = "https://registry.npmjs.org/@peterwangze%2Fclaude-trigger-router/latest";
|
|
8023
|
+
PACKAGE_REGISTRY_URL = "https://registry.npmjs.org/";
|
|
6893
8024
|
if (process.env.CTR_SKIP_MAIN !== "1") {
|
|
6894
8025
|
main().catch((error) => {
|
|
6895
8026
|
console.error("Error:", error);
|