crosscheck-mcp 0.2.30 → 0.2.31

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.
@@ -6,426 +6,35 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  throw Error('Dynamic require of "' + x + '" is not supported');
7
7
  });
8
8
 
9
- // ../../config/pricing.json
10
- var pricing_default = {
11
- _meta: {
12
- currency: "USD",
13
- unit: "per_1k_tokens",
14
- updated_at: "2026-09-06",
15
- notes: "Prices reflect public list pricing per provider. cached_per_1k applies to prompt tokens served from prompt-cache (Anthropic) or input-cache (OpenAI) when supported. Update when providers change rates. Missing models fall back to cost=0 with estimated=true. 2026-06-07: removed decommissioned gemini-2.0-flash / gemini-2.0-flash-lite (Google returns 404) and replaced the low-tier lite model with gemini-2.5-flash-lite. 2026-06-08: bumped the default Anthropic model to claude-opus-4-8. Kept gemini-2.5-pro as the Gemini default (gemini-3.1-pro-preview measured ~2.2x the cost at $2/$12 per M \u2014 output-token dominated by ~1.5k thinking tokens \u2014 so not worth it for a 1-of-4 panel voice). 2026-06-09: corrected rates verified against live provider pricing pages \u2014 gemini-2.5-pro output $5->$10/M; gemini-2.5-flash $0.15/$0.60 -> $0.30/$2.50; gpt-5 $5/$15 -> $1.25/$10 (cached $0.125); grok-4-latest input $5->$3/M (legacy grok-4 tier). Anthropic opus/sonnet/haiku rates confirmed unchanged. Older/unused models (gpt-4o, o1, o3, grok-3) not all re-verified. 2026-07-02: added claude-fable-5 as the optional reasoning-upgrade model (used only for the lead reasoning seat when the operator bumps a heavy task). 2026-07-04: set claude-fable-5 to its real pay-as-you-go API price effective 2026-07-07 \u2014 $10/M input ($0.01/1k), $50/M output ($0.05/1k), cached at 10% of input ($0.001/1k). Dropped the estimated flag. Note: Fable 5 is cheaper than opus-4-8 on both input and output. 2026-07-09: Google retired gemini-2.5-pro AND gemini-3-pro-preview (both now 404 'no longer available'). Moved the Gemini default to gemini-3.1-pro-preview ($2/M in, $12/M out, cache $0.20/M \u2014 the newest working pro; note it's a preview and may need another bump) and added gemini-3.5-flash ($1.50/M in, $9/M out, cache $0.15/M \u2014 GA) to the low/cheap tier. Removed the dead gemini-2.5-pro rate. IMPORTANT: gemini-3.x are heavy thinking models \u2014 thoughtsTokenCount (billed as output) is now folded into completion_tokens in the adapter, so cost reflects reasoning tokens. 2026-07-13: bumped the default OpenAI model to gpt-5.5 ($5/M in, $30/M out, cached $0.50/M \u2014 verified live). Note gpt-5.5 is markedly pricier than gpt-5 ($1.25/$10): ~4x input, 3x output. Kept gpt-5 in the table for cost lookups on pinned installs. 2026-07-16: added gpt-5.6 at the Sol (flagship) tier \u2014 $5/M in, $30/M out, cached $0.50/M (cache-write $6.25/M not modeled). gpt-5.6 is the second premium voice for the max-reasoning co-reasoner (runs alongside claude-fable-5 when an OpenAI key is set). Terra/Luna tiers not added (unused). 2026-07-17: added Moonshot AI (Kimi) provider with kimi-k3, base URL https://api.moonshot.ai/v1. Confirmed real rates \u2014 $3.00/M input (cache miss, $0.003/1k), $15.00/M output ($0.015/1k), $0.30/M cache-hit input ($0.0003/1k); 1M-token context. Kimi K3 is reasoning-class (temperature must be 1; uses max_completion_tokens). 2026-07-24: added claude-opus-5 ($5/M in, $25/M out, cached $0.50/M -- 10% of input, matching this table's existing cache-discount ratio) and made it the default anthropic model (was claude-opus-4-8). Fable 5 remains the reasoning-upgrade/super-mode target, unchanged. 2026-08-04: added Alibaba Cloud Qwen provider (OpenAI-compatible, DashScope international endpoint https://dashscope-intl.aliyuncs.com/compatible-mode/v1) with default model qwen3.8-max -- confirmed business pricing $2.00/M input ($0.002/1k), $6.00/M output ($0.006/1k), cache estimated at 10% of input ($0.0002/1k, not independently confirmed). Also added qwen3.7-max as a fallback tier at $1.25/M in ($0.00125/1k) / $3.75/M out ($0.00375/1k). Note: the model string initially configured (qwen-v3.8 / qwen-v3.7) does not exist on DashScope and 404s -- live-probed the real key against qwen3.8-max, qwen3.7-max, qwen3-max, qwen-max, and qwen-plus, all of which work; corrected the default and pricing keys to qwen3.8-max/qwen3.7-max and fixed QWEN_MODEL in both .env files to match. Qwen's Max tier is a standard instruct model, not reasoning-class -- normal temperature/max_tokens handling. 2026-09-06: added gpt-6-astra at published list rates ($10/$50 per Mtok, $1/Mtok cache read; the $12.50/Mtok cache-WRITE rate has no field in this schema and is not modelled). gemini-3.8-flash now carries Google's published paid-tier rates ($0.75/$3.75/$0.075 per Mtok). THESE DOUBLE ON 2027-01-01 ($1.50/$7.50/$0.15) and this schema has no effective-date support, so they must be updated by hand on that date or every Gemini cost will read half of actual. claude-fable-5-1 now carries published rates ($10/$50/$0.25 per Mtok). Note its cache-read rate is 4x cheaper than claude-fable-5's, so the two must not be assumed to track each other. No inferred rows remain. All three were previously absent, which silently recorded their cost as $0."
16
- },
17
- anthropic: {
18
- _source: {
19
- url: "https://platform.claude.com/docs/en/about-claude/pricing",
20
- verified_at: "2026-09-27"
21
- },
22
- "claude-opus-4-5": {
23
- prompt_per_1k: 5e-3,
24
- completion_per_1k: 0.025,
25
- cached_per_1k: 5e-4
26
- },
27
- "claude-opus-4-7": {
28
- prompt_per_1k: 5e-3,
29
- completion_per_1k: 0.025,
30
- cached_per_1k: 5e-4
31
- },
32
- "claude-opus-4-8": {
33
- prompt_per_1k: 5e-3,
34
- completion_per_1k: 0.025,
35
- cached_per_1k: 5e-4
36
- },
37
- "claude-opus-5-5": {
38
- prompt_per_1k: 4e-3,
39
- completion_per_1k: 0.02,
40
- cached_per_1k: 2e-4
41
- },
42
- "claude-opus-5": {
43
- prompt_per_1k: 5e-3,
44
- completion_per_1k: 0.025,
45
- cached_per_1k: 5e-4
46
- },
47
- "claude-fable-5": {
48
- prompt_per_1k: 0.01,
49
- completion_per_1k: 0.05,
50
- cached_per_1k: 1e-3
51
- },
52
- "claude-sonnet-4-5": {
53
- prompt_per_1k: 3e-3,
54
- completion_per_1k: 0.015,
55
- cached_per_1k: 3e-4
56
- },
57
- "claude-sonnet-4-6": {
58
- prompt_per_1k: 3e-3,
59
- completion_per_1k: 0.015,
60
- cached_per_1k: 3e-4
61
- },
62
- "claude-haiku-4-5": {
63
- prompt_per_1k: 1e-3,
64
- completion_per_1k: 5e-3,
65
- cached_per_1k: 1e-4
66
- },
67
- "claude-3-5-sonnet": {
68
- prompt_per_1k: 3e-3,
69
- completion_per_1k: 0.015,
70
- cached_per_1k: 3e-4
71
- },
72
- "claude-3-5-haiku": {
73
- prompt_per_1k: 8e-4,
74
- completion_per_1k: 4e-3,
75
- cached_per_1k: 8e-5
76
- },
77
- "claude-fable-5-1": {
78
- prompt_per_1k: 0.01,
79
- completion_per_1k: 0.05,
80
- cached_per_1k: 25e-5
81
- }
82
- },
83
- openai: {
84
- _source: {
85
- url: "https://developers.openai.com/api/docs/pricing",
86
- verified_at: "2026-09-27"
87
- },
88
- "gpt-6-astra": {
89
- prompt_per_1k: 0.01,
90
- completion_per_1k: 0.05,
91
- cached_per_1k: 1e-3
92
- },
93
- "gpt-6-sol": {
94
- prompt_per_1k: 2e-3,
95
- completion_per_1k: 0.01,
96
- cached_per_1k: 2e-4
97
- },
98
- "gpt-6-luna": {
99
- prompt_per_1k: 1e-4,
100
- completion_per_1k: 5e-4,
101
- cached_per_1k: 1e-5
102
- },
103
- "gpt-5.6-sol": {
104
- prompt_per_1k: 4e-3,
105
- completion_per_1k: 0.02,
106
- cached_per_1k: 4e-4
107
- },
108
- "gpt-5.6-terra": {
109
- prompt_per_1k: 2e-3,
110
- completion_per_1k: 0.012,
111
- cached_per_1k: 2e-4
112
- },
113
- "gpt-5.6-luna": {
114
- prompt_per_1k: 2e-4,
115
- completion_per_1k: 12e-4,
116
- cached_per_1k: 2e-5
117
- },
118
- "gpt-5.6": {
119
- prompt_per_1k: 4e-3,
120
- completion_per_1k: 0.02,
121
- cached_per_1k: 4e-4
122
- },
123
- "gpt-5.5": {
124
- prompt_per_1k: 5e-3,
125
- completion_per_1k: 0.03,
126
- cached_per_1k: 5e-4
127
- },
128
- "gpt-5": {
129
- prompt_per_1k: 125e-5,
130
- completion_per_1k: 0.01,
131
- cached_per_1k: 125e-6
132
- },
133
- "gpt-5-mini": {
134
- prompt_per_1k: 25e-5,
135
- completion_per_1k: 2e-3,
136
- cached_per_1k: 25e-6
137
- },
138
- "gpt-5-nano": {
139
- prompt_per_1k: 5e-5,
140
- completion_per_1k: 4e-4,
141
- cached_per_1k: 5e-6
142
- },
143
- "gpt-4o": {
144
- prompt_per_1k: 25e-4,
145
- completion_per_1k: 0.01,
146
- cached_per_1k: 125e-5
147
- },
148
- "gpt-4o-mini": {
149
- prompt_per_1k: 15e-5,
150
- completion_per_1k: 6e-4,
151
- cached_per_1k: 75e-6
152
- },
153
- o1: {
154
- prompt_per_1k: 0.015,
155
- completion_per_1k: 0.06,
156
- cached_per_1k: 75e-4
157
- },
158
- "o1-mini": {
159
- prompt_per_1k: 3e-3,
160
- completion_per_1k: 0.012,
161
- cached_per_1k: 15e-4
162
- },
163
- o3: {
164
- prompt_per_1k: 2e-3,
165
- completion_per_1k: 8e-3,
166
- cached_per_1k: 5e-4
167
- },
168
- "o3-mini": {
169
- prompt_per_1k: 11e-4,
170
- completion_per_1k: 44e-4,
171
- cached_per_1k: 55e-5
172
- },
173
- "gpt-5-pro": {
174
- prompt_per_1k: 0.015,
175
- completion_per_1k: 0.12,
176
- cached_per_1k: 0
177
- },
178
- "gpt-5.2-pro": {
179
- prompt_per_1k: 0.021,
180
- completion_per_1k: 0.168,
181
- cached_per_1k: 0
182
- },
183
- "gpt-5.4-pro": {
184
- prompt_per_1k: 0.03,
185
- completion_per_1k: 0.18,
186
- cached_per_1k: 0
187
- },
188
- "gpt-5.5-pro": {
189
- prompt_per_1k: 0.03,
190
- completion_per_1k: 0.18,
191
- cached_per_1k: 0
192
- },
193
- "o1-pro": {
194
- prompt_per_1k: 0.15,
195
- completion_per_1k: 0.6,
196
- cached_per_1k: 0
197
- },
198
- "o3-pro": {
199
- prompt_per_1k: 0.02,
200
- completion_per_1k: 0.08,
201
- cached_per_1k: 0
202
- }
203
- },
204
- xai: {
205
- "grok-4-latest": {
206
- prompt_per_1k: 3e-3,
207
- completion_per_1k: 0.015,
208
- cached_per_1k: 75e-5
209
- },
210
- "grok-3": {
211
- prompt_per_1k: 3e-3,
212
- completion_per_1k: 0.015,
213
- cached_per_1k: 15e-4
214
- },
215
- "grok-3-mini": {
216
- prompt_per_1k: 3e-4,
217
- completion_per_1k: 5e-4,
218
- cached_per_1k: 15e-5
219
- }
220
- },
221
- gemini: {
222
- "gemini-3.1-pro-preview": {
223
- prompt_per_1k: 2e-3,
224
- completion_per_1k: 0.012,
225
- cached_per_1k: 2e-4
226
- },
227
- "gemini-3.5-flash": {
228
- prompt_per_1k: 15e-4,
229
- completion_per_1k: 9e-3,
230
- cached_per_1k: 15e-5
231
- },
232
- "gemini-2.5-flash": {
233
- prompt_per_1k: 3e-4,
234
- completion_per_1k: 25e-4,
235
- cached_per_1k: 75e-6
236
- },
237
- "gemini-2.5-flash-lite": {
238
- prompt_per_1k: 1e-4,
239
- completion_per_1k: 4e-4,
240
- cached_per_1k: 25e-6
241
- },
242
- "gemini-3.8-flash": {
243
- prompt_per_1k: 75e-5,
244
- completion_per_1k: 375e-5,
245
- cached_per_1k: 75e-6
246
- }
247
- },
248
- mistral: {
249
- "mistral-large-latest": {
250
- prompt_per_1k: 2e-3,
251
- completion_per_1k: 6e-3,
252
- cached_per_1k: 2e-4
253
- },
254
- "mistral-small-latest": {
255
- prompt_per_1k: 2e-4,
256
- completion_per_1k: 6e-4,
257
- cached_per_1k: 2e-5
258
- }
259
- },
260
- groq: {
261
- "llama-3.3-70b-versatile": {
262
- prompt_per_1k: 59e-5,
263
- completion_per_1k: 79e-5,
264
- cached_per_1k: 295e-6
265
- },
266
- "llama-3.1-8b-instant": {
267
- prompt_per_1k: 5e-5,
268
- completion_per_1k: 8e-5,
269
- cached_per_1k: 25e-6
270
- }
271
- },
272
- deepseek: {
273
- "deepseek-chat": {
274
- prompt_per_1k: 27e-5,
275
- completion_per_1k: 11e-4,
276
- cached_per_1k: 7e-5
277
- },
278
- "deepseek-reasoner": {
279
- prompt_per_1k: 55e-5,
280
- completion_per_1k: 22e-4,
281
- cached_per_1k: 14e-5
282
- }
283
- },
284
- kimi: {
285
- "kimi-k3": {
286
- prompt_per_1k: 3e-3,
287
- completion_per_1k: 0.015,
288
- cached_per_1k: 3e-4
289
- }
290
- },
291
- qwen: {
292
- "qwen3.8-max": {
293
- prompt_per_1k: 2e-3,
294
- completion_per_1k: 6e-3,
295
- cached_per_1k: 2e-4
296
- },
297
- "qwen3.7-max": {
298
- prompt_per_1k: 125e-5,
299
- completion_per_1k: 375e-5,
300
- cached_per_1k: 125e-6
301
- }
302
- },
303
- _tiers: {
304
- low: {
305
- description: "Cheap, fast models for simple subtasks (extraction, formatting, short summaries).",
306
- models: [
307
- {
308
- provider: "openai",
309
- model: "gpt-4o-mini"
310
- },
311
- {
312
- provider: "openai",
313
- model: "gpt-5-mini"
314
- },
315
- {
316
- provider: "anthropic",
317
- model: "claude-haiku-4-5"
318
- },
319
- {
320
- provider: "anthropic",
321
- model: "claude-3-5-haiku"
322
- },
323
- {
324
- provider: "gemini",
325
- model: "gemini-3.5-flash"
326
- },
327
- {
328
- provider: "gemini",
329
- model: "gemini-2.5-flash"
330
- },
331
- {
332
- provider: "gemini",
333
- model: "gemini-2.5-flash-lite"
334
- },
335
- {
336
- provider: "xai",
337
- model: "grok-3-mini"
338
- },
339
- {
340
- provider: "groq",
341
- model: "llama-3.1-8b-instant"
342
- },
343
- {
344
- provider: "deepseek",
345
- model: "deepseek-chat"
346
- }
347
- ]
348
- },
349
- med: {
350
- description: "Mid-tier models for reasoning, code, multi-step thinking.",
351
- models: [
352
- {
353
- provider: "anthropic",
354
- model: "claude-sonnet-4-5"
355
- },
356
- {
357
- provider: "anthropic",
358
- model: "claude-sonnet-4-6"
359
- },
360
- {
361
- provider: "openai",
362
- model: "gpt-4o"
363
- },
364
- {
365
- provider: "openai",
366
- model: "o3-mini"
367
- },
368
- {
369
- provider: "gemini",
370
- model: "gemini-3.1-pro-preview"
371
- },
372
- {
373
- provider: "xai",
374
- model: "grok-3"
375
- },
376
- {
377
- provider: "mistral",
378
- model: "mistral-large-latest"
379
- },
380
- {
381
- provider: "deepseek",
382
- model: "deepseek-reasoner"
383
- },
384
- {
385
- provider: "groq",
386
- model: "llama-3.3-70b-versatile"
387
- }
388
- ]
389
- },
390
- high: {
391
- description: "Most capable models for hard reasoning, planning, audits.",
392
- models: [
393
- {
394
- provider: "anthropic",
395
- model: "claude-opus-4-5"
396
- },
397
- {
398
- provider: "anthropic",
399
- model: "claude-opus-4-7"
400
- },
401
- {
402
- provider: "anthropic",
403
- model: "claude-fable-5"
404
- },
405
- {
406
- provider: "openai",
407
- model: "gpt-5.5"
408
- },
409
- {
410
- provider: "openai",
411
- model: "gpt-5"
412
- },
413
- {
414
- provider: "openai",
415
- model: "o1"
416
- },
417
- {
418
- provider: "openai",
419
- model: "o3"
420
- },
421
- {
422
- provider: "xai",
423
- model: "grok-4-latest"
424
- }
425
- ]
9
+ // src/core/retarget.ts
10
+ function retargetProvider(p, newModel, fallbackModels) {
11
+ const chain = fallbackModels && fallbackModels.length > 0 ? fallbackModels : void 0;
12
+ if (p.model === newModel && chain === void 0) return p;
13
+ return {
14
+ name: p.name,
15
+ model: newModel,
16
+ ...chain ? { fallbackModels: chain } : {},
17
+ send: (args) => p.send({ ...args, modelOverride: newModel })
18
+ };
19
+ }
20
+ async function loadProviderWeights(storage, names) {
21
+ const out = {};
22
+ for (const raw of names) {
23
+ const name = raw.toLowerCase();
24
+ if (name in out) continue;
25
+ const row = await storage.getProviderStats(name);
26
+ if (!row) {
27
+ out[name] = 0.5;
28
+ continue;
426
29
  }
30
+ const total = row.wins + row.losses + row.abstains;
31
+ out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
427
32
  }
428
- };
33
+ return out;
34
+ }
35
+
36
+ // src/core/super-mode.ts
37
+ import { AsyncLocalStorage } from "async_hooks";
429
38
 
430
39
  // src/core/pricing.ts
431
40
  import { readFileSync } from "fs";
@@ -1316,7 +925,208 @@ async function sendGemini(args) {
1316
925
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1317
926
  const init = {
1318
927
  method: "POST",
1319
- headers: { ...headers, "content-type": "application/json" },
928
+ headers: { ...headers, "content-type": "application/json" },
929
+ body: JSON.stringify(body)
930
+ };
931
+ if (args.signal) init.signal = args.signal;
932
+ const attemptOnce = async () => {
933
+ let respLike;
934
+ try {
935
+ respLike = await doFetch(url, init);
936
+ } catch (e) {
937
+ const d = diagnoseNetworkError(e);
938
+ if (!looksLikeNetworkFailure(d)) throw e;
939
+ throw new ProviderError(d.kind, networkErrorMessage("gemini", d), {
940
+ transient: d.transient
941
+ });
942
+ }
943
+ const status = respLike.status;
944
+ if (status >= 200 && status < 300) {
945
+ let parsed;
946
+ try {
947
+ parsed = await respLike.json();
948
+ } catch (e) {
949
+ throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
950
+ }
951
+ const { text, usage, finishReason } = parseGeminiResponse({
952
+ resp: parsed,
953
+ model: args.model,
954
+ purpose: args.purpose ?? "worker"
955
+ });
956
+ if (text.trim() === "" && finishReason === "MAX_TOKENS") {
957
+ throw new ProviderError(
958
+ "parse",
959
+ `gemini: the answer was cut off before any visible text \u2014 thinking used the whole output budget (${usage.completion_tokens} tokens). Raise CROSSCHECK_GEMINI_THINKING_HEADROOM_TOKENS or the purpose's budget.`,
960
+ { transient: false }
961
+ );
962
+ }
963
+ return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
964
+ }
965
+ const bodyText = await respLike.text().catch(() => "");
966
+ throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike), args.model);
967
+ };
968
+ await acquireRateLimit("gemini", args.signal ? { signal: args.signal } : void 0);
969
+ return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
970
+ }
971
+
972
+ // src/providers/openai-compatible.ts
973
+ var OPENAI_COMPAT_DEFAULT_URLS = {
974
+ openai: "https://api.openai.com/v1/chat/completions",
975
+ xai: "https://api.x.ai/v1/chat/completions",
976
+ mistral: "https://api.mistral.ai/v1/chat/completions",
977
+ groq: "https://api.groq.com/openai/v1/chat/completions",
978
+ deepseek: "https://api.deepseek.com/v1/chat/completions",
979
+ // Moonshot AI (Kimi). OpenAI-compatible chat completions endpoint.
980
+ kimi: "https://api.moonshot.ai/v1/chat/completions",
981
+ // Alibaba Cloud DashScope (Qwen). International endpoint — the mainland
982
+ // China endpoint (dashscope.aliyuncs.com) 403s/404s from outside China in
983
+ // a way that can look like a bad key; this is the deliberate default for
984
+ // a US-based deployment.
985
+ qwen: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions"
986
+ };
987
+ var OPENAI_COMPAT_NATIVE_STRUCTURED = /* @__PURE__ */ new Set([
988
+ "openai"
989
+ ]);
990
+ function supportsNativeJsonSchema(provider, model) {
991
+ if (!OPENAI_COMPAT_NATIVE_STRUCTURED.has(provider.toLowerCase())) {
992
+ return false;
993
+ }
994
+ const m = model.toLowerCase();
995
+ if (/^o1(?:-|$)/.test(m)) return false;
996
+ return true;
997
+ }
998
+ function buildOpenAICompatibleRequest(opts) {
999
+ const msgs = opts.messages.map((m) => {
1000
+ const text = typeof m.content === "string" ? m.content : "";
1001
+ const images = imagesOf(m);
1002
+ return images.length ? { role: m.role, content: [{ type: "text", text }, ...images.map((i) => ({ type: "image_url", image_url: { url: `data:${i.mediaType};base64,${i.data}` } }))] } : { role: m.role, content: text };
1003
+ });
1004
+ const body = {
1005
+ model: opts.model,
1006
+ messages: msgs
1007
+ };
1008
+ if (supportsTemperature(opts.provider, opts.model)) {
1009
+ body.max_tokens = opts.maxTokens;
1010
+ body.temperature = opts.temperature;
1011
+ } else {
1012
+ body.max_completion_tokens = opts.maxTokens;
1013
+ }
1014
+ if (opts.jsonSchema && supportsNativeJsonSchema(opts.provider, opts.model)) {
1015
+ body.response_format = {
1016
+ type: "json_schema",
1017
+ json_schema: {
1018
+ name: "structured_output",
1019
+ schema: opts.jsonSchema,
1020
+ // NOT strict. Strict mode requires `additionalProperties: false` on
1021
+ // every object and every property listed in `required` — the
1022
+ // engine's schemas don't meet that, so every structured OpenAI call
1023
+ // (planner, judges, synthesis) was a 400, unnoticed until the canary
1024
+ // grew a structured check (2026-10-11). Non-strict still shapes the
1025
+ // reply to the schema, and structured.ts validates it against the
1026
+ // full schema regardless, so strictness added a failure mode and no
1027
+ // safety.
1028
+ strict: false
1029
+ }
1030
+ };
1031
+ }
1032
+ const headers = {
1033
+ "content-type": "application/json",
1034
+ Authorization: `Bearer ${opts.apiKey}`
1035
+ };
1036
+ return { url: opts.url, headers, body };
1037
+ }
1038
+ function parseOpenAICompatibleResponse(opts) {
1039
+ const r = opts.resp ?? {};
1040
+ let text;
1041
+ try {
1042
+ const choices = r["choices"];
1043
+ if (!Array.isArray(choices) || choices.length === 0) {
1044
+ throw new Error("choices missing or empty");
1045
+ }
1046
+ const first = choices[0];
1047
+ const message = first["message"];
1048
+ if (!message || typeof message["content"] !== "string") {
1049
+ throw new Error("message.content missing or not string");
1050
+ }
1051
+ text = message["content"];
1052
+ } catch (e) {
1053
+ throw new ProviderError(
1054
+ "parse",
1055
+ `${opts.provider}: unexpected response shape: ${JSON.stringify(r).slice(0, 200)}`
1056
+ );
1057
+ }
1058
+ const u = r["usage"] ?? {};
1059
+ const details = u["prompt_tokens_details"] ?? {};
1060
+ const cached = Math.trunc(Number(details["cached_tokens"] ?? 0)) || 0;
1061
+ const promptTokens = Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0;
1062
+ const reportedCompletion = Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0;
1063
+ const reportedTotal = Math.trunc(Number(u["total_tokens"] ?? 0)) || 0;
1064
+ const derivedCompletion = reportedTotal > promptTokens ? reportedTotal - promptTokens : reportedCompletion;
1065
+ const completionTokens = Math.max(reportedCompletion, derivedCompletion);
1066
+ const usage = {
1067
+ provider: opts.provider,
1068
+ model: opts.model,
1069
+ prompt_tokens: promptTokens,
1070
+ completion_tokens: completionTokens,
1071
+ cached_tokens: cached,
1072
+ // Use the response's reported total_tokens directly. Python's
1073
+ // `Usage.to_dict()` falls back to prompt+completion only when
1074
+ // total_tokens is 0/missing; mirror that.
1075
+ total_tokens: reportedTotal,
1076
+ cost_usd: 0,
1077
+ estimated: Object.keys(u).length === 0,
1078
+ purpose: opts.purpose
1079
+ };
1080
+ if (usage.total_tokens === 0) {
1081
+ usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1082
+ }
1083
+ return { text, usage };
1084
+ }
1085
+ function applyPricing3(usage, pricing) {
1086
+ const { cost_usd, estimated } = calculateCost(
1087
+ pricing,
1088
+ usage.provider,
1089
+ usage.model,
1090
+ usage.prompt_tokens,
1091
+ usage.completion_tokens,
1092
+ usage.cached_tokens
1093
+ );
1094
+ return {
1095
+ ...usage,
1096
+ cost_usd,
1097
+ estimated: usage.estimated || estimated
1098
+ };
1099
+ }
1100
+ async function sendOpenAICompatible(args) {
1101
+ const { url, headers, body } = buildOpenAICompatibleRequest({
1102
+ provider: args.provider,
1103
+ model: args.model,
1104
+ apiKey: args.apiKey,
1105
+ url: args.url,
1106
+ messages: args.messages,
1107
+ maxTokens: args.maxTokens,
1108
+ temperature: args.temperature,
1109
+ ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1110
+ });
1111
+ if (args.provider === "openai" && isReasoningModel("openai", args.model) && !/^o1(?:-|$)/i.test(args.model)) {
1112
+ const effort = (process.env["CROSSCHECK_OPENAI_REASONING_EFFORT"] ?? "low").toLowerCase();
1113
+ if (effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") {
1114
+ body.reasoning_effort = effort;
1115
+ }
1116
+ }
1117
+ if (isReasoningModel(args.provider, args.model) && // Only where the cap is a SHARED budget. On a provider that bounds visible
1118
+ // output only (xAI), headroom cannot protect the answer from being crowded
1119
+ // out — nothing is crowding it — so it would merely authorise a
1120
+ // 25,000-token reply and the bill that comes with it.
1121
+ reasoningSharesOutputBudget(args.provider) && typeof body.max_completion_tokens === "number") {
1122
+ const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1123
+ const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1124
+ body.max_completion_tokens += headroom;
1125
+ }
1126
+ const doFetch = args.fetchImpl ?? globalThis.fetch;
1127
+ const init = {
1128
+ method: "POST",
1129
+ headers,
1320
1130
  body: JSON.stringify(body)
1321
1131
  };
1322
1132
  if (args.signal) init.signal = args.signal;
@@ -1327,7 +1137,7 @@ async function sendGemini(args) {
1327
1137
  } catch (e) {
1328
1138
  const d = diagnoseNetworkError(e);
1329
1139
  if (!looksLikeNetworkFailure(d)) throw e;
1330
- throw new ProviderError(d.kind, networkErrorMessage("gemini", d), {
1140
+ throw new ProviderError(d.kind, networkErrorMessage(args.provider, d), {
1331
1141
  transient: d.transient
1332
1142
  });
1333
1143
  }
@@ -1337,374 +1147,808 @@ async function sendGemini(args) {
1337
1147
  try {
1338
1148
  parsed = await respLike.json();
1339
1149
  } catch (e) {
1340
- throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
1150
+ throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
1341
1151
  }
1342
- const { text, usage, finishReason } = parseGeminiResponse({
1152
+ const { text, usage } = parseOpenAICompatibleResponse({
1343
1153
  resp: parsed,
1154
+ provider: args.provider,
1344
1155
  model: args.model,
1345
1156
  purpose: args.purpose ?? "worker"
1346
1157
  });
1347
- if (text.trim() === "" && finishReason === "MAX_TOKENS") {
1348
- throw new ProviderError(
1349
- "parse",
1350
- `gemini: the answer was cut off before any visible text \u2014 thinking used the whole output budget (${usage.completion_tokens} tokens). Raise CROSSCHECK_GEMINI_THINKING_HEADROOM_TOKENS or the purpose's budget.`,
1351
- { transient: false }
1352
- );
1353
- }
1354
- return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
1158
+ return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1355
1159
  }
1356
1160
  const bodyText = await respLike.text().catch(() => "");
1357
- throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike), args.model);
1161
+ throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike), args.model);
1358
1162
  };
