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.
@@ -39,426 +39,35 @@ module.exports = __toCommonJS(browser_ext_exports);
39
39
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
40
40
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
41
 
42
- // ../../config/pricing.json
43
- var pricing_default = {
44
- _meta: {
45
- currency: "USD",
46
- unit: "per_1k_tokens",
47
- updated_at: "2026-09-06",
48
- 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."
49
- },
50
- anthropic: {
51
- _source: {
52
- url: "https://platform.claude.com/docs/en/about-claude/pricing",
53
- verified_at: "2026-09-27"
54
- },
55
- "claude-opus-4-5": {
56
- prompt_per_1k: 5e-3,
57
- completion_per_1k: 0.025,
58
- cached_per_1k: 5e-4
59
- },
60
- "claude-opus-4-7": {
61
- prompt_per_1k: 5e-3,
62
- completion_per_1k: 0.025,
63
- cached_per_1k: 5e-4
64
- },
65
- "claude-opus-4-8": {
66
- prompt_per_1k: 5e-3,
67
- completion_per_1k: 0.025,
68
- cached_per_1k: 5e-4
69
- },
70
- "claude-opus-5-5": {
71
- prompt_per_1k: 4e-3,
72
- completion_per_1k: 0.02,
73
- cached_per_1k: 2e-4
74
- },
75
- "claude-opus-5": {
76
- prompt_per_1k: 5e-3,
77
- completion_per_1k: 0.025,
78
- cached_per_1k: 5e-4
79
- },
80
- "claude-fable-5": {
81
- prompt_per_1k: 0.01,
82
- completion_per_1k: 0.05,
83
- cached_per_1k: 1e-3
84
- },
85
- "claude-sonnet-4-5": {
86
- prompt_per_1k: 3e-3,
87
- completion_per_1k: 0.015,
88
- cached_per_1k: 3e-4
89
- },
90
- "claude-sonnet-4-6": {
91
- prompt_per_1k: 3e-3,
92
- completion_per_1k: 0.015,
93
- cached_per_1k: 3e-4
94
- },
95
- "claude-haiku-4-5": {
96
- prompt_per_1k: 1e-3,
97
- completion_per_1k: 5e-3,
98
- cached_per_1k: 1e-4
99
- },
100
- "claude-3-5-sonnet": {
101
- prompt_per_1k: 3e-3,
102
- completion_per_1k: 0.015,
103
- cached_per_1k: 3e-4
104
- },
105
- "claude-3-5-haiku": {
106
- prompt_per_1k: 8e-4,
107
- completion_per_1k: 4e-3,
108
- cached_per_1k: 8e-5
109
- },
110
- "claude-fable-5-1": {
111
- prompt_per_1k: 0.01,
112
- completion_per_1k: 0.05,
113
- cached_per_1k: 25e-5
114
- }
115
- },
116
- openai: {
117
- _source: {
118
- url: "https://developers.openai.com/api/docs/pricing",
119
- verified_at: "2026-09-27"
120
- },
121
- "gpt-6-astra": {
122
- prompt_per_1k: 0.01,
123
- completion_per_1k: 0.05,
124
- cached_per_1k: 1e-3
125
- },
126
- "gpt-6-sol": {
127
- prompt_per_1k: 2e-3,
128
- completion_per_1k: 0.01,
129
- cached_per_1k: 2e-4
130
- },
131
- "gpt-6-luna": {
132
- prompt_per_1k: 1e-4,
133
- completion_per_1k: 5e-4,
134
- cached_per_1k: 1e-5
135
- },
136
- "gpt-5.6-sol": {
137
- prompt_per_1k: 4e-3,
138
- completion_per_1k: 0.02,
139
- cached_per_1k: 4e-4
140
- },
141
- "gpt-5.6-terra": {
142
- prompt_per_1k: 2e-3,
143
- completion_per_1k: 0.012,
144
- cached_per_1k: 2e-4
145
- },
146
- "gpt-5.6-luna": {
147
- prompt_per_1k: 2e-4,
148
- completion_per_1k: 12e-4,
149
- cached_per_1k: 2e-5
150
- },
151
- "gpt-5.6": {
152
- prompt_per_1k: 4e-3,
153
- completion_per_1k: 0.02,
154
- cached_per_1k: 4e-4
155
- },
156
- "gpt-5.5": {
157
- prompt_per_1k: 5e-3,
158
- completion_per_1k: 0.03,
159
- cached_per_1k: 5e-4
160
- },
161
- "gpt-5": {
162
- prompt_per_1k: 125e-5,
163
- completion_per_1k: 0.01,
164
- cached_per_1k: 125e-6
165
- },
166
- "gpt-5-mini": {
167
- prompt_per_1k: 25e-5,
168
- completion_per_1k: 2e-3,
169
- cached_per_1k: 25e-6
170
- },
171
- "gpt-5-nano": {
172
- prompt_per_1k: 5e-5,
173
- completion_per_1k: 4e-4,
174
- cached_per_1k: 5e-6
175
- },
176
- "gpt-4o": {
177
- prompt_per_1k: 25e-4,
178
- completion_per_1k: 0.01,
179
- cached_per_1k: 125e-5
180
- },
181
- "gpt-4o-mini": {
182
- prompt_per_1k: 15e-5,
183
- completion_per_1k: 6e-4,
184
- cached_per_1k: 75e-6
185
- },
186
- o1: {
187
- prompt_per_1k: 0.015,
188
- completion_per_1k: 0.06,
189
- cached_per_1k: 75e-4
190
- },
191
- "o1-mini": {
192
- prompt_per_1k: 3e-3,
193
- completion_per_1k: 0.012,
194
- cached_per_1k: 15e-4
195
- },
196
- o3: {
197
- prompt_per_1k: 2e-3,
198
- completion_per_1k: 8e-3,
199
- cached_per_1k: 5e-4
200
- },
201
- "o3-mini": {
202
- prompt_per_1k: 11e-4,
203
- completion_per_1k: 44e-4,
204
- cached_per_1k: 55e-5
205
- },
206
- "gpt-5-pro": {
207
- prompt_per_1k: 0.015,
208
- completion_per_1k: 0.12,
209
- cached_per_1k: 0
210
- },
211
- "gpt-5.2-pro": {
212
- prompt_per_1k: 0.021,
213
- completion_per_1k: 0.168,
214
- cached_per_1k: 0
215
- },
216
- "gpt-5.4-pro": {
217
- prompt_per_1k: 0.03,
218
- completion_per_1k: 0.18,
219
- cached_per_1k: 0
220
- },
221
- "gpt-5.5-pro": {
222
- prompt_per_1k: 0.03,
223
- completion_per_1k: 0.18,
224
- cached_per_1k: 0
225
- },
226
- "o1-pro": {
227
- prompt_per_1k: 0.15,
228
- completion_per_1k: 0.6,
229
- cached_per_1k: 0
230
- },
231
- "o3-pro": {
232
- prompt_per_1k: 0.02,
233
- completion_per_1k: 0.08,
234
- cached_per_1k: 0
235
- }
236
- },
237
- xai: {
238
- "grok-4-latest": {
239
- prompt_per_1k: 3e-3,
240
- completion_per_1k: 0.015,
241
- cached_per_1k: 75e-5
242
- },
243
- "grok-3": {
244
- prompt_per_1k: 3e-3,
245
- completion_per_1k: 0.015,
246
- cached_per_1k: 15e-4
247
- },
248
- "grok-3-mini": {
249
- prompt_per_1k: 3e-4,
250
- completion_per_1k: 5e-4,
251
- cached_per_1k: 15e-5
252
- }
253
- },
254
- gemini: {
255
- "gemini-3.1-pro-preview": {
256
- prompt_per_1k: 2e-3,
257
- completion_per_1k: 0.012,
258
- cached_per_1k: 2e-4
259
- },
260
- "gemini-3.5-flash": {
261
- prompt_per_1k: 15e-4,
262
- completion_per_1k: 9e-3,
263
- cached_per_1k: 15e-5
264
- },
265
- "gemini-2.5-flash": {
266
- prompt_per_1k: 3e-4,
267
- completion_per_1k: 25e-4,
268
- cached_per_1k: 75e-6
269
- },
270
- "gemini-2.5-flash-lite": {
271
- prompt_per_1k: 1e-4,
272
- completion_per_1k: 4e-4,
273
- cached_per_1k: 25e-6
274
- },
275
- "gemini-3.8-flash": {
276
- prompt_per_1k: 75e-5,
277
- completion_per_1k: 375e-5,
278
- cached_per_1k: 75e-6
279
- }
280
- },
281
- mistral: {
282
- "mistral-large-latest": {
283
- prompt_per_1k: 2e-3,
284
- completion_per_1k: 6e-3,
285
- cached_per_1k: 2e-4
286
- },
287
- "mistral-small-latest": {
288
- prompt_per_1k: 2e-4,
289
- completion_per_1k: 6e-4,
290
- cached_per_1k: 2e-5
291
- }
292
- },
293
- groq: {
294
- "llama-3.3-70b-versatile": {
295
- prompt_per_1k: 59e-5,
296
- completion_per_1k: 79e-5,
297
- cached_per_1k: 295e-6
298
- },
299
- "llama-3.1-8b-instant": {
300
- prompt_per_1k: 5e-5,
301
- completion_per_1k: 8e-5,
302
- cached_per_1k: 25e-6
303
- }
304
- },
305
- deepseek: {
306
- "deepseek-chat": {
307
- prompt_per_1k: 27e-5,
308
- completion_per_1k: 11e-4,
309
- cached_per_1k: 7e-5
310
- },
311
- "deepseek-reasoner": {
312
- prompt_per_1k: 55e-5,
313
- completion_per_1k: 22e-4,
314
- cached_per_1k: 14e-5
315
- }
316
- },
317
- kimi: {
318
- "kimi-k3": {
319
- prompt_per_1k: 3e-3,
320
- completion_per_1k: 0.015,
321
- cached_per_1k: 3e-4
322
- }
323
- },
324
- qwen: {
325
- "qwen3.8-max": {
326
- prompt_per_1k: 2e-3,
327
- completion_per_1k: 6e-3,
328
- cached_per_1k: 2e-4
329
- },
330
- "qwen3.7-max": {
331
- prompt_per_1k: 125e-5,
332
- completion_per_1k: 375e-5,
333
- cached_per_1k: 125e-6
334
- }
335
- },
336
- _tiers: {
337
- low: {
338
- description: "Cheap, fast models for simple subtasks (extraction, formatting, short summaries).",
339
- models: [
340
- {
341
- provider: "openai",
342
- model: "gpt-4o-mini"
343
- },
344
- {
345
- provider: "openai",
346
- model: "gpt-5-mini"
347
- },
348
- {
349
- provider: "anthropic",
350
- model: "claude-haiku-4-5"
351
- },
352
- {
353
- provider: "anthropic",
354
- model: "claude-3-5-haiku"
355
- },
356
- {
357
- provider: "gemini",
358
- model: "gemini-3.5-flash"
359
- },
360
- {
361
- provider: "gemini",
362
- model: "gemini-2.5-flash"
363
- },
364
- {
365
- provider: "gemini",
366
- model: "gemini-2.5-flash-lite"
367
- },
368
- {
369
- provider: "xai",
370
- model: "grok-3-mini"
371
- },
372
- {
373
- provider: "groq",
374
- model: "llama-3.1-8b-instant"
375
- },
376
- {
377
- provider: "deepseek",
378
- model: "deepseek-chat"
379
- }
380
- ]
381
- },
382
- med: {
383
- description: "Mid-tier models for reasoning, code, multi-step thinking.",
384
- models: [
385
- {
386
- provider: "anthropic",
387
- model: "claude-sonnet-4-5"
388
- },
389
- {
390
- provider: "anthropic",
391
- model: "claude-sonnet-4-6"
392
- },
393
- {
394
- provider: "openai",
395
- model: "gpt-4o"
396
- },
397
- {
398
- provider: "openai",
399
- model: "o3-mini"
400
- },
401
- {
402
- provider: "gemini",
403
- model: "gemini-3.1-pro-preview"
404
- },
405
- {
406
- provider: "xai",
407
- model: "grok-3"
408
- },
409
- {
410
- provider: "mistral",
411
- model: "mistral-large-latest"
412
- },
413
- {
414
- provider: "deepseek",
415
- model: "deepseek-reasoner"
416
- },
417
- {
418
- provider: "groq",
419
- model: "llama-3.3-70b-versatile"
420
- }
421
- ]
422
- },
423
- high: {
424
- description: "Most capable models for hard reasoning, planning, audits.",
425
- models: [
426
- {
427
- provider: "anthropic",
428
- model: "claude-opus-4-5"
429
- },
430
- {
431
- provider: "anthropic",
432
- model: "claude-opus-4-7"
433
- },
434
- {
435
- provider: "anthropic",
436
- model: "claude-fable-5"
437
- },
438
- {
439
- provider: "openai",
440
- model: "gpt-5.5"
441
- },
442
- {
443
- provider: "openai",
444
- model: "gpt-5"
445
- },
446
- {
447
- provider: "openai",
448
- model: "o1"
449
- },
450
- {
451
- provider: "openai",
452
- model: "o3"
453
- },
454
- {
455
- provider: "xai",
456
- model: "grok-4-latest"
457
- }
458
- ]
42
+ // src/core/retarget.ts
43
+ function retargetProvider(p, newModel, fallbackModels) {
44
+ const chain = fallbackModels && fallbackModels.length > 0 ? fallbackModels : void 0;
45
+ if (p.model === newModel && chain === void 0) return p;
46
+ return {
47
+ name: p.name,
48
+ model: newModel,
49
+ ...chain ? { fallbackModels: chain } : {},
50
+ send: (args) => p.send({ ...args, modelOverride: newModel })
51
+ };
52
+ }
53
+ async function loadProviderWeights(storage, names) {
54
+ const out = {};
55
+ for (const raw of names) {
56
+ const name = raw.toLowerCase();
57
+ if (name in out) continue;
58
+ const row = await storage.getProviderStats(name);
59
+ if (!row) {
60
+ out[name] = 0.5;
61
+ continue;
459
62
  }
63
+ const total = row.wins + row.losses + row.abstains;
64
+ out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
460
65
  }
461
- };
66
+ return out;
67
+ }
68
+
69
+ // src/core/super-mode.ts
70
+ var import_node_async_hooks = require("async_hooks");
462
71
 
463
72
  // src/core/pricing.ts
464
73
  var import_node_fs = require("fs");
@@ -1349,7 +958,208 @@ async function sendGemini(args) {
1349
958
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1350
959
  const init = {
1351
960
  method: "POST",
1352
- headers: { ...headers, "content-type": "application/json" },
961
+ headers: { ...headers, "content-type": "application/json" },
962
+ body: JSON.stringify(body)
963
+ };
964
+ if (args.signal) init.signal = args.signal;
965
+ const attemptOnce = async () => {
966
+ let respLike;
967
+ try {
968
+ respLike = await doFetch(url, init);
969
+ } catch (e) {
970
+ const d = diagnoseNetworkError(e);
971
+ if (!looksLikeNetworkFailure(d)) throw e;
972
+ throw new ProviderError(d.kind, networkErrorMessage("gemini", d), {
973
+ transient: d.transient
974
+ });
975
+ }
976
+ const status = respLike.status;
977
+ if (status >= 200 && status < 300) {
978
+ let parsed;
979
+ try {
980
+ parsed = await respLike.json();
981
+ } catch (e) {
982
+ throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
983
+ }
984
+ const { text, usage, finishReason } = parseGeminiResponse({
985
+ resp: parsed,
986
+ model: args.model,
987
+ purpose: args.purpose ?? "worker"
988
+ });
989
+ if (text.trim() === "" && finishReason === "MAX_TOKENS") {
990
+ throw new ProviderError(
991
+ "parse",
992
+ `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.`,
993
+ { transient: false }
994
+ );
995
+ }
996
+ return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
997
+ }
998
+ const bodyText = await respLike.text().catch(() => "");
999
+ throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike), args.model);
1000
+ };
1001
+ await acquireRateLimit("gemini", args.signal ? { signal: args.signal } : void 0);
1002
+ return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1003
+ }
1004
+
1005
+ // src/providers/openai-compatible.ts
1006
+ var OPENAI_COMPAT_DEFAULT_URLS = {
1007
+ openai: "https://api.openai.com/v1/chat/completions",
1008
+ xai: "https://api.x.ai/v1/chat/completions",
1009
+ mistral: "https://api.mistral.ai/v1/chat/completions",
1010
+ groq: "https://api.groq.com/openai/v1/chat/completions",
1011
+ deepseek: "https://api.deepseek.com/v1/chat/completions",
1012
+ // Moonshot AI (Kimi). OpenAI-compatible chat completions endpoint.
1013
+ kimi: "https://api.moonshot.ai/v1/chat/completions",
1014
+ // Alibaba Cloud DashScope (Qwen). International endpoint — the mainland
1015
+ // China endpoint (dashscope.aliyuncs.com) 403s/404s from outside China in
1016
+ // a way that can look like a bad key; this is the deliberate default for
1017
+ // a US-based deployment.
1018
+ qwen: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions"
1019
+ };
1020
+ var OPENAI_COMPAT_NATIVE_STRUCTURED = /* @__PURE__ */ new Set([
1021
+ "openai"
1022
+ ]);
1023
+ function supportsNativeJsonSchema(provider, model) {
1024
+ if (!OPENAI_COMPAT_NATIVE_STRUCTURED.has(provider.toLowerCase())) {
1025
+ return false;
1026
+ }
1027
+ const m = model.toLowerCase();
1028
+ if (/^o1(?:-|$)/.test(m)) return false;
1029
+ return true;
1030
+ }
1031
+ function buildOpenAICompatibleRequest(opts) {
1032
+ const msgs = opts.messages.map((m) => {
1033
+ const text = typeof m.content === "string" ? m.content : "";
1034
+ const images = imagesOf(m);
1035
+ 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 };
1036
+ });
1037
+ const body = {
1038
+ model: opts.model,
1039
+ messages: msgs
1040
+ };
1041
+ if (supportsTemperature(opts.provider, opts.model)) {
1042
+ body.max_tokens = opts.maxTokens;
1043
+ body.temperature = opts.temperature;
1044
+ } else {
1045
+ body.max_completion_tokens = opts.maxTokens;
1046
+ }
1047
+ if (opts.jsonSchema && supportsNativeJsonSchema(opts.provider, opts.model)) {
1048
+ body.response_format = {
1049
+ type: "json_schema",
1050
+ json_schema: {
1051
+ name: "structured_output",
1052
+ schema: opts.jsonSchema,
1053
+ // NOT strict. Strict mode requires `additionalProperties: false` on
1054
+ // every object and every property listed in `required` — the
1055
+ // engine's schemas don't meet that, so every structured OpenAI call
1056
+ // (planner, judges, synthesis) was a 400, unnoticed until the canary
1057
+ // grew a structured check (2026-10-11). Non-strict still shapes the
1058
+ // reply to the schema, and structured.ts validates it against the
1059
+ // full schema regardless, so strictness added a failure mode and no
1060
+ // safety.
1061
+ strict: false
1062
+ }
1063
+ };
1064
+ }
1065
+ const headers = {
1066
+ "content-type": "application/json",
1067
+ Authorization: `Bearer ${opts.apiKey}`
1068
+ };
1069
+ return { url: opts.url, headers, body };
1070
+ }
1071
+ function parseOpenAICompatibleResponse(opts) {
1072
+ const r = opts.resp ?? {};
1073
+ let text;
1074
+ try {
1075
+ const choices = r["choices"];
1076
+ if (!Array.isArray(choices) || choices.length === 0) {
1077
+ throw new Error("choices missing or empty");
1078
+ }
1079
+ const first = choices[0];
1080
+ const message = first["message"];
1081
+ if (!message || typeof message["content"] !== "string") {
1082
+ throw new Error("message.content missing or not string");
1083
+ }
1084
+ text = message["content"];
1085
+ } catch (e) {
1086
+ throw new ProviderError(
1087
+ "parse",
1088
+ `${opts.provider}: unexpected response shape: ${JSON.stringify(r).slice(0, 200)}`
1089
+ );
1090
+ }
1091
+ const u = r["usage"] ?? {};
1092
+ const details = u["prompt_tokens_details"] ?? {};
1093
+ const cached = Math.trunc(Number(details["cached_tokens"] ?? 0)) || 0;
1094
+ const promptTokens = Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0;
1095
+ const reportedCompletion = Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0;
1096
+ const reportedTotal = Math.trunc(Number(u["total_tokens"] ?? 0)) || 0;
1097
+ const derivedCompletion = reportedTotal > promptTokens ? reportedTotal - promptTokens : reportedCompletion;
1098
+ const completionTokens = Math.max(reportedCompletion, derivedCompletion);
1099
+ const usage = {
1100
+ provider: opts.provider,
1101
+ model: opts.model,
1102
+ prompt_tokens: promptTokens,
1103
+ completion_tokens: completionTokens,
1104
+ cached_tokens: cached,
1105
+ // Use the response's reported total_tokens directly. Python's
1106
+ // `Usage.to_dict()` falls back to prompt+completion only when
1107
+ // total_tokens is 0/missing; mirror that.
1108
+ total_tokens: reportedTotal,
1109
+ cost_usd: 0,
1110
+ estimated: Object.keys(u).length === 0,
1111
+ purpose: opts.purpose
1112
+ };
1113
+ if (usage.total_tokens === 0) {
1114
+ usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1115
+ }
1116
+ return { text, usage };
1117
+ }
1118
+ function applyPricing3(usage, pricing) {
1119
+ const { cost_usd, estimated } = calculateCost(
1120
+ pricing,
1121
+ usage.provider,
1122
+ usage.model,
1123
+ usage.prompt_tokens,
1124
+ usage.completion_tokens,
1125
+ usage.cached_tokens
1126
+ );
1127
+ return {
1128
+ ...usage,
1129
+ cost_usd,
1130
+ estimated: usage.estimated || estimated
1131
+ };
1132
+ }
1133
+ async function sendOpenAICompatible(args) {
1134
+ const { url, headers, body } = buildOpenAICompatibleRequest({
1135
+ provider: args.provider,
1136
+ model: args.model,
1137
+ apiKey: args.apiKey,
1138
+ url: args.url,
1139
+ messages: args.messages,
1140
+ maxTokens: args.maxTokens,
1141
+ temperature: args.temperature,
1142
+ ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1143
+ });
1144
+ if (args.provider === "openai" && isReasoningModel("openai", args.model) && !/^o1(?:-|$)/i.test(args.model)) {
1145
+ const effort = (process.env["CROSSCHECK_OPENAI_REASONING_EFFORT"] ?? "low").toLowerCase();
1146
+ if (effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") {
1147
+ body.reasoning_effort = effort;
1148
+ }
1149
+ }
1150
+ if (isReasoningModel(args.provider, args.model) && // Only where the cap is a SHARED budget. On a provider that bounds visible
1151
+ // output only (xAI), headroom cannot protect the answer from being crowded
1152
+ // out — nothing is crowding it — so it would merely authorise a
1153
+ // 25,000-token reply and the bill that comes with it.
1154
+ reasoningSharesOutputBudget(args.provider) && typeof body.max_completion_tokens === "number") {
1155
+ const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1156
+ const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1157
+ body.max_completion_tokens += headroom;
1158
+ }
1159
+ const doFetch = args.fetchImpl ?? globalThis.fetch;
1160
+ const init = {
1161
+ method: "POST",
1162
+ headers,
1353
1163
  body: JSON.stringify(body)
1354
1164
  };
1355
1165
  if (args.signal) init.signal = args.signal;
@@ -1360,7 +1170,7 @@ async function sendGemini(args) {
1360
1170
  } catch (e) {
1361
1171
  const d = diagnoseNetworkError(e);
1362
1172
  if (!looksLikeNetworkFailure(d)) throw e;
1363
- throw new ProviderError(d.kind, networkErrorMessage("gemini", d), {
1173
+ throw new ProviderError(d.kind, networkErrorMessage(args.provider, d), {
1364
1174
  transient: d.transient
1365
1175
  });
1366
1176
  }
@@ -1370,374 +1180,808 @@ async function sendGemini(args) {
1370
1180
  try {
1371
1181
  parsed = await respLike.json();
1372
1182
  } catch (e) {
1373
- throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
1183
+ throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
1374
1184
  }
1375
- const { text, usage, finishReason } = parseGeminiResponse({
1185
+ const { text, usage } = parseOpenAICompatibleResponse({
1376
1186
  resp: parsed,
1187
+ provider: args.provider,
1377
1188
  model: args.model,
1378
1189
  purpose: args.purpose ?? "worker"
1379
1190
  });
1380
- if (text.trim() === "" && finishReason === "MAX_TOKENS") {
1381
- throw new ProviderError(
1382
- "parse",
1383
- `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.`,
1384
- { transient: false }
1385
- );
1386
- }
1387
- return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
1191
+ return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1388
1192
  }
1389
1193
  const bodyText = await respLike.text().catch(() => "");
1390
- throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike), args.model);
1194
+ throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike), args.model);
1391
1195
  };
