@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.
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
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ // classify-codex-samples.mjs
3
+ // 按照 Virtual Router classifier 分类 codex 样本
4
+ // 支持 providerKey、工具类型、tool_calls 结构识别
5
+
6
+ import { readdir, readFile } from 'node:fs/promises';
7
+ import { join, dirname, basename } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const SAMPLES_ROOT = join(__dirname, '..', 'samples', 'mock-provider');
12
+
13
+ // Virtual Router classifier 规则
14
+ const PROVIDER_KEYS = {
15
+ 'glm.default': 'glm',
16
+ 'glm.key1': 'glm',
17
+ 'gemini.default': 'gemini',
18
+ 'openai.default': 'openai',
19
+ 'anthropic.default': 'anthropic',
20
+ 'kimi.key1': 'kimi',
21
+ 'qwen.key1': 'qwen',
22
+ 'iflow.key1': 'iflow',
23
+ 'tab.key1': 'tab',
24
+ 'crs.key1': 'crs',
25
+ 'fai.key1': 'fai',
26
+ 'modelscope.default': 'modelscope',
27
+ 'unknown': 'unknown'
28
+ };
29
+
30
+ const TOOL_TYPES = {
31
+ 'apply_patch': 'apply_patch',
32
+ 'shell': 'shell_command',
33
+ 'toon': 'toon_tool',
34
+ 'submit_tool_outputs': 'tool_loop',
35
+ 'list_files': 'file_operation',
36
+ 'write_file': 'file_operation',
37
+ 'read_file': 'file_operation'
38
+ };
39
+
40
+ // 样本分类结果
41
+ class SampleClassifier {
42
+ constructor() {
43
+ this.samples = [];
44
+ this.stats = {
45
+ total: 0,
46
+ byProvider: {},
47
+ byToolType: {},
48
+ withToolCalls: 0,
49
+ withToon: 0,
50
+ errors: 0
51
+ };
52
+ }
53
+
54
+ // 从路径提取 provider key
55
+ extractProviderKey(filePath) {
56
+ const parts = filePath.split('/');
57
+ const providerPart = parts.find(part => Object.keys(PROVIDER_KEYS).some(key => part.includes(key)));
58
+
59
+ if (!providerPart) return 'unknown';
60
+
61
+ for (const [key, value] of Object.entries(PROVIDER_KEYS)) {
62
+ if (providerPart.includes(key)) return value;
63
+ }
64
+ return 'unknown';
65
+ }
66
+
67
+ // 识别工具类型
68
+ identifyToolType(toolCall) {
69
+ const funcName = toolCall.function?.name || '';
70
+
71
+ // 检查 TOON 格式
72
+ if (this.isToonTool(toolCall)) return 'toon_tool';
73
+
74
+ // 检查已知工具名称
75
+ for (const [pattern, type] of Object.entries(TOOL_TYPES)) {
76
+ if (funcName.toLowerCase().includes(pattern)) return type;
77
+ }
78
+
79
+ // 默认分类
80
+ if (funcName.includes('patch')) return 'apply_patch';
81
+ if (funcName.includes('shell')) return 'shell_command';
82
+ if (funcName.includes('file')) return 'file_operation';
83
+
84
+ return 'unknown_tool';
85
+ }
86
+
87
+ // 检查是否为 TOON 工具
88
+ isToonTool(toolCall) {
89
+ const args = toolCall.function?.arguments;
90
+ if (!args) return false;
91
+
92
+ try {
93
+ const parsed = JSON.parse(args);
94
+ return parsed && typeof parsed.toon === 'string';
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
99
+
100
+ // 分析单个样本
101
+ async analyzeSample(filePath) {
102
+ try {
103
+ const content = await readFile(filePath, 'utf-8');
104
+ const data = JSON.parse(content);
105
+
106
+ const providerKey = this.extractProviderKey(filePath);
107
+ const sampleId = basename(dirname(filePath)) + '/' + basename(filePath, '.json');
108
+
109
+ let toolCalls = [];
110
+ let hasToon = false;
111
+
112
+ // 提取 tool_calls
113
+ if (data.tool_calls) {
114
+ toolCalls = data.tool_calls;
115
+ } else if (data.choices?.[0]?.message?.tool_calls) {
116
+ toolCalls = data.choices[0].message.tool_calls;
117
+ } else if (data.messages) {
118
+ // 查找最后一条用户消息中的工具调用
119
+ const lastMessage = data.messages[data.messages.length - 1];
120
+ if (lastMessage.tool_calls) {
121
+ toolCalls = lastMessage.tool_calls;
122
+ }
123
+ }
124
+
125
+ // 分析工具调用
126
+ const toolTypes = [];
127
+ for (const toolCall of toolCalls) {
128
+ const toolType = this.identifyToolType(toolCall);
129
+ toolTypes.push(toolType);
130
+
131
+ if (toolType === 'toon_tool') hasToon = true;
132
+
133
+ // 更新统计
134
+ this.stats.byToolType[toolType] = (this.stats.byToolType[toolType] || 0) + 1;
135
+ }
136
+
137
+ const sample = {
138
+ id: sampleId,
139
+ provider: providerKey,
140
+ filePath,
141
+ hasToolCalls: toolCalls.length > 0,
142
+ toolTypes,
143
+ hasToon,
144
+ toolCallCount: toolCalls.length
145
+ };
146
+
147
+ this.samples.push(sample);
148
+
149
+ // 更新统计
150
+ this.stats.total++;
151
+ this.stats.byProvider[providerKey] = (this.stats.byProvider[providerKey] || 0) + 1;
152
+ if (toolCalls.length > 0) this.stats.withToolCalls++;
153
+ if (hasToon) this.stats.withToon++;
154
+
155
+ } catch (error) {
156
+ console.error(`Error analyzing ${filePath}:`, error.message);
157
+ this.stats.errors++;
158
+ }
159
+ }
160
+
161
+ // 递归查找 JSON 文件
162
+ async findJsonFiles(dir) {
163
+ const files = [];
164
+ const entries = await readdir(dir, { withFileTypes: true });
165
+
166
+ for (const entry of entries) {
167
+ const fullPath = join(dir, entry.name);
168
+ if (entry.isDirectory()) {
169
+ files.push(...await this.findJsonFiles(fullPath));
170
+ } else if (entry.isFile() && entry.name.endsWith('.json')) {
171
+ files.push(fullPath);
172
+ }
173
+ }
174
+
175
+ return files;
176
+ }
177
+
178
+ // 运行分类
179
+ async run() {
180
+ console.log('🔍 开始分析 samples/mock-provider 目录...');
181
+
182
+ const jsonFiles = await this.findJsonFiles(SAMPLES_ROOT);
183
+ console.log(`📁 找到 ${jsonFiles.length} 个 JSON 文件`);
184
+
185
+ for (const file of jsonFiles) {
186
+ await this.analyzeSample(file);
187
+ }
188
+
189
+ this.printResults();
190
+ }
191
+
192
+ // 打印结果
193
+ printResults() {
194
+ console.log('\n📊 分类结果统计:');
195
+ console.log('==================');
196
+
197
+ console.log(`总样本数: ${this.stats.total}`);
198
+ console.log(`包含工具调用: ${this.stats.withToolCalls}`);
199
+ console.log(`包含 TOON: ${this.stats.withToon}`);
200
+ console.log(`错误数: ${this.stats.errors}`);
201
+
202
+ console.log('\n按 Provider 分布:');
203
+ for (const [provider, count] of Object.entries(this.stats.byProvider)) {
204
+ console.log(` ${provider}: ${count}`);
205
+ }
206
+
207
+ console.log('\n按工具类型分布:');
208
+ for (const [toolType, count] of Object.entries(this.stats.byToolType)) {
209
+ console.log(` ${toolType}: ${count}`);
210
+ }
211
+
212
+ // 详细样本列表
213
+ console.log('\n📋 详细样本分类:');
214
+ console.log('==================');
215
+
216
+ for (const sample of this.samples) {
217
+ if (sample.hasToolCalls) {
218
+ console.log(`${sample.provider.padEnd(12)} | ${sample.toolTypes.join(', ').padEnd(20)} | ${sample.id}`);
219
+ }
220
+ }
221
+
222
+ // 识别未覆盖场景
223
+ console.log('\n⚠️ 未覆盖场景:');
224
+ console.log('==================');
225
+
226
+ const hasApplyPatch = this.stats.byToolType['apply_patch'] > 0;
227
+ const hasToon = this.stats.byToolType['toon_tool'] > 0;
228
+ const hasShell = this.stats.byToolType['shell_command'] > 0;
229
+
230
+ if (!hasApplyPatch) console.log(' - 缺少 apply_patch 样本');
231
+ if (!hasToon) console.log(' - 缺少 TOON 工具样本');
232
+ if (!hasShell) console.log(' - 缺少 shell command 样本');
233
+
234
+ // TOON 样本详情
235
+ if (this.stats.withToon > 0) {
236
+ console.log('\n🔧 TOON 工具样本:');
237
+ const toonSamples = this.samples.filter(s => s.hasToon);
238
+ for (const sample of toonSamples) {
239
+ console.log(` - ${sample.provider}: ${sample.id}`);
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ // 主函数
246
+ async function main() {
247
+ const classifier = new SampleClassifier();
248
+ await classifier.run();
249
+ }
250
+
251
+ main().catch(console.error);
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cleanup stale Codex error samples so that后续运行只记录新的失败。
5
+ *
6
+ * 行为:
7
+ * - 扫描 ~/.routecodex/codex-samples/openai-responses 下所有 *.json;
8
+ * - 如果包含以下任一错误模式,则视为“旧错误样本”,移动到归档目录:
9
+ * ~/.routecodex/codex-samples-archive/openai-responses
10
+ * - "apply_patch verification failed"
11
+ * - "failed to parse exec_command arguments"
12
+ * - "Instructions are not valid"
13
+ *
14
+ * 注意:
15
+ * - 只做移动(rename),不直接删除文件,方便必要时回溯;
16
+ * - 归档目录不参与当前 verify:errorsamples/分析脚本。
17
+ */
18
+
19
+ import fs from 'node:fs/promises';
20
+ import path from 'node:path';
21
+ import os from 'node:os';
22
+
23
+ const HOME = os.homedir();
24
+ const RESPONSES_DIR = path.join(HOME, '.routecodex', 'codex-samples', 'openai-responses');
25
+
26
+ const PATTERNS = [
27
+ 'apply_patch verification failed',
28
+ 'failed to parse exec_command arguments',
29
+ 'Instructions are not valid'
30
+ ];
31
+
32
+ async function listJsonFiles(root) {
33
+ const entries = await fs.readdir(root);
34
+ return entries
35
+ .filter((name) => name.toLowerCase().endsWith('.json'))
36
+ .map((name) => path.join(root, name));
37
+ }
38
+
39
+ async function hasErrorPattern(filePath) {
40
+ const text = await fs.readFile(filePath, 'utf-8');
41
+ return PATTERNS.some((p) => text.includes(p));
42
+ }
43
+
44
+ async function main() {
45
+ try {
46
+ const st = await fs.stat(RESPONSES_DIR);
47
+ if (!st.isDirectory()) {
48
+ console.log('[cleanup-codex-error-samples] no codex-samples directory:', RESPONSES_DIR);
49
+ return;
50
+ }
51
+ } catch {
52
+ console.log('[cleanup-codex-error-samples] no codex-samples directory:', RESPONSES_DIR);
53
+ return;
54
+ }
55
+
56
+ const files = await listJsonFiles(RESPONSES_DIR);
57
+ if (!files.length) {
58
+ console.log('[cleanup-codex-error-samples] no JSON files under', RESPONSES_DIR);
59
+ return;
60
+ }
61
+
62
+ console.log(`[cleanup-codex-error-samples] scanning ${files.length} file(s) under ${RESPONSES_DIR}`);
63
+
64
+ let removed = 0;
65
+ for (const file of files) {
66
+ try {
67
+ const shouldMove = await hasErrorPattern(file);
68
+ if (!shouldMove) continue;
69
+ await fs.unlink(file);
70
+ removed += 1;
71
+ if (removed <= 10) {
72
+ console.log(` removed: ${path.basename(file)}`);
73
+ }
74
+ } catch {
75
+ // best-effort: skip problematic file
76
+ }
77
+ }
78
+
79
+ console.log(`[cleanup-codex-error-samples] removed ${removed} file(s) matching error patterns.`);
80
+ if (removed > 10) {
81
+ console.log(' (only first 10 shown above)');
82
+ }
83
+ }
84
+
85
+ main().catch((error) => {
86
+ console.error('[cleanup-codex-error-samples] failed:', error);
87
+ process.exit(99);
88
+ });
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 黑盒对比:使用 codex-samples 中的一条样本,
4
+ * 分别通过 RouteCodex(dev) 和 rccx(wasm 引擎) 重放,
5
+ * 然后对比 HTTP 元数据和基础响应结构。
6
+ *
7
+ * 前提:
8
+ * - 已经有 RouteCodex 服务在某个端口运行(默认 http://127.0.0.1:5555);
9
+ * - 已经有 rccx 服务在另一个端口运行(默认 http://127.0.0.1:5556);
10
+ * - codex 样本是由 RouteCodex 捕获的 client-request JSON(~/.routecodex/codex-samples/...)。
11
+ *
12
+ * 使用方式(示例):
13
+ * node scripts/compare-codex-rccx.mjs \\
14
+ * --sample ~/.routecodex/codex-samples/openai-responses/xxx_client-request.json \\
15
+ * --route-base http://127.0.0.1:5555 \\
16
+ * --rccx-base http://127.0.0.1:5556
17
+ *
18
+ * 脚本会调用 scripts/replay-codex-sample.mjs 对同一条样本重放两次:
19
+ * - label=routecodex,base = --route-base;
20
+ * - label=rccx, base = --rccx-base;
21
+ *
22
+ * 然后读取各自 runs/<requestId>/<label>/response.* 进行对比。
23
+ */
24
+
25
+ import fs from 'node:fs';
26
+ import path from 'node:path';
27
+ import { spawn } from 'node:child_process';
28
+
29
+ const DEFAULT_ROUTE_BASE = process.env.ROUTECODEX_BASE || 'http://127.0.0.1:5555';
30
+ const DEFAULT_RCCX_BASE = process.env.RCCX_BASE || 'http://127.0.0.1:5556';
31
+ const DEFAULT_API_KEY = process.env.ROUTECODEX_API_KEY || 'routecodex-test';
32
+
33
+ function usage() {
34
+ console.log(`Usage:
35
+ node scripts/compare-codex-rccx.mjs --sample <file> [--route-base URL] [--rccx-base URL] [--key TOKEN]
36
+
37
+ Environment:
38
+ ROUTECODEX_BASE 默认 RouteCodex 基础 URL (默认 ${DEFAULT_ROUTE_BASE})
39
+ RCCX_BASE 默认 rccx 基础 URL (默认 ${DEFAULT_RCCX_BASE})
40
+ ROUTECODEX_API_KEY 默认 API key (默认 ${DEFAULT_API_KEY})
41
+ `);
42
+ }
43
+
44
+ function parseArgs() {
45
+ const args = process.argv.slice(2);
46
+ const opts = {
47
+ routeBase: DEFAULT_ROUTE_BASE,
48
+ rccxBase: DEFAULT_RCCX_BASE,
49
+ key: DEFAULT_API_KEY,
50
+ routeLabel: 'routecodex',
51
+ rccxLabel: 'rccx'
52
+ };
53
+ for (let i = 0; i < args.length; i += 1) {
54
+ const a = args[i];
55
+ if (a === '--sample') opts.sample = args[++i];
56
+ else if (a === '--route-base') opts.routeBase = args[++i];
57
+ else if (a === '--rccx-base') opts.rccxBase = args[++i];
58
+ else if (a === '--key') opts.key = args[++i];
59
+ else if (a === '--route-label') opts.routeLabel = args[++i];
60
+ else if (a === '--rccx-label') opts.rccxLabel = args[++i];
61
+ else if (a === '--help' || a === '-h') {
62
+ usage();
63
+ process.exit(0);
64
+ } else {
65
+ console.error(`Unknown arg: ${a}`);
66
+ usage();
67
+ process.exit(1);
68
+ }
69
+ }
70
+ if (!opts.sample) {
71
+ usage();
72
+ process.exit(1);
73
+ }
74
+ return opts;
75
+ }
76
+
77
+ function readJson(file) {
78
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
79
+ }
80
+
81
+ function computeRequestId(sample) {
82
+ return (
83
+ sample?.requestId ||
84
+ sample?.data?.meta?.requestId ||
85
+ sample?.meta?.requestId ||
86
+ `sample_${Date.now()}`
87
+ );
88
+ }
89
+
90
+ function computeRunDir(samplePath, requestId, label) {
91
+ const baseDir = path.dirname(path.resolve(samplePath));
92
+ return path.join(baseDir, 'runs', requestId, label);
93
+ }
94
+
95
+ function runReplay({ base, label, samplePath, key }) {
96
+ return new Promise((resolve, reject) => {
97
+ const args = [
98
+ 'scripts/replay-codex-sample.mjs',
99
+ '--sample',
100
+ samplePath,
101
+ '--label',
102
+ label,
103
+ '--base',
104
+ base,
105
+ '--key',
106
+ key
107
+ ];
108
+ const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
109
+ const child = spawn(process.execPath, args, {
110
+ cwd: repoRoot,
111
+ stdio: 'inherit'
112
+ });
113
+ child.on('exit', (code) => {
114
+ if (code === 0) resolve();
115
+ else reject(new Error(`replay-codex-sample failed for label=${label} (exit ${code})`));
116
+ });
117
+ child.on('error', (err) => reject(err));
118
+ });
119
+ }
120
+
121
+ function loadRunResult(runDir) {
122
+ const metaPath = path.join(runDir, 'response.meta.json');
123
+ if (!fs.existsSync(metaPath)) {
124
+ return { error: `missing response.meta.json in ${runDir}` };
125
+ }
126
+ const meta = readJson(metaPath);
127
+
128
+ const jsonPath = path.join(runDir, 'response.json');
129
+ const errorPath = path.join(runDir, 'response.error.txt');
130
+ const ssePath = path.join(runDir, 'response.sse.ndjson');
131
+
132
+ let bodyKind = 'none';
133
+ let bodySample = null;
134
+
135
+ if (fs.existsSync(jsonPath)) {
136
+ bodyKind = 'json';
137
+ try {
138
+ bodySample = readJson(jsonPath);
139
+ } catch {
140
+ bodySample = null;
141
+ }
142
+ } else if (fs.existsSync(errorPath)) {
143
+ bodyKind = 'error-text';
144
+ try {
145
+ const txt = fs.readFileSync(errorPath, 'utf8');
146
+ bodySample = txt.slice(0, 1024);
147
+ } catch {
148
+ bodySample = null;
149
+ }
150
+ } else if (fs.existsSync(ssePath)) {
151
+ bodyKind = 'sse';
152
+ try {
153
+ const txt = fs.readFileSync(ssePath, 'utf8');
154
+ const lines = txt.split('\n').filter(Boolean);
155
+ bodySample = lines.slice(0, 5);
156
+ } catch {
157
+ bodySample = null;
158
+ }
159
+ }
160
+
161
+ return { meta, bodyKind, bodySample };
162
+ }
163
+
164
+ function stableSubset(meta) {
165
+ if (!meta || typeof meta !== 'object') return null;
166
+ return {
167
+ status: meta.status,
168
+ statusText: meta.statusText,
169
+ endpoint: meta.endpoint,
170
+ targetUrl: meta.targetUrl,
171
+ wantsSse: meta.wantsSse,
172
+ contentType: meta.headers && meta.headers['content-type'],
173
+ routeHint:
174
+ (meta.headers && (meta.headers['x-route-hint'] || meta.headers['X-Route-Hint'])) || undefined
175
+ };
176
+ }
177
+
178
+ function stableStringify(value) {
179
+ return JSON.stringify(
180
+ value,
181
+ (key, val) => {
182
+ if (val && typeof val === 'object' && !Array.isArray(val)) {
183
+ const out = {};
184
+ for (const k of Object.keys(val).sort()) {
185
+ out[k] = val[k];
186
+ }
187
+ return out;
188
+ }
189
+ return val;
190
+ }
191
+ );
192
+ }
193
+
194
+ async function main() {
195
+ const opts = parseArgs();
196
+ const samplePath = path.resolve(opts.sample);
197
+ const sample = readJson(samplePath);
198
+ const requestId = computeRequestId(sample);
199
+
200
+ console.log(
201
+ `[compare-codex-rccx] sample=${samplePath} requestId=${requestId} routeBase=${opts.routeBase} rccxBase=${opts.rccxBase}`
202
+ );
203
+
204
+ // RouteCodex 路径
205
+ console.log('[compare-codex-rccx] ▶ routecodex replay...');
206
+ await runReplay({
207
+ base: opts.routeBase,
208
+ label: opts.routeLabel,
209
+ samplePath,
210
+ key: opts.key
211
+ });
212
+ const routeRunDir = computeRunDir(samplePath, requestId, opts.routeLabel);
213
+ const routeResult = loadRunResult(routeRunDir);
214
+
215
+ // rccx 路径
216
+ console.log('[compare-codex-rccx] ▶ rccx replay...');
217
+ await runReplay({
218
+ base: opts.rccxBase,
219
+ label: opts.rccxLabel,
220
+ samplePath,
221
+ key: opts.key
222
+ });
223
+ const rccxRunDir = computeRunDir(samplePath, requestId, opts.rccxLabel);
224
+ const rccxResult = loadRunResult(rccxRunDir);
225
+
226
+ if (routeResult.error || rccxResult.error) {
227
+ console.error('[compare-codex-rccx] ❌ missing run artifacts:', {
228
+ routeRunDir,
229
+ routeError: routeResult.error || null,
230
+ rccxRunDir,
231
+ rccxError: rccxResult.error || null
232
+ });
233
+ process.exitCode = 1;
234
+ return;
235
+ }
236
+
237
+ const routeMeta = stableSubset(routeResult.meta);
238
+ const rccxMeta = stableSubset(rccxResult.meta);
239
+
240
+ const metaEqual = stableStringify(routeMeta) === stableStringify(rccxMeta);
241
+ const bodyKindEqual = routeResult.bodyKind === rccxResult.bodyKind;
242
+
243
+ console.log('[compare-codex-rccx] routecodex.meta =', routeMeta);
244
+ console.log('[compare-codex-rccx] rccx.meta =', rccxMeta);
245
+ console.log('[compare-codex-rccx] routecodex.bodyKind =', routeResult.bodyKind);
246
+ console.log('[compare-codex-rccx] rccx.bodyKind =', rccxResult.bodyKind);
247
+
248
+ if (!metaEqual || !bodyKindEqual) {
249
+ console.log('[compare-codex-rccx] ❌ mismatch detected between RouteCodex and rccx');
250
+ console.log('[compare-codex-rccx] routecodex runDir =', routeRunDir);
251
+ console.log('[compare-codex-rccx] rccx runDir =', rccxRunDir);
252
+ // 为了调试 429 / 系列冷却问题,额外打印一小段 body 样本。
253
+ console.log('[compare-codex-rccx] routecodex.bodySample =', routeResult.bodySample);
254
+ console.log('[compare-codex-rccx] rccx.bodySample =', rccxResult.bodySample);
255
+ process.exitCode = 1;
256
+ return;
257
+ }
258
+
259
+ console.log('[compare-codex-rccx] ✅ meta/bodyKind aligned for this sample');
260
+ console.log('[compare-codex-rccx] routecodex runDir =', routeRunDir);
261
+ console.log('[compare-codex-rccx] rccx runDir =', rccxRunDir);
262
+ }
263
+
264
+ main().catch((err) => {
265
+ console.error('[compare-codex-rccx] fatal error:', err);
266
+ process.exitCode = 1;
267
+ });
268
+
@@ -17,7 +17,10 @@ async function ensureDir(p) {
17
17
  async function main() {
18
18
  const PROMPT_SRC = path.resolve(process.cwd(), 'src/config/system-prompts');
19
19
  const PROMPT_DIST = path.resolve(process.cwd(), 'dist/config/system-prompts');
20
+ const CAMOUFOX_SRC = path.resolve(process.cwd(), 'scripts/camoufox');
21
+ const CAMOUFOX_DIST = path.resolve(process.cwd(), 'dist/scripts/camoufox');
20
22
  const promptCopied = [];
23
+ const camoufoxCopied = [];
21
24
  try {
22
25
  // copy system prompt artifacts only; provider compat assets are owned by llmswitch-core
23
26
  try {
@@ -35,7 +38,22 @@ async function main() {
35
38
  if (promptErr && promptErr.code !== 'ENOENT') throw promptErr;
36
39
  }
37
40
  // 不再复制 provider compat 资产;兼容层由 sharedmodule/llmswitch-core 提供
41
+ try {
42
+ for await (const file of walk(CAMOUFOX_SRC)) {
43
+ const stats = await fs.stat(file);
44
+ if (stats.isFile()) {
45
+ const rel = path.relative(CAMOUFOX_SRC, file);
46
+ const dest = path.join(CAMOUFOX_DIST, rel);
47
+ await ensureDir(path.dirname(dest));
48
+ await fs.copyFile(file, dest);
49
+ camoufoxCopied.push(rel);
50
+ }
51
+ }
52
+ } catch (camoufoxErr) {
53
+ if (camoufoxErr && camoufoxErr.code !== 'ENOENT') throw camoufoxErr;
54
+ }
38
55
  console.log(`[copy-compat-assets] prompts copied: ${promptCopied.length}`);
56
+ console.log(`[copy-compat-assets] camoufox assets copied: ${camoufoxCopied.length}`);
39
57
  } catch (err) {
40
58
  console.error('[copy-compat-assets] failed:', err?.message || String(err));
41
59
  process.exit(1);
@@ -85,7 +85,7 @@ else
85
85
  fi
86
86
 
87
87
  verify_server_request() {
88
- local VERIFY_CONFIG=${ROUTECODEX_INSTALL_VERIFY_CONFIG:-"$HOME/.routecodex/provider/glm/config.v1.json"}
88
+ local VERIFY_CONFIG=${ROUTECODEX_INSTALL_VERIFY_CONFIG:-"$HOME/.routecodex/config.json"}
89
89
  local VERIFY_TIMEOUT=${ROUTECODEX_INSTALL_VERIFY_TIMEOUT:-240}
90
90
  local VERIFY_LOG="/tmp/routecodex-release-verify-$(date +%s).log"
91
91
  local TIMEOUT_BIN=""
@@ -25,7 +25,7 @@ async function main() {
25
25
  const payload = obj?.data || obj; // accept wrapped
26
26
  const requestId = (obj?.requestId) || `replay_${Date.now()}`;
27
27
 
28
- const codecUrl = pathToFileURL(path.join(process.cwd(), 'sharedmodule', 'llmswitch-core', 'dist', 'v2', 'conversion', 'codecs', 'openai-openai-codec.js')).href;
28
+ const codecUrl = pathToFileURL(path.join(process.cwd(), 'sharedmodule', 'llmswitch-core', 'dist', 'conversion', 'codecs', 'openai-openai-codec.js')).href;
29
29
  const { OpenAIOpenAIConversionCodec } = await import(codecUrl);
30
30
  const codec = new OpenAIOpenAIConversionCodec({});
31
31
  const profile = { outgoingProtocol: 'openai-chat' };
@@ -38,4 +38,3 @@ async function main() {
38
38
  }
39
39
 
40
40
  main().catch(err => { console.error(err); process.exit(1); });
41
-
@@ -77,14 +77,24 @@ try {
77
77
  '@jsonstudio/llms': llmsVersion
78
78
  };
79
79
  } else if (isRccx) {
80
- // rccx: wasm-backed llms 核心,使用 dev 配置,不强制切换 release llms
80
+ // rccx: wasm-backed llms 核心,通过 npm alias @jsonstudio/llms 指向 wasm 引擎包。
81
81
  mutated.bundledDependencies = [];
82
82
  mutated.bundleDependencies = [];
83
- mutated.dependencies = {
84
- ...(original.dependencies || {}),
85
- ajv: original.dependencies?.ajv || '^8.17.1',
86
- zod: original.dependencies?.zod || '^3.23.8'
87
- };
83
+
84
+ const deps = { ...(original.dependencies || {}) };
85
+ // 移除原有 TS 版 llms 直连依赖
86
+ if (deps['@jsonstudio/llms']) {
87
+ delete deps['@jsonstudio/llms'];
88
+ }
89
+ // 推断 wasm 引擎版本,若未声明则使用本地 llms-engine 缺省版本
90
+ const engineVersion = deps['@jsonstudio/llms-engine'] || '^0.3.0';
91
+ deps['@jsonstudio/llms-engine'] = engineVersion;
92
+ // 通过 npm alias 保持 import 形状不变
93
+ deps['@jsonstudio/llms'] = `npm:@jsonstudio/llms-engine@${engineVersion}`;
94
+ deps.ajv = deps.ajv || '^8.17.1';
95
+ deps.zod = deps.zod || '^3.23.8';
96
+
97
+ mutated.dependencies = deps;
88
98
  }
89
99
  fs.writeFileSync(pkgPath, JSON.stringify(mutated, null, 2));
90
100