@jsonstudio/rcc 0.89.873 → 0.89.912

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 (37) hide show
  1. package/README.md +44 -0
  2. package/dist/build-info.js +2 -2
  3. package/dist/providers/core/runtime/responses-provider.js +17 -19
  4. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  5. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.d.ts +3 -0
  6. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +138 -0
  7. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -0
  8. package/dist/server/runtime/http-server/daemon-admin/providers-handler.d.ts +3 -0
  9. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +166 -0
  10. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -0
  11. package/dist/server/runtime/http-server/daemon-admin/quota-handler.d.ts +3 -0
  12. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +109 -0
  13. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -0
  14. package/dist/server/runtime/http-server/daemon-admin/status-handler.d.ts +3 -0
  15. package/dist/server/runtime/http-server/daemon-admin/status-handler.js +43 -0
  16. package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -0
  17. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +19 -0
  18. package/dist/server/runtime/http-server/daemon-admin-routes.js +27 -0
  19. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -0
  20. package/dist/server/runtime/http-server/index.d.ts +5 -0
  21. package/dist/server/runtime/http-server/index.js +34 -1
  22. package/dist/server/runtime/http-server/index.js.map +1 -1
  23. package/dist/server/runtime/http-server/request-executor.d.ts +3 -0
  24. package/dist/server/runtime/http-server/request-executor.js +68 -2
  25. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  26. package/dist/server/runtime/http-server/routes.d.ts +3 -0
  27. package/dist/server/runtime/http-server/routes.js +12 -0
  28. package/dist/server/runtime/http-server/routes.js.map +1 -1
  29. package/package.json +4 -3
  30. package/scripts/analyze-codex-error-failures.mjs +4 -2
  31. package/scripts/analyze-usage-estimate.mjs +240 -0
  32. package/scripts/tests/apply-patch-loop.mjs +266 -7
  33. package/scripts/tests/exec-command-loop.mjs +165 -0
  34. package/scripts/tool-classification-report.ts +281 -0
  35. package/scripts/verification/samples/openai-chat-list-local-files.json +1 -1
  36. package/scripts/verify-codex-error-samples.mjs +4 -1
  37. package/scripts/verify-e2e-toolcall.mjs +52 -0
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Generate a tool classification report for codex samples using the
4
+ * virtual router tool classifier (read/write/search/other).
5
+ *
6
+ * Usage:
7
+ * npx tsx scripts/tool-classification-report.ts [samplesRoot] [outputFile]
8
+ *
9
+ * Defaults:
10
+ * samplesRoot = ~/.routecodex/codex-samples
11
+ * outputFile = ./reports/tool-classification-report.md
12
+ */
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import os from 'node:os';
16
+ import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
17
+
18
+ import {
19
+ canonicalizeToolName,
20
+ classifyToolCallForReport,
21
+ type ToolCategory
22
+ } from '../sharedmodule/llmswitch-core/src/router/virtual-router/tool-signals.js';
23
+
24
+ type CategoryCounts = Record<ToolCategory, number>;
25
+
26
+ type ExampleEntry = {
27
+ category: ToolCategory;
28
+ file: string;
29
+ snippet?: string;
30
+ };
31
+
32
+ type ToolSummary = {
33
+ total: number;
34
+ categories: CategoryCounts;
35
+ examples: Record<ToolCategory, ExampleEntry[]>;
36
+ };
37
+
38
+ const CATEGORY_KEYS: ToolCategory[] = ['websearch', 'read', 'write', 'search', 'other'];
39
+ const SAMPLE_SUFFIX = '_req_process_tool_filters_request_pre.json';
40
+ const EXAMPLE_LIMIT = 5;
41
+
42
+ async function main(): Promise<void> {
43
+ const [, , rootArg, outputArg] = process.argv;
44
+ const sampleRoot = rootArg || path.join(os.homedir(), '.routecodex', 'codex-samples');
45
+ const outputFile = outputArg || path.join(process.cwd(), 'reports', 'tool-classification-report.md');
46
+
47
+ if (!fs.existsSync(sampleRoot)) {
48
+ throw new Error(`Sample root not found: ${sampleRoot}`);
49
+ }
50
+
51
+ const files = await collectSampleFiles(sampleRoot);
52
+ files.sort((a, b) => a.localeCompare(b));
53
+
54
+ const processedRequests = new Set<string>();
55
+ const summary = new Map<string, ToolSummary>();
56
+ const categoryTotals: CategoryCounts = emptyCounts();
57
+ let totalCalls = 0;
58
+ let classifiedCalls = 0;
59
+ let unclassifiedCalls = 0;
60
+ let skippedDuplicates = 0;
61
+
62
+ for (const filePath of files) {
63
+ const requestKey = extractRequestKey(filePath);
64
+ if (processedRequests.has(requestKey)) {
65
+ skippedDuplicates += 1;
66
+ continue;
67
+ }
68
+ processedRequests.add(requestKey);
69
+
70
+ const data = await readJsonSafe(filePath);
71
+ if (!data) {
72
+ continue;
73
+ }
74
+ const messages = Array.isArray(data.messages) ? data.messages : [];
75
+ for (const msg of messages) {
76
+ if (!msg || typeof msg !== 'object') {
77
+ continue;
78
+ }
79
+ if (msg.role !== 'assistant') {
80
+ continue;
81
+ }
82
+ const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
83
+ for (const call of toolCalls) {
84
+ totalCalls += 1;
85
+ const classification = classifyToolCallForReport(call as any);
86
+ if (!classification) {
87
+ unclassifiedCalls += 1;
88
+ continue;
89
+ }
90
+ classifiedCalls += 1;
91
+ const toolName = canonicalizeToolName(classification.name ?? '') || '(unknown)';
92
+ const entry = ensureSummary(summary, toolName);
93
+ entry.total += 1;
94
+ entry.categories[classification.category] += 1;
95
+ categoryTotals[classification.category] += 1;
96
+ const examples = entry.examples[classification.category];
97
+ if (examples.length < EXAMPLE_LIMIT) {
98
+ examples.push({
99
+ category: classification.category,
100
+ file: path.relative(sampleRoot, filePath),
101
+ snippet: classification.commandSnippet || buildFallbackSnippet(call)
102
+ });
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ const orderedSummaries = Array.from(summary.entries()).sort((a, b) => b[1].total - a[1].total);
109
+ const report = buildReport({
110
+ sampleRoot,
111
+ outputFile,
112
+ filesScanned: files.length,
113
+ uniqueRequests: processedRequests.size,
114
+ skippedDuplicates,
115
+ totalCalls,
116
+ classifiedCalls,
117
+ unclassifiedCalls,
118
+ categoryTotals,
119
+ summaries: orderedSummaries
120
+ });
121
+
122
+ await mkdir(path.dirname(outputFile), { recursive: true });
123
+ await writeFile(outputFile, report.join('\n'), 'utf8');
124
+ console.log(`[tool-classification-report] wrote ${outputFile}`);
125
+ }
126
+
127
+ function emptyCounts(): CategoryCounts {
128
+ return { websearch: 0, read: 0, write: 0, search: 0, other: 0 };
129
+ }
130
+
131
+ function emptyExampleBuckets(): Record<ToolCategory, ExampleEntry[]> {
132
+ return {
133
+ websearch: [],
134
+ read: [],
135
+ write: [],
136
+ search: [],
137
+ other: []
138
+ };
139
+ }
140
+
141
+ function ensureSummary(summary: Map<string, ToolSummary>, name: string): ToolSummary {
142
+ if (!summary.has(name)) {
143
+ summary.set(name, {
144
+ total: 0,
145
+ categories: emptyCounts(),
146
+ examples: emptyExampleBuckets()
147
+ });
148
+ }
149
+ return summary.get(name)!;
150
+ }
151
+
152
+ async function collectSampleFiles(dir: string): Promise<string[]> {
153
+ const entries = await readdir(dir, { withFileTypes: true });
154
+ const files: string[] = [];
155
+ for (const entry of entries) {
156
+ const fullPath = path.join(dir, entry.name);
157
+ if (entry.isDirectory()) {
158
+ const nested = await collectSampleFiles(fullPath);
159
+ files.push(...nested);
160
+ } else if (entry.isFile() && entry.name.endsWith(SAMPLE_SUFFIX)) {
161
+ files.push(fullPath);
162
+ }
163
+ }
164
+ return files;
165
+ }
166
+
167
+ async function readJsonSafe(filePath: string): Promise<any | null> {
168
+ try {
169
+ const raw = await readFile(filePath, 'utf8');
170
+ return JSON.parse(raw);
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+
176
+ function extractRequestKey(filePath: string): string {
177
+ const base = path.basename(filePath);
178
+ const match = base.match(/^(req_[^_]+_[^_]+)/);
179
+ return match ? match[1] : base;
180
+ }
181
+
182
+ function sanitizeSnippet(snippet?: string): string {
183
+ if (!snippet) {
184
+ return '(no snippet)';
185
+ }
186
+ return snippet.replace(/[\r\n]+/g, ' ').replace(/`/g, "'");
187
+ }
188
+
189
+ function titleCase(category: ToolCategory): string {
190
+ if (category === 'websearch') {
191
+ return 'WebSearch';
192
+ }
193
+ return category.charAt(0).toUpperCase() + category.slice(1);
194
+ }
195
+
196
+ function buildFallbackSnippet(call: any): string | undefined {
197
+ const rawArgs = call?.function?.arguments;
198
+ if (typeof rawArgs === 'string' && rawArgs.trim()) {
199
+ const trimmed = rawArgs.trim().replace(/\s+/g, ' ');
200
+ return trimmed.length > 120 ? `${trimmed.slice(0, 117)}…` : trimmed;
201
+ }
202
+ return undefined;
203
+ }
204
+
205
+ function buildReport(options: {
206
+ sampleRoot: string;
207
+ outputFile: string;
208
+ filesScanned: number;
209
+ uniqueRequests: number;
210
+ skippedDuplicates: number;
211
+ totalCalls: number;
212
+ classifiedCalls: number;
213
+ unclassifiedCalls: number;
214
+ categoryTotals: CategoryCounts;
215
+ summaries: Array<[string, ToolSummary]>;
216
+ }): string[] {
217
+ const {
218
+ sampleRoot,
219
+ filesScanned,
220
+ uniqueRequests,
221
+ skippedDuplicates,
222
+ totalCalls,
223
+ classifiedCalls,
224
+ unclassifiedCalls,
225
+ categoryTotals,
226
+ summaries
227
+ } = options;
228
+
229
+ const lines: string[] = [];
230
+ lines.push('# Tool Classification Report');
231
+ lines.push('');
232
+ lines.push(`- Sample root: ${sampleRoot}`);
233
+ lines.push(`- Files scanned: ${filesScanned}`);
234
+ lines.push(`- Unique requests: ${uniqueRequests}`);
235
+ lines.push(`- Skipped duplicates: ${skippedDuplicates}`);
236
+ lines.push(`- Tool calls processed: ${totalCalls}`);
237
+ lines.push(`- Classified calls: ${classifiedCalls}`);
238
+ lines.push(`- Unclassified calls: ${unclassifiedCalls}`);
239
+ lines.push(`- Generated at: ${new Date().toISOString()}`);
240
+ lines.push('');
241
+ lines.push('## Category Totals');
242
+ lines.push('');
243
+ for (const category of CATEGORY_KEYS) {
244
+ lines.push(`- ${category}: ${categoryTotals[category]}`);
245
+ }
246
+ lines.push('');
247
+ lines.push('## Per-Tool Summary');
248
+ lines.push('');
249
+ const categoryHeader = CATEGORY_KEYS.map((key) => titleCase(key)).join(' | ');
250
+ lines.push(`| Tool | Total | ${categoryHeader} |`);
251
+ lines.push(`| --- | ---: | ${CATEGORY_KEYS.map(() => '---:').join(' | ')} |`);
252
+ for (const [name, info] of summaries) {
253
+ const counts = CATEGORY_KEYS.map((key) => info.categories[key]);
254
+ lines.push(`| ${name} | ${info.total} | ${counts.join(' | ')} |`);
255
+ }
256
+ lines.push('');
257
+ for (const [name, info] of summaries) {
258
+ lines.push(`### ${name} (total ${info.total})`);
259
+ const countSummary = CATEGORY_KEYS.map((key) => `${key}: ${info.categories[key]}`).join(', ');
260
+ lines.push(`Counts → ${countSummary}`);
261
+ for (const category of CATEGORY_KEYS) {
262
+ const examples = info.examples[category];
263
+ if (!examples.length) {
264
+ continue;
265
+ }
266
+ lines.push(`- ${category} examples:`);
267
+ for (const example of examples) {
268
+ lines.push(
269
+ ` - ${example.file}: \`${sanitizeSnippet(example.snippet)}\``
270
+ );
271
+ }
272
+ }
273
+ lines.push('');
274
+ }
275
+ return lines;
276
+ }
277
+
278
+ main().catch((err) => {
279
+ console.error('[tool-classification-report] failed', err);
280
+ process.exit(1);
281
+ });
@@ -3,7 +3,7 @@
3
3
  "source": "synthetic",
4
4
  "endpoint": "/v1/chat/completions",
5
5
  "payload": {
6
- "model": "glm-4.6",
6
+ "model": "glm-4.7",
7
7
  "temperature": 0,
8
8
  "stream": false,
9
9
  "tool_choice": {
@@ -24,7 +24,10 @@ const ROOT =
24
24
  : path.join(os.homedir(), '.routecodex', 'errorsamples');
25
25
 
26
26
  const ERROR_PATTERNS = [
27
- 'failed to parse exec_command arguments',
27
+ // exec_command / apply_patch 参数解码错误(CLI 侧报错)
28
+ 'failed to parse function arguments: missing field `cmd`',
29
+ 'failed to parse function arguments: missing field `input`',
30
+ // 历史回滚:统一 diff 校验失败
28
31
  'apply_patch verification failed'
29
32
  ];
30
33
 
@@ -64,6 +64,9 @@ async function main() {
64
64
  await runToolcallVerification();
65
65
  console.log('✅ 端到端工具调用校验通过');
66
66
 
67
+ await runDaemonAdminSmokeCheck();
68
+ await runConfigV2ProvidersSmokeCheck();
69
+
67
70
  // 附加:Gemini CLI 配置健康性快速检查(仅尝试初始化,不做请求)
68
71
  await runGeminiCliStartupCheck();
69
72
  } finally {
@@ -163,6 +166,55 @@ async function runToolcallVerification() {
163
166
  }
164
167
  }
165
168
 
169
+ async function runDaemonAdminSmokeCheck() {
170
+ // 仅做最小的健康性探测:确保 daemon 管理类只读 API 可用,不做语义校验。
171
+ try {
172
+ const res = await fetch(`${VERIFY_BASE}/daemon/status`);
173
+ if (!res.ok) {
174
+ throw new Error(`daemon/status HTTP ${res.status}`);
175
+ }
176
+ const json = await res.json();
177
+ if (!json || typeof json !== 'object' || json.ok !== true) {
178
+ throw new Error('daemon/status 返回值不符合预期形态');
179
+ }
180
+ } catch (error) {
181
+ console.error('[verify:e2e-toolcall] /daemon/status smoke 检查失败:', error);
182
+ throw error;
183
+ }
184
+
185
+ const paths = ['/daemon/credentials', '/quota/summary', '/providers/runtimes'];
186
+ for (const path of paths) {
187
+ try {
188
+ const res = await fetch(`${VERIFY_BASE}${path}`);
189
+ if (!res.ok) {
190
+ throw new Error(`${path} HTTP ${res.status}`);
191
+ }
192
+ // 只要 JSON 可解析即可视为通过,避免把语义校验塞到这里。
193
+ await res.json().catch(() => ({}));
194
+ } catch (error) {
195
+ console.error('[verify:e2e-toolcall] Daemon admin smoke 检查失败:', path, error);
196
+ throw error;
197
+ }
198
+ }
199
+ }
200
+
201
+ async function runConfigV2ProvidersSmokeCheck() {
202
+ try {
203
+ const res = await fetch(`${VERIFY_BASE}/config/providers/v2`);
204
+ if (!res.ok) {
205
+ throw new Error(`/config/providers/v2 HTTP ${res.status}`);
206
+ }
207
+ const json = await res.json();
208
+ if (!Array.isArray(json)) {
209
+ // 出于兼容考虑,只要不是致命错误就通过;Config V2 视图是辅助信息。
210
+ console.warn('[verify:e2e-toolcall] /config/providers/v2 未返回数组形态,跳过进一步校验');
211
+ }
212
+ } catch (error) {
213
+ console.error('[verify:e2e-toolcall] Config V2 providers smoke 检查失败:', error);
214
+ // 不将 Config V2 视图问题视为构建阻断条件,以免挡住主链路;仅提示。
215
+ }
216
+ }
217
+
166
218
  async function runGeminiCliStartupCheck() {
167
219
  if (!GEMINI_CLI_CONFIG || !fs.existsSync(GEMINI_CLI_CONFIG)) {
168
220
  return;