1392
- await acquireRateLimit("gemini", args.signal ? { signal: args.signal } : void 0);
1196
+ await acquireRateLimit(args.provider, args.signal ? { signal: args.signal } : void 0);
1393
1197
  return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1394
1198
  }
1395
1199
 
1396
- // src/providers/openai-compatible.ts
1397
- var OPENAI_COMPAT_DEFAULT_URLS = {
1398
- openai: "https://api.openai.com/v1/chat/completions",
1399
- xai: "https://api.x.ai/v1/chat/completions",
1400
- mistral: "https://api.mistral.ai/v1/chat/completions",
1401
- groq: "https://api.groq.com/openai/v1/chat/completions",
1402
- deepseek: "https://api.deepseek.com/v1/chat/completions",
1403
- // Moonshot AI (Kimi). OpenAI-compatible chat completions endpoint.
1404
- kimi: "https://api.moonshot.ai/v1/chat/completions",
1405
- // Alibaba Cloud DashScope (Qwen). International endpoint — the mainland
1406
- // China endpoint (dashscope.aliyuncs.com) 403s/404s from outside China in
1407
- // a way that can look like a bad key; this is the deliberate default for
1408
- // a US-based deployment.
1409
- qwen: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions"
1200
+ // src/providers/registry.ts
1201
+ var DEFAULT_MODELS = {
1202
+ anthropic: "claude-opus-5-5",
1203
+ openai: "gpt-6-sol",
1204
+ xai: "grok-4-latest",
1205
+ mistral: "mistral-large-latest",
1206
+ groq: "llama-3.3-70b-versatile",
1207
+ deepseek: "deepseek-chat",
1208
+ gemini: "gemini-3.8-flash",
1209
+ kimi: "kimi-k3",
1210
+ qwen: "qwen3.8-max"
1211
+ };
1212
+ var DEFAULT_FALLBACKS = {
1213
+ // gpt-5.6 requires a billing-enabled account and is excluded from the free
1214
+ // tier, so gpt-5 remains the broadly-available floor. One hop, not a
1215
+ // ladder — an intermediate 5.5 step would just be another model to reason
1216
+ // about.
1217
+ openai: ["gpt-5"],
1218
+ // 3.8 Flash is recent enough that some projects won't have it yet.
1219
+ gemini: ["gemini-3.1-pro-preview"],
1220
+ // Opus 5.5 is new enough that an account could still be waiting on it.
1221
+ // Opus 5 is the same family one step back — and this chain is consulted
1222
+ // ONLY for a model-access failure, never for a bad key or a rate limit,
1223
+ // so it can't quietly downgrade anyone who simply hit a 429.
1224
+ anthropic: ["claude-opus-5"]
1225
+ };
1226
+ var MODEL_ENV_VARS = {
1227
+ anthropic: "ANTHROPIC_MODEL",
1228
+ openai: "OPENAI_MODEL",
1229
+ xai: "XAI_MODEL",
1230
+ mistral: "MISTRAL_MODEL",
1231
+ groq: "GROQ_MODEL",
1232
+ deepseek: "DEEPSEEK_MODEL",
1233
+ gemini: "GEMINI_MODEL",
1234
+ kimi: "KIMI_MODEL",
1235
+ qwen: "QWEN_MODEL"
1236
+ };
1237
+ function parseFallbackModels(raw) {
1238
+ if (!raw) return [];
1239
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
1240
+ }
1241
+ function fallbackModelsFor(env, provider) {
1242
+ const envVar = MODEL_ENV_VARS[provider];
1243
+ const fromEnv = envVar ? parseFallbackModels(env[`${envVar}_FALLBACKS`]) : [];
1244
+ if (fromEnv.length > 0) return fromEnv;
1245
+ const pinned = envVar ? env[envVar] : void 0;
1246
+ if (pinned && pinned !== DEFAULT_MODELS[provider]) return [];
1247
+ return DEFAULT_FALLBACKS[provider] ?? [];
1248
+ }
1249
+ var DEFAULT_EXCLUDED_PROVIDERS = ["mistral", "groq"];
1250
+ function excludedProviders(env) {
1251
+ const parse = (v) => (v ?? "").split(",").map((x) => x.trim().toLowerCase()).filter((x) => x !== "");
1252
+ const excluded = new Set(
1253
+ parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]).length > 0 ? parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]) : DEFAULT_EXCLUDED_PROVIDERS
1254
+ );
1255
+ for (const name of parse(env["CROSSCHECK_ENABLE_PROVIDERS"])) excluded.delete(name);
1256
+ return excluded;
1257
+ }
1258
+ function buildProviders(opts) {
1259
+ const out = {};
1260
+ const excluded = excludedProviders(opts.env);
1261
+ const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
1262
+ if (anthropicKey && !excluded.has("anthropic")) {
1263
+ const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
1264
+ out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
1265
+ }
1266
+ const openAiCompatSpec = [
1267
+ { name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
1268
+ { name: "xai", keyEnv: "XAI_API_KEY", modelEnv: "XAI_MODEL", defaultModel: DEFAULT_MODELS["xai"] },
1269
+ { name: "mistral", keyEnv: "MISTRAL_API_KEY", modelEnv: "MISTRAL_MODEL", defaultModel: DEFAULT_MODELS["mistral"] },
1270
+ { name: "groq", keyEnv: "GROQ_API_KEY", modelEnv: "GROQ_MODEL", defaultModel: DEFAULT_MODELS["groq"] },
1271
+ { name: "deepseek", keyEnv: "DEEPSEEK_API_KEY", modelEnv: "DEEPSEEK_MODEL", defaultModel: DEFAULT_MODELS["deepseek"] },
1272
+ { name: "kimi", keyEnv: "KIMI_API_KEY", modelEnv: "KIMI_MODEL", defaultModel: DEFAULT_MODELS["kimi"] },
1273
+ { name: "qwen", keyEnv: "QWEN_API_KEY", modelEnv: "QWEN_MODEL", defaultModel: DEFAULT_MODELS["qwen"] }
1274
+ ];
1275
+ for (const s of openAiCompatSpec) {
1276
+ const apiKey = opts.env[s.keyEnv];
1277
+ if (!apiKey) continue;
1278
+ if (excluded.has(s.name)) continue;
1279
+ const model = opts.env[s.modelEnv] ?? s.defaultModel;
1280
+ out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
1281
+ }
1282
+ const geminiKey = opts.env["GEMINI_API_KEY"];
1283
+ if (geminiKey && !excluded.has("gemini")) {
1284
+ const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
1285
+ out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
1286
+ }
1287
+ return out;
1288
+ }
1289
+ function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
1290
+ return {
1291
+ name: "anthropic",
1292
+ model,
1293
+ fallbackModels,
1294
+ send: async (args) => {
1295
+ const sendOpts = {
1296
+ ...args,
1297
+ apiKey,
1298
+ model: args.modelOverride ?? model,
1299
+ pricing: opts.pricing
1300
+ };
1301
+ if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1302
+ return sendAnthropic(sendOpts);
1303
+ }
1304
+ };
1305
+ }
1306
+ function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
1307
+ const url = OPENAI_COMPAT_DEFAULT_URLS[name];
1308
+ return {
1309
+ name,
1310
+ model,
1311
+ fallbackModels,
1312
+ send: async (args) => {
1313
+ const sendOpts = {
1314
+ ...args,
1315
+ provider: name,
1316
+ apiKey,
1317
+ model: args.modelOverride ?? model,
1318
+ url,
1319
+ pricing: opts.pricing
1320
+ };
1321
+ if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1322
+ return sendOpenAICompatible(sendOpts);
1323
+ }
1324
+ };
1325
+ }
1326
+ function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
1327
+ return {
1328
+ name: "gemini",
1329
+ model,
1330
+ fallbackModels,
1331
+ send: async (args) => {
1332
+ const sendOpts = {
1333
+ ...args,
1334
+ apiKey,
1335
+ model: args.modelOverride ?? model,
1336
+ pricing: opts.pricing
1337
+ };
1338
+ if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1339
+ return sendGemini(sendOpts);
1340
+ }
1341
+ };
1342
+ }
1343
+
1344
+ // src/core/reasoning-upgrade.ts
1345
+ var UPGRADE_MODEL = "claude-fable-5";
1346
+ var UPGRADE_LABEL = "Fable 5";
1347
+ function isUpgradeRequested(args) {
1348
+ if (args["upgrade"] === true) return true;
1349
+ const reasoning = args["reasoning"];
1350
+ if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
1351
+ return true;
1352
+ }
1353
+ const model = args["model"];
1354
+ if (typeof model === "string") {
1355
+ const m = model.trim().toLowerCase();
1356
+ if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
1357
+ return true;
1358
+ }
1359
+ }
1360
+ return false;
1361
+ }
1362
+ var HEAVY_TERMS = [
1363
+ /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
1364
+ /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
1365
+ /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
1366
+ /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
1367
+ /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
1368
+ /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
1369
+ /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
1370
+ /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
1371
+ /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
1372
+ /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
1373
+ /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
1374
+ /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
1375
+ ];
1376
+ var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
1377
+ function assessReasoning(text) {
1378
+ const s = typeof text === "string" ? text : "";
1379
+ const signals = [];
1380
+ let score = 0;
1381
+ let termHits = 0;
1382
+ for (const re of HEAVY_TERMS) {
1383
+ if (re.test(s)) termHits += 1;
1384
+ }
1385
+ if (termHits > 0) {
1386
+ const add = Math.min(termHits, 4);
1387
+ score += add;
1388
+ signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
1389
+ }
1390
+ if (DIFFICULTY_TERMS.test(s)) {
1391
+ score += 1;
1392
+ signals.push("explicit difficulty marker");
1393
+ }
1394
+ const words = s.split(/\s+/).filter(Boolean).length;
1395
+ if (words >= 120) {
1396
+ score += 2;
1397
+ signals.push("long, detailed prompt");
1398
+ } else if (words >= 50) {
1399
+ score += 1;
1400
+ signals.push("substantial prompt");
1401
+ }
1402
+ const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
1403
+ if (constraints >= 3) {
1404
+ score += 1;
1405
+ signals.push(`${constraints} constraints/steps`);
1406
+ }
1407
+ const heavy = score >= 3;
1408
+ const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
1409
+ return { heavy, score, reason, signals };
1410
+ }
1411
+ function buildSuggestedUpgrade(toolName, assessment) {
1412
+ return {
1413
+ model: UPGRADE_MODEL,
1414
+ label: UPGRADE_LABEL,
1415
+ reason: assessment.reason,
1416
+ 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.`
1417
+ };
1418
+ }
1419
+
1420
+ // src/core/super-mode.ts
1421
+ var SUPER_MODELS = {
1422
+ // Fable 5.1 verified present on the account; 5 as the fallback for accounts
1423
+ // that haven't been granted 5.1 yet.
1424
+ anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
1425
+ // Astra is live (verified against the account's model list 2026-09-23).
1426
+ // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
1427
+ // that constant means "OpenAI's top reasoning model" for the max-reasoning
1428
+ // co-reasoner, which is a different question from "what should super fall
1429
+ // back to". Sharing the symbol silently coupled two unrelated decisions.
1430
+ openai: ["gpt-6-astra", "gpt-6-sol"],
1431
+ // Explicitly left as-is per product decision. Note grok-4-latest is not in
1432
+ // xAI's published model list, but it resolves in practice — real panel calls
1433
+ // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
1434
+ // rather than a dead id.
1435
+ xai: [DEFAULT_MODELS["xai"]],
1436
+ // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
1437
+ // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
1438
+ // repeat the same model and retry one that just failed. Named explicitly.
1439
+ gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
1440
+ kimi: [DEFAULT_MODELS["kimi"]],
1441
+ qwen: [DEFAULT_MODELS["qwen"]]
1410
1442
  };
1411
- var OPENAI_COMPAT_NATIVE_STRUCTURED = /* @__PURE__ */ new Set([
1412
- "openai"
1413
- ]);
1414
- function supportsNativeJsonSchema(provider, model) {
1415
- if (!OPENAI_COMPAT_NATIVE_STRUCTURED.has(provider.toLowerCase())) {
1416
- return false;
1443
+ var SUPER_ENV_VARS = {
1444
+ anthropic: "ANTHROPIC_SUPER_MODEL",
1445
+ openai: "OPENAI_SUPER_MODEL",
1446
+ xai: "XAI_SUPER_MODEL",
1447
+ gemini: "GEMINI_SUPER_MODEL",
1448
+ kimi: "KIMI_SUPER_MODEL",
1449
+ qwen: "QWEN_SUPER_MODEL",
1450
+ mistral: "MISTRAL_SUPER_MODEL",
1451
+ groq: "GROQ_SUPER_MODEL",
1452
+ deepseek: "DEEPSEEK_SUPER_MODEL"
1453
+ };
1454
+ function parseChain(v) {
1455
+ if (!v) return [];
1456
+ const out = [];
1457
+ for (const raw of v.split(",")) {
1458
+ const m = raw.trim();
1459
+ if (m && !out.includes(m)) out.push(m);
1417
1460
  }
1418
- const m = model.toLowerCase();
1419
- if (/^o1(?:-|$)/.test(m)) return false;
1420
- return true;
1461
+ return out;
1421
1462
  }
1422
- function buildOpenAICompatibleRequest(opts) {
1423
- const msgs = opts.messages.map((m) => {
1424
- const text = typeof m.content === "string" ? m.content : "";
1425
- const images = imagesOf(m);
1426
- 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 };
1427
- });
1428
- const body = {
1429
- model: opts.model,
1430
- messages: msgs
1463
+ var superEnvStore = new import_node_async_hooks.AsyncLocalStorage();
1464
+ var currentEnv = () => superEnvStore.getStore() ?? process.env;
1465
+ function superChainFor(provider, env = currentEnv()) {
1466
+ const varName = SUPER_ENV_VARS[provider];
1467
+ const user = varName ? parseChain(env[varName]) : [];
1468
+ if (user.length > 0) {
1469
+ const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
1470
+ return [...user, ...fallbacks.filter((m) => !user.includes(m))];
1471
+ }
1472
+ return SUPER_MODELS[provider] ?? [];
1473
+ }
1474
+ function superPrimary(provider, env = currentEnv()) {
1475
+ return superChainFor(provider, env)[0];
1476
+ }
1477
+ var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
1478
+ function isSuperRequested(args) {
1479
+ return args["super"] === true;
1480
+ }
1481
+ function retargetForSuper(p, env = currentEnv()) {
1482
+ const chain = superChainFor(p.name, env);
1483
+ if (chain.length === 0) return p;
1484
+ const [primary, ...fallbacks] = chain;
1485
+ return retargetProvider(p, primary, fallbacks);
1486
+ }
1487
+ var SUPER_BUDGET_MULTIPLIER = 3;
1488
+ var SUPER_BUDGET_CEILING = 16e3;
1489
+ function superBudget(base) {
1490
+ return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
1491
+ }
1492
+
1493
+ // src/core/moderator.ts
1494
+ var DEFAULT_MODERATOR = "anthropic";
1495
+ var PROVIDER_ALIASES = {
1496
+ grok: "xai",
1497
+ claude: "anthropic",
1498
+ google: "gemini",
1499
+ chatgpt: "openai",
1500
+ gpt: "openai",
1501
+ moonshot: "kimi"
1502
+ };
1503
+ function parseModeratorSpec(raw) {
1504
+ if (typeof raw !== "string") return null;
1505
+ const s = raw.trim();
1506
+ if (!s) return null;
1507
+ const i = s.indexOf(":");
1508
+ const providerPart = (i < 0 ? s : s.slice(0, i)).trim().toLowerCase();
1509
+ const modelPart = i < 0 ? "" : s.slice(i + 1).trim();
1510
+ if (!providerPart) return null;
1511
+ return {
1512
+ provider: PROVIDER_ALIASES[providerPart] ?? providerPart,
1513
+ model: modelPart || null
1431
1514
  };
1432
- if (supportsTemperature(opts.provider, opts.model)) {
1433
- body.max_tokens = opts.maxTokens;
1434
- body.temperature = opts.temperature;
1435
- } else {
1436
- body.max_completion_tokens = opts.maxTokens;
1515
+ }
1516
+ function pickModeratorSpec(...candidates) {
1517
+ for (const c of candidates) {
1518
+ const s = parseModeratorSpec(c);
1519
+ if (s) return s;
1520
+ }
1521
+ return { provider: DEFAULT_MODERATOR, model: null };
1522
+ }
1523
+ function resolveModerator(providers, ...candidates) {
1524
+ const spec = pickModeratorSpec(...candidates);
1525
+ const base = providers[spec.provider];
1526
+ if (!base) return { provider: void 0, spec, pinned: false };
1527
+ if (!spec.model) return { provider: base, spec, pinned: false };
1528
+ return { provider: retargetProvider(base, spec.model), spec, pinned: true };
1529
+ }
1530
+ function synthesisSeat(o) {
1531
+ const { moderator, pinned, superRequested, upgradeRequested } = o;
1532
+ const wantsMore = superRequested || upgradeRequested;
1533
+ if (!wantsMore) return { provider: moderator, upgraded: false, label: null, skipReason: null };
1534
+ if (pinned) {
1535
+ return {
1536
+ provider: moderator,
1537
+ upgraded: false,
1538
+ label: null,
1539
+ skipReason: `the moderator is pinned to ${moderator.name}:${moderator.model}; a pinned model is used as-is.`
1540
+ };
1437
1541
  }
1438
- if (opts.jsonSchema && supportsNativeJsonSchema(opts.provider, opts.model)) {
1439
- body.response_format = {
1440
- type: "json_schema",
1441
- json_schema: {
1442
- name: "structured_output",
1443
- schema: opts.jsonSchema,
1444
- // NOT strict. Strict mode requires `additionalProperties: false` on
1445
- // every object and every property listed in `required` — the
1446
- // engine's schemas don't meet that, so every structured OpenAI call
1447
- // (planner, judges, synthesis) was a 400, unnoticed until the canary
1448
- // grew a structured check (2026-10-11). Non-strict still shapes the
1449
- // reply to the schema, and structured.ts validates it against the
1450
- // full schema regardless, so strictness added a failure mode and no
1451
- // safety.
1452
- strict: false
1453
- }
1542
+ if (moderator.name === "anthropic" && !superRequested) {
1543
+ return { provider: retargetProvider(moderator, UPGRADE_MODEL), upgraded: true, label: UPGRADE_LABEL, skipReason: null };
1544
+ }
1545
+ if (superChainFor(moderator.name).length === 0) {
1546
+ return {
1547
+ provider: moderator,
1548
+ upgraded: false,
1549
+ label: null,
1550
+ skipReason: `no flagship model is known for ${moderator.name}; the moderator ran at ${moderator.model}.`
1454
1551
  };
1455
1552
  }
1456
- const headers = {
1457
- "content-type": "application/json",
1458
- Authorization: `Bearer ${opts.apiKey}`
1459
- };
1460
- return { url: opts.url, headers, body };
1553
+ const seat = retargetForSuper(moderator);
1554
+ const label = moderator.name === "anthropic" ? UPGRADE_LABEL : seat.model;
1555
+ return { provider: seat, upgraded: true, label, skipReason: null };
1461
1556
  }
1462
- function parseOpenAICompatibleResponse(opts) {
1463
- const r = opts.resp ?? {};
1464
- let text;
1465
- try {
1466
- const choices = r["choices"];
1467
- if (!Array.isArray(choices) || choices.length === 0) {
1468
- throw new Error("choices missing or empty");
1557
+
1558
+ // ../../config/pricing.json
1559
+ var pricing_default = {
1560
+ _meta: {
1561
+ currency: "USD",
1562
+ unit: "per_1k_tokens",
1563
+ updated_at: "2026-09-06",
1564
+ 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."
1565
+ },
1566
+ anthropic: {
1567
+ _source: {
1568
+ url: "https://platform.claude.com/docs/en/about-claude/pricing",
1569
+ verified_at: "2026-09-27"
1570
+ },
1571
+ "claude-opus-4-5": {
1572
+ prompt_per_1k: 5e-3,
1573
+ completion_per_1k: 0.025,
1574
+ cached_per_1k: 5e-4
1575
+ },
1576
+ "claude-opus-4-7": {
1577
+ prompt_per_1k: 5e-3,
1578
+ completion_per_1k: 0.025,
1579
+ cached_per_1k: 5e-4
1580
+ },
1581
+ "claude-opus-4-8": {
1582
+ prompt_per_1k: 5e-3,
1583
+ completion_per_1k: 0.025,
1584
+ cached_per_1k: 5e-4
1585
+ },
1586
+ "claude-opus-5-5": {
1587
+ prompt_per_1k: 4e-3,
1588
+ completion_per_1k: 0.02,
1589
+ cached_per_1k: 2e-4
1590
+ },
1591
+ "claude-opus-5": {
1592
+ prompt_per_1k: 5e-3,
1593
+ completion_per_1k: 0.025,
1594
+ cached_per_1k: 5e-4
1595
+ },
1596
+ "claude-fable-5": {
1597
+ prompt_per_1k: 0.01,
1598
+ completion_per_1k: 0.05,
1599
+ cached_per_1k: 1e-3
1600
+ },
1601
+ "claude-sonnet-4-5": {
1602
+ prompt_per_1k: 3e-3,
1603
+ completion_per_1k: 0.015,
1604
+ cached_per_1k: 3e-4
1605
+ },
1606
+ "claude-sonnet-4-6": {
1607
+ prompt_per_1k: 3e-3,
1608
+ completion_per_1k: 0.015,
1609
+ cached_per_1k: 3e-4
1610
+ },
1611
+ "claude-haiku-4-5": {
1612
+ prompt_per_1k: 1e-3,
1613
+ completion_per_1k: 5e-3,
1614
+ cached_per_1k: 1e-4
1615
+ },
1616
+ "claude-3-5-sonnet": {
1617
+ prompt_per_1k: 3e-3,
1618
+ completion_per_1k: 0.015,
1619
+ cached_per_1k: 3e-4
1620
+ },
1621
+ "claude-3-5-haiku": {
1622
+ prompt_per_1k: 8e-4,
1623
+ completion_per_1k: 4e-3,
1624
+ cached_per_1k: 8e-5
1625
+ },
1626
+ "claude-fable-5-1": {
1627
+ prompt_per_1k: 0.01,
1628
+ completion_per_1k: 0.05,
1629
+ cached_per_1k: 25e-5
1630
+ }
1631
+ },
1632
+ openai: {
1633
+ _source: {
1634
+ url: "https://developers.openai.com/api/docs/pricing",
1635
+ verified_at: "2026-09-27"
1636
+ },
1637
+ "gpt-6-astra": {
1638
+ prompt_per_1k: 0.01,
1639
+ completion_per_1k: 0.05,
1640
+ cached_per_1k: 1e-3
1641
+ },
1642
+ "gpt-6-sol": {
1643
+ prompt_per_1k: 2e-3,
1644
+ completion_per_1k: 0.01,
1645
+ cached_per_1k: 2e-4
1646
+ },
1647
+ "gpt-6-luna": {
1648
+ prompt_per_1k: 1e-4,
1649
+ completion_per_1k: 5e-4,
1650
+ cached_per_1k: 1e-5
1651
+ },
1652
+ "gpt-5.6-sol": {
1653
+ prompt_per_1k: 4e-3,
1654
+ completion_per_1k: 0.02,
1655
+ cached_per_1k: 4e-4
1656
+ },
1657
+ "gpt-5.6-terra": {
1658
+ prompt_per_1k: 2e-3,
1659
+ completion_per_1k: 0.012,
1660
+ cached_per_1k: 2e-4
1661
+ },
1662
+ "gpt-5.6-luna": {
1663
+ prompt_per_1k: 2e-4,
1664
+ completion_per_1k: 12e-4,
1665
+ cached_per_1k: 2e-5
1666
+ },
1667
+ "gpt-5.6": {
1668
+ prompt_per_1k: 4e-3,
1669
+ completion_per_1k: 0.02,
1670
+ cached_per_1k: 4e-4
1671
+ },
1672
+ "gpt-5.5": {
1673
+ prompt_per_1k: 5e-3,
1674
+ completion_per_1k: 0.03,
1675
+ cached_per_1k: 5e-4
1676
+ },
1677
+ "gpt-5": {
1678
+ prompt_per_1k: 125e-5,
1679
+ completion_per_1k: 0.01,
1680
+ cached_per_1k: 125e-6
1681
+ },
1682
+ "gpt-5-mini": {
1683
+ prompt_per_1k: 25e-5,
1684
+ completion_per_1k: 2e-3,
1685
+ cached_per_1k: 25e-6
1686
+ },
1687
+ "gpt-5-nano": {
1688
+ prompt_per_1k: 5e-5,
1689
+ completion_per_1k: 4e-4,
1690
+ cached_per_1k: 5e-6
1691
+ },
1692
+ "gpt-4o": {
1693
+ prompt_per_1k: 25e-4,
1694
+ completion_per_1k: 0.01,
1695
+ cached_per_1k: 125e-5
1696
+ },
1697
+ "gpt-4o-mini": {
1698
+ prompt_per_1k: 15e-5,
1699
+ completion_per_1k: 6e-4,
1700
+ cached_per_1k: 75e-6
1701
+ },
1702
+ o1: {
1703
+ prompt_per_1k: 0.015,
1704
+ completion_per_1k: 0.06,
1705
+ cached_per_1k: 75e-4
1706
+ },
1707
+ "o1-mini": {
1708
+ prompt_per_1k: 3e-3,
1709
+ completion_per_1k: 0.012,
1710
+ cached_per_1k: 15e-4
1711
+ },
1712
+ o3: {
1713
+ prompt_per_1k: 2e-3,
1714
+ completion_per_1k: 8e-3,
1715
+ cached_per_1k: 5e-4
1716
+ },
1717
+ "o3-mini": {
1718
+ prompt_per_1k: 11e-4,
1719
+ completion_per_1k: 44e-4,
1720
+ cached_per_1k: 55e-5
1721
+ },
1722
+ "gpt-5-pro": {
1723
+ prompt_per_1k: 0.015,
1724
+ completion_per_1k: 0.12,
1725
+ cached_per_1k: 0
1726
+ },
1727
+ "gpt-5.2-pro": {
1728
+ prompt_per_1k: 0.021,
1729
+ completion_per_1k: 0.168,
1730
+ cached_per_1k: 0
1731
+ },
1732
+ "gpt-5.4-pro": {
1733
+ prompt_per_1k: 0.03,
1734
+ completion_per_1k: 0.18,
1735
+ cached_per_1k: 0
1736
+ },
1737
+ "gpt-5.5-pro": {
1738
+ prompt_per_1k: 0.03,
1739
+ completion_per_1k: 0.18,
1740
+ cached_per_1k: 0
1741
+ },
1742
+ "o1-pro": {
1743
+ prompt_per_1k: 0.15,
1744
+ completion_per_1k: 0.6,
1745
+ cached_per_1k: 0
1746
+ },
1747
+ "o3-pro": {
1748
+ prompt_per_1k: 0.02,
1749
+ completion_per_1k: 0.08,
1750
+ cached_per_1k: 0
1469
1751
  }
1470
- const first = choices[0];
1471
- const message = first["message"];
1472
- if (!message || typeof message["content"] !== "string") {
1473
- throw new Error("message.content missing or not string");
1752
+ },
1753
+ xai: {
1754
+ "grok-4-latest": {
1755
+ prompt_per_1k: 3e-3,
1756
+ completion_per_1k: 0.015,
1757
+ cached_per_1k: 75e-5
1758
+ },
1759
+ "grok-3": {
1760
+ prompt_per_1k: 3e-3,
1761
+ completion_per_1k: 0.015,
1762
+ cached_per_1k: 15e-4
1763
+ },
1764
+ "grok-3-mini": {
1765
+ prompt_per_1k: 3e-4,
1766
+ completion_per_1k: 5e-4,
1767
+ cached_per_1k: 15e-5
1474
1768
  }
1475
- text = message["content"];
1476
- } catch (e) {
1477
- throw new ProviderError(
1478
- "parse",
1479
- `${opts.provider}: unexpected response shape: ${JSON.stringify(r).slice(0, 200)}`
1480
- );
1481
- }
1482
- const u = r["usage"] ?? {};
1483
- const details = u["prompt_tokens_details"] ?? {};
1484
- const cached = Math.trunc(Number(details["cached_tokens"] ?? 0)) || 0;
1485
- const promptTokens = Math.trunc(Number(u["prompt_tokens"] ?? 0)) || 0;
1486
- const reportedCompletion = Math.trunc(Number(u["completion_tokens"] ?? 0)) || 0;
1487
- const reportedTotal = Math.trunc(Number(u["total_tokens"] ?? 0)) || 0;
1488
- const derivedCompletion = reportedTotal > promptTokens ? reportedTotal - promptTokens : reportedCompletion;
1489
- const completionTokens = Math.max(reportedCompletion, derivedCompletion);
1490
- const usage = {
1491
- provider: opts.provider,
1492
- model: opts.model,
1493
- prompt_tokens: promptTokens,
1494
- completion_tokens: completionTokens,
1495
- cached_tokens: cached,
1496
- // Use the response's reported total_tokens directly. Python's
1497
- // `Usage.to_dict()` falls back to prompt+completion only when
1498
- // total_tokens is 0/missing; mirror that.
1499
- total_tokens: reportedTotal,
1500
- cost_usd: 0,
1501
- estimated: Object.keys(u).length === 0,
1502
- purpose: opts.purpose
1503
- };
1504
- if (usage.total_tokens === 0) {
1505
- usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1506
- }
1507
- return { text, usage };
1508
- }
1509
- function applyPricing3(usage, pricing) {
1510
- const { cost_usd, estimated } = calculateCost(
1511
- pricing,
1512
- usage.provider,
1513
- usage.model,
1514
- usage.prompt_tokens,
1515
- usage.completion_tokens,
1516
- usage.cached_tokens
1517
- );
1518
- return {
1519
- ...usage,
1520
- cost_usd,
1521
- estimated: usage.estimated || estimated
1522
- };
1523
- }
1524
- async function sendOpenAICompatible(args) {
1525
- const { url, headers, body } = buildOpenAICompatibleRequest({
1526
- provider: args.provider,
1527
- model: args.model,
1528
- apiKey: args.apiKey,
1529
- url: args.url,
1530
- messages: args.messages,
1531
- maxTokens: args.maxTokens,
1532
- temperature: args.temperature,
1533
- ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1534
- });
1535
- if (args.provider === "openai" && isReasoningModel("openai", args.model) && !/^o1(?:-|$)/i.test(args.model)) {
1536
- const effort = (process.env["CROSSCHECK_OPENAI_REASONING_EFFORT"] ?? "low").toLowerCase();
1537
- if (effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") {
1538
- body.reasoning_effort = effort;
1769
+ },
1770
+ gemini: {
1771
+ "gemini-3.1-pro-preview": {
1772
+ prompt_per_1k: 2e-3,
1773
+ completion_per_1k: 0.012,
1774
+ cached_per_1k: 2e-4
1775
+ },
1776
+ "gemini-3.5-flash": {
1777
+ prompt_per_1k: 15e-4,
1778
+ completion_per_1k: 9e-3,
1779
+ cached_per_1k: 15e-5
1780
+ },
1781
+ "gemini-2.5-flash": {
1782
+ prompt_per_1k: 3e-4,
1783
+ completion_per_1k: 25e-4,
1784
+ cached_per_1k: 75e-6
1785
+ },
1786
+ "gemini-2.5-flash-lite": {
1787
+ prompt_per_1k: 1e-4,
1788
+ completion_per_1k: 4e-4,
1789
+ cached_per_1k: 25e-6
1790
+ },
1791
+ "gemini-3.8-flash": {
1792
+ prompt_per_1k: 75e-5,
1793
+ completion_per_1k: 375e-5,
1794
+ cached_per_1k: 75e-6
1539
1795
  }
1540
- }
1541
- if (isReasoningModel(args.provider, args.model) && // Only where the cap is a SHARED budget. On a provider that bounds visible
1542
- // output only (xAI), headroom cannot protect the answer from being crowded
1543
- // out — nothing is crowding it — so it would merely authorise a
1544
- // 25,000-token reply and the bill that comes with it.
1545
- reasoningSharesOutputBudget(args.provider) && typeof body.max_completion_tokens === "number") {
1546
- const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1547
- const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1548
- body.max_completion_tokens += headroom;
1549
- }
1550
- const doFetch = args.fetchImpl ?? globalThis.fetch;
1551
- const init = {
1552
- method: "POST",
1553
- headers,
1554
- body: JSON.stringify(body)
1555
- };
1556
- if (args.signal) init.signal = args.signal;
1557
- const attemptOnce = async () => {
1558
- let respLike;
1559
- try {
1560
- respLike = await doFetch(url, init);
1561
- } catch (e) {
1562
- const d = diagnoseNetworkError(e);
1563
- if (!looksLikeNetworkFailure(d)) throw e;
1564
- throw new ProviderError(d.kind, networkErrorMessage(args.provider, d), {
1565
- transient: d.transient
1566
- });
1796
+ },
1797
+ mistral: {
1798
+ "mistral-large-latest": {
1799
+ prompt_per_1k: 2e-3,
1800
+ completion_per_1k: 6e-3,
1801
+ cached_per_1k: 2e-4
1802
+ },
1803
+ "mistral-small-latest": {
1804
+ prompt_per_1k: 2e-4,
1805
+ completion_per_1k: 6e-4,
1806
+ cached_per_1k: 2e-5
1567
1807
  }
1568
- const status = respLike.status;
1569
- if (status >= 200 && status < 300) {
1570
- let parsed;
1571
- try {
1572
- parsed = await respLike.json();
1573
- } catch (e) {
1574
- throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
1575
- }
1576
- const { text, usage } = parseOpenAICompatibleResponse({
1577
- resp: parsed,
1578
- provider: args.provider,
1579
- model: args.model,
1580
- purpose: args.purpose ?? "worker"
1581
- });
1582
- return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1808
+ },
1809
+ groq: {
1810
+ "llama-3.3-70b-versatile": {
1811
+ prompt_per_1k: 59e-5,
1812
+ completion_per_1k: 79e-5,
1813
+ cached_per_1k: 295e-6
1814
+ },
1815
+ "llama-3.1-8b-instant": {
1816
+ prompt_per_1k: 5e-5,
1817
+ completion_per_1k: 8e-5,
1818
+ cached_per_1k: 25e-6
1583
1819
  }
1584
- const bodyText = await respLike.text().catch(() => "");
1585
- throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike), args.model);
1586
- };
1587
- await acquireRateLimit(args.provider, args.signal ? { signal: args.signal } : void 0);
1588
- return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1589
- }
1590
-
1591
- // src/providers/registry.ts
1592
- var DEFAULT_MODELS = {
1593
- anthropic: "claude-opus-5-5",
1594
- openai: "gpt-6-sol",
1595
- xai: "grok-4-latest",
1596
- mistral: "mistral-large-latest",
1597
- groq: "llama-3.3-70b-versatile",
1598
- deepseek: "deepseek-chat",
1599
- gemini: "gemini-3.8-flash",
1600
- kimi: "kimi-k3",
1601
- qwen: "qwen3.8-max"
1602
- };
1603
- var DEFAULT_FALLBACKS = {
1604
- // gpt-5.6 requires a billing-enabled account and is excluded from the free
1605
- // tier, so gpt-5 remains the broadly-available floor. One hop, not a
1606
- // ladder — an intermediate 5.5 step would just be another model to reason
1607
- // about.
1608
- openai: ["gpt-5"],
1609
- // 3.8 Flash is recent enough that some projects won't have it yet.
1610
- gemini: ["gemini-3.1-pro-preview"],
1611
- // Opus 5.5 is new enough that an account could still be waiting on it.
1612
- // Opus 5 is the same family one step back — and this chain is consulted
1613
- // ONLY for a model-access failure, never for a bad key or a rate limit,
1614
- // so it can't quietly downgrade anyone who simply hit a 429.
1615
- anthropic: ["claude-opus-5"]
1616
- };
1617
- var MODEL_ENV_VARS = {
1618
- anthropic: "ANTHROPIC_MODEL",
1619
- openai: "OPENAI_MODEL",
1620
- xai: "XAI_MODEL",
1621
- mistral: "MISTRAL_MODEL",
1622
- groq: "GROQ_MODEL",
1623
- deepseek: "DEEPSEEK_MODEL",
1624
- gemini: "GEMINI_MODEL",
1625
- kimi: "KIMI_MODEL",
1626
- qwen: "QWEN_MODEL"
1627
- };
1628
- function parseFallbackModels(raw) {
1629
- if (!raw) return [];
1630
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
1631
- }
1632
- function fallbackModelsFor(env, provider) {
1633
- const envVar = MODEL_ENV_VARS[provider];
1634
- const fromEnv = envVar ? parseFallbackModels(env[`${envVar}_FALLBACKS`]) : [];
1635
- if (fromEnv.length > 0) return fromEnv;
1636
- const pinned = envVar ? env[envVar] : void 0;
1637
- if (pinned && pinned !== DEFAULT_MODELS[provider]) return [];
1638
- return DEFAULT_FALLBACKS[provider] ?? [];
1639
- }
1640
- var DEFAULT_EXCLUDED_PROVIDERS = ["mistral", "groq"];
1641
- function excludedProviders(env) {
1642
- const parse = (v) => (v ?? "").split(",").map((x) => x.trim().toLowerCase()).filter((x) => x !== "");
1643
- const excluded = new Set(
1644
- parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]).length > 0 ? parse(env["CROSSCHECK_EXCLUDE_PROVIDERS"]) : DEFAULT_EXCLUDED_PROVIDERS
1645
- );
1646
- for (const name of parse(env["CROSSCHECK_ENABLE_PROVIDERS"])) excluded.delete(name);
1647
- return excluded;
1648
- }
1649
- function buildProviders(opts) {
1650
- const out = {};
1651
- const excluded = excludedProviders(opts.env);
1652
- const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
1653
- if (anthropicKey && !excluded.has("anthropic")) {
1654
- const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
1655
- out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
1656
- }
1657
- const openAiCompatSpec = [
1658
- { name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
1659
- { name: "xai", keyEnv: "XAI_API_KEY", modelEnv: "XAI_MODEL", defaultModel: DEFAULT_MODELS["xai"] },
1660
- { name: "mistral", keyEnv: "MISTRAL_API_KEY", modelEnv: "MISTRAL_MODEL", defaultModel: DEFAULT_MODELS["mistral"] },
1661
- { name: "groq", keyEnv: "GROQ_API_KEY", modelEnv: "GROQ_MODEL", defaultModel: DEFAULT_MODELS["groq"] },
1662
- { name: "deepseek", keyEnv: "DEEPSEEK_API_KEY", modelEnv: "DEEPSEEK_MODEL", defaultModel: DEFAULT_MODELS["deepseek"] },
1663
- { name: "kimi", keyEnv: "KIMI_API_KEY", modelEnv: "KIMI_MODEL", defaultModel: DEFAULT_MODELS["kimi"] },
1664
- { name: "qwen", keyEnv: "QWEN_API_KEY", modelEnv: "QWEN_MODEL", defaultModel: DEFAULT_MODELS["qwen"] }
1665
- ];
1666
- for (const s of openAiCompatSpec) {
1667
- const apiKey = opts.env[s.keyEnv];
1668
- if (!apiKey) continue;
1669
- if (excluded.has(s.name)) continue;
1670
- const model = opts.env[s.modelEnv] ?? s.defaultModel;
1671
- out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
1672
- }
1673
- const geminiKey = opts.env["GEMINI_API_KEY"];
1674
- if (geminiKey && !excluded.has("gemini")) {
1675
- const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
1676
- out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
1677
- }
1678
- return out;
1679
- }
1680
- function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
1681
- return {
1682
- name: "anthropic",
1683
- model,
1684
- fallbackModels,
1685
- send: async (args) => {
1686
- const sendOpts = {
1687
- ...args,
1688
- apiKey,
1689
- model: args.modelOverride ?? model,
1690
- pricing: opts.pricing
1691
- };
1692
- if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1693
- return sendAnthropic(sendOpts);
1820
+ },
1821
+ deepseek: {
1822
+ "deepseek-chat": {
1823
+ prompt_per_1k: 27e-5,
1824
+ completion_per_1k: 11e-4,
1825
+ cached_per_1k: 7e-5
1826
+ },
1827
+ "deepseek-reasoner": {
1828
+ prompt_per_1k: 55e-5,
1829
+ completion_per_1k: 22e-4,
1830
+ cached_per_1k: 14e-5
1694
1831
  }
1695
- };
1696
- }
1697
- function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
1698
- const url = OPENAI_COMPAT_DEFAULT_URLS[name];
1699
- return {
1700
- name,
1701
- model,
1702
- fallbackModels,
1703
- send: async (args) => {
1704
- const sendOpts = {
1705
- ...args,
1706
- provider: name,
1707
- apiKey,
1708
- model: args.modelOverride ?? model,
1709
- url,
1710
- pricing: opts.pricing
1711
- };
1712
- if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1713
- return sendOpenAICompatible(sendOpts);
1832
+ },
1833
+ kimi: {
1834
+ "kimi-k3": {
1835
+ prompt_per_1k: 3e-3,
1836
+ completion_per_1k: 0.015,
1837
+ cached_per_1k: 3e-4
1714
1838
  }
1715
- };
1716
- }
1717
- function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
1718
- return {
1719
- name: "gemini",
1720
- model,
1721
- fallbackModels,
1722
- send: async (args) => {
1723
- const sendOpts = {
1724
- ...args,
1725
- apiKey,
1726
- model: args.modelOverride ?? model,
1727
- pricing: opts.pricing
1728
- };
1729
- if (opts.fetchImpl) sendOpts.fetchImpl = opts.fetchImpl;
1730
- return sendGemini(sendOpts);
1839
+ },
1840
+ qwen: {
1841
+ "qwen3.8-max": {
1842
+ prompt_per_1k: 2e-3,
1843
+ completion_per_1k: 6e-3,
1844
+ cached_per_1k: 2e-4
1845
+ },
1846
+ "qwen3.7-max": {
1847
+ prompt_per_1k: 125e-5,
1848
+ completion_per_1k: 375e-5,
1849
+ cached_per_1k: 125e-6
1850
+ }
1851
+ },
1852
+ _tiers: {
1853
+ low: {
1854
+ description: "Cheap, fast models for simple subtasks (extraction, formatting, short summaries).",
1855
+ models: [
1856
+ {
1857
+ provider: "openai",
1858
+ model: "gpt-4o-mini"
1859
+ },
1860
+ {
1861
+ provider: "openai",
1862
+ model: "gpt-5-mini"
1863
+ },
1864
+ {
1865
+ provider: "anthropic",
1866
+ model: "claude-haiku-4-5"
1867
+ },
1868
+ {
1869
+ provider: "anthropic",
1870
+ model: "claude-3-5-haiku"
1871
+ },
1872
+ {
1873
+ provider: "gemini",
1874
+ model: "gemini-3.5-flash"
1875
+ },
1876
+ {
1877
+ provider: "gemini",
1878
+ model: "gemini-2.5-flash"
1879
+ },
1880
+ {
1881
+ provider: "gemini",
1882
+ model: "gemini-2.5-flash-lite"
1883
+ },
1884
+ {
1885
+ provider: "xai",
1886
+ model: "grok-3-mini"
1887
+ },
1888
+ {
1889
+ provider: "groq",
1890
+ model: "llama-3.1-8b-instant"
1891
+ },
1892
+ {
1893
+ provider: "deepseek",
1894
+ model: "deepseek-chat"
1895
+ }
1896
+ ]
1897
+ },
1898
+ med: {
1899
+ description: "Mid-tier models for reasoning, code, multi-step thinking.",
1900
+ models: [
1901
+ {
1902
+ provider: "anthropic",
1903
+ model: "claude-sonnet-4-5"
1904
+ },
1905
+ {
1906
+ provider: "anthropic",
1907
+ model: "claude-sonnet-4-6"
1908
+ },
1909
+ {
1910
+ provider: "openai",
1911
+ model: "gpt-4o"
1912
+ },
1913
+ {
1914
+ provider: "openai",
1915
+ model: "o3-mini"
1916
+ },
1917
+ {
1918
+ provider: "gemini",
1919
+ model: "gemini-3.1-pro-preview"
1920
+ },
1921
+ {
1922
+ provider: "xai",
1923
+ model: "grok-3"
1924
+ },
1925
+ {
1926
+ provider: "mistral",
1927
+ model: "mistral-large-latest"
1928
+ },
1929
+ {
1930
+ provider: "deepseek",
1931
+ model: "deepseek-reasoner"
1932
+ },
1933
+ {
1934
+ provider: "groq",
1935
+ model: "llama-3.3-70b-versatile"
1936
+ }
1937
+ ]
1938
+ },
1939
+ high: {
1940
+ description: "Most capable models for hard reasoning, planning, audits.",
1941
+ models: [
1942
+ {
1943
+ provider: "anthropic",
1944
+ model: "claude-opus-4-5"
1945
+ },
1946
+ {
1947
+ provider: "anthropic",
1948
+ model: "claude-opus-4-7"
1949
+ },
1950
+ {
1951
+ provider: "anthropic",
1952
+ model: "claude-fable-5"
1953
+ },
1954
+ {
1955
+ provider: "openai",
1956
+ model: "gpt-5.5"
1957
+ },
1958
+ {
1959
+ provider: "openai",
1960
+ model: "gpt-5"
1961
+ },
1962
+ {
1963
+ provider: "openai",
1964
+ model: "o1"
1965
+ },
1966
+ {
1967
+ provider: "openai",
1968
+ model: "o3"
1969
+ },
1970
+ {
1971
+ provider: "xai",
1972
+ model: "grok-4-latest"
1973
+ }
1974
+ ]
1731
1975
  }
1732
- };
1733
- }
1976
+ }
1977
+ };
1734
1978
 
1735
1979
  // src/tools/index.ts
1736
1980
  var import_zod = require("zod");
1737
1981
 
1738
1982
  // src/server-meta.ts
1739
1983
  var SERVER_NAME = "crosscheck-agent";
1740
- var SERVER_VERSION = true ? "0.2.30" : "0.0.0-dev";
1984
+ var SERVER_VERSION = true ? "0.2.31" : "0.0.0-dev";
1741
1985
 
1742
1986
  // src/tools/audit.ts
1743
1987
  var import_node_fs4 = require("fs");
@@ -2420,33 +2664,6 @@ function operatorCeiling(purpose, provider) {
2420
2664
  return null;
2421
2665
  }
2422
2666
 
2423
- // src/core/retarget.ts
2424
- function retargetProvider(p, newModel, fallbackModels) {
2425
- const chain = fallbackModels && fallbackModels.length > 0 ? fallbackModels : void 0;
2426
- if (p.model === newModel && chain === void 0) return p;
2427
- return {
2428
- name: p.name,
2429
- model: newModel,
2430
- ...chain ? { fallbackModels: chain } : {},
2431
- send: (args) => p.send({ ...args, modelOverride: newModel })
2432
- };
2433
- }
2434
- async function loadProviderWeights(storage, names) {
2435
- const out = {};
2436
- for (const raw of names) {
2437
- const name = raw.toLowerCase();
2438
- if (name in out) continue;
2439
- const row = await storage.getProviderStats(name);
2440
- if (!row) {
2441
- out[name] = 0.5;
2442
- continue;
2443
- }
2444
- const total = row.wins + row.losses + row.abstains;
2445
- out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
2446
- }
2447
- return out;
2448
- }
2449
-
2450
2667
  // src/core/model-fallback.ts
2451
2668
  async function sendWithModelFallback(provider, args) {
2452
2669
  const chain = [provider.model, ...provider.fallbackModels ?? []];
@@ -3513,82 +3730,6 @@ function selectForDifficulty(args) {
3513
3730
  };
3514
3731
  }
3515
3732
 
3516
- // src/core/reasoning-upgrade.ts
3517
- var UPGRADE_MODEL = "claude-fable-5";
3518
- var UPGRADE_LABEL = "Fable 5";
3519
- function isUpgradeRequested(args) {
3520
- if (args["upgrade"] === true) return true;
3521
- const reasoning = args["reasoning"];
3522
- if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
3523
- return true;
3524
- }
3525
- const model = args["model"];
3526
- if (typeof model === "string") {
3527
- const m = model.trim().toLowerCase();
3528
- if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
3529
- return true;
3530
- }
3531
- }
3532
- return false;
3533
- }
3534
- var HEAVY_TERMS = [
3535
- /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
3536
- /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
3537
- /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
3538
- /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
3539
- /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
3540
- /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
3541
- /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
3542
- /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
3543
- /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
3544
- /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
3545
- /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
3546
- /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
3547
- ];
3548
- var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
3549
- function assessReasoning(text) {
3550
- const s = typeof text === "string" ? text : "";
3551
- const signals = [];
3552
- let score = 0;
3553
- let termHits = 0;
3554
- for (const re of HEAVY_TERMS) {
3555
- if (re.test(s)) termHits += 1;
3556
- }
3557
- if (termHits > 0) {
3558
- const add = Math.min(termHits, 4);
3559
- score += add;
3560
- signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
3561
- }
3562
- if (DIFFICULTY_TERMS.test(s)) {
3563
- score += 1;
3564
- signals.push("explicit difficulty marker");
3565
- }
3566
- const words = s.split(/\s+/).filter(Boolean).length;
3567
- if (words >= 120) {
3568
- score += 2;
3569
- signals.push("long, detailed prompt");
3570
- } else if (words >= 50) {
3571
- score += 1;
3572
- signals.push("substantial prompt");
3573
- }
3574
- const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
3575
- if (constraints >= 3) {
3576
- score += 1;
3577
- signals.push(`${constraints} constraints/steps`);
3578
- }
3579
- const heavy = score >= 3;
3580
- const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
3581
- return { heavy, score, reason, signals };
3582
- }
3583
- function buildSuggestedUpgrade(toolName, assessment) {
3584
- return {
3585
- model: UPGRADE_MODEL,
3586
- label: UPGRADE_LABEL,
3587
- reason: assessment.reason,
3588
- 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.`
3589
- };
3590
- }
3591
-
3592
3733
  // src/tools/audit.ts
