crosscheck-mcp 0.1.0 → 0.1.1

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.
@@ -48,6 +48,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
48
48
  // src/entrypoints/node-stdio.ts
49
49
  var import_node_fs11 = require("fs");
50
50
  var import_node_path11 = __toESM(require("path"), 1);
51
+ var import_node_url2 = require("url");
51
52
  var import_stdio2 = require("@modelcontextprotocol/sdk/server/stdio.js");
52
53
 
53
54
  // src/adapters/storage/better-sqlite3.ts
@@ -1265,6 +1266,7 @@ function defaultTransient(kind) {
1265
1266
  // src/providers/anthropic.ts
1266
1267
  var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
1267
1268
  var ANTHROPIC_VERSION_HEADER = "2023-06-01";
1269
+ var ANTHROPIC_STRUCTURED_TOOL_NAME = "structured_output";
1268
1270
  function buildAnthropicRequest(opts) {
1269
1271
  let system;
1270
1272
  const convo = [];
@@ -1289,6 +1291,17 @@ function buildAnthropicRequest(opts) {
1289
1291
  if (system !== void 0) {
1290
1292
  body.system = system;
1291
1293
  }
1294
+ if (opts.jsonSchema) {
1295
+ body.tools = [{
1296
+ name: ANTHROPIC_STRUCTURED_TOOL_NAME,
1297
+ description: "Emit a single JSON object matching the requested schema.",
1298
+ input_schema: opts.jsonSchema
1299
+ }];
1300
+ body.tool_choice = {
1301
+ type: "tool",
1302
+ name: ANTHROPIC_STRUCTURED_TOOL_NAME
1303
+ };
1304
+ }
1292
1305
  const headers = {
1293
1306
  "content-type": "application/json",
1294
1307
  "x-api-key": opts.apiKey,
@@ -1299,6 +1312,7 @@ function buildAnthropicRequest(opts) {
1299
1312
  function parseAnthropicResponse(opts) {
1300
1313
  const r = opts.resp ?? {};
1301
1314
  let text = "";
1315
+ let toolUseInput = void 0;
1302
1316
  const content = r["content"];
1303
1317
  if (!Array.isArray(content)) {
1304
1318
  throw new ProviderError(
@@ -1308,10 +1322,19 @@ function parseAnthropicResponse(opts) {
1308
1322
  }
1309
1323
  for (const block of content) {
1310
1324
  if (block && typeof block === "object") {
1311
- const t = block["text"];
1312
- if (typeof t === "string") text += t;
1325
+ const b = block;
1326
+ const type = b["type"];
1327
+ if (type === "tool_use" && b["name"] === ANTHROPIC_STRUCTURED_TOOL_NAME) {
1328
+ toolUseInput = b["input"];
1329
+ } else {
1330
+ const t = b["text"];
1331
+ if (typeof t === "string") text += t;
1332
+ }
1313
1333
  }
1314
1334
  }
1335
+ if (toolUseInput !== void 0) {
1336
+ text = JSON.stringify(toolUseInput);
1337
+ }
1315
1338
  const u = r["usage"] ?? {};
1316
1339
  const prompt = Math.trunc(Number(u["input_tokens"] ?? 0)) || 0;
1317
1340
  const cached = Math.trunc(Number(u["cache_read_input_tokens"] ?? 0)) || 0;
@@ -1356,7 +1379,8 @@ async function sendAnthropic(args) {
1356
1379
  apiKey: args.apiKey,
1357
1380
  messages: args.messages,
1358
1381
  maxTokens: args.maxTokens,
1359
- temperature: args.temperature
1382
+ temperature: args.temperature,
1383
+ ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1360
1384
  });
1361
1385
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1362
1386
  const init = {
@@ -1401,6 +1425,45 @@ async function sendAnthropic(args) {
1401
1425
 
1402
1426
  // src/providers/gemini.ts
1403
1427
  var GEMINI_API_URL_BASE = "https://generativelanguage.googleapis.com/v1beta/models";
1428
+ function jsonSchemaToGeminiSchema(schema) {
1429
+ const out = {};
1430
+ const type = schema["type"];
1431
+ if (typeof type === "string") {
1432
+ out["type"] = type.toUpperCase();
1433
+ } else if (Array.isArray(type)) {
1434
+ const nonNull = type.find((t) => typeof t === "string" && t !== "null");
1435
+ const hasNull = type.includes("null");
1436
+ if (typeof nonNull === "string") out["type"] = nonNull.toUpperCase();
1437
+ if (hasNull) out["nullable"] = true;
1438
+ }
1439
+ const passthrough = [
1440
+ "description",
1441
+ "enum",
1442
+ "format",
1443
+ "nullable",
1444
+ "required",
1445
+ "minItems",
1446
+ "maxItems",
1447
+ "minimum",
1448
+ "maximum"
1449
+ ];
1450
+ for (const k of passthrough) {
1451
+ if (k in schema) out[k] = schema[k];
1452
+ }
1453
+ if (schema["properties"] && typeof schema["properties"] === "object") {
1454
+ const props = {};
1455
+ for (const [name, val] of Object.entries(schema["properties"])) {
1456
+ if (val && typeof val === "object") {
1457
+ props[name] = jsonSchemaToGeminiSchema(val);
1458
+ }
1459
+ }
1460
+ out["properties"] = props;
1461
+ }
1462
+ if (schema["items"] && typeof schema["items"] === "object" && !Array.isArray(schema["items"])) {
1463
+ out["items"] = jsonSchemaToGeminiSchema(schema["items"]);
1464
+ }
1465
+ return out;
1466
+ }
1404
1467
  function buildGeminiRequest(opts) {
1405
1468
  const contents = [];
1406
1469
  let systemText = null;
@@ -1426,6 +1489,10 @@ function buildGeminiRequest(opts) {
1426
1489
  if (systemText !== null && systemText !== "") {
1427
1490
  body.systemInstruction = { parts: [{ text: systemText }] };
1428
1491
  }
1492
+ if (opts.jsonSchema) {
1493
+ body.generationConfig.responseMimeType = "application/json";
1494
+ body.generationConfig.responseSchema = jsonSchemaToGeminiSchema(opts.jsonSchema);
1495
+ }
1429
1496
  const url = `${GEMINI_API_URL_BASE}/${encodeURIComponent(opts.model)}:generateContent?key=${encodeURIComponent(opts.apiKey)}`;
1430
1497
  return { url, headers: {}, body };
1431
1498
  }
@@ -1494,7 +1561,8 @@ async function sendGemini(args) {
1494
1561
  apiKey: args.apiKey,
1495
1562
  messages: args.messages,
1496
1563
  maxTokens: args.maxTokens,
1497
- temperature: args.temperature
1564
+ temperature: args.temperature,
1565
+ ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1498
1566
  });
1499
1567
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1500
1568
  const init = {
@@ -1545,6 +1613,17 @@ var OPENAI_COMPAT_DEFAULT_URLS = {
1545
1613
  groq: "https://api.groq.com/openai/v1/chat/completions",
1546
1614
  deepseek: "https://api.deepseek.com/v1/chat/completions"
1547
1615
  };
1616
+ var OPENAI_COMPAT_NATIVE_STRUCTURED = /* @__PURE__ */ new Set([
1617
+ "openai"
1618
+ ]);
1619
+ function supportsNativeJsonSchema(provider, model) {
1620
+ if (!OPENAI_COMPAT_NATIVE_STRUCTURED.has(provider.toLowerCase())) {
1621
+ return false;
1622
+ }
1623
+ const m = model.toLowerCase();
1624
+ if (/^o1(?:-|$)/.test(m)) return false;
1625
+ return true;
1626
+ }
1548
1627
  function buildOpenAICompatibleRequest(opts) {
1549
1628
  const msgs = opts.messages.map((m) => ({
1550
1629
  role: m.role,
@@ -1560,6 +1639,16 @@ function buildOpenAICompatibleRequest(opts) {
1560
1639
  } else {
1561
1640
  body.max_completion_tokens = opts.maxTokens;
1562
1641
  }
1642
+ if (opts.jsonSchema && supportsNativeJsonSchema(opts.provider, opts.model)) {
1643
+ body.response_format = {
1644
+ type: "json_schema",
1645
+ json_schema: {
1646
+ name: "structured_output",
1647
+ schema: opts.jsonSchema,
1648
+ strict: true
1649
+ }
1650
+ };
1651
+ }
1563
1652
  const headers = {
1564
1653
  "content-type": "application/json",
1565
1654
  Authorization: `Bearer ${opts.apiKey}`
@@ -1631,7 +1720,8 @@ async function sendOpenAICompatible(args) {
1631
1720
  url: args.url,
1632
1721
  messages: args.messages,
1633
1722
  maxTokens: args.maxTokens,
1634
- temperature: args.temperature
1723
+ temperature: args.temperature,
1724
+ ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1635
1725
  });
1636
1726
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1637
1727
  const init = {
@@ -2104,11 +2194,13 @@ ${schemaText}`;
2104
2194
  content: "Your previous response failed validation:\n- " + lastErrs.slice(0, 5).join("\n- ") + "\nFix the issues and re-emit valid JSON only."
2105
2195
  });
2106
2196
  }
2197
+ const useNative = opts.useNativeStructured !== false;
2107
2198
  const ans = await askOne(provider, msgs, {
2108
2199
  maxTokens: opts.maxTokens,
2109
2200
  temperature: temp,
2110
2201
  purpose,
2111
- ...opts.signal ? { signal: opts.signal } : {}
2202
+ ...opts.signal ? { signal: opts.signal } : {},
2203
+ ...useNative ? { jsonSchema: schema } : {}
2112
2204
  });
2113
2205
  lastAnswer = ans;
2114
2206
  if (ans.error !== void 0) {
@@ -2143,7 +2235,8 @@ async function askOne(provider, messages, opts) {
2143
2235
  maxTokens: opts.maxTokens,
2144
2236
  temperature: opts.temperature,
2145
2237
  purpose: opts.purpose,
2146
- ...opts.signal ? { signal: opts.signal } : {}
2238
+ ...opts.signal ? { signal: opts.signal } : {},
2239
+ ...opts.jsonSchema ? { jsonSchema: opts.jsonSchema } : {}
2147
2240
  });
2148
2241
  const wallMs = Math.trunc(performance.now() - startedWall);
2149
2242
  const cpu = process.cpuUsage(startedCpu);
@@ -2386,7 +2479,7 @@ async function runAudit(args, opts) {
2386
2479
  const rubricOverride = args["rubric"];
2387
2480
  const producing = toStringArray(args["producing_panelists"]).map((s) => s.toLowerCase());
2388
2481
  const explicit = typeof args["auditor"] === "string" ? args["auditor"] : null;
2389
- const cheapMode = boolArg(args["cheap_mode"], true);
2482
+ const cheapMode = boolArg(args["cheap_mode"], opts.ctx?.cheap_mode ?? true);
2390
2483
  const allowSelf = boolArg(args["allow_self_audit"], false);
2391
2484
  let coalesce = boolArg(args["coalesce"], false);
2392
2485
  const strictMode = boolArg(args["strict_mode"], false);
@@ -3916,7 +4009,7 @@ async function runConfer(args, opts) {
3916
4009
  };
3917
4010
  }
3918
4011
  }
3919
- const { selected, unknown: unknownNames, blocked } = resolveProviders(
4012
+ let { selected, unknown: unknownNames, blocked } = resolveProviders(
3920
4013
  resolvedProviders,
3921
4014
  opts.providers,
3922
4015
  opts.allowlist ?? null
@@ -3933,6 +4026,49 @@ async function runConfer(args, opts) {
3933
4026
  }
3934
4027
  return { tool: "confer", error: "no active providers have API keys in .env" };
3935
4028
  }
4029
+ let cheapModePanelMeta = null;
4030
+ if (opts.ctx?.cheap_mode === true && !callerSuppliedProviders && selected.length > 1) {
4031
+ if (!opts.pricing) {
4032
+ cheapModePanelMeta = {
4033
+ before: selected.length,
4034
+ after: selected.length,
4035
+ picked: null,
4036
+ reason: "ctx.cheap_mode=true but no pricing doc wired; kept full panel"
4037
+ };
4038
+ } else {
4039
+ const availableSet = new Set(selected.map((p) => p.name.toLowerCase()));
4040
+ const cheapWeights = opts.storage ? await loadProviderWeights(opts.storage, selected.map((p) => p.name)) : {};
4041
+ const allowOnly = opts.allowlist && opts.allowlist.length > 0 ? opts.allowlist.map((s) => s.toLowerCase()) : void 0;
4042
+ const tierPick = selectForDifficulty({
4043
+ pricing: opts.pricing,
4044
+ tier: "low",
4045
+ availableProviders: availableSet,
4046
+ providerWeights: cheapWeights,
4047
+ ...allowOnly !== void 0 ? { allowOnly } : {}
4048
+ });
4049
+ if (tierPick.pick) {
4050
+ const baseProvider = opts.providers[tierPick.pick.provider.toLowerCase()];
4051
+ if (baseProvider) {
4052
+ const beforeCount = selected.length;
4053
+ const retargeted = retargetProvider(baseProvider, tierPick.pick.model);
4054
+ selected = [retargeted];
4055
+ cheapModePanelMeta = {
4056
+ before: beforeCount,
4057
+ after: 1,
4058
+ picked: { provider: tierPick.pick.provider, model: tierPick.pick.model },
4059
+ reason: "ctx.cheap_mode=true; narrowed to cheapest low-tier candidate"
4060
+ };
4061
+ }
4062
+ } else {
4063
+ cheapModePanelMeta = {
4064
+ before: selected.length,
4065
+ after: selected.length,
4066
+ picked: null,
4067
+ reason: `ctx.cheap_mode=true; ${tierPick.reason ?? "no candidate"}; kept full panel`
4068
+ };
4069
+ }
4070
+ }
4071
+ }
3936
4072
  const untrusted = Boolean(args["untrusted_input"]);
3937
4073
  const canary = untrusted ? mintCanary() : null;
3938
4074
  const baseSys = "You are part of a panel of LLMs consulted by an engineer working inside Claude Code. Answer directly, cite assumptions, and keep it crisp.";
@@ -4049,6 +4185,7 @@ ${ctxBody}` });
4049
4185
  if (claimsBlock !== null) result["claims"] = claimsBlock;
4050
4186
  if (leaks.length > 0) result["canary_leaks"] = leaks;
4051
4187
  if (autoPanelMeta !== null) result["auto_panel"] = autoPanelMeta;
4188
+ if (cheapModePanelMeta !== null) result["cheap_mode_panel"] = cheapModePanelMeta;
4052
4189
  if (requestedWorkerTools.length > 0) {
4053
4190
  result["worker_tools"] = {
4054
4191
  accepted: acceptedWorkerTools,
@@ -5220,6 +5357,40 @@ var import_node_path8 = __toESM(require("path"), 1);
5220
5357
 
5221
5358
  // src/tools/orchestrate.ts
5222
5359
  var import_node_perf_hooks2 = require("perf_hooks");
5360
+
5361
+ // src/core/dead-models.ts
5362
+ var DEAD_MODELS_TTL_MS = 5 * 60 * 1e3;
5363
+ var deadModels = /* @__PURE__ */ new Map();
5364
+ function makeKey(provider, model) {
5365
+ return `${provider.toLowerCase()}:${model.toLowerCase()}`;
5366
+ }
5367
+ function recordDeadModel(provider, model, reason, nowMs = Date.now()) {
5368
+ const key = makeKey(provider, model);
5369
+ deadModels.set(key, {
5370
+ provider: provider.toLowerCase(),
5371
+ model: model.toLowerCase(),
5372
+ reason: reason.slice(0, 200),
5373
+ expires: nowMs + DEAD_MODELS_TTL_MS
5374
+ });
5375
+ }
5376
+ function isModelDead(provider, model, nowMs = Date.now()) {
5377
+ const key = makeKey(provider, model);
5378
+ const entry = deadModels.get(key);
5379
+ if (!entry) return false;
5380
+ if (nowMs >= entry.expires) {
5381
+ deadModels.delete(key);
5382
+ return false;
5383
+ }
5384
+ return true;
5385
+ }
5386
+ function isDeadModelError(err) {
5387
+ if (typeof err.error !== "string") return false;
5388
+ if (err.error_kind !== "client") return false;
5389
+ return /HTTP\s*404|not\s*found|does not exist|deprecated|decommission/i.test(err.error);
5390
+ }
5391
+
5392
+ // src/tools/orchestrate.ts
5393
+ var F1B_MAX_TIER_RETRIES = 2;
5223
5394
  var DIFFICULTY_TIERS2 = ["low", "med", "high"];
5224
5395
  var EST_TOKENS = {
5225
5396
  low: [800, 400],
@@ -5270,7 +5441,7 @@ async function runOrchestrate(args, opts) {
5270
5441
  const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
5271
5442
  const failFast = boolArg2(args["fail_fast"], false);
5272
5443
  const planOnly = boolArg2(args["plan_only"], false);
5273
- const cheapMode = boolArg2(args["cheap_mode"], false);
5444
+ const cheapMode = boolArg2(args["cheap_mode"], opts.ctx?.cheap_mode ?? false);
5274
5445
  const maxTokens = opts.maxTokens ?? 4096;
5275
5446
  const cheapAvailable = new Set(
5276
5447
  selected.map((p) => p.name.toLowerCase())
@@ -5356,12 +5527,51 @@ async function runOrchestrate(args, opts) {
5356
5527
  failedIds.add(nid);
5357
5528
  continue;
5358
5529
  }
5530
+ const depsRaw = Array.isArray(node["depends_on"]) ? node["depends_on"] : [];
5531
+ const upstreamBlocks = [];
5532
+ for (const d of depsRaw) {
5533
+ if (typeof d !== "string") continue;
5534
+ const ur = nodeResults[d];
5535
+ if (ur && ur.status === "ok") {
5536
+ upstreamBlocks.push(`[node ${d} output]
5537
+ ${ur.output ?? ""}`);
5538
+ } else if (ur && ur.status === "failed") {
5539
+ upstreamBlocks.push(`[node ${d}] [MISSING: failed \u2014 ${ur.error ?? ""}]`);
5540
+ }
5541
+ }
5542
+ const ctxBlock = upstreamBlocks.length > 0 ? upstreamBlocks.join("\n\n") : "(no upstream nodes)";
5543
+ const sysMsg = "You are a worker LLM in an orchestrated DAG. Complete the assigned task using outputs from upstream nodes when relevant. Be concise.";
5544
+ const role = typeof node["role"] === "string" ? node["role"] : "worker";
5545
+ const task = String(node["task"] ?? "");
5546
+ const difficulty = String(node["difficulty"] ?? "med");
5547
+ const msgs = [
5548
+ { role: "system", content: sysMsg },
5549
+ {
5550
+ role: "user",
5551
+ content: `ROLE: ${role}
5552
+ DIFFICULTY: ${difficulty}
5553
+
5554
+ UPSTREAM:
5555
+ ${ctxBlock}
5556
+
5557
+ TASK:
5558
+ ${task}`
5559
+ }
5560
+ ];
5359
5561
  let chosen;
5562
+ let ans;
5360
5563
  let cheapReason = null;
5564
+ const retryAttempts = [];
5361
5565
  const pinnedName = typeof node["provider"] === "string" ? node["provider"].toLowerCase() : null;
5362
5566
  const pinnedModel = typeof node["model"] === "string" ? node["model"] : null;
5567
+ const started = import_node_perf_hooks2.performance.now();
5363
5568
  if (pinnedName && opts.providers[pinnedName]) {
5364
5569
  chosen = opts.providers[pinnedName];
5570
+ ans = await askOne(chosen, msgs, {
5571
+ maxTokens,
5572
+ temperature: 0.4,
5573
+ purpose: "worker"
5574
+ });
5365
5575
  } else if (cheapMode && !pinnedName && !pinnedModel && opts.pricing) {
5366
5576
  const tier = String(node["difficulty"] ?? "med");
5367
5577
  const pick = selectForDifficulty({
@@ -5371,21 +5581,78 @@ async function runOrchestrate(args, opts) {
5371
5581
  providerWeights: cheapWeights,
5372
5582
  ...cheapAllowOnly ? { allowOnly: cheapAllowOnly } : {}
5373
5583
  });
5374
- if (pick.pick) {
5375
- const base = opts.providers[pick.pick.provider];
5376
- if (base) chosen = retargetProvider(base, pick.pick.model);
5584
+ const aliveCandidates = pick.scored.filter(
5585
+ (c) => !isModelDead(c.provider, c.model)
5586
+ );
5587
+ if (aliveCandidates.length === 0) {
5588
+ cheapReason = pick.reason ?? "all tier candidates filtered by deadModels cache";
5377
5589
  } else {
5378
- cheapReason = pick.reason;
5590
+ const tryLimit = Math.min(F1B_MAX_TIER_RETRIES, aliveCandidates.length);
5591
+ for (let i = 0; i < tryLimit; i++) {
5592
+ const c = aliveCandidates[i];
5593
+ const base = opts.providers[c.provider];
5594
+ if (!base) {
5595
+ retryAttempts.push({
5596
+ provider: c.provider,
5597
+ model: c.model,
5598
+ reason: "provider not in active registry"
5599
+ });
5600
+ continue;
5601
+ }
5602
+ const candidateProvider = retargetProvider(base, c.model);
5603
+ const candidateAns = await askOne(candidateProvider, msgs, {
5604
+ maxTokens,
5605
+ temperature: 0.4,
5606
+ purpose: "worker"
5607
+ });
5608
+ if (candidateAns.error === void 0) {
5609
+ chosen = candidateProvider;
5610
+ ans = candidateAns;
5611
+ if (retryAttempts.length > 0) {
5612
+ cheapReason = `recovered after ${retryAttempts.length} dead-model retry(s): ` + retryAttempts.map((a) => `${a.provider}/${a.model}`).join(", ");
5613
+ }
5614
+ break;
5615
+ }
5616
+ if (isDeadModelError(candidateAns)) {
5617
+ recordDeadModel(c.provider, c.model, candidateAns.error ?? "");
5618
+ retryAttempts.push({
5619
+ provider: c.provider,
5620
+ model: c.model,
5621
+ reason: (candidateAns.error ?? "dead").slice(0, 100)
5622
+ });
5623
+ continue;
5624
+ }
5625
+ chosen = candidateProvider;
5626
+ ans = candidateAns;
5627
+ if (retryAttempts.length > 0) {
5628
+ cheapReason = `${retryAttempts.length} dead-model retry(s) before non-dead failure: ` + retryAttempts.map((a) => `${a.provider}/${a.model}`).join(", ");
5629
+ }
5630
+ break;
5631
+ }
5379
5632
  }
5380
5633
  if (!chosen) {
5381
5634
  const idx = djb2Hash(nid) % Math.max(1, selected.length);
5382
5635
  chosen = selected[idx];
5636
+ ans = await askOne(chosen, msgs, {
5637
+ maxTokens,
5638
+ temperature: 0.4,
5639
+ purpose: "worker"
5640
+ });
5641
+ if (retryAttempts.length > 0) {
5642
+ cheapReason = `cheap-mode tier exhausted (` + retryAttempts.map((a) => `${a.provider}/${a.model}`).join(", ") + `); fell back to id-hash rotation`;
5643
+ }
5383
5644
  }
5384
5645
  } else {
5385
5646
  const idx = djb2Hash(nid) % Math.max(1, selected.length);
5386
5647
  chosen = selected[idx];
5648
+ ans = await askOne(chosen, msgs, {
5649
+ maxTokens,
5650
+ temperature: 0.4,
5651
+ purpose: "worker"
5652
+ });
5387
5653
  }
5388
- if (!chosen) {
5654
+ const wallMs = Math.trunc(import_node_perf_hooks2.performance.now() - started);
5655
+ if (!chosen || !ans) {
5389
5656
  nodeResults[nid] = {
5390
5657
  id: nid,
5391
5658
  status: "failed",
@@ -5393,49 +5660,12 @@ async function runOrchestrate(args, opts) {
5393
5660
  model: null,
5394
5661
  error: "no provider available",
5395
5662
  wall_ms: 0,
5396
- cpu_ms: 0
5663
+ cpu_ms: 0,
5664
+ ...retryAttempts.length > 0 ? { retry_attempts: retryAttempts } : {}
5397
5665
  };
5398
5666
  failedIds.add(nid);
5399
5667
  continue;
5400
5668
  }
5401
- const depsRaw = Array.isArray(node["depends_on"]) ? node["depends_on"] : [];
5402
- const upstreamBlocks = [];
5403
- for (const d of depsRaw) {
5404
- if (typeof d !== "string") continue;
5405
- const ur = nodeResults[d];
5406
- if (ur && ur.status === "ok") {
5407
- upstreamBlocks.push(`[node ${d} output]
5408
- ${ur.output ?? ""}`);
5409
- } else if (ur && ur.status === "failed") {
5410
- upstreamBlocks.push(`[node ${d}] [MISSING: failed \u2014 ${ur.error ?? ""}]`);
5411
- }
5412
- }
5413
- const ctxBlock = upstreamBlocks.length > 0 ? upstreamBlocks.join("\n\n") : "(no upstream nodes)";
5414
- const sysMsg = "You are a worker LLM in an orchestrated DAG. Complete the assigned task using outputs from upstream nodes when relevant. Be concise.";
5415
- const role = typeof node["role"] === "string" ? node["role"] : "worker";
5416
- const task = String(node["task"] ?? "");
5417
- const difficulty = String(node["difficulty"] ?? "med");
5418
- const msgs = [
5419
- { role: "system", content: sysMsg },
5420
- {
5421
- role: "user",
5422
- content: `ROLE: ${role}
5423
- DIFFICULTY: ${difficulty}
5424
-
5425
- UPSTREAM:
5426
- ${ctxBlock}
5427
-
5428
- TASK:
5429
- ${task}`
5430
- }
5431
- ];
5432
- const started = import_node_perf_hooks2.performance.now();
5433
- const ans = await askOne(chosen, msgs, {
5434
- maxTokens,
5435
- temperature: 0.4,
5436
- purpose: "worker"
5437
- });
5438
- const wallMs = Math.trunc(import_node_perf_hooks2.performance.now() - started);
5439
5669
  if (ans.error !== void 0) {
5440
5670
  nodeResults[nid] = {
5441
5671
  id: nid,
@@ -5445,7 +5675,8 @@ ${task}`
5445
5675
  error: ans.error,
5446
5676
  wall_ms: wallMs,
5447
5677
  cpu_ms: ans.cpu_ms,
5448
- ...cheapReason ? { cheap_fallback_reason: cheapReason } : {}
5678
+ ...cheapReason ? { cheap_fallback_reason: cheapReason } : {},
5679
+ ...retryAttempts.length > 0 ? { retry_attempts: retryAttempts } : {}
5449
5680
  };
5450
5681
  failedIds.add(nid);
5451
5682
  continue;
@@ -5458,7 +5689,8 @@ ${task}`
5458
5689
  output: ans.response ?? "",
5459
5690
  wall_ms: wallMs,
5460
5691
  cpu_ms: ans.cpu_ms,
5461
- ...cheapReason ? { cheap_fallback_reason: cheapReason } : {}
5692
+ ...cheapReason ? { cheap_fallback_reason: cheapReason } : {},
5693
+ ...retryAttempts.length > 0 ? { retry_attempts: retryAttempts } : {}
5462
5694
  };
5463
5695
  }
5464
5696
  const missing = Object.keys(nodesById).filter(
@@ -6084,6 +6316,16 @@ function errorPayload(code, message, hint, kind = "client") {
6084
6316
  };
6085
6317
  }
6086
6318
 
6319
+ // src/core/call-context.ts
6320
+ function buildCallContext(args) {
6321
+ const ctx = {
6322
+ cheap_mode: args.cheapMode,
6323
+ session_id: args.sessionId
6324
+ };
6325
+ if (args.purpose) ctx.purpose = args.purpose;
6326
+ return ctx;
6327
+ }
6328
+
6087
6329
  // src/tools/create.ts
6088
6330
  var CREATE_DOC_MAX_BYTES = 32 * 1024;
6089
6331
  var CREATE_AUDIT_THRESHOLD = 0.7;
@@ -6112,6 +6354,10 @@ async function runCreate(args, opts) {
6112
6354
  const dryRun = boolArg3(args["dry_run"], false);
6113
6355
  const planOnly = boolArg3(args["plan_only"], false);
6114
6356
  const moderator = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
6357
+ const ctx = opts.ctx ?? buildCallContext({
6358
+ cheapMode,
6359
+ sessionId
6360
+ });
6115
6361
  const descriptors = await ingestDocuments(documents, sessionId, opts);
6116
6362
  const documentsPayload = formatDocumentsPayload(descriptors);
6117
6363
  const okCount = descriptors.filter((d) => d.status === "ok").length;
@@ -6129,7 +6375,8 @@ ${documentsPayload}`;
6129
6375
  }, {
6130
6376
  providers: opts.providers,
6131
6377
  allowlist: opts.allowlist ?? null,
6132
- ...opts.bridge ? { bridge: opts.bridge } : {}
6378
+ ...opts.bridge ? { bridge: opts.bridge } : {},
6379
+ ctx
6133
6380
  });
6134
6381
  const scopeAnswer = Array.isArray(scope["answers"]) ? scope["answers"].filter((a) => typeof a["response"] === "string" && a["response"]).map((a) => `[${a["provider"]}]
6135
6382
  ${a["response"]}`).join("\n\n") : "";
@@ -6151,7 +6398,8 @@ ${scopeAnswer || "(none)"}
6151
6398
  providers: opts.providers,
6152
6399
  allowlist: opts.allowlist ?? null,
6153
6400
  moderator,
6154
- ...opts.bridge ? { bridge: opts.bridge } : {}
6401
+ ...opts.bridge ? { bridge: opts.bridge } : {},
6402
+ ctx
6155
6403
  });
6156
6404
  let attempts = 1;
6157
6405
  if (planOnly) {
@@ -6185,7 +6433,8 @@ ${scopeAnswer || "(none)"}
6185
6433
  }, {
6186
6434
  providers: opts.providers,
6187
6435
  allowlist: opts.allowlist ?? null,
6188
- ...opts.bridge ? { bridge: opts.bridge } : {}
6436
+ ...opts.bridge ? { bridge: opts.bridge } : {},
6437
+ ctx
6189
6438
  });
6190
6439
  }
6191
6440
  let auditEnvelope = null;
@@ -6207,7 +6456,8 @@ ${scopeAnswer || "(none)"}
6207
6456
  }, {
6208
6457
  providers: opts.providers,
6209
6458
  allowlist: opts.allowlist ?? null,
6210
- ...opts.bridge ? { bridge: opts.bridge } : {}
6459
+ ...opts.bridge ? { bridge: opts.bridge } : {},
6460
+ ctx
6211
6461
  });
6212
6462
  let overallF = toNumberOrNull(auditEnvelope["overall_score"]);
6213
6463
  if (overallF !== null && overallF < auditThreshold && !cheapMode && !boolArg3(args["_no_retry"], false)) {
@@ -6231,7 +6481,8 @@ ${documentsPayload}`
6231
6481
  providers: opts.providers,
6232
6482
  allowlist: opts.allowlist ?? null,
6233
6483
  moderator,
6234
- ...opts.bridge ? { bridge: opts.bridge } : {}
6484
+ ...opts.bridge ? { bridge: opts.bridge } : {},
6485
+ ctx
6235
6486
  }
6236
6487
  );
6237
6488
  orchestration = retryOrchestration;
@@ -6250,7 +6501,8 @@ ${documentsPayload}`
6250
6501
  }, {
6251
6502
  providers: opts.providers,
6252
6503
  allowlist: opts.allowlist ?? null,
6253
- ...opts.bridge ? { bridge: opts.bridge } : {}
6504
+ ...opts.bridge ? { bridge: opts.bridge } : {},
6505
+ ctx
6254
6506
  });
6255
6507
  overallF = toNumberOrNull(auditEnvelope["overall_score"]);
6256
6508
  } else {
@@ -8101,7 +8353,7 @@ CRITERIA:
8101
8353
  ${criteriaBlock}`
8102
8354
  }
8103
8355
  ];
8104
- const maxTokens = opts.maxTokens ?? 2048;
8356
+ const maxTokens = opts.maxTokens ?? 4096;
8105
8357
  const scoresByProvider = {};
8106
8358
  const answersCollected = [];
8107
8359
  const scoringErrors = {};
@@ -8297,9 +8549,23 @@ function toArray(v) {
8297
8549
  }
8298
8550
 
8299
8551
  // src/tools/plan.ts
8552
+ var PLAN_MODE_PRESETS = {
8553
+ fast: { max_rounds: 2, early_stop: true, early_stop_threshold: 0.7 },
8554
+ thorough: { max_rounds: 5, early_stop: false, early_stop_threshold: 0.7 }
8555
+ };
8556
+ var DEFAULT_PLAN_MODE = "fast";
8300
8557
  async function runPlan(args, opts) {
8301
8558
  const goal = typeof args["goal"] === "string" ? args["goal"] : String(args["goal"] ?? "");
8302
8559
  const constraints = typeof args["constraints"] === "string" ? args["constraints"] : "";
8560
+ const modeArg = args["mode"];
8561
+ const mode = modeArg === "fast" || modeArg === "thorough" ? modeArg : DEFAULT_PLAN_MODE;
8562
+ const preset = PLAN_MODE_PRESETS[mode];
8563
+ const maxRoundsRaw = args["max_rounds"];
8564
+ const earlyStopRaw = args["early_stop"];
8565
+ const earlyStopThresholdRaw = args["early_stop_threshold"];
8566
+ const maxRounds = typeof maxRoundsRaw === "number" && Number.isFinite(maxRoundsRaw) && maxRoundsRaw > 0 ? Math.trunc(maxRoundsRaw) : preset.max_rounds;
8567
+ const earlyStop = typeof earlyStopRaw === "boolean" ? earlyStopRaw : preset.early_stop;
8568
+ const earlyStopThreshold = typeof earlyStopThresholdRaw === "number" && Number.isFinite(earlyStopThresholdRaw) ? earlyStopThresholdRaw : preset.early_stop_threshold;
8303
8569
  const merged = `We need a step-by-step plan to achieve this goal.
8304
8570
 
8305
8571
  GOAL: ${goal}
@@ -8310,7 +8576,10 @@ Return: (1) the plan as numbered steps, (2) risks, (3) alternatives considered.`
8310
8576
  const debateArgs = {
8311
8577
  topic: merged,
8312
8578
  context: typeof args["context"] === "string" ? args["context"] : "",
8313
- structured: Boolean(args["structured"])
8579
+ structured: Boolean(args["structured"]),
8580
+ max_rounds: maxRounds,
8581
+ early_stop: earlyStop,
8582
+ early_stop_threshold: earlyStopThreshold
8314
8583
  };
8315
8584
  if (args["providers"] !== void 0) debateArgs["providers"] = args["providers"];
8316
8585
  if (args["moderator"] !== void 0) debateArgs["moderator"] = args["moderator"];
@@ -10020,7 +10289,7 @@ function critiqueTool(providers, allowlist, bridge) {
10020
10289
  function planTool(providers, allowlist, bridge) {
10021
10290
  return {
10022
10291
  name: "plan",
10023
- description: "Have an LLM panel debate a step-by-step plan for the stated goal under the given constraints. Returns the debate envelope with a moderator-synthesised plan. Use `structured: true` (bridge) for schema-validated synthesis.",
10292
+ description: 'Have an LLM panel debate a step-by-step plan for the stated goal under the given constraints. Returns the debate envelope with a moderator-synthesised plan. Defaults to `mode: "fast"` (2 rounds + early_stop) \u2014 set `mode: "thorough"` for 5 rounds without early_stop. Explicit `max_rounds` / `early_stop` / `early_stop_threshold` always override the mode preset. Use `structured: true` for schema-validated synthesis.',
10024
10293
  inputSchema: {
10025
10294
  type: "object",
10026
10295
  additionalProperties: true,
@@ -10031,7 +10300,11 @@ function planTool(providers, allowlist, bridge) {
10031
10300
  providers: { type: "array", items: { type: "string" } },
10032
10301
  moderator: { type: "string" },
10033
10302
  session_id: { type: "string" },
10034
- structured: { type: "boolean" }
10303
+ structured: { type: "boolean" },
10304
+ mode: { type: "string", enum: ["fast", "thorough"] },
10305
+ max_rounds: { type: "integer", minimum: 1 },
10306
+ early_stop: { type: "boolean" },
10307
+ early_stop_threshold: { type: "number", minimum: 0, maximum: 1 }
10035
10308
  },
10036
10309
  required: ["goal"]
10037
10310
  },
@@ -10424,8 +10697,19 @@ async function main() {
10424
10697
  );
10425
10698
  }
10426
10699
  installShutdownHandlers(bridge);
10427
- const pricingPath = process.env["CROSSCHECK_PRICING_PATH"] ?? resolveRepoFile("config/pricing.json");
10428
- const pricing = pricingPath && (0, import_node_fs11.existsSync)(pricingPath) ? loadPricing(pricingPath) : {};
10700
+ const bundledPricing = (() => {
10701
+ try {
10702
+ return import_node_path11.default.join(import_node_path11.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl)), "pricing.json");
10703
+ } catch {
10704
+ return void 0;
10705
+ }
10706
+ })();
10707
+ const pricingPath = [
10708
+ process.env["CROSSCHECK_PRICING_PATH"],
10709
+ resolveRepoFile("config/pricing.json"),
10710
+ bundledPricing
10711
+ ].find((p) => p && (0, import_node_fs11.existsSync)(p));
10712
+ const pricing = pricingPath ? loadPricing(pricingPath) : {};
10429
10713
  const providers = buildProviders({ env: process.env, pricing });
10430
10714
  if (Object.keys(providers).length > 0) {
10431
10715
  process.stderr.write(