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.
- package/dist/browser-ext.cjs +74 -10
- package/dist/browser-ext.cjs.map +1 -1
- package/dist/browser-ext.js +74 -10
- package/dist/browser-ext.js.map +1 -1
- package/dist/node-stdio.cjs +129 -17
- package/dist/node-stdio.cjs.map +1 -1
- package/dist/node-stdio.js +129 -17
- package/dist/node-stdio.js.map +1 -1
- package/package.json +1 -1
package/dist/browser-ext.js
CHANGED
|
@@ -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: {
|
|
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:
|
|
1132
|
-
completion_tokens:
|
|
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:
|
|
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) &&
|
|
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.
|
|
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.
|
|
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");
|