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.cjs
CHANGED
|
@@ -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: {
|
|
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:
|
|
1165
|
-
completion_tokens:
|
|
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:
|
|
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) &&
|
|
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.
|
|
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.
|
|
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");
|