@jsonstudio/rcc 0.89.932 → 0.89.942

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.
@@ -300,6 +300,67 @@ function collectInvalidNames(payload) {
300
300
  return failures;
301
301
  }
302
302
 
303
+ function validateToolCallIds(payload, sample, tagSet) {
304
+ const errors = [];
305
+ if (!payload || typeof payload !== 'object') {
306
+ return errors;
307
+ }
308
+ const wantsFcIds =
309
+ tagSet.has('require_fc_call_ids') ||
310
+ tagSet.has('missing_tool_call_id') ||
311
+ tagSet.has('regression');
312
+
313
+ const allToolCallIds = new Set();
314
+ if (Array.isArray(payload.output)) {
315
+ payload.output.forEach((entry, oi) => {
316
+ if (!entry || typeof entry !== 'object') return;
317
+ const toolCalls = Array.isArray(entry.tool_calls) ? entry.tool_calls : [];
318
+ toolCalls.forEach((tc, ti) => {
319
+ if (!tc || typeof tc !== 'object') return;
320
+ const rawId = typeof tc.id === 'string' ? tc.id.trim() : '';
321
+ if (!rawId) {
322
+ errors.push(`output[${oi}].tool_calls[${ti}].id missing`);
323
+ return;
324
+ }
325
+ allToolCallIds.add(rawId);
326
+ if (wantsFcIds && !/^call_[A-Za-z0-9]+$/.test(rawId)) {
327
+ errors.push(`output[${oi}].tool_calls[${ti}].id has invalid format: ${rawId}`);
328
+ }
329
+ });
330
+ });
331
+ }
332
+
333
+ const requiredAction = payload.required_action;
334
+ const submit = requiredAction && typeof requiredAction === 'object'
335
+ ? requiredAction.submit_tool_outputs
336
+ : undefined;
337
+ const submitCalls = submit && typeof submit === 'object'
338
+ ? submit.tool_calls
339
+ : undefined;
340
+ if (Array.isArray(submitCalls)) {
341
+ submitCalls.forEach((tc, i) => {
342
+ if (!tc || typeof tc !== 'object') return;
343
+ const rawId = typeof tc.tool_call_id === 'string' ? tc.tool_call_id.trim() : '';
344
+ if (!rawId) {
345
+ errors.push(`required_action.submit_tool_outputs.tool_calls[${i}].tool_call_id missing`);
346
+ return;
347
+ }
348
+ if (wantsFcIds && !/^call_[A-Za-z0-9]+$/.test(rawId)) {
349
+ errors.push(
350
+ `required_action.submit_tool_outputs.tool_calls[${i}].tool_call_id has invalid format: ${rawId}`
351
+ );
352
+ }
353
+ if (allToolCallIds.size > 0 && !allToolCallIds.has(rawId)) {
354
+ errors.push(
355
+ `required_action.submit_tool_outputs.tool_calls[${i}].tool_call_id=${rawId} has no matching output.tool_calls entry`
356
+ );
357
+ }
358
+ });
359
+ }
360
+
361
+ return errors;
362
+ }
363
+
303
364
  function extractRequestBody(doc) {
304
365
  const body = doc && typeof doc === 'object' ? doc.body : undefined;
305
366
  if (!body || typeof body !== 'object') {
@@ -379,6 +440,7 @@ async function runSample(sample, index) {
379
440
  return undefined;
380
441
  }
381
442
  })();
443
+ const tags = new Set(Array.isArray(sample.tags) ? sample.tags : []);
382
444
  if (body && Array.isArray(body.output)) {
383
445
  const invalid = collectInvalidNames(body);
384
446
  if (invalid.length) {
@@ -388,6 +450,14 @@ async function runSample(sample, index) {
388
450
  .join('\n')}`
389
451
  );
390
452
  }
453
+ const idErrors = validateToolCallIds(body, sample, tags);
454
+ if (idErrors.length) {
455
+ throw new Error(
456
+ `[${sample.reqId}] tool_call_id invariants failed:\n${idErrors
457
+ .map((msg) => ` - ${msg}`)
458
+ .join('\n')}`
459
+ );
460
+ }
391
461
  }
392
462
  } catch (error) {
393
463
  const logs = server.logs();
@@ -412,6 +482,10 @@ async function main() {
412
482
  console.warn('[mock:regressions] No regression-tagged samples matched current filters; skipping mock replay.');
413
483
  return;
414
484
  }
485
+
486
+ const coverageByEntry = Object.create(null);
487
+ const coverageByProvider = Object.create(null);
488
+
415
489
  const tagCounter = Object.create(null);
416
490
  const incrementTag = (tag) => {
417
491
  if (!watchedTags.has(tag)) {
@@ -421,6 +495,12 @@ async function main() {
421
495
  };
422
496
  regressionSamples.forEach((sample) => {
423
497
  const matched = new Set();
498
+ const entry = typeof sample.entry === 'string' && sample.entry.trim().length ? sample.entry.trim() : 'unknown';
499
+ const providerId =
500
+ typeof sample.providerId === 'string' && sample.providerId.trim().length ? sample.providerId.trim() : 'unknown';
501
+ coverageByEntry[entry] = (coverageByEntry[entry] || 0) + 1;
502
+ coverageByProvider[providerId] = (coverageByProvider[providerId] || 0) + 1;
503
+
424
504
  (sample.tags || []).forEach((tag) => {
425
505
  if (watchedTags.has(tag) && !matched.has(tag)) {
426
506
  incrementTag(tag);
@@ -436,6 +516,14 @@ async function main() {
436
516
  .map((tag) => `${tag}=${tagCounter[tag] || 0}`)
437
517
  .join(', ');
438
518
  console.log(`✅ mock provider regressions passed (${regressionSamples.length} samples · ${summary})`);
519
+ const byEntry = Object.entries(coverageByEntry)
520
+ .map(([entry, count]) => `${entry}=${count}`)
521
+ .join(', ');
522
+ const byProvider = Object.entries(coverageByProvider)
523
+ .map(([pid, count]) => `${pid}=${count}`)
524
+ .join(', ');
525
+ console.log(`[mock:regressions] coverage by entry: ${byEntry}`);
526
+ console.log(`[mock:regressions] coverage by providerId: ${byProvider}`);
439
527
  }
440
528
 
441
529
  main().catch((error) => {