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.
@@ -413,7 +413,27 @@ var PROVIDER_CAPS = {
413
413
  // temperature, so the prefix is the fix.
414
414
  reasoning_prefixes: ["gpt-5", "gpt-6", "o1", "o3", "o4"]
415
415
  },
416
- xai: { family: "openai_chat", system_role: "inline", supports_temperature: true },
416
+ xai: {
417
+ family: "openai_chat",
418
+ system_role: "inline",
419
+ // Kept `true`, unlike other reasoning models: grok-4 accepts temperature
420
+ // (verified 200 at 0.4 and 1.0) and a panel wants the variance. Dropping
421
+ // it would buy nothing and cost diversity.
422
+ supports_temperature: true,
423
+ // grok-4 IS reasoning-class — it emitted 1,432 reasoning tokens against 64
424
+ // visible on one call. Saying so strips the "think step by step" preambles
425
+ // it does not need, and lifts it off the non-reasoning ceiling (1500) onto
426
+ // the reasoning-safe one (2048).
427
+ //
428
+ // NOT the reason grok answers short, which was the initial guess and was
429
+ // wrong: at caps of 1500, 2048 and 6144 it returned 445, 404 and 461
430
+ // visible tokens, finish_reason=stop every time. The cap was never
431
+ // binding; grok is simply terse. The ceiling change removes a latent
432
+ // constraint, it does not make grok say more.
433
+ reasoning_prefixes: ["grok-4"],
434
+ // Measured: xAI's cap bounds visible output only. See the field docs.
435
+ reasoning_shares_output_budget: false
436
+ },
417
437
  mistral: { family: "openai_chat", system_role: "inline", supports_temperature: true },
418
438
  groq: { family: "openai_chat", system_role: "inline", supports_temperature: true },
419
439
  deepseek: { family: "openai_chat", system_role: "inline", supports_temperature: true },
@@ -452,6 +472,10 @@ function supportsTemperature(provider, model) {
452
472
  if (caps.supports_temperature === "model") return !isReasoningModel(provider, model);
453
473
  return Boolean(caps.supports_temperature);
454
474
  }
475
+ function reasoningSharesOutputBudget(provider) {
476
+ const caps = PROVIDER_CAPS[provider.toLowerCase()];
477
+ return caps?.reasoning_shares_output_budget ?? true;
478
+ }
455
479
 
456
480
  // src/providers/types.ts
