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.
@@ -558,6 +558,22 @@ var init_better_sqlite3 = __esm({
558
558
  ORDER BY purpose`
559
559
  ).all(sessionId);
560
560
  }
561
+ async listUsageGroupedByModel(sessionId) {
562
+ return this.cached(
563
+ "usage-grp-model",
564
+ `SELECT provider,
565
+ model,
566
+ COUNT(*) AS calls,
567
+ COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,
568
+ COALESCE(SUM(completion_tokens), 0) AS completion_tokens,
569
+ COALESCE(SUM(total_tokens), 0) AS total_tokens,
570
+ COALESCE(SUM(cost_usd), 0) AS cost_usd
571
+ FROM usage_log
572
+ WHERE session_id = ?
573
+ GROUP BY provider, model
574
+ ORDER BY cost_usd DESC, provider, model`
575
+ ).all(sessionId);
576
+ }
561
577
  async listUsageGroupedByProvider(purpose) {
562
578
  if (purpose !== void 0) {
563
579
  return this.cached(
@@ -1076,7 +1092,8 @@ function modelPricing(pricing, provider, model) {
1076
1092
  return {
1077
1093
  prompt_per_1k: numberOrZero(e["prompt_per_1k"]),
1078
1094
  completion_per_1k: numberOrZero(e["completion_per_1k"]),
1079
- cached_per_1k: numberOrZero(e["cached_per_1k"])
1095
+ cached_per_1k: numberOrZero(e["cached_per_1k"]),
1096
+ ...e["estimated"] === true ? { estimated: true } : {}
1080
1097
  };
1081
1098
  }
1082
1099
  function numberOrZero(v) {
@@ -1096,7 +1113,7 @@ function calculateCost(pricing, provider, model, promptTokens, completionTokens,
1096
1113
  const prompt = Math.max(0, Math.trunc(promptTokens) - cached);
1097
1114
  const completion = Math.max(0, Math.trunc(completionTokens));
1098
1115
  const raw = prompt / 1e3 * rates.prompt_per_1k + completion / 1e3 * rates.completion_per_1k + cached / 1e3 * rates.cached_per_1k;
1099
- return { cost_usd: roundTo(raw, 8), estimated: false };
1116
+ return { cost_usd: roundTo(raw, 8), estimated: rates.estimated === true };
1100
1117
  }
1101
1118
  function roundTo(x, n) {
1102
1119
  if (!Number.isFinite(x)) return 0;
@@ -1116,7 +1133,7 @@ var PROVIDER_CAPS = {
1116
1133
  family: "anthropic",
1117
1134
  system_role: "separate",
1118
1135
  supports_temperature: "model",
1119
- reasoning_prefixes: ["claude-opus-4-7", "claude-opus-4-8"]
1136
+ reasoning_prefixes: ["claude-opus-4-7", "claude-opus-4-8", "claude-fable-5"]
1120
1137
  },
1121
1138
  openai: {
1122
1139
  family: "openai_chat",
@@ -2065,7 +2082,17 @@ Examples:
2065
2082
  - \`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})
2066
2083
  - \`XC how risky is this refactor?\` \u2192 same default-to-${DEFAULT_TOOL} behavior
2067
2084
 
2068
- 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.`;
2085
+ 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.
2086
+
2087
+ Reasoning upgrade (Fable 5):
2088
+ - 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.
2089
+ - 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.
2090
+ - 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.
2091
+
2092
+ Spend visibility:
2093
+ - 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.
2094
+ - "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.
2095
+ - Fable 5's price is a provisional placeholder; rows from it are flagged "(est)"/estimated \u2014 say so when reporting those dollar figures.`;
2069
2096
  }
2070
2097
 
2071
2098
  // src/tools/index.ts
@@ -2321,6 +2348,7 @@ function roundCost(x) {
2321
2348
  function aggregateUsage(usages) {
2322
2349
  const byCall = usages.map((u) => ({ ...u }));
2323
2350
  const byProvider = /* @__PURE__ */ new Map();
2351
+ const byModel = /* @__PURE__ */ new Map();
2324
2352
  let totalPrompt = 0;
2325
2353
  let totalCompletion = 0;
2326
2354
  let totalCached = 0;
@@ -2348,6 +2376,29 @@ function aggregateUsage(usages) {
2348
2376
  bp.cost_usd = roundCost(bp.cost_usd + u.cost_usd);
2349
2377
  bp.calls += 1;
2350
2378
  bp.estimated = bp.estimated || u.estimated;
2379
+ const modelKey = `${u.provider}/${u.model}`;
2380
+ let bm = byModel.get(modelKey);
2381
+ if (!bm) {
2382
+ bm = {
2383
+ provider: u.provider,
2384
+ model: u.model,
2385
+ prompt_tokens: 0,
2386
+ completion_tokens: 0,
2387
+ cached_tokens: 0,
2388
+ total_tokens: 0,
2389
+ cost_usd: 0,
2390
+ calls: 0,
2391
+ estimated: false
2392
+ };
2393
+ byModel.set(modelKey, bm);
2394
+ }
2395
+ bm.prompt_tokens += u.prompt_tokens;
2396
+ bm.completion_tokens += u.completion_tokens;
2397
+ bm.cached_tokens += u.cached_tokens;
2398
+ bm.total_tokens += u.total_tokens;
2399
+ bm.cost_usd = roundCost(bm.cost_usd + u.cost_usd);
2400
+ bm.calls += 1;
2401
+ bm.estimated = bm.estimated || u.estimated;
2351
2402
  totalPrompt += u.prompt_tokens;
2352
2403
  totalCompletion += u.completion_tokens;
2353
2404
  totalCached += u.cached_tokens;
@@ -2357,6 +2408,7 @@ function aggregateUsage(usages) {
2357
2408
  return {
2358
2409
  by_call: byCall,
2359
2410
  by_provider: Array.from(byProvider.values()),
2411
+ by_model: Array.from(byModel.values()),
2360
2412
  totals: {
2361
2413
  prompt_tokens: totalPrompt,
2362
2414
  completion_tokens: totalCompletion,
@@ -2439,7 +2491,46 @@ function renderRunSummary(opts) {
2439
2491
  `${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)`
2440
2492
  );
2441
2493
  }
2442
- const text = [header, ...bodyLines].join("\n");
2494
+ const callByModel = rollupSpendByModel(
2495
+ opts.answers.map((a) => ({
2496
+ provider: a.usage?.provider ?? "",
2497
+ model: a.usage?.model ?? "",
2498
+ calls: 1,
2499
+ total_tokens: a.usage?.total_tokens ?? 0,
2500
+ cost_usd: a.usage?.cost_usd ?? 0,
2501
+ estimated: a.usage?.estimated ?? false
2502
+ })).filter((r) => r.provider !== "")
2503
+ );
2504
+ let sessionByModel;
2505
+ let sessionCost;
2506
+ if (opts.sessionModelRows && opts.sessionModelRows.length > 0) {
2507
+ sessionByModel = rollupSpendByModel(
2508
+ opts.sessionModelRows.map((r) => ({
2509
+ provider: r.provider,
2510
+ model: r.model,
2511
+ calls: r.calls,
2512
+ total_tokens: r.total_tokens,
2513
+ cost_usd: r.cost_usd,
2514
+ // Derive estimated from the pricing doc (SQL rows don't carry it).
2515
+ estimated: opts.pricing ? modelPricing(opts.pricing, r.provider, r.model)?.estimated === true : false
2516
+ }))
2517
+ );
2518
+ sessionCost = round8(sessionByModel.reduce((s, r) => s + r.cost_usd, 0));
2519
+ }
2520
+ const lines = [header, ...bodyLines];
2521
+ if (callByModel.length > 0) {
2522
+ lines.push("spend by model (this call):");
2523
+ lines.push(...renderSpendByModelLines(callByModel));
2524
+ }
2525
+ if (sessionByModel && sessionByModel.length > 0 && sessionCost !== void 0) {
2526
+ lines.push(
2527
+ `session so far \u2014 spend by model (${withCommas(
2528
+ sessionByModel.reduce((s, r) => s + r.total_tokens, 0)
2529
+ )} tokens, $${sessionCost.toFixed(4)}${sessionByModel.some((r) => r.estimated) ? " \xB7 includes estimated rates" : ""}):`
2530
+ );
2531
+ lines.push(...renderSpendByModelLines(sessionByModel));
2532
+ }
2533
+ const text = lines.join("\n");
2443
2534
  return {
2444
2535
  session_id: opts.sessionId,
2445
2536
  tool: opts.toolName,
@@ -2449,9 +2540,47 @@ function renderRunSummary(opts) {
2449
2540
  ended_at: endedAt,
2450
2541
  rows,
2451
2542
  totals,
2543
+ call_by_model: callByModel,
2544
+ ...sessionByModel ? { session_by_model: sessionByModel } : {},
2545
+ ...sessionCost !== void 0 ? { session_cost_usd: sessionCost } : {},
2452
2546
  text
2453
2547
  };
2454
2548
  }
2549
+ function rollupSpendByModel(frags) {
2550
+ const m = /* @__PURE__ */ new Map();
2551
+ for (const f of frags) {
2552
+ const key = `${f.provider}/${f.model}`;
2553
+ let row = m.get(key);
2554
+ if (!row) {
2555
+ row = {
2556
+ provider: f.provider,
2557
+ model: f.model,
2558
+ calls: 0,
2559
+ total_tokens: 0,
2560
+ cost_usd: 0,
2561
+ estimated: false
2562
+ };
2563
+ m.set(key, row);
2564
+ }
2565
+ row.calls += f.calls;
2566
+ row.total_tokens += f.total_tokens;
2567
+ row.cost_usd = round8(row.cost_usd + f.cost_usd);
2568
+ row.estimated = row.estimated || f.estimated;
2569
+ }
2570
+ return Array.from(m.values()).sort(
2571
+ (a, b) => b.cost_usd - a.cost_usd || a.provider.localeCompare(b.provider)
2572
+ );
2573
+ }
2574
+ function renderSpendByModelLines(rows) {
2575
+ const nameW = Math.max(...rows.map((r) => `${r.provider}/${r.model}`.length), 0);
2576
+ return rows.map((r) => {
2577
+ const name = `${r.provider}/${r.model}`.padEnd(nameW);
2578
+ const calls = `${r.calls} call${r.calls === 1 ? "" : "s"}`.padStart(8);
2579
+ const toks = `${withCommas(r.total_tokens)} tok`.padStart(13);
2580
+ const cost = `$${r.cost_usd.toFixed(4)}`.padStart(10);
2581
+ return ` ${name} ${calls} ${toks} ${cost}${r.estimated ? " (est)" : ""}`;
2582
+ });
2583
+ }
2455
2584
  async function attachUsageBlock(result, answers, opts) {
2456
2585
  const allCalls = [...answers, ...opts?.extras ?? []];
2457
2586
  const usages = [];
@@ -2487,7 +2616,12 @@ async function attachUsageBlock(result, answers, opts) {
2487
2616
  result["timing"] = timing;
2488
2617
  if (opts?.toolName && !result["_suppress_run_summary"]) {
2489
2618
  let sessionScope;
2619
+ let sessionModelRows;
2490
2620
  if (opts.storage && opts.sessionId) {
2621
+ try {
2622
+ sessionModelRows = await opts.storage.listUsageGroupedByModel(opts.sessionId);
2623
+ } catch {
2624
+ }
2491
2625
  try {
2492
2626
  const rows = await opts.storage.listUsageGroupedByPurpose(opts.sessionId);
2493
2627
  if (rows.length > 0) {
@@ -2517,6 +2651,8 @@ async function attachUsageBlock(result, answers, opts) {
2517
2651
  toolName: opts.toolName,
2518
2652
  answers: allCalls,
2519
2653
  ...sessionScope ? { sessionScope } : {},
2654
+ ...sessionModelRows && sessionModelRows.length > 0 ? { sessionModelRows } : {},
2655
+ ...opts.pricing ? { pricing: opts.pricing } : {},
2520
2656
  ...opts.endedAtS !== void 0 ? { endedAtS: opts.endedAtS } : {}
2521
2657
  });
2522
2658
  }
@@ -3582,6 +3718,83 @@ function selectForDifficulty(args) {
3582
3718
  };
3583
3719
  }
3584
3720
 
3721
+ // src/core/reasoning-upgrade.ts
3722
+ init_cjs_shims();
3723
+ var UPGRADE_MODEL = "claude-fable-5";
3724
+ var UPGRADE_LABEL = "Fable 5";
3725
+ function isUpgradeRequested(args) {
3726
+ if (args["upgrade"] === true) return true;
3727
+ const reasoning = args["reasoning"];
3728
+ if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
3729
+ return true;
3730
+ }
3731
+ const model = args["model"];
3732
+ if (typeof model === "string") {
3733
+ const m = model.trim().toLowerCase();
3734
+ if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
3735
+ return true;
3736
+ }
3737
+ }
3738
+ return false;
3739
+ }
3740
+ var HEAVY_TERMS = [
3741
+ /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
3742
+ /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
3743
+ /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
3744
+ /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
3745
+ /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
3746
+ /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
3747
+ /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
3748
+ /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
3749
+ /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
3750
+ /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
3751
+ /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
3752
+ /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
3753
+ ];
3754
+ var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
3755
+ function assessReasoning(text) {
3756
+ const s = typeof text === "string" ? text : "";
3757
+ const signals = [];
3758
+ let score = 0;
3759
+ let termHits = 0;
3760
+ for (const re of HEAVY_TERMS) {
3761
+ if (re.test(s)) termHits += 1;
3762
+ }
3763
+ if (termHits > 0) {
3764
+ const add = Math.min(termHits, 4);
3765
+ score += add;
3766
+ signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
3767
+ }
3768
+ if (DIFFICULTY_TERMS.test(s)) {
3769
+ score += 1;
3770
+ signals.push("explicit difficulty marker");
3771
+ }
3772
+ const words = s.split(/\s+/).filter(Boolean).length;
3773
+ if (words >= 120) {
3774
+ score += 2;
3775
+ signals.push("long, detailed prompt");
3776
+ } else if (words >= 50) {
3777
+ score += 1;
3778
+ signals.push("substantial prompt");
3779
+ }
3780
+ const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
3781
+ if (constraints >= 3) {
3782
+ score += 1;
3783
+ signals.push(`${constraints} constraints/steps`);
3784
+ }
3785
+ const heavy = score >= 3;
3786
+ const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
3787
+ return { heavy, score, reason, signals };
3788
+ }
3789
+ function buildSuggestedUpgrade(toolName, assessment) {
3790
+ return {
3791
+ model: UPGRADE_MODEL,
3792
+ label: UPGRADE_LABEL,
3793
+ reason: assessment.reason,
3794
+ 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.`
3795
+ };
3796
+ }
3797
+
3585
3798
  // src/tools/audit.ts
3586
3799
  var AUDIT_OBVIOUS_FAILURE_HIGH = 0.3;
3587
3800
  var AUDIT_OBVIOUS_FAILURE_MED = 0.2;
@@ -3848,7 +4061,10 @@ ${rubricText}`;
3848
4061
  "Set ANTHROPIC_API_KEY or another provider in .env, or set `allow_self_audit=true` to permit the producing panel to self-grade."
3849
4062
  );
3850
4063
  }
3851
- const r = await requestStructured(auditor, msgs, AUDIT_RUBRIC_SCHEMA, {
4064
+ const upgradeRequested = isUpgradeRequested(args);
4065
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
4066
+ const auditorForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : auditor;
4067
+ const r = await requestStructured(auditorForCall, msgs, AUDIT_RUBRIC_SCHEMA, {
3852
4068
  maxTokens: 2048,
3853
4069
  maxRetries: 1,
3854
4070
  purpose: "audit"
@@ -3890,7 +4106,7 @@ ${rubricText}`;
3890
4106
  tool: "audit",
3891
4107
  mode: "single",
3892
4108
  strict_mode: strictMode,
3893
- auditor: { provider: auditor.name, model: auditor.model },
4109
+ auditor: { provider: auditorForCall.name, model: auditorForCall.model },
3894
4110
  rubric: rubricItems,
3895
4111
  items: itemsWithMeta,
3896
4112
  overall_score: overall,
@@ -3898,6 +4114,26 @@ ${rubricText}`;
3898
4114
  };
3899
4115
  if (persona.meta.used !== null) result["persona"] = persona.meta;
3900
4116
  if (errs.length > 0) result["validation_errors"] = errs;
4117
+ if (upgraded) {
4118
+ result["reasoning_upgrade"] = {
4119
+ applied: true,
4120
+ model: UPGRADE_MODEL,
4121
+ label: UPGRADE_LABEL,
4122
+ seat: "auditor"
4123
+ };
4124
+ } else if (upgradeRequested) {
4125
+ result["reasoning_upgrade"] = {
4126
+ applied: false,
4127
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
4128
+ };
4129
+ } else {
4130
+ const assessment = assessReasoning(
4131
+ typeof outputToAudit === "string" ? outputToAudit : String(outputToAudit ?? "")
4132
+ );
4133
+ if (assessment.heavy && opts.providers["anthropic"]) {
4134
+ result["suggested_upgrade"] = buildSuggestedUpgrade("audit", assessment);
4135
+ }
4136
+ }
3901
4137
  if (!allPass && opts.storage && typeof sessionId === "string" && sessionId) {
3902
4138
  const marked = await markStaleOnAuditFailure(
3903
4139
  opts.storage,
@@ -7550,7 +7786,12 @@ ${recombineSys}` : recombineSys },
7550
7786
  ];
7551
7787
  let synthProvider = moderator;
7552
7788
  let synthModel = null;
7553
- if (cheapMode && opts.pricing) {
7789
+ const upgradeRequested = isUpgradeRequested(args);
7790
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
7791
+ if (upgraded) {
7792
+ synthProvider = retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL);
7793
+ synthModel = `anthropic/${UPGRADE_MODEL}`;
7794
+ } else if (cheapMode && opts.pricing) {
7554
7795
  const pick = selectForDifficulty({
7555
7796
  pricing: opts.pricing,
7556
7797
  tier: "med",
@@ -7621,6 +7862,24 @@ ${recombineSys}` : recombineSys },
7621
7862
  if (personaMeta.used !== null) result["persona"] = personaMeta;
7622
7863
  if (synthModel) result["synth_model"] = synthModel;
7623
7864
  if (synthErr) result["synth_error"] = synthErr;
7865
+ if (upgraded) {
7866
+ result["reasoning_upgrade"] = {
7867
+ applied: true,
7868
+ model: UPGRADE_MODEL,
7869
+ label: UPGRADE_LABEL,
7870
+ seat: "recombine/synthesis"
7871
+ };
7872
+ } else if (upgradeRequested) {
7873
+ result["reasoning_upgrade"] = {
7874
+ applied: false,
7875
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
7876
+ };
7877
+ } else if (goal) {
7878
+ const assessment = assessReasoning(goal);
7879
+ if (assessment.heavy && opts.providers["anthropic"]) {
7880
+ result["suggested_upgrade"] = buildSuggestedUpgrade("orchestrate", assessment);
7881
+ }
7882
+ }
7624
7883
  if (plannerErrors.length > 0) result["planner_errors"] = plannerErrors;
7625
7884
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
7626
7885
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
@@ -8792,6 +9051,9 @@ async function runCoordinate(args, opts) {
8792
9051
  const synthName = typeof args["synthesizer"] === "string" && args["synthesizer"] ? args["synthesizer"] : typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
8793
9052
  const proposer = opts.providers[proposerName.toLowerCase()] ?? selected[0];
8794
9053
  const synth = opts.providers[synthName.toLowerCase()] ?? proposer;
9054
+ const upgradeRequested = isUpgradeRequested(args);
9055
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9056
+ const synthForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : synth;
8795
9057
  let critics = [];
8796
9058
  if (Array.isArray(args["critics"])) {
8797
9059
  for (const n of args["critics"]) {
@@ -8940,7 +9202,7 @@ ${critiqueBlock}`
8940
9202
  }
8941
9203
  ];
8942
9204
  const synthResult = await requestStructured(
8943
- synth,
9205
+ synthForCall,
8944
9206
  synthMessages,
8945
9207
  STRUCTURED_SYNTHESIS_SCHEMA,
8946
9208
  { maxTokens, maxRetries: 1, purpose: "synth" }
@@ -8989,6 +9251,24 @@ ${critiqueBlock}`
8989
9251
  }
8990
9252
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
8991
9253
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
9254
+ if (upgraded) {
9255
+ result["reasoning_upgrade"] = {
9256
+ applied: true,
9257
+ model: UPGRADE_MODEL,
9258
+ label: UPGRADE_LABEL,
9259
+ seat: "synthesizer"
9260
+ };
9261
+ } else if (upgradeRequested) {
9262
+ result["reasoning_upgrade"] = {
9263
+ applied: false,
9264
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
9265
+ };
9266
+ } else {
9267
+ const assessment = assessReasoning(topic);
9268
+ if (assessment.heavy && opts.providers["anthropic"]) {
9269
+ result["suggested_upgrade"] = buildSuggestedUpgrade("coordinate", assessment);
9270
+ }
9271
+ }
8992
9272
  const allCallEnvelopes = [
8993
9273
  scannedProposal,
8994
9274
  ...scannedCritiques,
@@ -9529,6 +9809,8 @@ async function runDebate(args, opts) {
9529
9809
  let agreementBlock = null;
9530
9810
  const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
9531
9811
  const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
9812
+ const upgradeRequested = isUpgradeRequested(args);
9813
+ const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9532
9814
  for (let rnd = 1; rnd <= maxRounds; rnd++) {
9533
9815
  const roundMessages = [
9534
9816
  {
@@ -9590,6 +9872,7 @@ ${prior}` });
9590
9872
  let synthesisErrors = [];
9591
9873
  let personaMeta = null;
9592
9874
  if (moderator) {
9875
+ const synthProvider = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : moderator;
9593
9876
  const condensed = transcript.map(
9594
9877
  (e) => `[${e.provider} \u2014 round ${e.round}]
9595
9878
  ${e.response ?? "(error)"}`
@@ -9620,7 +9903,7 @@ ${condensed}`
9620
9903
  ];
9621
9904
  if (structuredOpt) {
9622
9905
  const r = await requestStructured(
9623
- moderator,
9906
+ synthProvider,
9624
9907
  synthMessages,
9625
9908
  STRUCTURED_SYNTHESIS_SCHEMA,
9626
9909
  { maxTokens, maxRetries: 1, purpose: "synth" }
@@ -9629,7 +9912,7 @@ ${condensed}`
9629
9912
  synthesisStructured = r.obj;
9630
9913
  synthesisErrors = r.errors;
9631
9914
  } else {
9632
- synthesis = await askOne(moderator, synthMessages, {
9915
+ synthesis = await askOne(synthProvider, synthMessages, {
9633
9916
  maxTokens,
9634
9917
  temperature: 0.4,
9635
9918
  purpose: "synth"
@@ -9661,6 +9944,24 @@ ${condensed}`
9661
9944
  synthesis
9662
9945
  };
9663
9946
  if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
9947
+ if (upgraded) {
9948
+ result["reasoning_upgrade"] = {
9949
+ applied: true,
9950
+ model: UPGRADE_MODEL,
9951
+ label: UPGRADE_LABEL,
9952
+ seat: "moderator synthesis"
9953
+ };
9954
+ } else if (upgradeRequested) {
9955
+ result["reasoning_upgrade"] = {
9956
+ applied: false,
9957
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
9958
+ };
9959
+ } else {
9960
+ const assessment = assessReasoning(topic);
9961
+ if (assessment.heavy && opts.providers["anthropic"]) {
9962
+ result["suggested_upgrade"] = buildSuggestedUpgrade("debate", assessment);
9963
+ }
9964
+ }
9664
9965
  if (synthesisStructured !== null) {
9665
9966
  result["synthesis_structured"] = synthesisStructured;
9666
9967
  }
@@ -10732,7 +11033,15 @@ Return: (1) the plan as numbered steps, (2) risks, (3) alternatives considered.`
10732
11033
  if (args["providers"] !== void 0) debateArgs["providers"] = args["providers"];
10733
11034
  if (args["moderator"] !== void 0) debateArgs["moderator"] = args["moderator"];
10734
11035
  if (args["session_id"] !== void 0) debateArgs["session_id"] = args["session_id"];
10735
- return await runDebate(debateArgs, opts);
11036
+ if (args["reasoning"] !== void 0) debateArgs["reasoning"] = args["reasoning"];
11037
+ if (args["upgrade"] !== void 0) debateArgs["upgrade"] = args["upgrade"];
11038
+ if (args["model"] !== void 0) debateArgs["model"] = args["model"];
11039
+ const result = await runDebate(debateArgs, opts);
11040
+ if (result["suggested_upgrade"] && typeof result["suggested_upgrade"] === "object") {
11041
+ const su = result["suggested_upgrade"];
11042
+ 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.`;
11043
+ }
11044
+ return result;
10736
11045
  }
10737
11046
 
10738
11047
  // src/tools/recall.ts
@@ -11465,8 +11774,11 @@ ${problem}` : problem;
11465
11774
  let winningProvider = null;
11466
11775
  let lastVerification = null;
11467
11776
  const maxTokens = opts.maxTokens ?? 4096;
11777
+ const upgradeRequested = isUpgradeRequested(args);
11778
+ const fableProvider = upgradeRequested && opts.providers["anthropic"] ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : null;
11779
+ const upgraded = fableProvider !== null;
11468
11780
  for (let i = 1; i <= maxAttempts; i++) {
11469
- const provider = selected[(i - 1) % selected.length];
11781
+ const provider = fableProvider ?? selected[(i - 1) % selected.length];
11470
11782
  const msgs = [
11471
11783
  { role: "system", content: systemMsg },
11472
11784
  { role: "user", content: userBlock }
@@ -11563,6 +11875,24 @@ Re-emit the FULL solution. No commentary.`
11563
11875
  }
11564
11876
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
11565
11877
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
11878
+ if (upgraded) {
11879
+ result["reasoning_upgrade"] = {
11880
+ applied: true,
11881
+ model: UPGRADE_MODEL,
11882
+ label: UPGRADE_LABEL,
11883
+ seat: "solver"
11884
+ };
11885
+ } else if (upgradeRequested) {
11886
+ result["reasoning_upgrade"] = {
11887
+ applied: false,
11888
+ reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
11889
+ };
11890
+ } else {
11891
+ const assessment = assessReasoning(problem);
11892
+ if (assessment.heavy && opts.providers["anthropic"]) {
11893
+ result["suggested_upgrade"] = buildSuggestedUpgrade("solve", assessment);
11894
+ }
11895
+ }
11566
11896
  const sessionId = typeof args["session_id"] === "string" ? args["session_id"] : null;
11567
11897
  const allCallEnvelopes = solveAnswers;
11568
11898
  const wallMs = Math.trunc(import_node_perf_hooks11.performance.now() - callStartedWall);
@@ -11841,7 +12171,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11841
12171
  var DEFAULT_PACKAGE = "crosscheck-cli";
11842
12172
  var FETCH_TIMEOUT_MS = 3e3;
11843
12173
  function engineVersion() {
11844
- return true ? "0.1.13" : "0.0.0-dev";
12174
+ return true ? "0.1.15" : "0.0.0-dev";
11845
12175
  }
11846
12176
  function defaultUpdateCachePath() {
11847
12177
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path13.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
@@ -12195,6 +12525,9 @@ async function runTriangulate(args, opts) {
12195
12525
  };
12196
12526
  if (args["providers"] !== void 0) coordArgs["providers"] = args["providers"];
12197
12527
  if (args["session_id"] !== void 0) coordArgs["session_id"] = args["session_id"];
12528
+ if (args["reasoning"] !== void 0) coordArgs["reasoning"] = args["reasoning"];
12529
+ if (args["upgrade"] !== void 0) coordArgs["upgrade"] = args["upgrade"];
12530
+ if (args["model"] !== void 0) coordArgs["model"] = args["model"];
12198
12531
  const coord = await runCoordinate(coordArgs, opts);
12199
12532
  if (typeof coord["error"] === "string") return coord;
12200
12533
  const synth = coord["synthesis_structured"] ?? {};
@@ -12243,10 +12576,16 @@ async function runTriangulate(args, opts) {
12243
12576
  "session",
12244
12577
  "transcript_path",
12245
12578
  "blocked_by_allowlist",
12246
- "skipped_unknown_providers"
12579
+ "skipped_unknown_providers",
12580
+ "reasoning_upgrade",
12581
+ "suggested_upgrade"
12247
12582
  ]) {
12248
12583
  if (k in coord) result[k] = coord[k];
12249
12584
  }
12585
+ if (result["suggested_upgrade"] && typeof result["suggested_upgrade"] === "object") {
12586
+ const su = result["suggested_upgrade"];
12587
+ 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.`;
12588
+ }
12250
12589
  return result;
12251
12590
  }
12252
12591
 
@@ -12467,6 +12806,7 @@ function orchestrateTool(providers, allowlist, bridge, moderator, storage, prici
12467
12806
  additionalProperties: true,
12468
12807
  properties: {
12469
12808
  goal: { type: "string" },
12809
+ 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.' },
12470
12810
  dag: { type: "object", additionalProperties: true },
12471
12811
  context: { type: "string" },
12472
12812
  providers: { type: "array", items: { type: "string" } },
@@ -12501,6 +12841,7 @@ function solveTool(providers, allowlist, bridge, storage, breakers, transcriptsD
12501
12841
  additionalProperties: true,
12502
12842
  properties: {
12503
12843
  problem: { type: "string" },
12844
+ 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.' },
12504
12845
  verifier: { type: "object", additionalProperties: true },
12505
12846
  context: { type: "string" },
12506
12847
  max_attempts: { type: "integer", minimum: 1 },
@@ -12835,6 +13176,7 @@ function planTool(providers, allowlist, bridge) {
12835
13176
  additionalProperties: true,
12836
13177
  properties: {
12837
13178
  goal: { type: "string" },
13179
+ 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.' },
12838
13180
  constraints: { type: "string" },
12839
13181
  context: { type: "string" },
12840
13182
  providers: { type: "array", items: { type: "string" } },
@@ -12864,6 +13206,7 @@ function triangulateTool(providers, allowlist, bridge) {
12864
13206
  additionalProperties: true,
12865
13207
  properties: {
12866
13208
  question: { type: "string" },
13209
+ 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.' },
12867
13210
  context: { type: "string" },
12868
13211
  providers: { type: "array", items: { type: "string" } },
12869
13212
  session_id: { type: "string" },
@@ -12927,6 +13270,7 @@ function debateTool(providers, allowlist, bridge, innerCallers, storage, pricing
12927
13270
  providers: { type: "array", items: { type: "string" } },
12928
13271
  moderator: { type: "string" },
12929
13272
  session_id: { type: "string" },
13273
+ 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.' },
12930
13274
  structured: { type: "boolean" },
12931
13275
  extract_claims: { type: "boolean" },
12932
13276
  early_stop: { type: "boolean" },
@@ -13011,6 +13355,7 @@ function auditTool(providers, allowlist, bridge, transcriptsDir, pricing, storag
13011
13355
  additionalProperties: true,
13012
13356
  properties: {
13013
13357
  output_to_audit: { type: "string" },
13358
+ 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.' },
13014
13359
  session_id: { type: "string" },
13015
13360
  auditor: { type: "string" },
13016
13361
  producing_panelists: { type: "array", items: { type: "string" } },
@@ -13373,7 +13718,7 @@ async function flush() {
13373
13718
 
13374
13719
  // src/server.ts
13375
13720
  var SERVER_NAME = "crosscheck-agent";
13376
- var SERVER_VERSION = true ? "0.1.13" : "0.0.0-dev";
13721
+ var SERVER_VERSION = true ? "0.1.15" : "0.0.0-dev";
13377
13722
  function extractRunSummaryText(out) {
13378
13723
  if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
13379
13724
  const rs = out["run_summary"];