@jsonstudio/rcc 0.89.682 → 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.
Files changed (191) hide show
  1. package/dist/build-info.js +2 -2
  2. package/dist/cli.js +164 -116
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/anthropic/anthropic-protocol-client.js +42 -1
  5. package/dist/client/anthropic/anthropic-protocol-client.js.map +1 -1
  6. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +4 -1
  7. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  8. package/dist/commands/camoufox-backfill.d.ts +2 -0
  9. package/dist/commands/camoufox-backfill.js +33 -0
  10. package/dist/commands/camoufox-backfill.js.map +1 -0
  11. package/dist/commands/camoufox-fp.d.ts +2 -0
  12. package/dist/commands/camoufox-fp.js +86 -0
  13. package/dist/commands/camoufox-fp.js.map +1 -0
  14. package/dist/commands/oauth.d.ts +2 -0
  15. package/dist/commands/oauth.js +170 -0
  16. package/dist/commands/oauth.js.map +1 -0
  17. package/dist/commands/provider-update.js +439 -2
  18. package/dist/commands/provider-update.js.map +1 -1
  19. package/dist/commands/quota-status.d.ts +2 -0
  20. package/dist/commands/quota-status.js +80 -0
  21. package/dist/commands/quota-status.js.map +1 -0
  22. package/dist/commands/token-daemon.js +12 -1
  23. package/dist/commands/token-daemon.js.map +1 -1
  24. package/dist/config/provider-v2-loader.d.ts +16 -0
  25. package/dist/config/provider-v2-loader.js +84 -0
  26. package/dist/config/provider-v2-loader.js.map +1 -0
  27. package/dist/config/routecodex-config-loader.js +27 -4
  28. package/dist/config/routecodex-config-loader.js.map +1 -1
  29. package/dist/config/system-prompts/codex-cli.txt +1 -0
  30. package/dist/config/virtual-router-builder.d.ts +9 -0
  31. package/dist/config/virtual-router-builder.js +34 -0
  32. package/dist/config/virtual-router-builder.js.map +1 -0
  33. package/dist/config/virtual-router-types.d.ts +25 -0
  34. package/dist/config/virtual-router-types.js +30 -0
  35. package/dist/config/virtual-router-types.js.map +1 -0
  36. package/dist/manager/index.d.ts +10 -0
  37. package/dist/manager/index.js +27 -0
  38. package/dist/manager/index.js.map +1 -0
  39. package/dist/manager/modules/health/index.d.ts +22 -0
  40. package/dist/manager/modules/health/index.js +82 -0
  41. package/dist/manager/modules/health/index.js.map +1 -0
  42. package/dist/manager/modules/quota/index.d.ts +57 -0
  43. package/dist/manager/modules/quota/index.js +426 -0
  44. package/dist/manager/modules/quota/index.js.map +1 -0
  45. package/dist/manager/modules/routing/index.d.ts +17 -0
  46. package/dist/manager/modules/routing/index.js +61 -0
  47. package/dist/manager/modules/routing/index.js.map +1 -0
  48. package/dist/manager/modules/token/index.d.ts +10 -0
  49. package/dist/manager/modules/token/index.js +58 -0
  50. package/dist/manager/modules/token/index.js.map +1 -0
  51. package/dist/manager/storage/base-store.d.ts +6 -0
  52. package/dist/manager/storage/base-store.js +2 -0
  53. package/dist/manager/storage/base-store.js.map +1 -0
  54. package/dist/manager/storage/file-store.d.ts +25 -0
  55. package/dist/manager/storage/file-store.js +117 -0
  56. package/dist/manager/storage/file-store.js.map +1 -0
  57. package/dist/manager/types.d.ts +9 -0
  58. package/dist/manager/types.js +2 -0
  59. package/dist/manager/types.js.map +1 -0
  60. package/dist/message-center/index.d.ts +5 -0
  61. package/dist/message-center/index.js +6 -0
  62. package/dist/message-center/index.js.map +1 -0
  63. package/dist/message-center/message-center.d.ts +93 -0
  64. package/dist/message-center/message-center.js +189 -0
  65. package/dist/message-center/message-center.js.map +1 -0
  66. package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -0
  67. package/dist/providers/auth/antigravity-userinfo-helper.js +102 -0
  68. package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
  69. package/dist/providers/auth/iflow-cookie-auth.d.ts +27 -0
  70. package/dist/providers/auth/iflow-cookie-auth.js +209 -0
  71. package/dist/providers/auth/iflow-cookie-auth.js.map +1 -0
  72. package/dist/providers/auth/oauth-lifecycle.js +29 -22
  73. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  74. package/dist/providers/auth/token-scanner/index.js +16 -1
  75. package/dist/providers/auth/token-scanner/index.js.map +1 -1
  76. package/dist/providers/core/config/camoufox-launcher.d.ts +16 -0
  77. package/dist/providers/core/config/camoufox-launcher.js +314 -0
  78. package/dist/providers/core/config/camoufox-launcher.js.map +1 -0
  79. package/dist/providers/core/config/oauth-flows.d.ts +9 -0
  80. package/dist/providers/core/config/oauth-flows.js +50 -19
  81. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  82. package/dist/providers/core/config/provider-oauth-configs.d.ts +6 -0
  83. package/dist/providers/core/config/provider-oauth-configs.js +12 -0
  84. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  85. package/dist/providers/core/config/service-profiles.js +26 -3
  86. package/dist/providers/core/config/service-profiles.js.map +1 -1
  87. package/dist/providers/core/runtime/antigravity-quota-client.d.ts +10 -0
  88. package/dist/providers/core/runtime/antigravity-quota-client.js +88 -0
  89. package/dist/providers/core/runtime/antigravity-quota-client.js.map +1 -0
  90. package/dist/providers/core/runtime/base-provider.d.ts +2 -1
  91. package/dist/providers/core/runtime/base-provider.js +93 -34
  92. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  93. package/dist/providers/core/runtime/gemini-cli-http-provider.js +42 -10
  94. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  95. package/dist/providers/core/runtime/http-request-executor.js +24 -0
  96. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  97. package/dist/providers/core/runtime/http-transport-provider.d.ts +0 -3
  98. package/dist/providers/core/runtime/http-transport-provider.js +32 -136
  99. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  100. package/dist/providers/core/runtime/provider-error-classifier.js +18 -10
  101. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  102. package/dist/providers/core/runtime/rate-limit-manager.d.ts +6 -0
  103. package/dist/providers/core/runtime/rate-limit-manager.js +23 -0
  104. package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
  105. package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
  106. package/dist/providers/core/strategies/oauth-auth-code-flow.js +3 -2
  107. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  108. package/dist/providers/core/strategies/oauth-device-flow.d.ts +1 -0
  109. package/dist/providers/core/strategies/oauth-device-flow.js +3 -2
  110. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  111. package/dist/providers/core/strategies/oauth-hybrid-flow.d.ts +1 -0
  112. package/dist/providers/core/strategies/oauth-hybrid-flow.js +3 -2
  113. package/dist/providers/core/strategies/oauth-hybrid-flow.js.map +1 -1
  114. package/dist/providers/core/utils/http-client.js +43 -1
  115. package/dist/providers/core/utils/http-client.js.map +1 -1
  116. package/dist/providers/mock/mock-provider-runtime.js +4 -4
  117. package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
  118. package/dist/providers/profile/provider-profile-loader.js +13 -1
  119. package/dist/providers/profile/provider-profile-loader.js.map +1 -1
  120. package/dist/providers/profile/provider-profile.d.ts +5 -0
  121. package/dist/scripts/camoufox/gen-fingerprint-env.py +171 -0
  122. package/dist/scripts/camoufox/launch-auth.mjs +617 -0
  123. package/dist/server/runtime/http-server/executor-provider.d.ts +1 -0
  124. package/dist/server/runtime/http-server/executor-provider.js +26 -0
  125. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  126. package/dist/server/runtime/http-server/executor-response.d.ts +16 -0
  127. package/dist/server/runtime/http-server/executor-response.js +164 -0
  128. package/dist/server/runtime/http-server/executor-response.js.map +1 -0
  129. package/dist/server/runtime/http-server/index.d.ts +1 -0
  130. package/dist/server/runtime/http-server/index.js +88 -53
  131. package/dist/server/runtime/http-server/index.js.map +1 -1
  132. package/dist/server/runtime/http-server/request-executor.js +5 -19
  133. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  134. package/dist/server/runtime/http-server/routes.d.ts +2 -0
  135. package/dist/server/runtime/http-server/routes.js +33 -1
  136. package/dist/server/runtime/http-server/routes.js.map +1 -1
  137. package/dist/server/runtime/http-server/types.d.ts +1 -0
  138. package/dist/server/utils/client-connection-state.d.ts +8 -0
  139. package/dist/server/utils/client-connection-state.js +52 -0
  140. package/dist/server/utils/client-connection-state.js.map +1 -0
  141. package/dist/server/utils/request-id-manager.js +21 -3
  142. package/dist/server/utils/request-id-manager.js.map +1 -1
  143. package/dist/token-daemon/history-store.d.ts +2 -0
  144. package/dist/token-daemon/history-store.js +6 -2
  145. package/dist/token-daemon/history-store.js.map +1 -1
  146. package/dist/token-daemon/index.js +36 -5
  147. package/dist/token-daemon/index.js.map +1 -1
  148. package/dist/token-daemon/leader-lock.d.ts +11 -0
  149. package/dist/token-daemon/leader-lock.js +79 -0
  150. package/dist/token-daemon/leader-lock.js.map +1 -0
  151. package/dist/token-daemon/message-bus-integrator.d.ts +98 -0
  152. package/dist/token-daemon/message-bus-integrator.js +144 -0
  153. package/dist/token-daemon/message-bus-integrator.js.map +1 -0
  154. package/dist/token-daemon/provider-registry.d.ts +22 -0
  155. package/dist/token-daemon/provider-registry.js +201 -0
  156. package/dist/token-daemon/provider-registry.js.map +1 -0
  157. package/dist/token-daemon/token-daemon.d.ts +8 -0
  158. package/dist/token-daemon/token-daemon.js +196 -11
  159. package/dist/token-daemon/token-daemon.js.map +1 -1
  160. package/dist/token-portal/local-token-portal.d.ts +1 -0
  161. package/dist/token-portal/local-token-portal.js +18 -0
  162. package/dist/token-portal/local-token-portal.js.map +1 -1
  163. package/dist/token-portal/render.js +1 -0
  164. package/dist/token-portal/render.js.map +1 -1
  165. package/dist/tools/error-log.d.ts +31 -0
  166. package/dist/tools/error-log.js +117 -0
  167. package/dist/tools/error-log.js.map +1 -0
  168. package/dist/tools/stats-request-events.d.ts +2 -0
  169. package/dist/tools/stats-request-events.js +16 -0
  170. package/dist/tools/stats-request-events.js.map +1 -0
  171. package/dist/tools/stats-usage.d.ts +31 -0
  172. package/dist/tools/stats-usage.js +206 -0
  173. package/dist/tools/stats-usage.js.map +1 -0
  174. package/package.json +8 -4
  175. package/scripts/analyze-codex-error-failures.mjs +109 -0
  176. package/scripts/camoufox/gen-fingerprint-env.py +171 -0
  177. package/scripts/camoufox/launch-auth.mjs +617 -0
  178. package/scripts/classify-codex-samples.mjs +251 -0
  179. package/scripts/cleanup-codex-error-samples.mjs +88 -0
  180. package/scripts/compare-codex-rccx.mjs +268 -0
  181. package/scripts/copy-compat-assets.mjs +18 -0
  182. package/scripts/install-release.sh +1 -1
  183. package/scripts/local-replay-openai-response.mjs +1 -2
  184. package/scripts/pack-mode.mjs +16 -6
  185. package/scripts/replay-codex-sample.mjs +24 -2
  186. package/scripts/responses-compare-server.mjs +119 -0
  187. package/scripts/verify-apply-patch.mjs +28 -17
  188. package/scripts/verify-codex-error-samples.mjs +99 -0
  189. package/scripts/verify-e2e-toolcall.mjs +19 -4
  190. package/scripts/virtual-router-shadow-v2-real.mjs +143 -0
  191. 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
- * 用统一 diff(*** Begin Patch/*** End Patch)触发 apply_patch。
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, patchText) {
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: patchText
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 plainPatch =
110
- '*** Begin Patch\n' +
111
- '*** Add File: hello.txt\n' +
112
- '+Hello from apply_patch\n' +
113
- '*** End Patch\n';
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 fencedPatch =
116
- '```patch\n' +
117
- '*** Begin Patch\n' +
118
- '*** Add File: hello-fenced.txt\n' +
119
- '+Hello from apply_patch (fenced)\n' +
120
- '*** End Patch\n' +
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', plainPatch);
124
- await runApplyPatchTextCase('fenced', fencedPatch);
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 = process.env.ROUTECODEX_VERIFY_CONFIG || '/Users/fanzhang/.routecodex/provider/glm/config.v1.json';
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: 'text',
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
- Array.isArray(json?.output) && json.output.some((item) => Array.isArray(item?.content) && item.content.some((c) => c?.type === 'tool_call'));
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
+ });