457
481
  var ProviderError = class extends Error {
@@ -466,12 +490,20 @@ var ProviderError = class extends Error {
466
490
  * actually fix. Distinct from `transient`: this is never worth
467
491
  * retrying the SAME model, but IS worth trying a different one. */
468
492
  modelAccessFailure;
493
+ /** True for "the key works, the account is out of credit / over a spend
494
+ * limit." Rides alongside kind "auth" rather than replacing it, because
495
+ * every retry and fallback decision downstream is already tuned to that
496
+ * kind — but the two are completely different things to a human, and
497
+ * reporting "API key rejected" for an unpaid invoice sends people to
498
+ * regenerate a key that was never the problem. */
499
+ billing;
469
500
  constructor(kind, message, opts) {
470
501
  super(message);
471
502
  this.kind = kind;
472
503
  this.status = opts?.status;
473
504
  this.transient = opts?.transient ?? defaultTransient(kind);
474
505
  this.modelAccessFailure = opts?.modelAccessFailure ?? false;
506
+ this.billing = opts?.billing ?? false;
475
507
  if (opts?.retryAfterS !== void 0) {
476
508
  this.retryAfterS = opts.retryAfterS;
477
509
  }
@@ -509,7 +541,7 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
509
541
  return new ProviderError(
510
542
  "auth",
511
543
  `${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}`,
512
- { status }
544
+ { status, billing: true }
513
545
  );
514
546
  }
515
547
  if (status === 401 || status === 403) {
@@ -685,6 +717,23 @@ async function acquireRateLimit(provider, deps) {
685
717
  var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
686
718
  var ANTHROPIC_VERSION_HEADER = "2023-06-01";
687
719
  var ANTHROPIC_STRUCTURED_TOOL_NAME = "structured_output";
720
+ function applyPromptCaching(body, env = process.env) {
721
+ const flag = (env["CROSSCHECK_ANTHROPIC_PROMPT_CACHE"] ?? "").trim();
722
+ if (flag === "0" || flag.toLowerCase() === "false") return;
723
+ if (typeof body.system === "string" && body.system !== "") {
724
+ body.system = [{ type: "text", text: body.system, cache_control: { type: "ephemeral" } }];
725
+ }
726
+ if (body.messages.length >= 2) {
727
+ const idx = body.messages.length - 2;
728
+ const m = body.messages[idx];
729
+ if (typeof m.content === "string" && m.content !== "") {
730
+ body.messages[idx] = {
731
+ role: m.role,
732
+ content: [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }]
733
+ };
734
+ }
735
+ }
736
+ }
688
737
  function buildAnthropicRequest(opts) {
689
738
  let system;
690
739
  const convo = [];
@@ -709,6 +758,7 @@ function buildAnthropicRequest(opts) {
709
758
  if (system !== void 0) {
710
759
  body.system = system;
711
760
  }
761
+ applyPromptCaching(body);
712
762
  if (isReasoningModel("anthropic", opts.model)) {
713
763
  if (opts.model.toLowerCase().startsWith("claude-fable-5")) {
714
764
  body.thinking = { type: "adaptive" };
@@ -768,6 +818,7 @@ function parseAnthropicResponse(opts) {
768
818
  const prompt = Math.trunc(Number(u["input_tokens"] ?? 0)) || 0;
769
819
  const cached = Math.trunc(Number(u["cache_read_input_tokens"] ?? 0)) || 0;
770
820
  const completion = Math.trunc(Number(u["output_tokens"] ?? 0)) || 0;
821
+ const cacheWrite = Math.trunc(Number(u["cache_creation_input_tokens"] ?? 0)) || 0;
771
822
  const usage = {
772
823
  provider: "anthropic",
773
824
  model: opts.model,
@@ -775,7 +826,7 @@ function parseAnthropicResponse(opts) {
775
826
  // production helper folds them in so prompt_tokens is the FULL
776
827
  // input volume; calculateCost then bills the cached subset at the
777
828
  // cached rate (cached <= prompt_tokens, since prompt = input+cached).
778
- prompt_tokens: prompt + cached,
829
+ prompt_tokens: prompt + cached + cacheWrite,
779
830
  completion_tokens: completion,
780
831
  cached_tokens: cached,
781
832
  total_tokens: 0,
@@ -1125,16 +1176,21 @@ function parseOpenAICompatibleResponse(opts) {
1125
1176
  const u = r["usage"] ?? {};
1126
1177
  const details = u["prompt_tokens_details"] ?? {};
1127
1178
  const cached = Math.trunc(Number(details["cached_tokens"] ?? 0)) || 0;
1179
+ const promptTokens = Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0;
1180
+ const reportedCompletion = Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0;
1181
+ const reportedTotal = Math.trunc(Number(u["total_tokens"] ?? 0)) || 0;
1182
+ const derivedCompletion = reportedTotal > promptTokens ? reportedTotal - promptTokens : reportedCompletion;
1183
+ const completionTokens = Math.max(reportedCompletion, derivedCompletion);
1128
1184
  const usage = {
1129
1185
  provider: opts.provider,
1130
1186
  model: opts.model,
1131
- prompt_tokens: Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0,
1132
- completion_tokens: Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0,
1187
+ prompt_tokens: promptTokens,
1188
+ completion_tokens: completionTokens,
1133
1189
  cached_tokens: cached,
1134
1190
  // Use the response's reported total_tokens directly. Python's
1135
1191
  // `Usage.to_dict()` falls back to prompt+completion only when
1136
1192
  // total_tokens is 0/missing; mirror that.
1137
- total_tokens: Math.trunc(Number(u["total_tokens"] ?? 0)) || 0,
1193
+ total_tokens: reportedTotal,
1138
1194
  cost_usd: 0,
1139
1195
  estimated: Object.keys(u).length === 0,
1140
1196
  purpose: opts.purpose
@@ -1176,7 +1232,11 @@ async function sendOpenAICompatible(args) {
1176
1232
  body.reasoning_effort = effort;
1177
1233
  }
1178
1234
  }
1179
- if (isReasoningModel(args.provider, args.model) && typeof body.max_completion_tokens === "number") {
1235
+ if (isReasoningModel(args.provider, args.model) && // Only where the cap is a SHARED budget. On a provider that bounds visible
1236
+ // output only (xAI), headroom cannot protect the answer from being crowded
1237
+ // out — nothing is crowding it — so it would merely authorise a
1238
+ // 25,000-token reply and the bill that comes with it.
1239
+ reasoningSharesOutputBudget(args.provider) && typeof body.max_completion_tokens === "number") {
1180
1240
  const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1181
1241
  const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1182
1242
  body.max_completion_tokens += headroom;
@@ -1351,7 +1411,7 @@ import { z } from "zod";
1351
1411
 
1352
1412
  // src/server-meta.ts
1353
1413
  var SERVER_NAME = "crosscheck-agent";
1354
- var SERVER_VERSION = true ? "0.2.20" : "0.0.0-dev";
1414
+ var SERVER_VERSION = true ? "0.2.22" : "0.0.0-dev";
1355
1415
 
1356
1416
  // src/tools/audit.ts
1357
1417
  import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
@@ -1857,7 +1917,9 @@ async function attachUsageBlock(result, answers, opts) {
1857
1917
  purpose: a.usage?.purpose ?? null,
1858
1918
  wall_ms: a.elapsed_ms ?? 0,
1859
1919
  cpu_ms: a.cpu_ms ?? 0,
1860
- cache_hit: Boolean(a.cache_hit)
1920
+ cache_hit: Boolean(a.cache_hit),
1921
+ ...a.error_kind ? { error_kind: a.error_kind } : {},
1922
+ ...a.error_billing ? { error_billing: true } : {}
1861
1923
  }))
1862
1924
  };
1863
1925
  result["timing"] = timing;
@@ -2158,11 +2220,13 @@ async function askOne(provider, messages, opts) {
2158
2220
  const cpuMs = Math.trunc((cpu.user + cpu.system) / 1e3);
2159
2221
  const kind = e instanceof ProviderError ? e.kind : "other";
2160
2222
  const msg = e instanceof Error ? e.message : String(e);
2223
+ const billing = e instanceof ProviderError && e.billing;
2161
2224
  return {
2162
2225
  provider: provider.name,
2163
2226
  model: provider.model,
2164
2227
  error: msg,
2165
2228
  error_kind: kind,
2229
+ ...billing ? { error_billing: true } : {},
2166
2230
  attempts: 0,
2167
2231
  usage: emptyUsage(provider.name, provider.model, opts.purpose),
2168
2232
  cache_hit: false,
@@ -11770,7 +11834,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11770
11834
  var DEFAULT_PACKAGE = "crosscheck-cli";
11771
11835
  var FETCH_TIMEOUT_MS = 3e3;
11772
11836
  function engineVersion() {
11773
- return true ? "0.2.20" : "0.0.0-dev";
11837
+ return true ? "0.2.22" : "0.0.0-dev";
11774
11838
  }
11775
11839
  function defaultUpdateCachePath() {
11776
11840
  const base = process.env["CROSSCHECK_DATA_DIR"] || path9.join(os.homedir() || os.tmpdir(), ".crosscheck");