1359
- await acquireRateLimit("gemini", args.signal ? { signal: args.signal } : void 0);
1163
+ await acquireRateLimit(args.provider, args.signal ? { signal: args.signal } : void 0);
1360
1164
  return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1361
1165
  }
1362
1166
 
1363
- // src/providers/openai-compatible.ts
1364
- var OPENAI_COMPAT_DEFAULT_URLS = {
1365
- openai: "https://api.openai.com/v1/chat/completions",
1366
- xai: "https://api.x.ai/v1/chat/completions",
1367
- mistral: "https://api.mistral.ai/v1/chat/completions",
1368
- groq: "https://api.groq.com/openai/v1/chat/completions",
1369
- deepseek: "https://api.deepseek.com/v1/chat/completions",
1370
- // Moonshot AI (Kimi). OpenAI-compatible chat completions endpoint.
1371
- kimi: "https://api.moonshot.ai/v1/chat/completions",
1372
- // Alibaba Cloud DashScope (Qwen). International endpoint — the mainland
1373
- // China endpoint (dashscope.aliyuncs.com) 403s/404s from outside China in
1374
- // a way that can look like a bad key; this is the deliberate default for
1375
- // a US-based deployment.
1376
- qwen: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions"
1167
+ // src/providers/registry.ts
1168
+ var DEFAULT_MODELS = {
1169
+ anthropic: "claude-opus-5-5",
1170
+ openai: "gpt-6-sol",
1171
+ xai: "grok-4-latest",
1172
+ mistral: "mistral-large-latest",
1173
+ groq: "llama-3.3-70b-versatile",
1174
+ deepseek: "deepseek-chat",
1175
+ gemini: "gemini-3.8-flash",
1176
+ kimi: "kimi-k3",
1177
+ qwen: "qwen3.8-max"
1178
+ };
1179
+ var DEFAULT_FALLBACKS = {
1180
+ // gpt-5.6 requires a billing-enabled account and is excluded from the free
1181
+ // tier, so gpt-5 remains the broadly-available floor. One hop, not a
1182
+ // ladder — an intermediate 5.5 step would just be another model to reason
1183
+ // about.
1184
+ openai: ["gpt-5"],
1185
+ // 3.8 Flash is recent enough that some projects won't have it yet.
1186
+ gemini: ["gemini-3.1-pro-preview"],
1187
+ // Opus 5.5 is new enough that an account could still be waiting on it.
1188
+ // Opus 5 is the same family one step back — and this chain is consulted
1189
+ // ONLY for a model-access failure, never for a bad key or a rate limit,
1190
+ // so it can't quietly downgrade anyone who simply hit a 429.
1191
+ anthropic: ["claude-opus-5"]
1192
+ };
1193
+ var MODEL_ENV_VARS = {
1194
+ anthropic: "ANTHROPIC_MODEL",
1195
+ openai: "OPENAI_MODEL",
1196
+ xai: "XAI_MODEL",
1197
+ mistral: "MISTRAL_MODEL",
1198
+ groq: "GROQ_MODEL",
1199
+ deepseek: "DEEPSEEK_MODEL",
1200
+ gemini: "GEMINI_MODEL",
1201
+ kimi: "KIMI_MODEL",
1202
+ qwen: "QWEN_MODEL"
1203
+ };
1204
+ function parseFallbackModels(raw) {
1205
+ if (!raw) return [];
1206
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
1207
+ }
1208
+ function fallbackModelsFor(env, provider) {
1209
+ const envVar = MODEL_ENV_VARS[provider];
1210
+ const fromEnv = envVar ? parseFallbackModels(env[`${envVar}_FALLBACKS`]) : [];
1211
+ if (fromEnv.length > 0) return fromEnv;
1212
+ const pinned = envVar ? env[envVar] : void 0;
1213
+ if (pinned && pinned !== DEFAULT_MODELS[provider]) return [];
1214
+ return DEFAULT_FALLBACKS[provider] ?? [];
1215
+ }
1216
+ var DEFAULT_EXCLUDED_PROVIDERS = ["mistral", "groq"];
1217
+ function excludedProviders(env) {
1218
+ const parse = (v) => (v ?? "").split(",").map((x) => x.trim().toLowerCase()).filter((x) => x !== "");
1219
+ const excluded = new Set(
1220
+ parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]).length > 0 ? parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]) : DEFAULT_EXCLUDED_PROVIDERS
1221
+ );
1222
+ for (const name of parse(env["CROSSCHECK_ENABLE_PROVIDERS"])) excluded.delete(name);
1223
+ return excluded;
1224
+ }
1225
+ function buildProviders(opts) {
1226
+ const out = {};
1227
+ const excluded = excludedProviders(opts.env);
1228
+ const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
1229
+ if (anthropicKey && !excluded.has("anthropic")) {
1230
+ const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
1231
+ out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
1232
+ }
1233
+ const openAiCompatSpec = [
1234
+ { name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
1235
+ { name: "xai", keyEnv: "XAI_API_KEY", modelEnv: "XAI_MODEL", defaultModel: DEFAULT_MODELS["xai"] },
1236
+ { name: "mistral", keyEnv: "MISTRAL_API_KEY", modelEnv: "MISTRAL_MODEL", defaultModel: DEFAULT_MODELS["mistral"] },
1237
+ { name: "groq", keyEnv: "GROQ_API_KEY", modelEnv: "GROQ_MODEL", defaultModel: DEFAULT_MODELS["groq"] },
1238
+ { name: "deepseek", keyEnv: "DEEPSEEK_API_KEY", modelEnv: "DEEPSEEK_MODEL", defaultModel: DEFAULT_MODELS["deepseek"] },
1239
+ { name: "kimi", keyEnv: "KIMI_API_KEY", modelEnv: "KIMI_MODEL", defaultModel: DEFAULT_MODELS["kimi"] },
1240
+ { name: "qwen", keyEnv: "QWEN_API_KEY", modelEnv: "QWEN_MODEL", defaultModel: DEFAULT_MODELS["qwen"] }
1241
+ ];
1242
+ for (const s of openAiCompatSpec) {
1243
+ const apiKey = opts.env[s.keyEnv];
1244
+ if (!apiKey) continue;
1245
+ if (excluded.has(s.name)) continue;
1246
+ const model = opts.env[s.modelEnv] ?? s.defaultModel;
1247
+ out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
1248
+ }
1249
+ const geminiKey = opts.env["GEMINI_API_KEY"];
1250
+ if (geminiKey && !excluded.has("gemini")) {
1251
+ const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
1252
+ out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
1253
+ }
1254
+ return out;
1255
+ }
1256
+ function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
1257
+ return {
1258
+ name: "anthropic",
1259
+ model,
1260
+ fallbackModels,
1261
+ send: async (args) => {
1262
+ const sendOpts = {
1263
+ ...args,
1264
+ apiKey,
1265
+ model: args.modelOverride ?? model,
1266
+ pricing: opts.pricing
1267
+ };
1268
+ if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1269
+ return sendAnthropic(sendOpts);
1270
+ }
1271
+ };
1272
+ }
1273
+ function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
1274
+ const url = OPENAI_COMPAT_DEFAULT_URLS[name];
1275
+ return {
1276
+ name,
1277
+ model,
1278
+ fallbackModels,
1279
+ send: async (args) => {
1280
+ const sendOpts = {
1281
+ ...args,
1282
+ provider: name,
1283
+ apiKey,
1284
+ model: args.modelOverride ?? model,
1285
+ url,
1286
+ pricing: opts.pricing
1287
+ };
1288
+ if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1289
+ return sendOpenAICompatible(sendOpts);
1290
+ }
1291
+ };
1292
+ }
1293
+ function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
1294
+ return {
1295
+ name: "gemini",
1296
+ model,
1297
+ fallbackModels,
1298
+ send: async (args) => {
1299
+ const sendOpts = {
1300
+ ...args,
1301
+ apiKey,
1302
+ model: args.modelOverride ?? model,
1303
+ pricing: opts.pricing
1304
+ };
1305
+ if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1306
+ return sendGemini(sendOpts);
1307
+ }
1308
+ };
1309
+ }
1310
+
1311
+ // src/core/reasoning-upgrade.ts
1312
+ var UPGRADE_MODEL = "claude-fable-5";
1313
+ var UPGRADE_LABEL = "Fable 5";
1314
+ function isUpgradeRequested(args) {
1315
+ if (args["upgrade"] === true) return true;
1316
+ const reasoning = args["reasoning"];
1317
+ if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
1318
+ return true;
1319
+ }
1320
+ const model = args["model"];
1321
+ if (typeof model === "string") {
1322
+ const m = model.trim().toLowerCase();
1323
+ if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
1324
+ return true;
1325
+ }
1326
+ }
1327
+ return false;
1328
+ }
1329
+ var HEAVY_TERMS = [
1330
+ /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
1331
+ /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
1332
+ /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
1333
+ /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
1334
+ /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
1335
+ /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
1336
+ /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
1337
+ /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
1338
+ /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
1339
+ /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
1340
+ /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
1341
+ /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
1342
+ ];
1343
+ var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
1344
+ function assessReasoning(text) {
1345
+ const s = typeof text === "string" ? text : "";
1346
+ const signals = [];
1347
+ let score = 0;
1348
+ let termHits = 0;
1349
+ for (const re of HEAVY_TERMS) {
1350
+ if (re.test(s)) termHits += 1;
1351
+ }
1352
+ if (termHits > 0) {
1353
+ const add = Math.min(termHits, 4);
1354
+ score += add;
1355
+ signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
1356
+ }
1357
+ if (DIFFICULTY_TERMS.test(s)) {
1358
+ score += 1;
1359
+ signals.push("explicit difficulty marker");
1360
+ }
1361
+ const words = s.split(/\s+/).filter(Boolean).length;
1362
+ if (words >= 120) {
1363
+ score += 2;
1364
+ signals.push("long, detailed prompt");
1365
+ } else if (words >= 50) {
1366
+ score += 1;
1367
+ signals.push("substantial prompt");
1368
+ }
1369
+ const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
1370
+ if (constraints >= 3) {
1371
+ score += 1;
1372
+ signals.push(`${constraints} constraints/steps`);
1373
+ }
1374
+ const heavy = score >= 3;
1375
+ const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
1376
+ return { heavy, score, reason, signals };
1377
+ }
1378
+ function buildSuggestedUpgrade(toolName, assessment) {
1379
+ return {
1380
+ model: UPGRADE_MODEL,
1381
+ label: UPGRADE_LABEL,
1382
+ reason: assessment.reason,
1383
+ how: `re-run \`${toolName}\` with reasoning:"max" (or upgrade:true) to route the lead reasoning seat to ${UPGRADE_LABEL}, and \u2014 when an OpenAI key is configured \u2014 add an OpenAI second-opinion pass that ${UPGRADE_LABEL} weighs before finalizing (the result names which model ran). Other panel seats stay on their normal models.`
1384
+ };
1385
+ }
1386
+
1387
+ // src/core/super-mode.ts
1388
+ var SUPER_MODELS = {
1389
+ // Fable 5.1 verified present on the account; 5 as the fallback for accounts
1390
+ // that haven't been granted 5.1 yet.
1391
+ anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
1392
+ // Astra is live (verified against the account's model list 2026-09-23).
1393
+ // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
1394
+ // that constant means "OpenAI's top reasoning model" for the max-reasoning
1395
+ // co-reasoner, which is a different question from "what should super fall
1396
+ // back to". Sharing the symbol silently coupled two unrelated decisions.
1397
+ openai: ["gpt-6-astra", "gpt-6-sol"],
1398
+ // Explicitly left as-is per product decision. Note grok-4-latest is not in
1399
+ // xAI's published model list, but it resolves in practice — real panel calls
1400
+ // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
1401
+ // rather than a dead id.
1402
+ xai: [DEFAULT_MODELS["xai"]],
1403
+ // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
1404
+ // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
1405
+ // repeat the same model and retry one that just failed. Named explicitly.
1406
+ gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
1407
+ kimi: [DEFAULT_MODELS["kimi"]],
1408
+ qwen: [DEFAULT_MODELS["qwen"]]
1377
1409
  };