3593
3734
  var AUDIT_OBVIOUS_FAILURE_HIGH = 0.3;
3594
3735
  var AUDIT_OBVIOUS_FAILURE_MED = 0.2;
@@ -3886,9 +4027,14 @@ ${rubricText}`;
3886
4027
  );
3887
4028
  }
3888
4029
  const upgradeRequested = isUpgradeRequested(args);
3889
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
3890
- const auditorForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : auditor;
3891
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
4030
+ const seat = synthesisSeat({ moderator: auditor, pinned: false, superRequested: false, upgradeRequested });
4031
+ const upgraded = seat.upgraded;
4032
+ const auditorForCall = seat.provider;
4033
+ const coProvider = pickCoReasoner(
4034
+ opts.providers,
4035
+ upgradeRequested,
4036
+ auditorForCall.name === "openai" ? auditorForCall.model : void 0
4037
+ );
3892
4038
  const coReason = await coReasonThenInject({
3893
4039
  provider: coProvider,
3894
4040
  messages: msgs,
@@ -3971,15 +4117,16 @@ ${rubricText}`;
3971
4117
  if (upgraded) {
3972
4118
  result["reasoning_upgrade"] = {
3973
4119
  applied: true,
3974
- model: UPGRADE_MODEL,
3975
- label: UPGRADE_LABEL,
4120
+ provider: auditorForCall.name,
4121
+ model: auditorForCall.model,
4122
+ label: seat.label ?? auditorForCall.model,
3976
4123
  seat: "auditor",
3977
4124
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
3978
4125
  };
3979
4126
  } else if (upgradeRequested) {
3980
4127
  result["reasoning_upgrade"] = {
3981
4128
  applied: false,
3982
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
4129
+ reason: seat.skipReason ?? "the auditor couldn't be upgraded."
3983
4130
  };
3984
4131
  } else {
3985
4132
  const assessment = assessReasoning(
@@ -4062,7 +4209,7 @@ function pickAuditor(providers, exclude, explicit, moderatorName, allowlist, che
4062
4209
  }
4063
4210
  }
4064
4211
  }
