crosscheck-mcp 0.2.21 → 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 {
@@ -726,6 +750,23 @@ async function acquireRateLimit(provider, deps) {
726
750
  var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
727
751
  var ANTHROPIC_VERSION_HEADER = "2023-06-01";
728
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
+ }
729
770
  function buildAnthropicRequest(opts) {
730
771
  let system;
731
772
  const convo = [];
@@ -750,6 +791,7 @@ function buildAnthropicRequest(opts) {
750
791
  if (system !== void 0) {
751
792
  body.system = system;
752
793
  }
794
+ applyPromptCaching(body);
753
795
  if (isReasoningModel("anthropic", opts.model)) {
754
796
  if (opts.model.toLowerCase().startsWith("claude-fable-5")) {
755
797
  body.thinking = { type: "adaptive" };
@@ -809,6 +851,7 @@ function parseAnthropicResponse(opts) {
809
851
  const prompt = Math.trunc(Number(u["input_tokens"] ?? 0)) || 0;
810
852
  const cached = Math.trunc(Number(u["cache_read_input_tokens"] ?? 0)) || 0;
811
853
  const completion = Math.trunc(Number(u["output_tokens"] ?? 0)) || 0;
854
+ const cacheWrite = Math.trunc(Number(u["cache_creation_input_tokens"] ?? 0)) || 0;
812
855
  const usage = {
813
856
  provider: "anthropic",
814
857
  model: opts.model,
@@ -816,7 +859,7 @@ function parseAnthropicResponse(opts) {
816
859
  // production helper folds them in so prompt_tokens is the FULL
817
860
  // input volume; calculateCost then bills the cached subset at the
818
861
  // cached rate (cached <= prompt_tokens, since prompt = input+cached).
819
- prompt_tokens: prompt + cached,
862
+ prompt_tokens: prompt + cached + cacheWrite,
820
863
  completion_tokens: completion,
821
864
  cached_tokens: cached,
822
865
  total_tokens: 0,
@@ -1166,16 +1209,21 @@ function parseOpenAICompatibleResponse(opts) {
1166
1209
  const u = r["usage"] ?? {};
1167
1210
  const details = u["prompt_tokens_details"] ?? {};
1168
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);
1169
1217
  const usage = {
1170
1218
  provider: opts.provider,
1171
1219
  model: opts.model,
1172
- prompt_tokens: Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0,
1173
- completion_tokens: Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0,
1220
+ prompt_tokens: promptTokens,
1221
+ completion_tokens: completionTokens,
1174
1222
  cached_tokens: cached,
1175
1223
  // Use the response's reported total_tokens directly. Python's
1176
1224
  // `Usage.to_dict()` falls back to prompt+completion only when
1177
1225
  // total_tokens is 0/missing; mirror that.
1178
- total_tokens: Math.trunc(Number(u["total_tokens"] ?? 0)) || 0,
1226
+ total_tokens: reportedTotal,
1179
1227
  cost_usd: 0,
1180
1228
  estimated: Object.keys(u).length === 0,
1181
1229
  purpose: opts.purpose
@@ -1217,7 +1265,11 @@ async function sendOpenAICompatible(args) {
1217
1265
  body.reasoning_effort = effort;
1218
1266
  }
1219
1267
  }
1220
- 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") {
1221
1273
  const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1222
1274
  const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1223
1275
  body.max_completion_tokens += headroom;
@@ -1392,7 +1444,7 @@ var import_zod = require("zod");
1392
1444
 
1393
1445
  // src/server-meta.ts
1394
1446
  var SERVER_NAME = "crosscheck-agent";
1395
- var SERVER_VERSION = true ? "0.2.21" : "0.0.0-dev";
1447
+ var SERVER_VERSION = true ? "0.2.22" : "0.0.0-dev";
1396
1448
 
1397
1449
  // src/tools/audit.ts
1398
1450
  var import_node_fs4 = require("fs");
@@ -11798,7 +11850,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11798
11850
  var DEFAULT_PACKAGE = "crosscheck-cli";
11799
11851
  var FETCH_TIMEOUT_MS = 3e3;
11800
11852
  function engineVersion() {
11801
- return true ? "0.2.21" : "0.0.0-dev";
11853
+ return true ? "0.2.22" : "0.0.0-dev";
11802
11854
  }
11803
11855
  function defaultUpdateCachePath() {
11804
11856
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path12.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");