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.
@@ -446,7 +446,27 @@ var PROVIDER_CAPS = {
446
446
  // temperature, so the prefix is the fix.
447
447
  reasoning_prefixes: ["gpt-5", "gpt-6", "o1", "o3", "o4"]
448
448
  },
449
- xai: { family: "openai_chat", system_role: "inline", supports_temperature: true },
449
+ xai: {
450
+ family: "openai_chat",
451
+ system_role: "inline",
452
+ // Kept `true`, unlike other reasoning models: grok-4 accepts temperature
453
+ // (verified 200 at 0.4 and 1.0) and a panel wants the variance. Dropping
454
+ // it would buy nothing and cost diversity.
455
+ supports_temperature: true,
456
+ // grok-4 IS reasoning-class — it emitted 1,432 reasoning tokens against 64
457
+ // visible on one call. Saying so strips the "think step by step" preambles
458
+ // it does not need, and lifts it off the non-reasoning ceiling (1500) onto
459
+ // the reasoning-safe one (2048).
460
+ //
461
+ // NOT the reason grok answers short, which was the initial guess and was
462
+ // wrong: at caps of 1500, 2048 and 6144 it returned 445, 404 and 461
463
+ // visible tokens, finish_reason=stop every time. The cap was never
464
+ // binding; grok is simply terse. The ceiling change removes a latent
465
+ // constraint, it does not make grok say more.
466
+ reasoning_prefixes: ["grok-4"],
467
+ // Measured: xAI's cap bounds visible output only. See the field docs.
468
+ reasoning_shares_output_budget: false
469
+ },
450
470
  mistral: { family: "openai_chat", system_role: "inline", supports_temperature: true },
451
471
  groq: { family: "openai_chat", system_role: "inline", supports_temperature: true },
452
472
  deepseek: { family: "openai_chat", system_role: "inline", supports_temperature: true },
@@ -485,6 +505,10 @@ function supportsTemperature(provider, model) {
485
505
  if (caps.supports_temperature === "model") return !isReasoningModel(provider, model);
486
506
  return Boolean(caps.supports_temperature);
487
507
  }
508
+ function reasoningSharesOutputBudget(provider) {
509
+ const caps = PROVIDER_CAPS[provider.toLowerCase()];
510
+ return caps?.reasoning_shares_output_budget ?? true;
511
+ }
488
512
 
489
513
  // src/providers/types.ts