4065
- const mod = providers[moderatorName.toLowerCase()];
4212
+ const mod = resolveModerator(providers, moderatorName).provider;
4066
4213
  if (mod !== void 0 && !exclude.has(mod.name) && (allowlist === null || allowlist.includes(mod.name))) {
4067
4214
  return { auditor: mod, reason: null };
4068
4215
  }
@@ -4381,9 +4528,7 @@ function pickPanelJudge(providers, moderatorName, pricing) {
4381
4528
  return entry.model && entry.model !== base.model ? retargetProvider(base, entry.model) : base;
4382
4529
  }
4383
4530
  }
4384
- const mod = providers[moderatorName.toLowerCase()];
4385
- if (mod !== void 0) return mod;
4386
- return null;
4531
+ return resolveModerator(providers, moderatorName).provider ?? null;
4387
4532
  }
4388
4533
  var AGREEMENT_CHECK_SCHEMA = {
4389
4534
  type: "object",
@@ -4700,80 +4845,6 @@ async function pickAutoPanel(storage, purpose, n, providers, allowlist) {
4700
4845
  return { providers: ranked.slice(0, Math.max(1, n)), coldStart: true };
4701
4846
  }
4702
4847
 
4703
- // src/core/super-mode.ts
4704
- var import_node_async_hooks = require("async_hooks");
4705
- var SUPER_MODELS = {
4706
- // Fable 5.1 verified present on the account; 5 as the fallback for accounts
4707
- // that haven't been granted 5.1 yet.
4708
- anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
4709
- // Astra is live (verified against the account's model list 2026-09-23).
4710
- // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
4711
- // that constant means "OpenAI's top reasoning model" for the max-reasoning
4712
- // co-reasoner, which is a different question from "what should super fall
4713
- // back to". Sharing the symbol silently coupled two unrelated decisions.
4714
- openai: ["gpt-6-astra", "gpt-6-sol"],
4715
- // Explicitly left as-is per product decision. Note grok-4-latest is not in
4716
- // xAI's published model list, but it resolves in practice — real panel calls
4717
- // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
4718
- // rather than a dead id.
4719
- xai: [DEFAULT_MODELS["xai"]],
4720
- // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
4721
- // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
4722
- // repeat the same model and retry one that just failed. Named explicitly.
4723
- gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
4724
- kimi: [DEFAULT_MODELS["kimi"]],
4725
- qwen: [DEFAULT_MODELS["qwen"]]
4726
- };
4727
- var SUPER_ENV_VARS = {
4728
- anthropic: "ANTHROPIC_SUPER_MODEL",
4729
- openai: "OPENAI_SUPER_MODEL",
4730
- xai: "XAI_SUPER_MODEL",
4731
- gemini: "GEMINI_SUPER_MODEL",
4732
- kimi: "KIMI_SUPER_MODEL",
4733
- qwen: "QWEN_SUPER_MODEL",
4734
- mistral: "MISTRAL_SUPER_MODEL",
4735
- groq: "GROQ_SUPER_MODEL",
4736
- deepseek: "DEEPSEEK_SUPER_MODEL"
4737
- };
4738
- function parseChain(v) {
4739
- if (!v) return [];
4740
- const out = [];
4741
- for (const raw of v.split(",")) {
4742
- const m = raw.trim();
4743
- if (m && !out.includes(m)) out.push(m);
4744
- }
4745
- return out;
4746
- }
4747
- var superEnvStore = new import_node_async_hooks.AsyncLocalStorage();
4748
- var currentEnv = () => superEnvStore.getStore() ?? process.env;
4749
- function superChainFor(provider, env = currentEnv()) {
4750
- const varName = SUPER_ENV_VARS[provider];
4751
- const user = varName ? parseChain(env[varName]) : [];
4752
- if (user.length > 0) {
4753
- const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
4754
- return [...user, ...fallbacks.filter((m) => !user.includes(m))];
4755
- }
4756
- return SUPER_MODELS[provider] ?? [];
4757
- }
4758
- function superPrimary(provider, env = currentEnv()) {
4759
- return superChainFor(provider, env)[0];
4760
- }
4761
- var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
4762
- function isSuperRequested(args) {
4763
- return args["super"] === true;
4764
- }
4765
- function retargetForSuper(p, env = currentEnv()) {
4766
- const chain = superChainFor(p.name, env);
4767
- if (chain.length === 0) return p;
4768
- const [primary, ...fallbacks] = chain;
4769
- return retargetProvider(p, primary, fallbacks);
4770
- }
4771
- var SUPER_BUDGET_MULTIPLIER = 3;
4772
- var SUPER_BUDGET_CEILING = 16e3;
4773
- function superBudget(base) {
4774
- return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
4775
- }
4776
-
4777
4848
  // src/core/session-memory.ts
4778
4849
  var SESSION_MEMORY_DEFAULT_LIMIT = 50;
4779
4850
  var SESSION_MEMORY_INJECT_BUDGET_CHARS = 4e3;
@@ -7124,8 +7195,9 @@ async function runOrchestrate(args, opts) {
7124
7195
  blocked
7125
7196
  };
7126
7197
  }
