crosscheck-mcp 0.2.13 → 0.2.15

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.
@@ -1242,11 +1242,18 @@ var ProviderError = class extends Error {
1242
1242
  status;
1243
1243
  transient;
1244
1244
  retryAfterS;
1245
+ /** True specifically for "the key is fine but this model isn't
1246
+ * available to this account/project" (see http-errors.ts's
1247
+ * isModelAccessFailure) — the one failure a model fallback chain can
1248
+ * actually fix. Distinct from `transient`: this is never worth
1249
+ * retrying the SAME model, but IS worth trying a different one. */
1250
+ modelAccessFailure;
1245
1251
  constructor(kind, message, opts) {
1246
1252
  super(message);
1247
1253
  this.kind = kind;
1248
1254
  this.status = opts?.status;
1249
1255
  this.transient = opts?.transient ?? defaultTransient(kind);
1256
+ this.modelAccessFailure = opts?.modelAccessFailure ?? false;
1250
1257
  if (opts?.retryAfterS !== void 0) {
1251
1258
  this.retryAfterS = opts.retryAfterS;
1252
1259
  }
@@ -1266,8 +1273,21 @@ function isCreditsFailure(status, bodyText) {
1266
1273
  }
1267
1274
  return false;
1268
1275
  }
1269
- function httpFailureToProviderError(provider, status, bodyText, retryAfterS) {
1276
+ var MODEL_ACCESS_RE = /model_not_found|does not have access to model|does not exist|model[\s_]not[\s_]found|invalid model|unknown model|unsupported model/i;
1277
+ function isModelAccessFailure(status, bodyText) {
1278
+ if (status < 400 || status >= 500) return false;
1279
+ return MODEL_ACCESS_RE.test(bodyText);
1280
+ }
1281
+ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, model) {
1270
1282
  const detail = bodyText.slice(0, 512);
1283
+ if (isModelAccessFailure(status, bodyText)) {
1284
+ const modelRef = model ? `"${model}"` : "the configured model";
1285
+ return new ProviderError(
1286
+ "client",
1287
+ `${provider}: your account/project doesn't have access to ${modelRef} (HTTP ${status}). This is a model-access problem, not a key problem \u2014 your ${provider} API key is fine, ${modelRef} just isn't enabled for this account/project. Fix: run \`crosscheck models set ${provider} <a-model-you-have-access-to>\` (or set ${provider.toUpperCase()}_MODEL in your .env until that's available). Detail: ${detail}`,
1288
+ { status, modelAccessFailure: true }
1289
+ );
1290
+ }
1271
1291
  if (isCreditsFailure(status, bodyText)) {
1272
1292
  return new ProviderError(
1273
1293
  "auth",
@@ -1481,6 +1501,14 @@ function buildAnthropicRequest(opts) {
1481
1501
  if (system !== void 0) {
1482
1502
  body.system = system;
1483
1503
  }
1504
+ if (isReasoningModel("anthropic", opts.model)) {
1505
+ if (opts.model.toLowerCase().startsWith("claude-fable-5")) {
1506
+ body.thinking = { type: "adaptive" };
1507
+ body.output_config = { effort: "low" };
1508
+ } else {
1509
+ body.thinking = { type: "disabled" };
1510
+ }
1511
+ }
1484
1512
  if (opts.jsonSchema) {
1485
1513
  body.tools = [{
1486
1514
  name: ANTHROPIC_STRUCTURED_TOOL_NAME,
@@ -1548,7 +1576,12 @@ function parseAnthropicResponse(opts) {
1548
1576
  purpose: opts.purpose
1549
1577
  };
1550
1578
  usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1551
- return { text, usage };
1579
+ const stopReasonRaw = r["stop_reason"];
1580
+ const stopReason = typeof stopReasonRaw === "string" ? stopReasonRaw : void 0;
1581
+ return { text, usage, ...stopReason !== void 0 ? { stopReason } : {} };
1582
+ }
1583
+ function isEmptyDueToMaxTokens(text, stopReason) {
1584
+ return text.trim() === "" && stopReason === "max_tokens";
1552
1585
  }
1553
1586
  function applyPricing(usage, pricing) {
1554
1587
  const { cost_usd, estimated } = calculateCost(
@@ -1566,49 +1599,64 @@ function applyPricing(usage, pricing) {
1566
1599
  estimated: usage.estimated || estimated
1567
1600
  };
1568
1601
  }
1602
+ var MAX_TOKENS_ESCALATIONS = 2;
1603
+ var ESCALATION_MULTIPLIER = 2;
1569
1604
  async function sendAnthropic(args) {
1570
- const { url, headers, body } = buildAnthropicRequest({
1571
- model: args.model,
1572
- apiKey: args.apiKey,
1573
- messages: args.messages,
1574
- maxTokens: args.maxTokens,
1575
- temperature: args.temperature,
1576
- ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1577
- });
1578
1605
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1579
- const init = {
1580
- method: "POST",
1581
- headers,
1582
- body: JSON.stringify(body)
1583
- };
1584
- if (args.signal) init.signal = args.signal;
1585
- const attemptOnce = async () => {
1586
- let respLike;
1587
- try {
1588
- respLike = await doFetch(url, init);
1589
- } catch (e) {
1590
- throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1591
- }
1592
- const status = respLike.status;
1593
- if (status >= 200 && status < 300) {
1594
- let parsed;
1606
+ let maxTokens = args.maxTokens;
1607
+ let result = null;
1608
+ let totalAttempts = 0;
1609
+ for (let escalation = 0; escalation <= MAX_TOKENS_ESCALATIONS; escalation += 1) {
1610
+ const { url, headers, body } = buildAnthropicRequest({
1611
+ model: args.model,
1612
+ apiKey: args.apiKey,
1613
+ messages: args.messages,
1614
+ maxTokens,
1615
+ temperature: args.temperature,
1616
+ ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1617
+ });
1618
+ const init = {
1619
+ method: "POST",
1620
+ headers,
1621
+ body: JSON.stringify(body)
1622
+ };
1623
+ if (args.signal) init.signal = args.signal;
1624
+ let stopReason;
1625
+ const attemptOnce = async () => {
1626
+ let respLike;
1595
1627
  try {
1596
- parsed = await respLike.json();
1628
+ respLike = await doFetch(url, init);
1597
1629
  } catch (e) {
1598
- throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
1630
+ throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1599
1631
  }
1600
- const { text, usage } = parseAnthropicResponse({
1601
- resp: parsed,
1602
- model: args.model,
1603
- purpose: args.purpose ?? "worker"
1604
- });
1605
- return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
1632
+ const status = respLike.status;
1633
+ if (status >= 200 && status < 300) {
1634
+ let parsed;
1635
+ try {
1636
+ parsed = await respLike.json();
1637
+ } catch (e) {
1638
+ throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
1639
+ }
1640
+ const { text, usage, stopReason: sr } = parseAnthropicResponse({
1641
+ resp: parsed,
1642
+ model: args.model,
1643
+ purpose: args.purpose ?? "worker"
1644
+ });
1645
+ stopReason = sr;
1646
+ return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
1647
+ }
1648
+ const bodyText = await respLike.text().catch(() => "");
1649
+ throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike), args.model);
1650
+ };
1651
+ await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
1652
+ result = await sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1653
+ totalAttempts += result.attempts;
1654
+ if (escalation >= MAX_TOKENS_ESCALATIONS || !isEmptyDueToMaxTokens(result.text, stopReason)) {
1655
+ break;
1606
1656
  }
1607
- const bodyText = await respLike.text().catch(() => "");
1608
- throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike));
1609
- };
1610
- await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
1611
- return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1657
+ maxTokens *= ESCALATION_MULTIPLIER;
1658
+ }
1659
+ return { ...result, attempts: totalAttempts };
1612
1660
  }
1613
1661
 
1614
1662
  // src/providers/gemini.ts
@@ -1784,7 +1832,7 @@ async function sendGemini(args) {
1784
1832
  return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
1785
1833
  }
1786
1834
  const bodyText = await respLike.text().catch(() => "");
1787
- throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike));
1835
+ throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike), args.model);
1788
1836
  };
