crosscheck-mcp 0.2.20 → 0.2.22

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.
@@ -1200,7 +1200,27 @@ var PROVIDER_CAPS = {
1200
1200
  // temperature, so the prefix is the fix.
1201
1201
  reasoning_prefixes: ["gpt-5", "gpt-6", "o1", "o3", "o4"]
1202
1202
  },
1203
- xai: { family: "openai_chat", system_role: "inline", supports_temperature: true },
1203
+ xai: {
1204
+ family: "openai_chat",
1205
+ system_role: "inline",
1206
+ // Kept `true`, unlike other reasoning models: grok-4 accepts temperature
1207
+ // (verified 200 at 0.4 and 1.0) and a panel wants the variance. Dropping
1208
+ // it would buy nothing and cost diversity.
1209
+ supports_temperature: true,
1210
+ // grok-4 IS reasoning-class — it emitted 1,432 reasoning tokens against 64
1211
+ // visible on one call. Saying so strips the "think step by step" preambles
1212
+ // it does not need, and lifts it off the non-reasoning ceiling (1500) onto
1213
+ // the reasoning-safe one (2048).
1214
+ //
1215
+ // NOT the reason grok answers short, which was the initial guess and was
1216
+ // wrong: at caps of 1500, 2048 and 6144 it returned 445, 404 and 461
1217
+ // visible tokens, finish_reason=stop every time. The cap was never
1218
+ // binding; grok is simply terse. The ceiling change removes a latent
1219
+ // constraint, it does not make grok say more.
1220
+ reasoning_prefixes: ["grok-4"],
1221
+ // Measured: xAI's cap bounds visible output only. See the field docs.
1222
+ reasoning_shares_output_budget: false
1223
+ },
1204
1224
  mistral: { family: "openai_chat", system_role: "inline", supports_temperature: true },
1205
1225
  groq: { family: "openai_chat", system_role: "inline", supports_temperature: true },
1206
1226
  deepseek: { family: "openai_chat", system_role: "inline", supports_temperature: true },
@@ -1239,6 +1259,10 @@ function supportsTemperature(provider, model) {
1239
1259
  if (caps.supports_temperature === "model") return !isReasoningModel(provider, model);
1240
1260
  return Boolean(caps.supports_temperature);
1241
1261
  }
1262
+ function reasoningSharesOutputBudget(provider) {
1263
+ const caps = PROVIDER_CAPS[provider.toLowerCase()];
1264
+ return caps?.reasoning_shares_output_budget ?? true;
1265
+ }
1242
1266
 
1243
1267
  // src/providers/types.ts
1244
1268
  init_cjs_shims();