7127
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
7128
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
7198
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
7199
+ const moderator = resolvedModerator.provider ?? selected[0];
7200
+ const moderatorChosen = resolvedModerator.provider !== void 0 && (resolvedModerator.pinned || parseModeratorSpec(args["moderator"]) !== null);
7129
7201
  const failFast = boolArg2(args["fail_fast"], false);
7130
7202
  const planOnly = boolArg2(args["plan_only"], false);
7131
7203
  const cheapMode = boolArg2(args["cheap_mode"], opts.ctx?.cheap_mode ?? false);
@@ -7609,11 +7681,17 @@ ${recombineSys}` : recombineSys },
7609
7681
  let synthProvider = moderator;
7610
7682
  let synthModel = null;
7611
7683
  const upgradeRequested = isUpgradeRequested(args);
7612
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
7684
+ const seat = synthesisSeat({
7685
+ moderator,
7686
+ pinned: resolvedModerator.provider !== void 0 && resolvedModerator.pinned,
7687
+ superRequested: false,
7688
+ upgradeRequested
7689
+ });
7690
+ const upgraded = seat.upgraded;
7613
7691
  if (upgraded) {
7614
- synthProvider = retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL);
7615
- synthModel = `anthropic/${UPGRADE_MODEL}`;
7616
- } else if (cheapMode && opts.pricing) {
7692
+ synthProvider = seat.provider;
7693
+ synthModel = `${seat.provider.name}/${seat.provider.model}`;
7694
+ } else if (cheapMode && opts.pricing && !moderatorChosen) {
7617
7695
  const pick = selectForDifficulty({
7618
7696
  pricing: opts.pricing,
7619
7697
  tier: "med",
@@ -7639,7 +7717,11 @@ ${recombineSys}` : recombineSys },
7639
7717
  provider: synthProvider.name