1789
1837
  await acquireRateLimit("gemini", args.signal ? { signal: args.signal } : void 0);
1790
1838
  return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
@@ -1958,7 +2006,7 @@ async function sendOpenAICompatible(args) {
1958
2006
  return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1959
2007
  }
1960
2008
  const bodyText = await respLike.text().catch(() => "");
1961
- throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike));
2009
+ throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike), args.model);
1962
2010
  };
1963
2011
  await acquireRateLimit(args.provider, args.signal ? { signal: args.signal } : void 0);
1964
2012
  return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
@@ -1967,7 +2015,10 @@ async function sendOpenAICompatible(args) {
1967
2015
  // src/providers/registry.ts
1968
2016
  var DEFAULT_MODELS = {
1969
2017
  anthropic: "claude-opus-5",
1970
- openai: "gpt-5.5",
2018
+ // gpt-5.5 is not enabled on every OpenAI account/project (some are still
2019
+ // capped at gpt-5) — gpt-5 is the safer, more broadly available default
2020
+ // until a user/org explicitly opts into 5.5 via `crosscheck models set`.
2021
+ openai: "gpt-5",
1971
2022
  xai: "grok-4-latest",
1972
2023
  mistral: "mistral-large-latest",
1973
2024
  groq: "llama-3.3-70b-versatile",
@@ -1998,12 +2049,20 @@ function detectStalePins(env) {
1998
2049
  }
1999
2050
  return out;
2000
2051
  }
2052
+ function parseFallbackModels(raw) {
2053
+ if (!raw) return [];
2054
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
2055
+ }
2056
+ function fallbackModelsFor(env, provider) {
2057
+ const envVar = MODEL_ENV_VARS[provider];
2058
+ return envVar ? parseFallbackModels(env[`${envVar}_FALLBACKS`]) : [];
2059
+ }
2001
2060
  function buildProviders(opts) {
2002
2061
  const out = {};
2003
2062
  const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
2004
2063
  if (anthropicKey) {
2005
2064
  const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
2006
- out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts);
2065
+ out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
2007
2066
  }
2008
2067
  const openAiCompatSpec = [
2009
2068
  { name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
@@ -2018,19 +2077,20 @@ function buildProviders(opts) {
2018
2077
  const apiKey = opts.env[s.keyEnv];
2019
2078
  if (!apiKey) continue;
2020
2079
  const model = opts.env[s.modelEnv] ?? s.defaultModel;
2021
- out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts);
2080
+ out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
2022
2081
  }
2023
2082
  const geminiKey = opts.env["GEMINI_API_KEY"];
2024
2083
  if (geminiKey) {
2025
2084
  const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
2026
- out["gemini"] = makeGeminiProvider(model, geminiKey, opts);
2085
+ out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
2027
2086
  }
2028
2087
  return out;
2029
2088
  }
2030
- function makeAnthropicProvider(model, apiKey, opts) {
2089
+ function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
2031
2090
  return {
2032
2091
  name: "anthropic",
2033
2092
  model,
2093
+ fallbackModels,
2034
2094
  send: async (args) => {
2035
2095
  const sendOpts = {
2036
2096
  ...args,
@@ -2043,11 +2103,12 @@ function makeAnthropicProvider(model, apiKey, opts) {
2043
2103
  }
2044
2104
  };
2045
2105
  }
2046
- function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
2106
+ function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
2047
2107
  const url = OPENAI_COMPAT_DEFAULT_URLS[name];
2048
2108
  return {
2049
2109
  name,
2050
2110
  model,
2111
+ fallbackModels,
2051
2112
  send: async (args) => {
2052
2113
  const sendOpts = {
2053
2114
  ...args,
@@ -2062,10 +2123,11 @@ function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
2062
2123
  }
2063
2124
  };
2064
2125
  }
2065
- function makeGeminiProvider(model, apiKey, opts) {
2126
+ function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
2066
2127
  return {
2067
2128
  name: "gemini",
2068
2129
  model,
2130
+ fallbackModels,
2069
2131
  send: async (args) => {
2070
2132
  const sendOpts = {
2071
2133
  ...args,
@@ -2082,7 +2144,7 @@ function makeGeminiProvider(model, apiKey, opts) {
2082
2144
  // src/server.ts
2083
2145
  init_cjs_shims();
2084
2146
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
2085
- var import_types8 = require("@modelcontextprotocol/sdk/types.js");
2147
+ var import_types9 = require("@modelcontextprotocol/sdk/types.js");
2086
2148
 
2087
2149
  // src/bridge/index.ts
2088
2150
  init_cjs_shims();
@@ -2213,7 +2275,7 @@ var import_zod = require("zod");
2213
2275
  // src/server-meta.ts
2214
2276
  init_cjs_shims();
2215
2277
  var SERVER_NAME = "crosscheck-agent";
2216
- var SERVER_VERSION = true ? "0.2.13" : "0.0.0-dev";
2278
+ var SERVER_VERSION = true ? "0.2.15" : "0.0.0-dev";
2217
2279
 
2218
2280
  // src/tools/audit.ts
2219
2281
  init_cjs_shims();
@@ -2875,6 +2937,55 @@ function operatorCeiling(purpose, provider) {
2875
2937
  return null;
2876
2938
  }
2877
2939
 
2940
+ // src/core/model-fallback.ts
2941
+ init_cjs_shims();
2942
+
2943
+ // src/core/retarget.ts
2944
+ init_cjs_shims();
2945
+ function retargetProvider(p, newModel) {
2946
+ if (p.model === newModel) return p;
2947
+ return {
2948
+ name: p.name,
2949
+ model: newModel,
2950
+ send: (args) => p.send({ ...args, modelOverride: newModel })
2951
+ };
2952
+ }
2953
+ async function loadProviderWeights(storage, names) {
2954
+ const out = {};
2955
+ for (const raw of names) {
2956
+ const name = raw.toLowerCase();
2957
+ if (name in out) continue;
2958
+ const row = await storage.getProviderStats(name);
2959
+ if (!row) {
2960
+ out[name] = 0.5;
2961
+ continue;
2962
+ }
2963
+ const total = row.wins + row.losses + row.abstains;
2964
+ out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
2965
+ }
2966
+ return out;
2967
+ }
2968
+
2969
+ // src/core/model-fallback.ts
2970
+ async function sendWithModelFallback(provider, args) {
2971
+ const chain = [provider.model, ...provider.fallbackModels ?? []];
2972
+ let lastErr;
2973
+ for (let i = 0; i < chain.length; i += 1) {
2974
+ const candidate = chain[i];
2975
+ const p = i === 0 ? provider : retargetProvider(provider, candidate);
2976
+ try {
2977
+ const result = await p.send(args);
2978
+ return { result, modelUsed: candidate };
2979
+ } catch (e) {
2980
+ lastErr = e;
2981
+ const isModelAccessFailure2 = e instanceof ProviderError && e.modelAccessFailure;
2982
+ const hasMoreCandidates = i < chain.length - 1;
2983
+ if (!isModelAccessFailure2 || !hasMoreCandidates) throw e;
2984
+ }
2985
+ }
2986
+ throw lastErr;
2987
+ }
2988
+
2878
2989
  // src/core/structured.ts
2879
2990
  async function requestStructured(provider, baseMessages, schema, opts) {
2880
2991
  const maxRetries = opts.maxRetries ?? 1;
@@ -2953,7 +3064,7 @@ async function askOne(provider, messages, opts) {
2953
3064
  const ceiling = operatorCeiling(opts.purpose, provider.name);
2954
3065
  if (ceiling !== null && ceiling < maxTokens) maxTokens = ceiling;
2955
3066
  try {
2956
- const r = await provider.send({
3067
+ const { result: r, modelUsed } = await sendWithModelFallback(provider, {
2957
3068
  messages,
2958
3069
  maxTokens,
2959
3070
  temperature: opts.temperature,
@@ -2966,7 +3077,10 @@ async function askOne(provider, messages, opts) {
2966
3077
  const cpuMs = Math.trunc((cpu.user + cpu.system) / 1e3);
2967
3078
  return {
2968
3079
  provider: provider.name,
2969
- model: provider.model,
3080
+ // modelUsed reflects whichever model actually answered — the
3081
+ // primary, or a fallback if one was needed. Equivalent to the old
3082
+ // `provider.model` whenever no fallback occurred.
3083
+ model: modelUsed,
2970
3084
  response: r.text,
2971
3085
  attempts: r.attempts,
2972
3086
  usage: r.usage,
@@ -3687,32 +3801,6 @@ function numberOrNull(v) {
3687
3801
  // src/tools/audit.ts
3688
3802
  var import_node_perf_hooks = require("perf_hooks");
3689
3803
 
3690
- // src/core/retarget.ts
3691
- init_cjs_shims();
3692
- function retargetProvider(p, newModel) {
3693
- if (p.model === newModel) return p;
3694
- return {
3695
- name: p.name,
3696
- model: newModel,
3697
- send: (args) => p.send({ ...args, modelOverride: newModel })
3698
- };
3699
- }
3700
- async function loadProviderWeights(storage, names) {
3701
- const out = {};
3702
- for (const raw of names) {
3703
- const name = raw.toLowerCase();
3704
- if (name in out) continue;
3705
- const row = await storage.getProviderStats(name);
3706
- if (!row) {
3707
- out[name] = 0.5;
3708
- continue;
3709
- }
3710
- const total = row.wins + row.losses + row.abstains;
3711
- out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
3712
- }
3713
- return out;
3714
- }
3715
-
3716
3804
  // src/core/co-reason.ts
3717
3805
  init_cjs_shims();
3718
3806
  var CO_REASON_MODEL = "gpt-5.6";
@@ -12507,7 +12595,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12507
12595
  var DEFAULT_PACKAGE = "crosscheck-cli";
12508
12596
  var FETCH_TIMEOUT_MS = 3e3;
12509
12597
  function engineVersion() {
12510
- return true ? "0.2.13" : "0.0.0-dev";
12598
+ return true ? "0.2.15" : "0.0.0-dev";
12511
12599
  }
12512
12600
  function defaultUpdateCachePath() {
12513
12601
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path13.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
@@ -14441,14 +14529,14 @@ function createServer(opts = {}) {
14441
14529
  });
14442
14530
  };
14443
14531
  const tools = buildToolRegistry(opts);
14444
- server.setRequestHandler(import_types8.ListToolsRequestSchema, async () => ({
14532
+ server.setRequestHandler(import_types9.ListToolsRequestSchema, async () => ({
14445
14533
  tools: Array.from(tools.values()).map((t) => ({
14446
14534
  name: t.name,
14447
14535
  description: t.description,
14448
14536
  inputSchema: t.inputSchema
14449
14537
  }))
14450
14538
  }));
14451
- server.setRequestHandler(import_types8.CallToolRequestSchema, async (req) => {
14539
+ server.setRequestHandler(import_types9.CallToolRequestSchema, async (req) => {
14452
14540
  const name = req.params.name;
14453
14541
  const tool = tools.get(name);
14454
14542
  if (!tool) {