@jsonstudio/rcc 0.89.1136 → 0.89.1205
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.js +2 -2
- package/dist/cli/commands/clean.d.ts +16 -0
- package/dist/cli/commands/clean.js +58 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/code.d.ts +55 -0
- package/dist/cli/commands/code.js +376 -0
- package/dist/cli/commands/code.js.map +1 -0
- package/dist/cli/commands/config.d.ts +31 -0
- package/dist/cli/commands/config.js +168 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/env.d.ts +20 -0
- package/dist/cli/commands/env.js +73 -0
- package/dist/cli/commands/env.js.map +1 -0
- package/dist/cli/commands/examples.d.ts +5 -0
- package/dist/cli/commands/examples.js +66 -0
- package/dist/cli/commands/examples.js.map +1 -0
- package/dist/cli/commands/port.d.ts +24 -0
- package/dist/cli/commands/port.js +85 -0
- package/dist/cli/commands/port.js.map +1 -0
- package/dist/cli/commands/restart.d.ts +50 -0
- package/dist/cli/commands/restart.js +176 -0
- package/dist/cli/commands/restart.js.map +1 -0
- package/dist/cli/commands/start.d.ts +68 -0
- package/dist/cli/commands/start.js +295 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +16 -0
- package/dist/cli/commands/status.js +104 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/stop.d.ts +35 -0
- package/dist/cli/commands/stop.js +95 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/logger.d.ts +8 -0
- package/dist/cli/logger.js +9 -0
- package/dist/cli/logger.js.map +1 -0
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.js +16 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/program.d.ts +8 -0
- package/dist/cli/program.js +16 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/register/basic-commands.d.ts +30 -0
- package/dist/cli/register/basic-commands.js +11 -0
- package/dist/cli/register/basic-commands.js.map +1 -0
- package/dist/cli/register/code-command.d.ts +3 -0
- package/dist/cli/register/code-command.js +5 -0
- package/dist/cli/register/code-command.js.map +1 -0
- package/dist/cli/register/restart-command.d.ts +3 -0
- package/dist/cli/register/restart-command.js +5 -0
- package/dist/cli/register/restart-command.js.map +1 -0
- package/dist/cli/register/start-command.d.ts +3 -0
- package/dist/cli/register/start-command.js +5 -0
- package/dist/cli/register/start-command.js.map +1 -0
- package/dist/cli/register/status-config-commands.d.ts +16 -0
- package/dist/cli/register/status-config-commands.js +7 -0
- package/dist/cli/register/status-config-commands.js.map +1 -0
- package/dist/cli/register/stop-command.d.ts +3 -0
- package/dist/cli/register/stop-command.js +5 -0
- package/dist/cli/register/stop-command.js.map +1 -0
- package/dist/cli/runtime.d.ts +5 -0
- package/dist/cli/runtime.js +11 -0
- package/dist/cli/runtime.js.map +1 -0
- package/dist/cli/server/port-utils.d.ts +52 -0
- package/dist/cli/server/port-utils.js +193 -0
- package/dist/cli/server/port-utils.js.map +1 -0
- package/dist/cli/spinner.d.ts +10 -0
- package/dist/cli/spinner.js +59 -0
- package/dist/cli/spinner.js.map +1 -0
- package/dist/cli/utils/normalize.d.ts +2 -0
- package/dist/cli/utils/normalize.js +22 -0
- package/dist/cli/utils/normalize.js.map +1 -0
- package/dist/cli/utils/safe-read-json.d.ts +1 -0
- package/dist/cli/utils/safe-read-json.js +11 -0
- package/dist/cli/utils/safe-read-json.js.map +1 -0
- package/dist/cli.js +148 -1775
- package/dist/cli.js.map +1 -1
- package/dist/client/anthropic/anthropic-protocol-client.js +4 -3
- package/dist/client/anthropic/anthropic-protocol-client.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.d.ts +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +10 -3
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
- package/dist/commands/quota-daemon.js +2 -2
- package/dist/commands/quota-daemon.js.map +1 -1
- package/dist/config/provider-v2-loader.js +4 -2
- package/dist/config/provider-v2-loader.js.map +1 -1
- package/dist/manager/modules/quota/index.js +21 -4
- package/dist/manager/modules/quota/index.js.map +1 -1
- package/dist/manager/modules/routing/index.js.map +1 -1
- package/dist/manager/storage/file-store.js +1 -1
- package/dist/manager/storage/file-store.js.map +1 -1
- package/dist/modules/llmswitch/bridge.js +45 -1
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +2 -2
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/core/api/provider-config.d.ts +2 -0
- package/dist/providers/core/api/provider-types.d.ts +2 -0
- package/dist/providers/core/runtime/base-provider.js +21 -27
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +1 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +37 -5
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.js +23 -29
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.js +20 -0
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/utils/http-client.d.ts +9 -0
- package/dist/providers/core/utils/http-client.js +9 -11
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/core/utils/provider-error-reporter.js +2 -6
- package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.js +19 -5
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/server/runtime/http-server/hub-shadow-compare.d.ts +18 -0
- package/dist/server/runtime/http-server/hub-shadow-compare.js +180 -0
- package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -0
- package/dist/server/runtime/http-server/index.d.ts +4 -0
- package/dist/server/runtime/http-server/index.js +202 -11
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +9 -1
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +8 -4
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/stats-manager.js +9 -3
- package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
- package/dist/utils/errorsamples.d.ts +5 -0
- package/dist/utils/errorsamples.js +27 -0
- package/dist/utils/errorsamples.js.map +1 -0
- package/dist/utils/runtime-versions.d.ts +1 -0
- package/dist/utils/runtime-versions.js +38 -0
- package/dist/utils/runtime-versions.js.map +1 -0
- package/package.json +10 -4
- package/scripts/anthropic-compare-modes.mjs +40 -3
- package/scripts/antigravity-smoke.mjs +180 -0
- package/scripts/backfill-apply-patch-exec-errorsamples.mjs +225 -0
- package/scripts/compare-codex-rccx.mjs +59 -1
- package/scripts/compare-responses-request.mjs +50 -4
- package/scripts/lib/errorsamples.mjs +23 -0
- package/scripts/mock-provider/run-regressions.mjs +12 -2
- package/scripts/policy-violations-report.mjs +257 -0
- package/scripts/publish-rcc.mjs +16 -2
- package/scripts/tests/unified-hub-responses-enforce-safe.mjs +37 -0
- package/scripts/tests/unified-hub-shadow-regression.mjs +55 -0
- package/scripts/unified-hub-shadow-compare.mjs +359 -0
- package/scripts/verify-e2e-gemini-followup-sample.mjs +269 -0
- package/scripts/virtual-router-shadow-v2-real.mjs +71 -1
- package/scripts/virtual-router-shadow-v2.mjs +41 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Antigravity upstream smoke test (direct provider call).
|
|
4
|
+
*
|
|
5
|
+
* Purpose:
|
|
6
|
+
* - Hit Antigravity (Cloud Code Assist) upstream using Gemini CLI HTTP provider.
|
|
7
|
+
* - Validate request shaping is accepted (especially "no body.request.request.*").
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/antigravity-smoke.mjs
|
|
11
|
+
*
|
|
12
|
+
* Required:
|
|
13
|
+
* - A valid Antigravity OAuth token file (JSON), e.g.
|
|
14
|
+
* ~/.routecodex/auth/antigravity-oauth-1-<alias>.json
|
|
15
|
+
* - Export env:
|
|
16
|
+
* ANTIGRAVITY_TOKEN_FILE=/absolute/path/to/token.json
|
|
17
|
+
*
|
|
18
|
+
* Optional:
|
|
19
|
+
* ANTIGRAVITY_BASEURL=https://daily-cloudcode-pa.sandbox.googleapis.com
|
|
20
|
+
* ANTIGRAVITY_MODEL=gemini-3-pro-high
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import os from 'node:os';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
|
|
27
|
+
import { GeminiCLIHttpProvider } from '../dist/providers/core/runtime/gemini-cli-http-provider.js';
|
|
28
|
+
import { GeminiSseToJsonConverter } from '../sharedmodule/llmswitch-core/dist/sse/sse-to-json/index.js';
|
|
29
|
+
|
|
30
|
+
function resolveTokenFile() {
|
|
31
|
+
const raw =
|
|
32
|
+
(process.env.ANTIGRAVITY_TOKEN_FILE && process.env.ANTIGRAVITY_TOKEN_FILE.trim()) ||
|
|
33
|
+
(process.env.ROUTECODEX_ANTIGRAVITY_TOKEN_FILE && process.env.ROUTECODEX_ANTIGRAVITY_TOKEN_FILE.trim()) ||
|
|
34
|
+
(process.env.RCC_ANTIGRAVITY_TOKEN_FILE && process.env.RCC_ANTIGRAVITY_TOKEN_FILE.trim()) ||
|
|
35
|
+
'';
|
|
36
|
+
if (raw) {
|
|
37
|
+
const expanded = raw.startsWith('~/') ? path.join(os.homedir(), raw.slice(2)) : raw;
|
|
38
|
+
return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);
|
|
39
|
+
}
|
|
40
|
+
const authDir = path.join(os.homedir(), '.routecodex', 'auth');
|
|
41
|
+
const defaultPath = path.join(authDir, 'antigravity-oauth.json');
|
|
42
|
+
if (fs.existsSync(defaultPath)) {
|
|
43
|
+
return defaultPath;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const candidates = fs
|
|
47
|
+
.readdirSync(authDir)
|
|
48
|
+
.filter((name) => name.startsWith('antigravity-oauth-') && name.endsWith('.json'))
|
|
49
|
+
.map((name) => path.join(authDir, name));
|
|
50
|
+
if (!candidates.length) {
|
|
51
|
+
return defaultPath;
|
|
52
|
+
}
|
|
53
|
+
candidates.sort((a, b) => {
|
|
54
|
+
try {
|
|
55
|
+
return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs;
|
|
56
|
+
} catch {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return candidates[0];
|
|
61
|
+
} catch {
|
|
62
|
+
return defaultPath;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractTextFromGeminiResponse(payload) {
|
|
67
|
+
const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
|
|
68
|
+
const primary = candidates[0] && typeof candidates[0] === 'object' ? candidates[0] : null;
|
|
69
|
+
const parts = primary?.content?.parts;
|
|
70
|
+
if (!Array.isArray(parts)) return '';
|
|
71
|
+
return parts
|
|
72
|
+
.map((p) => (p && typeof p === 'object' && typeof p.text === 'string' ? p.text : ''))
|
|
73
|
+
.filter((s) => s.trim().length > 0)
|
|
74
|
+
.join('')
|
|
75
|
+
.trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function decodeGeminiSse(stream, requestId) {
|
|
79
|
+
const converter = new GeminiSseToJsonConverter();
|
|
80
|
+
return await converter.convertSseToJson(stream, { requestId });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function main() {
|
|
84
|
+
const tokenFile = resolveTokenFile();
|
|
85
|
+
if (!fs.existsSync(tokenFile)) {
|
|
86
|
+
console.error(`[antigravity-smoke] Missing token file: ${tokenFile}`);
|
|
87
|
+
console.error('[antigravity-smoke] Create one via: `node scripts/auth-antigravity-token.mjs`');
|
|
88
|
+
process.exit(2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const baseUrl =
|
|
92
|
+
(process.env.ANTIGRAVITY_BASEURL && process.env.ANTIGRAVITY_BASEURL.trim()) ||
|
|
93
|
+
'https://daily-cloudcode-pa.sandbox.googleapis.com';
|
|
94
|
+
const model =
|
|
95
|
+
(process.env.ANTIGRAVITY_MODEL && process.env.ANTIGRAVITY_MODEL.trim()) || 'gemini-3-pro-high';
|
|
96
|
+
|
|
97
|
+
const config = {
|
|
98
|
+
id: 'antigravity-smoke',
|
|
99
|
+
config: {
|
|
100
|
+
// IMPORTANT: Antigravity uses Gemini CLI protocol (Cloud Code Assist v1internal).
|
|
101
|
+
providerType: 'gemini',
|
|
102
|
+
providerId: 'antigravity',
|
|
103
|
+
baseUrl,
|
|
104
|
+
auth: {
|
|
105
|
+
type: 'antigravity-oauth',
|
|
106
|
+
apiKey: '',
|
|
107
|
+
tokenFile
|
|
108
|
+
},
|
|
109
|
+
overrides: { maxRetries: 0 }
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const dependencies = {
|
|
114
|
+
logger: { logModule: () => {}, logProviderRequest: () => {} },
|
|
115
|
+
errorHandlingCenter: { handleError: async () => {} }
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const provider = new GeminiCLIHttpProvider(config, dependencies);
|
|
119
|
+
await provider.initialize();
|
|
120
|
+
|
|
121
|
+
const buildPayload = ({ nestedRequest }) => {
|
|
122
|
+
const core = {
|
|
123
|
+
model,
|
|
124
|
+
contents: [{ role: 'user', parts: [{ text: 'Reply with exactly: pong' }] }],
|
|
125
|
+
// Antigravity agent runtime may spend a portion of maxOutputTokens on internal thoughts.
|
|
126
|
+
// Use a sufficiently high value so we can reliably observe a visible text response.
|
|
127
|
+
generationConfig: { maxOutputTokens: 256 }
|
|
128
|
+
};
|
|
129
|
+
if (nestedRequest) {
|
|
130
|
+
// This intentionally mimics an illegal intermediate shape that used to produce
|
|
131
|
+
// `body.request.request.*` after protocol-client wrapping. Provider preprocess
|
|
132
|
+
// must flatten it before sending upstream.
|
|
133
|
+
return { model, request: { contents: core.contents, generationConfig: core.generationConfig } };
|
|
134
|
+
}
|
|
135
|
+
return core;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
for (const variant of [
|
|
139
|
+
{ name: 'top_level_contents', nestedRequest: false },
|
|
140
|
+
{ name: 'nested_request_container', nestedRequest: true }
|
|
141
|
+
]) {
|
|
142
|
+
const maxAttempts = 3;
|
|
143
|
+
let lastDecoded = null;
|
|
144
|
+
let okText = '';
|
|
145
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
146
|
+
const requestId = `antigravity-smoke-${variant.name}-a${attempt}-${Date.now()}`;
|
|
147
|
+
const request = { data: buildPayload(variant) };
|
|
148
|
+
const res = await provider.sendRequest(request);
|
|
149
|
+
const stream = res?.__sse_responses || res?.data?.__sse_responses;
|
|
150
|
+
if (!stream) {
|
|
151
|
+
console.error(`[antigravity-smoke] Missing SSE stream for ${variant.name} (attempt ${attempt}/${maxAttempts})`);
|
|
152
|
+
console.error(JSON.stringify(res?.data ?? res).slice(0, 800));
|
|
153
|
+
process.exit(3);
|
|
154
|
+
}
|
|
155
|
+
const decoded = await decodeGeminiSse(stream, requestId);
|
|
156
|
+
lastDecoded = decoded;
|
|
157
|
+
const text = extractTextFromGeminiResponse(decoded);
|
|
158
|
+
if (text && text.toLowerCase().includes('pong')) {
|
|
159
|
+
okText = text;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
if (attempt < maxAttempts) {
|
|
163
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!okText) {
|
|
167
|
+
console.error(`[antigravity-smoke] Unexpected response for ${variant.name}`);
|
|
168
|
+
console.error(JSON.stringify(lastDecoded).slice(0, 1200));
|
|
169
|
+
process.exit(4);
|
|
170
|
+
}
|
|
171
|
+
console.log(`[antigravity-smoke] OK ${variant.name}: ${JSON.stringify(okText).slice(0, 120)}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log('[antigravity-smoke] done');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
main().catch((err) => {
|
|
178
|
+
console.error('[antigravity-smoke] Error:', err instanceof Error ? err.stack || err.message : String(err));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Backfill apply_patch execution failures into ~/.routecodex/errorsamples/apply_patch_exec/**.
|
|
5
|
+
*
|
|
6
|
+
* Source: ~/.routecodex/codex-samples/** (stage snapshots)
|
|
7
|
+
* Signal: tool role message name=apply_patch and content includes "apply_patch verification failed".
|
|
8
|
+
*
|
|
9
|
+
* This is a best-effort collector for debugging. It does NOT attempt any semantic repair.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs/promises';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import crypto from 'node:crypto';
|
|
16
|
+
|
|
17
|
+
const HOME = os.homedir();
|
|
18
|
+
const CODEX_ROOT_PRIMARY = path.join(HOME, '.routecodex', 'codex-samples');
|
|
19
|
+
const CODEX_ROOT_ALT = path.join(HOME, '.routecodex', 'codex samples');
|
|
20
|
+
|
|
21
|
+
const ERR_BASE =
|
|
22
|
+
process.env.ROUTECODEX_ERRORSAMPLES_DIR && process.env.ROUTECODEX_ERRORSAMPLES_DIR.trim().length
|
|
23
|
+
? path.resolve(process.env.ROUTECODEX_ERRORSAMPLES_DIR)
|
|
24
|
+
: path.join(HOME, '.routecodex', 'errorsamples');
|
|
25
|
+
const OUT_ROOT = path.join(ERR_BASE, 'apply_patch_exec');
|
|
26
|
+
|
|
27
|
+
const MAX_PER_TYPE = 250;
|
|
28
|
+
|
|
29
|
+
function detectApplyPatchToolMode() {
|
|
30
|
+
return 'freeform';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function classifyExecutionFailure(content) {
|
|
34
|
+
const raw = String(content || '');
|
|
35
|
+
const trimmed = raw.trim();
|
|
36
|
+
const prefix = 'apply_patch verification failed:';
|
|
37
|
+
const msg = trimmed.toLowerCase().startsWith(prefix) ? trimmed.slice(prefix.length).trim() : trimmed;
|
|
38
|
+
const lower = msg.toLowerCase();
|
|
39
|
+
|
|
40
|
+
if (lower.includes('failed to read file')) return { errorType: 'read_file_failed', message: msg };
|
|
41
|
+
if (lower.includes('no such file') || lower.includes('file not found')) return { errorType: 'file_not_found', message: msg };
|
|
42
|
+
if (lower.includes('failed to find context')) return { errorType: 'context_not_found', message: msg };
|
|
43
|
+
if (lower.includes('failed to find expected lines')) return { errorType: 'expected_lines_not_found', message: msg };
|
|
44
|
+
if (lower.includes('invalid patch')) return { errorType: 'invalid_patch', message: msg };
|
|
45
|
+
if (lower.includes('failed to parse')) return { errorType: 'parse_failed', message: msg };
|
|
46
|
+
|
|
47
|
+
return { errorType: 'unknown', message: msg };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stableId({ errorType, errorMessage, toolCallId, toolCallArgs, requestId, mode }) {
|
|
51
|
+
const key = `${String(errorType)}:${String(errorMessage)}:${String(toolCallId || '')}:${String(toolCallArgs || '')}:${String(
|
|
52
|
+
requestId || ''
|
|
53
|
+
)}:${String(mode || '')}`;
|
|
54
|
+
return crypto.createHash('sha1').update(key).digest('hex').slice(0, 16);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function fileExists(p) {
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(p);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function* walkJsonFiles(root) {
|
|
67
|
+
const stack = [root];
|
|
68
|
+
while (stack.length) {
|
|
69
|
+
const current = stack.pop();
|
|
70
|
+
let entries;
|
|
71
|
+
try {
|
|
72
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
73
|
+
} catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const full = path.join(current, entry.name);
|
|
78
|
+
if (entry.isDirectory()) stack.push(full);
|
|
79
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) yield full;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function ensureDir(p) {
|
|
85
|
+
await fs.mkdir(p, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function countJsonFiles(dir) {
|
|
89
|
+
try {
|
|
90
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
91
|
+
return entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.json')).length;
|
|
92
|
+
} catch {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildToolCallArgsIndex(messages) {
|
|
98
|
+
const map = new Map();
|
|
99
|
+
for (const msg of messages) {
|
|
100
|
+
const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
|
|
101
|
+
for (const tc of toolCalls) {
|
|
102
|
+
const id = typeof tc?.id === 'string' ? tc.id : '';
|
|
103
|
+
const fn = tc?.function;
|
|
104
|
+
const name = typeof fn?.name === 'string' ? String(fn.name).trim().toLowerCase() : '';
|
|
105
|
+
if (!id || name !== 'apply_patch') continue;
|
|
106
|
+
const args = typeof fn?.arguments === 'string' ? fn.arguments : '';
|
|
107
|
+
if (args) map.set(id, args);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main() {
|
|
114
|
+
const roots = [];
|
|
115
|
+
if (await fileExists(CODEX_ROOT_PRIMARY)) roots.push(CODEX_ROOT_PRIMARY);
|
|
116
|
+
if (await fileExists(CODEX_ROOT_ALT)) roots.push(CODEX_ROOT_ALT);
|
|
117
|
+
|
|
118
|
+
if (!roots.length) {
|
|
119
|
+
console.log('[backfill:apply_patch_exec] skip (no codex-samples root found)');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await ensureDir(OUT_ROOT);
|
|
124
|
+
|
|
125
|
+
const mode = detectApplyPatchToolMode();
|
|
126
|
+
|
|
127
|
+
let scanned = 0;
|
|
128
|
+
let matchedFiles = 0;
|
|
129
|
+
let captured = 0;
|
|
130
|
+
let skippedLimit = 0;
|
|
131
|
+
let skippedExists = 0;
|
|
132
|
+
|
|
133
|
+
for (const root of roots) {
|
|
134
|
+
for await (const file of walkJsonFiles(root)) {
|
|
135
|
+
scanned += 1;
|
|
136
|
+
if (!file.endsWith('req_process_stage1_tool_governance.json')) continue;
|
|
137
|
+
|
|
138
|
+
let doc;
|
|
139
|
+
try {
|
|
140
|
+
doc = JSON.parse(await fs.readFile(file, 'utf-8'));
|
|
141
|
+
} catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const messages = Array.isArray(doc?.messages) ? doc.messages : [];
|
|
146
|
+
if (!messages.length) continue;
|
|
147
|
+
|
|
148
|
+
const toolCallArgsById = buildToolCallArgsIndex(messages);
|
|
149
|
+
|
|
150
|
+
const toolMsgs = messages.filter(
|
|
151
|
+
(m) =>
|
|
152
|
+
m &&
|
|
153
|
+
m.role === 'tool' &&
|
|
154
|
+
String(m.name || '').trim().toLowerCase() === 'apply_patch' &&
|
|
155
|
+
typeof m.content === 'string' &&
|
|
156
|
+
m.content.toLowerCase().includes('apply_patch verification failed')
|
|
157
|
+
);
|
|
158
|
+
if (!toolMsgs.length) continue;
|
|
159
|
+
|
|
160
|
+
matchedFiles += 1;
|
|
161
|
+
|
|
162
|
+
const metadata = doc?.metadata || {};
|
|
163
|
+
const requestId = typeof metadata?.requestId === 'string' ? metadata.requestId : undefined;
|
|
164
|
+
const entryEndpoint = typeof metadata?.originalEndpoint === 'string' ? metadata.originalEndpoint : undefined;
|
|
165
|
+
const providerKey = typeof metadata?.providerKey === 'string' ? metadata.providerKey : undefined;
|
|
166
|
+
const modelId = typeof doc?.model === 'string' ? doc.model : undefined;
|
|
167
|
+
|
|
168
|
+
for (const m of toolMsgs) {
|
|
169
|
+
const { errorType, message } = classifyExecutionFailure(m.content);
|
|
170
|
+
const safeType = String(errorType || 'unknown').replace(/[^a-z0-9-]/gi, '_');
|
|
171
|
+
const typeDir = path.join(OUT_ROOT, safeType);
|
|
172
|
+
await ensureDir(typeDir);
|
|
173
|
+
|
|
174
|
+
const currentCount = await countJsonFiles(typeDir);
|
|
175
|
+
if (currentCount >= MAX_PER_TYPE) {
|
|
176
|
+
skippedLimit += 1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const toolCallId = typeof m.tool_call_id === 'string' ? m.tool_call_id : undefined;
|
|
181
|
+
const toolCallArgs = toolCallId ? toolCallArgsById.get(toolCallId) : undefined;
|
|
182
|
+
const id = `sample_${stableId({
|
|
183
|
+
errorType,
|
|
184
|
+
errorMessage: message,
|
|
185
|
+
toolCallId,
|
|
186
|
+
toolCallArgs,
|
|
187
|
+
requestId,
|
|
188
|
+
mode
|
|
189
|
+
})}`;
|
|
190
|
+
const outPath = path.join(typeDir, `${id}.json`);
|
|
191
|
+
|
|
192
|
+
if (await fileExists(outPath)) {
|
|
193
|
+
skippedExists += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const payload = {
|
|
198
|
+
id,
|
|
199
|
+
timestamp: new Date().toISOString(),
|
|
200
|
+
errorType,
|
|
201
|
+
errorMessage: message,
|
|
202
|
+
toolCallId,
|
|
203
|
+
toolCallArgs,
|
|
204
|
+
requestId,
|
|
205
|
+
entryEndpoint,
|
|
206
|
+
providerKey,
|
|
207
|
+
model: modelId,
|
|
208
|
+
source: 'codex-samples:req_process_stage1_tool_governance',
|
|
209
|
+
meta: { applyPatchToolMode: mode, sourceFile: file }
|
|
210
|
+
};
|
|
211
|
+
await fs.writeFile(outPath, JSON.stringify(payload, null, 2), 'utf-8');
|
|
212
|
+
captured += 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(
|
|
218
|
+
`[backfill:apply_patch_exec] scanned=${scanned} matchedFiles=${matchedFiles} captured=${captured} skippedExists=${skippedExists} skippedLimit=${skippedLimit} out=${OUT_ROOT}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main().catch((err) => {
|
|
223
|
+
console.error('[backfill:apply_patch_exec] failed:', err?.stack || err?.message || String(err));
|
|
224
|
+
process.exit(2);
|
|
225
|
+
});
|
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import fs from 'node:fs';
|
|
26
|
+
import os from 'node:os';
|
|
26
27
|
import path from 'node:path';
|
|
27
28
|
import { spawn } from 'node:child_process';
|
|
29
|
+
import { writeErrorSampleJson } from './lib/errorsamples.mjs';
|
|
28
30
|
|
|
29
31
|
const DEFAULT_ROUTE_BASE = process.env.ROUTECODEX_BASE || 'http://127.0.0.1:5555';
|
|
30
32
|
const DEFAULT_RCCX_BASE = process.env.RCCX_BASE || 'http://127.0.0.1:5556';
|
|
@@ -230,6 +232,29 @@ async function main() {
|
|
|
230
232
|
rccxRunDir,
|
|
231
233
|
rccxError: rccxResult.error || null
|
|
232
234
|
});
|
|
235
|
+
try {
|
|
236
|
+
const file = await writeErrorSampleJson({
|
|
237
|
+
group: 'compare-codex-rccx',
|
|
238
|
+
kind: 'missing-artifacts',
|
|
239
|
+
payload: {
|
|
240
|
+
kind: 'compare-codex-rccx-missing-artifacts',
|
|
241
|
+
stamp: new Date().toISOString(),
|
|
242
|
+
samplePath,
|
|
243
|
+
requestId,
|
|
244
|
+
routeBase: opts.routeBase,
|
|
245
|
+
rccxBase: opts.rccxBase,
|
|
246
|
+
routeLabel: opts.routeLabel,
|
|
247
|
+
rccxLabel: opts.rccxLabel,
|
|
248
|
+
routeRunDir,
|
|
249
|
+
rccxRunDir,
|
|
250
|
+
routeError: routeResult.error || null,
|
|
251
|
+
rccxError: rccxResult.error || null
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
console.error(`[compare-codex-rccx] wrote errorsample: ${file}`);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error('[compare-codex-rccx] failed to write errorsample:', err);
|
|
257
|
+
}
|
|
233
258
|
process.exitCode = 1;
|
|
234
259
|
return;
|
|
235
260
|
}
|
|
@@ -252,6 +277,33 @@ async function main() {
|
|
|
252
277
|
// 为了调试 429 / 系列冷却问题,额外打印一小段 body 样本。
|
|
253
278
|
console.log('[compare-codex-rccx] routecodex.bodySample =', routeResult.bodySample);
|
|
254
279
|
console.log('[compare-codex-rccx] rccx.bodySample =', rccxResult.bodySample);
|
|
280
|
+
try {
|
|
281
|
+
const file = await writeErrorSampleJson({
|
|
282
|
+
group: 'compare-codex-rccx',
|
|
283
|
+
kind: 'mismatch',
|
|
284
|
+
payload: {
|
|
285
|
+
kind: 'compare-codex-rccx-mismatch',
|
|
286
|
+
stamp: new Date().toISOString(),
|
|
287
|
+
samplePath,
|
|
288
|
+
requestId,
|
|
289
|
+
routeBase: opts.routeBase,
|
|
290
|
+
rccxBase: opts.rccxBase,
|
|
291
|
+
routeLabel: opts.routeLabel,
|
|
292
|
+
rccxLabel: opts.rccxLabel,
|
|
293
|
+
routeRunDir,
|
|
294
|
+
rccxRunDir,
|
|
295
|
+
routeMeta,
|
|
296
|
+
rccxMeta,
|
|
297
|
+
routeBodyKind: routeResult.bodyKind,
|
|
298
|
+
rccxBodyKind: rccxResult.bodyKind,
|
|
299
|
+
routeBodySample: routeResult.bodySample,
|
|
300
|
+
rccxBodySample: rccxResult.bodySample
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
console.error(`[compare-codex-rccx] wrote errorsample: ${file}`);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.error('[compare-codex-rccx] failed to write errorsample:', err);
|
|
306
|
+
}
|
|
255
307
|
process.exitCode = 1;
|
|
256
308
|
return;
|
|
257
309
|
}
|
|
@@ -263,6 +315,12 @@ async function main() {
|
|
|
263
315
|
|
|
264
316
|
main().catch((err) => {
|
|
265
317
|
console.error('[compare-codex-rccx] fatal error:', err);
|
|
318
|
+
try {
|
|
319
|
+
const root = path.join(os.homedir(), '.routecodex', 'errorsamples', 'compare-codex-rccx');
|
|
320
|
+
const file = path.join(root, `fatal-${Date.now()}.txt`);
|
|
321
|
+
fs.mkdirSync(root, { recursive: true });
|
|
322
|
+
fs.writeFileSync(file, String(err?.stack || err), 'utf8');
|
|
323
|
+
console.error(`[compare-codex-rccx] wrote errorsample: ${file}`);
|
|
324
|
+
} catch {}
|
|
266
325
|
process.exitCode = 1;
|
|
267
326
|
});
|
|
268
|
-
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import path from 'node:path';
|
|
13
|
+
import { writeErrorSampleJson } from './lib/errorsamples.mjs';
|
|
13
14
|
|
|
14
15
|
const protocolFolders = {
|
|
15
16
|
'openai-responses': 'openai-responses',
|
|
@@ -118,9 +119,15 @@ function listCounts(label, payload) {
|
|
|
118
119
|
console.log(
|
|
119
120
|
`${label} counts → input:${inputCount} messages:${messageCount} tools:${toolCount} instructions:${typeof payload?.instructions === 'string'}`
|
|
120
121
|
);
|
|
122
|
+
return {
|
|
123
|
+
input: inputCount,
|
|
124
|
+
messages: messageCount,
|
|
125
|
+
tools: toolCount,
|
|
126
|
+
hasInstructions: typeof payload?.instructions === 'string'
|
|
127
|
+
};
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
function main() {
|
|
130
|
+
async function main() {
|
|
124
131
|
const opts = parseArgs();
|
|
125
132
|
const clientProtocol = opts.clientProtocol || opts.protocol;
|
|
126
133
|
const providerProtocol = opts.providerProtocol || opts.protocol;
|
|
@@ -146,8 +153,8 @@ function main() {
|
|
|
146
153
|
const clientPayload = extractPayload(clientSnapshot);
|
|
147
154
|
const providerPayload = extractPayload(providerSnapshot);
|
|
148
155
|
|
|
149
|
-
listCounts('Client', clientPayload);
|
|
150
|
-
listCounts('Provider', providerPayload);
|
|
156
|
+
const clientCounts = listCounts('Client', clientPayload);
|
|
157
|
+
const providerCounts = listCounts('Provider', providerPayload);
|
|
151
158
|
|
|
152
159
|
const diffs = diffPayloads(pruneUndefined(clientPayload), pruneUndefined(providerPayload));
|
|
153
160
|
if (!diffs.length) {
|
|
@@ -160,6 +167,30 @@ function main() {
|
|
|
160
167
|
if (diffs.length > 50) {
|
|
161
168
|
console.log(` ... ${diffs.length - 50} more differences`);
|
|
162
169
|
}
|
|
170
|
+
|
|
171
|
+
const record = {
|
|
172
|
+
kind: 'compare-responses-request-diff',
|
|
173
|
+
requestId: normalizedId,
|
|
174
|
+
clientProtocol,
|
|
175
|
+
providerProtocol,
|
|
176
|
+
clientPath,
|
|
177
|
+
providerPath,
|
|
178
|
+
clientCounts,
|
|
179
|
+
providerCounts,
|
|
180
|
+
diffsCount: diffs.length,
|
|
181
|
+
diffsHead: diffs.slice(0, 200),
|
|
182
|
+
diffsTruncated: diffs.length > 200
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
const file = await writeErrorSampleJson({
|
|
186
|
+
group: 'compare-responses-request',
|
|
187
|
+
kind: 'diff',
|
|
188
|
+
payload: record
|
|
189
|
+
});
|
|
190
|
+
console.error(`[compare-responses-request] wrote errorsample: ${file}`);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.error('[compare-responses-request] failed to write errorsample:', err);
|
|
193
|
+
}
|
|
163
194
|
}
|
|
164
195
|
}
|
|
165
196
|
|
|
@@ -217,4 +248,19 @@ function diffPayloads(expected, actual, path = '<root>') {
|
|
|
217
248
|
return [{ path, expected, actual }];
|
|
218
249
|
}
|
|
219
250
|
|
|
220
|
-
main()
|
|
251
|
+
main().catch((err) => {
|
|
252
|
+
console.error('[compare-responses-request] fatal error:', err);
|
|
253
|
+
void writeErrorSampleJson({
|
|
254
|
+
group: 'compare-responses-request',
|
|
255
|
+
kind: 'fatal',
|
|
256
|
+
payload: {
|
|
257
|
+
kind: 'compare-responses-request-fatal',
|
|
258
|
+
stamp: new Date().toISOString(),
|
|
259
|
+
argv: process.argv.slice(2),
|
|
260
|
+
error: String(err?.stack || err)
|
|
261
|
+
}
|
|
262
|
+
}).then((file) => {
|
|
263
|
+
console.error(`[compare-responses-request] wrote errorsample: ${file}`);
|
|
264
|
+
}).catch(() => {});
|
|
265
|
+
process.exitCode = 1;
|
|
266
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function safeStamp() {
|
|
6
|
+
const iso = new Date().toISOString();
|
|
7
|
+
// 2026-01-18T12:34:56.789Z -> 20260118-123456-789Z
|
|
8
|
+
return iso.replace(/[-:]/g, '').replace('T', '-').replace('.', '-');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function safeName(name) {
|
|
12
|
+
return String(name || 'sample').replace(/[^\w.-]/g, '_');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function writeErrorSampleJson({ group, kind, payload }) {
|
|
16
|
+
const root = path.join(os.homedir(), '.routecodex', 'errorsamples');
|
|
17
|
+
const dir = path.join(root, safeName(group));
|
|
18
|
+
await fs.mkdir(dir, { recursive: true });
|
|
19
|
+
const file = path.join(dir, `${safeName(kind)}-${safeStamp()}-${Math.random().toString(16).slice(2)}.json`);
|
|
20
|
+
await fs.writeFile(file, JSON.stringify(payload, null, 2), 'utf8');
|
|
21
|
+
return file;
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -188,7 +188,7 @@ async function writeTempConfig(sample, port) {
|
|
|
188
188
|
return { dir, file };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
function createServer(configPath, port, snapshotRoot) {
|
|
191
|
+
async function createServer(configPath, port, snapshotRoot) {
|
|
192
192
|
const env = {
|
|
193
193
|
...process.env,
|
|
194
194
|
ROUTECODEX_USE_MOCK: '1',
|
|
@@ -207,6 +207,16 @@ function createServer(configPath, port, snapshotRoot) {
|
|
|
207
207
|
: {})
|
|
208
208
|
};
|
|
209
209
|
const entry = path.join(PROJECT_ROOT, 'dist', 'index.js');
|
|
210
|
+
// Defensive: build scripts or other verification steps may clean dist between checks.
|
|
211
|
+
// Ensure the server entry exists right before spawning.
|
|
212
|
+
// (Avoids confusing "Cannot find module dist/index.js" regressions.)
|
|
213
|
+
if (!(await fileExists(entry))) {
|
|
214
|
+
console.warn('[mock:regressions] dist/index.js missing at spawn time, rebuilding via "npm run build:min"...');
|
|
215
|
+
await runBuildForMockRegressions();
|
|
216
|
+
if (!(await fileExists(entry))) {
|
|
217
|
+
throw new Error(`dist/index.js still missing after rebuild: ${entry}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
210
220
|
const child = spawn(process.execPath, [entry], {
|
|
211
221
|
cwd: PROJECT_ROOT,
|
|
212
222
|
env,
|
|
@@ -465,7 +475,7 @@ async function runSample(sample, index) {
|
|
|
465
475
|
const { dir, file } = await writeTempConfig(sample, port);
|
|
466
476
|
// 为当前样本创建独立的临时快照根目录,并在完成后整体删除
|
|
467
477
|
const snapshotRoot = path.join(dir, 'codex-samples');
|
|
468
|
-
const server = createServer(file, port, snapshotRoot);
|
|
478
|
+
const server = await createServer(file, port, snapshotRoot);
|
|
469
479
|
const tags = new Set(Array.isArray(sample.tags) ? sample.tags : []);
|
|
470
480
|
const expectSseTerminationError = tags.has('responses_sse_terminated');
|
|
471
481
|
const allowSampleError =
|