7640
7718
  } }
7641
7719
  );
7642
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
7720
+ const coProvider = pickCoReasoner(
7721
+ opts.providers,
7722
+ upgradeRequested,
7723
+ synthProvider.name === "openai" ? synthProvider.model : void 0
7724
+ );
7643
7725
  const coReason = await coReasonThenInject({
7644
7726
  provider: coProvider,
7645
7727
  messages: recMsgs,
@@ -7689,7 +7771,8 @@ ${recombineSys}` : recombineSys },
7689
7771
  missing,
7690
7772
  partial,
7691
7773
  fail_fast: failFast,
7692
- cheap_mode: cheapMode
7774
+ cheap_mode: cheapMode,
7775
+ moderator: { provider: moderator.name, model: moderator.model }
7693
7776
  };
7694
7777
  if (personaMeta.used !== null) result["persona"] = personaMeta;
7695
7778
  if (synthModel) result["synth_model"] = synthModel;
@@ -7697,15 +7780,16 @@ ${recombineSys}` : recombineSys },
7697
7780
  if (upgraded) {
7698
7781
  result["reasoning_upgrade"] = {
7699
7782
  applied: true,
7700
- model: UPGRADE_MODEL,
7701
- label: UPGRADE_LABEL,
7783
+ provider: seat.provider.name,
7784
+ model: seat.provider.model,
7785
+ label: seat.label ?? seat.provider.model,
7702
7786
  seat: "recombine/synthesis",
7703
7787
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
7704
7788
  };
7705
7789
  } else if (upgradeRequested) {
7706
7790
  result["reasoning_upgrade"] = {
7707
7791
  applied: false,
7708
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
7792
+ reason: seat.skipReason ?? "the moderator couldn't be upgraded."
7709
7793
  };
7710
7794
  } else if (goal) {
7711
7795
  const assessment = assessReasoning(goal);
@@ -8887,13 +8971,24 @@ async function runCoordinate(args, opts) {
8887
8971
  };
8888
8972
  }
8889
8973
  const proposerName = typeof args["proposer"] === "string" && args["proposer"] ? args["proposer"] : selected[0].name;
8890
- const synthName = typeof args["synthesizer"] === "string" && args["synthesizer"] ? args["synthesizer"] : typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
8974
+ const resolvedSynth = resolveModerator(
8975
+ opts.providers,
8976
+ args["synthesizer"],
8977
+ args["moderator"],
8978
+ opts.moderator
8979
+ );
8891
8980
  const proposer = opts.providers[proposerName.toLowerCase()] ?? selected[0];
8892
- const synth = opts.providers[synthName.toLowerCase()] ?? proposer;
8981
+ const synth = resolvedSynth.provider ?? proposer;
8893
8982
  const proposerForCall = superRequested ? retargetForSuper(proposer) : proposer;
8894
8983
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
8895
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
8896
- const synthForCall = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : synth;
8984
+ const seat = synthesisSeat({
8985
+ moderator: synth,
8986
+ pinned: resolvedSynth.provider !== void 0 && resolvedSynth.pinned,
8987
+ superRequested,
8988
+ upgradeRequested
8989
+ });
8990
+ const upgraded = seat.upgraded;
8991
+ const synthForCall = seat.provider;
8897
8992
  let critics = [];
8898
8993
  if (Array.isArray(args["critics"])) {
8899
8994
  for (const n of args["critics"]) {
@@ -9044,7 +9139,11 @@ CRITIQUES:
9044
9139
  ${critiqueBlock}`
9045
9140
  }
9046
9141
  ];
9047
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
9142
+ const coProvider = pickCoReasoner(
9143
+ opts.providers,
9144
+ upgradeRequested,
9145
+ synthForCall.name === "openai" ? synthForCall.model : void 0
9146
+ );
9048
9147
  const coReason = await coReasonThenInject({
9049
9148
  provider: coProvider,
9050
9149
  messages: synthMessages,
@@ -9089,7 +9188,8 @@ ${critiqueBlock}`
9089
9188
  critique_answers: scannedCritiques,
9090
9189
  critique_structured: critiqueStructured,
9091
9190
  synthesis_answer: synthAns,
9092
- synthesis_structured: synthObj
9191
+ synthesis_structured: synthObj,
9192
+ moderator: { provider: synthForCall.name, model: synthForCall.model }
9093
9193
  };
9094
9194
  if (synthErrs.length > 0) result["synthesis_errors"] = synthErrs;
9095
9195
  if (canaryLeaks.length > 0) result["canary_leaks"] = canaryLeaks;
@@ -9107,15 +9207,16 @@ ${critiqueBlock}`
9107
9207
  result["reasoning_upgrade"] = {
9108
9208
  applied: true,
9109
9209
  // The model that actually ran the seat, not the one we'd have picked.
9110
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
9111
- label: UPGRADE_LABEL,
9210
+ provider: synthForCall.name,
9211
+ model: synthForCall.model,
9212
+ label: seat.label ?? synthForCall.model,
9112
9213
  seat: "synthesizer",
9113
9214
  ...coReasonAns ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
9114
9215
  };
9115
9216
  } else if (upgradeRequested) {
9116
9217
  result["reasoning_upgrade"] = {
9117
9218
  applied: false,
9118
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
9219
+ reason: seat.skipReason ?? "the synthesizer couldn't be upgraded."
9119
9220
  };
9120
9221
  } else {
9121
9222
  const assessment = assessReasoning(topic);
@@ -9721,10 +9822,12 @@ async function runDebate(args, opts) {
9721
9822
  const earlyStopThreshold = Number.isFinite(earlyStopThresholdRaw) ? earlyStopThresholdRaw : 0.7;
9722
9823
  let earlyStoppedRound = null;
9723
9824
  let agreementBlock = null;
9724
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
9725
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
9825
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
9826
+ const moderatorName = resolvedModerator.spec.provider;
9827
+ const moderator = resolvedModerator.provider ?? selected[0];
9726
9828
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
9727
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9829
+ const seat = moderator ? synthesisSeat({ moderator, pinned: resolvedModerator.pinned, superRequested, upgradeRequested }) : null;
9830
+ const upgraded = seat?.upgraded ?? false;
9728
9831
  for (let rnd = 1; rnd <= maxRounds; rnd++) {
9729
9832
  const roundMessages = [
9730
9833
  {
@@ -9809,8 +9912,10 @@ ${prior.body}` });
9809
9912
  let personaMeta = null;
