@jsonstudio/rcc 0.89.1086 → 0.89.1136
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.js +39 -1
- package/dist/cli.js.map +1 -1
- package/dist/client/gemini/gemini-protocol-client.js +5 -0
- package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
- package/dist/commands/provider-update.js +355 -5
- package/dist/commands/provider-update.js.map +1 -1
- package/dist/docs/daemon-admin-ui.html +604 -91
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/manager/modules/quota/index.d.ts +37 -1
- package/dist/manager/modules/quota/index.js +378 -18
- package/dist/manager/modules/quota/index.js.map +1 -1
- package/dist/manager/quota/provider-quota-center.d.ts +3 -0
- package/dist/manager/quota/provider-quota-center.js +88 -24
- package/dist/manager/quota/provider-quota-center.js.map +1 -1
- package/dist/manager/quota/provider-quota-store.js +5 -2
- package/dist/manager/quota/provider-quota-store.js.map +1 -1
- package/dist/manager/types.d.ts +5 -0
- package/dist/providers/core/config/service-profiles.js +1 -1
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +5 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.js +26 -38
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +10 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/server/handlers/handler-utils.d.ts +1 -1
- package/dist/server/handlers/handler-utils.js +82 -4
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +26 -3
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/handlers/sse-dispatcher.js +1 -4
- package/dist/server/handlers/sse-dispatcher.js.map +1 -1
- package/dist/server/runtime/http-server/colored-logger.d.ts +1 -1
- package/dist/server/runtime/http-server/colored-logger.js +22 -10
- package/dist/server/runtime/http-server/colored-logger.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.d.ts +1 -1
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +10 -14
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +108 -115
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +132 -7
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.js +22 -0
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +56 -0
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js +100 -4
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +24 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js +25 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
- package/dist/server/runtime/http-server/executor-provider.js +74 -0
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +7 -1
- package/dist/server/runtime/http-server/index.js +171 -14
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/middleware.d.ts +2 -1
- package/dist/server/runtime/http-server/middleware.js +7 -6
- package/dist/server/runtime/http-server/middleware.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.d.ts +1 -1
- package/dist/server/runtime/http-server/provider-utils.js +19 -2
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +9 -10
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +10 -0
- package/dist/server/runtime/http-server/routes.js +14 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/stats-manager.d.ts +7 -0
- package/dist/server/runtime/http-server/stats-manager.js +22 -3
- package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +10 -0
- package/dist/server/utils/http-error-mapper.js +85 -7
- package/dist/server/utils/http-error-mapper.js.map +1 -1
- package/dist/server/utils/request-id-manager.js +9 -5
- package/dist/server/utils/request-id-manager.js.map +1 -1
- package/dist/server/utils/sse-request-parser.js +2 -1
- package/dist/server/utils/sse-request-parser.js.map +1 -1
- package/dist/server/utils/utf8-chunk-buffer.d.ts +15 -30
- package/dist/server/utils/utf8-chunk-buffer.js +78 -88
- package/dist/server/utils/utf8-chunk-buffer.js.map +1 -1
- package/dist/server/utils/warmup-storm-tracker.js +1 -1
- package/dist/server/utils/warmup-storm-tracker.js.map +1 -1
- package/dist/token-daemon/token-daemon.js +7 -1
- package/dist/token-daemon/token-daemon.js.map +1 -1
- package/dist/tools/provider-update/fetch-models.js +8 -5
- package/dist/tools/provider-update/fetch-models.js.map +1 -1
- package/dist/tools/provider-update/probe-context.d.ts +24 -0
- package/dist/tools/provider-update/probe-context.js +199 -0
- package/dist/tools/provider-update/probe-context.js.map +1 -0
- package/dist/tools/provider-update/types.d.ts +1 -0
- package/package.json +6 -4
- package/scripts/scan-apply-patch-samples.mjs +362 -0
- package/scripts/scan-exec-command-samples.mjs +269 -0
- package/scripts/scan-tool-shape-samples.mjs +291 -0
- package/scripts/tools/sync-apply-patch-regressions.mjs +86 -0
- package/scripts/verify-apply-patch-regressions.mjs +119 -0
- package/scripts/verify-tool-arguments.mjs +1 -2
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { encoding_for_model } from 'tiktoken';
|
|
2
|
+
const encoderCache = new Map();
|
|
3
|
+
function getEncoder(model) {
|
|
4
|
+
const key = model.trim() || 'gpt-4o';
|
|
5
|
+
const existing = encoderCache.get(key);
|
|
6
|
+
if (existing) {
|
|
7
|
+
return existing;
|
|
8
|
+
}
|
|
9
|
+
// tiktoken's TS types restrict this to a known model union; allow override for local experiments.
|
|
10
|
+
const created = encoding_for_model(key);
|
|
11
|
+
encoderCache.set(key, created);
|
|
12
|
+
return created;
|
|
13
|
+
}
|
|
14
|
+
const UNIT_TEXT = 'a ';
|
|
15
|
+
export function buildTextForExactTokenCount(targetTokens, encoderModel = 'gpt-4o') {
|
|
16
|
+
if (!Number.isFinite(targetTokens) || targetTokens <= 0) {
|
|
17
|
+
throw new Error(`Invalid targetTokens: ${targetTokens}`);
|
|
18
|
+
}
|
|
19
|
+
// We deliberately generate text from plain strings (not decode(tokens)) because
|
|
20
|
+
// decode() is not guaranteed to round-trip back to the same token count via encode().
|
|
21
|
+
//
|
|
22
|
+
// Empirically for tiktoken (e.g. 'gpt-4o'), UNIT_TEXT repeat has stable linear growth:
|
|
23
|
+
// encode('a '.repeat(n)).length == n + k
|
|
24
|
+
// We detect k at runtime and compute n to hit targetTokens exactly.
|
|
25
|
+
const encoder = getEncoder(encoderModel);
|
|
26
|
+
if (Math.floor(targetTokens) === 1) {
|
|
27
|
+
return 'a';
|
|
28
|
+
}
|
|
29
|
+
const f1 = encoder.encode(UNIT_TEXT).length;
|
|
30
|
+
const f99 = encoder.encode(UNIT_TEXT.repeat(99)).length;
|
|
31
|
+
const f100 = encoder.encode(UNIT_TEXT.repeat(100)).length;
|
|
32
|
+
const slope = f100 - f99;
|
|
33
|
+
const offset = slope === 1 ? (f1 - 1) : 0;
|
|
34
|
+
let repeats = slope === 1 ? Math.max(1, Math.floor(targetTokens) - offset) : Math.max(1, Math.floor(targetTokens));
|
|
35
|
+
// Fine-tune with minimal extra encodes (should converge immediately for slope=1).
|
|
36
|
+
for (let i = 0; i < 20; i++) {
|
|
37
|
+
const text = UNIT_TEXT.repeat(repeats);
|
|
38
|
+
const tokens = encoder.encode(text).length;
|
|
39
|
+
if (tokens === Math.floor(targetTokens)) {
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
if (tokens < Math.floor(targetTokens)) {
|
|
43
|
+
repeats += Math.max(1, Math.floor(targetTokens) - tokens);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
repeats -= Math.max(1, tokens - Math.floor(targetTokens));
|
|
47
|
+
if (repeats <= 0) {
|
|
48
|
+
repeats = 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Last resort: return best-effort (caller will validate).
|
|
53
|
+
return UNIT_TEXT.repeat(repeats);
|
|
54
|
+
}
|
|
55
|
+
export function countTokens(text, encoderModel = 'gpt-4o') {
|
|
56
|
+
const encoder = getEncoder(encoderModel);
|
|
57
|
+
return encoder.encode(text).length;
|
|
58
|
+
}
|
|
59
|
+
function sleep(ms) {
|
|
60
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
+
}
|
|
62
|
+
async function requestOnce(modelId, threshold, text, opts) {
|
|
63
|
+
const fetcher = opts.fetcher ?? fetch;
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
66
|
+
try {
|
|
67
|
+
const headers = {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
Accept: 'application/json'
|
|
70
|
+
};
|
|
71
|
+
const key = typeof opts.apiKey === 'string' ? opts.apiKey.trim() : '';
|
|
72
|
+
if (key) {
|
|
73
|
+
headers.Authorization = `Bearer ${key}`;
|
|
74
|
+
}
|
|
75
|
+
const body = {
|
|
76
|
+
model: modelId,
|
|
77
|
+
input: [
|
|
78
|
+
{
|
|
79
|
+
role: 'user',
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: 'input_text',
|
|
83
|
+
text
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
stream: false,
|
|
89
|
+
max_output_tokens: 1
|
|
90
|
+
};
|
|
91
|
+
return await fetcher(opts.endpoint, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers,
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
signal: controller.signal
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function requestWithRetries(modelId, threshold, text, opts) {
|
|
103
|
+
let lastError = null;
|
|
104
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
105
|
+
try {
|
|
106
|
+
const resp = await requestOnce(modelId, threshold, text, opts);
|
|
107
|
+
if (resp.status !== 429 && resp.status !== 503 && resp.status !== 504) {
|
|
108
|
+
return resp;
|
|
109
|
+
}
|
|
110
|
+
lastError = new Error(`HTTP ${resp.status}`);
|
|
111
|
+
// Drain body to free sockets.
|
|
112
|
+
try {
|
|
113
|
+
await resp.text();
|
|
114
|
+
}
|
|
115
|
+
catch { /* ignore */ }
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
lastError = err;
|
|
119
|
+
}
|
|
120
|
+
if (attempt < opts.maxRetries) {
|
|
121
|
+
await sleep(500 * (attempt + 1));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError ?? 'request failed'));
|
|
125
|
+
}
|
|
126
|
+
export async function probeContextForModel(modelId, thresholds, { endpoint, apiKey, timeoutMs = 60_000, fetcher, maxRetries = 2, encoderModel = 'gpt-4o' }) {
|
|
127
|
+
const model = (modelId || '').trim();
|
|
128
|
+
if (!model) {
|
|
129
|
+
throw new Error('probeContextForModel: modelId is required');
|
|
130
|
+
}
|
|
131
|
+
const normalizedEndpoint = (endpoint || '').trim();
|
|
132
|
+
if (!normalizedEndpoint) {
|
|
133
|
+
throw new Error('probeContextForModel: endpoint is required');
|
|
134
|
+
}
|
|
135
|
+
const sorted = thresholds
|
|
136
|
+
.map((value) => Math.floor(Number(value)))
|
|
137
|
+
.filter((value) => Number.isFinite(value) && value > 0);
|
|
138
|
+
if (!sorted.length) {
|
|
139
|
+
throw new Error('probeContextForModel: thresholds must be non-empty');
|
|
140
|
+
}
|
|
141
|
+
const passed = [];
|
|
142
|
+
let firstFailure;
|
|
143
|
+
for (const threshold of sorted) {
|
|
144
|
+
const text = buildTextForExactTokenCount(threshold, encoderModel);
|
|
145
|
+
const check = countTokens(text, encoderModel);
|
|
146
|
+
if (check !== threshold) {
|
|
147
|
+
throw new Error(`probeContextForModel: internal token mismatch: want=${threshold} got=${check}`);
|
|
148
|
+
}
|
|
149
|
+
let resp;
|
|
150
|
+
try {
|
|
151
|
+
resp = await requestWithRetries(model, threshold, text, {
|
|
152
|
+
endpoint: normalizedEndpoint,
|
|
153
|
+
apiKey,
|
|
154
|
+
timeoutMs,
|
|
155
|
+
fetcher,
|
|
156
|
+
maxRetries
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
firstFailure = {
|
|
161
|
+
threshold,
|
|
162
|
+
status: 0,
|
|
163
|
+
message: err instanceof Error ? err.message : String(err)
|
|
164
|
+
};
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
if (resp.ok) {
|
|
168
|
+
passed.push(threshold);
|
|
169
|
+
// Drain body to reuse socket; but keep it small.
|
|
170
|
+
try {
|
|
171
|
+
await resp.text();
|
|
172
|
+
}
|
|
173
|
+
catch { /* ignore */ }
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
let responseText = '';
|
|
177
|
+
try {
|
|
178
|
+
responseText = await resp.text();
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
responseText = '';
|
|
182
|
+
}
|
|
183
|
+
firstFailure = {
|
|
184
|
+
threshold,
|
|
185
|
+
status: resp.status,
|
|
186
|
+
statusText: resp.statusText,
|
|
187
|
+
responseSnippet: responseText ? responseText.slice(0, 2000) : undefined
|
|
188
|
+
};
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
modelId: model,
|
|
193
|
+
thresholds: sorted,
|
|
194
|
+
passed,
|
|
195
|
+
maxPassedTokens: passed.length ? passed[passed.length - 1] : null,
|
|
196
|
+
...(firstFailure ? { firstFailure } : {})
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=probe-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe-context.js","sourceRoot":"","sources":["../../../src/tools/provider-update/probe-context.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsonstudio/rcc",
|
|
3
|
-
"version": "0.89.
|
|
3
|
+
"version": "0.89.1136",
|
|
4
4
|
"description": "Multi-provider OpenAI proxy server with anthropic/responses/chat support (release)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "npm run llmswitch:ensure && node scripts/build-core.mjs && node scripts/vendor-core.mjs && npm run clean && node scripts/gen-build-info.mjs && tsc && node scripts/copy-compat-assets.mjs && node scripts/copy-modules-config.mjs",
|
|
31
|
-
"build:dev": "BUILD_MODE=dev npm run build && npm run verify:e2e-toolcall && npm run verify:apply-patch && npm run verify:exec-command && npm run test:routing-instructions && npm run install:global && npm run mock:regressions && npm run verify:errorsamples",
|
|
31
|
+
"build:dev": "BUILD_MODE=dev npm run build && npm run verify:e2e-toolcall && npm run verify:apply-patch && npm run verify:apply-patch-regressions && npm run verify:exec-command && npm run test:routing-instructions && npm run install:global && npm run mock:regressions && npm run verify:errorsamples",
|
|
32
32
|
"build:min": "npm run llmswitch:ensure && node scripts/build-core.mjs && node scripts/vendor-core.mjs && npm run clean && node scripts/gen-build-info.mjs && tsc && node scripts/copy-compat-assets.mjs && node scripts/copy-modules-config.mjs",
|
|
33
33
|
"prepack": "echo skip-prepack",
|
|
34
34
|
"postbuild": "chmod +x dist/cli.js || true",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dev": "tsx watch src/index.ts",
|
|
38
38
|
"jest:run": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
|
39
39
|
"test": "npm run test:routing-instructions && npm run mock:regressions",
|
|
40
|
-
"test:routing-instructions": "npm run jest:run -- --runTestsByPath tests/server/runtime/request-executor.single-attempt.spec.ts tests/servertool/virtual-router-series-cooldown.spec.ts tests/utils/is-direct-execution.test.ts tests/utils/windows-netstat.test.ts",
|
|
40
|
+
"test:routing-instructions": "npm run jest:run -- --runTestsByPath tests/server/runtime/request-executor.single-attempt.spec.ts tests/server/runtime/executor-provider.retryable.spec.ts tests/servertool/virtual-router-series-cooldown.spec.ts tests/servertool/virtual-router-quota-routing.spec.ts tests/servertool/virtual-router-engine-update-deps.spec.ts tests/manager/quota/provider-quota-center.spec.ts tests/manager/quota/provider-quota-store.spec.ts tests/server/http-server/daemon-admin.e2e.spec.ts tests/server/http-server/quota-view-injection.spec.ts tests/server/handlers/sse-timeout.spec.ts tests/utils/is-direct-execution.test.ts tests/utils/windows-netstat.test.ts",
|
|
41
41
|
"test:watch": "npm run jest:run -- --watch",
|
|
42
42
|
"test:coverage": "npm run jest:run -- --coverage",
|
|
43
43
|
"test:integration": "npm run jest:run -- --testPathPattern=integration",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"prepare": "",
|
|
55
55
|
"postinstall": "chmod +x dist/cli.js || true",
|
|
56
56
|
"verify:apply-patch": "node scripts/verify-apply-patch.mjs",
|
|
57
|
+
"verify:apply-patch-regressions": "node scripts/verify-apply-patch-regressions.mjs",
|
|
57
58
|
"verify:exec-command": "node scripts/tests/exec-command-loop.mjs",
|
|
58
59
|
"verify:errorsamples": "node scripts/verify-codex-error-samples.mjs",
|
|
59
60
|
"install:global": "./scripts/install-global.sh",
|
|
@@ -119,6 +120,7 @@
|
|
|
119
120
|
"test:golden": "node scripts/tests/golden-provider-cycle.mjs",
|
|
120
121
|
"test:sharedmodule": "npm run jest:run -- tests/sharedmodule/*.spec.ts",
|
|
121
122
|
"sync:ci-goldens": "node scripts/tools/sync-ci-goldens.mjs",
|
|
123
|
+
"sync:apply-patch-regressions": "node scripts/tools/sync-apply-patch-regressions.mjs",
|
|
122
124
|
"mock:extract": "node scripts/mock-provider/extract.mjs",
|
|
123
125
|
"mock:validate": "node scripts/mock-provider/validate.mjs",
|
|
124
126
|
"mock:regressions": "node scripts/mock-provider/run-regressions.mjs",
|
|
@@ -129,7 +131,7 @@
|
|
|
129
131
|
},
|
|
130
132
|
"dependencies": {
|
|
131
133
|
"@anthropic-ai/sdk": "^0.65.0",
|
|
132
|
-
"@jsonstudio/llms": "^0.6.
|
|
134
|
+
"@jsonstudio/llms": "^0.6.802",
|
|
133
135
|
"@jsonstudio/rcc": "^0.89.555",
|
|
134
136
|
"@lmstudio/sdk": "^1.5.0",
|
|
135
137
|
"@radix-ui/react-switch": "^1.2.6",
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scan apply_patch tool call shapes in:
|
|
5
|
+
* 1) repo samples: samples/ci-goldens/** (including _regressions/apply_patch)
|
|
6
|
+
* 2) user samples: ~/.routecodex/codex-samples/**
|
|
7
|
+
*
|
|
8
|
+
* Goal: identify remaining non-context shape failures (invalid_json/missing_changes/etc.)
|
|
9
|
+
* and optionally capture failures into repo ci-goldens regressions.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/scan-apply-patch-samples.mjs
|
|
13
|
+
* node scripts/scan-apply-patch-samples.mjs --user-all
|
|
14
|
+
* ROUTECODEX_APPLY_PATCH_REGRESSION_TO_REPO=1 node scripts/scan-apply-patch-samples.mjs --user-all
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs/promises';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import crypto from 'node:crypto';
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
25
|
+
|
|
26
|
+
const coreLoaderPath = path.join(repoRoot, 'dist', 'modules', 'llmswitch', 'core-loader.js');
|
|
27
|
+
const coreLoaderUrl = pathToFileURL(coreLoaderPath).href;
|
|
28
|
+
|
|
29
|
+
const HOME = os.homedir();
|
|
30
|
+
const USER_CODEX_ROOT = path.join(HOME, '.routecodex', 'codex-samples');
|
|
31
|
+
const USER_CODEX_ROOT_ALT = path.join(HOME, '.routecodex', 'codex samples');
|
|
32
|
+
const USER_CODEX_PENDING = path.join(USER_CODEX_ROOT, 'openai-chat', '__pending__');
|
|
33
|
+
const REPO_GOLDENS_ROOT = path.join(repoRoot, 'samples', 'ci-goldens');
|
|
34
|
+
const ERROR_SAMPLES_ROOT = path.join(HOME, '.routecodex', 'errorsamples', 'apply_patch');
|
|
35
|
+
|
|
36
|
+
const DEFAULT_CAPTURE_TOTAL_LIMIT = 5000;
|
|
37
|
+
const DEFAULT_CAPTURE_PER_REASON_LIMIT = 250;
|
|
38
|
+
|
|
39
|
+
async function fileExists(p) {
|
|
40
|
+
try {
|
|
41
|
+
await fs.access(p);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function* walkJsonFiles(root) {
|
|
49
|
+
const stack = [root];
|
|
50
|
+
while (stack.length) {
|
|
51
|
+
const current = stack.pop();
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
55
|
+
} catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const full = path.join(current, entry.name);
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
stack.push(full);
|
|
62
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
|
|
63
|
+
yield full;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function extractApplyPatchArgs(doc, { allowRegressionOriginalArgs } = { allowRegressionOriginalArgs: false }) {
|
|
70
|
+
const out = [];
|
|
71
|
+
|
|
72
|
+
function visit(node) {
|
|
73
|
+
if (!node) return;
|
|
74
|
+
if (Array.isArray(node)) {
|
|
75
|
+
for (const item of node) visit(item);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (typeof node !== 'object') return;
|
|
79
|
+
|
|
80
|
+
const any = node;
|
|
81
|
+
|
|
82
|
+
// OpenAI chat tool_calls: { function: { name, arguments } }
|
|
83
|
+
if (any.function && typeof any.function === 'object') {
|
|
84
|
+
const fn = any.function;
|
|
85
|
+
if (fn && fn.name === 'apply_patch') {
|
|
86
|
+
const args = fn.arguments;
|
|
87
|
+
if (typeof args === 'string' && args.trim()) {
|
|
88
|
+
out.push(args);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Responses output: { type:"function_call", name:"apply_patch", arguments:"..." }
|
|
94
|
+
if (any.type === 'function_call' && any.name === 'apply_patch') {
|
|
95
|
+
const args = any.arguments;
|
|
96
|
+
if (typeof args === 'string' && args.trim()) {
|
|
97
|
+
out.push(args);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Regression sample shape: { originalArgs: "...", errorType: "..." }
|
|
102
|
+
if (allowRegressionOriginalArgs) {
|
|
103
|
+
if (
|
|
104
|
+
typeof any.originalArgs === 'string' &&
|
|
105
|
+
any.originalArgs.trim() &&
|
|
106
|
+
typeof any.errorType === 'string' &&
|
|
107
|
+
any.errorType.trim()
|
|
108
|
+
) {
|
|
109
|
+
out.push(any.originalArgs);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const value of Object.values(any)) visit(value);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
visit(doc);
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isContextMismatchReason(reason) {
|
|
121
|
+
// We only capture/repair shape issues; context mismatches are not actionable here.
|
|
122
|
+
// Keep this conservative: if a new reason is introduced, we still capture it unless
|
|
123
|
+
// it clearly indicates a context mismatch.
|
|
124
|
+
if (!reason) return false;
|
|
125
|
+
const r = String(reason);
|
|
126
|
+
return (
|
|
127
|
+
r.includes('context') ||
|
|
128
|
+
r.includes('expected') ||
|
|
129
|
+
r.includes('hunk') ||
|
|
130
|
+
r.includes('no_match') ||
|
|
131
|
+
r.includes('not_found')
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function stableHash(input) {
|
|
136
|
+
return crypto.createHash('sha256').update(input).digest('hex').slice(0, 16);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function captureFailure({
|
|
140
|
+
destRoot,
|
|
141
|
+
label,
|
|
142
|
+
sourceFile,
|
|
143
|
+
reason,
|
|
144
|
+
originalArgs,
|
|
145
|
+
validationResult,
|
|
146
|
+
captureState,
|
|
147
|
+
}) {
|
|
148
|
+
if (isContextMismatchReason(reason)) return;
|
|
149
|
+
if (captureState?.reasonAllowList && !captureState.reasonAllowList.has(reason)) return;
|
|
150
|
+
if (!destRoot) return;
|
|
151
|
+
|
|
152
|
+
const totalLimit = captureState.totalLimit ?? DEFAULT_CAPTURE_TOTAL_LIMIT;
|
|
153
|
+
const perReasonLimit = captureState.perReasonLimit ?? DEFAULT_CAPTURE_PER_REASON_LIMIT;
|
|
154
|
+
if (captureState.totalCaptured >= totalLimit) return;
|
|
155
|
+
|
|
156
|
+
const currentReasonCount = captureState.byReason.get(reason) ?? 0;
|
|
157
|
+
if (currentReasonCount >= perReasonLimit) return;
|
|
158
|
+
|
|
159
|
+
const key = `${label}\n${reason}\n${sourceFile}\n${originalArgs}`;
|
|
160
|
+
const filename = `sample_${stableHash(key)}.json`;
|
|
161
|
+
const folder = path.join(destRoot, reason);
|
|
162
|
+
const outPath = path.join(folder, filename);
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await fs.access(outPath);
|
|
166
|
+
return;
|
|
167
|
+
} catch {
|
|
168
|
+
// continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await fs.mkdir(folder, { recursive: true });
|
|
172
|
+
const payload = {
|
|
173
|
+
tool: 'apply_patch',
|
|
174
|
+
label,
|
|
175
|
+
reason,
|
|
176
|
+
capturedAt: new Date().toISOString(),
|
|
177
|
+
sourceFile,
|
|
178
|
+
argsSha256: crypto.createHash('sha256').update(originalArgs).digest('hex'),
|
|
179
|
+
originalArgs,
|
|
180
|
+
validationResult,
|
|
181
|
+
};
|
|
182
|
+
await fs.writeFile(outPath, JSON.stringify(payload, null, 2), 'utf-8');
|
|
183
|
+
|
|
184
|
+
captureState.totalCaptured += 1;
|
|
185
|
+
captureState.byReason.set(reason, currentReasonCount + 1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function loadValidator() {
|
|
189
|
+
if (!(await fileExists(coreLoaderPath))) {
|
|
190
|
+
throw new Error(`core-loader missing at ${coreLoaderPath} (run npm run build:dev first)`);
|
|
191
|
+
}
|
|
192
|
+
const { importCoreModule } = await import(coreLoaderUrl);
|
|
193
|
+
const { validateToolCall } = await importCoreModule('tools/tool-registry');
|
|
194
|
+
if (typeof validateToolCall !== 'function') {
|
|
195
|
+
throw new Error('validateToolCall not found in llmswitch-core tools/tool-registry');
|
|
196
|
+
}
|
|
197
|
+
return { validateToolCall };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function scanRoot(label, rootDir, validateToolCall, capture) {
|
|
201
|
+
if (!(await fileExists(rootDir))) {
|
|
202
|
+
return { label, rootDir, files: 0, calls: 0, ok: 0, byReason: new Map(), examples: [] };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const byReason = new Map();
|
|
206
|
+
const examples = [];
|
|
207
|
+
let files = 0;
|
|
208
|
+
let calls = 0;
|
|
209
|
+
let ok = 0;
|
|
210
|
+
|
|
211
|
+
for await (const filePath of walkJsonFiles(rootDir)) {
|
|
212
|
+
files += 1;
|
|
213
|
+
let raw;
|
|
214
|
+
try {
|
|
215
|
+
raw = await fs.readFile(filePath, 'utf-8');
|
|
216
|
+
} catch {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
// Fast prefilter: skip JSON that doesn't even mention apply_patch.
|
|
220
|
+
if (!raw.includes('apply_patch')) continue;
|
|
221
|
+
|
|
222
|
+
let doc;
|
|
223
|
+
try {
|
|
224
|
+
doc = JSON.parse(raw);
|
|
225
|
+
} catch {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const isRegressionFile = filePath.includes(`${path.sep}_regressions${path.sep}`);
|
|
230
|
+
const argsList = extractApplyPatchArgs(doc, { allowRegressionOriginalArgs: isRegressionFile });
|
|
231
|
+
if (!argsList.length) continue;
|
|
232
|
+
|
|
233
|
+
const dedup = Array.from(new Set(argsList));
|
|
234
|
+
for (const args of dedup) {
|
|
235
|
+
calls += 1;
|
|
236
|
+
const res = validateToolCall('apply_patch', args);
|
|
237
|
+
if (res && res.ok) {
|
|
238
|
+
ok += 1;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const reason = (res && res.reason) || 'unknown';
|
|
242
|
+
const cur = byReason.get(reason) || { count: 0 };
|
|
243
|
+
cur.count += 1;
|
|
244
|
+
byReason.set(reason, cur);
|
|
245
|
+
if (capture?.enabled) {
|
|
246
|
+
await captureFailure({
|
|
247
|
+
destRoot: capture.destRoot,
|
|
248
|
+
label,
|
|
249
|
+
sourceFile: filePath,
|
|
250
|
+
reason,
|
|
251
|
+
originalArgs: args,
|
|
252
|
+
validationResult: res,
|
|
253
|
+
captureState: capture.state,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (examples.length < 12) {
|
|
257
|
+
examples.push({ file: filePath, reason });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return { label, rootDir, files, calls, ok, byReason, examples };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function printReport(report) {
|
|
266
|
+
const failures = report.calls - report.ok;
|
|
267
|
+
console.log(`\n[scan] ${report.label}`);
|
|
268
|
+
console.log(`root: ${report.rootDir}`);
|
|
269
|
+
console.log(`files: ${report.files} apply_patch_calls: ${report.calls} ok: ${report.ok} fail: ${failures}`);
|
|
270
|
+
const entries = Array.from(report.byReason.entries()).sort((a, b) => b[1].count - a[1].count);
|
|
271
|
+
if (entries.length) {
|
|
272
|
+
console.log('failures_by_reason:');
|
|
273
|
+
for (const [reason, v] of entries.slice(0, 20)) {
|
|
274
|
+
console.log(` - ${reason}: ${v.count}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (report.examples.length) {
|
|
278
|
+
console.log('examples:');
|
|
279
|
+
for (const ex of report.examples) {
|
|
280
|
+
console.log(` - ${path.relative(repoRoot, ex.file)} reason=${ex.reason}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function main() {
|
|
286
|
+
const { validateToolCall } = await loadValidator();
|
|
287
|
+
|
|
288
|
+
const captureEnabled = process.argv.slice(2).includes('--capture') || process.env.ROUTECODEX_SCAN_CAPTURE === '1';
|
|
289
|
+
const captureRepo = process.argv.slice(2).includes('--capture-repo') || process.env.ROUTECODEX_SCAN_CAPTURE_REPO === '1';
|
|
290
|
+
const captureRoot = process.env.ROUTECODEX_ERRORSAMPLES_DIR || ERROR_SAMPLES_ROOT;
|
|
291
|
+
const totalLimit = Number.parseInt(process.env.ROUTECODEX_CAPTURE_TOTAL_LIMIT || '', 10);
|
|
292
|
+
const perReasonLimit = Number.parseInt(process.env.ROUTECODEX_CAPTURE_PER_REASON_LIMIT || '', 10);
|
|
293
|
+
const reasonsArg = process.argv
|
|
294
|
+
.slice(2)
|
|
295
|
+
.find((arg) => arg.startsWith('--capture-reasons='))
|
|
296
|
+
?.split('=')[1];
|
|
297
|
+
const reasonsEnv = process.env.ROUTECODEX_CAPTURE_REASONS;
|
|
298
|
+
const reasonAllowListRaw = (reasonsArg || reasonsEnv || '').trim();
|
|
299
|
+
const reasonAllowList = reasonAllowListRaw
|
|
300
|
+
? new Set(
|
|
301
|
+
reasonAllowListRaw
|
|
302
|
+
.split(',')
|
|
303
|
+
.map((s) => s.trim())
|
|
304
|
+
.filter(Boolean)
|
|
305
|
+
)
|
|
306
|
+
: null;
|
|
307
|
+
const captureState = {
|
|
308
|
+
totalCaptured: 0,
|
|
309
|
+
totalLimit: Number.isFinite(totalLimit) ? totalLimit : DEFAULT_CAPTURE_TOTAL_LIMIT,
|
|
310
|
+
perReasonLimit: Number.isFinite(perReasonLimit) ? perReasonLimit : DEFAULT_CAPTURE_PER_REASON_LIMIT,
|
|
311
|
+
byReason: new Map(),
|
|
312
|
+
reasonAllowList,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const repo = await scanRoot('repo samples/ci-goldens', REPO_GOLDENS_ROOT, validateToolCall, {
|
|
316
|
+
enabled: captureEnabled && captureRepo,
|
|
317
|
+
destRoot: captureRoot,
|
|
318
|
+
state: captureState,
|
|
319
|
+
});
|
|
320
|
+
printReport(repo);
|
|
321
|
+
|
|
322
|
+
const userAll = process.argv.slice(2).includes('--user-all');
|
|
323
|
+
const userRoots = [];
|
|
324
|
+
if (userAll) {
|
|
325
|
+
if (await fileExists(USER_CODEX_ROOT)) userRoots.push({ label: 'user ~/.routecodex/codex-samples (all)', root: USER_CODEX_ROOT });
|
|
326
|
+
if (await fileExists(USER_CODEX_ROOT_ALT))
|
|
327
|
+
userRoots.push({ label: 'user ~/.routecodex/codex samples (all)', root: USER_CODEX_ROOT_ALT });
|
|
328
|
+
} else {
|
|
329
|
+
const pending = (await fileExists(USER_CODEX_PENDING)) ? USER_CODEX_PENDING : null;
|
|
330
|
+
if (pending) userRoots.push({ label: 'user ~/.routecodex/codex-samples/openai-chat/__pending__', root: pending });
|
|
331
|
+
else if (await fileExists(USER_CODEX_ROOT)) userRoots.push({ label: 'user ~/.routecodex/codex-samples', root: USER_CODEX_ROOT });
|
|
332
|
+
else if (await fileExists(USER_CODEX_ROOT_ALT)) userRoots.push({ label: 'user ~/.routecodex/codex samples', root: USER_CODEX_ROOT_ALT });
|
|
333
|
+
else userRoots.push({ label: 'user ~/.routecodex/codex-samples', root: USER_CODEX_ROOT });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const userReports = [];
|
|
337
|
+
for (const entry of userRoots) {
|
|
338
|
+
const report = await scanRoot(entry.label, entry.root, validateToolCall, {
|
|
339
|
+
enabled: captureEnabled,
|
|
340
|
+
destRoot: captureRoot,
|
|
341
|
+
state: captureState,
|
|
342
|
+
});
|
|
343
|
+
userReports.push(report);
|
|
344
|
+
printReport(report);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const totalCalls = repo.calls + userReports.reduce((sum, r) => sum + r.calls, 0);
|
|
348
|
+
const totalOk = repo.ok + userReports.reduce((sum, r) => sum + r.ok, 0);
|
|
349
|
+
console.log(`\n[scan] TOTAL apply_patch_calls=${totalCalls} ok=${totalOk} fail=${totalCalls - totalOk}`);
|
|
350
|
+
if (captureEnabled) {
|
|
351
|
+
console.log(
|
|
352
|
+
`[scan] captured_failures=${captureState.totalCaptured} dest=${captureRoot} (per_reason<=${captureState.perReasonLimit}, total<=${captureState.totalLimit})`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
process.exitCode = totalCalls - totalOk > 0 ? 1 : 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
main().catch((error) => {
|
|
360
|
+
console.error('[scan-apply-patch-samples] failed:', error?.stack || error?.message || String(error ?? 'unknown'));
|
|
361
|
+
process.exit(2);
|
|
362
|
+
});
|