@jsonstudio/rcc 0.89.683 → 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.
- package/README.md +44 -0
- 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/runtime/responses-provider.js +17 -19
- package/dist/providers/core/runtime/responses-provider.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/daemon-admin/credentials-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +138 -0
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +166 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +109 -0
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js +43 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +19 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js +27 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -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 +6 -0
- package/dist/server/runtime/http-server/index.js +121 -53
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.d.ts +3 -0
- package/dist/server/runtime/http-server/request-executor.js +73 -21
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +5 -0
- package/dist/server/runtime/http-server/routes.js +45 -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 +9 -4
- package/scripts/analyze-codex-error-failures.mjs +111 -0
- package/scripts/analyze-usage-estimate.mjs +240 -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/tests/apply-patch-loop.mjs +266 -7
- package/scripts/tests/exec-command-loop.mjs +165 -0
- package/scripts/tool-classification-report.ts +281 -0
- package/scripts/verification/samples/openai-chat-list-local-files.json +1 -1
- package/scripts/verify-apply-patch.mjs +28 -17
- package/scripts/verify-codex-error-samples.mjs +102 -0
- package/scripts/verify-e2e-toolcall.mjs +71 -4
- package/scripts/virtual-router-shadow-v2-real.mjs +143 -0
- package/scripts/virtual-router-shadow-v2.mjs +122 -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
|
* 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,102 @@
|
|
|
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
|
+
// 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 校验失败
|
|
31
|
+
'apply_patch verification failed'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
async function fileExists(p) {
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(p);
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function collectSampleFiles(rootDir) {
|
|
44
|
+
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
45
|
+
const files = [];
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (!entry.isFile()) continue;
|
|
48
|
+
if (!entry.name.toLowerCase().endsWith('.json')) continue;
|
|
49
|
+
files.push(path.join(rootDir, entry.name));
|
|
50
|
+
}
|
|
51
|
+
return files;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function checkFile(filePath) {
|
|
55
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
56
|
+
const hits = [];
|
|
57
|
+
for (const pattern of ERROR_PATTERNS) {
|
|
58
|
+
if (raw.includes(pattern)) {
|
|
59
|
+
hits.push(pattern);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return hits;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
if (!(await fileExists(ROOT))) {
|
|
67
|
+
console.log(`[verify:errorsamples] skip (directory not found: ${ROOT})`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const files = await collectSampleFiles(ROOT);
|
|
72
|
+
if (!files.length) {
|
|
73
|
+
console.log(`[verify:errorsamples] skip (no *.json samples under ${ROOT})`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(`[verify:errorsamples] scanning ${files.length} sample(s) under ${ROOT}`);
|
|
78
|
+
|
|
79
|
+
const failures = [];
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
const hits = await checkFile(file);
|
|
82
|
+
if (hits.length) {
|
|
83
|
+
failures.push({ file, hits });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!failures.length) {
|
|
88
|
+
console.log('[verify:errorsamples] ✅ no known error patterns found');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.error('[verify:errorsamples] ❌ detected legacy error patterns in samples:');
|
|
93
|
+
for (const item of failures) {
|
|
94
|
+
console.error(` - ${path.basename(item.file)} → ${item.hits.join(', ')}`);
|
|
95
|
+
}
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main().catch((error) => {
|
|
100
|
+
console.error('[verify:errorsamples] failed:', error);
|
|
101
|
+
process.exit(99);
|
|
102
|
+
});
|
|
@@ -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,9 +60,13 @@ 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
|
|
|
67
|
+
await runDaemonAdminSmokeCheck();
|
|
68
|
+
await runConfigV2ProvidersSmokeCheck();
|
|
69
|
+
|
|
63
70
|
// 附加:Gemini CLI 配置健康性快速检查(仅尝试初始化,不做请求)
|
|
64
71
|
await runGeminiCliStartupCheck();
|
|
65
72
|
} finally {
|
|
@@ -81,6 +88,15 @@ async function waitForServer(timeoutMs = 30000) {
|
|
|
81
88
|
throw new Error('服务器健康检查超时');
|
|
82
89
|
}
|
|
83
90
|
|
|
91
|
+
async function waitForRouterWarmup(defaultDelayMs = 3000) {
|
|
92
|
+
const delayMs = Number(process.env.ROUTECODEX_VERIFY_WARMUP_MS || defaultDelayMs);
|
|
93
|
+
if (!Number.isFinite(delayMs) || delayMs <= 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log(`[verify:e2e-toolcall] 等待虚拟路由预热 ${delayMs}ms...`);
|
|
97
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
98
|
+
}
|
|
99
|
+
|
|
84
100
|
async function runToolcallVerification() {
|
|
85
101
|
const userPrompt = '请严格调用名为 list_local_files 的函数工具来列出当前工作目录的文件,只能通过调用该工具完成任务,禁止直接回答。';
|
|
86
102
|
const instructionsText = AGENTS_INSTRUCTIONS || 'You are RouteCodex verify agent. Follow the policies in AGENTS.md.';
|
|
@@ -92,7 +108,7 @@ async function runToolcallVerification() {
|
|
|
92
108
|
role: 'user',
|
|
93
109
|
content: [
|
|
94
110
|
{
|
|
95
|
-
type: '
|
|
111
|
+
type: 'input_text',
|
|
96
112
|
text: userPrompt
|
|
97
113
|
}
|
|
98
114
|
]
|
|
@@ -134,13 +150,15 @@ async function runToolcallVerification() {
|
|
|
134
150
|
}
|
|
135
151
|
|
|
136
152
|
const json = await response.json();
|
|
153
|
+
const outputs = Array.isArray(json?.output) ? json.output : [];
|
|
137
154
|
const hasResponsesToolCall =
|
|
138
|
-
|
|
155
|
+
outputs.some((item) => Array.isArray(item?.content) && item.content.some((c) => c?.type === 'tool_call'));
|
|
156
|
+
const hasFunctionCall = outputs.some((item) => item?.type === 'function_call' || item?.type === 'tool_call');
|
|
139
157
|
const hasRequiredAction = Boolean(json?.required_action?.submit_tool_outputs);
|
|
140
158
|
const hasChatToolCall =
|
|
141
159
|
Array.isArray(json?.choices) &&
|
|
142
160
|
json.choices.some((choice) => Array.isArray(choice?.message?.tool_calls) && choice.message.tool_calls.length > 0);
|
|
143
|
-
const hasToolCall = hasResponsesToolCall || hasRequiredAction || hasChatToolCall;
|
|
161
|
+
const hasToolCall = hasResponsesToolCall || hasFunctionCall || hasRequiredAction || hasChatToolCall;
|
|
144
162
|
|
|
145
163
|
if (!hasToolCall) {
|
|
146
164
|
console.error('[verify:e2e-toolcall] Unexpected response:', JSON.stringify(json, null, 2));
|
|
@@ -148,6 +166,55 @@ async function runToolcallVerification() {
|
|
|
148
166
|
}
|
|
149
167
|
}
|
|
150
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
|
+
|
|
151
218
|
async function runGeminiCliStartupCheck() {
|
|
152
219
|
if (!GEMINI_CLI_CONFIG || !fs.existsSync(GEMINI_CLI_CONFIG)) {
|
|
153
220
|
return;
|