1378
- var OPENAI_COMPAT_NATIVE_STRUCTURED = /* @__PURE__ */ new Set([
1379
- "openai"
1380
- ]);
1381
- function supportsNativeJsonSchema(provider, model) {
1382
- if (!OPENAI_COMPAT_NATIVE_STRUCTURED.has(provider.toLowerCase())) {
1383
- return false;
1410
+ var SUPER_ENV_VARS = {
1411
+ anthropic: "ANTHROPIC_SUPER_MODEL",
1412
+ openai: "OPENAI_SUPER_MODEL",
1413
+ xai: "XAI_SUPER_MODEL",
1414
+ gemini: "GEMINI_SUPER_MODEL",
1415
+ kimi: "KIMI_SUPER_MODEL",
1416
+ qwen: "QWEN_SUPER_MODEL",
1417
+ mistral: "MISTRAL_SUPER_MODEL",
1418
+ groq: "GROQ_SUPER_MODEL",
1419
+ deepseek: "DEEPSEEK_SUPER_MODEL"
1420
+ };
1421
+ function parseChain(v) {
1422
+ if (!v) return [];
1423
+ const out = [];
1424
+ for (const raw of v.split(",")) {
1425
+ const m = raw.trim();
1426
+ if (m && !out.includes(m)) out.push(m);
1384
1427
  }
1385
- const m = model.toLowerCase();
1386
- if (/^o1(?:-|$)/.test(m)) return false;
1387
- return true;
1428
+ return out;
1388
1429
  }
1389
- function buildOpenAICompatibleRequest(opts) {
1390
- const msgs = opts.messages.map((m) => {
1391
- const text = typeof m.content === "string" ? m.content : "";
1392
- const images = imagesOf(m);
1393
- return images.length ? { role: m.role, content: [{ type: "text", text }, ...images.map((i) => ({ type: "image_url", image_url: { url: `data:${i.mediaType};base64,${i.data}` } }))] } : { role: m.role, content: text };
1394
- });
1395
- const body = {
1396
- model: opts.model,
1397
- messages: msgs
1430
+ var superEnvStore = new AsyncLocalStorage();
1431
+ var currentEnv = () => superEnvStore.getStore() ?? process.env;
1432
+ function superChainFor(provider, env = currentEnv()) {
1433
+ const varName = SUPER_ENV_VARS[provider];
1434
+ const user = varName ? parseChain(env[varName]) : [];
1435
+ if (user.length > 0) {
1436
+ const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
1437
+ return [...user, ...fallbacks.filter((m) => !user.includes(m))];
1438
+ }
1439
+ return SUPER_MODELS[provider] ?? [];
1440
+ }
1441
+ function superPrimary(provider, env = currentEnv()) {
1442
+ return superChainFor(provider, env)[0];
1443
+ }
1444
+ var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
1445
+ function isSuperRequested(args) {
1446
+ return args["super"] === true;
1447
+ }
1448
+ function retargetForSuper(p, env = currentEnv()) {
1449
+ const chain = superChainFor(p.name, env);
1450
+ if (chain.length === 0) return p;
1451
+ const [primary, ...fallbacks] = chain;
1452
+ return retargetProvider(p, primary, fallbacks);
1453
+ }
1454
+ var SUPER_BUDGET_MULTIPLIER = 3;
1455
+ var SUPER_BUDGET_CEILING = 16e3;
1456
+ function superBudget(base) {
1457
+ return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
1458
+ }
1459
+
1460
+ // src/core/moderator.ts
1461
+ var DEFAULT_MODERATOR = "anthropic";
1462
+ var PROVIDER_ALIASES = {
1463
+ grok: "xai",
1464
+ claude: "anthropic",
1465
+ google: "gemini",
1466
+ chatgpt: "openai",
1467
+ gpt: "openai",
1468
+ moonshot: "kimi"
1469
+ };
1470
+ function parseModeratorSpec(raw) {
1471
+ if (typeof raw !== "string") return null;
1472
+ const s = raw.trim();
1473
+ if (!s) return null;
1474
+ const i = s.indexOf(":");
1475
+ const providerPart = (i < 0 ? s : s.slice(0, i)).trim().toLowerCase();
1476
+ const modelPart = i < 0 ? "" : s.slice(i + 1).trim();
1477
+ if (!providerPart) return null;
1478
+ return {
1479
+ provider: PROVIDER_ALIASES[providerPart] ?? providerPart,
1480
+ model: modelPart || null
1398
1481
  };
1399
- if (supportsTemperature(opts.provider, opts.model)) {
1400
- body.max_tokens = opts.maxTokens;
1401
- body.temperature = opts.temperature;
1402
- } else {
1403
- body.max_completion_tokens = opts.maxTokens;
1482
+ }
1483
+ function pickModeratorSpec(...candidates) {
1484
+ for (const c of candidates) {
1485
+ const s = parseModeratorSpec(c);
1486
+ if (s) return s;
1487
+ }
1488
+ return { provider: DEFAULT_MODERATOR, model: null };
1489
+ }
1490
+ function resolveModerator(providers, ...candidates) {
1491
+ const spec = pickModeratorSpec(...candidates);
1492
+ const base = providers[spec.provider];
1493
+ if (!base) return { provider: void 0, spec, pinned: false };
1494
+ if (!spec.model) return { provider: base, spec, pinned: false };
1495
+ return { provider: retargetProvider(base, spec.model), spec, pinned: true };
1496
+ }
1497
+ function synthesisSeat(o) {
1498
+ const { moderator, pinned, superRequested, upgradeRequested } = o;
1499
+ const wantsMore = superRequested || upgradeRequested;
1500
+ if (!wantsMore) return { provider: moderator, upgraded: false, label: null, skipReason: null };
1501
+ if (pinned) {
1502
+ return {
1503
+ provider: moderator,
1504
+ upgraded: false,
1505
+ label: null,
1506
+ skipReason: `the moderator is pinned to ${moderator.name}:${moderator.model}; a pinned model is used as-is.`
1507
+ };
1404
1508
  }
1405
- if (opts.jsonSchema && supportsNativeJsonSchema(opts.provider, opts.model)) {
1406
- body.response_format = {
1407
- type: "json_schema",
1408
- json_schema: {
1409
- name: "structured_output",
1410
- schema: opts.jsonSchema,
1411
- // NOT strict. Strict mode requires `additionalProperties: false` on
1412
- // every object and every property listed in `required` — the
1413
- // engine's schemas don't meet that, so every structured OpenAI call
1414
- // (planner, judges, synthesis) was a 400, unnoticed until the canary
1415
- // grew a structured check (2026-10-11). Non-strict still shapes the
1416
- // reply to the schema, and structured.ts validates it against the
1417
- // full schema regardless, so strictness added a failure mode and no
1418
- // safety.
1419
- strict: false
1420
- }
1509
+ if (moderator.name === "anthropic" && !superRequested) {
1510
+ return { provider: retargetProvider(moderator, UPGRADE_MODEL), upgraded: true, label: UPGRADE_LABEL, skipReason: null };
1511
+ }
1512
+ if (superChainFor(moderator.name).length === 0) {
1513
+ return {
1514
+ provider: moderator,
1515
+ upgraded: false,
1516
+ label: null,
1517
+ skipReason: `no flagship model is known for ${moderator.name}; the moderator ran at ${moderator.model}.`
1421
1518
  };
1422
1519
  }
1423
- const headers = {
1424
- "content-type": "application/json",
1425
- Authorization: `Bearer ${opts.apiKey}`
1426
- };
1427
- return { url: opts.url, headers, body };
1520
+ const seat = retargetForSuper(moderator);
1521
+ const label = moderator.name === "anthropic" ? UPGRADE_LABEL : seat.model;
1522
+ return { provider: seat, upgraded: true, label, skipReason: null };
1428
1523
  }
1429
- function parseOpenAICompatibleResponse(opts) {
1430
- const r = opts.resp ?? {};
1431
- let text;
1432
- try {
1433
- const choices = r["choices"];
1434
- if (!Array.isArray(choices) || choices.length === 0) {
1435
- throw new Error("choices missing or empty");
1524
+
1525
+ // ../../config/pricing.json
1526
+ var pricing_default = {
1527
+ _meta: {
1528
+ currency: "USD",
1529
+ unit: "per_1k_tokens",
1530
+ updated_at: "2026-09-06",
1531
+ notes: "Prices reflect public list pricing per provider. cached_per_1k applies to prompt tokens served from prompt-cache (Anthropic) or input-cache (OpenAI) when supported. Update when providers change rates. Missing models fall back to cost=0 with estimated=true. 2026-06-07: removed decommissioned gemini-2.0-flash / gemini-2.0-flash-lite (Google returns 404) and replaced the low-tier lite model with gemini-2.5-flash-lite. 2026-06-08: bumped the default Anthropic model to claude-opus-4-8. Kept gemini-2.5-pro as the Gemini default (gemini-3.1-pro-preview measured ~2.2x the cost at $2/$12 per M \u2014 output-token dominated by ~1.5k thinking tokens \u2014 so not worth it for a 1-of-4 panel voice). 2026-06-09: corrected rates verified against live provider pricing pages \u2014 gemini-2.5-pro output $5->$10/M; gemini-2.5-flash $0.15/$0.60 -> $0.30/$2.50; gpt-5 $5/$15 -> $1.25/$10 (cached $0.125); grok-4-latest input $5->$3/M (legacy grok-4 tier). Anthropic opus/sonnet/haiku rates confirmed unchanged. Older/unused models (gpt-4o, o1, o3, grok-3) not all re-verified. 2026-07-02: added claude-fable-5 as the optional reasoning-upgrade model (used only for the lead reasoning seat when the operator bumps a heavy task). 2026-07-04: set claude-fable-5 to its real pay-as-you-go API price effective 2026-07-07 \u2014 $10/M input ($0.01/1k), $50/M output ($0.05/1k), cached at 10% of input ($0.001/1k). Dropped the estimated flag. Note: Fable 5 is cheaper than opus-4-8 on both input and output. 2026-07-09: Google retired gemini-2.5-pro AND gemini-3-pro-preview (both now 404 'no longer available'). Moved the Gemini default to gemini-3.1-pro-preview ($2/M in, $12/M out, cache $0.20/M \u2014 the newest working pro; note it's a preview and may need another bump) and added gemini-3.5-flash ($1.50/M in, $9/M out, cache $0.15/M \u2014 GA) to the low/cheap tier. Removed the dead gemini-2.5-pro rate. IMPORTANT: gemini-3.x are heavy thinking models \u2014 thoughtsTokenCount (billed as output) is now folded into completion_tokens in the adapter, so cost reflects reasoning tokens. 2026-07-13: bumped the default OpenAI model to gpt-5.5 ($5/M in, $30/M out, cached $0.50/M \u2014 verified live). Note gpt-5.5 is markedly pricier than gpt-5 ($1.25/$10): ~4x input, 3x output. Kept gpt-5 in the table for cost lookups on pinned installs. 2026-07-16: added gpt-5.6 at the Sol (flagship) tier \u2014 $5/M in, $30/M out, cached $0.50/M (cache-write $6.25/M not modeled). gpt-5.6 is the second premium voice for the max-reasoning co-reasoner (runs alongside claude-fable-5 when an OpenAI key is set). Terra/Luna tiers not added (unused). 2026-07-17: added Moonshot AI (Kimi) provider with kimi-k3, base URL https://api.moonshot.ai/v1. Confirmed real rates \u2014 $3.00/M input (cache miss, $0.003/1k), $15.00/M output ($0.015/1k), $0.30/M cache-hit input ($0.0003/1k); 1M-token context. Kimi K3 is reasoning-class (temperature must be 1; uses max_completion_tokens). 2026-07-24: added claude-opus-5 ($5/M in, $25/M out, cached $0.50/M -- 10% of input, matching this table's existing cache-discount ratio) and made it the default anthropic model (was claude-opus-4-8). Fable 5 remains the reasoning-upgrade/super-mode target, unchanged. 2026-08-04: added Alibaba Cloud Qwen provider (OpenAI-compatible, DashScope international endpoint https://dashscope-intl.aliyuncs.com/compatible-mode/v1) with default model qwen3.8-max -- confirmed business pricing $2.00/M input ($0.002/1k), $6.00/M output ($0.006/1k), cache estimated at 10% of input ($0.0002/1k, not independently confirmed). Also added qwen3.7-max as a fallback tier at $1.25/M in ($0.00125/1k) / $3.75/M out ($0.00375/1k). Note: the model string initially configured (qwen-v3.8 / qwen-v3.7) does not exist on DashScope and 404s -- live-probed the real key against qwen3.8-max, qwen3.7-max, qwen3-max, qwen-max, and qwen-plus, all of which work; corrected the default and pricing keys to qwen3.8-max/qwen3.7-max and fixed QWEN_MODEL in both .env files to match. Qwen's Max tier is a standard instruct model, not reasoning-class -- normal temperature/max_tokens handling. 2026-09-06: added gpt-6-astra at published list rates ($10/$50 per Mtok, $1/Mtok cache read; the $12.50/Mtok cache-WRITE rate has no field in this schema and is not modelled). gemini-3.8-flash now carries Google's published paid-tier rates ($0.75/$3.75/$0.075 per Mtok). THESE DOUBLE ON 2027-01-01 ($1.50/$7.50/$0.15) and this schema has no effective-date support, so they must be updated by hand on that date or every Gemini cost will read half of actual. claude-fable-5-1 now carries published rates ($10/$50/$0.25 per Mtok). Note its cache-read rate is 4x cheaper than claude-fable-5's, so the two must not be assumed to track each other. No inferred rows remain. All three were previously absent, which silently recorded their cost as $0."
1532
+ },
1533
+ anthropic: {
1534
+ _source: {
1535
+ url: "https://platform.claude.com/docs/en/about-claude/pricing",
1536
+ verified_at: "2026-09-27"
1537
+ },
1538
+ "claude-opus-4-5": {
1539
+ prompt_per_1k: 5e-3,
1540
+ completion_per_1k: 0.025,
1541
+ cached_per_1k: 5e-4
1542
+ },
1543
+ "claude-opus-4-7": {
1544
+ prompt_per_1k: 5e-3,
1545
+ completion_per_1k: 0.025,
1546
+ cached_per_1k: 5e-4
1547
+ },
1548
+ "claude-opus-4-8": {
1549
+ prompt_per_1k: 5e-3,
1550
+ completion_per_1k: 0.025,
1551
+ cached_per_1k: 5e-4
1552
+ },
1553
+ "claude-opus-5-5": {
1554
+ prompt_per_1k: 4e-3,
1555
+ completion_per_1k: 0.02,
1556
+ cached_per_1k: 2e-4
1557
+ },
1558
+ "claude-opus-5": {
1559
+ prompt_per_1k: 5e-3,
1560
+ completion_per_1k: 0.025,
1561
+ cached_per_1k: 5e-4
1562
+ },
1563
+ "claude-fable-5": {
1564
+ prompt_per_1k: 0.01,
1565
+ completion_per_1k: 0.05,
1566
+ cached_per_1k: 1e-3
1567
+ },
1568
+ "claude-sonnet-4-5": {
1569
+ prompt_per_1k: 3e-3,
1570
+ completion_per_1k: 0.015,
1571
+ cached_per_1k: 3e-4
1572
+ },
1573
+ "claude-sonnet-4-6": {
1574
+ prompt_per_1k: 3e-3,
1575
+ completion_per_1k: 0.015,
1576
+ cached_per_1k: 3e-4
1577
+ },
1578
+ "claude-haiku-4-5": {
1579
+ prompt_per_1k: 1e-3,
1580
+ completion_per_1k: 5e-3,
1581
+ cached_per_1k: 1e-4
1582
+ },
1583
+ "claude-3-5-sonnet": {
1584
+ prompt_per_1k: 3e-3,
1585
+ completion_per_1k: 0.015,
1586
+ cached_per_1k: 3e-4
1587
+ },
1588
+ "claude-3-5-haiku": {
1589
+ prompt_per_1k: 8e-4,
1590
+ completion_per_1k: 4e-3,
1591
+ cached_per_1k: 8e-5
1592
+ },
1593
+ "claude-fable-5-1": {
1594
+ prompt_per_1k: 0.01,
1595
+ completion_per_1k: 0.05,
1596
+ cached_per_1k: 25e-5
1597
+ }
1598
+ },
1599
+ openai: {
1600
+ _source: {
1601
+ url: "https://developers.openai.com/api/docs/pricing",
1602
+ verified_at: "2026-09-27"
1603
+ },
1604
+ "gpt-6-astra": {
1605
+ prompt_per_1k: 0.01,
1606
+ completion_per_1k: 0.05,
1607
+ cached_per_1k: 1e-3
1608
+ },
1609
+ "gpt-6-sol": {
1610
+ prompt_per_1k: 2e-3,
1611
+ completion_per_1k: 0.01,
1612
+ cached_per_1k: 2e-4
1613
+ },
1614
+ "gpt-6-luna": {
1615
+ prompt_per_1k: 1e-4,
1616
+ completion_per_1k: 5e-4,
1617
+ cached_per_1k: 1e-5
1618
+ },
1619
+ "gpt-5.6-sol": {
1620
+ prompt_per_1k: 4e-3,
1621
+ completion_per_1k: 0.02,
1622
+ cached_per_1k: 4e-4
1623
+ },
1624
+ "gpt-5.6-terra": {
1625
+ prompt_per_1k: 2e-3,
1626
+ completion_per_1k: 0.012,
1627
+ cached_per_1k: 2e-4
1628
+ },
1629
+ "gpt-5.6-luna": {
1630
+ prompt_per_1k: 2e-4,
1631
+ completion_per_1k: 12e-4,
1632
+ cached_per_1k: 2e-5
1633
+ },
1634
+ "gpt-5.6": {
1635
+ prompt_per_1k: 4e-3,
1636
+ completion_per_1k: 0.02,
1637
+ cached_per_1k: 4e-4
1638
+ },
1639
+ "gpt-5.5": {
1640
+ prompt_per_1k: 5e-3,
1641
+ completion_per_1k: 0.03,
1642
+ cached_per_1k: 5e-4
1643
+ },
1644
+ "gpt-5": {
1645
+ prompt_per_1k: 125e-5,
1646
+ completion_per_1k: 0.01,
1647
+ cached_per_1k: 125e-6
1648
+ },
1649
+ "gpt-5-mini": {
1650
+ prompt_per_1k: 25e-5,
1651
+ completion_per_1k: 2e-3,
1652
+ cached_per_1k: 25e-6
1653
+ },
1654
+ "gpt-5-nano": {
1655
+ prompt_per_1k: 5e-5,
1656
+ completion_per_1k: 4e-4,
1657
+ cached_per_1k: 5e-6
1658
+ },
1659
+ "gpt-4o": {
1660
+ prompt_per_1k: 25e-4,
1661
+ completion_per_1k: 0.01,
1662
+ cached_per_1k: 125e-5
1663
+ },
1664
+ "gpt-4o-mini": {
1665
+ prompt_per_1k: 15e-5,
1666
+ completion_per_1k: 6e-4,
1667
+ cached_per_1k: 75e-6
1668
+ },
1669
+ o1: {
1670
+ prompt_per_1k: 0.015,
1671
+ completion_per_1k: 0.06,
1672
+ cached_per_1k: 75e-4
1673
+ },
1674
+ "o1-mini": {
1675
+ prompt_per_1k: 3e-3,
1676
+ completion_per_1k: 0.012,
1677
+ cached_per_1k: 15e-4
1678
+ },
1679
+ o3: {
1680
+ prompt_per_1k: 2e-3,
1681
+ completion_per_1k: 8e-3,
1682
+ cached_per_1k: 5e-4
1683
+ },
1684
+ "o3-mini": {
1685
+ prompt_per_1k: 11e-4,
1686
+ completion_per_1k: 44e-4,
1687
+ cached_per_1k: 55e-5
1688
+ },
1689
+ "gpt-5-pro": {
1690
+ prompt_per_1k: 0.015,
1691
+ completion_per_1k: 0.12,
1692
+ cached_per_1k: 0
1693
+ },
1694
+ "gpt-5.2-pro": {
1695
+ prompt_per_1k: 0.021,
1696
+ completion_per_1k: 0.168,
1697
+ cached_per_1k: 0
1698
+ },
1699
+ "gpt-5.4-pro": {
1700
+ prompt_per_1k: 0.03,
1701
+ completion_per_1k: 0.18,
1702
+ cached_per_1k: 0
1703
+ },
1704
+ "gpt-5.5-pro": {
1705
+ prompt_per_1k: 0.03,
1706
+ completion_per_1k: 0.18,
1707
+ cached_per_1k: 0
1708
+ },
1709
+ "o1-pro": {
1710
+ prompt_per_1k: 0.15,
1711
+ completion_per_1k: 0.6,
1712
+ cached_per_1k: 0
1713
+ },
1714
+ "o3-pro": {
1715
+ prompt_per_1k: 0.02,
1716
+ completion_per_1k: 0.08,
1717
+ cached_per_1k: 0
1436
1718
  }
1437
- const first = choices[0];
1438
- const message = first["message"];
1439
- if (!message || typeof message["content"] !== "string") {
1440
- throw new Error("message.content missing or not string");
1719
+ },
1720
+ xai: {
1721
+ "grok-4-latest": {
1722
+ prompt_per_1k: 3e-3,
1723
+ completion_per_1k: 0.015,
1724
+ cached_per_1k: 75e-5
1725
+ },
1726
+ "grok-3": {
1727
+ prompt_per_1k: 3e-3,
1728
+ completion_per_1k: 0.015,
1729
+ cached_per_1k: 15e-4
1730
+ },
1731
+ "grok-3-mini": {
1732
+ prompt_per_1k: 3e-4,
1733
+ completion_per_1k: 5e-4,
1734
+ cached_per_1k: 15e-5
1441
1735
  }
1442
- text = message["content"];
1443
- } catch (e) {
1444
- throw new ProviderError(
1445
- "parse",
1446
- `${opts.provider}: unexpected response shape: ${JSON.stringify(r).slice(0, 200)}`
1447
- );
1448
- }
1449
- const u = r["usage"] ?? {};
1450
- const details = u["prompt_tokens_details"] ?? {};
1451
- const cached = Math.trunc(Number(details["cached_tokens"] ?? 0)) || 0;
1452
- const promptTokens = Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0;
1453
- const reportedCompletion = Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0;
1454
- const reportedTotal = Math.trunc(Number(u["total_tokens"] ?? 0)) || 0;
1455
- const derivedCompletion = reportedTotal > promptTokens ? reportedTotal - promptTokens : reportedCompletion;
1456
- const completionTokens = Math.max(reportedCompletion, derivedCompletion);
1457
- const usage = {
1458
- provider: opts.provider,
1459
- model: opts.model,
1460
- prompt_tokens: promptTokens,
1461
- completion_tokens: completionTokens,
1462
- cached_tokens: cached,
1463
- // Use the response's reported total_tokens directly. Python's
1464
- // `Usage.to_dict()` falls back to prompt+completion only when
1465
- // total_tokens is 0/missing; mirror that.
1466
- total_tokens: reportedTotal,
1467
- cost_usd: 0,
1468
- estimated: Object.keys(u).length === 0,
1469
- purpose: opts.purpose
1470
- };
1471
- if (usage.total_tokens === 0) {
1472
- usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1473
- }
1474
- return { text, usage };
1475
- }
1476
- function applyPricing3(usage, pricing) {
1477
- const { cost_usd, estimated } = calculateCost(
1478
- pricing,
1479
- usage.provider,
1480
- usage.model,
1481
- usage.prompt_tokens,
1482
- usage.completion_tokens,
1483
- usage.cached_tokens
1484
- );
1485
- return {
1486
- ...usage,
1487
- cost_usd,
1488
- estimated: usage.estimated || estimated
1489
- };
1490
- }
1491
- async function sendOpenAICompatible(args) {
1492
- const { url, headers, body } = buildOpenAICompatibleRequest({
1493
- provider: args.provider,
1494
- model: args.model,
1495
- apiKey: args.apiKey,
1496
- url: args.url,
1497
- messages: args.messages,
1498
- maxTokens: args.maxTokens,
1499
- temperature: args.temperature,
1500
- ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1501
- });
1502
- if (args.provider === "openai" && isReasoningModel("openai", args.model) && !/^o1(?:-|$)/i.test(args.model)) {
1503
- const effort = (process.env["CROSSCHECK_OPENAI_REASONING_EFFORT"] ?? "low").toLowerCase();
1504
- if (effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") {
1505
- body.reasoning_effort = effort;
1736
+ },
1737
+ gemini: {
1738
+ "gemini-3.1-pro-preview": {
1739
+ prompt_per_1k: 2e-3,
1740
+ completion_per_1k: 0.012,
1741
+ cached_per_1k: 2e-4
1742
+ },
1743
+ "gemini-3.5-flash": {
1744
+ prompt_per_1k: 15e-4,
1745
+ completion_per_1k: 9e-3,
1746
+ cached_per_1k: 15e-5
1747
+ },
1748
+ "gemini-2.5-flash": {
1749
+ prompt_per_1k: 3e-4,
1750
+ completion_per_1k: 25e-4,
1751
+ cached_per_1k: 75e-6
1752
+ },
1753
+ "gemini-2.5-flash-lite": {
1754
+ prompt_per_1k: 1e-4,
1755
+ completion_per_1k: 4e-4,
1756
+ cached_per_1k: 25e-6
1757
+ },
1758
+ "gemini-3.8-flash": {
1759
+ prompt_per_1k: 75e-5,
1760
+ completion_per_1k: 375e-5,
1761
+ cached_per_1k: 75e-6
1506
1762
  }
1507
- }
1508
- if (isReasoningModel(args.provider, args.model) && // Only where the cap is a SHARED budget. On a provider that bounds visible
1509
- // output only (xAI), headroom cannot protect the answer from being crowded
1510
- // out — nothing is crowding it — so it would merely authorise a
1511
- // 25,000-token reply and the bill that comes with it.
1512
- reasoningSharesOutputBudget(args.provider) && typeof body.max_completion_tokens === "number") {
1513
- const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1514
- const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1515
- body.max_completion_tokens += headroom;
1516
- }
1517
- const doFetch = args.fetchImpl ?? globalThis.fetch;
1518
- const init = {
1519
- method: "POST",
1520
- headers,
1521
- body: JSON.stringify(body)
1522
- };
1523
- if (args.signal) init.signal = args.signal;
1524
- const attemptOnce = async () => {
1525
- let respLike;
1526
- try {
1527
- respLike = await doFetch(url, init);
1528
- } catch (e) {
1529
- const d = diagnoseNetworkError(e);
1530
- if (!looksLikeNetworkFailure(d)) throw e;
1531
- throw new ProviderError(d.kind, networkErrorMessage(args.provider, d), {
1532
- transient: d.transient
1533
- });
1763
+ },
1764
+ mistral: {
1765
+ "mistral-large-latest": {
1766
+ prompt_per_1k: 2e-3,
1767
+ completion_per_1k: 6e-3,
1768
+ cached_per_1k: 2e-4
1769
+ },
1770
+ "mistral-small-latest": {
1771
+ prompt_per_1k: 2e-4,
1772
+ completion_per_1k: 6e-4,
1773
+ cached_per_1k: 2e-5
1534
1774
  }
1535
- const status = respLike.status;
1536
- if (status >= 200 && status < 300) {
1537
- let parsed;
1538
- try {
1539
- parsed = await respLike.json();
1540
- } catch (e) {
1541
- throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
1542
- }
1543
- const { text, usage } = parseOpenAICompatibleResponse({
1544
- resp: parsed,
1545
- provider: args.provider,
1546
- model: args.model,
1547
- purpose: args.purpose ?? "worker"
1548
- });
1549
- return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1775
+ },
1776
+ groq: {
1777
+ "llama-3.3-70b-versatile": {
1778
+ prompt_per_1k: 59e-5,
1779
+ completion_per_1k: 79e-5,
1780
+ cached_per_1k: 295e-6
1781
+ },
1782
+ "llama-3.1-8b-instant": {
1783
+ prompt_per_1k: 5e-5,
1784
+ completion_per_1k: 8e-5,
1785
+ cached_per_1k: 25e-6
1550
1786
  }
1551
- const bodyText = await respLike.text().catch(() => "");
1552
- throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike), args.model);
1553
- };
1554
- await acquireRateLimit(args.provider, args.signal ? { signal: args.signal } : void 0);
1555
- return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1556
- }
1557
-
1558
- // src/providers/registry.ts
1559
- var DEFAULT_MODELS = {
1560
- anthropic: "claude-opus-5-5",
1561
- openai: "gpt-6-sol",
1562
- xai: "grok-4-latest",
1563
- mistral: "mistral-large-latest",
1564
- groq: "llama-3.3-70b-versatile",
1565
- deepseek: "deepseek-chat",
1566
- gemini: "gemini-3.8-flash",
1567
- kimi: "kimi-k3",
1568
- qwen: "qwen3.8-max"
1569
- };
1570
- var DEFAULT_FALLBACKS = {
1571
- // gpt-5.6 requires a billing-enabled account and is excluded from the free
1572
- // tier, so gpt-5 remains the broadly-available floor. One hop, not a
1573
- // ladder — an intermediate 5.5 step would just be another model to reason
1574
- // about.
1575
- openai: ["gpt-5"],
1576
- // 3.8 Flash is recent enough that some projects won't have it yet.
1577
- gemini: ["gemini-3.1-pro-preview"],
1578
- // Opus 5.5 is new enough that an account could still be waiting on it.
1579
- // Opus 5 is the same family one step back — and this chain is consulted
1580
- // ONLY for a model-access failure, never for a bad key or a rate limit,
1581
- // so it can't quietly downgrade anyone who simply hit a 429.
1582
- anthropic: ["claude-opus-5"]
1583
- };
1584
- var MODEL_ENV_VARS = {
1585
- anthropic: "ANTHROPIC_MODEL",
1586
- openai: "OPENAI_MODEL",
1587
- xai: "XAI_MODEL",
1588
- mistral: "MISTRAL_MODEL",
1589
- groq: "GROQ_MODEL",
1590
- deepseek: "DEEPSEEK_MODEL",
1591
- gemini: "GEMINI_MODEL",
1592
- kimi: "KIMI_MODEL",
1593
- qwen: "QWEN_MODEL"
1594
- };
1595
- function parseFallbackModels(raw) {
1596
- if (!raw) return [];
1597
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
1598
- }
1599
- function fallbackModelsFor(env, provider) {
1600
- const envVar = MODEL_ENV_VARS[provider];
1601
- const fromEnv = envVar ? parseFallbackModels(env[`${envVar}_FALLBACKS`]) : [];
1602
- if (fromEnv.length > 0) return fromEnv;
1603
- const pinned = envVar ? env[envVar] : void 0;
1604
- if (pinned && pinned !== DEFAULT_MODELS[provider]) return [];
1605
- return DEFAULT_FALLBACKS[provider] ?? [];
1606
- }
1607
- var DEFAULT_EXCLUDED_PROVIDERS = ["mistral", "groq"];
1608
- function excludedProviders(env) {
1609
- const parse = (v) => (v ?? "").split(",").map((x) => x.trim().toLowerCase()).filter((x) => x !== "");
1610
- const excluded = new Set(
1611
- parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]).length > 0 ? parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]) : DEFAULT_EXCLUDED_PROVIDERS
1612
- );
1613
- for (const name of parse(env["CROSSCHECK_ENABLE_PROVIDERS"])) excluded.delete(name);
1614
- return excluded;
1615
- }
1616
- function buildProviders(opts) {
1617
- const out = {};
1618
- const excluded = excludedProviders(opts.env);
1619
- const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
1620
- if (anthropicKey && !excluded.has("anthropic")) {
1621
- const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
1622
- out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
1623
- }
1624
- const openAiCompatSpec = [
1625
- { name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
1626
- { name: "xai", keyEnv: "XAI_API_KEY", modelEnv: "XAI_MODEL", defaultModel: DEFAULT_MODELS["xai"] },
1627
- { name: "mistral", keyEnv: "MISTRAL_API_KEY", modelEnv: "MISTRAL_MODEL", defaultModel: DEFAULT_MODELS["mistral"] },
1628
- { name: "groq", keyEnv: "GROQ_API_KEY", modelEnv: "GROQ_MODEL", defaultModel: DEFAULT_MODELS["groq"] },
1629
- { name: "deepseek", keyEnv: "DEEPSEEK_API_KEY", modelEnv: "DEEPSEEK_MODEL", defaultModel: DEFAULT_MODELS["deepseek"] },
1630
- { name: "kimi", keyEnv: "KIMI_API_KEY", modelEnv: "KIMI_MODEL", defaultModel: DEFAULT_MODELS["kimi"] },
1631
- { name: "qwen", keyEnv: "QWEN_API_KEY", modelEnv: "QWEN_MODEL", defaultModel: DEFAULT_MODELS["qwen"] }
1632
- ];
1633
- for (const s of openAiCompatSpec) {
1634
- const apiKey = opts.env[s.keyEnv];
1635
- if (!apiKey) continue;
1636
- if (excluded.has(s.name)) continue;
1637
- const model = opts.env[s.modelEnv] ?? s.defaultModel;
1638
- out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
1639
- }
1640
- const geminiKey = opts.env["GEMINI_API_KEY"];
1641
- if (geminiKey && !excluded.has("gemini")) {
1642
- const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
1643
- out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
1644
- }
1645
- return out;
1646
- }
1647
- function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
1648
- return {
1649
- name: "anthropic",
1650
- model,
1651
- fallbackModels,
1652
- send: async (args) => {
1653
- const sendOpts = {
1654
- ...args,
1655
- apiKey,
1656
- model: args.modelOverride ?? model,
1657
- pricing: opts.pricing
1658
- };
1659
- if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1660
- return sendAnthropic(sendOpts);
1787
+ },
1788
+ deepseek: {
1789
+ "deepseek-chat": {
1790
+ prompt_per_1k: 27e-5,
1791
+ completion_per_1k: 11e-4,
1792
+ cached_per_1k: 7e-5
1793
+ },
1794
+ "deepseek-reasoner": {
1795
+ prompt_per_1k: 55e-5,
1796
+ completion_per_1k: 22e-4,
1797
+ cached_per_1k: 14e-5
1661
1798
  }
1662
- };
1663
- }
1664
- function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
1665
- const url = OPENAI_COMPAT_DEFAULT_URLS[name];
1666
- return {
1667
- name,
1668
- model,
1669
- fallbackModels,
1670
- send: async (args) => {
1671
- const sendOpts = {
1672
- ...args,
1673
- provider: name,
1674
- apiKey,
1675
- model: args.modelOverride ?? model,
1676
- url,
1677
- pricing: opts.pricing
1678
- };
1679
- if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1680
- return sendOpenAICompatible(sendOpts);
1799
+ },
1800
+ kimi: {
1801
+ "kimi-k3": {
1802
+ prompt_per_1k: 3e-3,
1803
+ completion_per_1k: 0.015,
1804
+ cached_per_1k: 3e-4
1681
1805
  }
1682
- };
1683
- }
1684
- function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
1685
- return {
1686
- name: "gemini",
1687
- model,
1688
- fallbackModels,
1689
- send: async (args) => {
1690
- const sendOpts = {
1691
- ...args,
1692
- apiKey,
1693
- model: args.modelOverride ?? model,
1694
- pricing: opts.pricing
1695
- };
1696
- if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1697
- return sendGemini(sendOpts);
1806
+ },
1807
+ qwen: {
1808
+ "qwen3.8-max": {
1809
+ prompt_per_1k: 2e-3,
1810
+ completion_per_1k: 6e-3,
1811
+ cached_per_1k: 2e-4
1812
+ },
1813
+ "qwen3.7-max": {
1814
+ prompt_per_1k: 125e-5,
1815
+ completion_per_1k: 375e-5,
1816
+ cached_per_1k: 125e-6
1817
+ }
1818
+ },
1819
+ _tiers: {
1820
+ low: {
1821
+ description: "Cheap, fast models for simple subtasks (extraction, formatting, short summaries).",
1822
+ models: [
1823
+ {
1824
+ provider: "openai",
1825
+ model: "gpt-4o-mini"
1826
+ },
1827
+ {
1828
+ provider: "openai",
1829
+ model: "gpt-5-mini"
1830
+ },
1831
+ {
1832
+ provider: "anthropic",
1833
+ model: "claude-haiku-4-5"
1834
+ },
1835
+ {
1836
+ provider: "anthropic",
1837
+ model: "claude-3-5-haiku"
1838
+ },
1839
+ {
1840
+ provider: "gemini",
1841
+ model: "gemini-3.5-flash"
1842
+ },
1843
+ {
1844
+ provider: "gemini",
1845
+ model: "gemini-2.5-flash"
1846
+ },
1847
+ {
1848
+ provider: "gemini",
1849
+ model: "gemini-2.5-flash-lite"
1850
+ },
1851
+ {
1852
+ provider: "xai",
1853
+ model: "grok-3-mini"
1854
+ },
1855
+ {
1856
+ provider: "groq",
1857
+ model: "llama-3.1-8b-instant"
1858
+ },
1859
+ {
1860
+ provider: "deepseek",
1861
+ model: "deepseek-chat"
1862
+ }
1863
+ ]
1864
+ },
1865
+ med: {
1866
+ description: "Mid-tier models for reasoning, code, multi-step thinking.",
1867
+ models: [
1868
+ {
1869
+ provider: "anthropic",
1870
+ model: "claude-sonnet-4-5"
1871
+ },
1872
+ {
1873
+ provider: "anthropic",
1874
+ model: "claude-sonnet-4-6"
1875
+ },
1876
+ {
1877
+ provider: "openai",
1878
+ model: "gpt-4o"
1879
+ },
1880
+ {
1881
+ provider: "openai",
1882
+ model: "o3-mini"
1883
+ },
1884
+ {
1885
+ provider: "gemini",
1886
+ model: "gemini-3.1-pro-preview"
1887
+ },
1888
+ {
1889
+ provider: "xai",
1890
+ model: "grok-3"
1891
+ },
1892
+ {
1893
+ provider: "mistral",
1894
+ model: "mistral-large-latest"
1895
+ },
1896
+ {
1897
+ provider: "deepseek",
1898
+ model: "deepseek-reasoner"
1899
+ },
1900
+ {
1901
+ provider: "groq",
1902
+ model: "llama-3.3-70b-versatile"
1903
+ }
1904
+ ]
1905
+ },
1906
+ high: {
1907
+ description: "Most capable models for hard reasoning, planning, audits.",
1908
+ models: [
1909
+ {
1910
+ provider: "anthropic",
1911
+ model: "claude-opus-4-5"
1912
+ },
1913
+ {
1914
+ provider: "anthropic",
1915
+ model: "claude-opus-4-7"
1916
+ },
1917
+ {
1918
+ provider: "anthropic",
1919
+ model: "claude-fable-5"
1920
+ },
1921
+ {
1922
+ provider: "openai",
1923
+ model: "gpt-5.5"
1924
+ },
1925
+ {
1926
+ provider: "openai",
1927
+ model: "gpt-5"
1928
+ },
1929
+ {
1930
+ provider: "openai",
1931
+ model: "o1"
1932
+ },
1933
+ {
1934
+ provider: "openai",
1935
+ model: "o3"
1936
+ },
1937
+ {
1938
+ provider: "xai",
1939
+ model: "grok-4-latest"
1940
+ }
1941
+ ]
1698
1942
  }
1699
- };
1700
- }
1943
+ }
1944
+ };
1701
1945
 
1702
1946
  // src/tools/index.ts
1703
1947
  import { z } from "zod";
1704
1948
 
1705
1949
  // src/server-meta.ts
1706
1950
  var SERVER_NAME = "crosscheck-agent";
1707
- var SERVER_VERSION = true ? "0.2.30" : "0.0.0-dev";
1951
+ var SERVER_VERSION = true ? "0.2.31" : "0.0.0-dev";
1708
1952
 
1709
1953
  // src/tools/audit.ts
1710
1954
  import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
@@ -2387,33 +2631,6 @@ function operatorCeiling(purpose, provider) {
2387
2631
  return null;
2388
2632
  }
2389
2633
 
2390
- // src/core/retarget.ts
2391
- function retargetProvider(p, newModel, fallbackModels) {
2392
- const chain = fallbackModels && fallbackModels.length > 0 ? fallbackModels : void 0;
2393
- if (p.model === newModel && chain === void 0) return p;
2394
- return {
2395
- name: p.name,
2396
- model: newModel,
2397
- ...chain ? { fallbackModels: chain } : {},
2398
- send: (args) => p.send({ ...args, modelOverride: newModel })
2399
- };
2400
- }
2401
- async function loadProviderWeights(storage, names) {
2402
- const out = {};
2403
- for (const raw of names) {
2404
- const name = raw.toLowerCase();
2405
- if (name in out) continue;
2406
- const row = await storage.getProviderStats(name);
2407
- if (!row) {
2408
- out[name] = 0.5;
2409
- continue;
2410
- }
2411
- const total = row.wins + row.losses + row.abstains;
2412
- out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
2413
- }
2414
- return out;
2415
- }
2416
-
2417
2634
  // src/core/model-fallback.ts
2418
2635
  async function sendWithModelFallback(provider, args) {
2419
2636
  const chain = [provider.model, ...provider.fallbackModels ?? []];
@@ -3480,82 +3697,6 @@ function selectForDifficulty(args) {
3480
3697
  };
3481
3698
  }
3482
3699
 
3483
- // src/core/reasoning-upgrade.ts
3484
- var UPGRADE_MODEL = "claude-fable-5";
3485
- var UPGRADE_LABEL = "Fable 5";
3486
- function isUpgradeRequested(args) {
3487
- if (args["upgrade"] === true) return true;
3488
- const reasoning = args["reasoning"];
3489
- if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
3490
- return true;
3491
- }
3492
- const model = args["model"];
3493
- if (typeof model === "string") {
3494
- const m = model.trim().toLowerCase();
3495
- if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
3496
- return true;
3497
- }
3498
- }
3499
- return false;
3500
- }
3501
- var HEAVY_TERMS = [
3502
- /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
3503
- /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
3504
- /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
3505
- /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
3506
- /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
3507
- /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
3508
- /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
3509
- /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
3510
- /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
3511
- /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
3512
- /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
3513
- /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
3514
- ];
3515
- var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
3516
- function assessReasoning(text) {
3517
- const s = typeof text === "string" ? text : "";
3518
- const signals = [];
3519
- let score = 0;
3520
- let termHits = 0;
3521
- for (const re of HEAVY_TERMS) {
3522
- if (re.test(s)) termHits += 1;
3523
- }
3524
- if (termHits > 0) {
3525
- const add = Math.min(termHits, 4);
3526
- score += add;
3527
- signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
3528
- }
3529
- if (DIFFICULTY_TERMS.test(s)) {
3530
- score += 1;
3531
- signals.push("explicit difficulty marker");
3532
- }
3533
- const words = s.split(/\s+/).filter(Boolean).length;
3534
- if (words >= 120) {
3535
- score += 2;
3536
- signals.push("long, detailed prompt");
3537
- } else if (words >= 50) {
3538
- score += 1;
3539
- signals.push("substantial prompt");
3540
- }
3541
- const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
3542
- if (constraints >= 3) {
3543
- score += 1;
3544
- signals.push(`${constraints} constraints/steps`);
3545
- }
3546
- const heavy = score >= 3;
3547
- const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
3548
- return { heavy, score, reason, signals };
3549
- }
3550
- function buildSuggestedUpgrade(toolName, assessment) {
3551
- return {
3552
- model: UPGRADE_MODEL,
3553
- label: UPGRADE_LABEL,
3554
- reason: assessment.reason,
3555
- how: `re-run \`${toolName}\` with reasoning:"max" (or upgrade:true) to route the lead reasoning seat to ${UPGRADE_LABEL}, and \u2014 when an OpenAI key is configured \u2014 add an OpenAI second-opinion pass that ${UPGRADE_LABEL} weighs before finalizing (the result names which model ran). Other panel seats stay on their normal models.`
3556
- };
3557
- }
3558
-
3559
3700
  // src/tools/audit.ts
3560
3701
  var AUDIT_OBVIOUS_FAILURE_HIGH = 0.3;
3561
3702
  var AUDIT_OBVIOUS_FAILURE_MED = 0.2;
@@ -3853,9 +3994,14 @@ ${rubricText}`;
3853
3994
  );
3854
3995
  }
3855
3996
  const upgradeRequested = isUpgradeRequested(args);
3856
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
3857
- const auditorForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : auditor;
3858
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
3997
+ const seat = synthesisSeat({ moderator: auditor, pinned: false, superRequested: false, upgradeRequested });
3998
+ const upgraded = seat.upgraded;
3999
+ const auditorForCall = seat.provider;
4000
+ const coProvider = pickCoReasoner(
4001
+ opts.providers,
4002
+ upgradeRequested,
4003
+ auditorForCall.name === "openai" ? auditorForCall.model : void 0
4004
+ );
3859
4005
  const coReason = await coReasonThenInject({
3860
4006
  provider: coProvider,
3861
4007
  messages: msgs,
@@ -3938,15 +4084,16 @@ ${rubricText}`;
3938
4084
  if (upgraded) {
3939
4085
  result["reasoning_upgrade"] = {
3940
4086
  applied: true,
3941
- model: UPGRADE_MODEL,
3942
- label: UPGRADE_LABEL,
4087
+ provider: auditorForCall.name,
4088
+ model: auditorForCall.model,
4089
+ label: seat.label ?? auditorForCall.model,
3943
4090
  seat: "auditor",
3944
4091
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
3945
4092
  };
3946
4093
  } else if (upgradeRequested) {
3947
4094
  result["reasoning_upgrade"] = {
3948
4095
  applied: false,
3949
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
4096
+ reason: seat.skipReason ?? "the auditor couldn't be upgraded."
3950
4097
  };
3951
4098
  } else {
3952
4099
  const assessment = assessReasoning(
@@ -4029,7 +4176,7 @@ function pickAuditor(providers, exclude, explicit, moderatorName, allowlist, che
4029
4176
  }
4030
4177
  }
4031
4178
  }
4032
- const mod = providers[moderatorName.toLowerCase()];
4179
+ const mod = resolveModerator(providers, moderatorName).provider;
4033
4180
  if (mod !== void 0 && !exclude.has(mod.name) && (allowlist === null || allowlist.includes(mod.name))) {
4034
4181
  return { auditor: mod, reason: null };
4035
4182
  }
@@ -4348,9 +4495,7 @@ function pickPanelJudge(providers, moderatorName, pricing) {
4348
4495
  return entry.model && entry.model !== base.model ? retargetProvider(base, entry.model) : base;
4349
4496
  }
4350
4497
  }
4351
- const mod = providers[moderatorName.toLowerCase()];
4352
- if (mod !== void 0) return mod;
4353
- return null;
4498
+ return resolveModerator(providers, moderatorName).provider ?? null;
4354
4499
  }
4355
4500
  var AGREEMENT_CHECK_SCHEMA = {
4356
4501
  type: "object",
@@ -4667,80 +4812,6 @@ async function pickAutoPanel(storage, purpose, n, providers, allowlist) {
4667
4812
  return { providers: ranked.slice(0, Math.max(1, n)), coldStart: true };
4668
4813
  }
4669
4814
 
4670
- // src/core/super-mode.ts
4671
- import { AsyncLocalStorage } from "async_hooks";
4672
- var SUPER_MODELS = {
4673
- // Fable 5.1 verified present on the account; 5 as the fallback for accounts
4674
- // that haven't been granted 5.1 yet.
4675
- anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
4676
- // Astra is live (verified against the account's model list 2026-09-23).
4677
- // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
4678
- // that constant means "OpenAI's top reasoning model" for the max-reasoning
4679
- // co-reasoner, which is a different question from "what should super fall
4680
- // back to". Sharing the symbol silently coupled two unrelated decisions.
4681
- openai: ["gpt-6-astra", "gpt-6-sol"],
4682
- // Explicitly left as-is per product decision. Note grok-4-latest is not in
4683
- // xAI's published model list, but it resolves in practice — real panel calls
4684
- // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
4685
- // rather than a dead id.
4686
- xai: [DEFAULT_MODELS["xai"]],
4687
- // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
4688
- // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
4689
- // repeat the same model and retry one that just failed. Named explicitly.
4690
- gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
4691
- kimi: [DEFAULT_MODELS["kimi"]],
4692
- qwen: [DEFAULT_MODELS["qwen"]]
4693
- };
4694
- var SUPER_ENV_VARS = {
4695
- anthropic: "ANTHROPIC_SUPER_MODEL",
4696
- openai: "OPENAI_SUPER_MODEL",
4697
- xai: "XAI_SUPER_MODEL",
4698
- gemini: "GEMINI_SUPER_MODEL",
4699
- kimi: "KIMI_SUPER_MODEL",
4700
- qwen: "QWEN_SUPER_MODEL",
4701
- mistral: "MISTRAL_SUPER_MODEL",
4702
- groq: "GROQ_SUPER_MODEL",
4703
- deepseek: "DEEPSEEK_SUPER_MODEL"
4704
- };
4705
- function parseChain(v) {
4706
- if (!v) return [];
4707
- const out = [];
4708
- for (const raw of v.split(",")) {
4709
- const m = raw.trim();
4710
- if (m && !out.includes(m)) out.push(m);
4711
- }
4712
- return out;
4713
- }
4714
- var superEnvStore = new AsyncLocalStorage();
4715
- var currentEnv = () => superEnvStore.getStore() ?? process.env;
4716
- function superChainFor(provider, env = currentEnv()) {
4717
- const varName = SUPER_ENV_VARS[provider];
4718
- const user = varName ? parseChain(env[varName]) : [];
4719
- if (user.length > 0) {
4720
- const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
4721
- return [...user, ...fallbacks.filter((m) => !user.includes(m))];
4722
- }
4723
- return SUPER_MODELS[provider] ?? [];
4724
- }
4725
- function superPrimary(provider, env = currentEnv()) {
4726
- return superChainFor(provider, env)[0];
4727
- }
4728
- var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
4729
- function isSuperRequested(args) {
4730
- return args["super"] === true;
4731
- }
4732
- function retargetForSuper(p, env = currentEnv()) {
4733
- const chain = superChainFor(p.name, env);
4734
- if (chain.length === 0) return p;
4735
- const [primary, ...fallbacks] = chain;
4736
- return retargetProvider(p, primary, fallbacks);
4737
- }
4738
- var SUPER_BUDGET_MULTIPLIER = 3;
4739
- var SUPER_BUDGET_CEILING = 16e3;
4740
- function superBudget(base) {
4741
- return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
4742
- }
4743
-
4744
4815
  // src/core/session-memory.ts
4745
4816
  var SESSION_MEMORY_DEFAULT_LIMIT = 50;
4746
4817
  var SESSION_MEMORY_INJECT_BUDGET_CHARS = 4e3;
@@ -7108,8 +7179,9 @@ async function runOrchestrate(args, opts) {
7108
7179
  blocked
7109
7180
  };
7110
7181
  }
7111
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
7112
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
7182
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
7183
+ const moderator = resolvedModerator.provider ?? selected[0];
7184
+ const moderatorChosen = resolvedModerator.provider !== void 0 && (resolvedModerator.pinned || parseModeratorSpec(args["moderator"]) !== null);
7113
7185
  const failFast = boolArg2(args["fail_fast"], false);
7114
7186
  const planOnly = boolArg2(args["plan_only"], false);
7115
7187
  const cheapMode = boolArg2(args["cheap_mode"], opts.ctx?.cheap_mode ?? false);
@@ -7593,11 +7665,17 @@ ${recombineSys}` : recombineSys },
7593
7665
  let synthProvider = moderator;
7594
7666
  let synthModel = null;
7595
7667
  const upgradeRequested = isUpgradeRequested(args);
7596
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
7668
+ const seat = synthesisSeat({
7669
+ moderator,
7670
+ pinned: resolvedModerator.provider !== void 0 && resolvedModerator.pinned,
7671
+ superRequested: false,
7672
+ upgradeRequested
7673
+ });
7674
+ const upgraded = seat.upgraded;
7597
7675
  if (upgraded) {
7598
- synthProvider = retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL);
7599
- synthModel = `anthropic/${UPGRADE_MODEL}`;
7600
- } else if (cheapMode && opts.pricing) {
7676
+ synthProvider = seat.provider;
7677
+ synthModel = `${seat.provider.name}/${seat.provider.model}`;
7678
+ } else if (cheapMode && opts.pricing && !moderatorChosen) {
7601
7679
  const pick = selectForDifficulty({
7602
7680
  pricing: opts.pricing,
7603
7681
  tier: "med",
@@ -7623,7 +7701,11 @@ ${recombineSys}` : recombineSys },
7623
7701
  provider: synthProvider.name
7624
7702
  } }
