crosscheck-mcp 0.1.13 → 0.1.15

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.
@@ -545,6 +545,22 @@ var init_better_sqlite3 = __esm({
545
545
  ORDER BY purpose`
546
546
  ).all(sessionId);
547
547
  }
548
+ async listUsageGroupedByModel(sessionId) {
549
+ return this.cached(
550
+ "usage-grp-model",
551
+ `SELECT provider,
552
+ model,
553
+ COUNT(*) AS calls,
554
+ COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,
555
+ COALESCE(SUM(completion_tokens), 0) AS completion_tokens,
556
+ COALESCE(SUM(total_tokens), 0) AS total_tokens,
557
+ COALESCE(SUM(cost_usd), 0) AS cost_usd
558
+ FROM usage_log
559
+ WHERE session_id = ?
560
+ GROUP BY provider, model
561
+ ORDER BY cost_usd DESC, provider, model`
562
+ ).all(sessionId);
563
+ }
548
564
  async listUsageGroupedByProvider(purpose) {
549
565
  if (purpose !== void 0) {
550
566
  return this.cached(
@@ -1052,7 +1068,8 @@ function modelPricing(pricing, provider, model) {
1052
1068
  return {
1053
1069
  prompt_per_1k: numberOrZero(e["prompt_per_1k"]),
1054
1070
  completion_per_1k: numberOrZero(e["completion_per_1k"]),
1055
- cached_per_1k: numberOrZero(e["cached_per_1k"])
1071
+ cached_per_1k: numberOrZero(e["cached_per_1k"]),
1072
+ ...e["estimated"] === true ? { estimated: true } : {}
1056
1073
  };
1057
1074
  }
1058
1075
  function numberOrZero(v) {
@@ -1072,7 +1089,7 @@ function calculateCost(pricing, provider, model, promptTokens, completionTokens,
1072
1089
  const prompt = Math.max(0, Math.trunc(promptTokens) - cached);
1073
1090
  const completion = Math.max(0, Math.trunc(completionTokens));
1074
1091
  const raw = prompt / 1e3 * rates.prompt_per_1k + completion / 1e3 * rates.completion_per_1k + cached / 1e3 * rates.cached_per_1k;
1075
- return { cost_usd: roundTo(raw, 8), estimated: false };
1092
+ return { cost_usd: roundTo(raw, 8), estimated: rates.estimated === true };
1076
1093
  }
1077
1094
  function roundTo(x, n) {
1078
1095
  if (!Number.isFinite(x)) return 0;
@@ -1092,7 +1109,7 @@ var PROVIDER_CAPS = {
1092
1109
  family: "anthropic",
1093
1110
  system_role: "separate",
1094
1111
  supports_temperature: "model",
1095
- reasoning_prefixes: ["claude-opus-4-7", "claude-opus-4-8"]
1112
+ reasoning_prefixes: ["claude-opus-4-7", "claude-opus-4-8", "claude-fable-5"]
1096
1113
  },
1097
1114
  openai: {
1098
1115
  family: "openai_chat",
@@ -2044,7 +2061,17 @@ Examples:
2044
2061
  - \`xc what's the right caching strategy here?\` \u2192 \`mcp__crosscheck__${DEFAULT_TOOL}({ question: "what's the right caching strategy here?" })\` (no tool name match \u2192 default to ${DEFAULT_TOOL})
2045
2062
  - \`XC how risky is this refactor?\` \u2192 same default-to-${DEFAULT_TOOL} behavior
2046
2063
 
2047
- This convention is opt-in: a user message without the \`xc\`/\`XC\` prefix should NOT be auto-routed here. Pick tools normally based on the conversation.`;
2064
+ This convention is opt-in: a user message without the \`xc\`/\`XC\` prefix should NOT be auto-routed here. Pick tools normally based on the conversation.
2065
+
2066
+ Reasoning upgrade (Fable 5):
2067
+ - The reasoning tools (plan, solve, debate, orchestrate, audit, triangulate) can bump their lead reasoning seat to Fable 5 \u2014 Anthropic's newest thinking model \u2014 instead of the default Opus 4.8. Only that one seat is upgraded; other panel seats stay on their normal models, so premium tokens are not wasted.
2068
+ - When a tool result includes a "suggested_upgrade" block, the engine judged the task reasoning-heavy. Surface it: ask the user something like "This looks reasoning-heavy \u2014 bump the lead seat to Fable 5 (higher quality, higher cost)?" If they say yes, re-run the SAME tool with reasoning:"max" added. Do NOT upgrade silently.
2069
+ - If the user explicitly asks for the best/deepest reasoning up front, pass reasoning:"max" directly. A result with reasoning_upgrade.applied = true confirms Fable 5 was used.
2070
+
2071
+ Spend visibility:
2072
+ - Every reasoning-tool result carries a "run_summary" whose pre-rendered "text" block already includes a "spend by model (this call)" breakdown. Show that block \u2014 do not bury the cost.
2073
+ - "run_summary" also carries "session_by_model" + "session_cost_usd" (per-model spend for the whole session). At the END of a multi-turn task, present the session spend per model plus the aggregate total, so the user knows what the task cost without scrolling back.
2074
+ - Fable 5's price is a provisional placeholder; rows from it are flagged "(est)"/estimated \u2014 say so when reporting those dollar figures.`;
2048
2075
  }
2049
2076
 
2050
2077
  // src/tools/index.ts
@@ -2300,6 +2327,7 @@ function roundCost(x) {
2300
2327
  function aggregateUsage(usages) {
2301
2328
  const byCall = usages.map((u) => ({ ...u }));
2302
2329
  const byProvider = /* @__PURE__ */ new Map();
2330
+ const byModel = /* @__PURE__ */ new Map();
2303
2331
  let totalPrompt = 0;
2304
2332
  let totalCompletion = 0;
2305
2333
  let totalCached = 0;
@@ -2327,6 +2355,29 @@ function aggregateUsage(usages) {
2327
2355
  bp.cost_usd = roundCost(bp.cost_usd + u.cost_usd);
2328
2356
  bp.calls += 1;
2329
2357
  bp.estimated = bp.estimated || u.estimated;
2358
+ const modelKey = `${u.provider}/${u.model}`;
2359
+ let bm = byModel.get(modelKey);
2360
+ if (!bm) {
2361
+ bm = {
2362
+ provider: u.provider,
2363
+ model: u.model,
2364
+ prompt_tokens: 0,
2365
+ completion_tokens: 0,
2366
+ cached_tokens: 0,
2367
+ total_tokens: 0,
2368
+ cost_usd: 0,
2369
+ calls: 0,
2370
+ estimated: false
2371
+ };
2372
+ byModel.set(modelKey, bm);
2373
+ }
2374
+ bm.prompt_tokens += u.prompt_tokens;
2375
+ bm.completion_tokens += u.completion_tokens;
2376
+ bm.cached_tokens += u.cached_tokens;
2377
+ bm.total_tokens += u.total_tokens;
2378
+ bm.cost_usd = roundCost(bm.cost_usd + u.cost_usd);
2379
+ bm.calls += 1;
2380
+ bm.estimated = bm.estimated || u.estimated;
2330
2381
  totalPrompt += u.prompt_tokens;
2331
2382
  totalCompletion += u.completion_tokens;
2332
2383
  totalCached += u.cached_tokens;
@@ -2336,6 +2387,7 @@ function aggregateUsage(usages) {
2336
2387
  return {
2337
2388
  by_call: byCall,
2338
2389
  by_provider: Array.from(byProvider.values()),
2390
+ by_model: Array.from(byModel.values()),
2339
2391
  totals: {
2340
2392
  prompt_tokens: totalPrompt,
2341
2393
  completion_tokens: totalCompletion,
@@ -2418,7 +2470,46 @@ function renderRunSummary(opts) {
2418
2470
  `${glyph} purpose: ${r.purpose} (calls=${r.calls} tokens=${withCommas(r.total_tokens)} cost=$${r.cost_usd.toFixed(4)} wall=${(r.wall_ms / 1e3).toFixed(1)}s cpu=${(r.cpu_ms / 1e3).toFixed(3)}s)`
2419
2471
  );
2420
2472
  }
2421
- const text = [header, ...bodyLines].join("\n");
2473
+ const callByModel = rollupSpendByModel(
2474
+ opts.answers.map((a) => ({
2475
+ provider: a.usage?.provider ?? "",
2476
+ model: a.usage?.model ?? "",
2477
+ calls: 1,
2478
+ total_tokens: a.usage?.total_tokens ?? 0,
2479
+ cost_usd: a.usage?.cost_usd ?? 0,
2480
+ estimated: a.usage?.estimated ?? false
2481
+ })).filter((r) => r.provider !== "")
2482
+ );
2483
+ let sessionByModel;
2484
+ let sessionCost;
2485
+ if (opts.sessionModelRows && opts.sessionModelRows.length > 0) {
2486
+ sessionByModel = rollupSpendByModel(
2487
+ opts.sessionModelRows.map((r) => ({
2488
+ provider: r.provider,
2489
+ model: r.model,
2490
+ calls: r.calls,
2491
+ total_tokens: r.total_tokens,
2492
+ cost_usd: r.cost_usd,
2493
+ // Derive estimated from the pricing doc (SQL rows don't carry it).
2494
+ estimated: opts.pricing ? modelPricing(opts.pricing, r.provider, r.model)?.estimated === true : false
2495
+ }))
2496
+ );
2497
+ sessionCost = round8(sessionByModel.reduce((s, r) => s + r.cost_usd, 0));
2498
+ }
2499
+ const lines = [header, ...bodyLines];
2500
+ if (callByModel.length > 0) {
2501
+ lines.push("spend by model (this call):");
2502
+ lines.push(...renderSpendByModelLines(callByModel));
2503
+ }
2504
+ if (sessionByModel && sessionByModel.length > 0 && sessionCost !== void 0) {
2505
+ lines.push(
2506
+ `session so far \u2014 spend by model (${withCommas(
2507
+ sessionByModel.reduce((s, r) => s + r.total_tokens, 0)
2508
+ )} tokens, $${sessionCost.toFixed(4)}${sessionByModel.some((r) => r.estimated) ? " \xB7 includes estimated rates" : ""}):`
2509
+ );
2510
+ lines.push(...renderSpendByModelLines(sessionByModel));
2511
+ }
2512
+ const text = lines.join("\n");
2422
2513
  return {
2423
2514
  session_id: opts.sessionId,
2424
2515
  tool: opts.toolName,
@@ -2428,9 +2519,47 @@ function renderRunSummary(opts) {
2428
2519
  ended_at: endedAt,
2429
2520
  rows,
2430
2521
  totals,
2522
+ call_by_model: callByModel,
2523
+ ...sessionByModel ? { session_by_model: sessionByModel } : {},
2524
+ ...sessionCost !== void 0 ? { session_cost_usd: sessionCost } : {},
2431
2525
  text
2432
2526
  };
2433
2527
  }
2528
+ function rollupSpendByModel(frags) {
2529
+ const m = /* @__PURE__ */ new Map();
2530
+ for (const f of frags) {
2531
+ const key = `${f.provider}/${f.model}`;
2532
+ let row = m.get(key);
2533
+ if (!row) {
2534
+ row = {
2535
+ provider: f.provider,
2536
+ model: f.model,
2537
+ calls: 0,
2538
+ total_tokens: 0,
2539
+ cost_usd: 0,
2540
+ estimated: false
2541
+ };
2542
+ m.set(key, row);
2543
+ }
2544
+ row.calls += f.calls;
2545
+ row.total_tokens += f.total_tokens;
2546
+ row.cost_usd = round8(row.cost_usd + f.cost_usd);
2547
+ row.estimated = row.estimated || f.estimated;
2548
+ }
2549
+ return Array.from(m.values()).sort(
2550
+ (a, b) => b.cost_usd - a.cost_usd || a.provider.localeCompare(b.provider)
2551
+ );
2552
+ }
2553
+ function renderSpendByModelLines(rows) {
2554
+ const nameW = Math.max(...rows.map((r) => `${r.provider}/${r.model}`.length), 0);
2555
+ return rows.map((r) => {
2556
+ const name = `${r.provider}/${r.model}`.padEnd(nameW);
2557
+ const calls = `${r.calls} call${r.calls === 1 ? "" : "s"}`.padStart(8);
2558
+ const toks = `${withCommas(r.total_tokens)} tok`.padStart(13);
2559
+ const cost = `$${r.cost_usd.toFixed(4)}`.padStart(10);
2560
+ return ` ${name} ${calls} ${toks} ${cost}${r.estimated ? " (est)" : ""}`;
2561
+ });
2562
+ }
2434
2563
  async function attachUsageBlock(result, answers, opts) {
2435
2564
  const allCalls = [...answers, ...opts?.extras ?? []];
2436
2565
  const usages = [];
@@ -2466,7 +2595,12 @@ async function attachUsageBlock(result, answers, opts) {
2466
2595
  result["timing"] = timing;
2467
2596
  if (opts?.toolName && !result["_suppress_run_summary"]) {
2468
2597
  let sessionScope;
2598
+ let sessionModelRows;
2469
2599
  if (opts.storage && opts.sessionId) {
2600
+ try {
2601
+ sessionModelRows = await opts.storage.listUsageGroupedByModel(opts.sessionId);
2602
+ } catch {
2603
+ }
2470
2604
  try {
2471
2605
  const rows = await opts.storage.listUsageGroupedByPurpose(opts.sessionId);
2472
2606
  if (rows.length > 0) {
@@ -2496,6 +2630,8 @@ async function attachUsageBlock(result, answers, opts) {
2496
2630
  toolName: opts.toolName,
2497
2631
  answers: allCalls,
2498
2632
  ...sessionScope ? { sessionScope } : {},
2633
+ ...sessionModelRows && sessionModelRows.length > 0 ? { sessionModelRows } : {},
2634
+ ...opts.pricing ? { pricing: opts.pricing } : {},
2499
2635
  ...opts.endedAtS !== void 0 ? { endedAtS: opts.endedAtS } : {}
2500
2636
  });
2501
2637
  }
@@ -3561,6 +3697,83 @@ function selectForDifficulty(args) {
3561
3697
  };
3562
3698
  }
3563
3699
 
3700
+ // src/core/reasoning-upgrade.ts
3701
+ init_esm_shims();
3702
+ var UPGRADE_MODEL = "claude-fable-5";
3703
+ var UPGRADE_LABEL = "Fable 5";
3704
+ function isUpgradeRequested(args) {
3705
+ if (args["upgrade"] === true) return true;
3706
+ const reasoning = args["reasoning"];
3707
+ if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
3708
+ return true;
3709
+ }
3710
+ const model = args["model"];
3711
+ if (typeof model === "string") {
3712
+ const m = model.trim().toLowerCase();
3713
+ if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
3714
+ return true;
3715
+ }
3716
+ }
3717
+ return false;
3718
+ }
3719
+ var HEAVY_TERMS = [
3720
+ /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
3721
+ /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
3722
+ /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
3723
+ /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
3724
+ /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
3725
+ /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
3726
+ /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
3727
+ /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
3728
+ /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
3729
+ /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
3730
+ /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
3731
+ /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
3732
+ ];
3733
+ var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
3734
+ function assessReasoning(text) {
3735
+ const s = typeof text === "string" ? text : "";
3736
+ const signals = [];
3737
+ let score = 0;
3738
+ let termHits = 0;
3739
+ for (const re of HEAVY_TERMS) {
3740
+ if (re.test(s)) termHits += 1;
3741
+ }
3742
+ if (termHits > 0) {
3743
+ const add = Math.min(termHits, 4);
3744
+ score += add;
3745
+ signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
3746
+ }
3747
+ if (DIFFICULTY_TERMS.test(s)) {
3748
+ score += 1;
3749
+ signals.push("explicit difficulty marker");
3750
+ }
3751
+ const words = s.split(/\s+/).filter(Boolean).length;
3752
+ if (words >= 120) {
3753
+ score += 2;
3754
+ signals.push("long, detailed prompt");
3755
+ } else if (words >= 50) {
3756
+ score += 1;
3757
+ signals.push("substantial prompt");
3758
+ }
3759
+ const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
3760
+ if (constraints >= 3) {
3761
+ score += 1;
3762
+ signals.push(`${constraints} constraints/steps`);
3763
+ }
3764
+ const heavy = score >= 3;
3765
+ const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
3766
+ return { heavy, score, reason, signals };
3767
+ }
3768
+ function buildSuggestedUpgrade(toolName, assessment) {
3769
+ return {
3770
+ model: UPGRADE_MODEL,
3771
+ label: UPGRADE_LABEL,
3772
+ reason: assessment.reason,
3773
+ how: `re-run \`${toolName}\` with reasoning:"max" (or upgrade:true) to route the lead reasoning seat to ${UPGRADE_LABEL}. Other panel seats stay on their normal models, so only the high-level reasoning uses the premium model.`
3774
+ };
3775
+ }
3776
+
3564
3777
  // src/tools/audit.ts
3565
3778
  var AUDIT_OBVIOUS_FAILURE_HIGH = 0.3;
3566
3779
  var AUDIT_OBVIOUS_FAILURE_MED = 0.2;
@@ -3827,7 +4040,10 @@ ${rubricText}`;
3827
4040
  "Set ANTHROPIC_API_KEY or another provider in .env, or set `allow_self_audit=true` to permit the producing panel to self-grade."
3828
4041
  );
3829
4042
  }
3830
- const r = await requestStructured(auditor, msgs, AUDIT_RUBRIC_SCHEMA, {
4043
+ const upgradeRequested = isUpgradeRequested(args);
4044
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
4045
+ const auditorForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : auditor;
4046
+ const r = await requestStructured(auditorForCall, msgs, AUDIT_RUBRIC_SCHEMA, {
3831
4047
  maxTokens: 2048,
3832
4048
  maxRetries: 1,
3833
4049
  purpose: "audit"
@@ -3869,7 +4085,7 @@ ${rubricText}`;
3869
4085
  tool: "audit",
3870
4086
  mode: "single",
3871
4087
  strict_mode: strictMode,
3872
- auditor: { provider: auditor.name, model: auditor.model },
4088
+ auditor: { provider: auditorForCall.name, model: auditorForCall.model },
3873
4089
  rubric: rubricItems,
3874
4090
  items: itemsWithMeta,
3875
4091
  overall_score: overall,
@@ -3877,6 +4093,26 @@ ${rubricText}`;
3877
4093
  };
3878
4094
  if (persona.meta.used !== null) result["persona"] = persona.meta;
3879
4095
  if (errs.length > 0) result["validation_errors"] = errs;
4096
+ if (upgraded) {
4097
+ result["reasoning_upgrade"] = {
4098
+ applied: true,
4099
+ model: UPGRADE_MODEL,
4100
+ label: UPGRADE_LABEL,
4101
+ seat: "auditor"
4102
+ };
4103
+ } else if (upgradeRequested) {
4104
+ result["reasoning_upgrade"] = {
4105
+ applied: false,
4106
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
4107
+ };
4108
+ } else {
4109
+ const assessment = assessReasoning(
4110
+ typeof outputToAudit === "string" ? outputToAudit : String(outputToAudit ?? "")
4111
+ );
4112
+ if (assessment.heavy && opts.providers["anthropic"]) {
4113
+ result["suggested_upgrade"] = buildSuggestedUpgrade("audit", assessment);
4114
+ }
4115
+ }
3880
4116
  if (!allPass && opts.storage && typeof sessionId === "string" && sessionId) {
3881
4117
  const marked = await markStaleOnAuditFailure(
3882
4118
  opts.storage,
@@ -7546,7 +7782,12 @@ ${recombineSys}` : recombineSys },
7546
7782
  ];
7547
7783
  let synthProvider = moderator;
7548
7784
  let synthModel = null;
7549
- if (cheapMode && opts.pricing) {
7785
+ const upgradeRequested = isUpgradeRequested(args);
7786
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
7787
+ if (upgraded) {
7788
+ synthProvider = retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL);
7789
+ synthModel = `anthropic/${UPGRADE_MODEL}`;
7790
+ } else if (cheapMode && opts.pricing) {
7550
7791
  const pick = selectForDifficulty({
7551
7792
  pricing: opts.pricing,
7552
7793
  tier: "med",
@@ -7617,6 +7858,24 @@ ${recombineSys}` : recombineSys },
7617
7858
  if (personaMeta.used !== null) result["persona"] = personaMeta;
7618
7859
  if (synthModel) result["synth_model"] = synthModel;
7619
7860
  if (synthErr) result["synth_error"] = synthErr;
7861
+ if (upgraded) {
7862
+ result["reasoning_upgrade"] = {
7863
+ applied: true,
7864
+ model: UPGRADE_MODEL,
7865
+ label: UPGRADE_LABEL,
7866
+ seat: "recombine/synthesis"
7867
+ };
7868
+ } else if (upgradeRequested) {
7869
+ result["reasoning_upgrade"] = {
7870
+ applied: false,
7871
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
7872
+ };
7873
+ } else if (goal) {
7874
+ const assessment = assessReasoning(goal);
7875
+ if (assessment.heavy && opts.providers["anthropic"]) {
7876
+ result["suggested_upgrade"] = buildSuggestedUpgrade("orchestrate", assessment);
7877
+ }
7878
+ }
7620
7879
  if (plannerErrors.length > 0) result["planner_errors"] = plannerErrors;
7621
7880
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
7622
7881
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
@@ -8788,6 +9047,9 @@ async function runCoordinate(args, opts) {
8788
9047
  const synthName = typeof args["synthesizer"] === "string" && args["synthesizer"] ? args["synthesizer"] : typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
8789
9048
  const proposer = opts.providers[proposerName.toLowerCase()] ?? selected[0];
8790
9049
  const synth = opts.providers[synthName.toLowerCase()] ?? proposer;
9050
+ const upgradeRequested = isUpgradeRequested(args);
9051
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9052
+ const synthForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : synth;
8791
9053
  let critics = [];
8792
9054
  if (Array.isArray(args["critics"])) {
8793
9055
  for (const n of args["critics"]) {
@@ -8936,7 +9198,7 @@ ${critiqueBlock}`
8936
9198
  }
8937
9199
  ];
8938
9200
  const synthResult = await requestStructured(
8939
- synth,
9201
+ synthForCall,
8940
9202
  synthMessages,
8941
9203
  STRUCTURED_SYNTHESIS_SCHEMA,
8942
9204
  { maxTokens, maxRetries: 1, purpose: "synth" }
@@ -8985,6 +9247,24 @@ ${critiqueBlock}`
8985
9247
  }
8986
9248
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
8987
9249
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
9250
+ if (upgraded) {
9251
+ result["reasoning_upgrade"] = {
9252
+ applied: true,
9253
+ model: UPGRADE_MODEL,
9254
+ label: UPGRADE_LABEL,
9255
+ seat: "synthesizer"
9256
+ };
9257
+ } else if (upgradeRequested) {
9258
+ result["reasoning_upgrade"] = {
9259
+ applied: false,
9260
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
9261
+ };
9262
+ } else {
9263
+ const assessment = assessReasoning(topic);
9264
+ if (assessment.heavy && opts.providers["anthropic"]) {
9265
+ result["suggested_upgrade"] = buildSuggestedUpgrade("coordinate", assessment);
9266
+ }
9267
+ }
8988
9268
  const allCallEnvelopes = [
8989
9269
  scannedProposal,
8990
9270
  ...scannedCritiques,
@@ -9525,6 +9805,8 @@ async function runDebate(args, opts) {
9525
9805
  let agreementBlock = null;
9526
9806
  const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
9527
9807
  const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
9808
+ const upgradeRequested = isUpgradeRequested(args);
9809
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9528
9810
  for (let rnd = 1; rnd <= maxRounds; rnd++) {
9529
9811
  const roundMessages = [
9530
9812
  {
@@ -9586,6 +9868,7 @@ ${prior}` });
9586
9868
  let synthesisErrors = [];
9587
9869
  let personaMeta = null;
9588
9870
  if (moderator) {
9871
+ const synthProvider = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : moderator;
9589
9872
  const condensed = transcript.map(
9590
9873
  (e) => `[${e.provider} \u2014 round ${e.round}]
9591
9874
  ${e.response ?? "(error)"}`
@@ -9616,7 +9899,7 @@ ${condensed}`
9616
9899
  ];
9617
9900
  if (structuredOpt) {
9618
9901
  const r = await requestStructured(
9619
- moderator,
9902
+ synthProvider,
9620
9903
  synthMessages,
9621
9904
  STRUCTURED_SYNTHESIS_SCHEMA,
9622
9905
  { maxTokens, maxRetries: 1, purpose: "synth" }
@@ -9625,7 +9908,7 @@ ${condensed}`
9625
9908
  synthesisStructured = r.obj;
9626
9909
  synthesisErrors = r.errors;
9627
9910
  } else {
9628
- synthesis = await askOne(moderator, synthMessages, {
9911
+ synthesis = await askOne(synthProvider, synthMessages, {
9629
9912
  maxTokens,
9630
9913
  temperature: 0.4,
9631
9914
  purpose: "synth"
@@ -9657,6 +9940,24 @@ ${condensed}`
9657
9940
  synthesis
9658
9941
  };
9659
9942
  if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
9943
+ if (upgraded) {
9944
+ result["reasoning_upgrade"] = {
9945
+ applied: true,
9946
+ model: UPGRADE_MODEL,
9947
+ label: UPGRADE_LABEL,
9948
+ seat: "moderator synthesis"
9949
+ };
9950
+ } else if (upgradeRequested) {
9951
+ result["reasoning_upgrade"] = {
9952
+ applied: false,
9953
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
9954
+ };
9955
+ } else {
9956
+ const assessment = assessReasoning(topic);
9957
+ if (assessment.heavy && opts.providers["anthropic"]) {
9958
+ result["suggested_upgrade"] = buildSuggestedUpgrade("debate", assessment);
9959
+ }
9960
+ }
9660
9961
  if (synthesisStructured !== null) {
9661
9962
  result["synthesis_structured"] = synthesisStructured;
9662
9963
  }
@@ -10728,7 +11029,15 @@ Return: (1) the plan as numbered steps, (2) risks, (3) alternatives considered.`
10728
11029
  if (args["providers"] !== void 0) debateArgs["providers"] = args["providers"];
10729
11030
  if (args["moderator"] !== void 0) debateArgs["moderator"] = args["moderator"];
10730
11031
  if (args["session_id"] !== void 0) debateArgs["session_id"] = args["session_id"];
10731
- return await runDebate(debateArgs, opts);
11032
+ if (args["reasoning"] !== void 0) debateArgs["reasoning"] = args["reasoning"];
11033
+ if (args["upgrade"] !== void 0) debateArgs["upgrade"] = args["upgrade"];
11034
+ if (args["model"] !== void 0) debateArgs["model"] = args["model"];
11035
+ const result = await runDebate(debateArgs, opts);
11036
+ if (result["suggested_upgrade"] && typeof result["suggested_upgrade"] === "object") {
11037
+ const su = result["suggested_upgrade"];
11038
+ su["how"] = `re-run \`plan\` with reasoning:"max" (or upgrade:true) to route the plan-synthesis seat to Fable 5. The debaters stay on their normal models.`;
11039
+ }
11040
+ return result;
10732
11041
  }
10733
11042
 
10734
11043
  // src/tools/recall.ts
@@ -11461,8 +11770,11 @@ ${problem}` : problem;
11461
11770
  let winningProvider = null;
11462
11771
  let lastVerification = null;
11463
11772
  const maxTokens = opts.maxTokens ?? 4096;
11773
+ const upgradeRequested = isUpgradeRequested(args);
11774
+ const fableProvider = upgradeRequested && opts.providers["anthropic"] ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : null;
11775
+ const upgraded = fableProvider !== null;
11464
11776
  for (let i = 1; i <= maxAttempts; i++) {
11465
- const provider = selected[(i - 1) % selected.length];
11777
+ const provider = fableProvider ?? selected[(i - 1) % selected.length];
11466
11778
  const msgs = [
11467
11779
  { role: "system", content: systemMsg },
11468
11780
  { role: "user", content: userBlock }
@@ -11559,6 +11871,24 @@ Re-emit the FULL solution. No commentary.`
11559
11871
  }
11560
11872
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
11561
11873
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
11874
+ if (upgraded) {
11875
+ result["reasoning_upgrade"] = {
11876
+ applied: true,
11877
+ model: UPGRADE_MODEL,
11878
+ label: UPGRADE_LABEL,
11879
+ seat: "solver"
11880
+ };
11881
+ } else if (upgradeRequested) {
11882
+ result["reasoning_upgrade"] = {
11883
+ applied: false,
11884
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
11885
+ };
11886
+ } else {
11887
+ const assessment = assessReasoning(problem);
11888
+ if (assessment.heavy && opts.providers["anthropic"]) {
11889
+ result["suggested_upgrade"] = buildSuggestedUpgrade("solve", assessment);
11890
+ }
11891
+ }
11562
11892
  const sessionId = typeof args["session_id"] === "string" ? args["session_id"] : null;
11563
11893
  const allCallEnvelopes = solveAnswers;
11564
11894
  const wallMs = Math.trunc(performance12.now() - callStartedWall);
@@ -11837,7 +12167,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11837
12167
  var DEFAULT_PACKAGE = "crosscheck-cli";
11838
12168
  var FETCH_TIMEOUT_MS = 3e3;
11839
12169
  function engineVersion() {
11840
- return true ? "0.1.13" : "0.0.0-dev";
12170
+ return true ? "0.1.15" : "0.0.0-dev";
11841
12171
  }
11842
12172
  function defaultUpdateCachePath() {
11843
12173
  const base = process.env["CROSSCHECK_DATA_DIR"] || path10.join(os.homedir() || os.tmpdir(), ".crosscheck");
@@ -12191,6 +12521,9 @@ async function runTriangulate(args, opts) {
12191
12521
  };
12192
12522
  if (args["providers"] !== void 0) coordArgs["providers"] = args["providers"];
12193
12523
  if (args["session_id"] !== void 0) coordArgs["session_id"] = args["session_id"];
12524
+ if (args["reasoning"] !== void 0) coordArgs["reasoning"] = args["reasoning"];
12525
+ if (args["upgrade"] !== void 0) coordArgs["upgrade"] = args["upgrade"];
12526
+ if (args["model"] !== void 0) coordArgs["model"] = args["model"];
12194
12527
  const coord = await runCoordinate(coordArgs, opts);
12195
12528
  if (typeof coord["error"] === "string") return coord;
12196
12529
  const synth = coord["synthesis_structured"] ?? {};
@@ -12239,10 +12572,16 @@ async function runTriangulate(args, opts) {
12239
12572
  "session",
12240
12573
  "transcript_path",
12241
12574
  "blocked_by_allowlist",
12242
- "skipped_unknown_providers"
12575
+ "skipped_unknown_providers",
12576
+ "reasoning_upgrade",
12577
+ "suggested_upgrade"
12243
12578
  ]) {
12244
12579
  if (k in coord) result[k] = coord[k];
12245
12580
  }
12581
+ if (result["suggested_upgrade"] && typeof result["suggested_upgrade"] === "object") {
12582
+ const su = result["suggested_upgrade"];
12583
+ su["how"] = `re-run \`triangulate\` with reasoning:"max" (or upgrade:true) to route the synthesizer seat to Fable 5. The proposer + critics stay on their normal models.`;
12584
+ }
12246
12585
  return result;
12247
12586
  }
12248
12587
 
@@ -12463,6 +12802,7 @@ function orchestrateTool(providers, allowlist, bridge, moderator, storage, prici
12463
12802
  additionalProperties: true,
12464
12803
  properties: {
12465
12804
  goal: { type: "string" },
12805
+ reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the recombine/synthesis seat to Fable 5 (premium thinking model); worker nodes stay on their normal (incl. cheap-mode) models. Omit for the default.' },
12466
12806
  dag: { type: "object", additionalProperties: true },
12467
12807
  context: { type: "string" },
12468
12808
  providers: { type: "array", items: { type: "string" } },
@@ -12497,6 +12837,7 @@ function solveTool(providers, allowlist, bridge, storage, breakers, transcriptsD
12497
12837
  additionalProperties: true,
12498
12838
  properties: {
12499
12839
  problem: { type: "string" },
12840
+ reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the lead reasoning seat to Fable 5 (premium thinking model) for this call; other seats stay on their normal models. Omit for the Opus 4.8 default.' },
12500
12841
  verifier: { type: "object", additionalProperties: true },
12501
12842
  context: { type: "string" },
12502
12843
  max_attempts: { type: "integer", minimum: 1 },
@@ -12831,6 +13172,7 @@ function planTool(providers, allowlist, bridge) {
12831
13172
  additionalProperties: true,
12832
13173
  properties: {
12833
13174
  goal: { type: "string" },
13175
+ reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the plan-synthesis seat to Fable 5 (premium thinking model); the debaters stay on their normal models. Omit for the Opus 4.8 default.' },
12834
13176
  constraints: { type: "string" },
12835
13177
  context: { type: "string" },
12836
13178
  providers: { type: "array", items: { type: "string" } },
@@ -12860,6 +13202,7 @@ function triangulateTool(providers, allowlist, bridge) {
12860
13202
  additionalProperties: true,
12861
13203
  properties: {
12862
13204
  question: { type: "string" },
13205
+ reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the synthesizer seat to Fable 5 (premium thinking model); the proposer + critics stay on their normal models. Omit for the default.' },
12863
13206
  context: { type: "string" },
12864
13207
  providers: { type: "array", items: { type: "string" } },
12865
13208
  session_id: { type: "string" },
@@ -12923,6 +13266,7 @@ function debateTool(providers, allowlist, bridge, innerCallers, storage, pricing
12923
13266
  providers: { type: "array", items: { type: "string" } },
12924
13267
  moderator: { type: "string" },
12925
13268
  session_id: { type: "string" },
13269
+ reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the moderator-synthesis seat to Fable 5 (premium thinking model); the debaters stay on their normal models. Omit for the Opus 4.8 default.' },
12926
13270
  structured: { type: "boolean" },
12927
13271
  extract_claims: { type: "boolean" },
12928
13272
  early_stop: { type: "boolean" },
@@ -13007,6 +13351,7 @@ function auditTool(providers, allowlist, bridge, transcriptsDir, pricing, storag
13007
13351
  additionalProperties: true,
13008
13352
  properties: {
13009
13353
  output_to_audit: { type: "string" },
13354
+ reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the auditor seat to Fable 5 (premium thinking model) in single-auditor mode. Omit for the default.' },
13010
13355
  session_id: { type: "string" },
13011
13356
  auditor: { type: "string" },
13012
13357
  producing_panelists: { type: "array", items: { type: "string" } },
@@ -13369,7 +13714,7 @@ async function flush() {
13369
13714
 
13370
13715
  // src/server.ts
13371
13716
  var SERVER_NAME = "crosscheck-agent";
13372
- var SERVER_VERSION = true ? "0.1.13" : "0.0.0-dev";
13717
+ var SERVER_VERSION = true ? "0.1.15" : "0.0.0-dev";
13373
13718
  function extractRunSummaryText(out) {
13374
13719
  if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
13375
13720
  const rs = out["run_summary"];