@jsonstudio/rcc 0.89.2202 → 0.90.1
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 +27 -0
- package/dist/build-info.js +2 -2
- package/dist/build-info.js.map +1 -1
- package/dist/cli/commands/claude.js +1 -0
- package/dist/cli/commands/claude.js.map +1 -1
- package/dist/cli/commands/codex.js +1 -0
- package/dist/cli/commands/codex.js.map +1 -1
- package/dist/cli/commands/guardian-daemon.d.ts +2 -0
- package/dist/cli/commands/guardian-daemon.js +299 -0
- package/dist/cli/commands/guardian-daemon.js.map +1 -0
- package/dist/cli/commands/init/camoufox.js +1 -1
- package/dist/cli/commands/init/camoufox.js.map +1 -1
- package/dist/cli/commands/init.js +15 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/launcher/types.d.ts +6 -0
- package/dist/cli/commands/launcher-kernel.js +456 -109
- package/dist/cli/commands/launcher-kernel.js.map +1 -1
- package/dist/cli/commands/port.js +28 -8
- package/dist/cli/commands/port.js.map +1 -1
- package/dist/cli/commands/restart.d.ts +4 -0
- package/dist/cli/commands/restart.js +91 -42
- package/dist/cli/commands/restart.js.map +1 -1
- package/dist/cli/commands/start-types.d.ts +4 -0
- package/dist/cli/commands/start.js +112 -68
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/stop.d.ts +3 -0
- package/dist/cli/commands/stop.js +30 -63
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/cli/config/init-config.js +15 -1
- package/dist/cli/config/init-config.js.map +1 -1
- package/dist/cli/config/init-provider-catalog.js +13 -5
- package/dist/cli/config/init-provider-catalog.js.map +1 -1
- package/dist/cli/guardian/client.d.ts +38 -0
- package/dist/cli/guardian/client.js +237 -0
- package/dist/cli/guardian/client.js.map +1 -0
- package/dist/cli/guardian/paths.d.ts +7 -0
- package/dist/cli/guardian/paths.js +13 -0
- package/dist/cli/guardian/paths.js.map +1 -0
- package/dist/cli/guardian/types.d.ts +30 -0
- package/dist/cli/guardian/types.js +2 -0
- package/dist/cli/guardian/types.js.map +1 -0
- package/dist/cli/register/guardian-daemon-command.d.ts +2 -0
- package/dist/cli/register/guardian-daemon-command.js +5 -0
- package/dist/cli/register/guardian-daemon-command.js.map +1 -0
- package/dist/cli/server/port-utils.js +57 -1
- package/dist/cli/server/port-utils.js.map +1 -1
- package/dist/cli.js +48 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/oauth.js +6 -6
- package/dist/commands/oauth.js.map +1 -1
- package/dist/commands/provider-update.js +12 -0
- package/dist/commands/provider-update.js.map +1 -1
- package/dist/config/routecodex-config-loader.js +66 -1
- package/dist/config/routecodex-config-loader.js.map +1 -1
- package/dist/config/virtual-router-builder.js +18 -0
- package/dist/config/virtual-router-builder.js.map +1 -1
- package/dist/config/virtual-router-types.js +20 -5
- package/dist/config/virtual-router-types.js.map +1 -1
- package/dist/daemon-admin-ui/assets/index-C8vP_c5E.js +15 -0
- package/dist/daemon-admin-ui/assets/index-DjIoHmNv.css +1 -0
- package/dist/daemon-admin-ui/index.html +13 -0
- package/dist/docs/daemon-admin-ui.html +328 -57
- package/dist/index.d.ts +9 -0
- package/dist/index.js +268 -10
- package/dist/index.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.d.ts +1 -0
- package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js +36 -0
- package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.events.js +50 -1
- package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
- package/dist/providers/auth/antigravity-user-agent.js +78 -31
- package/dist/providers/auth/antigravity-user-agent.js.map +1 -1
- package/dist/providers/auth/gemini-cli-userinfo-helper.js +94 -63
- package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/iflow-userinfo-helper.js +1 -1
- package/dist/providers/auth/iflow-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/oauth-error-message.d.ts +1 -0
- package/dist/providers/auth/oauth-error-message.js +44 -0
- package/dist/providers/auth/oauth-error-message.js.map +1 -0
- package/dist/providers/auth/oauth-lifecycle/error-detection.js +42 -8
- package/dist/providers/auth/oauth-lifecycle/error-detection.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle/token-io.d.ts +1 -0
- package/dist/providers/auth/oauth-lifecycle/token-io.js +12 -0
- package/dist/providers/auth/oauth-lifecycle/token-io.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +502 -87
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/oauth-repair-cooldown.js +2 -7
- package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
- package/dist/providers/auth/oauth-repair-env.js +3 -5
- package/dist/providers/auth/oauth-repair-env.js.map +1 -1
- package/dist/providers/auth/oauth-utils/error-extraction.js +42 -8
- package/dist/providers/auth/oauth-utils/error-extraction.js.map +1 -1
- package/dist/providers/auth/qwen-userinfo-helper.js +18 -3
- package/dist/providers/auth/qwen-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/tokenfile-auth.d.ts +1 -0
- package/dist/providers/auth/tokenfile-auth.js +15 -9
- package/dist/providers/auth/tokenfile-auth.js.map +1 -1
- package/dist/providers/core/config/camoufox-actions.d.ts +31 -0
- package/dist/providers/core/config/camoufox-actions.js +461 -0
- package/dist/providers/core/config/camoufox-actions.js.map +1 -0
- package/dist/providers/core/config/camoufox-launcher.d.ts +3 -0
- package/dist/providers/core/config/camoufox-launcher.js +518 -160
- package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
- package/dist/providers/core/config/oauth-flows.js +6 -44
- package/dist/providers/core/config/oauth-flows.js.map +1 -1
- package/dist/providers/core/config/provider-oauth-configs.js +51 -7
- package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +13 -4
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +1 -0
- package/dist/providers/core/runtime/http-transport-provider.js +60 -1
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-error-classifier.js +32 -15
- package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
- package/dist/providers/core/runtime/provider-family-profile-utils.js +1 -1
- package/dist/providers/core/runtime/provider-family-profile-utils.js.map +1 -1
- package/dist/providers/core/runtime/provider-response-postprocessor.js +61 -14
- package/dist/providers/core/runtime/provider-response-postprocessor.js.map +1 -1
- package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +124 -19
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.js +6 -3
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/profile/families/iflow-profile.js +83 -10
- package/dist/providers/profile/families/iflow-profile.js.map +1 -1
- package/dist/providers/profile/families/qwen-profile.js +203 -0
- package/dist/providers/profile/families/qwen-profile.js.map +1 -1
- package/dist/scripts/camoufox/launch-auth.mjs +112 -5
- package/dist/server/handlers/config-admin-handler.js +9 -2
- package/dist/server/handlers/config-admin-handler.js.map +1 -1
- package/dist/server/handlers/handler-utils.js +3 -14
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/logging.js +3 -4
- package/dist/server/handlers/logging.js.map +1 -1
- package/dist/server/runtime/http-server/clock-client-reaper.d.ts +1 -0
- package/dist/server/runtime/http-server/clock-client-reaper.js +21 -15
- package/dist/server/runtime/http-server/clock-client-reaper.js.map +1 -1
- package/dist/server/runtime/http-server/clock-client-registry-utils.d.ts +4 -0
- package/dist/server/runtime/http-server/clock-client-registry-utils.js +74 -16
- package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +1 -1
- package/dist/server/runtime/http-server/clock-client-registry.d.ts +15 -0
- package/dist/server/runtime/http-server/clock-client-registry.js +300 -6
- package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -1
- package/dist/server/runtime/http-server/clock-client-routes.js +49 -19
- package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -1
- package/dist/server/runtime/http-server/clock-daemon-log-throttle.d.ts +16 -0
- package/dist/server/runtime/http-server/clock-daemon-log-throttle.js +49 -0
- package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +1 -1
- package/dist/server/runtime/http-server/clock-scope-resolution.d.ts +14 -0
- package/dist/server/runtime/http-server/clock-scope-resolution.js +212 -0
- package/dist/server/runtime/http-server/clock-scope-resolution.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +5 -3
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/control-handler.js +104 -15
- package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +2 -2
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +24 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +316 -70
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +190 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/routing-policy.js +18 -29
- package/dist/server/runtime/http-server/daemon-admin/routing-policy.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +2 -0
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +8 -1
- package/dist/server/runtime/http-server/daemon-admin-routes.js +30 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
- package/dist/server/runtime/http-server/executor/client-injection-flow.d.ts +14 -0
- package/dist/server/runtime/http-server/executor/client-injection-flow.js +287 -0
- package/dist/server/runtime/http-server/executor/client-injection-flow.js.map +1 -0
- package/dist/server/runtime/http-server/executor/index.d.ts +1 -1
- package/dist/server/runtime/http-server/executor/index.js +1 -1
- package/dist/server/runtime/http-server/executor/index.js.map +1 -1
- package/dist/server/runtime/http-server/executor/provider-response-converter.js +236 -62
- package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
- package/dist/server/runtime/http-server/executor/provider-response-utils.js +5 -0
- package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -1
- package/dist/server/runtime/http-server/executor/request-executor-core-utils.d.ts +2 -0
- package/dist/server/runtime/http-server/executor/request-executor-core-utils.js +60 -0
- package/dist/server/runtime/http-server/executor/request-executor-core-utils.js.map +1 -1
- package/dist/server/runtime/http-server/executor/request-retry-helpers.js +20 -8
- package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -1
- package/dist/server/runtime/http-server/executor/sse-error-handler.d.ts +1 -0
- package/dist/server/runtime/http-server/executor/sse-error-handler.js +13 -2
- package/dist/server/runtime/http-server/executor/sse-error-handler.js.map +1 -1
- package/dist/server/runtime/http-server/executor/usage-aggregator.d.ts +0 -12
- package/dist/server/runtime/http-server/executor/usage-aggregator.js +84 -88
- package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
- package/dist/server/runtime/http-server/executor-metadata.js +328 -7
- package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
- package/dist/server/runtime/http-server/executor-response.d.ts +1 -0
- package/dist/server/runtime/http-server/executor-response.js +52 -50
- package/dist/server/runtime/http-server/executor-response.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-bootstrap.js +55 -6
- package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +1 -0
- package/dist/server/runtime/http-server/http-server-clock-daemon.js +199 -44
- package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-lifecycle.js +4 -4
- package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
- package/dist/server/runtime/http-server/hub-shadow-compare.js +1 -1
- package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +1 -0
- package/dist/server/runtime/http-server/index.js +1 -0
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/middleware.js +82 -4
- package/dist/server/runtime/http-server/middleware.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +26 -7
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +2 -1
- package/dist/server/runtime/http-server/routes.js +4 -2
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/session-dir.js +12 -1
- package/dist/server/runtime/http-server/session-dir.js.map +1 -1
- package/dist/server/runtime/http-server/stats-manager.d.ts +35 -0
- package/dist/server/runtime/http-server/stats-manager.js +269 -21
- package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
- package/dist/server/runtime/http-server/stopmessage-scope-rebind.d.ts +13 -0
- package/dist/server/runtime/http-server/stopmessage-scope-rebind.js +168 -0
- package/dist/server/runtime/http-server/stopmessage-scope-rebind.js.map +1 -0
- package/dist/server/runtime/http-server/tmux-session-probe.d.ts +10 -0
- package/dist/server/runtime/http-server/tmux-session-probe.js +97 -0
- package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -1
- package/dist/server-lifecycle/port-utils.d.ts +2 -1
- package/dist/server-lifecycle/port-utils.js +84 -4
- package/dist/server-lifecycle/port-utils.js.map +1 -1
- package/dist/token-daemon/index.d.ts +1 -0
- package/dist/token-daemon/index.js +17 -12
- package/dist/token-daemon/index.js.map +1 -1
- package/dist/utils/clock-client-token.d.ts +2 -1
- package/dist/utils/clock-client-token.js +52 -8
- package/dist/utils/clock-client-token.js.map +1 -1
- package/dist/utils/clock-scope-trace.d.ts +11 -0
- package/dist/utils/clock-scope-trace.js +41 -0
- package/dist/utils/clock-scope-trace.js.map +1 -0
- package/dist/utils/llms-engine-shadow.js +1 -1
- package/dist/utils/llms-engine-shadow.js.map +1 -1
- package/docs/DAEMON_CONTROL_PLANE.md +1 -0
- package/docs/ROUTING_POLICY_SCHEMA.md +4 -2
- package/docs/daemon-admin-ui.html +328 -57
- package/docs/design/servertool-stopmessage-lifecycle.md +109 -0
- package/docs/exec-command-guard-policy.example.v1.json +7 -1
- package/docs/providers/antigravity-gemini-provider-compat.md +2 -2
- package/package.json +23 -6
- package/scripts/build-core.mjs +12 -0
- package/scripts/camoufox/launch-auth.mjs +112 -5
- package/scripts/ci/repo-sanity.mjs +1 -0
- package/scripts/cleanup-stale-server-pids.mjs +142 -0
- package/scripts/install-global.sh +8 -0
- package/scripts/install-verify.mjs +33 -16
- package/scripts/run-bg.sh +226 -43
- package/scripts/run-fg-gtimeout.sh +158 -14
- package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +3 -3
- package/scripts/tests/ci-jest.mjs +9 -1
- package/scripts/triage-errorsamples.mjs +216 -0
- package/scripts/verify-codex-error-samples.mjs +92 -15
- package/scripts/verify-e2e-toolcall.mjs +12 -1
- package/scripts/verify-install-e2e.mjs +69 -28
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
|
|
7
|
+
const ROOT =
|
|
8
|
+
process.env.ROUTECODEX_ERROR_SAMPLES_DIR &&
|
|
9
|
+
process.env.ROUTECODEX_ERROR_SAMPLES_DIR.trim().length
|
|
10
|
+
? path.resolve(process.env.ROUTECODEX_ERROR_SAMPLES_DIR)
|
|
11
|
+
: path.join(os.homedir(), '.routecodex', 'errorsamples');
|
|
12
|
+
|
|
13
|
+
const SKIP_SUBDIR = (() => {
|
|
14
|
+
const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_SKIP_SUBDIR || '').trim();
|
|
15
|
+
return raw || 'skip';
|
|
16
|
+
})();
|
|
17
|
+
const GOLD_SUBDIR = (() => {
|
|
18
|
+
const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_GOLD_SUBDIR || '').trim();
|
|
19
|
+
return raw || 'gold';
|
|
20
|
+
})();
|
|
21
|
+
|
|
22
|
+
const SHAPE_ERROR_PATTERNS = [
|
|
23
|
+
'failed to parse function arguments: missing field `cmd`',
|
|
24
|
+
'failed to parse function arguments: missing field `input`',
|
|
25
|
+
'failed to parse function arguments: missing field `command`',
|
|
26
|
+
'invalid type: map, expected a string'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const APPLY_PATCH_SHAPE_PATTERNS = [
|
|
30
|
+
'invalid patch',
|
|
31
|
+
'failed to parse',
|
|
32
|
+
'missing field `input`',
|
|
33
|
+
'missing field `cmd`',
|
|
34
|
+
'missing field `command`',
|
|
35
|
+
'invalid type: map, expected a string'
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const APPLY_PATCH_NON_SHAPE_PATTERNS = [
|
|
39
|
+
'failed to find context',
|
|
40
|
+
'failed to find expected lines',
|
|
41
|
+
'failed to read file',
|
|
42
|
+
'no such file',
|
|
43
|
+
'file not found'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function isJsonFile(name) {
|
|
47
|
+
return name.toLowerCase().endsWith('.json');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function exists(p) {
|
|
51
|
+
try {
|
|
52
|
+
await fs.access(p);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function collectSampleFiles(rootDir) {
|
|
60
|
+
const files = [];
|
|
61
|
+
const queue = [rootDir];
|
|
62
|
+
|
|
63
|
+
while (queue.length > 0) {
|
|
64
|
+
const currentDir = queue.shift();
|
|
65
|
+
if (!currentDir) continue;
|
|
66
|
+
|
|
67
|
+
let entries = [];
|
|
68
|
+
try {
|
|
69
|
+
entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
70
|
+
} catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
76
|
+
if (entry.isDirectory()) {
|
|
77
|
+
queue.push(fullPath);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!entry.isFile()) continue;
|
|
81
|
+
if (!isJsonFile(entry.name)) continue;
|
|
82
|
+
files.push(fullPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return files.sort();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function classifySample(raw) {
|
|
90
|
+
const lower = raw.toLowerCase();
|
|
91
|
+
|
|
92
|
+
const shapeHits = SHAPE_ERROR_PATTERNS.filter((pattern) => raw.includes(pattern));
|
|
93
|
+
if (shapeHits.length > 0) {
|
|
94
|
+
return { kind: 'shape', hits: shapeHits };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (lower.includes('apply_patch verification failed')) {
|
|
98
|
+
const shapeMatched = APPLY_PATCH_SHAPE_PATTERNS.filter((needle) => lower.includes(needle));
|
|
99
|
+
if (shapeMatched.length > 0) {
|
|
100
|
+
return { kind: 'shape', hits: ['apply_patch verification failed (shape)'] };
|
|
101
|
+
}
|
|
102
|
+
const nonShapeMatched = APPLY_PATCH_NON_SHAPE_PATTERNS.filter((needle) => lower.includes(needle));
|
|
103
|
+
if (nonShapeMatched.length > 0) {
|
|
104
|
+
return { kind: 'non_shape', hits: ['apply_patch verification failed (context/non-shape)'] };
|
|
105
|
+
}
|
|
106
|
+
return { kind: 'non_shape', hits: ['apply_patch verification failed (unknown subtype)'] };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { kind: 'none', hits: [] };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function inIgnoredSubdir(filePath) {
|
|
113
|
+
const rel = path.relative(ROOT, filePath);
|
|
114
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const parts = rel.split(path.sep).filter(Boolean);
|
|
118
|
+
return parts.includes(SKIP_SUBDIR) || parts.includes(GOLD_SUBDIR);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function moveIntoSkip(filePath) {
|
|
122
|
+
const rel = path.relative(ROOT, filePath);
|
|
123
|
+
const target = path.join(ROOT, SKIP_SUBDIR, rel);
|
|
124
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
125
|
+
await fs.rename(filePath, target);
|
|
126
|
+
return target;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function moveIntoGold(filePath) {
|
|
130
|
+
const rel = path.relative(ROOT, filePath);
|
|
131
|
+
const target = path.join(ROOT, GOLD_SUBDIR, rel);
|
|
132
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
133
|
+
await fs.rename(filePath, target);
|
|
134
|
+
return target;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function main() {
|
|
138
|
+
const apply = process.argv.includes('--apply');
|
|
139
|
+
|
|
140
|
+
if (!(await exists(ROOT))) {
|
|
141
|
+
console.log(`[triage:errorsamples] skip (directory not found: ${ROOT})`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const files = await collectSampleFiles(ROOT);
|
|
146
|
+
if (!files.length) {
|
|
147
|
+
console.log(`[triage:errorsamples] skip (no *.json samples under ${ROOT})`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const shape = [];
|
|
152
|
+
const nonShape = [];
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
if (inIgnoredSubdir(file)) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
let raw = '';
|
|
158
|
+
try {
|
|
159
|
+
raw = await fs.readFile(file, 'utf-8');
|
|
160
|
+
} catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const classified = classifySample(raw);
|
|
164
|
+
if (classified.kind === 'shape') {
|
|
165
|
+
shape.push({ file, hits: classified.hits });
|
|
166
|
+
} else if (classified.kind === 'non_shape') {
|
|
167
|
+
nonShape.push({ file, hits: classified.hits });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(`[triage:errorsamples] scanned: ${files.length}`);
|
|
172
|
+
console.log(`[triage:errorsamples] shape/actionable: ${shape.length}`);
|
|
173
|
+
console.log(`[triage:errorsamples] non-shape/skip-candidate: ${nonShape.length}`);
|
|
174
|
+
|
|
175
|
+
if (shape.length > 0) {
|
|
176
|
+
console.log('[triage:errorsamples] actionable shape samples (move to gold regression set):');
|
|
177
|
+
for (const item of shape.slice(0, 20)) {
|
|
178
|
+
console.log(` - ${path.basename(item.file)} → ${item.hits.join(', ')}`);
|
|
179
|
+
}
|
|
180
|
+
if (shape.length > 20) {
|
|
181
|
+
console.log(` - ... and ${shape.length - 20} more`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!apply) {
|
|
186
|
+
console.log(`[triage:errorsamples] dry-run complete (pass --apply to move shape -> ${GOLD_SUBDIR}/ and non-shape -> ${SKIP_SUBDIR}/)`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let movedGold = 0;
|
|
191
|
+
for (const item of shape) {
|
|
192
|
+
try {
|
|
193
|
+
await moveIntoGold(item.file);
|
|
194
|
+
movedGold += 1;
|
|
195
|
+
} catch {
|
|
196
|
+
// best-effort
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let movedSkip = 0;
|
|
201
|
+
for (const item of nonShape) {
|
|
202
|
+
try {
|
|
203
|
+
await moveIntoSkip(item.file);
|
|
204
|
+
movedSkip += 1;
|
|
205
|
+
} catch {
|
|
206
|
+
// best-effort
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
console.log(`[triage:errorsamples] moved to ${GOLD_SUBDIR}/: ${movedGold}/${shape.length}`);
|
|
210
|
+
console.log(`[triage:errorsamples] moved to ${SKIP_SUBDIR}/: ${movedSkip}/${nonShape.length}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
main().catch((error) => {
|
|
214
|
+
console.error('[triage:errorsamples] failed:', error);
|
|
215
|
+
process.exit(99);
|
|
216
|
+
});
|
|
@@ -22,13 +22,40 @@ const ROOT =
|
|
|
22
22
|
process.env.ROUTECODEX_ERROR_SAMPLES_DIR.trim().length
|
|
23
23
|
? path.resolve(process.env.ROUTECODEX_ERROR_SAMPLES_DIR)
|
|
24
24
|
: path.join(os.homedir(), '.routecodex', 'errorsamples');
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
25
|
+
const SKIP_SUBDIR = (() => {
|
|
26
|
+
const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_SKIP_SUBDIR || '').trim();
|
|
27
|
+
return raw || 'skip';
|
|
28
|
+
})();
|
|
29
|
+
const GOLD_SUBDIR = (() => {
|
|
30
|
+
const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_GOLD_SUBDIR || '').trim();
|
|
31
|
+
return raw || 'gold';
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
const SHAPE_ERROR_PATTERNS = [
|
|
35
|
+
// exec_command / shell_command / apply_patch 参数形状错误
|
|
28
36
|
'failed to parse function arguments: missing field `cmd`',
|
|
29
37
|
'failed to parse function arguments: missing field `input`',
|
|
30
|
-
|
|
31
|
-
'
|
|
38
|
+
'failed to parse function arguments: missing field `command`',
|
|
39
|
+
'invalid type: map, expected a string'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const APPLY_PATCH_SHAPE_PATTERNS = [
|
|
43
|
+
// apply_patch 仍属于“可修复形状问题”的子类
|
|
44
|
+
'invalid patch',
|
|
45
|
+
'failed to parse',
|
|
46
|
+
'missing field `input`',
|
|
47
|
+
'missing field `cmd`',
|
|
48
|
+
'missing field `command`',
|
|
49
|
+
'invalid type: map, expected a string'
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const APPLY_PATCH_NON_SHAPE_PATTERNS = [
|
|
53
|
+
// 这些是上下文/执行问题,不应作为“形状回归”阻塞构建
|
|
54
|
+
'failed to find context',
|
|
55
|
+
'failed to find expected lines',
|
|
56
|
+
'failed to read file',
|
|
57
|
+
'no such file',
|
|
58
|
+
'file not found'
|
|
32
59
|
];
|
|
33
60
|
|
|
34
61
|
async function fileExists(p) {
|
|
@@ -43,6 +70,8 @@ async function fileExists(p) {
|
|
|
43
70
|
async function collectSampleFiles(rootDir) {
|
|
44
71
|
const files = [];
|
|
45
72
|
const queue = [rootDir];
|
|
73
|
+
const skippedDirs = [];
|
|
74
|
+
const goldDirs = [];
|
|
46
75
|
|
|
47
76
|
while (queue.length > 0) {
|
|
48
77
|
const currentDir = queue.shift();
|
|
@@ -60,6 +89,14 @@ async function collectSampleFiles(rootDir) {
|
|
|
60
89
|
for (const entry of entries) {
|
|
61
90
|
const fullPath = path.join(currentDir, entry.name);
|
|
62
91
|
if (entry.isDirectory()) {
|
|
92
|
+
if (entry.name === SKIP_SUBDIR) {
|
|
93
|
+
skippedDirs.push(fullPath);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (entry.name === GOLD_SUBDIR) {
|
|
97
|
+
goldDirs.push(fullPath);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
63
100
|
queue.push(fullPath);
|
|
64
101
|
continue;
|
|
65
102
|
}
|
|
@@ -69,18 +106,38 @@ async function collectSampleFiles(rootDir) {
|
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
108
|
|
|
72
|
-
return
|
|
109
|
+
return {
|
|
110
|
+
files: files.sort(),
|
|
111
|
+
skippedDirs,
|
|
112
|
+
goldDirs
|
|
113
|
+
};
|
|
73
114
|
}
|
|
74
115
|
|
|
75
116
|
async function checkFile(filePath) {
|
|
76
117
|
const raw = await fs.readFile(filePath, 'utf-8');
|
|
77
|
-
const
|
|
78
|
-
|
|
118
|
+
const lower = raw.toLowerCase();
|
|
119
|
+
const failHits = [];
|
|
120
|
+
const warnHits = [];
|
|
121
|
+
|
|
122
|
+
for (const pattern of SHAPE_ERROR_PATTERNS) {
|
|
79
123
|
if (raw.includes(pattern)) {
|
|
80
|
-
|
|
124
|
+
failHits.push(pattern);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (lower.includes('apply_patch verification failed')) {
|
|
129
|
+
const shapeMatched = APPLY_PATCH_SHAPE_PATTERNS.some((needle) => lower.includes(needle));
|
|
130
|
+
const nonShapeMatched = APPLY_PATCH_NON_SHAPE_PATTERNS.some((needle) => lower.includes(needle));
|
|
131
|
+
if (shapeMatched) {
|
|
132
|
+
failHits.push('apply_patch verification failed (shape)');
|
|
133
|
+
} else if (nonShapeMatched) {
|
|
134
|
+
warnHits.push('apply_patch verification failed (context/non-shape)');
|
|
135
|
+
} else {
|
|
136
|
+
warnHits.push('apply_patch verification failed (unknown subtype)');
|
|
81
137
|
}
|
|
82
138
|
}
|
|
83
|
-
|
|
139
|
+
|
|
140
|
+
return { failHits, warnHits };
|
|
84
141
|
}
|
|
85
142
|
|
|
86
143
|
async function main() {
|
|
@@ -89,24 +146,44 @@ async function main() {
|
|
|
89
146
|
return;
|
|
90
147
|
}
|
|
91
148
|
|
|
92
|
-
const files = await collectSampleFiles(ROOT);
|
|
149
|
+
const { files, skippedDirs, goldDirs } = await collectSampleFiles(ROOT);
|
|
93
150
|
if (!files.length) {
|
|
94
151
|
console.log(`[verify:errorsamples] skip (no *.json samples under ${ROOT})`);
|
|
95
152
|
return;
|
|
96
153
|
}
|
|
97
154
|
|
|
98
155
|
console.log(`[verify:errorsamples] scanning ${files.length} sample(s) under ${ROOT}`);
|
|
156
|
+
if (skippedDirs.length > 0) {
|
|
157
|
+
console.log(`[verify:errorsamples] skip subdir "${SKIP_SUBDIR}" (${skippedDirs.length} dir(s))`);
|
|
158
|
+
}
|
|
159
|
+
if (goldDirs.length > 0) {
|
|
160
|
+
console.log(`[verify:errorsamples] gold subdir "${GOLD_SUBDIR}" (${goldDirs.length} dir(s))`);
|
|
161
|
+
}
|
|
99
162
|
|
|
100
163
|
const failures = [];
|
|
164
|
+
const warnings = [];
|
|
101
165
|
for (const file of files) {
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
104
|
-
failures.push({ file, hits });
|
|
166
|
+
const { failHits, warnHits } = await checkFile(file);
|
|
167
|
+
if (failHits.length) {
|
|
168
|
+
failures.push({ file, hits: failHits });
|
|
169
|
+
}
|
|
170
|
+
if (warnHits.length) {
|
|
171
|
+
warnings.push({ file, hits: warnHits });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (warnings.length > 0) {
|
|
176
|
+
console.log(`[verify:errorsamples] warning-only samples: ${warnings.length}`);
|
|
177
|
+
for (const item of warnings.slice(0, 8)) {
|
|
178
|
+
console.log(` - ${path.basename(item.file)} → ${item.hits.join(', ')}`);
|
|
179
|
+
}
|
|
180
|
+
if (warnings.length > 8) {
|
|
181
|
+
console.log(` - ... and ${warnings.length - 8} more warning sample(s)`);
|
|
105
182
|
}
|
|
106
183
|
}
|
|
107
184
|
|
|
108
185
|
if (!failures.length) {
|
|
109
|
-
console.log('[verify:errorsamples] ✅ no
|
|
186
|
+
console.log('[verify:errorsamples] ✅ no shape-error regressions found');
|
|
110
187
|
return;
|
|
111
188
|
}
|
|
112
189
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -33,6 +33,16 @@ const AGENTS_INSTRUCTIONS = (() => {
|
|
|
33
33
|
}
|
|
34
34
|
})();
|
|
35
35
|
|
|
36
|
+
function cleanupStaleServerPidFiles() {
|
|
37
|
+
try {
|
|
38
|
+
spawnSync('node', [path.join(__dirname, 'cleanup-stale-server-pids.mjs'), '--quiet'], {
|
|
39
|
+
stdio: 'ignore'
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
// ignore cleanup failures
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
function readServerApiKeyFromConfig(configPath) {
|
|
37
47
|
try {
|
|
38
48
|
const raw = fs.readFileSync(configPath, 'utf8');
|
|
@@ -102,6 +112,7 @@ async function main() {
|
|
|
102
112
|
await runGeminiCliStartupCheck();
|
|
103
113
|
} finally {
|
|
104
114
|
shutdown();
|
|
115
|
+
cleanupStaleServerPidFiles();
|
|
105
116
|
if (resolved.tempDir) {
|
|
106
117
|
try {
|
|
107
118
|
fs.rmSync(resolved.tempDir, { recursive: true, force: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { once } from 'node:events';
|
|
4
4
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
5
5
|
import http from 'node:http';
|
|
@@ -7,26 +7,37 @@ import fs from 'node:fs';
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const REQUESTED_PORT = Number(process.env.ROUTECODEX_INSTALL_VERIFY_PORT || process.env.RCC_INSTALL_VERIFY_PORT || 5560);
|
|
11
11
|
const HOST = process.env.ROUTECODEX_INSTALL_VERIFY_HOST || '127.0.0.1';
|
|
12
|
-
const
|
|
12
|
+
const PORT_SEARCH_LIMIT = 50;
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
function resolveServerEntryArgs() {
|
|
15
|
+
const serverEntry = path.join(process.cwd(), 'dist', 'index.js');
|
|
16
|
+
const modulesConfigPath = path.join(process.cwd(), 'dist', 'config', 'modules.json');
|
|
17
|
+
if (!fs.existsSync(serverEntry)) {
|
|
18
|
+
throw new Error(`verify-install-e2e missing server entry: ${serverEntry}`);
|
|
19
|
+
}
|
|
20
|
+
if (!fs.existsSync(modulesConfigPath)) {
|
|
21
|
+
throw new Error(`verify-install-e2e missing modules config: ${modulesConfigPath}`);
|
|
21
22
|
}
|
|
22
|
-
return
|
|
23
|
+
return [serverEntry, modulesConfigPath];
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
function cleanupStaleServerPidFiles() {
|
|
27
|
+
try {
|
|
28
|
+
spawnSync('node', [path.join(process.cwd(), 'scripts', 'cleanup-stale-server-pids.mjs'), '--quiet'], {
|
|
29
|
+
stdio: 'ignore'
|
|
30
|
+
});
|
|
31
|
+
} catch {
|
|
32
|
+
// ignore cleanup failures
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function waitForHealth(baseUrl, timeoutMs = 60000) {
|
|
26
37
|
const start = Date.now();
|
|
27
38
|
while (Date.now() - start < timeoutMs) {
|
|
28
39
|
try {
|
|
29
|
-
const res = await fetch(`${
|
|
40
|
+
const res = await fetch(`${baseUrl}/health`);
|
|
30
41
|
if (res.ok) {
|
|
31
42
|
return;
|
|
32
43
|
}
|
|
@@ -38,7 +49,7 @@ async function waitForHealth(timeoutMs = 60000) {
|
|
|
38
49
|
throw new Error('Timed out waiting for /health');
|
|
39
50
|
}
|
|
40
51
|
|
|
41
|
-
async function runChatTest() {
|
|
52
|
+
async function runChatTest(baseUrl) {
|
|
42
53
|
const payload = {
|
|
43
54
|
model: process.env.ROUTECODEX_VERIFY_CHAT_MODEL || 'glm-4.6',
|
|
44
55
|
messages: [
|
|
@@ -47,7 +58,7 @@ async function runChatTest() {
|
|
|
47
58
|
],
|
|
48
59
|
stream: false
|
|
49
60
|
};
|
|
50
|
-
const res = await fetch(`${
|
|
61
|
+
const res = await fetch(`${baseUrl}/v1/chat/completions`, {
|
|
51
62
|
method: 'POST',
|
|
52
63
|
headers: { 'Content-Type': 'application/json' },
|
|
53
64
|
body: JSON.stringify(payload)
|
|
@@ -66,7 +77,7 @@ async function runChatTest() {
|
|
|
66
77
|
}
|
|
67
78
|
}
|
|
68
79
|
|
|
69
|
-
async function runAnthropicSseTest() {
|
|
80
|
+
async function runAnthropicSseTest(baseUrl) {
|
|
70
81
|
const payload = {
|
|
71
82
|
model: process.env.ROUTECODEX_VERIFY_ANTHROPIC_MODEL || 'glm-4.6',
|
|
72
83
|
messages: [
|
|
@@ -74,7 +85,7 @@ async function runAnthropicSseTest() {
|
|
|
74
85
|
],
|
|
75
86
|
stream: true
|
|
76
87
|
};
|
|
77
|
-
const res = await fetch(`${
|
|
88
|
+
const res = await fetch(`${baseUrl}/v1/messages`, {
|
|
78
89
|
method: 'POST',
|
|
79
90
|
headers: {
|
|
80
91
|
'Content-Type': 'application/json',
|
|
@@ -115,14 +126,16 @@ async function runAnthropicSseTest() {
|
|
|
115
126
|
}
|
|
116
127
|
|
|
117
128
|
async function main() {
|
|
118
|
-
const
|
|
129
|
+
const port = await resolveVerifyPort(REQUESTED_PORT, HOST);
|
|
130
|
+
const baseUrl = `http://${HOST}:${port}`;
|
|
131
|
+
const serverArgs = resolveServerEntryArgs();
|
|
119
132
|
const customConfigPath = process.env.ROUTECODEX_INSTALL_CONFIG;
|
|
120
133
|
const mockServer = customConfigPath ? null : await startMockProviderServer();
|
|
121
|
-
const verifyConfigPath = customConfigPath || await writeVerifyConfig(mockServer.baseUrl);
|
|
134
|
+
const verifyConfigPath = customConfigPath || await writeVerifyConfig(mockServer.baseUrl, HOST, port);
|
|
122
135
|
const env = {
|
|
123
136
|
...process.env,
|
|
124
|
-
ROUTECODEX_PORT: String(
|
|
125
|
-
RCC_PORT: String(
|
|
137
|
+
ROUTECODEX_PORT: String(port),
|
|
138
|
+
RCC_PORT: String(port),
|
|
126
139
|
ROUTECODEX_HOST: HOST,
|
|
127
140
|
RCC_HOST: HOST,
|
|
128
141
|
ROUTECODEX_CONFIG: verifyConfigPath,
|
|
@@ -132,7 +145,7 @@ async function main() {
|
|
|
132
145
|
// does not depend on external network availability.
|
|
133
146
|
ROUTECODEX_USE_MOCK: '1'
|
|
134
147
|
};
|
|
135
|
-
const server = spawn(
|
|
148
|
+
const server = spawn(process.execPath, serverArgs, {
|
|
136
149
|
env,
|
|
137
150
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
138
151
|
});
|
|
@@ -154,9 +167,9 @@ async function main() {
|
|
|
154
167
|
});
|
|
155
168
|
|
|
156
169
|
try {
|
|
157
|
-
await waitForHealth();
|
|
158
|
-
await runChatTest();
|
|
159
|
-
await runAnthropicSseTest();
|
|
170
|
+
await waitForHealth(baseUrl);
|
|
171
|
+
await runChatTest(baseUrl);
|
|
172
|
+
await runAnthropicSseTest(baseUrl);
|
|
160
173
|
} finally {
|
|
161
174
|
if (!serverExited) {
|
|
162
175
|
server.kill('SIGTERM');
|
|
@@ -165,6 +178,7 @@ async function main() {
|
|
|
165
178
|
if (mockServer) {
|
|
166
179
|
await mockServer.close();
|
|
167
180
|
}
|
|
181
|
+
cleanupStaleServerPidFiles();
|
|
168
182
|
}
|
|
169
183
|
}
|
|
170
184
|
|
|
@@ -284,14 +298,14 @@ function extractUserPrompt(payload) {
|
|
|
284
298
|
: 'verification prompt';
|
|
285
299
|
}
|
|
286
300
|
|
|
287
|
-
async function writeVerifyConfig(baseUrl) {
|
|
301
|
+
async function writeVerifyConfig(baseUrl, host, port) {
|
|
288
302
|
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'routecodex-verify-'));
|
|
289
303
|
const filePath = path.join(dir, 'config.json');
|
|
290
304
|
const config = {
|
|
291
305
|
version: '1.0.0',
|
|
292
306
|
httpserver: {
|
|
293
|
-
host
|
|
294
|
-
port
|
|
307
|
+
host,
|
|
308
|
+
port
|
|
295
309
|
},
|
|
296
310
|
virtualrouter: {
|
|
297
311
|
inputProtocol: 'openai',
|
|
@@ -324,3 +338,30 @@ async function writeVerifyConfig(baseUrl) {
|
|
|
324
338
|
await fs.promises.writeFile(filePath, JSON.stringify(config, null, 2), 'utf8');
|
|
325
339
|
return filePath;
|
|
326
340
|
}
|
|
341
|
+
|
|
342
|
+
async function resolveVerifyPort(preferredPort, host) {
|
|
343
|
+
const basePort = Number.isFinite(preferredPort) && preferredPort > 0 ? preferredPort : 5560;
|
|
344
|
+
for (let offset = 0; offset < PORT_SEARCH_LIMIT; offset += 1) {
|
|
345
|
+
const port = basePort + offset;
|
|
346
|
+
// eslint-disable-next-line no-await-in-loop
|
|
347
|
+
if (await canListenOnPort(port, host)) {
|
|
348
|
+
return port;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
throw new Error(`No available verification port in range ${basePort}-${basePort + PORT_SEARCH_LIMIT - 1}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function canListenOnPort(port, host) {
|
|
355
|
+
const probe = http.createServer();
|
|
356
|
+
try {
|
|
357
|
+
await new Promise((resolve, reject) => {
|
|
358
|
+
probe.once('error', reject);
|
|
359
|
+
probe.listen(port, host, resolve);
|
|
360
|
+
});
|
|
361
|
+
return true;
|
|
362
|
+
} catch {
|
|
363
|
+
return false;
|
|
364
|
+
} finally {
|
|
365
|
+
await new Promise((resolve) => probe.close(() => resolve(undefined))).catch(() => {});
|
|
366
|
+
}
|
|
367
|
+
}
|