7625
7703
  );
7626
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
7704
+ const coProvider = pickCoReasoner(
7705
+ opts.providers,
7706
+ upgradeRequested,
7707
+ synthProvider.name === "openai" ? synthProvider.model : void 0
7708
+ );
7627
7709
  const coReason = await coReasonThenInject({
7628
7710
  provider: coProvider,
7629
7711
  messages: recMsgs,
@@ -7673,7 +7755,8 @@ ${recombineSys}` : recombineSys },
7673
7755
  missing,
7674
7756
  partial,
7675
7757
  fail_fast: failFast,
7676
- cheap_mode: cheapMode
7758
+ cheap_mode: cheapMode,
7759
+ moderator: { provider: moderator.name, model: moderator.model }
7677
7760
  };
7678
7761
  if (personaMeta.used !== null) result["persona"] = personaMeta;
7679
7762
  if (synthModel) result["synth_model"] = synthModel;
@@ -7681,15 +7764,16 @@ ${recombineSys}` : recombineSys },
7681
7764
  if (upgraded) {
7682
7765
  result["reasoning_upgrade"] = {
7683
7766
  applied: true,
7684
- model: UPGRADE_MODEL,
7685
- label: UPGRADE_LABEL,
7767
+ provider: seat.provider.name,
7768
+ model: seat.provider.model,
7769
+ label: seat.label ?? seat.provider.model,
7686
7770
  seat: "recombine/synthesis",
7687
7771
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
7688
7772
  };
7689
7773
  } else if (upgradeRequested) {
7690
7774
  result["reasoning_upgrade"] = {
7691
7775
  applied: false,
7692
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
7776
+ reason: seat.skipReason ?? "the moderator couldn't be upgraded."
7693
7777
  };
7694
7778
  } else if (goal) {
7695
7779
  const assessment = assessReasoning(goal);
@@ -8871,13 +8955,24 @@ async function runCoordinate(args, opts) {
8871
8955
  };
8872
8956
  }
8873
8957
  const proposerName = typeof args["proposer"] === "string" && args["proposer"] ? args["proposer"] : selected[0].name;
8874
- const synthName = typeof args["synthesizer"] === "string" && args["synthesizer"] ? args["synthesizer"] : typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
8958
+ const resolvedSynth = resolveModerator(
8959
+ opts.providers,
8960
+ args["synthesizer"],
8961
+ args["moderator"],
8962
+ opts.moderator
8963
+ );
8875
8964
  const proposer = opts.providers[proposerName.toLowerCase()] ?? selected[0];
8876
- const synth = opts.providers[synthName.toLowerCase()] ?? proposer;
8965
+ const synth = resolvedSynth.provider ?? proposer;
8877
8966
  const proposerForCall = superRequested ? retargetForSuper(proposer) : proposer;
8878
8967
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
8879
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
8880
- const synthForCall = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : synth;
8968
+ const seat = synthesisSeat({
8969
+ moderator: synth,
8970
+ pinned: resolvedSynth.provider !== void 0 && resolvedSynth.pinned,
8971
+ superRequested,
8972
+ upgradeRequested
8973
+ });
8974
+ const upgraded = seat.upgraded;
8975
+ const synthForCall = seat.provider;
8881
8976
  let critics = [];
8882
8977
  if (Array.isArray(args["critics"])) {
8883
8978
  for (const n of args["critics"]) {
@@ -9028,7 +9123,11 @@ CRITIQUES:
9028
9123
  ${critiqueBlock}`
9029
9124
  }
9030
9125
  ];
9031
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
9126
+ const coProvider = pickCoReasoner(
9127
+ opts.providers,
9128
+ upgradeRequested,
9129
+ synthForCall.name === "openai" ? synthForCall.model : void 0
9130
+ );
9032
9131
  const coReason = await coReasonThenInject({
9033
9132
  provider: coProvider,
9034
9133
  messages: synthMessages,
@@ -9073,7 +9172,8 @@ ${critiqueBlock}`
9073
9172
  critique_answers: scannedCritiques,
9074
9173
  critique_structured: critiqueStructured,
9075
9174
  synthesis_answer: synthAns,
9076
- synthesis_structured: synthObj
9175
+ synthesis_structured: synthObj,
9176
+ moderator: { provider: synthForCall.name, model: synthForCall.model }
9077
9177
  };
9078
9178
  if (synthErrs.length > 0) result["synthesis_errors"] = synthErrs;
9079
9179
  if (canaryLeaks.length > 0) result["canary_leaks"] = canaryLeaks;
@@ -9091,15 +9191,16 @@ ${critiqueBlock}`
9091
9191
  result["reasoning_upgrade"] = {
9092
9192
  applied: true,
9093
9193
  // The model that actually ran the seat, not the one we'd have picked.
9094
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
9095
- label: UPGRADE_LABEL,
9194
+ provider: synthForCall.name,
9195
+ model: synthForCall.model,
9196
+ label: seat.label ?? synthForCall.model,
9096
9197
  seat: "synthesizer",
9097
9198
  ...coReasonAns ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
9098
9199
  };
9099
9200
  } else if (upgradeRequested) {
9100
9201
  result["reasoning_upgrade"] = {
9101
9202
  applied: false,
9102
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
9203
+ reason: seat.skipReason ?? "the synthesizer couldn't be upgraded."
9103
9204
  };
9104
9205
  } else {
9105
9206
  const assessment = assessReasoning(topic);
@@ -9705,10 +9806,12 @@ async function runDebate(args, opts) {
9705
9806
  const earlyStopThreshold = Number.isFinite(earlyStopThresholdRaw) ? earlyStopThresholdRaw : 0.7;
9706
9807
  let earlyStoppedRound = null;
9707
9808
  let agreementBlock = null;
9708
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
9709
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
9809
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
9810
+ const moderatorName = resolvedModerator.spec.provider;
9811
+ const moderator = resolvedModerator.provider ?? selected[0];
9710
9812
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
9711
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9813
+ const seat = moderator ? synthesisSeat({ moderator, pinned: resolvedModerator.pinned, superRequested, upgradeRequested }) : null;
9814
+ const upgraded = seat?.upgraded ?? false;
9712
9815
  for (let rnd = 1; rnd <= maxRounds; rnd++) {
9713
9816
  const roundMessages = [
9714
9817
  {
@@ -9793,8 +9896,10 @@ ${prior.body}` });
9793
9896
  let personaMeta = null;
