@jsonstudio/rcc 0.89.683 → 0.89.873
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.js +2 -2
- package/dist/cli.js +164 -116
- package/dist/cli.js.map +1 -1
- package/dist/client/anthropic/anthropic-protocol-client.js +42 -1
- package/dist/client/anthropic/anthropic-protocol-client.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +4 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
- package/dist/commands/camoufox-backfill.d.ts +2 -0
- package/dist/commands/camoufox-backfill.js +33 -0
- package/dist/commands/camoufox-backfill.js.map +1 -0
- package/dist/commands/camoufox-fp.d.ts +2 -0
- package/dist/commands/camoufox-fp.js +86 -0
- package/dist/commands/camoufox-fp.js.map +1 -0
- package/dist/commands/oauth.d.ts +2 -0
- package/dist/commands/oauth.js +170 -0
- package/dist/commands/oauth.js.map +1 -0
- package/dist/commands/provider-update.js +439 -2
- package/dist/commands/provider-update.js.map +1 -1
- package/dist/commands/quota-status.d.ts +2 -0
- package/dist/commands/quota-status.js +80 -0
- package/dist/commands/quota-status.js.map +1 -0
- package/dist/commands/token-daemon.js +12 -1
- package/dist/commands/token-daemon.js.map +1 -1
- package/dist/config/provider-v2-loader.d.ts +16 -0
- package/dist/config/provider-v2-loader.js +84 -0
- package/dist/config/provider-v2-loader.js.map +1 -0
- package/dist/config/routecodex-config-loader.js +27 -4
- package/dist/config/routecodex-config-loader.js.map +1 -1
- package/dist/config/system-prompts/codex-cli.txt +1 -0
- package/dist/config/virtual-router-builder.d.ts +9 -0
- package/dist/config/virtual-router-builder.js +34 -0
- package/dist/config/virtual-router-builder.js.map +1 -0
- package/dist/config/virtual-router-types.d.ts +25 -0
- package/dist/config/virtual-router-types.js +30 -0
- package/dist/config/virtual-router-types.js.map +1 -0
- package/dist/manager/index.d.ts +10 -0
- package/dist/manager/index.js +27 -0
- package/dist/manager/index.js.map +1 -0
- package/dist/manager/modules/health/index.d.ts +22 -0
- package/dist/manager/modules/health/index.js +82 -0
- package/dist/manager/modules/health/index.js.map +1 -0
- package/dist/manager/modules/quota/index.d.ts +57 -0
- package/dist/manager/modules/quota/index.js +426 -0
- package/dist/manager/modules/quota/index.js.map +1 -0
- package/dist/manager/modules/routing/index.d.ts +17 -0
- package/dist/manager/modules/routing/index.js +61 -0
- package/dist/manager/modules/routing/index.js.map +1 -0
- package/dist/manager/modules/token/index.d.ts +10 -0
- package/dist/manager/modules/token/index.js +58 -0
- package/dist/manager/modules/token/index.js.map +1 -0
- package/dist/manager/storage/base-store.d.ts +6 -0
- package/dist/manager/storage/base-store.js +2 -0
- package/dist/manager/storage/base-store.js.map +1 -0
- package/dist/manager/storage/file-store.d.ts +25 -0
- package/dist/manager/storage/file-store.js +117 -0
- package/dist/manager/storage/file-store.js.map +1 -0
- package/dist/manager/types.d.ts +9 -0
- package/dist/manager/types.js +2 -0
- package/dist/manager/types.js.map +1 -0
- package/dist/message-center/index.d.ts +5 -0
- package/dist/message-center/index.js +6 -0
- package/dist/message-center/index.js.map +1 -0
- package/dist/message-center/message-center.d.ts +93 -0
- package/dist/message-center/message-center.js +189 -0
- package/dist/message-center/message-center.js.map +1 -0
- package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -0
- package/dist/providers/auth/antigravity-userinfo-helper.js +102 -0
- package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/iflow-cookie-auth.d.ts +27 -0
- package/dist/providers/auth/iflow-cookie-auth.js +209 -0
- package/dist/providers/auth/iflow-cookie-auth.js.map +1 -0
- package/dist/providers/auth/oauth-lifecycle.js +29 -22
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/token-scanner/index.js +16 -1
- package/dist/providers/auth/token-scanner/index.js.map +1 -1
- package/dist/providers/core/config/camoufox-launcher.d.ts +16 -0
- package/dist/providers/core/config/camoufox-launcher.js +314 -0
- package/dist/providers/core/config/camoufox-launcher.js.map +1 -0
- package/dist/providers/core/config/oauth-flows.d.ts +9 -0
- package/dist/providers/core/config/oauth-flows.js +50 -19
- package/dist/providers/core/config/oauth-flows.js.map +1 -1
- package/dist/providers/core/config/provider-oauth-configs.d.ts +6 -0
- package/dist/providers/core/config/provider-oauth-configs.js +12 -0
- package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +26 -3
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/antigravity-quota-client.d.ts +10 -0
- package/dist/providers/core/runtime/antigravity-quota-client.js +88 -0
- package/dist/providers/core/runtime/antigravity-quota-client.js.map +1 -0
- package/dist/providers/core/runtime/base-provider.d.ts +2 -1
- package/dist/providers/core/runtime/base-provider.js +93 -34
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +42 -10
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.js +24 -0
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +0 -3
- package/dist/providers/core/runtime/http-transport-provider.js +32 -136
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-error-classifier.js +18 -10
- package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
- package/dist/providers/core/runtime/rate-limit-manager.d.ts +6 -0
- package/dist/providers/core/runtime/rate-limit-manager.js +23 -0
- package/dist/providers/core/runtime/rate-limit-manager.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 +3 -2
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-device-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-hybrid-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-hybrid-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-hybrid-flow.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +43 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.js +4 -4
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +13 -1
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +5 -0
- package/dist/scripts/camoufox/gen-fingerprint-env.py +171 -0
- package/dist/scripts/camoufox/launch-auth.mjs +617 -0
- package/dist/server/runtime/http-server/executor-provider.d.ts +1 -0
- package/dist/server/runtime/http-server/executor-provider.js +26 -0
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/executor-response.d.ts +16 -0
- package/dist/server/runtime/http-server/executor-response.js +164 -0
- package/dist/server/runtime/http-server/executor-response.js.map +1 -0
- package/dist/server/runtime/http-server/index.d.ts +1 -0
- package/dist/server/runtime/http-server/index.js +88 -53
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +5 -19
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +2 -0
- package/dist/server/runtime/http-server/routes.js +33 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +1 -0
- package/dist/server/utils/client-connection-state.d.ts +8 -0
- package/dist/server/utils/client-connection-state.js +52 -0
- package/dist/server/utils/client-connection-state.js.map +1 -0
- package/dist/server/utils/request-id-manager.js +21 -3
- package/dist/server/utils/request-id-manager.js.map +1 -1
- package/dist/token-daemon/history-store.d.ts +2 -0
- package/dist/token-daemon/history-store.js +6 -2
- package/dist/token-daemon/history-store.js.map +1 -1
- package/dist/token-daemon/index.js +36 -5
- package/dist/token-daemon/index.js.map +1 -1
- package/dist/token-daemon/leader-lock.d.ts +11 -0
- package/dist/token-daemon/leader-lock.js +79 -0
- package/dist/token-daemon/leader-lock.js.map +1 -0
- package/dist/token-daemon/message-bus-integrator.d.ts +98 -0
- package/dist/token-daemon/message-bus-integrator.js +144 -0
- package/dist/token-daemon/message-bus-integrator.js.map +1 -0
- package/dist/token-daemon/provider-registry.d.ts +22 -0
- package/dist/token-daemon/provider-registry.js +201 -0
- package/dist/token-daemon/provider-registry.js.map +1 -0
- package/dist/token-daemon/token-daemon.d.ts +8 -0
- package/dist/token-daemon/token-daemon.js +196 -11
- package/dist/token-daemon/token-daemon.js.map +1 -1
- package/dist/token-portal/local-token-portal.d.ts +1 -0
- package/dist/token-portal/local-token-portal.js +18 -0
- package/dist/token-portal/local-token-portal.js.map +1 -1
- package/dist/token-portal/render.js +1 -0
- package/dist/token-portal/render.js.map +1 -1
- package/dist/tools/error-log.d.ts +31 -0
- package/dist/tools/error-log.js +117 -0
- package/dist/tools/error-log.js.map +1 -0
- package/dist/tools/stats-request-events.d.ts +2 -0
- package/dist/tools/stats-request-events.js +16 -0
- package/dist/tools/stats-request-events.js.map +1 -0
- package/dist/tools/stats-usage.d.ts +31 -0
- package/dist/tools/stats-usage.js +206 -0
- package/dist/tools/stats-usage.js.map +1 -0
- package/package.json +8 -4
- package/scripts/analyze-codex-error-failures.mjs +109 -0
- package/scripts/camoufox/gen-fingerprint-env.py +171 -0
- package/scripts/camoufox/launch-auth.mjs +617 -0
- package/scripts/classify-codex-samples.mjs +251 -0
- package/scripts/cleanup-codex-error-samples.mjs +88 -0
- package/scripts/compare-codex-rccx.mjs +268 -0
- package/scripts/copy-compat-assets.mjs +18 -0
- package/scripts/install-release.sh +1 -1
- package/scripts/local-replay-openai-response.mjs +1 -2
- package/scripts/pack-mode.mjs +16 -6
- package/scripts/replay-codex-sample.mjs +24 -2
- package/scripts/responses-compare-server.mjs +119 -0
- package/scripts/verify-apply-patch.mjs +28 -17
- package/scripts/verify-codex-error-samples.mjs +99 -0
- package/scripts/verify-e2e-toolcall.mjs +19 -4
- package/scripts/virtual-router-shadow-v2-real.mjs +143 -0
- package/scripts/virtual-router-shadow-v2.mjs +122 -0
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
|
|
8
8
|
const DEFAULT_BASE_URL = process.env.ROUTECODEX_BASE || 'http://127.0.0.1:5555';
|
|
9
9
|
const DEFAULT_API_KEY = process.env.ROUTECODEX_API_KEY || 'routecodex-test';
|
|
10
|
+
const HEADER_DENYLIST = new Set(['authorization', 'content-length', 'host']);
|
|
10
11
|
|
|
11
12
|
function usage() {
|
|
12
13
|
console.log(`Usage:
|
|
@@ -63,6 +64,25 @@ function detectStream(doc, requestBody) {
|
|
|
63
64
|
return false;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
function extractSampleHeaders(doc) {
|
|
68
|
+
const raw = doc?.headers;
|
|
69
|
+
if (!raw || typeof raw !== 'object') {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const headers = {};
|
|
73
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
74
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const lowered = key.toLowerCase();
|
|
78
|
+
if (HEADER_DENYLIST.has(lowered)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
headers[key] = value;
|
|
82
|
+
}
|
|
83
|
+
return headers;
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
async function readSse(response) {
|
|
67
87
|
const reader = response.body?.getReader();
|
|
68
88
|
if (!reader) throw new Error('Response is not streamable');
|
|
@@ -98,15 +118,17 @@ async function main() {
|
|
|
98
118
|
const baseDir = path.dirname(samplePath);
|
|
99
119
|
const runDir = path.join(baseDir, 'runs', requestId, label);
|
|
100
120
|
ensureDir(runDir);
|
|
121
|
+
const sampleHeaders = extractSampleHeaders(sample);
|
|
101
122
|
|
|
102
123
|
const baseUrl = opts.base.replace(/\/$/, '');
|
|
103
124
|
const targetUrl = `${baseUrl}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
|
|
104
125
|
const headers = {
|
|
126
|
+
...sampleHeaders,
|
|
105
127
|
'Content-Type': 'application/json',
|
|
106
128
|
'Accept': wantsSse ? 'text/event-stream' : 'application/json',
|
|
107
129
|
'Authorization': `Bearer ${opts.key}`,
|
|
108
|
-
'OpenAI-Beta': 'responses-2024-12-17',
|
|
109
|
-
'X-Route-Hint': 'default'
|
|
130
|
+
'OpenAI-Beta': sampleHeaders['OpenAI-Beta'] || 'responses-2024-12-17',
|
|
131
|
+
'X-Route-Hint': sampleHeaders['X-Route-Hint'] || sampleHeaders['x-route-hint'] || 'default'
|
|
110
132
|
};
|
|
111
133
|
|
|
112
134
|
console.log(`[replay-codex-sample] ${endpoint} → ${targetUrl} (requestId=${requestId})`);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import cors from 'cors';
|
|
4
|
+
import fetch from 'node-fetch';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
|
|
7
|
+
const PORT = Number(process.env.COMPARE_PORT || process.argv[2]) || 5555;
|
|
8
|
+
const TARGET_BASE = process.env.CRS_BASE_URL?.trim() || 'https://capi.quan2go.com/openai';
|
|
9
|
+
const API_KEY = process.env.CRS_API_KEY?.trim();
|
|
10
|
+
const CODEx_UA = process.env.CODEX_UA?.trim() || 'codex_cli_rs/0.79.0 (Mac OS 15.7.3; arm64) iTerm.app/3.6.5';
|
|
11
|
+
const OPENAI_BETA = process.env.OPENAI_BETA_VERSION?.trim() || 'responses-2024-12-17';
|
|
12
|
+
|
|
13
|
+
if (!API_KEY) {
|
|
14
|
+
console.error('[compare-server] Missing CRS_API_KEY environment variable.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const app = express();
|
|
19
|
+
app.use(cors());
|
|
20
|
+
app.use(express.json({ limit: '4mb' }));
|
|
21
|
+
|
|
22
|
+
function buildCommonHeaders() {
|
|
23
|
+
return {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'OpenAI-Beta': OPENAI_BETA,
|
|
26
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
27
|
+
Accept: 'text/event-stream'
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildConversationId(req) {
|
|
32
|
+
return (
|
|
33
|
+
req.headers['conversation_id'] ||
|
|
34
|
+
req.headers['Conversation-Id'] ||
|
|
35
|
+
req.headers['session_id'] ||
|
|
36
|
+
randomUUID()
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildRequestHeaders(mode, req) {
|
|
41
|
+
const base = buildCommonHeaders();
|
|
42
|
+
if (mode === 'chat') {
|
|
43
|
+
return {
|
|
44
|
+
...base,
|
|
45
|
+
'User-Agent': CODEx_UA,
|
|
46
|
+
originator: 'codex_cli_rs',
|
|
47
|
+
conversation_id: buildConversationId(req),
|
|
48
|
+
session_id: buildConversationId(req)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const inboundUa = req.headers['user-agent'];
|
|
52
|
+
return {
|
|
53
|
+
...base,
|
|
54
|
+
'User-Agent': inboundUa || 'curl/8.5.0'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function forward(mode, body, req) {
|
|
59
|
+
const targetUrl = `${TARGET_BASE.replace(/\/$/, '')}/responses`;
|
|
60
|
+
const headers = buildRequestHeaders(mode, req);
|
|
61
|
+
const startedAt = Date.now();
|
|
62
|
+
const response = await fetch(targetUrl, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers,
|
|
65
|
+
body: JSON.stringify(body)
|
|
66
|
+
});
|
|
67
|
+
const rawText = await response.text();
|
|
68
|
+
const durationMs = Date.now() - startedAt;
|
|
69
|
+
let parsed;
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(rawText);
|
|
72
|
+
} catch {
|
|
73
|
+
parsed = rawText;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
mode,
|
|
77
|
+
ok: response.ok,
|
|
78
|
+
status: response.status,
|
|
79
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
80
|
+
durationMs,
|
|
81
|
+
body: parsed,
|
|
82
|
+
targetUrl
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handler(mode) {
|
|
87
|
+
return async (req, res) => {
|
|
88
|
+
try {
|
|
89
|
+
const result = await forward(mode, req.body, req);
|
|
90
|
+
res.status(result.status).json(result);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
res.status(500).json({
|
|
93
|
+
mode,
|
|
94
|
+
error: error instanceof Error ? error.message : String(error)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
app.post('/passthrough/v1/responses', handler('passthrough'));
|
|
101
|
+
app.post('/chat/v1/responses', handler('chat'));
|
|
102
|
+
app.post('/compare/v1/responses', async (req, res) => {
|
|
103
|
+
try {
|
|
104
|
+
const [passthrough, chat] = await Promise.all([
|
|
105
|
+
forward('passthrough', req.body, req),
|
|
106
|
+
forward('chat', req.body, req)
|
|
107
|
+
]);
|
|
108
|
+
res.json({ passthrough, chat });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
res.status(500).json({
|
|
111
|
+
error: error instanceof Error ? error.message : String(error)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
app.listen(PORT, () => {
|
|
117
|
+
console.log('[compare-server] listening on http://127.0.0.1:' + PORT);
|
|
118
|
+
console.log('[compare-server] targets PASSTHROUGH vs CHAT at', `${TARGET_BASE}/responses`);
|
|
119
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Minimal apply_patch governance verifier (CI client)
|
|
4
4
|
*
|
|
5
5
|
* 直接调用 llmswitch-core 的文本 → tool_calls → 校验链路,
|
|
6
|
-
*
|
|
6
|
+
* 用结构化 apply_patch payload(changes 数组)触发校验。
|
|
7
7
|
*/
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -18,7 +18,7 @@ async function loadCoreModule(subpath) {
|
|
|
18
18
|
return importCoreModule(subpath);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async function runApplyPatchTextCase(label,
|
|
21
|
+
async function runApplyPatchTextCase(label, payloadText) {
|
|
22
22
|
const { normalizeAssistantTextToToolCalls } = await loadCoreModule(
|
|
23
23
|
'conversion/shared/text-markup-normalizer'
|
|
24
24
|
);
|
|
@@ -29,7 +29,7 @@ async function runApplyPatchTextCase(label, patchText) {
|
|
|
29
29
|
|
|
30
30
|
const message = {
|
|
31
31
|
role: 'assistant',
|
|
32
|
-
content:
|
|
32
|
+
content: payloadText
|
|
33
33
|
};
|
|
34
34
|
const normalizedMsg = normalizeAssistantTextToToolCalls(message);
|
|
35
35
|
const toolCalls = normalizedMsg?.tool_calls;
|
|
@@ -106,22 +106,33 @@ async function main() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
const plainJson = JSON.stringify({
|
|
110
|
+
file: 'src/demo.ts',
|
|
111
|
+
changes: [
|
|
112
|
+
{
|
|
113
|
+
kind: 'insert_after',
|
|
114
|
+
anchor: 'const foo = 1;',
|
|
115
|
+
lines: ['const bar = 2;']
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}, null, 2);
|
|
114
119
|
|
|
115
|
-
const
|
|
116
|
-
'```
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
const fencedJson =
|
|
121
|
+
'```json\n' +
|
|
122
|
+
JSON.stringify({
|
|
123
|
+
file: 'src/demo-fenced.ts',
|
|
124
|
+
changes: [
|
|
125
|
+
{
|
|
126
|
+
kind: 'replace',
|
|
127
|
+
target: 'const status = "old";',
|
|
128
|
+
lines: ['const status = "new";']
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}, null, 2) +
|
|
132
|
+
'\n```';
|
|
122
133
|
|
|
123
|
-
await runApplyPatchTextCase('plain',
|
|
124
|
-
await runApplyPatchTextCase('fenced',
|
|
134
|
+
await runApplyPatchTextCase('plain', plainJson);
|
|
135
|
+
await runApplyPatchTextCase('fenced', fencedJson);
|
|
125
136
|
|
|
126
137
|
console.log('✅ verify-apply-patch: text→tool_calls pipeline passed');
|
|
127
138
|
} catch (error) {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight regression guard for previously captured Codex error样本.
|
|
5
|
+
*
|
|
6
|
+
* 目标:
|
|
7
|
+
* - 扫描 ~/.routecodex/errorsamples 目录下的 *.json 文件;
|
|
8
|
+
* - 如果仍包含已知错误模式(例如 exec_command 参数解析失败、apply_patch 验证失败),则视为回归;
|
|
9
|
+
* - 目录不存在或为空时直接跳过(不影响构建)。
|
|
10
|
+
*
|
|
11
|
+
* 注意:
|
|
12
|
+
* - 该脚本只是“错误字符串回归检测”,不会主动重放请求;
|
|
13
|
+
* - 每当修复一批问题后,应将修复后的样本覆盖到 ~/.routecodex/errorsamples,再由本脚本做守护。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
|
|
20
|
+
const ROOT =
|
|
21
|
+
process.env.ROUTECODEX_ERROR_SAMPLES_DIR &&
|
|
22
|
+
process.env.ROUTECODEX_ERROR_SAMPLES_DIR.trim().length
|
|
23
|
+
? path.resolve(process.env.ROUTECODEX_ERROR_SAMPLES_DIR)
|
|
24
|
+
: path.join(os.homedir(), '.routecodex', 'errorsamples');
|
|
25
|
+
|
|
26
|
+
const ERROR_PATTERNS = [
|
|
27
|
+
'failed to parse exec_command arguments',
|
|
28
|
+
'apply_patch verification failed'
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
async function fileExists(p) {
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(p);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function collectSampleFiles(rootDir) {
|
|
41
|
+
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
42
|
+
const files = [];
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
if (!entry.isFile()) continue;
|
|
45
|
+
if (!entry.name.toLowerCase().endsWith('.json')) continue;
|
|
46
|
+
files.push(path.join(rootDir, entry.name));
|
|
47
|
+
}
|
|
48
|
+
return files;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function checkFile(filePath) {
|
|
52
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
53
|
+
const hits = [];
|
|
54
|
+
for (const pattern of ERROR_PATTERNS) {
|
|
55
|
+
if (raw.includes(pattern)) {
|
|
56
|
+
hits.push(pattern);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return hits;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
if (!(await fileExists(ROOT))) {
|
|
64
|
+
console.log(`[verify:errorsamples] skip (directory not found: ${ROOT})`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const files = await collectSampleFiles(ROOT);
|
|
69
|
+
if (!files.length) {
|
|
70
|
+
console.log(`[verify:errorsamples] skip (no *.json samples under ${ROOT})`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`[verify:errorsamples] scanning ${files.length} sample(s) under ${ROOT}`);
|
|
75
|
+
|
|
76
|
+
const failures = [];
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
const hits = await checkFile(file);
|
|
79
|
+
if (hits.length) {
|
|
80
|
+
failures.push({ file, hits });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!failures.length) {
|
|
85
|
+
console.log('[verify:errorsamples] ✅ no known error patterns found');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.error('[verify:errorsamples] ❌ detected legacy error patterns in samples:');
|
|
90
|
+
for (const item of failures) {
|
|
91
|
+
console.error(` - ${path.basename(item.file)} → ${item.hits.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main().catch((error) => {
|
|
97
|
+
console.error('[verify:errorsamples] failed:', error);
|
|
98
|
+
process.exit(99);
|
|
99
|
+
});
|
|
@@ -12,7 +12,10 @@ if (String(process.env.ROUTECODEX_VERIFY_SKIP || '').trim() === '1') {
|
|
|
12
12
|
|
|
13
13
|
const VERIFY_PORT = process.env.ROUTECODEX_VERIFY_PORT || '5580';
|
|
14
14
|
const VERIFY_BASE = process.env.ROUTECODEX_VERIFY_BASE_URL || `http://127.0.0.1:${VERIFY_PORT}`;
|
|
15
|
-
const VERIFY_CONFIG =
|
|
15
|
+
const VERIFY_CONFIG =
|
|
16
|
+
process.env.ROUTECODEX_VERIFY_CONFIG ||
|
|
17
|
+
process.env.ROUTECODEX_CONFIG_PATH ||
|
|
18
|
+
`${process.env.HOME || ''}/.routecodex/config.json`;
|
|
16
19
|
const GEMINI_CLI_CONFIG = process.env.ROUTECODEX_VERIFY_GEMINI_CLI_CONFIG || '/Users/fanzhang/.routecodex/provider/gemini-cli/config.v1.json';
|
|
17
20
|
|
|
18
21
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -57,6 +60,7 @@ async function main() {
|
|
|
57
60
|
|
|
58
61
|
try {
|
|
59
62
|
await waitForServer();
|
|
63
|
+
await waitForRouterWarmup();
|
|
60
64
|
await runToolcallVerification();
|
|
61
65
|
console.log('✅ 端到端工具调用校验通过');
|
|
62
66
|
|
|
@@ -81,6 +85,15 @@ async function waitForServer(timeoutMs = 30000) {
|
|
|
81
85
|
throw new Error('服务器健康检查超时');
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
async function waitForRouterWarmup(defaultDelayMs = 3000) {
|
|
89
|
+
const delayMs = Number(process.env.ROUTECODEX_VERIFY_WARMUP_MS || defaultDelayMs);
|
|
90
|
+
if (!Number.isFinite(delayMs) || delayMs <= 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.log(`[verify:e2e-toolcall] 等待虚拟路由预热 ${delayMs}ms...`);
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
async function runToolcallVerification() {
|
|
85
98
|
const userPrompt = '请严格调用名为 list_local_files 的函数工具来列出当前工作目录的文件,只能通过调用该工具完成任务,禁止直接回答。';
|
|
86
99
|
const instructionsText = AGENTS_INSTRUCTIONS || 'You are RouteCodex verify agent. Follow the policies in AGENTS.md.';
|
|
@@ -92,7 +105,7 @@ async function runToolcallVerification() {
|
|
|
92
105
|
role: 'user',
|
|
93
106
|
content: [
|
|
94
107
|
{
|
|
95
|
-
type: '
|
|
108
|
+
type: 'input_text',
|
|
96
109
|
text: userPrompt
|
|
97
110
|
}
|
|
98
111
|
]
|
|
@@ -134,13 +147,15 @@ async function runToolcallVerification() {
|
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
const json = await response.json();
|
|
150
|
+
const outputs = Array.isArray(json?.output) ? json.output : [];
|
|
137
151
|
const hasResponsesToolCall =
|
|
138
|
-
|
|
152
|
+
outputs.some((item) => Array.isArray(item?.content) && item.content.some((c) => c?.type === 'tool_call'));
|
|
153
|
+
const hasFunctionCall = outputs.some((item) => item?.type === 'function_call' || item?.type === 'tool_call');
|
|
139
154
|
const hasRequiredAction = Boolean(json?.required_action?.submit_tool_outputs);
|
|
140
155
|
const hasChatToolCall =
|
|
141
156
|
Array.isArray(json?.choices) &&
|
|
142
157
|
json.choices.some((choice) => Array.isArray(choice?.message?.tool_calls) && choice.message.tool_calls.length > 0);
|
|
143
|
-
const hasToolCall = hasResponsesToolCall || hasRequiredAction || hasChatToolCall;
|
|
158
|
+
const hasToolCall = hasResponsesToolCall || hasFunctionCall || hasRequiredAction || hasChatToolCall;
|
|
144
159
|
|
|
145
160
|
if (!hasToolCall) {
|
|
146
161
|
console.error('[verify:e2e-toolcall] Unexpected response:', JSON.stringify(json, null, 2));
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Virtual Router v1/v2 shadow comparison against real user config.
|
|
5
|
+
*
|
|
6
|
+
* - 读取当前用户配置(~/.routecodex/config.json 或 env 指定路径);
|
|
7
|
+
* - 使用 buildVirtualRouterInputFromUserConfig 生成 v1 视图;
|
|
8
|
+
* - 使用 buildVirtualRouterInputV2 生成 v2 视图(从 ~/.routecodex/provider 加载 provider v2);
|
|
9
|
+
* - 对比 providers / routing 结构并打印差异摘要。
|
|
10
|
+
*
|
|
11
|
+
* 依赖前提:dist/ 已通过 `npm run build` 或等价 tsc 编译生成。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs/promises';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
async function resolveConfigPath() {
|
|
20
|
+
const explicit = process.env.ROUTECODEX_CONFIG || process.env.RCC_CONFIG;
|
|
21
|
+
if (explicit && explicit.trim()) {
|
|
22
|
+
return path.resolve(explicit.trim());
|
|
23
|
+
}
|
|
24
|
+
return path.join(os.homedir(), '.routecodex', 'config.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function loadUserConfig() {
|
|
28
|
+
const configPath = await resolveConfigPath();
|
|
29
|
+
const raw = await fs.readFile(configPath, 'utf8');
|
|
30
|
+
const parsed = raw.trim() ? JSON.parse(raw) : {};
|
|
31
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeKeys(obj) {
|
|
38
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return {};
|
|
39
|
+
return Object.fromEntries(Object.entries(obj).filter(([k]) => typeof k === 'string'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function diffKeys(a, b) {
|
|
43
|
+
const aKeys = new Set(Object.keys(a));
|
|
44
|
+
const bKeys = new Set(Object.keys(b));
|
|
45
|
+
const onlyA = [...aKeys].filter((k) => !bKeys.has(k));
|
|
46
|
+
const onlyB = [...bKeys].filter((k) => !aKeys.has(k));
|
|
47
|
+
return { onlyA, onlyB };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function main() {
|
|
51
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
52
|
+
const __dirname = path.dirname(__filename);
|
|
53
|
+
|
|
54
|
+
let buildVirtualRouterInputFromUserConfig;
|
|
55
|
+
let buildVirtualRouterInputV2;
|
|
56
|
+
try {
|
|
57
|
+
({ buildVirtualRouterInputFromUserConfig } = await import(
|
|
58
|
+
path.join(__dirname, '../dist/config/virtual-router-types.js')
|
|
59
|
+
));
|
|
60
|
+
({ buildVirtualRouterInputV2 } = await import(
|
|
61
|
+
path.join(__dirname, '../dist/config/virtual-router-builder.js')
|
|
62
|
+
));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.error(
|
|
66
|
+
'[virtual-router-shadow-v2-real] Failed to load dist modules. Please run `npm run build` or tsc first.',
|
|
67
|
+
error instanceof Error ? error.message : String(error)
|
|
68
|
+
);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let userConfig;
|
|
74
|
+
try {
|
|
75
|
+
userConfig = await loadUserConfig();
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.error(
|
|
79
|
+
'[virtual-router-shadow-v2-real] Failed to load user config:',
|
|
80
|
+
error instanceof Error ? error.message : String(error)
|
|
81
|
+
);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const v1Input = buildVirtualRouterInputFromUserConfig(userConfig);
|
|
87
|
+
const v2Input = await buildVirtualRouterInputV2(userConfig);
|
|
88
|
+
|
|
89
|
+
const v1Providers = normalizeKeys(v1Input.providers || {});
|
|
90
|
+
const v2Providers = normalizeKeys(v2Input.providers || {});
|
|
91
|
+
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.log('[virtual-router-shadow-v2-real] v1 provider keys:', Object.keys(v1Providers));
|
|
94
|
+
// eslint-disable-next-line no-console
|
|
95
|
+
console.log('[virtual-router-shadow-v2-real] v2 provider keys:', Object.keys(v2Providers));
|
|
96
|
+
|
|
97
|
+
const providerKeyDiff = diffKeys(v1Providers, v2Providers);
|
|
98
|
+
const providersEqual =
|
|
99
|
+
providerKeyDiff.onlyA.length === 0 && providerKeyDiff.onlyB.length === 0;
|
|
100
|
+
|
|
101
|
+
// eslint-disable-next-line no-console
|
|
102
|
+
console.log('[virtual-router-shadow-v2-real] providers key diff:', providerKeyDiff);
|
|
103
|
+
|
|
104
|
+
let providerPayloadEqual = providersEqual;
|
|
105
|
+
if (providersEqual) {
|
|
106
|
+
for (const key of Object.keys(v1Providers)) {
|
|
107
|
+
const a = v1Providers[key];
|
|
108
|
+
const b = v2Providers[key];
|
|
109
|
+
if (JSON.stringify(a) !== JSON.stringify(b)) {
|
|
110
|
+
providerPayloadEqual = false;
|
|
111
|
+
// eslint-disable-next-line no-console
|
|
112
|
+
console.log(
|
|
113
|
+
`[virtual-router-shadow-v2-real] provider payload mismatch for "${key}" (showing v1/v2 JSON):`
|
|
114
|
+
);
|
|
115
|
+
// eslint-disable-next-line no-console
|
|
116
|
+
console.log('v1:', JSON.stringify(a, null, 2));
|
|
117
|
+
// eslint-disable-next-line no-console
|
|
118
|
+
console.log('v2:', JSON.stringify(b, null, 2));
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const routingEqual = JSON.stringify(v1Input.routing || {}) === JSON.stringify(v2Input.routing || {});
|
|
125
|
+
|
|
126
|
+
// eslint-disable-next-line no-console
|
|
127
|
+
console.log('[virtual-router-shadow-v2-real] providers keys equal:', providersEqual);
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.log('[virtual-router-shadow-v2-real] providers payload equal:', providerPayloadEqual);
|
|
130
|
+
// eslint-disable-next-line no-console
|
|
131
|
+
console.log('[virtual-router-shadow-v2-real] routing equal:', routingEqual);
|
|
132
|
+
|
|
133
|
+
if (!providersEqual || !providerPayloadEqual || !routingEqual) {
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
main().catch((error) => {
|
|
139
|
+
// eslint-disable-next-line no-console
|
|
140
|
+
console.error('[virtual-router-shadow-v2-real] failed:', error);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Virtual Router v1/v2 shadow comparison script.
|
|
5
|
+
*
|
|
6
|
+
* 构造一个简化的 userConfig + 临时 provider-root:
|
|
7
|
+
* - 使用 buildVirtualRouterInputFromUserConfig 生成 v1 视图;
|
|
8
|
+
* - 使用 buildVirtualRouterInputV2 生成 v2 视图;
|
|
9
|
+
* 然后比较 providers / routing 结构是否一致。
|
|
10
|
+
*
|
|
11
|
+
* 依赖前提:dist/ 已通过 `npm run build` 生成。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs/promises';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = path.dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// 尝试从 dist 导入构建函数
|
|
24
|
+
let buildVirtualRouterInputFromUserConfig;
|
|
25
|
+
let buildVirtualRouterInputV2;
|
|
26
|
+
try {
|
|
27
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
28
|
+
({ buildVirtualRouterInputFromUserConfig } = await import(
|
|
29
|
+
path.join(__dirname, '../dist/config/virtual-router-types.js')
|
|
30
|
+
));
|
|
31
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
32
|
+
({ buildVirtualRouterInputV2 } = await import(
|
|
33
|
+
path.join(__dirname, '../dist/config/virtual-router-builder.js')
|
|
34
|
+
));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.error(
|
|
38
|
+
'[virtual-router-shadow-v2] Failed to load dist modules. Please run `npm run build` first.',
|
|
39
|
+
error instanceof Error ? error.message : String(error)
|
|
40
|
+
);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 构造简化 userConfig(v1 风格:virtualrouter.providers + routing)
|
|
46
|
+
const userConfig = {
|
|
47
|
+
virtualrouter: {
|
|
48
|
+
providers: {
|
|
49
|
+
demo: {
|
|
50
|
+
type: 'mock-provider',
|
|
51
|
+
baseURL: 'https://demo.example.com',
|
|
52
|
+
models: {
|
|
53
|
+
'mock-1': { maxTokens: 1024 }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
routing: {
|
|
58
|
+
default: [
|
|
59
|
+
{
|
|
60
|
+
id: 'primary',
|
|
61
|
+
targets: ['demo.mock-1']
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// 构造临时 provider-root,并写入 v2 风格配置
|
|
69
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'vr-shadow-v2-'));
|
|
70
|
+
const providerDir = path.join(tempRoot, 'demo');
|
|
71
|
+
await fs.mkdir(providerDir, { recursive: true });
|
|
72
|
+
const v2Payload = {
|
|
73
|
+
version: '2.0.0',
|
|
74
|
+
providerId: 'demo',
|
|
75
|
+
provider: userConfig.virtualrouter.providers.demo
|
|
76
|
+
};
|
|
77
|
+
await fs.writeFile(
|
|
78
|
+
path.join(providerDir, 'config.v2.json'),
|
|
79
|
+
`${JSON.stringify(v2Payload, null, 2)}\n`,
|
|
80
|
+
'utf8'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// 构建 v1/v2 视图
|
|
84
|
+
const v1Input = buildVirtualRouterInputFromUserConfig(userConfig);
|
|
85
|
+
const v2Input = await buildVirtualRouterInputV2(userConfig, tempRoot);
|
|
86
|
+
|
|
87
|
+
// 比较 providers
|
|
88
|
+
const v1Providers = Object.keys(v1Input.providers || {});
|
|
89
|
+
const v2Providers = Object.keys(v2Input.providers || {});
|
|
90
|
+
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log('[virtual-router-shadow-v2] v1 providers:', v1Providers);
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.log('[virtual-router-shadow-v2] v2 providers:', v2Providers);
|
|
95
|
+
|
|
96
|
+
const providersEqual =
|
|
97
|
+
v1Providers.length === v2Providers.length &&
|
|
98
|
+
v1Providers.every((id) => v2Providers.includes(id)) &&
|
|
99
|
+
v1Providers.every((id) => {
|
|
100
|
+
const v1 = v1Input.providers[id];
|
|
101
|
+
const v2 = v2Input.providers[id];
|
|
102
|
+
return JSON.stringify(v1) === JSON.stringify(v2);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// 比较 routing
|
|
106
|
+
const routingEqual = JSON.stringify(v1Input.routing) === JSON.stringify(v2Input.routing);
|
|
107
|
+
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
console.log('[virtual-router-shadow-v2] providers equal:', providersEqual);
|
|
110
|
+
// eslint-disable-next-line no-console
|
|
111
|
+
console.log('[virtual-router-shadow-v2] routing equal:', routingEqual);
|
|
112
|
+
|
|
113
|
+
if (!providersEqual || !routingEqual) {
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main().catch((error) => {
|
|
119
|
+
// eslint-disable-next-line no-console
|
|
120
|
+
console.error('[virtual-router-shadow-v2] failed:', error);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|