@@ -1254,12 +1278,20 @@ var ProviderError = class extends Error {
1254
1278
  * actually fix. Distinct from `transient`: this is never worth
1255
1279
  * retrying the SAME model, but IS worth trying a different one. */
1256
1280
  modelAccessFailure;
1281
+ /** True for "the key works, the account is out of credit / over a spend
1282
+ * limit." Rides alongside kind "auth" rather than replacing it, because
1283
+ * every retry and fallback decision downstream is already tuned to that
1284
+ * kind — but the two are completely different things to a human, and
1285
+ * reporting "API key rejected" for an unpaid invoice sends people to
1286
+ * regenerate a key that was never the problem. */
1287
+ billing;
1257
1288
  constructor(kind, message, opts) {
1258
1289
  super(message);
1259
1290
  this.kind = kind;
1260
1291
  this.status = opts?.status;
1261
1292
  this.transient = opts?.transient ?? defaultTransient(kind);
1262
1293
  this.modelAccessFailure = opts?.modelAccessFailure ?? false;
1294
+ this.billing = opts?.billing ?? false;
1263
1295
  if (opts?.retryAfterS !== void 0) {
1264
1296
  this.retryAfterS = opts.retryAfterS;
1265
1297
  }
@@ -1298,7 +1330,7 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
1298
1330
  return new ProviderError(
1299
1331
  "auth",
1300
1332
  `${provider}: out of credits or billing isn't set up (HTTP ${status}). Your API key is reaching ${provider}, but the account has no usable balance \u2014 add credits / enable billing in your ${provider} console, then retry. Detail: ${detail}`,
1301
- { status }
1333
+ { status, billing: true }
1302
1334
  );
1303
1335
  }
1304
1336
  if (status === 401 || status === 403) {
@@ -1483,6 +1515,23 @@ async function acquireRateLimit(provider, deps) {
1483
1515
  var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
1484
1516
  var ANTHROPIC_VERSION_HEADER = "2023-06-01";
1485
1517
  var ANTHROPIC_STRUCTURED_TOOL_NAME = "structured_output";
1518
+ function applyPromptCaching(body, env = process.env) {
1519
+ const flag = (env["CROSSCHECK_ANTHROPIC_PROMPT_CACHE"] ?? "").trim();
1520
+ if (flag === "0" || flag.toLowerCase() === "false") return;
1521
+ if (typeof body.system === "string" && body.system !== "") {
1522
+ body.system = [{ type: "text", text: body.system, cache_control: { type: "ephemeral" } }];
1523
+ }
1524
+ if (body.messages.length >= 2) {
1525
+ const idx = body.messages.length - 2;
1526
+ const m = body.messages[idx];
1527
+ if (typeof m.content === "string" && m.content !== "") {
1528
+ body.messages[idx] = {
1529
+ role: m.role,
1530
+ content: [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }]
1531
+ };
1532
+ }
1533
+ }
1534
+ }
1486
1535
  function buildAnthropicRequest(opts) {
1487
1536
  let system;
1488
1537
  const convo = [];
@@ -1507,6 +1556,7 @@ function buildAnthropicRequest(opts) {
1507
1556
  if (system !== void 0) {
1508
1557
  body.system = system;
1509
1558
  }
1559
+ applyPromptCaching(body);
1510
1560
  if (isReasoningModel("anthropic", opts.model)) {
1511
1561
  if (opts.model.toLowerCase().startsWith("claude-fable-5")) {
1512
1562
  body.thinking = { type: "adaptive" };
@@ -1566,6 +1616,7 @@ function parseAnthropicResponse(opts) {
1566
1616
  const prompt = Math.trunc(Number(u["input_tokens"] ?? 0)) || 0;
1567
1617
  const cached = Math.trunc(Number(u["cache_read_input_tokens"] ?? 0)) || 0;
1568
1618
  const completion = Math.trunc(Number(u["output_tokens"] ?? 0)) || 0;
1619
+ const cacheWrite = Math.trunc(Number(u["cache_creation_input_tokens"] ?? 0)) || 0;
1569
1620
  const usage = {
1570
1621
  provider: "anthropic",
1571
1622
  model: opts.model,
@@ -1573,7 +1624,7 @@ function parseAnthropicResponse(opts) {
1573
1624
  // production helper folds them in so prompt_tokens is the FULL
1574
1625
  // input volume; calculateCost then bills the cached subset at the
1575
1626
  // cached rate (cached <= prompt_tokens, since prompt = input+cached).
1576
- prompt_tokens: prompt + cached,
1627
+ prompt_tokens: prompt + cached + cacheWrite,
1577
1628
  completion_tokens: completion,
1578
1629
  cached_tokens: cached,
1579
1630
  total_tokens: 0,
@@ -1925,16 +1976,21 @@ function parseOpenAICompatibleResponse(opts) {
1925
1976
  const u = r["usage"] ?? {};
1926
1977
  const details = u["prompt_tokens_details"] ?? {};
1927
1978
  const cached = Math.trunc(Number(details["cached_tokens"] ?? 0)) || 0;
1979
+ const promptTokens = Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0;
1980
+ const reportedCompletion = Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0;
1981
+ const reportedTotal = Math.trunc(Number(u["total_tokens"] ?? 0)) || 0;
1982
+ const derivedCompletion = reportedTotal > promptTokens ? reportedTotal - promptTokens : reportedCompletion;
1983
+ const completionTokens = Math.max(reportedCompletion, derivedCompletion);
1928
1984
  const usage = {
1929
1985
  provider: opts.provider,
1930
1986
  model: opts.model,
1931
- prompt_tokens: Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0,
1932
- completion_tokens: Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0,
1987
+ prompt_tokens: promptTokens,
1988
+ completion_tokens: completionTokens,
1933
1989
  cached_tokens: cached,
1934
1990
  // Use the response's reported total_tokens directly. Python's
1935
1991
  // `Usage.to_dict()` falls back to prompt+completion only when
1936
1992
  // total_tokens is 0/missing; mirror that.
1937
- total_tokens: Math.trunc(Number(u["total_tokens"] ?? 0)) || 0,
1993
+ total_tokens: reportedTotal,
1938
1994
  cost_usd: 0,
1939
1995
  estimated: Object.keys(u).length === 0,
1940
1996
  purpose: opts.purpose
@@ -1976,7 +2032,11 @@ async function sendOpenAICompatible(args) {
1976
2032
  body.reasoning_effort = effort;
1977
2033
  }
1978
2034
  }
1979
- if (isReasoningModel(args.provider, args.model) && typeof body.max_completion_tokens === "number") {
2035
+ if (isReasoningModel(args.provider, args.model) && // Only where the cap is a SHARED budget. On a provider that bounds visible
2036
+ // output only (xAI), headroom cannot protect the answer from being crowded
2037
+ // out — nothing is crowding it — so it would merely authorise a
2038
+ // 25,000-token reply and the bill that comes with it.
2039
+ reasoningSharesOutputBudget(args.provider) && typeof body.max_completion_tokens === "number") {
1980
2040
  const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1981
2041
  const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1982
2042
  body.max_completion_tokens += headroom;
@@ -2292,7 +2352,7 @@ var import_zod = require("zod");
2292
2352
  // src/server-meta.ts
2293
2353
  init_cjs_shims();
2294
2354
  var SERVER_NAME = "crosscheck-agent";
2295
- var SERVER_VERSION = true ? "0.2.20" : "0.0.0-dev";
2355
+ var SERVER_VERSION = true ? "0.2.22" : "0.0.0-dev";
2296
2356
 
2297
2357
  // src/tools/audit.ts
2298
2358
  init_cjs_shims();
@@ -2805,7 +2865,9 @@ async function attachUsageBlock(result, answers, opts) {
2805
2865
  purpose: a.usage?.purpose ?? null,
2806
2866
  wall_ms: a.elapsed_ms ?? 0,
2807
2867
  cpu_ms: a.cpu_ms ?? 0,
2808
- cache_hit: Boolean(a.cache_hit)
2868
+ cache_hit: Boolean(a.cache_hit),
2869
+ ...a.error_kind ? { error_kind: a.error_kind } : {},
2870
+ ...a.error_billing ? { error_billing: true } : {}
2809
2871
  }))
2810
2872
  };
2811
2873
  result["timing"] = timing;
@@ -3114,11 +3176,13 @@ async function askOne(provider, messages, opts) {
3114
3176
  const cpuMs = Math.trunc((cpu.user + cpu.system) / 1e3);
3115
3177
  const kind = e instanceof ProviderError ? e.kind : "other";
3116
3178
  const msg = e instanceof Error ? e.message : String(e);
3179
+ const billing = e instanceof ProviderError && e.billing;
3117
3180
  return {
3118
3181
  provider: provider.name,
3119
3182
  model: provider.model,
3120
3183
  error: msg,
3121
3184
  error_kind: kind,
3185
+ ...billing ? { error_billing: true } : {},
3122
3186
  attempts: 0,
3123
3187
  usage: emptyUsage(provider.name, provider.model, opts.purpose),
3124
3188
  cache_hit: false,
@@ -12780,7 +12844,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12780
12844
  var DEFAULT_PACKAGE = "crosscheck-cli";
12781
12845
  var FETCH_TIMEOUT_MS = 3e3;
12782
12846
  function engineVersion() {
12783
- return true ? "0.2.20" : "0.0.0-dev";
12847
+ return true ? "0.2.22" : "0.0.0-dev";
12784
12848
  }
12785
12849
  function defaultUpdateCachePath() {
12786
12850
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path13.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
@@ -14795,6 +14859,23 @@ function getConfig2() {
14795
14859
  config = { url, seatId, orgId, seatKey };
14796
14860
  return config;
14797
14861
  }
14862
+ function errorClassFor(kind) {
14863
+ switch (kind) {
14864
+ case "auth":
14865
+ return "auth";
14866
+ case "rate_limit":
14867
+ return "rate_limit";
14868
+ case "timeout":
14869
+ return "timeout";
14870
+ case "server":
14871
+ return "server";
14872
+ // network/parse/client/other all mean "we don't know that the key is
14873
+ // bad", and guessing wrong here is what produces a dashboard that cries
14874
+ // wolf. Say unknown.
14875
+ default:
14876
+ return "unknown";
14877
+ }
14878
+ }
14798
14879
  function intNonNeg(v) {
14799
14880
  const n = Math.trunc(Number(v));
14800
14881
  return Number.isFinite(n) && n > 0 ? n : 0;
@@ -14802,12 +14883,12 @@ function intNonNeg(v) {
14802
14883
  function usageEventsFromEnvelope(out, toolName) {
14803
14884
  if (!out || typeof out !== "object" || Array.isArray(out)) return [];
14804
14885
  const usage = out["usage"];
14805
- if (!usage || typeof usage !== "object") return [];
14806
- const byCall = usage["by_call"];
14807
- if (!Array.isArray(byCall) || byCall.length === 0) return [];
14886
+ const rawByCall = usage && typeof usage === "object" ? usage["by_call"] : void 0;
14887
+ const byCall = Array.isArray(rawByCall) ? rawByCall : [];
14808
14888
  const timing = out["timing"];
14809
14889
  const timingByCall = timing && typeof timing === "object" ? timing["by_call"] : void 0;
14810
- const latencies = Array.isArray(timingByCall) ? timingByCall : [];
14890
+ const allTiming = Array.isArray(timingByCall) ? timingByCall : [];
14891
+ const latencies = allTiming;
14811
14892
  const aligned = latencies.length === byCall.length;
14812
14893
  const pattern = toolToPattern(toolName);
14813
14894
  const events = [];
@@ -14817,13 +14898,18 @@ function usageEventsFromEnvelope(out, toolName) {
14817
14898
  if (!provider) continue;
14818
14899
  const model = String(u.model ?? "").slice(0, 128) || "unknown";
14819
14900
  const cost = Math.max(0, Number(u.cost_usd) || 0);
14820
- const latencyMs = aligned ? intNonNeg(latencies[i]?.["wall_ms"]) : 0;
14901
+ const timingRow = aligned ? latencies[i] : void 0;
14902
+ if (timingRow?.["error_kind"]) continue;
14903
+ const promptTokens = intNonNeg(u.prompt_tokens);
14904
+ const completionTokens = intNonNeg(u.completion_tokens);
14905
+ if (promptTokens === 0 && completionTokens === 0) continue;
14906
+ const latencyMs = intNonNeg(timingRow?.["wall_ms"]);
14821
14907
  events.push({
14822
14908
  provider,
14823
14909
  model,
14824
14910
  pattern,
14825
- promptTokens: intNonNeg(u.prompt_tokens),
14826
- completionTokens: intNonNeg(u.completion_tokens),
14911
+ promptTokens,
14912
+ completionTokens,
14827
14913
  costUsdEstimate: cost,
14828
14914
  latencyMs,
14829
14915
  status: "ok",
@@ -14832,6 +14918,28 @@ function usageEventsFromEnvelope(out, toolName) {
14832
14918
  purpose: normalizePurpose(u.purpose)
14833
14919
  });
14834
14920
  }
14921
+ for (const t of allTiming) {
14922
+ const row = t;
14923
+ const kind = row["error_kind"];
14924
+ if (!kind) continue;
14925
+ const provider = providerToSchema(String(row["provider"] ?? ""));
14926
+ if (!provider) continue;
14927
+ events.push({
14928
+ provider,
14929
+ model: String(row["model"] ?? "").slice(0, 128) || "unknown",
14930
+ pattern,
14931
+ // A failure consumed no tokens and cost nothing. Recording it with a
14932
+ // nonzero cost would corrupt the per-feature ledger with money nobody
14933
+ // spent.
14934
+ promptTokens: 0,
14935
+ completionTokens: 0,
14936
+ costUsdEstimate: 0,
14937
+ latencyMs: intNonNeg(row["wall_ms"]),
14938
+ status: "error",
14939
+ errorClass: row["error_billing"] ? "auth" : errorClassFor(String(kind)),
14940
+ purpose: normalizePurpose(row["purpose"])
14941
+ });
14942
+ }
14835
14943
  return events;
14836
14944
  }
14837
14945
  var queue = [];
@@ -14858,6 +14966,10 @@ function enqueue(cfg2, input, runId, fingerprint) {
14858
14966
  costUsdEstimate: input.costUsdEstimate,
14859
14967
  latencyMs: input.latencyMs,
14860
14968
  status: input.status,
14969
+ // Only present on failures. The canonical body renders an absent
14970
+ // errorClass as "", so omitting and sending "" sign identically — but
14971
+ // omitting keeps the JSON honest about what we actually know.
14972
+ ...input.errorClass === void 0 ? {} : { errorClass: input.errorClass },
14861
14973
  runId,
14862
14974
  // Omit entirely (not "") when unknown, so the canonical body stays
14863
14975
  // consistent with what the server reconstructs from the JSON.