9794
9897
  let coReasoned = false;
9795
9898
  let coReasonerModel = null;
9899
+ let moderatorUsed = null;
9796
9900
  if (moderator) {
9797
- const synthProvider = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : moderator;
9901
+ const synthProvider = seat?.provider ?? moderator;
9902
+ moderatorUsed = { provider: synthProvider.name, model: synthProvider.model };
9798
9903
  const condensed = buildModeratorTranscript(transcript);
9799
9904
  const persona = buildPersonaInjection({
9800
9905
  toolName: "debate",
@@ -9820,7 +9925,7 @@ TRANSCRIPT:
9820
9925
  ${condensed}`
9821
9926
  }
9822
9927
  ];
9823
- const openAiSeat = selected.find((p) => p.name === "openai")?.model;
9928
+ const openAiSeat = synthProvider.name === "openai" ? synthProvider.model : selected.find((p) => p.name === "openai")?.model;
9824
9929
  const coProvider = pickCoReasoner(opts.providers, upgradeRequested, openAiSeat);
9825
9930
  const coReason = await coReasonThenInject({
9826
9931
  provider: coProvider,
@@ -9880,20 +9985,22 @@ ${condensed}`
9880
9985
  transcript,
9881
9986
  synthesis
9882
9987
  };
9988
+ if (moderatorUsed) result["moderator"] = moderatorUsed;
9883
9989
  if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
9884
9990
  if (compactionMeta) result["round_compaction"] = compactionMeta;
9885
9991
  if (upgraded) {
9886
9992
  result["reasoning_upgrade"] = {
9887
9993
  applied: true,
9888
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
9889
- label: UPGRADE_LABEL,
9994
+ provider: seat.provider.name,
9995
+ model: seat.provider.model,
9996
+ label: seat.label ?? seat.provider.model,
9890
9997
  seat: "moderator synthesis",
9891
9998
  ...coReasoned && coReasonerModel ? { co_reasoner: { model: coReasonerModel, label: coReasonLabel(coReasonerModel) } } : {}
9892
9999
  };
9893
10000
  } else if (upgradeRequested) {
9894
10001
  result["reasoning_upgrade"] = {
9895
10002
  applied: false,
9896
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10003
+ reason: seat?.skipReason ?? "no moderator was available for the upgrade."
9897
10004
  };
9898
10005
  } else {
9899
10006
  const assessment = assessReasoning(topic);
@@ -10522,12 +10629,25 @@ function runListProviders(args, opts) {
10522
10629
  }
10523
10630
  const result = {
10524
10631
  providers,
10525
- moderator_default: opts.moderatorDefault ?? "anthropic",
10632
+ moderator_default: opts.moderatorDefault ?? DEFAULT_MODERATOR,
10633
+ // Who will actually moderate right now: the configured spec if that
10634
+ // provider has a key, else the first available (what the tools fall to).
10635
+ moderator: describeModerator(opts.providers ?? {}, opts.moderatorDefault),
10526
10636
  usage_hint: USAGE_HINT
10527
10637
  };
10528
10638
  if (superRequested) result["super_mode"] = true;
10529
10639
  return result;
10530
10640
  }
10641
+ function describeModerator(providers, spec) {
10642
+ const r = resolveModerator(providers, spec);
10643
+ const p = r.provider ?? Object.values(providers)[0];
10644
+ return {
10645
+ spec: spec ?? DEFAULT_MODERATOR,
10646
+ provider: p?.name ?? null,
10647
+ model: p?.model ?? null,
10648
+ available: r.provider !== void 0
10649
+ };
10650
+ }
10531
10651
 
10532
10652
  // src/tools/models.ts
10533
10653
  var KNOWN_PROVIDERS7 = [
@@ -12077,7 +12197,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12077
12197
  var DEFAULT_PACKAGE = "crosscheck-cli";
12078
12198
  var FETCH_TIMEOUT_MS = 3e3;
12079
12199
  function engineVersion() {
12080
- return true ? "0.2.30" : "0.0.0-dev";
12200
+ return true ? "0.2.31" : "0.0.0-dev";
12081
12201
  }
12082
12202
  function defaultUpdateCachePath() {
12083
12203
  const base = process.env["CROSSCHECK_DATA_DIR"] || path9.join(os.homedir() || os.tmpdir(), ".crosscheck");
@@ -12522,7 +12642,7 @@ var HELP_CATALOG = [
12522
12642
  primaryArg: "topic",
12523
12643
  summary: "Bounded multi-round debate between models, ending in a synthesized recommendation.",
12524
12644
  example: "xc debate should we adopt a monorepo?",
12525
- detail: `The panel argues across a few rounds \u2014 each model sees the others' prior turns, concedes or pushes back \u2014 then a moderator synthesizes a single grounded recommendation. Supports reasoning:"max" to bump the moderator synthesis seat to Fable 5. Great for contentious or trade-off-heavy calls.`
12645
+ detail: `The panel argues across a few rounds \u2014 each model sees the others' prior turns, concedes or pushes back \u2014 then a moderator synthesizes a single grounded recommendation. Any model can moderate \u2014 pass moderator:"openai" or an exact model (moderator:"openai:gpt-6-sol"), or set a default with \`crosscheck moderator set\`. reasoning:"max" bumps the moderator's synthesis to its provider's flagship (Fable 5 for Anthropic). Great for contentious or trade-off-heavy calls.`
12526
12646
  },
12527
12647
  {
12528
12648
  name: "coordinate",
@@ -12765,6 +12885,10 @@ function renderHelpCommand(entry) {
12765
12885
  }
12766
12886
 
12767
12887
  // src/tools/index.ts
12888
+ var MODERATOR_PARAM = {
12889
+ type: "string",
12890
+ description: 'Who moderates (synthesises) this run: a provider ("openai") or an exact model ("openai:gpt-6-sol"). Any configured provider works. Defaults to the configured moderator (CROSSCHECK_MODERATOR / config `moderator`).'
12891
+ };
12768
12892
  function defineTool(opts) {
12769
12893
  return {
12770
12894
  name: opts.name,
@@ -12999,7 +13123,7 @@ function createTool(o, toolName, cheapDefault) {
12999
13123
  instruction: { type: "string" },
13000
13124
  documents: { type: "array", items: { type: "string" } },
13001
13125
  providers: { type: "array", items: { type: "string" } },
13002
- moderator: { type: "string" },
13126
+ moderator: MODERATOR_PARAM,
13003
13127
  constraints: { type: "string" },
13004
13128
  target_path: { type: "string" },
13005
13129
  session_id: { type: "string" },
@@ -13040,7 +13164,7 @@ function orchestrateTool(providers, allowlist, moderator, storage, pricing, brea
13040
13164
  dag: { type: "object", additionalProperties: true },
13041
13165
  context: { type: "string" },
13042
13166
  providers: { type: "array", items: { type: "string" } },
13043
- moderator: { type: "string" },
13167
+ moderator: MODERATOR_PARAM,
13044
13168
  fail_fast: { type: "boolean" },
13045
13169
  plan_only: { type: "boolean" },
13046
13170
  cheap_mode: { type: "boolean" },
@@ -13419,7 +13543,7 @@ function planTool(providers, allowlist, storage, breakers, transcriptsDir, repoR
13419
13543
  constraints: { type: "string" },
13420
13544
  context: { type: "string" },
13421
13545
  providers: { type: "array", items: { type: "string" } },
13422
- moderator: { type: "string" },
13546
+ moderator: MODERATOR_PARAM,
13423
13547
  session_id: { type: "string" },
13424
13548
  structured: { type: "boolean" },
13425
13549
  mode: { type: "string", enum: ["fast", "thorough"] },
@@ -13481,7 +13605,7 @@ function coordinateTool(providers, allowlist, innerCallers, storage, breakers, t
13481
13605
  providers: { type: "array", items: { type: "string" } },
13482
13606
  proposer: { type: "string" },
13483
13607
  synthesizer: { type: "string" },
13484
- moderator: { type: "string" },
13608
+ moderator: MODERATOR_PARAM,
13485
13609
  critics: { type: "array", items: { type: "string" } },
13486
13610
  session_id: { type: "string" },
13487
13611
  untrusted_input: { type: "boolean" },
@@ -13514,7 +13638,7 @@ function debateTool(providers, allowlist, innerCallers, storage, pricing, breake
13514
13638
  context: { type: "string" },
13515
13639
  max_rounds: { type: "integer", minimum: 1 },
13516
13640
  providers: { type: "array", items: { type: "string" } },
13517
- moderator: { type: "string" },
13641
+ moderator: MODERATOR_PARAM,
13518
13642
  session_id: { type: "string" },
13519
13643
  reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the moderator-synthesis seat to Fable 5 (premium thinking model); the debaters stay on their normal models. Omit for the Opus 4.8 default.' },
13520
13644
  structured: { type: "boolean" },
@@ -13731,6 +13855,7 @@ var KEY_ENV = {
13731
13855
  qwen: "QWEN_API_KEY"
13732
13856
  };
13733
13857
  function createCrosscheck(opts) {
13858
+ const moderator = opts.moderator ?? DEFAULT_MODERATOR;
13734
13859
  const env = {};
13735
13860
  for (const [provider, key] of Object.entries(opts.keys)) {
13736
13861
  const varName = KEY_ENV[provider.trim().toLowerCase()];
@@ -13738,7 +13863,7 @@ function createCrosscheck(opts) {
13738
13863
  }
13739
13864
  const pricingDoc = pricing_default;
13740
13865
  const providers = buildProviders({ env, pricing: pricingDoc });
13741
- const tools = registerCoreTools({ providers, pricing: pricingDoc });
13866
+ const tools = registerCoreTools({ providers, pricing: pricingDoc, moderatorDefault: moderator });
13742
13867
  return {
13743
13868
  listTools: () => [...tools.keys()],
13744
13869
  call: (name, args) => {
@@ -13751,11 +13876,11 @@ function createCrosscheck(opts) {
13751
13876
  // same opts the registry wires for confer in a browser build: providers
13752
13877
  // + pricing + default moderator; storage/innerCallers aren't
13753
13878
  // available here, matching the non-streaming path.
13754
- conferStream: (args, onAnswer) => runConfer(args, { providers, pricing: pricingDoc, moderator: "anthropic", onAnswer }),
13879
+ conferStream: (args, onAnswer) => runConfer(args, { providers, pricing: pricingDoc, moderator, onAnswer }),
13755
13880
  debateStream: (args, { onTurn, onSynthesis } = {}) => runDebate(args, {
13756
13881
  providers,
13757
13882
  pricing: pricingDoc,
13758
- moderator: "anthropic",
13883
+ moderator,
13759
13884
  ...onTurn ? { onTurn: (entry, round, provider) => onTurn(entry, round, provider) } : {},
13760
13885
  ...onSynthesis ? { onSynthesis: (s) => onSynthesis(s) } : {}
13761
13886
  })