9810
9913
  let coReasoned = false;
9811
9914
  let coReasonerModel = null;
9915
+ let moderatorUsed = null;
9812
9916
  if (moderator) {
9813
- const synthProvider = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : moderator;
9917
+ const synthProvider = seat?.provider ?? moderator;
9918
+ moderatorUsed = { provider: synthProvider.name, model: synthProvider.model };
9814
9919
  const condensed = buildModeratorTranscript(transcript);
9815
9920
  const persona = buildPersonaInjection({
9816
9921
  toolName: "debate",
@@ -9836,7 +9941,7 @@ TRANSCRIPT:
9836
9941
  ${condensed}`
9837
9942
  }
9838
9943
  ];
9839
- const openAiSeat = selected.find((p) => p.name === "openai")?.model;
9944
+ const openAiSeat = synthProvider.name === "openai" ? synthProvider.model : selected.find((p) => p.name === "openai")?.model;
9840
9945
  const coProvider = pickCoReasoner(opts.providers, upgradeRequested, openAiSeat);
9841
9946
  const coReason = await coReasonThenInject({
9842
9947
  provider: coProvider,
@@ -9896,20 +10001,22 @@ ${condensed}`
9896
10001
  transcript,
9897
10002
  synthesis
9898
10003
  };
10004
+ if (moderatorUsed) result["moderator"] = moderatorUsed;
9899
10005
  if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
9900
10006
  if (compactionMeta) result["round_compaction"] = compactionMeta;
9901
10007
  if (upgraded) {
9902
10008
  result["reasoning_upgrade"] = {
9903
10009
  applied: true,
9904
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
9905
- label: UPGRADE_LABEL,
10010
+ provider: seat.provider.name,
10011
+ model: seat.provider.model,
10012
+ label: seat.label ?? seat.provider.model,
9906
10013
  seat: "moderator synthesis",
9907
10014
  ...coReasoned && coReasonerModel ? { co_reasoner: { model: coReasonerModel, label: coReasonLabel(coReasonerModel) } } : {}
9908
10015
  };
9909
10016
  } else if (upgradeRequested) {
9910
10017
  result["reasoning_upgrade"] = {
9911
10018
  applied: false,
9912
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10019
+ reason: seat?.skipReason ?? "no moderator was available for the upgrade."
9913
10020
  };
9914
10021
  } else {
9915
10022
  const assessment = assessReasoning(topic);
@@ -10538,12 +10645,25 @@ function runListProviders(args, opts) {
10538
10645
  }
10539
10646
  const result = {
10540
10647
  providers,
10541
- moderator_default: opts.moderatorDefault ?? "anthropic",
10648
+ moderator_default: opts.moderatorDefault ?? DEFAULT_MODERATOR,
10649
+ // Who will actually moderate right now: the configured spec if that
10650
+ // provider has a key, else the first available (what the tools fall to).
10651
+ moderator: describeModerator(opts.providers ?? {}, opts.moderatorDefault),
10542
10652
  usage_hint: USAGE_HINT
10543
10653
  };
10544
10654
  if (superRequested) result["super_mode"] = true;
10545
10655
  return result;
10546
10656
  }
10657
+ function describeModerator(providers, spec) {
10658
+ const r = resolveModerator(providers, spec);
10659
+ const p = r.provider ?? Object.values(providers)[0];
10660
+ return {
10661
+ spec: spec ?? DEFAULT_MODERATOR,
10662
+ provider: p?.name ?? null,
10663
+ model: p?.model ?? null,
10664
+ available: r.provider !== void 0
10665
+ };
10666
+ }
10547
10667
 
10548
10668
  // src/tools/models.ts
10549
10669
  var KNOWN_PROVIDERS7 = [
@@ -12093,7 +12213,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12093
12213
  var DEFAULT_PACKAGE = "crosscheck-cli";
12094
12214
  var FETCH_TIMEOUT_MS = 3e3;
12095
12215
  function engineVersion() {
12096
- return true ? "0.2.30" : "0.0.0-dev";
12216
+ return true ? "0.2.31" : "0.0.0-dev";
12097
12217
  }
12098
12218
  function defaultUpdateCachePath() {
12099
12219
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path12.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
@@ -12538,7 +12658,7 @@ var HELP_CATALOG = [
12538
12658
  primaryArg: "topic",
12539
12659
  summary: "Bounded multi-round debate between models, ending in a synthesized recommendation.",
12540
12660
  example: "xc debate should we adopt a monorepo?",
12541
- 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.`
12661
+ 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.`
12542
12662
  },
12543
12663
  {
12544
12664
  name: "coordinate",
@@ -12781,6 +12901,10 @@ function renderHelpCommand(entry) {
12781
12901
  }
12782
12902
 
12783
12903
  // src/tools/index.ts
12904
+ var MODERATOR_PARAM = {
12905
+ type: "string",
12906
+ 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`).'
12907
+ };
12784
12908
  function defineTool(opts) {
12785
12909
  return {
12786
12910
  name: opts.name,
@@ -13015,7 +13139,7 @@ function createTool(o, toolName, cheapDefault) {
13015
13139
  instruction: { type: "string" },
13016
13140
  documents: { type: "array", items: { type: "string" } },
13017
13141
  providers: { type: "array", items: { type: "string" } },
13018
- moderator: { type: "string" },
13142
+ moderator: MODERATOR_PARAM,
13019
13143
  constraints: { type: "string" },
13020
13144
  target_path: { type: "string" },
13021
13145
  session_id: { type: "string" },
@@ -13056,7 +13180,7 @@ function orchestrateTool(providers, allowlist, moderator, storage, pricing, brea
13056
13180
  dag: { type: "object", additionalProperties: true },
13057
13181
  context: { type: "string" },
13058
13182
  providers: { type: "array", items: { type: "string" } },
13059
- moderator: { type: "string" },
13183
+ moderator: MODERATOR_PARAM,
13060
13184
  fail_fast: { type: "boolean" },
13061
13185
  plan_only: { type: "boolean" },
13062
13186
  cheap_mode: { type: "boolean" },
@@ -13435,7 +13559,7 @@ function planTool(providers, allowlist, storage, breakers, transcriptsDir, repoR
13435
13559
  constraints: { type: "string" },
13436
13560
  context: { type: "string" },
13437
13561
  providers: { type: "array", items: { type: "string" } },
13438
- moderator: { type: "string" },
13562
+ moderator: MODERATOR_PARAM,
13439
13563
  session_id: { type: "string" },
13440
13564
  structured: { type: "boolean" },
13441
13565
  mode: { type: "string", enum: ["fast", "thorough"] },
@@ -13497,7 +13621,7 @@ function coordinateTool(providers, allowlist, innerCallers, storage, breakers, t
13497
13621
  providers: { type: "array", items: { type: "string" } },
13498
13622
  proposer: { type: "string" },
13499
13623
  synthesizer: { type: "string" },
13500
- moderator: { type: "string" },
13624
+ moderator: MODERATOR_PARAM,
13501
13625
  critics: { type: "array", items: { type: "string" } },
13502
13626
  session_id: { type: "string" },
13503
13627
  untrusted_input: { type: "boolean" },
@@ -13530,7 +13654,7 @@ function debateTool(providers, allowlist, innerCallers, storage, pricing, breake
13530
13654
  context: { type: "string" },
13531
13655
  max_rounds: { type: "integer", minimum: 1 },
13532
13656
  providers: { type: "array", items: { type: "string" } },
13533
- moderator: { type: "string" },
13657
+ moderator: MODERATOR_PARAM,
13534
13658
  session_id: { type: "string" },
13535
13659
  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.' },
13536
13660
  structured: { type: "boolean" },
@@ -13747,6 +13871,7 @@ var KEY_ENV = {
13747
13871
  qwen: "QWEN_API_KEY"
13748
13872
  };
13749
13873
  function createCrosscheck(opts) {
13874
+ const moderator = opts.moderator ?? DEFAULT_MODERATOR;
13750
13875
  const env = {};
13751
13876
  for (const [provider, key] of Object.entries(opts.keys)) {
13752
13877
  const varName = KEY_ENV[provider.trim().toLowerCase()];
@@ -13754,7 +13879,7 @@ function createCrosscheck(opts) {
13754
13879
  }
13755
13880
  const pricingDoc = pricing_default;
13756
13881
  const providers = buildProviders({ env, pricing: pricingDoc });
13757
- const tools = registerCoreTools({ providers, pricing: pricingDoc });
13882
+ const tools = registerCoreTools({ providers, pricing: pricingDoc, moderatorDefault: moderator });
13758
13883
  return {
13759
13884
  listTools: () => [...tools.keys()],
13760
13885
  call: (name, args) => {
@@ -13767,11 +13892,11 @@ function createCrosscheck(opts) {
13767
13892
  // same opts the registry wires for confer in a browser build: providers
13768
13893
  // + pricing + default moderator; storage/innerCallers aren't
13769
13894
  // available here, matching the non-streaming path.
13770
- conferStream: (args, onAnswer) => runConfer(args, { providers, pricing: pricingDoc, moderator: "anthropic", onAnswer }),
13895
+ conferStream: (args, onAnswer) => runConfer(args, { providers, pricing: pricingDoc, moderator, onAnswer }),
13771
13896
  debateStream: (args, { onTurn, onSynthesis } = {}) => runDebate(args, {
13772
13897
  providers,
13773
13898
  pricing: pricingDoc,
13774
- moderator: "anthropic",
13899
+ moderator,
13775
13900
  ...onTurn ? { onTurn: (entry, round, provider) => onTurn(entry, round, provider) } : {},
13776
13901
  ...onSynthesis ? { onSynthesis: (s) => onSynthesis(s) } : {}
13777
13902
  })