@jsonstudio/rcc 0.89.942 → 0.89.1083
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 +1 -42
- package/dist/build-info.js +2 -2
- package/dist/build-info.js.map +1 -1
- package/dist/cli.js +106 -10
- package/dist/cli.js.map +1 -1
- package/dist/commands/quota-daemon.d.ts +2 -0
- package/dist/commands/quota-daemon.js +89 -0
- package/dist/commands/quota-daemon.js.map +1 -0
- package/dist/docs/daemon-admin-ui.html +958 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/manager/modules/quota/index.d.ts +34 -0
- package/dist/manager/modules/quota/index.js +291 -0
- package/dist/manager/modules/quota/index.js.map +1 -1
- package/dist/manager/modules/token/index.js +13 -2
- package/dist/manager/modules/token/index.js.map +1 -1
- package/dist/manager/quota/provider-quota-center.d.ts +48 -0
- package/dist/manager/quota/provider-quota-center.js +239 -0
- package/dist/manager/quota/provider-quota-center.js.map +1 -0
- package/dist/manager/quota/provider-quota-store.d.ts +17 -0
- package/dist/manager/quota/provider-quota-store.js +88 -0
- package/dist/manager/quota/provider-quota-store.js.map +1 -0
- package/dist/providers/auth/token-scanner/index.js +11 -3
- package/dist/providers/auth/token-scanner/index.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.js +24 -7
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.js +11 -3
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/responses-provider.js +9 -3
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/utils/http-client.d.ts +1 -0
- package/dist/providers/core/utils/http-client.js +139 -4
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.d.ts +12 -0
- package/dist/providers/core/utils/snapshot-writer.js +99 -18
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.d.ts +3 -0
- package/dist/providers/mock/mock-provider-runtime.js +176 -4
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/server/handlers/chat-handler.js +13 -1
- package/dist/server/handlers/chat-handler.js.map +1 -1
- package/dist/server/handlers/handler-utils.js +5 -0
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/messages-handler.js +13 -1
- package/dist/server/handlers/messages-handler.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +73 -1
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +174 -2
- 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 +519 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
- package/dist/server/runtime/http-server/executor-response.js +6 -0
- package/dist/server/runtime/http-server/executor-response.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +5 -0
- package/dist/server/runtime/http-server/index.js +205 -4
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/middleware.d.ts +2 -0
- package/dist/server/runtime/http-server/middleware.js +63 -0
- package/dist/server/runtime/http-server/middleware.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.d.ts +2 -0
- package/dist/server/runtime/http-server/request-executor.js +57 -10
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +38 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/stats-manager.d.ts +55 -0
- package/dist/server/runtime/http-server/stats-manager.js +462 -4
- package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +1 -0
- package/dist/token-daemon/token-daemon.js +70 -25
- package/dist/token-daemon/token-daemon.js.map +1 -1
- package/dist/token-daemon/token-utils.d.ts +1 -0
- package/dist/token-daemon/token-utils.js +9 -1
- package/dist/token-daemon/token-utils.js.map +1 -1
- package/dist/tools/semantic-replay.js +29 -0
- package/dist/tools/semantic-replay.js.map +1 -1
- package/dist/utils/snapshot-writer.d.ts +2 -0
- package/dist/utils/snapshot-writer.js +47 -4
- package/dist/utils/snapshot-writer.js.map +1 -1
- package/package.json +2 -3
- package/scripts/analyze-codex-error-failures.mjs +24 -14
- package/scripts/classify-codex-samples.mjs +0 -35
- package/scripts/copy-modules-config.mjs +17 -1
- package/scripts/generate-snapshot-data.mjs +41 -11
- package/scripts/mock-provider/extract.mjs +239 -21
- package/scripts/mock-provider/run-regressions.mjs +79 -16
- package/scripts/quota-dryrun.mjs +124 -0
- package/scripts/tests/apply-patch-loop.mjs +5 -1
- package/scripts/tests/exec-command-loop.mjs +16 -19
- package/scripts/verify-apply-patch.mjs +335 -5
- package/scripts/verify-e2e-toolcall.mjs +49 -10
- package/scripts/toon-suite.mjs +0 -141
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
|
+
import chalk from 'chalk';
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const repoRoot = path.resolve(__dirname, '..');
|
|
@@ -14,6 +15,8 @@ const coreLoaderPath = path.join(repoRoot, 'dist', 'modules', 'llmswitch', 'core
|
|
|
14
15
|
const coreLoaderUrl = pathToFileURL(coreLoaderPath).href;
|
|
15
16
|
const { importCoreModule } = await import(coreLoaderUrl);
|
|
16
17
|
|
|
18
|
+
const chalkError = typeof chalk?.redBright === 'function' ? chalk.redBright : (value) => value;
|
|
19
|
+
|
|
17
20
|
async function loadCoreModule(subpath) {
|
|
18
21
|
return importCoreModule(subpath);
|
|
19
22
|
}
|
|
@@ -99,6 +102,47 @@ async function runApplyPatchTextCase(label, payloadText) {
|
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
async function runApplyPatchArgsCase(label, argsString) {
|
|
106
|
+
const { validateToolCall } = await loadCoreModule('tools/tool-registry');
|
|
107
|
+
const validation = validateToolCall('apply_patch', argsString);
|
|
108
|
+
if (!validation?.ok) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`[verify-apply-patch] ${label}: validateToolCall failed with reason=${validation?.reason}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
let parsed;
|
|
114
|
+
try {
|
|
115
|
+
parsed = JSON.parse(validation.normalizedArgs || '{}');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`[verify-apply-patch] ${label}: normalized arguments not valid JSON: ${(error && error.message) || String(error)}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const normalizedPatchText =
|
|
122
|
+
typeof parsed?.patch === 'string'
|
|
123
|
+
? parsed.patch
|
|
124
|
+
: typeof parsed?.input === 'string'
|
|
125
|
+
? parsed.input
|
|
126
|
+
: '';
|
|
127
|
+
if (!normalizedPatchText) {
|
|
128
|
+
throw new Error(`[verify-apply-patch] ${label}: missing normalized patch text`);
|
|
129
|
+
}
|
|
130
|
+
if (!normalizedPatchText.startsWith('*** Begin Patch')) {
|
|
131
|
+
throw new Error(`[verify-apply-patch] ${label}: patch does not start with *** Begin Patch`);
|
|
132
|
+
}
|
|
133
|
+
if (!normalizedPatchText.includes('\n*** End Patch')) {
|
|
134
|
+
throw new Error(`[verify-apply-patch] ${label}: patch missing *** End Patch`);
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
normalizedPatchText.includes('*** End Patch","input":"*** Begin Patch') ||
|
|
138
|
+
normalizedPatchText.includes('*** End Patch","patch":"*** Begin Patch') ||
|
|
139
|
+
normalizedPatchText.includes('*** End Patch\\",\\"input\\":\\"*** Begin Patch') ||
|
|
140
|
+
normalizedPatchText.includes('*** End Patch\\",\\"patch\\":\\"*** Begin Patch')
|
|
141
|
+
) {
|
|
142
|
+
throw new Error(`[verify-apply-patch] ${label}: patch still contains stitched JSON keys`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
102
146
|
async function main() {
|
|
103
147
|
if (String(process.env.ROUTECODEX_VERIFY_SKIP || '').trim() === '1') {
|
|
104
148
|
console.log('[verify-apply-patch] 跳过(ROUTECODEX_VERIFY_SKIP=1)');
|
|
@@ -106,6 +150,294 @@ async function main() {
|
|
|
106
150
|
}
|
|
107
151
|
|
|
108
152
|
try {
|
|
153
|
+
const { validateToolCall } = await loadCoreModule('tools/tool-registry');
|
|
154
|
+
const escapeNewlines = (value) => String(value || '').replace(/\n/g, '\\n');
|
|
155
|
+
|
|
156
|
+
// Regression: tolerate newline-escaped patch text (e.g. "*** Begin Patch\\n...") and
|
|
157
|
+
// normalize into a real multi-line unified diff string.
|
|
158
|
+
{
|
|
159
|
+
const escapedPatch = '*** Begin Patch\\n*** End Patch';
|
|
160
|
+
const validation = validateToolCall('apply_patch', escapedPatch);
|
|
161
|
+
if (!validation?.ok) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`[verify-apply-patch] escaped_patch: validateToolCall failed with reason=${validation?.reason}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
let parsed;
|
|
167
|
+
try {
|
|
168
|
+
parsed = JSON.parse(validation.normalizedArgs);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`[verify-apply-patch] escaped_patch: normalized arguments not valid JSON: ${(error && error.message) || String(error)}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const patchText =
|
|
175
|
+
typeof parsed?.patch === 'string'
|
|
176
|
+
? parsed.patch
|
|
177
|
+
: typeof parsed?.input === 'string'
|
|
178
|
+
? parsed.input
|
|
179
|
+
: '';
|
|
180
|
+
if (!patchText || typeof patchText !== 'string') {
|
|
181
|
+
throw new Error('[verify-apply-patch] escaped_patch: missing patch text in normalized args');
|
|
182
|
+
}
|
|
183
|
+
if (patchText.includes('\\n') && !patchText.includes('\n')) {
|
|
184
|
+
throw new Error('[verify-apply-patch] escaped_patch: patch still contains literal \\\\n without real newlines');
|
|
185
|
+
}
|
|
186
|
+
if (patchText.split('\n')[0] !== '*** Begin Patch') {
|
|
187
|
+
throw new Error('[verify-apply-patch] escaped_patch: patch first line is not *** Begin Patch');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Regression: validateToolCall should be idempotent for already-normalized JSON arguments.
|
|
192
|
+
// Previously we could mis-detect the whole JSON as patch text, producing a merged line like:
|
|
193
|
+
// "*** End Patch\",\"input\":\"*** Begin Patch"
|
|
194
|
+
{
|
|
195
|
+
const patchText = '*** Begin Patch\n*** End Patch';
|
|
196
|
+
const alreadyNormalized = JSON.stringify({ patch: patchText, input: patchText });
|
|
197
|
+
const validation = validateToolCall('apply_patch', alreadyNormalized);
|
|
198
|
+
if (!validation?.ok) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`[verify-apply-patch] already_normalized: validateToolCall failed with reason=${validation?.reason}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const parsed = JSON.parse(validation.normalizedArgs);
|
|
204
|
+
const normalizedPatchText =
|
|
205
|
+
typeof parsed?.patch === 'string'
|
|
206
|
+
? parsed.patch
|
|
207
|
+
: typeof parsed?.input === 'string'
|
|
208
|
+
? parsed.input
|
|
209
|
+
: '';
|
|
210
|
+
if (!normalizedPatchText) {
|
|
211
|
+
throw new Error('[verify-apply-patch] already_normalized: missing patch text in normalized args');
|
|
212
|
+
}
|
|
213
|
+
if (normalizedPatchText.includes('","input":"*** Begin Patch') || normalizedPatchText.includes('*** End Patch","input":"')) {
|
|
214
|
+
throw new Error('[verify-apply-patch] already_normalized: patch text incorrectly contains serialized JSON keys');
|
|
215
|
+
}
|
|
216
|
+
if (normalizedPatchText.split('\n')[0] !== '*** Begin Patch') {
|
|
217
|
+
throw new Error('[verify-apply-patch] already_normalized: patch first line is not *** Begin Patch');
|
|
218
|
+
}
|
|
219
|
+
if (!normalizedPatchText.includes('*** End Patch')) {
|
|
220
|
+
throw new Error('[verify-apply-patch] already_normalized: patch missing *** End Patch');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Regression: accept a typical sequence of apply_patch calls (add/update/append/escape/multi-hunk/delete).
|
|
225
|
+
// Note: we only validate tool governance & patch text normalization, not filesystem application.
|
|
226
|
+
{
|
|
227
|
+
const addBasic = [
|
|
228
|
+
'*** Begin Patch',
|
|
229
|
+
'*** Add File: .apply_patch_basic_add.txt',
|
|
230
|
+
'+apply_patch basic add ok',
|
|
231
|
+
'+line2',
|
|
232
|
+
'*** End Patch'
|
|
233
|
+
].join('\n');
|
|
234
|
+
const updateBasic = [
|
|
235
|
+
'*** Begin Patch',
|
|
236
|
+
'*** Update File: .apply_patch_basic_add.txt',
|
|
237
|
+
'@@',
|
|
238
|
+
'-apply_patch basic add ok',
|
|
239
|
+
'+apply_patch basic add+update ok',
|
|
240
|
+
' line2',
|
|
241
|
+
'*** End Patch'
|
|
242
|
+
].join('\n');
|
|
243
|
+
const appendBasic = [
|
|
244
|
+
'*** Begin Patch',
|
|
245
|
+
'*** Update File: .apply_patch_basic_add.txt',
|
|
246
|
+
'@@',
|
|
247
|
+
' apply_patch basic add+update ok',
|
|
248
|
+
' line2',
|
|
249
|
+
'+line3 (append)',
|
|
250
|
+
'*** End Patch'
|
|
251
|
+
].join('\n');
|
|
252
|
+
const addEscapeChars = [
|
|
253
|
+
'*** Begin Patch',
|
|
254
|
+
'*** Add File: .apply_patch_escape_chars.txt',
|
|
255
|
+
'+quotes: "double" and \'single\'',
|
|
256
|
+
'+backslash: \\',
|
|
257
|
+
'+json: {"a":1,"b":"x"}',
|
|
258
|
+
'+template: ${notInterpolated}',
|
|
259
|
+
'*** End Patch'
|
|
260
|
+
].join('\n');
|
|
261
|
+
const addMulti = [
|
|
262
|
+
'*** Begin Patch',
|
|
263
|
+
'*** Add File: .apply_patch_multi_hunk.txt',
|
|
264
|
+
'+Header',
|
|
265
|
+
'+Section A',
|
|
266
|
+
'+Section B',
|
|
267
|
+
'+Footer',
|
|
268
|
+
'*** End Patch'
|
|
269
|
+
].join('\n');
|
|
270
|
+
const updateMulti = [
|
|
271
|
+
'*** Begin Patch',
|
|
272
|
+
'*** Update File: .apply_patch_multi_hunk.txt',
|
|
273
|
+
'@@',
|
|
274
|
+
' Header',
|
|
275
|
+
'-Section A',
|
|
276
|
+
'+Section A (updated)',
|
|
277
|
+
' Section B',
|
|
278
|
+
' Footer',
|
|
279
|
+
'@@',
|
|
280
|
+
' Header',
|
|
281
|
+
' Section A (updated)',
|
|
282
|
+
'-Section B',
|
|
283
|
+
'+Section B (updated)',
|
|
284
|
+
' Footer',
|
|
285
|
+
'*** End Patch'
|
|
286
|
+
].join('\n');
|
|
287
|
+
const deleteBasic = [
|
|
288
|
+
'*** Begin Patch',
|
|
289
|
+
'*** Delete File: .apply_patch_basic_add.txt',
|
|
290
|
+
'*** End Patch'
|
|
291
|
+
].join('\n');
|
|
292
|
+
const deleteEscapeChars = [
|
|
293
|
+
'*** Begin Patch',
|
|
294
|
+
'*** Delete File: .apply_patch_escape_chars.txt',
|
|
295
|
+
'*** End Patch'
|
|
296
|
+
].join('\n');
|
|
297
|
+
const deleteMulti = [
|
|
298
|
+
'*** Begin Patch',
|
|
299
|
+
'*** Delete File: .apply_patch_multi_hunk.txt',
|
|
300
|
+
'*** End Patch'
|
|
301
|
+
].join('\n');
|
|
302
|
+
|
|
303
|
+
const cases = [
|
|
304
|
+
['seq_basic_add_text', addBasic],
|
|
305
|
+
['seq_basic_update_text', updateBasic],
|
|
306
|
+
['seq_basic_append_text', appendBasic],
|
|
307
|
+
['seq_escape_chars_text', addEscapeChars],
|
|
308
|
+
['seq_multi_add_text', addMulti],
|
|
309
|
+
['seq_multi_update_text', updateMulti],
|
|
310
|
+
['seq_basic_delete_text', deleteBasic],
|
|
311
|
+
['seq_escape_chars_delete_text', deleteEscapeChars],
|
|
312
|
+
['seq_multi_delete_text', deleteMulti]
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
for (const [label, patchText] of cases) {
|
|
316
|
+
await runApplyPatchArgsCase(label, patchText);
|
|
317
|
+
await runApplyPatchArgsCase(
|
|
318
|
+
`${label}_json`,
|
|
319
|
+
JSON.stringify({ patch: patchText, input: patchText })
|
|
320
|
+
);
|
|
321
|
+
await runApplyPatchArgsCase(
|
|
322
|
+
`${label}_json_escaped_newlines`,
|
|
323
|
+
JSON.stringify({ patch: escapeNewlines(patchText), input: escapeNewlines(patchText) })
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Regression: tolerate <arg_key>/<arg_value> artifacts stitched into patch strings.
|
|
329
|
+
// Some upstream providers may leak XML-like markup into JSON string fields.
|
|
330
|
+
{
|
|
331
|
+
const patchText = [
|
|
332
|
+
'*** Begin Patch',
|
|
333
|
+
'*** Delete File: .apply_patch_escape_test.txt',
|
|
334
|
+
'*** End Patch'
|
|
335
|
+
].join('\n');
|
|
336
|
+
const injected = `${patchText}</arg_key><arg_value>input</arg_key><arg_value>${patchText}`;
|
|
337
|
+
const argsString = JSON.stringify({ patch: injected });
|
|
338
|
+
const validation = validateToolCall('apply_patch', argsString);
|
|
339
|
+
if (!validation?.ok) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`[verify-apply-patch] arg_key_artifacts: validateToolCall failed with reason=${validation?.reason}`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
const parsed = JSON.parse(validation.normalizedArgs);
|
|
345
|
+
const normalizedPatchText =
|
|
346
|
+
typeof parsed?.patch === 'string'
|
|
347
|
+
? parsed.patch
|
|
348
|
+
: typeof parsed?.input === 'string'
|
|
349
|
+
? parsed.input
|
|
350
|
+
: '';
|
|
351
|
+
if (normalizedPatchText.includes('</arg_key><arg_value>')) {
|
|
352
|
+
throw new Error('[verify-apply-patch] arg_key_artifacts: patch still contains arg_key artifacts');
|
|
353
|
+
}
|
|
354
|
+
if (parsed?.patch !== parsed?.input) {
|
|
355
|
+
throw new Error('[verify-apply-patch] arg_key_artifacts: patch/input mismatch after normalization');
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Regression: invalid JSON containers should be classified as invalid_json (not missing_changes).
|
|
360
|
+
{
|
|
361
|
+
const invalidJson = '{"file":"a.ts","changes":[{"kind":"create_file","lines":["x"],"file</arg_key><arg_value>a.ts"}]}';
|
|
362
|
+
const validation = validateToolCall('apply_patch', invalidJson);
|
|
363
|
+
if (validation?.ok || validation?.reason !== 'invalid_json') {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`[verify-apply-patch] invalid_json: expected invalid_json reason, got ok=${validation?.ok} reason=${validation?.reason}`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Regression: patches can contain ``` blocks inside file content; do not treat them as outer fences.
|
|
371
|
+
{
|
|
372
|
+
const patchText = [
|
|
373
|
+
'*** Begin Patch',
|
|
374
|
+
'*** Add File: src/demo-codefence.md',
|
|
375
|
+
'+```json',
|
|
376
|
+
'+{\"ok\":true}',
|
|
377
|
+
'+```',
|
|
378
|
+
'*** End Patch'
|
|
379
|
+
].join('\n');
|
|
380
|
+
const validation = validateToolCall('apply_patch', patchText);
|
|
381
|
+
if (!validation?.ok) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`[verify-apply-patch] inner_codefence: validateToolCall failed with reason=${validation?.reason}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const parsed = JSON.parse(validation.normalizedArgs);
|
|
387
|
+
const normalizedPatchText =
|
|
388
|
+
typeof parsed?.patch === 'string'
|
|
389
|
+
? parsed.patch
|
|
390
|
+
: typeof parsed?.input === 'string'
|
|
391
|
+
? parsed.input
|
|
392
|
+
: '';
|
|
393
|
+
if (!normalizedPatchText.startsWith('*** Begin Patch')) {
|
|
394
|
+
throw new Error('[verify-apply-patch] inner_codefence: patch lost *** Begin Patch header');
|
|
395
|
+
}
|
|
396
|
+
if (!normalizedPatchText.includes('*** Add File: src/demo-codefence.md')) {
|
|
397
|
+
throw new Error('[verify-apply-patch] inner_codefence: missing Add File header');
|
|
398
|
+
}
|
|
399
|
+
if (!normalizedPatchText.includes('+```json') || !normalizedPatchText.includes('+```')) {
|
|
400
|
+
throw new Error('[verify-apply-patch] inner_codefence: missing fenced lines inside patch');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Regression: tolerate newline-escaped snippets inside structured payload fields.
|
|
405
|
+
// Some models/clients double-escape multi-line anchors/targets (e.g. "\\n ").
|
|
406
|
+
{
|
|
407
|
+
const structuredArgs = JSON.stringify({
|
|
408
|
+
file: 'src/demo-escaped-snippet.ts',
|
|
409
|
+
changes: [
|
|
410
|
+
{
|
|
411
|
+
kind: 'replace',
|
|
412
|
+
target: 'const alpha = 1;\\n const beta = 2;',
|
|
413
|
+
lines: ['const alpha = 1;', ' const beta = 3;']
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
});
|
|
417
|
+
const validation = validateToolCall('apply_patch', structuredArgs);
|
|
418
|
+
if (!validation?.ok) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
`[verify-apply-patch] escaped_structured_snippet: validateToolCall failed with reason=${validation?.reason}`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
const parsed = JSON.parse(validation.normalizedArgs);
|
|
424
|
+
const patchText =
|
|
425
|
+
typeof parsed?.patch === 'string'
|
|
426
|
+
? parsed.patch
|
|
427
|
+
: typeof parsed?.input === 'string'
|
|
428
|
+
? parsed.input
|
|
429
|
+
: '';
|
|
430
|
+
if (!patchText) {
|
|
431
|
+
throw new Error('[verify-apply-patch] escaped_structured_snippet: missing patch text in normalized args');
|
|
432
|
+
}
|
|
433
|
+
if (patchText.includes('const alpha = 1;\\n') || patchText.includes('\\n const beta = 2;')) {
|
|
434
|
+
throw new Error('[verify-apply-patch] escaped_structured_snippet: patch still contains literal \\\\n in target');
|
|
435
|
+
}
|
|
436
|
+
if (!patchText.includes('-const alpha = 1;') || !patchText.includes('- const beta = 2;')) {
|
|
437
|
+
throw new Error('[verify-apply-patch] escaped_structured_snippet: expected multi-line "-" target not found');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
109
441
|
const plainJson = JSON.stringify({
|
|
110
442
|
file: 'src/demo.ts',
|
|
111
443
|
changes: [
|
|
@@ -136,11 +468,9 @@ async function main() {
|
|
|
136
468
|
|
|
137
469
|
console.log('✅ verify-apply-patch: text→tool_calls pipeline passed');
|
|
138
470
|
} catch (error) {
|
|
139
|
-
|
|
140
|
-
console.error(
|
|
141
|
-
|
|
142
|
-
error instanceof Error ? error.message : String(error ?? 'Unknown error')
|
|
143
|
-
);
|
|
471
|
+
const msg = error instanceof Error ? (error.stack || error.message) : String(error ?? 'Unknown error');
|
|
472
|
+
console.error(chalkError(msg));
|
|
473
|
+
console.error(chalkError(`❌ verify-apply-patch 失败: ${error instanceof Error ? error.message : String(error ?? 'Unknown error')}`));
|
|
144
474
|
process.exit(1);
|
|
145
475
|
}
|
|
146
476
|
}
|
|
@@ -29,6 +29,24 @@ const AGENTS_INSTRUCTIONS = (() => {
|
|
|
29
29
|
}
|
|
30
30
|
})();
|
|
31
31
|
|
|
32
|
+
function readServerApiKeyFromConfig(configPath) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
35
|
+
const json = raw && raw.trim() ? JSON.parse(raw) : {};
|
|
36
|
+
const apikey = json?.httpserver?.apikey;
|
|
37
|
+
return typeof apikey === 'string' && apikey.trim() ? apikey.trim() : '';
|
|
38
|
+
} catch {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildAuthHeaders(serverApiKey) {
|
|
44
|
+
if (!serverApiKey) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
return { 'x-api-key': serverApiKey };
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
async function main() {
|
|
33
51
|
if (!VERIFY_CONFIG) {
|
|
34
52
|
console.error('❌ ROUTECODEX_VERIFY_CONFIG 未设置,无法运行端到端校验');
|
|
@@ -36,6 +54,8 @@ async function main() {
|
|
|
36
54
|
}
|
|
37
55
|
|
|
38
56
|
console.log(`[verify:e2e-toolcall] 使用配置: ${VERIFY_CONFIG}`);
|
|
57
|
+
const serverApiKey = readServerApiKeyFromConfig(VERIFY_CONFIG);
|
|
58
|
+
const authHeaders = buildAuthHeaders(serverApiKey);
|
|
39
59
|
const serverEnv = {
|
|
40
60
|
...process.env,
|
|
41
61
|
ROUTECODEX_CONFIG_PATH: VERIFY_CONFIG,
|
|
@@ -61,11 +81,12 @@ async function main() {
|
|
|
61
81
|
try {
|
|
62
82
|
await waitForServer();
|
|
63
83
|
await waitForRouterWarmup();
|
|
64
|
-
await
|
|
84
|
+
await runModelsSmokeCheck(authHeaders);
|
|
85
|
+
await runToolcallVerification(authHeaders);
|
|
65
86
|
console.log('✅ 端到端工具调用校验通过');
|
|
66
87
|
|
|
67
|
-
await runDaemonAdminSmokeCheck();
|
|
68
|
-
await runConfigV2ProvidersSmokeCheck();
|
|
88
|
+
await runDaemonAdminSmokeCheck(authHeaders);
|
|
89
|
+
await runConfigV2ProvidersSmokeCheck(authHeaders);
|
|
69
90
|
|
|
70
91
|
// 附加:Gemini CLI 配置健康性快速检查(仅尝试初始化,不做请求)
|
|
71
92
|
await runGeminiCliStartupCheck();
|
|
@@ -74,6 +95,23 @@ async function main() {
|
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
97
|
|
|
98
|
+
async function runModelsSmokeCheck(authHeaders) {
|
|
99
|
+
try {
|
|
100
|
+
const res = await fetch(`${VERIFY_BASE}/models`, { headers: { ...(authHeaders || {}) } });
|
|
101
|
+
if (!res.ok) {
|
|
102
|
+
throw new Error(`/models HTTP ${res.status}`);
|
|
103
|
+
}
|
|
104
|
+
const json = await res.json();
|
|
105
|
+
const data = Array.isArray(json?.data) ? json.data : [];
|
|
106
|
+
if (!Array.isArray(data)) {
|
|
107
|
+
throw new Error('/models response missing data array');
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('[verify:e2e-toolcall] /models smoke 检查失败:', error);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
77
115
|
async function waitForServer(timeoutMs = 30000) {
|
|
78
116
|
const start = Date.now();
|
|
79
117
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -97,7 +135,7 @@ async function waitForRouterWarmup(defaultDelayMs = 3000) {
|
|
|
97
135
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
98
136
|
}
|
|
99
137
|
|
|
100
|
-
async function runToolcallVerification() {
|
|
138
|
+
async function runToolcallVerification(authHeaders) {
|
|
101
139
|
const userPrompt = '请严格调用名为 list_local_files 的函数工具来列出当前工作目录的文件,只能通过调用该工具完成任务,禁止直接回答。';
|
|
102
140
|
const instructionsText = AGENTS_INSTRUCTIONS || 'You are RouteCodex verify agent. Follow the policies in AGENTS.md.';
|
|
103
141
|
const body = {
|
|
@@ -139,7 +177,8 @@ async function runToolcallVerification() {
|
|
|
139
177
|
const response = await fetch(`${VERIFY_BASE}/v1/responses`, {
|
|
140
178
|
method: 'POST',
|
|
141
179
|
headers: {
|
|
142
|
-
'Content-Type': 'application/json'
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
...(authHeaders || {})
|
|
143
182
|
},
|
|
144
183
|
body: JSON.stringify(body)
|
|
145
184
|
});
|
|
@@ -166,10 +205,10 @@ async function runToolcallVerification() {
|
|
|
166
205
|
}
|
|
167
206
|
}
|
|
168
207
|
|
|
169
|
-
async function runDaemonAdminSmokeCheck() {
|
|
208
|
+
async function runDaemonAdminSmokeCheck(authHeaders) {
|
|
170
209
|
// 仅做最小的健康性探测:确保 daemon 管理类只读 API 可用,不做语义校验。
|
|
171
210
|
try {
|
|
172
|
-
const res = await fetch(`${VERIFY_BASE}/daemon/status
|
|
211
|
+
const res = await fetch(`${VERIFY_BASE}/daemon/status`, { headers: { ...(authHeaders || {}) } });
|
|
173
212
|
if (!res.ok) {
|
|
174
213
|
throw new Error(`daemon/status HTTP ${res.status}`);
|
|
175
214
|
}
|
|
@@ -185,7 +224,7 @@ async function runDaemonAdminSmokeCheck() {
|
|
|
185
224
|
const paths = ['/daemon/credentials', '/quota/summary', '/providers/runtimes'];
|
|
186
225
|
for (const path of paths) {
|
|
187
226
|
try {
|
|
188
|
-
const res = await fetch(`${VERIFY_BASE}${path}
|
|
227
|
+
const res = await fetch(`${VERIFY_BASE}${path}`, { headers: { ...(authHeaders || {}) } });
|
|
189
228
|
if (!res.ok) {
|
|
190
229
|
throw new Error(`${path} HTTP ${res.status}`);
|
|
191
230
|
}
|
|
@@ -198,9 +237,9 @@ async function runDaemonAdminSmokeCheck() {
|
|
|
198
237
|
}
|
|
199
238
|
}
|
|
200
239
|
|
|
201
|
-
async function runConfigV2ProvidersSmokeCheck() {
|
|
240
|
+
async function runConfigV2ProvidersSmokeCheck(authHeaders) {
|
|
202
241
|
try {
|
|
203
|
-
const res = await fetch(`${VERIFY_BASE}/config/providers/v2
|
|
242
|
+
const res = await fetch(`${VERIFY_BASE}/config/providers/v2`, { headers: { ...(authHeaders || {}) } });
|
|
204
243
|
if (!res.ok) {
|
|
205
244
|
throw new Error(`/config/providers/v2 HTTP ${res.status}`);
|
|
206
245
|
}
|
package/scripts/toon-suite.mjs
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// TOON tool-call probe: sends Chat requests that require arguments.toon, decodes via @toon-format/cli, and summarizes results.
|
|
3
|
-
|
|
4
|
-
import fetch from 'node-fetch';
|
|
5
|
-
import { spawnSync } from 'node:child_process';
|
|
6
|
-
|
|
7
|
-
function arg(k, d){ const i=process.argv.indexOf(k); return (i>0 && process.argv[i+1]) ? process.argv[i+1] : d; }
|
|
8
|
-
function flag(k){ return process.argv.includes(k); }
|
|
9
|
-
|
|
10
|
-
const server = arg('--server', 'http://127.0.0.1:5520');
|
|
11
|
-
const endpoint = arg('--endpoint', '/v1/chat/completions');
|
|
12
|
-
const url = `${server.replace(/\/$/,'')}${endpoint}`;
|
|
13
|
-
const model = arg('--model', 'glm.glm-4.6');
|
|
14
|
-
const doExec = flag('--exec'); // optional local execution (unsafe; whitelist only)
|
|
15
|
-
|
|
16
|
-
const systemMsg = (
|
|
17
|
-
'当你使用工具 (tool_calls) 时,将 function.arguments 设为 JSON 字符串,且只包含字段 toon(字符串,多行 TOON)。' +
|
|
18
|
-
'不要输出其它字段、不要解释。' +
|
|
19
|
-
'示例:{"toon": "command: bash -lc \'echo ok\'\\nworkdir: .\\n"}。' +
|
|
20
|
-
'务必返回 tool_calls,并仅填充 arguments.toon。'
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const tools = [
|
|
24
|
-
{
|
|
25
|
-
type: 'function',
|
|
26
|
-
function: {
|
|
27
|
-
name: 'shell_toon',
|
|
28
|
-
description: 'TOON 参数承载:将所有参数用 TOON 字符串放入 arguments.toon',
|
|
29
|
-
parameters: {
|
|
30
|
-
type: 'object',
|
|
31
|
-
properties: { toon: { type: 'string', description: 'TOON-encoded arguments' } },
|
|
32
|
-
required: ['toon'],
|
|
33
|
-
additionalProperties: false
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
const cases = [
|
|
40
|
-
{
|
|
41
|
-
title: 'find_parens_exec',
|
|
42
|
-
user: "请使用 shell_toon:bash -lc 执行:find . -type f \\( -name '*.md' -o -name '*.txt' \\) -exec basename {} \\; | sort -u | head -n 5。只返回 tool_calls。"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
title: 'awk_regex',
|
|
46
|
-
user: "请用 shell_toon:bash -lc 执行:printf 'alpha,error\\nbeta,ok\\n' | awk -F, '{ if (\\$2 ~ /error/) print \\$1 }'。只返回 tool_calls。"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
title: 'sed_replace',
|
|
50
|
-
user: "请用 shell_toon:bash -lc 执行:printf 'a=1\\nb=2\\n' | sed -E 's/([ab])=(\\\\d+)/\\\\1: \\\\2/g'。只返回 tool_calls。"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
title: 'python_heredoc',
|
|
54
|
-
user: `请用 shell_toon:构造“单行” bash -lc 命令来执行多行 Python。禁止在命令中出现真实换行符;改用 ANSI-C 风格 $'...' 在“一行内”嵌入换行(\\n)。示例(仅示意):bash -lc $'python3 - <<\'PY\'\\nprint(1)\\nPY'。实际请输出:用 $'...' 方式在一行内执行 python3 - <<'PY' 的多行脚本:\nimport json\nprint(json.dumps({"ok": true, "sum": 1+2+3}))\n并保持整条命令为一行。只返回 tool_calls。`
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
title: 'xargs_space',
|
|
58
|
-
user: "请用 shell_toon:bash -lc 执行:printf 'A B.md\\nC D.md\\n' | xargs -I{} bash -lc 'echo \"{}\" | tr \" \" _'。只返回 tool_calls。"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
title: 'grep_sort_uniq',
|
|
62
|
-
user: "请用 shell_toon:bash -lc 执行:printf 'a\\nb\\na\\nc\\n' | grep -E '[a-c]' | sort | uniq -c | sort -nr。只返回 tool_calls。"
|
|
63
|
-
}
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
function decodeToon(toonStr){
|
|
67
|
-
const res = spawnSync('npx', ['-y','@toon-format/cli','--decode'], { input: toonStr, encoding: 'utf-8' });
|
|
68
|
-
if ((res.status ?? 0) !== 0) return { ok: false, error: res.stderr || 'decode failed' };
|
|
69
|
-
try { const obj = JSON.parse(res.stdout || '{}'); return { ok: true, value: obj }; } catch (e){ return { ok: false, error: 'json parse failed', raw: res.stdout }; }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function safeExec(cmd){
|
|
73
|
-
const allow = /^(bash\s+-lc\s+).*/s.test(cmd) && /(find|printf|awk|sed|python3|ls|sort|head|uniq|tr)/.test(cmd);
|
|
74
|
-
if (!allow) return { ok: false, skipped: true };
|
|
75
|
-
const res = spawnSync('bash', ['-lc', cmd], { encoding: 'utf-8' });
|
|
76
|
-
return {
|
|
77
|
-
ok: true,
|
|
78
|
-
code: res.status ?? 0,
|
|
79
|
-
stdout: (res.stdout || '').split('\n').slice(0, 20).join('\n'),
|
|
80
|
-
stderr: (res.stderr || '').split('\n').slice(0, 20).join('\n')
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function runCase(c){
|
|
85
|
-
const payload = {
|
|
86
|
-
model,
|
|
87
|
-
stream: false,
|
|
88
|
-
tool_choice: { type: 'function', function: { name: 'shell_toon' } },
|
|
89
|
-
messages: [ { role: 'system', content: systemMsg }, { role: 'user', content: c.user } ],
|
|
90
|
-
tools
|
|
91
|
-
};
|
|
92
|
-
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
|
93
|
-
let body; try { body = await res.json(); } catch { return { title: c.title, status: 'http_error', note: res.statusText }; }
|
|
94
|
-
const msg = body?.choices?.[0]?.message;
|
|
95
|
-
const argStr = msg?.tool_calls?.[0]?.function?.arguments;
|
|
96
|
-
if (typeof argStr !== 'string') return { title: c.title, status: 'no_arguments' };
|
|
97
|
-
let argumentsObj; try { argumentsObj = JSON.parse(argStr); } catch { return { title: c.title, status: 'arguments_not_json' }; }
|
|
98
|
-
const toon = argumentsObj?.toon;
|
|
99
|
-
if (typeof toon !== 'string' || !toon.trim()) return { title: c.title, status: 'no_toon' };
|
|
100
|
-
const decoded = decodeToon(toon);
|
|
101
|
-
if (!decoded.ok) return { title: c.title, status: 'toon_decode_fail', note: decoded.error };
|
|
102
|
-
const cmd = decoded.value?.command;
|
|
103
|
-
let execRes = undefined;
|
|
104
|
-
if (doExec && typeof cmd === 'string' && cmd.trim()) execRes = safeExec(cmd);
|
|
105
|
-
return { title: c.title, status: 'ok', toon, decoded: decoded.value, exec: execRes };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function main(){
|
|
109
|
-
console.log(`TOON probe → ${url} model=${model} exec=${doExec?'on':'off'}`);
|
|
110
|
-
const results = [];
|
|
111
|
-
for (const c of cases) {
|
|
112
|
-
try {
|
|
113
|
-
const r = await runCase(c);
|
|
114
|
-
results.push(r);
|
|
115
|
-
console.log(`\n=== ${c.title} ===`);
|
|
116
|
-
console.log(`status: ${r.status}`);
|
|
117
|
-
if (r.status === 'ok') {
|
|
118
|
-
const preview = String(r.toon).split('\n').slice(0,4).join('\n');
|
|
119
|
-
console.log('toon:\n' + preview);
|
|
120
|
-
console.log('decoded:', JSON.stringify(r.decoded));
|
|
121
|
-
if (r.exec && r.exec.ok) {
|
|
122
|
-
console.log('exec code:', r.exec.code);
|
|
123
|
-
console.log('stdout:\n' + r.exec.stdout);
|
|
124
|
-
if (r.exec.stderr) console.log('stderr:\n' + r.exec.stderr);
|
|
125
|
-
}
|
|
126
|
-
} else if (r.note) {
|
|
127
|
-
console.log('note:', r.note);
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
results.push({ title: c.title, status: 'exception', note: e?.message || String(e) });
|
|
131
|
-
console.log(`\n=== ${c.title} ===`);
|
|
132
|
-
console.log('status: exception');
|
|
133
|
-
console.log('note:', e?.message || String(e));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const ok = results.filter(x=>x.status==='ok').length;
|
|
137
|
-
console.log('\nSUMMARY:', `${ok}/${results.length} ok`);
|
|
138
|
-
for (const r of results) console.log(`- ${r.title}: ${r.status}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
main().catch(e=>{ console.error('fatal:', e?.message || e); process.exit(1); });
|