490
514
  var ProviderError = class extends Error {
@@ -499,12 +523,20 @@ var ProviderError = class extends Error {
499
523
  * actually fix. Distinct from `transient`: this is never worth
500
524
  * retrying the SAME model, but IS worth trying a different one. */
501
525
  modelAccessFailure;
526
+ /** True for "the key works, the account is out of credit / over a spend
527
+ * limit." Rides alongside kind "auth" rather than replacing it, because
528
+ * every retry and fallback decision downstream is already tuned to that
529
+ * kind — but the two are completely different things to a human, and
530
+ * reporting "API key rejected" for an unpaid invoice sends people to
531
+ * regenerate a key that was never the problem. */
532
+ billing;
502
533
  constructor(kind, message, opts) {
503
534
  super(message);
504
535
  this.kind = kind;
505
536
  this.status = opts?.status;
506
537
  this.transient = opts?.transient ?? defaultTransient(kind);
507
538
  this.modelAccessFailure = opts?.modelAccessFailure ?? false;
539
+ this.billing = opts?.billing ?? false;
508
540
  if (opts?.retryAfterS !== void 0) {
509
541
  this.retryAfterS = opts.retryAfterS;
510
542
  }
@@ -542,7 +574,7 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
542
574
  return new ProviderError(
543
575
  "auth",
544
576
  `${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}`,
545
- { status }
577
+ { status, billing: true }
546
578
  );
547
579
  }
548
580
  if (status === 401 || status === 403) {
@@ -718,6 +750,23 @@ async function acquireRateLimit(provider, deps) {
718
750
  var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
719
751
  var ANTHROPIC_VERSION_HEADER = "2023-06-01";
720
752
  var ANTHROPIC_STRUCTURED_TOOL_NAME = "structured_output";
753
+ function applyPromptCaching(body, env = process.env) {
754
+ const flag = (env["CROSSCHECK_ANTHROPIC_PROMPT_CACHE"] ?? "").trim();
755
+ if (flag === "0" || flag.toLowerCase() === "false") return;
756
+ if (typeof body.system === "string" && body.system !== "") {
757
+ body.system = [{ type: "text", text: body.system, cache_control: { type: "ephemeral" } }];
758
+ }
759
+ if (body.messages.length >= 2) {
760
+ const idx = body.messages.length - 2;
761
+ const m = body.messages[idx];
762
+ if (typeof m.content === "string" && m.content !== "") {
763
+ body.messages[idx] = {
764
+ role: m.role,
765
+ content: [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }]
766
+ };
767
+ }
768
+ }
769
+ }
721
770
  function buildAnthropicRequest(opts) {
722
771
  let system;
723
772
  const convo = [];
@@ -742,6 +791,7 @@ function buildAnthropicRequest(opts) {
742
791
  if (system !== void 0) {
743
792
  body.system = system;
744
793
  }
794
+ applyPromptCaching(body);
745
795
  if (isReasoningModel("anthropic", opts.model)) {
746
796
  if (opts.model.toLowerCase().startsWith("claude-fable-5")) {
747
797
  body.thinking = { type: "adaptive" };
@@ -801,6 +851,7 @@ function parseAnthropicResponse(opts) {
801
851
  const prompt = Math.trunc(Number(u["input_tokens"] ?? 0)) || 0;
802
852
  const cached = Math.trunc(Number(u["cache_read_input_tokens"] ?? 0)) || 0;
803
853
  const completion = Math.trunc(Number(u["output_tokens"] ?? 0)) || 0;
854
+ const cacheWrite = Math.trunc(Number(u["cache_creation_input_tokens"] ?? 0)) || 0;
804
855
  const usage = {
805
856
  provider: "anthropic",
806
857
  model: opts.model,
@@ -808,7 +859,7 @@ function parseAnthropicResponse(opts) {
808
859
  // production helper folds them in so prompt_tokens is the FULL
809
860
  // input volume; calculateCost then bills the cached subset at the
810
861
  // cached rate (cached <= prompt_tokens, since prompt = input+cached).
811
- prompt_tokens: prompt + cached,
862
+ prompt_tokens: prompt + cached + cacheWrite,
812
863
  completion_tokens: completion,
813
864
  cached_tokens: cached,
814
865
  total_tokens: 0,
@@ -1158,16 +1209,21 @@ function parseOpenAICompatibleResponse(opts) {
1158
1209
  const u = r["usage"] ?? {};
1159
1210
  const details = u["prompt_tokens_details"] ?? {};
1160
1211
  const cached = Math.trunc(Number(details["cached_tokens"] ?? 0)) || 0;
1212
+ const promptTokens = Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0;
1213
+ const reportedCompletion = Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0;
1214
+ const reportedTotal = Math.trunc(Number(u["total_tokens"] ?? 0)) || 0;
1215
+ const derivedCompletion = reportedTotal > promptTokens ? reportedTotal - promptTokens : reportedCompletion;
1216
+ const completionTokens = Math.max(reportedCompletion, derivedCompletion);
1161
1217
  const usage = {
1162
1218
  provider: opts.provider,
1163
1219
  model: opts.model,
1164
- prompt_tokens: Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0,
1165
- completion_tokens: Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0,
1220
+ prompt_tokens: promptTokens,
1221
+ completion_tokens: completionTokens,
1166
1222
  cached_tokens: cached,
1167
1223
  // Use the response's reported total_tokens directly. Python's
1168
1224
  // `Usage.to_dict()` falls back to prompt+completion only when
1169
1225
  // total_tokens is 0/missing; mirror that.
1170
- total_tokens: Math.trunc(Number(u["total_tokens"] ?? 0)) || 0,
1226
+ total_tokens: reportedTotal,
1171
1227
  cost_usd: 0,
1172
1228
  estimated: Object.keys(u).length === 0,
1173
1229
  purpose: opts.purpose
@@ -1209,7 +1265,11 @@ async function sendOpenAICompatible(args) {
1209
1265
  body.reasoning_effort = effort;
1210
1266
  }
1211
1267
  }
1212
- if (isReasoningModel(args.provider, args.model) && typeof body.max_completion_tokens === "number") {
1268
+ if (isReasoningModel(args.provider, args.model) && // Only where the cap is a SHARED budget. On a provider that bounds visible
1269
+ // output only (xAI), headroom cannot protect the answer from being crowded
1270
+ // out — nothing is crowding it — so it would merely authorise a
1271
+ // 25,000-token reply and the bill that comes with it.
1272
+ reasoningSharesOutputBudget(args.provider) && typeof body.max_completion_tokens === "number") {
1213
1273
  const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1214
1274
  const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1215
1275
  body.max_completion_tokens += headroom;
@@ -1384,7 +1444,7 @@ var import_zod = require("zod");
1384
1444
 
1385
1445
  // src/server-meta.ts
1386
1446
  var SERVER_NAME = "crosscheck-agent";
1387
- var SERVER_VERSION = true ? "0.2.20" : "0.0.0-dev";
1447
+ var SERVER_VERSION = true ? "0.2.22" : "0.0.0-dev";
1388
1448
 
1389
1449
  // src/tools/audit.ts
1390
1450
  var import_node_fs4 = require("fs");
@@ -1890,7 +1950,9 @@ async function attachUsageBlock(result, answers, opts) {
1890
1950
  purpose: a.usage?.purpose ?? null,
1891
1951
  wall_ms: a.elapsed_ms ?? 0,
1892
1952
  cpu_ms: a.cpu_ms ?? 0,
1893
- cache_hit: Boolean(a.cache_hit)
1953
+ cache_hit: Boolean(a.cache_hit),
1954
+ ...a.error_kind ? { error_kind: a.error_kind } : {},
1955
+ ...a.error_billing ? { error_billing: true } : {}
1894
1956
  }))
1895
1957
  };
1896
1958
  result["timing"] = timing;
@@ -2191,11 +2253,13 @@ async function askOne(provider, messages, opts) {
2191
2253
  const cpuMs = Math.trunc((cpu.user + cpu.system) / 1e3);
2192
2254
  const kind = e instanceof ProviderError ? e.kind : "other";
2193
2255
  const msg = e instanceof Error ? e.message : String(e);
2256
+ const billing = e instanceof ProviderError && e.billing;
2194
2257
  return {
2195
2258
  provider: provider.name,
2196
2259
  model: provider.model,
2197
2260
  error: msg,
2198
2261
  error_kind: kind,
2262
+ ...billing ? { error_billing: true } : {},
2199
2263
  attempts: 0,
2200
2264
  usage: emptyUsage(provider.name, provider.model, opts.purpose),
2201
2265
  cache_hit: false,
@@ -11786,7 +11850,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11786
11850
  var DEFAULT_PACKAGE = "crosscheck-cli";
11787
11851
  var FETCH_TIMEOUT_MS = 3e3;
11788
11852
  function engineVersion() {
11789
- return true ? "0.2.20" : "0.0.0-dev";
11853
+ return true ? "0.2.22" : "0.0.0-dev";
11790
11854
  }
11791
11855
  function defaultUpdateCachePath() {
11792
11856
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path12.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");