crosscheck-mcp 0.2.14 → 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
  }
@@ -1278,7 +1285,7 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
1278
1285
  return new ProviderError(
1279
1286
  "client",
1280
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}`,
1281
- { status }
1288
+ { status, modelAccessFailure: true }
1282
1289
  );
1283
1290
  }
1284
1291
  if (isCreditsFailure(status, bodyText)) {
@@ -1494,6 +1501,14 @@ function buildAnthropicRequest(opts) {
1494
1501
  if (system !== void 0) {
1495
1502
  body.system = system;
1496
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
+ }
1497
1512
  if (opts.jsonSchema) {
1498
1513
  body.tools = [{
1499
1514
  name: ANTHROPIC_STRUCTURED_TOOL_NAME,
@@ -1561,7 +1576,12 @@ function parseAnthropicResponse(opts) {
1561
1576
  purpose: opts.purpose
1562
1577
  };
1563
1578
  usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1564
- 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";
1565
1585
  }
1566
1586
  function applyPricing(usage, pricing) {
1567
1587
  const { cost_usd, estimated } = calculateCost(
@@ -1579,49 +1599,64 @@ function applyPricing(usage, pricing) {
1579
1599
  estimated: usage.estimated || estimated
1580
1600
  };
1581
1601
  }
1602
+ var MAX_TOKENS_ESCALATIONS = 2;
1603
+ var ESCALATION_MULTIPLIER = 2;
1582
1604
  async function sendAnthropic(args) {
1583
- const { url, headers, body } = buildAnthropicRequest({
1584
- model: args.model,
1585
- apiKey: args.apiKey,
1586
- messages: args.messages,
1587
- maxTokens: args.maxTokens,
1588
- temperature: args.temperature,
1589
- ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1590
- });
1591
1605
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1592
- const init = {
1593
- method: "POST",
1594
- headers,
1595
- body: JSON.stringify(body)
1596
- };
1597
- if (args.signal) init.signal = args.signal;
1598
- const attemptOnce = async () => {
1599
- let respLike;
1600
- try {
1601
- respLike = await doFetch(url, init);
1602
- } catch (e) {
1603
- throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1604
- }
1605
- const status = respLike.status;
1606
- if (status >= 200 && status < 300) {
1607
- 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;
1608
1627
  try {
1609
- parsed = await respLike.json();
1628
+ respLike = await doFetch(url, init);
1610
1629
  } catch (e) {
1611
- throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
1630
+ throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1612
1631
  }
1613
- const { text, usage } = parseAnthropicResponse({
1614
- resp: parsed,
1615
- model: args.model,
1616
- purpose: args.purpose ?? "worker"
1617
- });
1618
- 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;
1619
1656
  }
1620
- const bodyText = await respLike.text().catch(() => "");
1621
- throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike), args.model);
1622
- };
1623
- await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
1624
- return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1657
+ maxTokens *= ESCALATION_MULTIPLIER;
1658
+ }
1659
+ return { ...result, attempts: totalAttempts };
1625
1660
  }
1626
1661
 
1627
1662
  // src/providers/gemini.ts
@@ -2014,12 +2049,20 @@ function detectStalePins(env) {
2014
2049
  }
2015
2050
  return out;
2016
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
+ }
2017
2060
  function buildProviders(opts) {
2018
2061
  const out = {};
2019
2062
  const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
2020
2063
  if (anthropicKey) {
2021
2064
  const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
2022
- out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts);
2065
+ out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
2023
2066
  }
2024
2067
  const openAiCompatSpec = [
2025
2068
  { name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
@@ -2034,19 +2077,20 @@ function buildProviders(opts) {
2034
2077
  const apiKey = opts.env[s.keyEnv];
2035
2078
  if (!apiKey) continue;
2036
2079
  const model = opts.env[s.modelEnv] ?? s.defaultModel;
2037
- out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts);
2080
+ out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
2038
2081
  }
2039
2082
  const geminiKey = opts.env["GEMINI_API_KEY"];
2040
2083
  if (geminiKey) {
2041
2084
  const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
2042
- out["gemini"] = makeGeminiProvider(model, geminiKey, opts);
2085
+ out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
2043
2086
  }
2044
2087
  return out;
2045
2088
  }
2046
- function makeAnthropicProvider(model, apiKey, opts) {
2089
+ function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
2047
2090
  return {
2048
2091
  name: "anthropic",
2049
2092
  model,
2093
+ fallbackModels,
2050
2094
  send: async (args) => {
2051
2095
  const sendOpts = {
2052
2096
  ...args,
@@ -2059,11 +2103,12 @@ function makeAnthropicProvider(model, apiKey, opts) {
2059
2103
  }
2060
2104
  };
2061
2105
  }
2062
- function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
2106
+ function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
2063
2107
  const url = OPENAI_COMPAT_DEFAULT_URLS[name];
2064
2108
  return {
2065
2109
  name,
2066
2110
  model,
2111
+ fallbackModels,
2067
2112
  send: async (args) => {
2068
2113
  const sendOpts = {
2069
2114
  ...args,
@@ -2078,10 +2123,11 @@ function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
2078
2123
  }
2079
2124
  };
2080
2125
  }
2081
- function makeGeminiProvider(model, apiKey, opts) {
2126
+ function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
2082
2127
  return {
2083
2128
  name: "gemini",
2084
2129
  model,
2130
+ fallbackModels,
2085
2131
  send: async (args) => {
2086
2132
  const sendOpts = {
2087
2133
  ...args,
@@ -2098,7 +2144,7 @@ function makeGeminiProvider(model, apiKey, opts) {
2098
2144
  // src/server.ts
2099
2145
  init_cjs_shims();
2100
2146
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
2101
- var import_types8 = require("@modelcontextprotocol/sdk/types.js");
2147
+ var import_types9 = require("@modelcontextprotocol/sdk/types.js");
2102
2148
 
2103
2149
  // src/bridge/index.ts
2104
2150
  init_cjs_shims();
@@ -2229,7 +2275,7 @@ var import_zod = require("zod");
2229
2275
  // src/server-meta.ts
2230
2276
  init_cjs_shims();
2231
2277
  var SERVER_NAME = "crosscheck-agent";
2232
- var SERVER_VERSION = true ? "0.2.14" : "0.0.0-dev";
2278
+ var SERVER_VERSION = true ? "0.2.15" : "0.0.0-dev";
2233
2279
 
2234
2280
  // src/tools/audit.ts
2235
2281
  init_cjs_shims();
@@ -2891,6 +2937,55 @@ function operatorCeiling(purpose, provider) {
2891
2937
  return null;
2892
2938
  }
2893
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
+
2894
2989
  // src/core/structured.ts
2895
2990
  async function requestStructured(provider, baseMessages, schema, opts) {
2896
2991
  const maxRetries = opts.maxRetries ?? 1;
@@ -2969,7 +3064,7 @@ async function askOne(provider, messages, opts) {
2969
3064
  const ceiling = operatorCeiling(opts.purpose, provider.name);
2970
3065
  if (ceiling !== null && ceiling < maxTokens) maxTokens = ceiling;
2971
3066
  try {
2972
- const r = await provider.send({
3067
+ const { result: r, modelUsed } = await sendWithModelFallback(provider, {
2973
3068
  messages,
2974
3069
  maxTokens,
2975
3070
  temperature: opts.temperature,
@@ -2982,7 +3077,10 @@ async function askOne(provider, messages, opts) {
2982
3077
  const cpuMs = Math.trunc((cpu.user + cpu.system) / 1e3);
2983
3078
  return {
2984
3079
  provider: provider.name,
2985
- 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,
2986
3084
  response: r.text,
2987
3085
  attempts: r.attempts,
2988
3086
  usage: r.usage,
@@ -3703,32 +3801,6 @@ function numberOrNull(v) {
3703
3801
  // src/tools/audit.ts
3704
3802
  var import_node_perf_hooks = require("perf_hooks");
3705
3803
 
3706
- // src/core/retarget.ts
3707
- init_cjs_shims();
3708
- function retargetProvider(p, newModel) {
3709
- if (p.model === newModel) return p;
3710
- return {
3711
- name: p.name,
3712
- model: newModel,
3713
- send: (args) => p.send({ ...args, modelOverride: newModel })
3714
- };
3715
- }
3716
- async function loadProviderWeights(storage, names) {
3717
- const out = {};
3718
- for (const raw of names) {
3719
- const name = raw.toLowerCase();
3720
- if (name in out) continue;
3721
- const row = await storage.getProviderStats(name);
3722
- if (!row) {
3723
- out[name] = 0.5;
3724
- continue;
3725
- }
3726
- const total = row.wins + row.losses + row.abstains;
3727
- out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
3728
- }
3729
- return out;
3730
- }
3731
-
3732
3804
  // src/core/co-reason.ts
3733
3805
  init_cjs_shims();
3734
3806
  var CO_REASON_MODEL = "gpt-5.6";
@@ -12523,7 +12595,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12523
12595
  var DEFAULT_PACKAGE = "crosscheck-cli";
12524
12596
  var FETCH_TIMEOUT_MS = 3e3;
12525
12597
  function engineVersion() {
12526
- return true ? "0.2.14" : "0.0.0-dev";
12598
+ return true ? "0.2.15" : "0.0.0-dev";
12527
12599
  }
12528
12600
  function defaultUpdateCachePath() {
12529
12601
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path13.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
@@ -14457,14 +14529,14 @@ function createServer(opts = {}) {
14457
14529
  });
14458
14530
  };
14459
14531
  const tools = buildToolRegistry(opts);
14460
- server.setRequestHandler(import_types8.ListToolsRequestSchema, async () => ({
14532
+ server.setRequestHandler(import_types9.ListToolsRequestSchema, async () => ({
14461
14533
  tools: Array.from(tools.values()).map((t) => ({
14462
14534
  name: t.name,
14463
14535
  description: t.description,
14464
14536
  inputSchema: t.inputSchema
14465
14537
  }))
14466
14538
  }));
14467
- server.setRequestHandler(import_types8.CallToolRequestSchema, async (req) => {
14539
+ server.setRequestHandler(import_types9.CallToolRequestSchema, async (req) => {
14468
14540
  const name = req.params.name;
14469
14541
  const tool = tools.get(name);
14470
14542
  if (!tool) {