@antonbabenko/deliberation-mcp 0.1.0

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.
Files changed (2) hide show
  1. package/dist/index.js +3106 -0
  2. package/package.json +15 -0
package/dist/index.js ADDED
@@ -0,0 +1,3106 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __commonJS = (cb, mod) => function __require() {
5
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
6
+ };
7
+
8
+ // ../../core/registry.js
9
+ var require_registry = __commonJS({
10
+ "../../core/registry.js"(exports2, module2) {
11
+ "use strict";
12
+ function eligibleForExpert(model, expert) {
13
+ if (model.experts === null || model.experts === void 0) return true;
14
+ if (model.experts.length === 0) return false;
15
+ return model.experts.includes(expert);
16
+ }
17
+ function askAllDelegates(or, expert) {
18
+ const pool = (or.models || []).filter((m) => m.askAll !== false && eligibleForExpert(m, expert));
19
+ const cap = Number.isInteger(or.maxFanout) && /** @type {number} */
20
+ or.maxFanout >= 1 ? (
21
+ /** @type {number} */
22
+ or.maxFanout
23
+ ) : 3;
24
+ return { selected: pool.slice(0, cap), omitted: pool.slice(cap) };
25
+ }
26
+ function consensusDelegates(or, expert) {
27
+ return (or.models || []).filter((m) => m.consensus === true && eligibleForExpert(m, expert));
28
+ }
29
+ var BUILTINS = ["codex", "gemini", "grok"];
30
+ function pinAlias(orProvider, delegate) {
31
+ return {
32
+ name: `openrouter:${delegate.alias}`,
33
+ capabilities: orProvider.capabilities,
34
+ health: orProvider.health.bind(orProvider),
35
+ async ask(req) {
36
+ const r = await orProvider.ask({ ...req, model: delegate.model });
37
+ return { ...r, provider: `openrouter:${delegate.alias}` };
38
+ }
39
+ };
40
+ }
41
+ function makeRegistry2(providers) {
42
+ const byName = new Map(providers.map((p) => [p.name, p]));
43
+ const enabled = (config, name) => {
44
+ const p = config && config.providers && config.providers[name];
45
+ return !p || p.enabled !== false;
46
+ };
47
+ const builtinsFor = (config) => BUILTINS.filter((n) => byName.has(n) && enabled(config, n)).map((n) => (
48
+ /** @type {Provider} */
49
+ byName.get(n)
50
+ ));
51
+ const pinDelegates = (delegates) => {
52
+ const orProvider = byName.get("openrouter");
53
+ return orProvider ? delegates.map((d) => pinAlias(orProvider, d)) : [];
54
+ };
55
+ return {
56
+ /** @param {string} n */
57
+ get: (n) => byName.get(n),
58
+ // Flat provider list ready for askAll(): built-ins + per-alias OR wrappers.
59
+ /** @param {{config: RegistryConfig, expert: string}} args */
60
+ selectForAskAll({ config, expert }) {
61
+ const or = config && config.openrouter || {};
62
+ const { selected, omitted } = askAllDelegates(or, expert);
63
+ return { providers: [...builtinsFor(config), ...pinDelegates(selected)], omitted };
64
+ },
65
+ // Uncapped: built-ins + per-alias OR consensus delegates.
66
+ /** @param {{config: RegistryConfig, expert: string}} args */
67
+ selectForConsensus({ config, expert }) {
68
+ const or = config && config.openrouter || {};
69
+ return { providers: [...builtinsFor(config), ...pinDelegates(consensusDelegates(or, expert))] };
70
+ }
71
+ };
72
+ }
73
+ module2.exports = { makeRegistry: makeRegistry2, eligibleForExpert, askAllDelegates, consensusDelegates, pinAlias };
74
+ }
75
+ });
76
+
77
+ // ../../core/orchestrate.js
78
+ var require_orchestrate = __commonJS({
79
+ "../../core/orchestrate.js"(exports2, module2) {
80
+ "use strict";
81
+ async function askAll2(providers, req) {
82
+ const settled = await Promise.allSettled(
83
+ providers.map(
84
+ (p) => p.ask({ ...req, files: req.files ? req.files.map((f) => ({ ...f })) : void 0 })
85
+ )
86
+ );
87
+ return settled.map(
88
+ (s, i) => s.status === "fulfilled" ? s.value : {
89
+ provider: providers[i].name,
90
+ model: "unknown",
91
+ isError: true,
92
+ errorKind: "unknown",
93
+ retryable: false,
94
+ message: String(s.reason && s.reason.message || s.reason || "rejected"),
95
+ ms: 0
96
+ }
97
+ );
98
+ }
99
+ async function askOne2(provider, req) {
100
+ return provider.ask({ ...req, files: req.files ? req.files.map((f) => ({ ...f })) : void 0 });
101
+ }
102
+ function buildArbiterPrompt(question, opinions) {
103
+ const blocks = opinions.map((o, i) => `### Opinion ${i + 1} - ${o.provider}
104
+ ${o.text}`).join("\n\n");
105
+ return [
106
+ "You are the arbiter. Below are independent expert opinions on the same question.",
107
+ "Cross-review them: note where they agree, where they disagree, and which view is best supported.",
108
+ "Then produce ONE synthesized verdict.",
109
+ "",
110
+ `## Original question
111
+ ${question}`,
112
+ "",
113
+ `## Opinions
114
+ ${blocks}`,
115
+ "",
116
+ "## Your verdict\nBottom line, points of agreement, points of disagreement, final recommendation."
117
+ ].join("\n");
118
+ }
119
+ async function consensus2(providers, req, opts = {}) {
120
+ const opinions = await askAll2(providers, req);
121
+ const ok = (
122
+ /** @type {DelegationSuccess[]} */
123
+ opinions.filter((o) => !o.isError)
124
+ );
125
+ if (!ok.length) return { opinions, verdict: null, error: "all-providers-failed" };
126
+ const arbiter = opts.arbiter || providers[0];
127
+ if (!arbiter) return { opinions, verdict: null, error: "no-arbiter" };
128
+ try {
129
+ const verdict = await arbiter.ask({
130
+ ...req,
131
+ files: req.files ? req.files.map((f) => ({ ...f })) : void 0,
132
+ prompt: buildArbiterPrompt(req.prompt, ok),
133
+ developerInstructions: opts.arbiterInstructions || req.developerInstructions
134
+ });
135
+ return { opinions, verdict };
136
+ } catch {
137
+ return { opinions, verdict: null, error: "arbiter-failed" };
138
+ }
139
+ }
140
+ module2.exports = { askAll: askAll2, askOne: askOne2, consensus: consensus2, buildArbiterPrompt };
141
+ }
142
+ });
143
+
144
+ // ../../core/provider.js
145
+ var require_provider = __commonJS({
146
+ "../../core/provider.js"(exports2, module2) {
147
+ "use strict";
148
+ function toErrorResult(name, model, started, err, classify) {
149
+ const { errorKind, retryable } = classify(err && err.status, err && err.code);
150
+ return { provider: name, model, isError: true, errorKind, retryable, ms: Date.now() - started };
151
+ }
152
+ var OPINION_SCHEMA = Object.freeze({
153
+ type: "object",
154
+ required: ["recommendation", "confidence"],
155
+ properties: {
156
+ recommendation: { type: "string" },
157
+ confidence: { type: "string", enum: ["low", "medium", "high"] },
158
+ reasoning: { type: "string" }
159
+ }
160
+ });
161
+ function validateOpinion(o) {
162
+ if (!o || typeof o !== "object") return { ok: false, reason: "opinion is not an object" };
163
+ for (const k of OPINION_SCHEMA.required) {
164
+ if (!(k in o)) return { ok: false, reason: `missing required field: ${k}` };
165
+ }
166
+ return { ok: true };
167
+ }
168
+ module2.exports = { toErrorResult, OPINION_SCHEMA, validateOpinion };
169
+ }
170
+ });
171
+
172
+ // ../openrouter/config.js
173
+ var require_config = __commonJS({
174
+ "../openrouter/config.js"(exports2, module2) {
175
+ "use strict";
176
+ var fs = require("node:fs");
177
+ var EXPERT_KEYS = /* @__PURE__ */ new Set([
178
+ "architect",
179
+ "plan-reviewer",
180
+ "scope-analyst",
181
+ "code-reviewer",
182
+ "security-analyst",
183
+ "researcher",
184
+ "debugger"
185
+ ]);
186
+ var RESERVED_ALIAS = "openrouter-default";
187
+ var ALIAS_RE = /^[a-z0-9-]+$/;
188
+ var SUPPORTED_MAJOR = 1;
189
+ var DEFAULT_API_BASE = "https://openrouter.ai/api/v1";
190
+ var DEFAULT_API_KEY_ENV = "OPENROUTER_API_KEY";
191
+ var DEFAULT_MAX_FANOUT = 3;
192
+ function isObject(v) {
193
+ return v !== null && typeof v === "object" && !Array.isArray(v);
194
+ }
195
+ function sanitizeAlias(raw) {
196
+ if (typeof raw !== "string") return "";
197
+ return raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
198
+ }
199
+ function validateConfig(raw) {
200
+ if (!isObject(raw)) return fail("config root must be a JSON object");
201
+ const version = raw.version === void 0 ? 1 : raw.version;
202
+ if (!Number.isInteger(version) || version < 1 || version > SUPPORTED_MAJOR) {
203
+ return fail(`unsupported config version ${version}; this build supports version <= ${SUPPORTED_MAJOR}`);
204
+ }
205
+ const providers = isObject(raw.providers) ? raw.providers : {};
206
+ const orRaw = isObject(raw.openrouter) ? raw.openrouter : null;
207
+ if (!orRaw) {
208
+ return { ok: true, resolved: { version, providers, openrouter: disabledOpenRouter() }, error: null };
209
+ }
210
+ const enabled = orRaw.enabled !== false;
211
+ const apiKeyEnv = orRaw.apiKeyEnv || DEFAULT_API_KEY_ENV;
212
+ const apiBase = orRaw.apiBase || DEFAULT_API_BASE;
213
+ const allowRawModel = orRaw.allowRawModel === true;
214
+ const maxFanout = orRaw.maxFanout === void 0 ? DEFAULT_MAX_FANOUT : orRaw.maxFanout;
215
+ if (!Number.isInteger(maxFanout) || maxFanout < 1) {
216
+ return fail(`maxFanout must be an integer >= 1 (got ${String(maxFanout)})`);
217
+ }
218
+ const defaultModel = typeof orRaw.defaultModel === "string" && orRaw.defaultModel.trim() ? orRaw.defaultModel.trim() : null;
219
+ const defaults = isObject(orRaw.defaults) ? orRaw.defaults : {};
220
+ const modelsRaw = Array.isArray(orRaw.models) ? orRaw.models : [];
221
+ const models = [];
222
+ const invalidModels = [];
223
+ const seen = /* @__PURE__ */ new Set();
224
+ const taken = /* @__PURE__ */ new Set([RESERVED_ALIAS]);
225
+ for (const m of modelsRaw) {
226
+ if (isObject(m) && typeof m.alias === "string") taken.add(m.alias);
227
+ }
228
+ function suggestFree(candidate) {
229
+ if (!candidate || candidate === RESERVED_ALIAS) return void 0;
230
+ let chosen = candidate;
231
+ if (taken.has(chosen)) {
232
+ chosen = void 0;
233
+ for (let n = 2; n <= 99; n++) {
234
+ if (!taken.has(`${candidate}-${n}`)) {
235
+ chosen = `${candidate}-${n}`;
236
+ break;
237
+ }
238
+ }
239
+ if (!chosen) return void 0;
240
+ }
241
+ taken.add(chosen);
242
+ return chosen;
243
+ }
244
+ function addInvalid(i, alias, reason, suggestedAlias) {
245
+ const entry = { index: i, alias: alias === void 0 ? null : alias, reason };
246
+ if (suggestedAlias) entry.suggestedAlias = suggestedAlias;
247
+ invalidModels.push(entry);
248
+ }
249
+ for (let i = 0; i < modelsRaw.length; i++) {
250
+ const m = modelsRaw[i];
251
+ if (!isObject(m)) {
252
+ addInvalid(i, null, `models[${i}] must be an object`);
253
+ continue;
254
+ }
255
+ const rawAlias = typeof m.alias === "string" ? m.alias : null;
256
+ if (typeof m.alias !== "string" || !ALIAS_RE.test(m.alias)) {
257
+ const sanitized = sanitizeAlias(m.alias);
258
+ addInvalid(
259
+ i,
260
+ rawAlias,
261
+ `models[${i}] alias must match [a-z0-9-]+ (got ${JSON.stringify(m.alias)})`,
262
+ sanitized ? suggestFree(sanitized) : void 0
263
+ );
264
+ continue;
265
+ }
266
+ if (m.alias === RESERVED_ALIAS) {
267
+ addInvalid(i, m.alias, `alias "${RESERVED_ALIAS}" is reserved`);
268
+ continue;
269
+ }
270
+ if (seen.has(m.alias)) {
271
+ addInvalid(i, m.alias, `duplicate alias "${m.alias}"`, suggestFree(m.alias));
272
+ continue;
273
+ }
274
+ if (typeof m.model !== "string" || !m.model.trim()) {
275
+ addInvalid(i, m.alias, `models[${i}] (${m.alias}) needs a non-empty model slug`);
276
+ continue;
277
+ }
278
+ let experts = null;
279
+ if (m.experts !== void 0) {
280
+ if (!Array.isArray(m.experts)) {
281
+ addInvalid(i, m.alias, `models[${i}] (${m.alias}) experts must be an array`);
282
+ continue;
283
+ }
284
+ let badExpert = null;
285
+ for (const e of m.experts) {
286
+ if (!EXPERT_KEYS.has(e)) {
287
+ badExpert = e;
288
+ break;
289
+ }
290
+ }
291
+ if (badExpert !== null) {
292
+ addInvalid(i, m.alias, `models[${i}] (${m.alias}) unknown expert "${badExpert}"`);
293
+ continue;
294
+ }
295
+ experts = m.experts.slice();
296
+ }
297
+ if (m.reasoning_effort !== void 0 && typeof m.reasoning_effort !== "string") {
298
+ addInvalid(i, m.alias, `models[${i}] (${m.alias}) reasoning_effort must be a string`);
299
+ continue;
300
+ }
301
+ if (m.timeout !== void 0 && !(Number.isInteger(m.timeout) && m.timeout > 0)) {
302
+ addInvalid(i, m.alias, `models[${i}] (${m.alias}) timeout must be a positive integer`);
303
+ continue;
304
+ }
305
+ if (m.temperature !== void 0 && !(typeof m.temperature === "number" && Number.isFinite(m.temperature))) {
306
+ addInvalid(i, m.alias, `models[${i}] (${m.alias}) temperature must be a finite number`);
307
+ continue;
308
+ }
309
+ if (m.apiBase !== void 0 && !(typeof m.apiBase === "string" && m.apiBase.trim())) {
310
+ addInvalid(i, m.alias, `models[${i}] (${m.alias}) apiBase must be a non-empty string`);
311
+ continue;
312
+ }
313
+ seen.add(m.alias);
314
+ models.push({
315
+ alias: m.alias,
316
+ model: m.model.trim(),
317
+ experts,
318
+ askAll: m.askAll !== false,
319
+ consensus: m.consensus === true,
320
+ reasoning_effort: m.reasoning_effort,
321
+ timeout: m.timeout,
322
+ temperature: m.temperature,
323
+ apiBase: m.apiBase
324
+ });
325
+ }
326
+ return {
327
+ ok: true,
328
+ error: null,
329
+ resolved: {
330
+ version,
331
+ providers,
332
+ openrouter: { enabled, apiKeyEnv, apiBase, allowRawModel, maxFanout, defaultModel, defaults, models, invalidModels }
333
+ }
334
+ };
335
+ }
336
+ function disabledOpenRouter() {
337
+ return {
338
+ enabled: false,
339
+ apiKeyEnv: DEFAULT_API_KEY_ENV,
340
+ apiBase: DEFAULT_API_BASE,
341
+ allowRawModel: false,
342
+ maxFanout: DEFAULT_MAX_FANOUT,
343
+ defaultModel: null,
344
+ defaults: {},
345
+ models: [],
346
+ invalidModels: []
347
+ };
348
+ }
349
+ function fail(message) {
350
+ return { ok: false, resolved: null, error: message };
351
+ }
352
+ function makeConfigReader(filePath) {
353
+ let cachedMtimeMs = null;
354
+ let cachedResult = null;
355
+ function read() {
356
+ let text;
357
+ try {
358
+ text = fs.readFileSync(filePath, "utf8");
359
+ } catch (_) {
360
+ return { ok: true, error: null, resolved: { version: 1, providers: {}, openrouter: disabledOpenRouter() } };
361
+ }
362
+ let parsed;
363
+ try {
364
+ parsed = JSON.parse(text);
365
+ } catch (e) {
366
+ return { ok: false, resolved: null, error: `config JSON parse error: ${e.message}` };
367
+ }
368
+ return validateConfig(parsed);
369
+ }
370
+ return {
371
+ get() {
372
+ let mtimeMs = null;
373
+ try {
374
+ mtimeMs = fs.statSync(filePath).mtimeMs;
375
+ } catch (_) {
376
+ mtimeMs = null;
377
+ }
378
+ if (cachedResult === null || mtimeMs !== cachedMtimeMs) {
379
+ cachedResult = read();
380
+ cachedMtimeMs = mtimeMs;
381
+ }
382
+ return cachedResult;
383
+ }
384
+ };
385
+ }
386
+ module2.exports = { validateConfig, makeConfigReader, EXPERT_KEYS, RESERVED_ALIAS, DEFAULT_API_BASE, DEFAULT_API_KEY_ENV };
387
+ }
388
+ });
389
+
390
+ // ../openrouter/routing.js
391
+ var require_routing = __commonJS({
392
+ "../openrouter/routing.js"(exports2, module2) {
393
+ "use strict";
394
+ var RESERVED_ALIAS = "openrouter-default";
395
+ function eligibleForExpert(model, expert) {
396
+ if (model.experts === null || model.experts === void 0) return true;
397
+ if (model.experts.length === 0) return false;
398
+ return model.experts.includes(expert);
399
+ }
400
+ function askAllDelegates(or, expert) {
401
+ const pool = (or.models || []).filter((m) => m.askAll !== false && eligibleForExpert(m, expert));
402
+ const cap = Number.isInteger(or.maxFanout) && or.maxFanout >= 1 ? or.maxFanout : 3;
403
+ return { selected: pool.slice(0, cap), omitted: pool.slice(cap) };
404
+ }
405
+ function consensusDelegates(or, expert) {
406
+ return (or.models || []).filter((m) => m.consensus === true && eligibleForExpert(m, expert));
407
+ }
408
+ function resolveAlias(or, alias) {
409
+ if (alias === RESERVED_ALIAS) {
410
+ return or.defaultModel ? { alias: RESERVED_ALIAS, model: or.defaultModel, experts: null } : null;
411
+ }
412
+ return (or.models || []).find((m) => m.alias === alias) || null;
413
+ }
414
+ module2.exports = { eligibleForExpert, askAllDelegates, consensusDelegates, resolveAlias, RESERVED_ALIAS };
415
+ }
416
+ });
417
+
418
+ // ../grok/glob.js
419
+ var require_glob = __commonJS({
420
+ "../grok/glob.js"(exports2, module2) {
421
+ "use strict";
422
+ function rejectBackslashes(pattern) {
423
+ if (typeof pattern === "string" && pattern.includes("\\")) {
424
+ throw new Error(`glob pattern "${pattern}" contains backslashes; v1 patterns are POSIX-only (use /)`);
425
+ }
426
+ }
427
+ function patternToRegex(pattern) {
428
+ let i = 0;
429
+ let out = "";
430
+ while (i < pattern.length) {
431
+ const ch = pattern[i];
432
+ if (ch === "*") {
433
+ if (pattern[i + 1] === "*") {
434
+ if (pattern[i + 2] === "/") {
435
+ out += "(?:.*/)?";
436
+ i += 3;
437
+ } else {
438
+ out += ".*";
439
+ i += 2;
440
+ }
441
+ } else {
442
+ out += "[^/]*";
443
+ i += 1;
444
+ }
445
+ } else if (ch === "?") {
446
+ out += "[^/]";
447
+ i += 1;
448
+ } else if (ch === "[") {
449
+ const end = pattern.indexOf("]", i);
450
+ if (end === -1) {
451
+ out += "\\[";
452
+ i += 1;
453
+ } else {
454
+ out += pattern.slice(i, end + 1);
455
+ i = end + 1;
456
+ }
457
+ } else if ("\\^$.|+(){}".includes(ch)) {
458
+ out += "\\" + ch;
459
+ i += 1;
460
+ } else {
461
+ out += ch;
462
+ i += 1;
463
+ }
464
+ }
465
+ return out;
466
+ }
467
+ function compile(pattern) {
468
+ rejectBackslashes(pattern);
469
+ if (!pattern.includes("/")) {
470
+ const re = patternToRegex(pattern);
471
+ const hasWildcard = pattern.includes("*") || pattern.includes("?") || pattern.includes("[");
472
+ const regex = hasWildcard ? new RegExp(`^${re}$`) : new RegExp(`(?:^|/)${re}$`);
473
+ return { kind: "bare", re: regex };
474
+ }
475
+ return { kind: "path", re: new RegExp(`^${patternToRegex(pattern)}$`) };
476
+ }
477
+ function matchPattern(pattern, relPath) {
478
+ return compile(pattern).re.test(relPath);
479
+ }
480
+ function matchAny(patterns, relPath) {
481
+ for (const p of patterns) if (matchPattern(p, relPath)) return true;
482
+ return false;
483
+ }
484
+ var fs = require("node:fs");
485
+ var path = require("node:path");
486
+ function walk(rootAbs, { include, exclude, maxFiles, maxBytes }) {
487
+ const includeRes = include.map((p) => compile(p));
488
+ const excludeRes = exclude.map((p) => compile(p));
489
+ const files = [];
490
+ let totalBytes = 0;
491
+ function matches(res, rel) {
492
+ for (const c of res) if (c.re.test(rel)) return true;
493
+ return false;
494
+ }
495
+ function descend(absDir, relDir) {
496
+ let entries;
497
+ try {
498
+ entries = fs.readdirSync(absDir, { withFileTypes: true });
499
+ } catch (_) {
500
+ return;
501
+ }
502
+ entries.sort((a, b) => a.name.localeCompare(b.name, "en"));
503
+ for (const ent of entries) {
504
+ const absChild = path.join(absDir, ent.name);
505
+ const relChild = (relDir ? relDir + "/" : "") + ent.name;
506
+ const relPosix = relChild.replace(/\\/g, "/");
507
+ if (ent.isSymbolicLink()) {
508
+ let realTarget;
509
+ try {
510
+ realTarget = fs.realpathSync(absChild);
511
+ } catch (_) {
512
+ continue;
513
+ }
514
+ const relReal = path.relative(rootAbs, realTarget);
515
+ if (relReal.startsWith("..") || path.isAbsolute(relReal)) continue;
516
+ let st;
517
+ try {
518
+ st = fs.statSync(realTarget);
519
+ } catch (_) {
520
+ continue;
521
+ }
522
+ if (st.isDirectory()) continue;
523
+ if (!st.isFile()) continue;
524
+ if (matches(excludeRes, relPosix)) continue;
525
+ if (!matches(includeRes, relPosix)) continue;
526
+ files.push({ rel: relPosix, abs: realTarget, size: st.size });
527
+ totalBytes += st.size;
528
+ } else if (ent.isDirectory()) {
529
+ if (matches(excludeRes, relPosix) || matches(excludeRes, relPosix + "/**")) continue;
530
+ descend(absChild, relPosix);
531
+ } else if (ent.isFile()) {
532
+ if (matches(excludeRes, relPosix)) continue;
533
+ if (!matches(includeRes, relPosix)) continue;
534
+ let st;
535
+ try {
536
+ st = fs.statSync(absChild);
537
+ } catch (_) {
538
+ continue;
539
+ }
540
+ files.push({ rel: relPosix, abs: absChild, size: st.size });
541
+ totalBytes += st.size;
542
+ }
543
+ }
544
+ }
545
+ descend(rootAbs, "");
546
+ if (files.length > maxFiles) {
547
+ throw new Error(`directory expansion selected ${files.length} files; exceeds maxFiles=${maxFiles}. Narrow include or raise the limit.`);
548
+ }
549
+ if (totalBytes > maxBytes) {
550
+ throw new Error(`directory expansion selected ${totalBytes} bytes; exceeds maxBytes=${maxBytes}. Narrow include or raise the limit.`);
551
+ }
552
+ files.sort((a, b) => a.rel.localeCompare(b.rel, "en"));
553
+ return { files, totalBytes };
554
+ }
555
+ module2.exports = { matchPattern, matchAny, rejectBackslashes, compile, walk };
556
+ }
557
+ });
558
+
559
+ // ../openrouter/files.js
560
+ var require_files = __commonJS({
561
+ "../openrouter/files.js"(exports2, module2) {
562
+ "use strict";
563
+ var fs = require("node:fs");
564
+ var path = require("node:path");
565
+ var glob = require_glob();
566
+ var DEFAULT_PER_FILE_CAP = Number(process.env.OPENROUTER_INLINE_MAX_BYTES) > 0 ? Math.floor(Number(process.env.OPENROUTER_INLINE_MAX_BYTES)) : 256 * 1024;
567
+ var DEFAULT_TOTAL_CAP = Number(process.env.OPENROUTER_INLINE_MAX_TOTAL_BYTES) > 0 ? Math.floor(Number(process.env.OPENROUTER_INLINE_MAX_TOTAL_BYTES)) : 1024 * 1024;
568
+ var DEFAULT_EXCLUDE = [
569
+ ".git",
570
+ "node_modules",
571
+ "**/dist/**",
572
+ "**/build/**",
573
+ "**/.venv/**",
574
+ "**/venv/**",
575
+ "**/__pycache__/**",
576
+ "**/target/**",
577
+ "**/vendor/**",
578
+ "**/*.lock",
579
+ "**/*.tfstate*",
580
+ "**/.env",
581
+ "**/.env.local",
582
+ "**/.ssh/**",
583
+ "**/*.pem",
584
+ "**/*.key"
585
+ ];
586
+ function isProbablyText(buf) {
587
+ if (!buf || buf.length === 0) return true;
588
+ const slice = buf.subarray(0, Math.min(buf.length, 4096));
589
+ if (slice.includes(0)) return false;
590
+ let np = 0;
591
+ for (const b of slice) {
592
+ if (b === 9 || b === 10 || b === 13) continue;
593
+ if (b >= 32 && b <= 126) continue;
594
+ if (b >= 128) continue;
595
+ np++;
596
+ }
597
+ return np / slice.length < 0.05;
598
+ }
599
+ function resolveUnderRoots(p, roots) {
600
+ const isAbs = path.isAbsolute(p);
601
+ for (const root of roots) {
602
+ const abs = isAbs ? p : path.join(root, p);
603
+ try {
604
+ const realRoot = fs.realpathSync(root);
605
+ const realAbs = fs.realpathSync(abs);
606
+ const rel = path.relative(realRoot, realAbs);
607
+ if (rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel)) return realAbs;
608
+ } catch (_) {
609
+ }
610
+ }
611
+ return null;
612
+ }
613
+ function inlineFiles(files, opts = {}) {
614
+ const roots = opts.roots && opts.roots.length ? opts.roots : [process.cwd()];
615
+ const perFileCap = opts.perFileCap != null ? opts.perFileCap : DEFAULT_PER_FILE_CAP;
616
+ const totalCap = opts.totalCap != null ? opts.totalCap : DEFAULT_TOTAL_CAP;
617
+ const blocks = [];
618
+ const notes = [];
619
+ let total = 0;
620
+ function addFile(abs, label) {
621
+ let st;
622
+ try {
623
+ st = fs.statSync(abs);
624
+ } catch (e) {
625
+ notes.push(`${label}: skipped (stat error: ${e.message})`);
626
+ return;
627
+ }
628
+ if (st.size > perFileCap) {
629
+ notes.push(`${label}: skipped (${st.size} bytes > per-file cap ${perFileCap})`);
630
+ return;
631
+ }
632
+ let buf;
633
+ try {
634
+ buf = fs.readFileSync(abs);
635
+ } catch (e) {
636
+ notes.push(`${label}: skipped (read error: ${e.message})`);
637
+ return;
638
+ }
639
+ if (!isProbablyText(buf)) {
640
+ notes.push(`${label}: skipped (binary)`);
641
+ return;
642
+ }
643
+ if (total + buf.length > totalCap) {
644
+ notes.push(`${label}: omitted (aggregate inline budget ${totalCap} bytes exceeded)`);
645
+ return;
646
+ }
647
+ total += buf.length;
648
+ blocks.push(`=== ${label} ===
649
+ ${buf.toString("utf8")}`);
650
+ }
651
+ for (const entry of files || []) {
652
+ if (entry.file_id !== void 0 || entry.file_url !== void 0) {
653
+ throw new Error("file_id / file_url are not supported by the OpenRouter bridge (text-inline only)");
654
+ }
655
+ if (entry.path) {
656
+ const abs = resolveUnderRoots(entry.path, roots);
657
+ if (!abs) {
658
+ notes.push(`${entry.path}: skipped (not found under roots)`);
659
+ continue;
660
+ }
661
+ addFile(abs, path.basename(entry.path));
662
+ } else if (entry.dir) {
663
+ const absDir = resolveUnderRoots(entry.dir, roots);
664
+ if (!absDir) {
665
+ notes.push(`${entry.dir}: skipped (dir not found under roots)`);
666
+ continue;
667
+ }
668
+ const exclude = entry.excludeReset === true ? entry.exclude || [] : [...DEFAULT_EXCLUDE, ...entry.exclude || []];
669
+ let walked;
670
+ try {
671
+ ({ files: walked } = glob.walk(absDir, {
672
+ include: entry.include || ["**/*"],
673
+ exclude,
674
+ maxFiles: entry.maxFiles || 50,
675
+ maxBytes: entry.maxBytes || 128 * 1024 * 1024
676
+ }));
677
+ } catch (e) {
678
+ notes.push(`${entry.dir}: skipped (${e.message})`);
679
+ continue;
680
+ }
681
+ for (const w of walked) addFile(w.abs, path.relative(absDir, w.abs) || path.basename(w.abs));
682
+ }
683
+ }
684
+ return { blocks, notes };
685
+ }
686
+ module2.exports = { inlineFiles, DEFAULT_PER_FILE_CAP, DEFAULT_TOTAL_CAP };
687
+ }
688
+ });
689
+
690
+ // ../../core/paths.js
691
+ var require_paths = __commonJS({
692
+ "../../core/paths.js"(exports2, module2) {
693
+ "use strict";
694
+ var os = require("node:os");
695
+ var path = require("node:path");
696
+ function resolveConfigPath(opts) {
697
+ const home = opts && opts.home || os.homedir();
698
+ const env = opts && opts.env || process.env;
699
+ const override = env.DELIBERATION_CONFIG;
700
+ if (typeof override === "string" && override.length > 0) {
701
+ return override;
702
+ }
703
+ return path.join(home, ".claude", "deliberation", "config.json");
704
+ }
705
+ function resolveGrokCachePath(opts) {
706
+ const home = opts && opts.home || os.homedir();
707
+ return path.join(home, ".claude", "cache", "deliberation", "grok-files.json");
708
+ }
709
+ module2.exports = {
710
+ resolveConfigPath,
711
+ resolveGrokCachePath
712
+ };
713
+ }
714
+ });
715
+
716
+ // ../openrouter/index.js
717
+ var require_openrouter = __commonJS({
718
+ "../openrouter/index.js"(exports2, module2) {
719
+ "use strict";
720
+ var DEFAULT_TIMEOUT_MS = 18e4;
721
+ var MAX_MS = 6e5;
722
+ function isNonEmptyString(v) {
723
+ return typeof v === "string" && v.trim().length > 0;
724
+ }
725
+ function truncate(s, n) {
726
+ s = String(s == null ? "" : s);
727
+ return s.length > n ? s.slice(0, n) + "..." : s;
728
+ }
729
+ function buildMessages(turns) {
730
+ return (turns || []).map((t) => {
731
+ if (t.role === "user" && Array.isArray(t.inlineBlocks) && t.inlineBlocks.length) {
732
+ return { role: "user", content: [t.text, ...t.inlineBlocks].join("\n\n") };
733
+ }
734
+ return { role: t.role, content: t.text };
735
+ });
736
+ }
737
+ function classifyError(status, code) {
738
+ switch (code) {
739
+ case "missing-auth":
740
+ return { errorKind: "auth", retryable: false };
741
+ case "unknown-thread":
742
+ return { errorKind: "unknown-thread", retryable: false };
743
+ case "timeout":
744
+ return { errorKind: "timeout", retryable: true };
745
+ case "network":
746
+ return { errorKind: "network", retryable: true };
747
+ case "parse":
748
+ return { errorKind: "parse", retryable: false };
749
+ case "config":
750
+ return { errorKind: "config", retryable: false };
751
+ case "model-not-allowed":
752
+ return { errorKind: "model-not-allowed", retryable: false };
753
+ }
754
+ const s = Number(status);
755
+ if (s === 401 || s === 403) return { errorKind: "auth", retryable: false };
756
+ if (s === 429) return { errorKind: "rate-limit", retryable: true };
757
+ if (s >= 500 && s <= 599) return { errorKind: "upstream", retryable: true };
758
+ return { errorKind: "unknown", retryable: false };
759
+ }
760
+ function parseCompletion(data) {
761
+ const fail = (why) => {
762
+ const e = new Error(`Parse error: ${why}`);
763
+ e.code = "parse";
764
+ return e;
765
+ };
766
+ if (!data || typeof data !== "object") throw fail("body was not a JSON object");
767
+ const choice = Array.isArray(data.choices) ? data.choices[0] : null;
768
+ const content = choice && choice.message && choice.message.content;
769
+ if (typeof content === "string") return content;
770
+ if (Array.isArray(content)) {
771
+ const text = content.filter((p) => p && typeof p.text === "string").map((p) => p.text).join("");
772
+ if (text) return text;
773
+ }
774
+ throw fail("no assistant message content in choices[0]");
775
+ }
776
+ async function callOpenRouter({ apiBase, apiKey, model, messages, reasoningEffort, temperature, timeoutMs, fetchImpl }) {
777
+ const f = fetchImpl || globalThis.fetch;
778
+ if (typeof f !== "function") {
779
+ const e = new Error("global fetch unavailable; Node 18+ required");
780
+ e.code = "network";
781
+ throw e;
782
+ }
783
+ const base = (apiBase || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
784
+ const url = `${base}/chat/completions`;
785
+ const payload = { model, messages, stream: false };
786
+ if (isNonEmptyString(reasoningEffort)) payload.reasoning = { effort: reasoningEffort };
787
+ if (typeof temperature === "number") payload.temperature = temperature;
788
+ const headers = {
789
+ "Content-Type": "application/json",
790
+ "HTTP-Referer": "https://github.com/antonbabenko/deliberation",
791
+ "X-Title": "deliberation"
792
+ };
793
+ if (isNonEmptyString(apiKey)) headers["Authorization"] = `Bearer ${apiKey}`;
794
+ const t = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
795
+ const controller = new AbortController();
796
+ const timer = setTimeout(() => controller.abort(), t);
797
+ let res;
798
+ try {
799
+ res = await f(url, { method: "POST", headers, body: JSON.stringify(payload), signal: controller.signal });
800
+ } catch (err) {
801
+ const msg = String(err && err.message || err);
802
+ if (err && err.name === "AbortError" || /abort/i.test(msg)) {
803
+ const e2 = new Error(`OpenRouter timed out after ${Math.round(t / 1e3)}s`);
804
+ e2.code = "timeout";
805
+ throw e2;
806
+ }
807
+ const e = new Error(`Network error: ${msg}`);
808
+ e.code = "network";
809
+ throw e;
810
+ } finally {
811
+ clearTimeout(timer);
812
+ }
813
+ let bodyText = "";
814
+ try {
815
+ bodyText = await res.text();
816
+ } catch (_) {
817
+ bodyText = "";
818
+ }
819
+ if (!res.ok) {
820
+ const e = new Error(`OpenRouter API error ${res.status}: ${truncate(bodyText, 500)}`);
821
+ e.status = res.status;
822
+ throw e;
823
+ }
824
+ let data;
825
+ try {
826
+ data = JSON.parse(bodyText);
827
+ } catch (e2) {
828
+ const e = new Error(`Parse error: invalid JSON: ${e2.message}`);
829
+ e.code = "parse";
830
+ throw e;
831
+ }
832
+ return { text: parseCompletion(data) };
833
+ }
834
+ var crypto = require("node:crypto");
835
+ var { makeConfigReader } = require_config();
836
+ var { resolveAlias, RESERVED_ALIAS, askAllDelegates, consensusDelegates } = require_routing();
837
+ var { inlineFiles } = require_files();
838
+ var CONFIG_PATH = require_paths().resolveConfigPath();
839
+ var configReader = makeConfigReader(CONFIG_PATH);
840
+ var sessions = /* @__PURE__ */ new Map();
841
+ function sendResponse(id, result) {
842
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
843
+ }
844
+ function sendError(id, code, message) {
845
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
846
+ }
847
+ function isObject(v) {
848
+ return v !== null && typeof v === "object" && !Array.isArray(v);
849
+ }
850
+ function hasRequestId(r) {
851
+ return isObject(r) && Object.prototype.hasOwnProperty.call(r, "id");
852
+ }
853
+ function logCall(cid, tool, outcome, ms) {
854
+ process.stderr.write(`[openrouter] ${cid} ${tool} -> ${outcome} in ${ms}ms
855
+ `);
856
+ }
857
+ function pick(callArg, modelOverride, defaultsVal) {
858
+ if (callArg !== void 0) return callArg;
859
+ if (modelOverride !== void 0) return modelOverride;
860
+ return defaultsVal;
861
+ }
862
+ function errorResult(id, e, tool, startedAt) {
863
+ const { errorKind, retryable } = classifyError(e && e.status, e && e.code);
864
+ logCall(id, tool, errorKind, Date.now() - startedAt);
865
+ return { content: [{ type: "text", text: `Error: ${e && e.message || String(e)}` }], isError: true, errorKind, retryable };
866
+ }
867
+ function buildInitialTurns(developerInstructions, prompt, blocks) {
868
+ const turns = [];
869
+ if (isNonEmptyString(developerInstructions)) turns.push({ role: "system", text: developerInstructions });
870
+ turns.push({ role: "user", text: prompt, inlineBlocks: blocks || [] });
871
+ return turns;
872
+ }
873
+ function resolveDelegate(or, args) {
874
+ if (isNonEmptyString(args.alias)) {
875
+ return resolveAlias(or, args.alias) || { _error: "model-not-allowed" };
876
+ }
877
+ if (isNonEmptyString(args.model)) {
878
+ if (!or.allowRawModel) return { _error: "model-not-allowed" };
879
+ return { model: args.model.trim() };
880
+ }
881
+ return resolveAlias(or, RESERVED_ALIAS) || { _error: "model-not-allowed" };
882
+ }
883
+ var TOOL_PROPS = {
884
+ prompt: { type: "string", description: "The delegation prompt" },
885
+ "developer-instructions": { type: "string", description: "Expert system instructions" },
886
+ alias: { type: "string", description: "Configured delegate alias (preferred)" },
887
+ model: { type: "string", description: "Raw OpenRouter model slug; honored only when allowRawModel:true" },
888
+ reasoning_effort: { type: "string", description: "low|medium|high (provider-dependent)" },
889
+ temperature: { type: "number" },
890
+ timeout: { type: "number", description: "Soft timeout ms, 1..600000" },
891
+ files: { type: "array", description: "Text-inline files: each item has path or dir (file_id/file_url rejected)" },
892
+ roots: { type: "array", items: { type: "string" }, description: "Absolute dirs to resolve files[].path/dir" },
893
+ cwd: { type: "string" },
894
+ sandbox: { type: "string", description: "Accepted for parity; ignored." }
895
+ };
896
+ var handlers = {
897
+ initialize: (id, _p, respond) => {
898
+ if (!respond) return;
899
+ sendResponse(id, { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "deliberation-openrouter", version: "1.0.0" } });
900
+ },
901
+ "notifications/initialized": () => {
902
+ },
903
+ "tools/list": (id, _p, respond) => {
904
+ if (!respond) return;
905
+ sendResponse(id, { tools: [
906
+ { name: "openrouter", description: "Start an OpenRouter expert session (advisory only). Pick a configured `alias`.", inputSchema: { type: "object", properties: TOOL_PROPS, required: ["prompt"] } },
907
+ { name: "openrouter-reply", description: "Continue an OpenRouter session by threadId (in-memory; lost on restart).", inputSchema: { type: "object", properties: { threadId: { type: "string" }, prompt: { type: "string" }, files: TOOL_PROPS.files, roots: TOOL_PROPS.roots, alias: TOOL_PROPS.alias, model: TOOL_PROPS.model, reasoning_effort: TOOL_PROPS.reasoning_effort, temperature: TOOL_PROPS.temperature, timeout: TOOL_PROPS.timeout, cwd: TOOL_PROPS.cwd }, required: ["threadId", "prompt"] } },
908
+ { name: "openrouter-list", description: "List configured OpenRouter delegates and settings. Pass mode ('ask-all'|'consensus') + optional expert to also get the resolved `selected` delegate set (selection applied server-side from the live config).", inputSchema: { type: "object", properties: { mode: { type: "string", enum: ["ask-all", "consensus"] }, expert: { type: "string" } } } }
909
+ ] });
910
+ },
911
+ "tools/call": async (id, params, respond) => {
912
+ const startedAt = Date.now();
913
+ if (!isObject(params) || !isNonEmptyString(params.name)) {
914
+ if (respond) sendError(id, -32602, "Invalid params");
915
+ return;
916
+ }
917
+ const { name, arguments: args } = params;
918
+ if (!isObject(args)) {
919
+ if (respond) sendError(id, -32602, "Invalid params: arguments must be an object");
920
+ return;
921
+ }
922
+ const cfg = configReader.get();
923
+ if (!cfg.ok) {
924
+ if (name === "openrouter-list") {
925
+ const errPayload = { delegates: [], defaultModelSet: false, maxFanout: 3, maxFanoutHigh: false, error: cfg.error };
926
+ if (isObject(args) && (args.mode === "ask-all" || args.mode === "consensus")) {
927
+ errPayload.mode = args.mode;
928
+ if (typeof args.expert === "string") errPayload.expert = args.expert;
929
+ errPayload.selected = [];
930
+ if (args.mode === "ask-all") errPayload.omitted = [];
931
+ }
932
+ if (respond) sendResponse(id, { content: [{ type: "text", text: JSON.stringify(errPayload) }] });
933
+ logCall(id, name, "config", Date.now() - startedAt);
934
+ return;
935
+ }
936
+ if (respond) sendResponse(id, errorResult(id, { code: "config", message: cfg.error }, name, startedAt));
937
+ return;
938
+ }
939
+ const or = cfg.resolved.openrouter;
940
+ if (name === "openrouter-list") {
941
+ if (!respond) return;
942
+ const shape = (m) => ({
943
+ alias: m.alias,
944
+ model: m.model,
945
+ experts: m.experts,
946
+ askAll: m.askAll,
947
+ consensus: m.consensus,
948
+ // Resolved effort the bridge would use absent a per-call override: per-model > defaults > null.
949
+ reasoning_effort: pick(void 0, m.reasoning_effort, or.defaults.reasoning_effort) ?? null
950
+ });
951
+ const payload = {
952
+ delegates: or.models.map(shape),
953
+ defaultModelSet: !!or.defaultModel,
954
+ maxFanout: or.maxFanout,
955
+ maxFanoutHigh: or.maxFanout > 10,
956
+ // Per-entry validation failures (kept-valid delegates above; these were skipped).
957
+ // Each: { index, alias, reason, suggestedAlias? }. Empty when the config is clean.
958
+ invalidModels: or.invalidModels || []
959
+ };
960
+ const mode = args.mode;
961
+ if (mode === "ask-all" || mode === "consensus") {
962
+ const expert = typeof args.expert === "string" ? args.expert : void 0;
963
+ payload.mode = mode;
964
+ if (expert !== void 0) payload.expert = expert;
965
+ if (mode === "ask-all") {
966
+ const out = askAllDelegates(or, expert);
967
+ payload.selected = out.selected.map(shape);
968
+ payload.omitted = out.omitted.map(shape);
969
+ } else {
970
+ payload.selected = consensusDelegates(or, expert).map(shape);
971
+ }
972
+ }
973
+ sendResponse(id, { content: [{ type: "text", text: JSON.stringify(payload) }] });
974
+ logCall(id, name, "ok", Date.now() - startedAt);
975
+ return;
976
+ }
977
+ if (name !== "openrouter" && name !== "openrouter-reply") {
978
+ if (respond) sendError(id, -32601, `Tool not found: ${name}`);
979
+ return;
980
+ }
981
+ if (!isNonEmptyString(args.prompt)) {
982
+ if (respond) sendError(id, -32602, "Invalid params: 'prompt' is required");
983
+ return;
984
+ }
985
+ if (args.timeout !== void 0 && (typeof args.timeout !== "number" || args.timeout <= 0 || args.timeout > MAX_MS)) {
986
+ if (respond) sendError(id, -32602, "Invalid params: 'timeout' must be 1..600000");
987
+ return;
988
+ }
989
+ if (args.alias !== void 0 && args.model !== void 0) {
990
+ if (respond) sendResponse(id, errorResult(id, { code: "config", message: "pass at most one of alias/model" }, name, startedAt));
991
+ return;
992
+ }
993
+ if (args.roots !== void 0 && (!Array.isArray(args.roots) || args.roots.some((r) => typeof r !== "string"))) {
994
+ if (respond) sendError(id, -32602, "Invalid params: 'roots' must be an array of strings");
995
+ return;
996
+ }
997
+ let delegate = null, priorSession = null, threadId;
998
+ if (name === "openrouter-reply") {
999
+ if (!isNonEmptyString(args.threadId)) {
1000
+ if (respond) sendError(id, -32602, "Invalid params: 'threadId' required");
1001
+ return;
1002
+ }
1003
+ threadId = args.threadId.trim();
1004
+ priorSession = sessions.get(threadId);
1005
+ if (!priorSession) {
1006
+ if (respond) sendResponse(id, errorResult(id, { code: "unknown-thread", message: `unknown threadId "${threadId}"` }, name, startedAt));
1007
+ return;
1008
+ }
1009
+ delegate = args.alias || args.model ? resolveDelegate(or, args) : { model: priorSession.model };
1010
+ } else {
1011
+ threadId = crypto.randomUUID();
1012
+ delegate = resolveDelegate(or, args);
1013
+ }
1014
+ if (delegate && delegate._error) {
1015
+ if (respond) sendResponse(id, errorResult(id, { code: delegate._error, message: "alias/model not in allowlist" }, name, startedAt));
1016
+ return;
1017
+ }
1018
+ if (!delegate || !delegate.model) {
1019
+ if (respond) sendResponse(id, errorResult(id, { code: "model-not-allowed", message: "no alias/model resolved and no defaultModel set" }, name, startedAt));
1020
+ return;
1021
+ }
1022
+ let blocks = [], notes = [];
1023
+ if (args.files) {
1024
+ try {
1025
+ const roots = Array.isArray(args.roots) && args.roots.length ? args.roots : [args.cwd || process.cwd()];
1026
+ ({ blocks, notes } = inlineFiles(args.files, { roots }));
1027
+ } catch (e) {
1028
+ if (respond) sendResponse(id, errorResult(id, { code: "config", message: e.message }, name, startedAt));
1029
+ return;
1030
+ }
1031
+ }
1032
+ const reasoningEffort = pick(args.reasoning_effort, delegate.reasoning_effort, or.defaults.reasoning_effort);
1033
+ const temperature = pick(args.temperature, delegate.temperature, or.defaults.temperature);
1034
+ const timeoutMs = pick(args.timeout, delegate.timeout, or.defaults.timeout);
1035
+ const apiBase = delegate.apiBase || or.apiBase;
1036
+ const apiKey = process.env[or.apiKeyEnv] || "";
1037
+ const turns = priorSession ? [...priorSession.turns, { role: "user", text: args.prompt, inlineBlocks: blocks }] : buildInitialTurns(args["developer-instructions"], args.prompt, blocks);
1038
+ try {
1039
+ const out = await callOpenRouter({ apiBase, apiKey, model: delegate.model, messages: buildMessages(turns), reasoningEffort, temperature, timeoutMs });
1040
+ sessions.set(threadId, { turns: [...turns, { role: "assistant", text: out.text }], model: delegate.model, apiBase, reasoningEffort, temperature });
1041
+ if (respond) {
1042
+ const text = notes.length ? `${out.text}
1043
+
1044
+ [files] ${notes.join("; ")}` : out.text;
1045
+ sendResponse(id, { content: [{ type: "text", text }], threadId });
1046
+ }
1047
+ logCall(id, name, "ok", Date.now() - startedAt);
1048
+ } catch (e) {
1049
+ if (respond) sendResponse(id, errorResult(id, e, name, startedAt));
1050
+ }
1051
+ }
1052
+ };
1053
+ async function processLine(line) {
1054
+ if (!line.trim()) return;
1055
+ let request;
1056
+ try {
1057
+ request = JSON.parse(line);
1058
+ } catch (_) {
1059
+ return;
1060
+ }
1061
+ const respond = hasRequestId(request);
1062
+ if (!isObject(request) || typeof request.method !== "string") {
1063
+ if (respond) sendError(request.id, -32600, "Invalid Request");
1064
+ return;
1065
+ }
1066
+ const handler = handlers[request.method];
1067
+ if (!handler) {
1068
+ if (respond) sendError(request.id, -32601, `Method not found: ${request.method}`);
1069
+ return;
1070
+ }
1071
+ try {
1072
+ await handler(request.id, request.params, respond);
1073
+ } catch (e) {
1074
+ if (respond) sendError(request.id, -32603, `Internal error: ${e.message}`);
1075
+ }
1076
+ }
1077
+ if (require.main === module2) {
1078
+ let buffer = "";
1079
+ const enqueue = (line) => {
1080
+ void processLine(line);
1081
+ };
1082
+ process.stdin.on("data", (chunk) => {
1083
+ buffer += chunk.toString();
1084
+ const lines = buffer.split("\n");
1085
+ buffer = lines.pop();
1086
+ for (const l of lines) enqueue(l);
1087
+ });
1088
+ process.stdin.on("end", () => {
1089
+ if (buffer) {
1090
+ enqueue(buffer);
1091
+ buffer = "";
1092
+ }
1093
+ });
1094
+ if (typeof globalThis.fetch !== "function") {
1095
+ console.error("OpenRouter bridge requires Node 18+ (global fetch unavailable).");
1096
+ process.exit(1);
1097
+ }
1098
+ }
1099
+ module2.exports = { buildMessages, classifyError, parseCompletion, callOpenRouter, buildInitialTurns, resolveDelegate, DEFAULT_TIMEOUT_MS, MAX_MS, isNonEmptyString, truncate };
1100
+ }
1101
+ });
1102
+
1103
+ // ../../core/providers/openai-compatible.js
1104
+ var require_openai_compatible = __commonJS({
1105
+ "../../core/providers/openai-compatible.js"(exports2, module2) {
1106
+ "use strict";
1107
+ var crypto = require("node:crypto");
1108
+ var { toErrorResult } = require_provider();
1109
+ var MAX_SESSIONS = 100;
1110
+ function makeOpenAICompatibleProvider(opts) {
1111
+ const { name = "openrouter", apiBase, apiKeyEnv, resolveModel } = opts;
1112
+ const bridge = (
1113
+ /** @type {any} */
1114
+ opts.bridge || require_openrouter()
1115
+ );
1116
+ const sessions = /* @__PURE__ */ new Map();
1117
+ return (
1118
+ /** @type {any} */
1119
+ {
1120
+ name,
1121
+ capabilities: { canImplement: false, fileUpload: false, multiTurn: true },
1122
+ /** Test-only: current number of cached sessions. */
1123
+ get __sessionCount() {
1124
+ return sessions.size;
1125
+ },
1126
+ async health() {
1127
+ return process.env[apiKeyEnv] ? { ok: true } : { ok: false, reason: `${apiKeyEnv} unset` };
1128
+ },
1129
+ async ask(req) {
1130
+ const started = Date.now();
1131
+ const model = resolveModel(req);
1132
+ const prior = req.threadId && sessions.get(req.threadId);
1133
+ const turns = prior ? [...prior, { role: "user", text: req.prompt }] : bridge.buildInitialTurns(req.developerInstructions, req.prompt, req.files || []);
1134
+ try {
1135
+ const { text } = await bridge.callOpenRouter({
1136
+ apiBase,
1137
+ apiKey: process.env[apiKeyEnv],
1138
+ model,
1139
+ messages: bridge.buildMessages(turns),
1140
+ reasoningEffort: req.reasoningEffort,
1141
+ temperature: req.temperature,
1142
+ timeoutMs: req.timeoutMs
1143
+ });
1144
+ const threadId = req.threadId || crypto.randomUUID();
1145
+ sessions.set(threadId, [...turns, { role: "assistant", text }]);
1146
+ if (sessions.size > MAX_SESSIONS) sessions.delete(sessions.keys().next().value);
1147
+ return { provider: name, model, text, threadId, isError: false, ms: Date.now() - started };
1148
+ } catch (e) {
1149
+ return toErrorResult(
1150
+ name,
1151
+ model,
1152
+ started,
1153
+ /** @type {any} */
1154
+ e,
1155
+ bridge.classifyError
1156
+ );
1157
+ }
1158
+ }
1159
+ }
1160
+ );
1161
+ }
1162
+ module2.exports = { makeOpenAICompatibleProvider, MAX_SESSIONS };
1163
+ }
1164
+ });
1165
+
1166
+ // ../grok/lock.js
1167
+ var require_lock = __commonJS({
1168
+ "../grok/lock.js"(exports2, module2) {
1169
+ "use strict";
1170
+ var fs = require("node:fs");
1171
+ var path = require("node:path");
1172
+ var crypto = require("node:crypto");
1173
+ var STALE_MS = 5e3;
1174
+ var POLL_MS = 20;
1175
+ function sleepSyncMs(ms) {
1176
+ const until = Date.now() + ms;
1177
+ while (Date.now() < until) {
1178
+ }
1179
+ }
1180
+ function acquire(basePath, { maxWaitMs = 1e3 } = {}) {
1181
+ const lockDir = `${basePath}.lock`;
1182
+ const token = crypto.randomBytes(16).toString("hex");
1183
+ const markerName = `owner.${token}.txt`;
1184
+ const deadline = Date.now() + maxWaitMs;
1185
+ try {
1186
+ fs.mkdirSync(path.dirname(lockDir), { recursive: true });
1187
+ } catch (_) {
1188
+ }
1189
+ while (true) {
1190
+ try {
1191
+ fs.mkdirSync(lockDir);
1192
+ const markerPath = path.join(lockDir, markerName);
1193
+ fs.writeFileSync(markerPath, JSON.stringify({ pid: process.pid, token, t: Date.now() }));
1194
+ return { lockDir, markerPath, token };
1195
+ } catch (e) {
1196
+ if (e.code !== "EEXIST") throw e;
1197
+ try {
1198
+ const st = fs.statSync(lockDir);
1199
+ if (Date.now() - st.mtimeMs > STALE_MS) {
1200
+ const dead = `${lockDir}.dead.${process.pid}.${Date.now()}`;
1201
+ try {
1202
+ fs.renameSync(lockDir, dead);
1203
+ fs.rmSync(dead, { recursive: true, force: true });
1204
+ continue;
1205
+ } catch (_) {
1206
+ }
1207
+ }
1208
+ } catch (_) {
1209
+ }
1210
+ }
1211
+ if (Date.now() >= deadline) return null;
1212
+ sleepSyncMs(POLL_MS);
1213
+ }
1214
+ }
1215
+ function release(handle) {
1216
+ if (!handle) return;
1217
+ try {
1218
+ fs.unlinkSync(handle.markerPath);
1219
+ } catch (_) {
1220
+ }
1221
+ try {
1222
+ fs.rmdirSync(handle.lockDir);
1223
+ } catch (_) {
1224
+ }
1225
+ }
1226
+ function heartbeat(handle) {
1227
+ if (!handle) return;
1228
+ const now = /* @__PURE__ */ new Date();
1229
+ try {
1230
+ fs.utimesSync(handle.lockDir, now, now);
1231
+ } catch (_) {
1232
+ }
1233
+ }
1234
+ module2.exports = { acquire, release, heartbeat, STALE_MS, POLL_MS };
1235
+ }
1236
+ });
1237
+
1238
+ // ../grok/cache.js
1239
+ var require_cache = __commonJS({
1240
+ "../grok/cache.js"(exports2, module2) {
1241
+ "use strict";
1242
+ var path = require("node:path");
1243
+ var os = require("node:os");
1244
+ var crypto = require("node:crypto");
1245
+ var { mkdirSync, readFileSync, writeFileSync, renameSync } = require("node:fs");
1246
+ var lock = require_lock();
1247
+ var CACHE_FILE = require_paths().resolveGrokCachePath();
1248
+ var CACHE_DIR = path.dirname(CACHE_FILE);
1249
+ var CACHE_VERSION = 1;
1250
+ function normalize(apiBase) {
1251
+ let u;
1252
+ try {
1253
+ u = new URL(apiBase);
1254
+ } catch (_) {
1255
+ u = new URL(`https://${apiBase}`);
1256
+ }
1257
+ const proto = u.protocol.toLowerCase();
1258
+ const host = u.host.toLowerCase();
1259
+ let pathname = u.pathname;
1260
+ if (pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
1261
+ return `${proto}//${host}${pathname}`;
1262
+ }
1263
+ function buildCacheKey({ bytes, apiKey, apiBase, filename }) {
1264
+ const contentHash = crypto.createHash("sha256").update(bytes).digest("hex");
1265
+ const keyFp = crypto.createHash("sha256").update(String(apiKey)).digest("hex").slice(0, 16);
1266
+ const baseNorm = normalize(apiBase);
1267
+ return `${contentHash}@${keyFp}@${baseNorm}@${filename}`;
1268
+ }
1269
+ function readCache(file) {
1270
+ try {
1271
+ const raw = readFileSync(file, "utf8");
1272
+ const obj = JSON.parse(raw);
1273
+ if (obj && typeof obj === "object" && obj.entries && typeof obj.entries === "object") {
1274
+ return { version: obj.version || CACHE_VERSION, entries: obj.entries };
1275
+ }
1276
+ } catch (e) {
1277
+ if (e && e.code !== "ENOENT") {
1278
+ process.stderr.write(`[grok] cache read failed (${e.message}); treating as empty
1279
+ `);
1280
+ }
1281
+ }
1282
+ return { version: CACHE_VERSION, entries: {} };
1283
+ }
1284
+ function writeCache(file, data) {
1285
+ mkdirSync(path.dirname(file), { recursive: true });
1286
+ const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
1287
+ writeFileSync(tmp, JSON.stringify(data));
1288
+ renameSync(tmp, file);
1289
+ }
1290
+ var _inflight = /* @__PURE__ */ new Map();
1291
+ function withInflight(key, worker) {
1292
+ if (_inflight.has(key)) return _inflight.get(key);
1293
+ const p = Promise.resolve().then(worker);
1294
+ _inflight.set(key, p);
1295
+ p.then(
1296
+ () => {
1297
+ _inflight.delete(key);
1298
+ },
1299
+ () => {
1300
+ _inflight.delete(key);
1301
+ }
1302
+ );
1303
+ return p;
1304
+ }
1305
+ function lookup(file, key, { apiBase, keyFp } = {}) {
1306
+ const data = readCache(file);
1307
+ const entry = data.entries[key];
1308
+ if (!entry) return null;
1309
+ const now = Math.floor(Date.now() / 1e3);
1310
+ if (entry.expiresAt - now < 60) return null;
1311
+ if (apiBase && entry.apiBase !== apiBase) return null;
1312
+ if (keyFp && entry.keyFp !== keyFp) return null;
1313
+ return entry;
1314
+ }
1315
+ async function store(file, key, entry) {
1316
+ if (!file) return;
1317
+ const handle = lock.acquire(file, { maxWaitMs: 1e3 });
1318
+ if (!handle) {
1319
+ process.stderr.write("[grok] cache lock contention; skipping persist\n");
1320
+ return;
1321
+ }
1322
+ try {
1323
+ const data = readCache(file);
1324
+ data.entries[key] = entry;
1325
+ writeCache(file, data);
1326
+ } finally {
1327
+ lock.release(handle);
1328
+ }
1329
+ }
1330
+ async function evict(file, fileId) {
1331
+ if (!file) return;
1332
+ const handle = lock.acquire(file, { maxWaitMs: 1e3 });
1333
+ if (!handle) return;
1334
+ try {
1335
+ const data = readCache(file);
1336
+ for (const k of Object.keys(data.entries)) {
1337
+ if (data.entries[k].fileId === fileId) delete data.entries[k];
1338
+ }
1339
+ writeCache(file, data);
1340
+ } finally {
1341
+ lock.release(handle);
1342
+ }
1343
+ }
1344
+ module2.exports = { normalize, buildCacheKey, readCache, writeCache, withInflight, lookup, store, evict, CACHE_DIR, CACHE_FILE, CACHE_VERSION };
1345
+ }
1346
+ });
1347
+
1348
+ // ../grok/index.js
1349
+ var require_grok = __commonJS({
1350
+ "../grok/index.js"(exports2, module2) {
1351
+ "use strict";
1352
+ var crypto = require("node:crypto");
1353
+ var path = require("node:path");
1354
+ var { stat, readFile } = require("node:fs/promises");
1355
+ var DEFAULT_MODEL = process.env.GROK_DEFAULT_MODEL || "grok-4.3";
1356
+ var DEFAULT_API_BASE = process.env.XAI_API_BASE || "https://api.x.ai/v1";
1357
+ var DEFAULT_TIMEOUT_MS = 18e4;
1358
+ var MAX_MS = 6e5;
1359
+ var VALID_SANDBOX_VALUES = /* @__PURE__ */ new Set(["read-only", "workspace-write"]);
1360
+ var FILE_TTL_MIN = 3600;
1361
+ var FILE_TTL_MAX = 2592e3;
1362
+ function resolveFileTtl() {
1363
+ const raw = Number(process.env.GROK_FILE_TTL_SECONDS);
1364
+ const v = Number.isFinite(raw) && raw > 0 ? raw : 604800;
1365
+ return Math.min(FILE_TTL_MAX, Math.max(FILE_TTL_MIN, Math.round(v)));
1366
+ }
1367
+ var FILE_TTL_SECONDS = resolveFileTtl();
1368
+ var MAX_FILE_BYTES = 48 * 1024 * 1024;
1369
+ var FILE_PREFIX = "deliberation-";
1370
+ var UPLOAD_PURPOSE = "assistants";
1371
+ var cacheModule = require_cache();
1372
+ var DEFAULT_CACHE_FILE = cacheModule.CACHE_FILE;
1373
+ var globMod = require_glob();
1374
+ var DEFAULT_INCLUDE = ["**/*"];
1375
+ var DEFAULT_EXCLUDE = [
1376
+ // VCS (bare = any-depth via (?:^|/) anchor in glob.js compile())
1377
+ ".git",
1378
+ // JS / Node ecosystems
1379
+ "node_modules",
1380
+ "**/dist/**",
1381
+ "**/build/**",
1382
+ "**/out/**",
1383
+ "**/.next/**",
1384
+ "**/.svelte-kit/**",
1385
+ "**/.nuxt/**",
1386
+ "**/.turbo/**",
1387
+ "**/.cache/**",
1388
+ "**/.parcel-cache/**",
1389
+ "**/.pnpm-store/**",
1390
+ // Yarn Berry caches (can run into thousands of files)
1391
+ "**/.yarn/cache/**",
1392
+ "**/.yarn/unplugged/**",
1393
+ "**/.yarn/install-state.gz",
1394
+ // Lockfiles
1395
+ "**/*.lock",
1396
+ // Python
1397
+ "**/.venv/**",
1398
+ "**/venv/**",
1399
+ "**/__pycache__/**",
1400
+ "**/.tox/**",
1401
+ "**/.pytest_cache/**",
1402
+ "**/.mypy_cache/**",
1403
+ "**/.ruff_cache/**",
1404
+ "**/.ipynb_checkpoints/**",
1405
+ "**/.eggs/**",
1406
+ "**/htmlcov/**",
1407
+ // Coverage
1408
+ "**/coverage/**",
1409
+ "**/.nyc_output/**",
1410
+ // Rust / Java / Gradle
1411
+ "**/target/**",
1412
+ "**/.gradle/**",
1413
+ // Go / PHP
1414
+ "**/vendor/**",
1415
+ // Terraform
1416
+ "**/.terraform/**",
1417
+ "**/.terragrunt-cache/**",
1418
+ // Security: Terraform state holds plaintext credentials and resource ARNs
1419
+ "**/*.tfstate*",
1420
+ "**/.terraform.tfstate.lock.info",
1421
+ // Security: dotenv (granular - keeps .env.example/.env.sample/.env.template readable)
1422
+ "**/.env",
1423
+ "**/.env.local",
1424
+ "**/.env.development",
1425
+ "**/.env.development.local",
1426
+ "**/.env.dev",
1427
+ "**/.env.dev.local",
1428
+ "**/.env.production",
1429
+ "**/.env.production.local",
1430
+ "**/.env.prod",
1431
+ "**/.env.prod.local",
1432
+ "**/.env.test",
1433
+ "**/.env.test.local",
1434
+ "**/.env.staging",
1435
+ "**/.env.staging.local",
1436
+ "**/.env.stage",
1437
+ "**/.env.stage.local",
1438
+ // Security: SSH config + private keys
1439
+ "**/.ssh/**",
1440
+ "**/id_rsa",
1441
+ "**/id_rsa.pub",
1442
+ "**/id_ed25519",
1443
+ "**/id_ed25519.pub",
1444
+ "**/id_ecdsa",
1445
+ "**/id_ecdsa.pub",
1446
+ "**/id_dsa",
1447
+ "**/id_dsa.pub",
1448
+ "**/*.pem",
1449
+ "**/*.key"
1450
+ ];
1451
+ var DEFAULT_MAX_FILES = 50;
1452
+ var DEFAULT_MAX_BYTES = 128 * 1024 * 1024;
1453
+ var DIR_UPLOAD_CONCURRENCY = 4;
1454
+ var DEFAULT_REASONING_EFFORT = "high";
1455
+ function resolveReasoningEffort(perCall) {
1456
+ let raw = perCall;
1457
+ if (raw === void 0 || raw === null) raw = process.env.GROK_REASONING_EFFORT;
1458
+ if (raw === void 0 || raw === null) raw = DEFAULT_REASONING_EFFORT;
1459
+ const v = String(raw).trim();
1460
+ if (v === "" || v.toLowerCase() === "none" || v.toLowerCase() === "off") return null;
1461
+ return v;
1462
+ }
1463
+ var sessions = /* @__PURE__ */ new Map();
1464
+ function sendResponse(id, result) {
1465
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
1466
+ }
1467
+ function sendError(id, code, message) {
1468
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
1469
+ }
1470
+ function isObject(value) {
1471
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1472
+ }
1473
+ function hasRequestId(request) {
1474
+ return isObject(request) && Object.prototype.hasOwnProperty.call(request, "id");
1475
+ }
1476
+ function isNonEmptyString(value) {
1477
+ return typeof value === "string" && value.trim().length > 0;
1478
+ }
1479
+ function truncate(str, max) {
1480
+ const s = String(str == null ? "" : str);
1481
+ return s.length > max ? s.slice(0, max) + "..." : s;
1482
+ }
1483
+ function logCall(cid, tool, outcome, ms) {
1484
+ process.stderr.write(`[grok] ${cid} ${tool} -> ${outcome} in ${ms}ms
1485
+ `);
1486
+ }
1487
+ function classifyGrokError(status, errCode) {
1488
+ switch (errCode) {
1489
+ case "missing-auth":
1490
+ return { errorKind: "missing-auth", retryable: false };
1491
+ case "unknown-thread":
1492
+ return { errorKind: "unknown-thread", retryable: false };
1493
+ case "timeout":
1494
+ return { errorKind: "timeout", retryable: true };
1495
+ case "network":
1496
+ return { errorKind: "network", retryable: true };
1497
+ case "parse":
1498
+ return { errorKind: "parse", retryable: false };
1499
+ case "file-too-large":
1500
+ return { errorKind: "file-too-large", retryable: false };
1501
+ case "file-read":
1502
+ return { errorKind: "file-read", retryable: false };
1503
+ case "file-upload":
1504
+ return { errorKind: "file-upload", retryable: true };
1505
+ }
1506
+ const s = Number(status);
1507
+ if (s === 401 || s === 403) return { errorKind: "auth", retryable: false };
1508
+ if (s === 429) return { errorKind: "rate-limit", retryable: true };
1509
+ if (s >= 500 && s <= 599) return { errorKind: "upstream", retryable: true };
1510
+ return { errorKind: "unknown", retryable: false };
1511
+ }
1512
+ function buildInitialTurns(developerInstructions, prompt, fileRefs) {
1513
+ const turns = [];
1514
+ if (isNonEmptyString(developerInstructions)) {
1515
+ turns.push({ role: "system", text: developerInstructions });
1516
+ }
1517
+ turns.push({ role: "user", text: prompt, fileRefs: fileRefs || [] });
1518
+ return turns;
1519
+ }
1520
+ function turnsToInput(turns) {
1521
+ const input = [];
1522
+ for (const turn of turns) {
1523
+ if (turn.role === "assistant") {
1524
+ if (Array.isArray(turn.items) && turn.items.length) {
1525
+ for (const item of turn.items) input.push(item);
1526
+ } else {
1527
+ input.push({ role: "assistant", content: [{ type: "output_text", text: turn.text }] });
1528
+ }
1529
+ continue;
1530
+ }
1531
+ if (turn.role === "system") {
1532
+ input.push({ role: "system", content: [{ type: "input_text", text: turn.text }] });
1533
+ continue;
1534
+ }
1535
+ const content = [{ type: "input_text", text: turn.text }];
1536
+ for (const ref of turn.fileRefs || []) {
1537
+ if (ref.inline_text != null) {
1538
+ const name = ref.inline_filename || "file";
1539
+ content.push({ type: "input_text", text: `=== ${name} ===
1540
+ ${ref.inline_text}` });
1541
+ } else if (ref.file_id) content.push({ type: "input_file", file_id: ref.file_id });
1542
+ else if (ref.file_url) content.push({ type: "input_file", file_url: ref.file_url });
1543
+ }
1544
+ input.push({ role: "user", content });
1545
+ }
1546
+ return input;
1547
+ }
1548
+ function parseResponsesOutput(data) {
1549
+ const fail = (why) => {
1550
+ const e = new Error(`Parse error: ${why}`);
1551
+ e.code = "parse";
1552
+ return e;
1553
+ };
1554
+ if (!isObject(data)) throw fail("response was not a JSON object");
1555
+ if (isNonEmptyString(data.output_text)) return data.output_text;
1556
+ const output = data.output;
1557
+ if (!Array.isArray(output) || output.length === 0) throw fail("no output in response");
1558
+ for (let i = output.length - 1; i >= 0; i--) {
1559
+ const item = output[i];
1560
+ const parts = item && item.content;
1561
+ if (!Array.isArray(parts)) continue;
1562
+ const texts = parts.filter((p) => p && typeof p.text === "string" && (p.type === "output_text" || p.type === "text" || p.type === void 0)).map((p) => p.text);
1563
+ if (texts.length) return texts.join("");
1564
+ }
1565
+ throw fail("no text part found in output");
1566
+ }
1567
+ var INLINE_MAX_BYTES_DEFAULT = 256 * 1024;
1568
+ function resolveInlineMaxBytes() {
1569
+ const raw = Number(process.env.GROK_INLINE_MAX_BYTES);
1570
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : INLINE_MAX_BYTES_DEFAULT;
1571
+ }
1572
+ function isProbablyText(buf) {
1573
+ if (!buf || buf.length === 0) return true;
1574
+ const slice = buf.subarray(0, Math.min(buf.length, 4096));
1575
+ if (slice.includes(0)) return false;
1576
+ let np = 0;
1577
+ for (const b of slice) {
1578
+ if (b === 9 || b === 10 || b === 13) continue;
1579
+ if (b >= 32 && b <= 126) continue;
1580
+ if (b >= 128) continue;
1581
+ np++;
1582
+ }
1583
+ return np / slice.length < 0.05;
1584
+ }
1585
+ function shouldInline(buf, mode) {
1586
+ if (mode === "inline") return true;
1587
+ if (mode === "auto") {
1588
+ return buf.length <= resolveInlineMaxBytes() && isProbablyText(buf);
1589
+ }
1590
+ return false;
1591
+ }
1592
+ async function uploadFile({ filePath, filename, apiKey, apiBase, ttl, roots, cwd, fetchImpl, cacheFile, mode }) {
1593
+ if (!isNonEmptyString(apiKey)) {
1594
+ const e = new Error("XAI_API_KEY is not set; cannot upload files.");
1595
+ e.code = "missing-auth";
1596
+ throw e;
1597
+ }
1598
+ const f = fetchImpl || globalThis.fetch;
1599
+ const base = (apiBase || DEFAULT_API_BASE).replace(/\/+$/, "");
1600
+ const rootList = Array.isArray(roots) && roots.length ? roots : [require("node:fs").realpathSync(cwd || process.cwd())];
1601
+ let resolved;
1602
+ try {
1603
+ resolved = resolvePathUnderRoots(filePath.replace(/\\/g, "/"), rootList, "file");
1604
+ } catch (err) {
1605
+ const e = new Error(`Cannot read file "${filePath}": ${err && err.message || err}`);
1606
+ e.code = "file-read";
1607
+ throw e;
1608
+ }
1609
+ if (resolved.size > MAX_FILE_BYTES) {
1610
+ const e = new Error(`File "${filePath}" is ${resolved.size} bytes; exceeds the ${MAX_FILE_BYTES}-byte cap.`);
1611
+ e.code = "file-too-large";
1612
+ throw e;
1613
+ }
1614
+ let buf;
1615
+ try {
1616
+ buf = await require("node:fs/promises").readFile(resolved.abs);
1617
+ } catch (err) {
1618
+ const e = new Error(`Cannot read file "${filePath}": ${err && err.message || err}`);
1619
+ e.code = "file-read";
1620
+ throw e;
1621
+ }
1622
+ const cacheMod = require_cache();
1623
+ const baseName = require("node:path").basename(filename || resolved.abs);
1624
+ const effectiveFilename = baseName;
1625
+ if (shouldInline(buf, mode)) {
1626
+ return {
1627
+ _inline: true,
1628
+ inline_text: buf.toString("utf8"),
1629
+ inline_filename: effectiveFilename,
1630
+ _sourcePath: resolved.abs,
1631
+ _sourceRoot: resolved.root,
1632
+ _bytes: buf.length
1633
+ };
1634
+ }
1635
+ const apiBaseNorm = cacheMod.normalize(base);
1636
+ const cacheKey = cacheMod.buildCacheKey({ bytes: buf, apiKey, apiBase: base, filename: effectiveFilename });
1637
+ const keyFp = require("node:crypto").createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
1638
+ const useCache = !process.env.XAI_DISABLE_FILE_CACHE && cacheFile;
1639
+ if (useCache) {
1640
+ const hit = cacheMod.lookup(cacheFile, cacheKey, { apiBase: apiBaseNorm, keyFp });
1641
+ if (hit) return { id: hit.fileId, _fromCache: true, _cacheKey: cacheKey, _sourcePath: resolved.abs, _sourceRoot: resolved.root };
1642
+ }
1643
+ const work = async () => {
1644
+ const contentHash = require("node:crypto").createHash("sha256").update(buf).digest("hex");
1645
+ const storedName = `${FILE_PREFIX}${contentHash.slice(0, 16)}-${baseName}`;
1646
+ const form = new FormData();
1647
+ form.append("expires_after", String(ttl != null ? ttl : FILE_TTL_SECONDS));
1648
+ form.append("purpose", UPLOAD_PURPOSE);
1649
+ form.append("file", new Blob([buf]), storedName);
1650
+ let res;
1651
+ try {
1652
+ res = await f(`${base}/files`, {
1653
+ method: "POST",
1654
+ headers: { "Authorization": `Bearer ${apiKey}` },
1655
+ body: form
1656
+ });
1657
+ } catch (err) {
1658
+ const e = new Error(`File upload network error: ${err && err.message || err}`);
1659
+ e.code = "file-upload";
1660
+ throw e;
1661
+ }
1662
+ let bodyText = "";
1663
+ try {
1664
+ bodyText = await res.text();
1665
+ } catch (_) {
1666
+ bodyText = "";
1667
+ }
1668
+ if (!res.ok) {
1669
+ const e = new Error(`xAI file upload error ${res.status}: ${truncate(bodyText, 300)}`);
1670
+ e.status = res.status;
1671
+ if (res.status < 400 || res.status >= 500) e.code = "file-upload";
1672
+ throw e;
1673
+ }
1674
+ let uploaded2;
1675
+ try {
1676
+ uploaded2 = JSON.parse(bodyText);
1677
+ } catch (e2) {
1678
+ const e = new Error(`File upload parse error: ${e2.message}`);
1679
+ e.code = "parse";
1680
+ throw e;
1681
+ }
1682
+ if (!isNonEmptyString(uploaded2 && uploaded2.id)) {
1683
+ const e = new Error("File upload returned no file id");
1684
+ e.code = "parse";
1685
+ throw e;
1686
+ }
1687
+ if (useCache) {
1688
+ const entry = {
1689
+ fileId: uploaded2.id,
1690
+ size: buf.length,
1691
+ filename: effectiveFilename,
1692
+ uploadedAt: Math.floor(Date.now() / 1e3),
1693
+ expiresAt: Math.floor(Date.now() / 1e3) + (ttl != null ? ttl : FILE_TTL_SECONDS),
1694
+ apiBase: apiBaseNorm,
1695
+ keyFp
1696
+ };
1697
+ await cacheMod.store(cacheFile, cacheKey, entry);
1698
+ }
1699
+ return uploaded2;
1700
+ };
1701
+ const uploaded = useCache ? await cacheMod.withInflight(cacheKey, work) : await work();
1702
+ return { ...uploaded, _cacheKey: cacheKey, _sourcePath: resolved.abs, _sourceRoot: resolved.root };
1703
+ }
1704
+ async function resolveFiles(files, opts) {
1705
+ const refs = [];
1706
+ const ownedIds = [];
1707
+ const seen = /* @__PURE__ */ new Set();
1708
+ const dedupKey = (single) => single._cacheKey || `inline:${single._sourcePath}`;
1709
+ async function handlePath(entry) {
1710
+ const uploaded = await uploadFile({
1711
+ filePath: entry.path,
1712
+ filename: entry.filename,
1713
+ mode: entry.mode,
1714
+ ...opts
1715
+ });
1716
+ if (uploaded && uploaded._inline) {
1717
+ const k = dedupKey(uploaded);
1718
+ if (seen.has(k)) return;
1719
+ seen.add(k);
1720
+ refs.push({
1721
+ inline_text: uploaded.inline_text,
1722
+ inline_filename: uploaded.inline_filename,
1723
+ sourcePath: uploaded._sourcePath,
1724
+ sourceRoot: uploaded._sourceRoot
1725
+ });
1726
+ return;
1727
+ }
1728
+ if (!isNonEmptyString(uploaded && uploaded.id)) {
1729
+ const e = new Error("File upload returned no file id");
1730
+ e.code = "parse";
1731
+ throw e;
1732
+ }
1733
+ if (uploaded._cacheKey) {
1734
+ if (seen.has(uploaded._cacheKey)) return;
1735
+ seen.add(uploaded._cacheKey);
1736
+ }
1737
+ refs.push({
1738
+ file_id: uploaded.id,
1739
+ sourcePath: uploaded._sourcePath,
1740
+ sourceRoot: uploaded._sourceRoot,
1741
+ sourceCacheKey: uploaded._cacheKey
1742
+ });
1743
+ if (!uploaded._fromCache) ownedIds.push(uploaded.id);
1744
+ }
1745
+ async function handleDir(entry) {
1746
+ const rootList = Array.isArray(opts.roots) && opts.roots.length ? opts.roots : [require("node:fs").realpathSync(opts.cwd || process.cwd())];
1747
+ const resolved = resolvePathUnderRoots(entry.dir.replace(/\\/g, "/"), rootList, "dir");
1748
+ const include = entry.include || DEFAULT_INCLUDE;
1749
+ const exclude = entry.excludeReset === true ? entry.exclude || [] : [...DEFAULT_EXCLUDE, ...entry.exclude || []];
1750
+ const maxFiles = entry.maxFiles || DEFAULT_MAX_FILES;
1751
+ const maxBytes = entry.maxBytes || DEFAULT_MAX_BYTES;
1752
+ const { files: walked } = globMod.walk(resolved.abs, { include, exclude, maxFiles, maxBytes });
1753
+ let cached = 0, uploaded = 0, skipped = 0;
1754
+ const queue = [...walked];
1755
+ let inlined = 0;
1756
+ async function worker() {
1757
+ while (queue.length) {
1758
+ const f = queue.shift();
1759
+ const single = await uploadFile({
1760
+ filePath: f.abs,
1761
+ apiKey: opts.apiKey,
1762
+ apiBase: opts.apiBase,
1763
+ ttl: opts.ttl,
1764
+ roots: [resolved.root],
1765
+ fetchImpl: opts.fetchImpl,
1766
+ cacheFile: opts.cacheFile,
1767
+ mode: entry.mode
1768
+ });
1769
+ if (single && single._inline) {
1770
+ const k = dedupKey(single);
1771
+ if (seen.has(k)) {
1772
+ skipped += 1;
1773
+ continue;
1774
+ }
1775
+ seen.add(k);
1776
+ inlined += 1;
1777
+ refs.push({
1778
+ inline_text: single.inline_text,
1779
+ inline_filename: single.inline_filename,
1780
+ sourcePath: single._sourcePath,
1781
+ sourceRoot: single._sourceRoot
1782
+ });
1783
+ continue;
1784
+ }
1785
+ if (!isNonEmptyString(single && single.id)) continue;
1786
+ if (seen.has(single._cacheKey)) {
1787
+ skipped += 1;
1788
+ continue;
1789
+ }
1790
+ seen.add(single._cacheKey);
1791
+ if (single._fromCache) cached += 1;
1792
+ else {
1793
+ uploaded += 1;
1794
+ ownedIds.push(single.id);
1795
+ }
1796
+ refs.push({
1797
+ file_id: single.id,
1798
+ sourcePath: single._sourcePath,
1799
+ sourceRoot: single._sourceRoot,
1800
+ sourceCacheKey: single._cacheKey
1801
+ });
1802
+ }
1803
+ }
1804
+ const N = Math.max(1, Math.min(DIR_UPLOAD_CONCURRENCY, walked.length));
1805
+ await Promise.all(Array.from({ length: N }, () => worker()));
1806
+ process.stderr.write(`[grok] ${opts.cid || "-"} expanded dir=${entry.dir} count=${walked.length} cached=${cached} uploaded=${uploaded} inlined=${inlined} skipped=${skipped}
1807
+ `);
1808
+ }
1809
+ for (const entry of files || []) {
1810
+ if (entry.file_id) refs.push({ file_id: entry.file_id, sourcePath: null, sourceRoot: null });
1811
+ else if (entry.file_url) refs.push({ file_url: entry.file_url, sourcePath: null, sourceRoot: null });
1812
+ else if (entry.path) await handlePath(entry);
1813
+ else if (entry.dir) await handleDir(entry);
1814
+ }
1815
+ return { refs, ownedIds };
1816
+ }
1817
+ async function runGrok({ turns, model, timeoutMs, apiKey, apiBase, fetchImpl, reasoningEffort }) {
1818
+ if (!isNonEmptyString(apiKey)) {
1819
+ const e = new Error("XAI_API_KEY is not set. Export it (export XAI_API_KEY=xai-...) or rerun /deliberation:setup.");
1820
+ e.code = "missing-auth";
1821
+ throw e;
1822
+ }
1823
+ const f = fetchImpl || globalThis.fetch;
1824
+ if (typeof f !== "function") {
1825
+ const e = new Error("global fetch is unavailable; Node 18+ is required for the Grok bridge.");
1826
+ e.code = "network";
1827
+ throw e;
1828
+ }
1829
+ const base = (apiBase || DEFAULT_API_BASE).replace(/\/+$/, "");
1830
+ const url = `${base}/responses`;
1831
+ const payload = { model: model || DEFAULT_MODEL, input: turnsToInput(turns), stream: false };
1832
+ if (isNonEmptyString(reasoningEffort)) payload.reasoning_effort = reasoningEffort;
1833
+ const t = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
1834
+ const controller = new AbortController();
1835
+ const timer = setTimeout(() => controller.abort(), t);
1836
+ let res;
1837
+ try {
1838
+ res = await f(url, {
1839
+ method: "POST",
1840
+ headers: {
1841
+ "Content-Type": "application/json",
1842
+ "Authorization": `Bearer ${apiKey}`
1843
+ },
1844
+ body: JSON.stringify(payload),
1845
+ signal: controller.signal
1846
+ });
1847
+ } catch (err) {
1848
+ const name = err && err.name;
1849
+ const msg = String(err && err.message || err);
1850
+ if (name === "AbortError" || /abort/i.test(msg)) {
1851
+ const e2 = new Error(`Grok timed out after ${Math.round(t / 1e3)}s`);
1852
+ e2.code = "timeout";
1853
+ throw e2;
1854
+ }
1855
+ const e = new Error(`Network error: ${msg}`);
1856
+ e.code = "network";
1857
+ throw e;
1858
+ } finally {
1859
+ clearTimeout(timer);
1860
+ }
1861
+ let bodyText = "";
1862
+ try {
1863
+ bodyText = await res.text();
1864
+ } catch (_) {
1865
+ bodyText = "";
1866
+ }
1867
+ if (!res.ok) {
1868
+ const e = new Error(`xAI API error ${res.status}: ${truncate(bodyText, 500)}`);
1869
+ e.status = res.status;
1870
+ throw e;
1871
+ }
1872
+ let data;
1873
+ try {
1874
+ data = JSON.parse(bodyText);
1875
+ } catch (e2) {
1876
+ const e = new Error(`Parse error: invalid JSON body: ${e2.message}`);
1877
+ e.code = "parse";
1878
+ throw e;
1879
+ }
1880
+ const text = parseResponsesOutput(data);
1881
+ return { text, output: Array.isArray(data.output) ? data.output : null };
1882
+ }
1883
+ var STALE_FILE_ID_TEST = /file[-_][A-Za-z0-9_-]+/;
1884
+ var STALE_FILE_ID_EXTRACT = /file[-_][A-Za-z0-9_-]+/g;
1885
+ function isStaleFileError(err) {
1886
+ if (!err) return false;
1887
+ const msg = String(err.message || "");
1888
+ if (!STALE_FILE_ID_TEST.test(msg)) return false;
1889
+ if (err.status && err.status >= 400 && err.status < 500) return true;
1890
+ return /invalid|not found|missing|expired/i.test(msg);
1891
+ }
1892
+ async function runWithFiles(args) {
1893
+ const { refs, ownedIds } = await resolveFiles(args.files, args);
1894
+ const developerInstructions = args["developer-instructions"];
1895
+ const prompt = args.prompt;
1896
+ const priorTurns = args.priorTurns || null;
1897
+ function buildTurns(currentRefs) {
1898
+ if (priorTurns) {
1899
+ return [...priorTurns, { role: "user", text: prompt, fileRefs: currentRefs }];
1900
+ }
1901
+ return buildInitialTurns(developerInstructions, prompt, currentRefs);
1902
+ }
1903
+ async function attempt(currentTurns) {
1904
+ return runGrok({
1905
+ turns: currentTurns,
1906
+ apiKey: args.apiKey,
1907
+ apiBase: args.apiBase,
1908
+ fetchImpl: args.fetchImpl,
1909
+ timeoutMs: args.timeout,
1910
+ model: args.model,
1911
+ reasoningEffort: args.reasoningEffort
1912
+ });
1913
+ }
1914
+ try {
1915
+ const out = await attempt(buildTurns(refs));
1916
+ return { text: out.text, output: out.output, refs, ownedIds };
1917
+ } catch (e) {
1918
+ if (!isStaleFileError(e)) throw e;
1919
+ const matches = (e.message || "").match(STALE_FILE_ID_EXTRACT) || [];
1920
+ const matchingRefs = refs.filter((r) => r.sourcePath && matches.includes(r.file_id));
1921
+ if (matchingRefs.length === 0) throw e;
1922
+ const cacheMod = require_cache();
1923
+ if (args.cacheFile) {
1924
+ for (const r of matchingRefs) {
1925
+ await cacheMod.evict(args.cacheFile, r.file_id);
1926
+ }
1927
+ }
1928
+ for (let i = 0; i < refs.length; i++) {
1929
+ const r = refs[i];
1930
+ if (!matchingRefs.includes(r)) continue;
1931
+ const reuploaded = await uploadFile({
1932
+ filePath: r.sourcePath,
1933
+ apiKey: args.apiKey,
1934
+ apiBase: args.apiBase,
1935
+ ttl: args.ttl || FILE_TTL_SECONDS,
1936
+ roots: [r.sourceRoot],
1937
+ cacheFile: args.cacheFile,
1938
+ fetchImpl: args.fetchImpl
1939
+ });
1940
+ refs[i] = {
1941
+ ...r,
1942
+ file_id: reuploaded.id,
1943
+ sourceCacheKey: reuploaded._cacheKey
1944
+ };
1945
+ if (!reuploaded._fromCache) ownedIds.push(reuploaded.id);
1946
+ }
1947
+ const out = await attempt(buildTurns(refs));
1948
+ return { text: out.text, output: out.output, refs, ownedIds };
1949
+ }
1950
+ }
1951
+ function validateRoots(roots) {
1952
+ const fsx = require("node:fs");
1953
+ if (!Array.isArray(roots) || roots.length === 0) {
1954
+ throw new Error("'roots' must be a non-empty array of absolute directory paths");
1955
+ }
1956
+ for (const r of roots) {
1957
+ if (typeof r !== "string" || r.length === 0) {
1958
+ throw new Error("'roots' entries must be non-empty strings");
1959
+ }
1960
+ if (!path.isAbsolute(r)) {
1961
+ throw new Error(`'roots' entry "${r}" must be an absolute path`);
1962
+ }
1963
+ let st;
1964
+ try {
1965
+ st = fsx.statSync(r);
1966
+ } catch (e) {
1967
+ throw new Error(`'roots' entry "${r}" does not exist: ${e.message}`);
1968
+ }
1969
+ if (!st.isDirectory()) {
1970
+ throw new Error(`'roots' entry "${r}" is not a directory`);
1971
+ }
1972
+ }
1973
+ }
1974
+ function resolvePathUnderRoots(p, roots, type) {
1975
+ const fsx = require("node:fs");
1976
+ const isAbs = path.isAbsolute(p);
1977
+ for (const root of roots) {
1978
+ const abs = isAbs ? p : path.join(root, p);
1979
+ let realRoot, realAbs;
1980
+ try {
1981
+ realRoot = fsx.realpathSync(root);
1982
+ } catch (_) {
1983
+ continue;
1984
+ }
1985
+ try {
1986
+ realAbs = fsx.realpathSync(abs);
1987
+ } catch (_) {
1988
+ continue;
1989
+ }
1990
+ const rel = path.relative(realRoot, realAbs);
1991
+ if (rel !== "" && (rel.startsWith("..") || path.isAbsolute(rel))) continue;
1992
+ let st;
1993
+ try {
1994
+ st = fsx.statSync(realAbs);
1995
+ } catch (_) {
1996
+ continue;
1997
+ }
1998
+ if (type === "file") {
1999
+ if (!st.isFile()) continue;
2000
+ return { root: realRoot, abs: realAbs, size: st.size };
2001
+ }
2002
+ if (type === "dir") {
2003
+ if (!st.isDirectory()) continue;
2004
+ return { root: realRoot, abs: realAbs, size: 0 };
2005
+ }
2006
+ }
2007
+ if (isAbs) {
2008
+ throw new Error(`"${p}" is outside all declared roots: ${roots.join(", ")}`);
2009
+ }
2010
+ throw new Error(`"${p}" not found in any root: ${roots.join(", ")}`);
2011
+ }
2012
+ var FILES_SCHEMA = {
2013
+ type: "array",
2014
+ description: "Optional files to attach. Each item has EXACTLY ONE of: path (local file; delivery controlled by mode = upload | inline | auto), file_id (an already-uploaded xAI file id), file_url (a public URL), or dir (recursive directory expansion; delivery controlled by mode). Optional filename overrides the stored upload name (applies only to path entries delivered via upload).",
2015
+ items: {
2016
+ type: "object",
2017
+ properties: {
2018
+ path: { type: "string", description: "Local file path; bridge attaches it (resolved against roots[] or cwd). Delivery is controlled by mode: uploaded via the xAI Files API (default) or inlined as input_text." },
2019
+ file_id: { type: "string", description: "Existing xAI file id" },
2020
+ file_url: { type: "string", description: "Public URL to a file" },
2021
+ dir: { type: "string", description: "Local directory to expand recursively (resolved against roots[] or cwd)" },
2022
+ include: { type: "array", items: { type: "string" }, description: "POSIX glob patterns to include during dir expansion. Defaults to ['**/*']." },
2023
+ exclude: { type: "array", items: { type: "string" }, description: "Additional POSIX glob patterns APPENDED to the bridge's safe defaults (.git, node_modules, .terraform, target, vendor, __pycache__, .yarn caches, *.tfstate, .env*, SSH keys, *.pem, *.key, framework build/cache dirs). To replace defaults entirely instead of appending, set excludeReset: true on the same dir entry." },
2024
+ excludeReset: { type: "boolean", description: "If true, caller's exclude REPLACES bridge defaults. If omitted or false (default), caller's exclude is APPENDED to defaults. Use only when reviewing files defaults would block (e.g., Terraform state in a security audit, or *.pem certificates)." },
2025
+ maxFiles: { type: "number", description: "Hard cap on files per dir expansion. Default 50." },
2026
+ maxBytes: { type: "number", description: "Hard cap on bytes per dir expansion. Default 134217728 (128 MB)." },
2027
+ filename: { type: "string", description: "Override stored filename for a path upload" },
2028
+ mode: { type: "string", enum: ["auto", "inline", "upload"], default: "upload", description: "How to deliver this file to Grok. 'upload' (default) uses the xAI Files API (input_file); 'inline' embeds the file content directly as input_text (best for source code so Grok reads line-by-line); 'auto' inlines when the file is probably text and <= GROK_INLINE_MAX_BYTES (default 256 KB), otherwise uploads. For {dir} entries the mode is inherited by every walked file. Must NOT be set on file_id/file_url entries (those bypass the upload path; setting mode there returns -32602)." }
2029
+ }
2030
+ }
2031
+ };
2032
+ var GROK_PROPERTIES = {
2033
+ prompt: { type: "string", description: "The delegation prompt" },
2034
+ "developer-instructions": { type: "string", description: "Expert system instructions (sent as a system message)" },
2035
+ model: { type: "string", description: "xAI model id. Defaults to GROK_DEFAULT_MODEL or grok-4.3.", default: DEFAULT_MODEL },
2036
+ reasoning_effort: { type: "string", description: "Reasoning effort (low, medium, high). 'none' omits the field.", default: DEFAULT_REASONING_EFFORT },
2037
+ timeout: { type: "number", description: "Soft timeout in ms. 1..600000. Default 180000.", default: DEFAULT_TIMEOUT_MS },
2038
+ files: FILES_SCHEMA,
2039
+ roots: { type: "array", items: { type: "string" }, description: "Optional absolute directory roots for resolving files[].path and files[].dir. Defaults to [cwd]." },
2040
+ sandbox: { type: "string", enum: ["read-only", "workspace-write"], default: "read-only", description: "Accepted for call-shape parity; ignored." },
2041
+ cwd: { type: "string", description: "Base dir for resolving relative paths. Defaults to server cwd." }
2042
+ };
2043
+ function validateFiles(files) {
2044
+ if (files === void 0) return null;
2045
+ if (!Array.isArray(files)) return "'files' must be an array when provided";
2046
+ for (const entry of files) {
2047
+ if (!isObject(entry)) return "each 'files' entry must be an object";
2048
+ const keys = ["path", "file_id", "file_url", "dir"].filter((k) => entry[k] !== void 0);
2049
+ if (keys.length !== 1) return "each 'files' entry needs exactly one of path, file_id, file_url, or dir";
2050
+ if (!isNonEmptyString(entry[keys[0]])) return `'files' entry ${keys[0]} must be a non-empty string`;
2051
+ if (entry.filename !== void 0 && !isNonEmptyString(entry.filename)) return "'files' entry filename must be a non-empty string when provided";
2052
+ if (entry.mode !== void 0) {
2053
+ if (typeof entry.mode !== "string") return "'files' entry mode must be a string when provided";
2054
+ if (!["auto", "inline", "upload"].includes(entry.mode)) return `'files' entry mode "${entry.mode}" must be one of: auto, inline, upload`;
2055
+ if (entry.file_id !== void 0 || entry.file_url !== void 0) return "'files' entry mode applies only to path/dir entries (not file_id/file_url)";
2056
+ }
2057
+ if (entry.excludeReset !== void 0 && typeof entry.excludeReset !== "boolean") {
2058
+ return "'excludeReset' must be a boolean";
2059
+ }
2060
+ if (entry.dir !== void 0) {
2061
+ for (const list of [entry.include, entry.exclude]) {
2062
+ if (list === void 0) continue;
2063
+ if (!Array.isArray(list)) return "'files' entry include/exclude must be arrays";
2064
+ for (const p of list) {
2065
+ if (typeof p !== "string" || p.length === 0) return "include/exclude patterns must be non-empty strings";
2066
+ if (p.includes("\\")) return `glob pattern "${p}" contains backslashes; v1 patterns are POSIX-only (use /)`;
2067
+ }
2068
+ }
2069
+ if (entry.maxFiles !== void 0 && (typeof entry.maxFiles !== "number" || entry.maxFiles <= 0)) return "'maxFiles' must be a positive number";
2070
+ if (entry.maxBytes !== void 0 && (typeof entry.maxBytes !== "number" || entry.maxBytes <= 0)) return "'maxBytes' must be a positive number";
2071
+ }
2072
+ }
2073
+ return null;
2074
+ }
2075
+ var handlers = {
2076
+ "initialize": (id, _params, shouldRespond) => {
2077
+ if (!shouldRespond) return;
2078
+ sendResponse(id, {
2079
+ protocolVersion: "2024-11-05",
2080
+ capabilities: { tools: {} },
2081
+ serverInfo: { name: "deliberation-grok", version: "1.8.0" }
2082
+ });
2083
+ },
2084
+ "tools/list": (id, _params, shouldRespond) => {
2085
+ if (!shouldRespond) return;
2086
+ sendResponse(id, {
2087
+ tools: [
2088
+ {
2089
+ name: "grok",
2090
+ description: "Start a new Grok (xAI) expert session. Advisory only (no filesystem editing). Supports attaching files.",
2091
+ inputSchema: { type: "object", properties: GROK_PROPERTIES, required: ["prompt"] }
2092
+ },
2093
+ {
2094
+ name: "grok-reply",
2095
+ description: "Continue an existing Grok session (in-memory; lost if the MCP server restarts).",
2096
+ inputSchema: {
2097
+ type: "object",
2098
+ properties: {
2099
+ threadId: { type: "string", description: "Session ID returned by a previous grok call" },
2100
+ prompt: { type: "string", description: "Follow-up prompt" },
2101
+ files: FILES_SCHEMA,
2102
+ roots: { type: "array", items: { type: "string" }, description: "Optional absolute directory roots for resolving files[].path and files[].dir." },
2103
+ model: { type: "string", default: DEFAULT_MODEL },
2104
+ reasoning_effort: { type: "string", default: DEFAULT_REASONING_EFFORT, description: "Reasoning effort; defaults to GROK_REASONING_EFFORT or high. Use 'none' to omit." },
2105
+ timeout: { type: "number", default: DEFAULT_TIMEOUT_MS },
2106
+ cwd: { type: "string", description: "Base directory for relative file path uploads" }
2107
+ },
2108
+ required: ["threadId", "prompt"]
2109
+ }
2110
+ }
2111
+ ]
2112
+ });
2113
+ },
2114
+ "tools/call": async (id, params, shouldRespond) => {
2115
+ if (!isObject(params)) {
2116
+ if (shouldRespond) sendError(id, -32602, "Invalid params: expected an object");
2117
+ return;
2118
+ }
2119
+ const { name, arguments: args } = params;
2120
+ if (!isNonEmptyString(name)) {
2121
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'name' must be a non-empty string");
2122
+ return;
2123
+ }
2124
+ if (!isObject(args)) {
2125
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'arguments' must be an object");
2126
+ return;
2127
+ }
2128
+ if (args.sandbox !== void 0 && !VALID_SANDBOX_VALUES.has(args.sandbox)) {
2129
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'sandbox' must be 'read-only' or 'workspace-write'");
2130
+ return;
2131
+ }
2132
+ if (args.cwd !== void 0 && !isNonEmptyString(args.cwd)) {
2133
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'cwd' must be a non-empty string when provided");
2134
+ return;
2135
+ }
2136
+ if (args.model !== void 0 && !isNonEmptyString(args.model)) {
2137
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'model' must be a non-empty string when provided");
2138
+ return;
2139
+ }
2140
+ if (args.reasoning_effort !== void 0 && typeof args.reasoning_effort !== "string") {
2141
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'reasoning_effort' must be a string when provided");
2142
+ return;
2143
+ }
2144
+ if (args.timeout !== void 0) {
2145
+ if (typeof args.timeout !== "number" || !Number.isFinite(args.timeout) || args.timeout <= 0 || args.timeout > MAX_MS) {
2146
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'timeout' must be a number > 0 and <= 600000 milliseconds");
2147
+ return;
2148
+ }
2149
+ }
2150
+ const filesErr = validateFiles(args.files);
2151
+ if (filesErr) {
2152
+ if (shouldRespond) sendError(id, -32602, `Invalid params: ${filesErr}`);
2153
+ return;
2154
+ }
2155
+ if (!isNonEmptyString(args.prompt)) {
2156
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'prompt' is required");
2157
+ return;
2158
+ }
2159
+ let priorTurns = null;
2160
+ let threadId;
2161
+ if (name === "grok") {
2162
+ if (args["developer-instructions"] !== void 0 && typeof args["developer-instructions"] !== "string") {
2163
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'developer-instructions' must be a string when provided");
2164
+ return;
2165
+ }
2166
+ threadId = crypto.randomUUID();
2167
+ } else if (name === "grok-reply") {
2168
+ if (!isNonEmptyString(args.threadId)) {
2169
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'threadId' is required for grok-reply");
2170
+ return;
2171
+ }
2172
+ threadId = args.threadId.trim();
2173
+ priorTurns = sessions.get(threadId);
2174
+ if (!priorTurns) {
2175
+ const { errorKind, retryable } = classifyGrokError(null, "unknown-thread");
2176
+ logCall(id != null ? id : threadId, "grok-reply", errorKind, 0);
2177
+ if (shouldRespond) {
2178
+ sendResponse(id, {
2179
+ content: [{ type: "text", text: `Error: unknown threadId "${threadId}". Start a fresh grok call (in-memory sessions do not survive an MCP restart).` }],
2180
+ isError: true,
2181
+ errorKind,
2182
+ retryable
2183
+ });
2184
+ }
2185
+ return;
2186
+ }
2187
+ } else {
2188
+ if (shouldRespond) sendError(id, -32601, `Tool not found: ${name}`);
2189
+ return;
2190
+ }
2191
+ const startedAt = Date.now();
2192
+ let outcome = "ok";
2193
+ try {
2194
+ let rootList;
2195
+ if (Array.isArray(args.roots) && args.roots.length) {
2196
+ try {
2197
+ validateRoots(args.roots);
2198
+ } catch (e) {
2199
+ if (shouldRespond) sendError(id, -32602, `Invalid params: ${e.message}`);
2200
+ return;
2201
+ }
2202
+ rootList = args.roots;
2203
+ } else {
2204
+ rootList = [require("node:fs").realpathSync(args.cwd || process.cwd())];
2205
+ }
2206
+ const out = await runWithFiles({
2207
+ prompt: args.prompt,
2208
+ "developer-instructions": args["developer-instructions"],
2209
+ files: args.files,
2210
+ priorTurns: priorTurns || null,
2211
+ apiKey: process.env.XAI_API_KEY,
2212
+ apiBase: DEFAULT_API_BASE,
2213
+ ttl: FILE_TTL_SECONDS,
2214
+ roots: rootList,
2215
+ cwd: args.cwd,
2216
+ cacheFile: process.env.XAI_DISABLE_FILE_CACHE ? null : DEFAULT_CACHE_FILE,
2217
+ model: args.model,
2218
+ reasoningEffort: resolveReasoningEffort(args.reasoning_effort),
2219
+ timeout: args.timeout,
2220
+ cid: id
2221
+ });
2222
+ const turnsForPersist = priorTurns ? [...priorTurns, { role: "user", text: args.prompt, fileRefs: out.refs }] : buildInitialTurns(args["developer-instructions"], args.prompt, out.refs);
2223
+ sessions.set(threadId, [...turnsForPersist, { role: "assistant", text: out.text, items: out.output || void 0 }]);
2224
+ if (shouldRespond) {
2225
+ const result = { content: [{ type: "text", text: out.text }], threadId };
2226
+ if (out.ownedIds.length) result.uploadedFileIds = out.ownedIds;
2227
+ sendResponse(id, result);
2228
+ }
2229
+ } catch (e) {
2230
+ const errMsg = e && e.message || String(e);
2231
+ const { errorKind, retryable } = classifyGrokError(e && e.status, e && e.code);
2232
+ outcome = errorKind;
2233
+ if (shouldRespond) {
2234
+ sendResponse(id, {
2235
+ content: [{ type: "text", text: `Error: ${errMsg}` }],
2236
+ isError: true,
2237
+ errorKind,
2238
+ retryable
2239
+ });
2240
+ }
2241
+ } finally {
2242
+ const cid = id != null ? id : threadId != null ? threadId : "-";
2243
+ const toolName = isNonEmptyString(name) ? name : "unknown";
2244
+ logCall(cid, toolName, outcome, Date.now() - startedAt);
2245
+ }
2246
+ },
2247
+ "notifications/initialized": () => {
2248
+ }
2249
+ };
2250
+ async function processLine(line) {
2251
+ if (!line.trim()) return;
2252
+ let request;
2253
+ try {
2254
+ request = JSON.parse(line);
2255
+ } catch (e) {
2256
+ return;
2257
+ }
2258
+ const shouldRespond = hasRequestId(request);
2259
+ if (!isObject(request) || typeof request.method !== "string") {
2260
+ if (shouldRespond) sendError(request.id, -32600, "Invalid Request");
2261
+ return;
2262
+ }
2263
+ const handler = handlers[request.method];
2264
+ if (!handler) {
2265
+ if (shouldRespond) sendError(request.id, -32601, `Method not found: ${request.method}`);
2266
+ return;
2267
+ }
2268
+ try {
2269
+ await handler(request.id, request.params, shouldRespond);
2270
+ } catch (e) {
2271
+ if (shouldRespond) sendError(request.id, -32603, `Internal error: ${e.message}`);
2272
+ }
2273
+ }
2274
+ if (require.main === module2) {
2275
+ let buffer = "";
2276
+ const enqueue = (line) => {
2277
+ void processLine(line);
2278
+ };
2279
+ process.stdin.on("data", (chunk) => {
2280
+ buffer += chunk.toString();
2281
+ const lines = buffer.split("\n");
2282
+ buffer = lines.pop();
2283
+ for (const line of lines) enqueue(line);
2284
+ });
2285
+ process.stdin.on("end", () => {
2286
+ if (buffer) {
2287
+ enqueue(buffer);
2288
+ buffer = "";
2289
+ }
2290
+ });
2291
+ if (typeof globalThis.fetch !== "function") {
2292
+ console.error("Grok bridge requires Node 18+ (global fetch unavailable).");
2293
+ process.exit(1);
2294
+ }
2295
+ if (!isNonEmptyString(process.env.XAI_API_KEY)) {
2296
+ console.error("[deliberation] warning: XAI_API_KEY is not set; grok calls will return errorKind:missing-auth until it is.");
2297
+ }
2298
+ }
2299
+ if (typeof module2 !== "undefined" && module2.exports) {
2300
+ module2.exports.classifyGrokError = classifyGrokError;
2301
+ module2.exports.resolveReasoningEffort = resolveReasoningEffort;
2302
+ module2.exports.buildInitialTurns = buildInitialTurns;
2303
+ module2.exports.turnsToInput = turnsToInput;
2304
+ module2.exports.parseResponsesOutput = parseResponsesOutput;
2305
+ module2.exports.runGrok = runGrok;
2306
+ module2.exports.runWithFiles = runWithFiles;
2307
+ module2.exports.uploadFile = uploadFile;
2308
+ module2.exports.resolveFiles = resolveFiles;
2309
+ module2.exports.validateFiles = validateFiles;
2310
+ module2.exports.FILE_PREFIX = FILE_PREFIX;
2311
+ module2.exports.FILE_TTL_SECONDS = FILE_TTL_SECONDS;
2312
+ }
2313
+ module2.exports.validateRoots = validateRoots;
2314
+ module2.exports.resolvePathUnderRoots = resolvePathUnderRoots;
2315
+ }
2316
+ });
2317
+
2318
+ // ../../core/providers/grok.js
2319
+ var require_grok2 = __commonJS({
2320
+ "../../core/providers/grok.js"(exports2, module2) {
2321
+ "use strict";
2322
+ var { toErrorResult } = require_provider();
2323
+ function makeGrokProvider(opts = {}) {
2324
+ const bridge = (
2325
+ /** @type {any} */
2326
+ opts.bridge || require_grok()
2327
+ );
2328
+ const model = opts.model || process.env.GROK_DEFAULT_MODEL || "grok-4.3";
2329
+ const apiBase = opts.apiBase || process.env.XAI_API_BASE || "https://api.x.ai/v1";
2330
+ return {
2331
+ name: "grok",
2332
+ // multiTurn is not wired through Core (runGrok/runWithFiles return no threadId),
2333
+ // so report false to match reality.
2334
+ capabilities: { canImplement: false, fileUpload: true, multiTurn: false },
2335
+ async health() {
2336
+ return process.env.XAI_API_KEY ? { ok: true } : { ok: false, reason: "XAI_API_KEY unset" };
2337
+ },
2338
+ async ask(req) {
2339
+ const started = Date.now();
2340
+ const reasoningEffort = bridge.resolveReasoningEffort(req.reasoningEffort);
2341
+ const apiKey = process.env.XAI_API_KEY;
2342
+ try {
2343
+ const out = req.files && req.files.length ? await bridge.runWithFiles({
2344
+ files: req.files,
2345
+ prompt: req.prompt,
2346
+ "developer-instructions": req.developerInstructions,
2347
+ apiKey,
2348
+ apiBase,
2349
+ model,
2350
+ reasoningEffort,
2351
+ timeout: req.timeoutMs,
2352
+ cwd: req.cwd
2353
+ }) : await bridge.runGrok({
2354
+ turns: bridge.buildInitialTurns(req.developerInstructions, req.prompt, []),
2355
+ model,
2356
+ apiKey,
2357
+ apiBase,
2358
+ reasoningEffort,
2359
+ timeoutMs: req.timeoutMs
2360
+ });
2361
+ return { provider: "grok", model, text: out.text || "", isError: false, ms: Date.now() - started };
2362
+ } catch (e) {
2363
+ return toErrorResult(
2364
+ "grok",
2365
+ model,
2366
+ started,
2367
+ /** @type {any} */
2368
+ e,
2369
+ bridge.classifyGrokError
2370
+ );
2371
+ }
2372
+ }
2373
+ };
2374
+ }
2375
+ module2.exports = { makeGrokProvider };
2376
+ }
2377
+ });
2378
+
2379
+ // ../gemini/index.js
2380
+ var require_gemini = __commonJS({
2381
+ "../gemini/index.js"(exports2, module2) {
2382
+ "use strict";
2383
+ var { spawn, execFileSync } = require("node:child_process");
2384
+ var fs = require("node:fs");
2385
+ var os = require("node:os");
2386
+ var path = require("node:path");
2387
+ var AGY_BIN = process.env.AGY_BIN || "agy";
2388
+ var DEFAULT_MODEL = process.env.GEMINI_DEFAULT_MODEL || "auto-gemini-3";
2389
+ var DEFAULT_TIMEOUT_MS = 3e5;
2390
+ var DEFAULT_RECOVERY_GRACE_MS = 12e4;
2391
+ var MAX_MS = 6e5;
2392
+ var VALID_SANDBOX_VALUES = /* @__PURE__ */ new Set(["read-only", "workspace-write"]);
2393
+ function goDuration(ms) {
2394
+ return Math.ceil(ms / 1e3) + "s";
2395
+ }
2396
+ function buildAgyArgs(req) {
2397
+ const args = [];
2398
+ if (req.sandbox === "workspace-write") args.push("--dangerously-skip-permissions");
2399
+ else args.push("--sandbox");
2400
+ for (const d of req.includeDirs || []) args.push("--add-dir", d);
2401
+ let prompt = req.prompt;
2402
+ if (req.developerInstructions) prompt = `${req.developerInstructions}
2403
+
2404
+ ${prompt}`;
2405
+ args.push("-p", prompt);
2406
+ return args;
2407
+ }
2408
+ function sendResponse(id, result) {
2409
+ process.stdout.write(JSON.stringify({
2410
+ jsonrpc: "2.0",
2411
+ id,
2412
+ result
2413
+ }) + "\n");
2414
+ }
2415
+ function sendError(id, code, message) {
2416
+ process.stdout.write(JSON.stringify({
2417
+ jsonrpc: "2.0",
2418
+ id,
2419
+ error: { code, message }
2420
+ }) + "\n");
2421
+ }
2422
+ function isObject(value) {
2423
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2424
+ }
2425
+ function hasRequestId(request) {
2426
+ return isObject(request) && Object.prototype.hasOwnProperty.call(request, "id");
2427
+ }
2428
+ function isNonEmptyString(value) {
2429
+ return typeof value === "string" && value.trim().length > 0;
2430
+ }
2431
+ function stdoutIsError(s) {
2432
+ return /^\s*Error:\s/m.test(s);
2433
+ }
2434
+ function classifyGeminiError(errMsg, errCode) {
2435
+ const msg = String(errMsg || "");
2436
+ const lower = msg.toLowerCase();
2437
+ if (errCode === "timeout") return { errorKind: "timeout", retryable: true };
2438
+ if (errCode === "parse") return { errorKind: "parse", retryable: false };
2439
+ if (msg.includes("(agy) not found")) return { errorKind: "missing-cli", retryable: false };
2440
+ if (lower.includes("aborterror") || lower.includes("aborted")) {
2441
+ return { errorKind: "upstream-abort", retryable: true };
2442
+ }
2443
+ return { errorKind: "unknown", retryable: false };
2444
+ }
2445
+ function resolveConversationId(cwd) {
2446
+ try {
2447
+ const mapPath = process.env.AGY_LAST_CONVERSATIONS || path.join(os.homedir(), ".gemini", "antigravity-cli", "cache", "last_conversations.json");
2448
+ const map = JSON.parse(fs.readFileSync(mapPath, "utf8"));
2449
+ if (!map || typeof map !== "object") return null;
2450
+ const resolved = path.resolve(cwd);
2451
+ let real = resolved;
2452
+ try {
2453
+ real = fs.realpathSync(resolved);
2454
+ } catch (_) {
2455
+ }
2456
+ return map[real] ?? map[resolved] ?? map[cwd] ?? null;
2457
+ } catch (_) {
2458
+ return null;
2459
+ }
2460
+ }
2461
+ async function runGemini(args, cwd, timeoutMs, recoveryGraceMs) {
2462
+ return new Promise((resolve, reject) => {
2463
+ const t = typeof timeoutMs === "number" && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
2464
+ const effCwd = cwd || process.cwd();
2465
+ const spawnStartMs = Date.now();
2466
+ const disableRecovery = process.env.GEMINI_DISABLE_TIMEOUT_RECOVERY === "1";
2467
+ const grace = disableRecovery ? 0 : typeof recoveryGraceMs === "number" && recoveryGraceMs >= 0 ? recoveryGraceMs : DEFAULT_RECOVERY_GRACE_MS;
2468
+ let killed = false;
2469
+ let draining = false;
2470
+ let settled = false;
2471
+ let graceTimer = null;
2472
+ const ptIdx = args.lastIndexOf("-p");
2473
+ const head = ptIdx >= 0 ? args.slice(0, ptIdx) : args;
2474
+ const tail = ptIdx >= 0 ? args.slice(ptIdx) : [];
2475
+ const agyArgs = [...head, "--print-timeout", goDuration(t + grace + 3e4), ...tail];
2476
+ const agyProcess = spawn(AGY_BIN, agyArgs, {
2477
+ env: process.env,
2478
+ shell: false,
2479
+ cwd: effCwd,
2480
+ // agy -p (print mode) waits for stdin EOF before returning; if the stdin
2481
+ // pipe is left open it hangs until the timeout. Give it /dev/null so it
2482
+ // sees EOF immediately and runs to completion.
2483
+ stdio: ["ignore", "pipe", "pipe"]
2484
+ });
2485
+ function clearTimers() {
2486
+ clearTimeout(killTimer);
2487
+ if (graceTimer) clearTimeout(graceTimer);
2488
+ }
2489
+ function destroyStreams() {
2490
+ try {
2491
+ agyProcess.stdout.destroy();
2492
+ } catch (_) {
2493
+ }
2494
+ try {
2495
+ agyProcess.stderr.destroy();
2496
+ } catch (_) {
2497
+ }
2498
+ }
2499
+ function timeoutError() {
2500
+ const tail2 = stderr && stderr.trim() ? "; last agy stderr: " + stderr.trim().slice(-500) : "";
2501
+ const err = new Error("Gemini (agy) timed out after " + Math.round(t / 1e3) + "s" + tail2);
2502
+ err.code = "timeout";
2503
+ return err;
2504
+ }
2505
+ function finishTimeout() {
2506
+ if (settled) return;
2507
+ settled = true;
2508
+ clearTimers();
2509
+ try {
2510
+ agyProcess.kill("SIGTERM");
2511
+ } catch (_) {
2512
+ }
2513
+ setTimeout(() => {
2514
+ try {
2515
+ agyProcess.kill("SIGKILL");
2516
+ } catch (_) {
2517
+ }
2518
+ }, 1e3);
2519
+ destroyStreams();
2520
+ reject(timeoutError());
2521
+ }
2522
+ const killTimer = setTimeout(() => {
2523
+ if (settled) return;
2524
+ if (grace > 0) {
2525
+ draining = true;
2526
+ graceTimer = setTimeout(() => finishTimeout(), grace);
2527
+ return;
2528
+ }
2529
+ killed = true;
2530
+ try {
2531
+ agyProcess.kill("SIGTERM");
2532
+ } catch (_) {
2533
+ }
2534
+ graceTimer = setTimeout(() => {
2535
+ try {
2536
+ agyProcess.kill("SIGKILL");
2537
+ } catch (_) {
2538
+ }
2539
+ }, 1e3);
2540
+ }, t);
2541
+ agyProcess.on("close", clearTimers);
2542
+ agyProcess.on("error", clearTimers);
2543
+ agyProcess.on("exit", () => {
2544
+ if (killed && !settled) {
2545
+ settled = true;
2546
+ clearTimers();
2547
+ destroyStreams();
2548
+ reject(timeoutError());
2549
+ }
2550
+ });
2551
+ let stdout = "";
2552
+ let stderr = "";
2553
+ agyProcess.on("error", (err) => {
2554
+ if (settled) return;
2555
+ settled = true;
2556
+ if (err.code === "ENOENT") {
2557
+ reject(new Error("Antigravity CLI (agy) not found. Install from https://antigravity.google and run `agy` once to sign in."));
2558
+ } else {
2559
+ reject(err);
2560
+ }
2561
+ });
2562
+ agyProcess.stdout.on("data", (data) => {
2563
+ stdout += data.toString();
2564
+ });
2565
+ agyProcess.stderr.on("data", (data) => {
2566
+ stderr += data.toString();
2567
+ });
2568
+ agyProcess.on("close", (code) => {
2569
+ if (settled) return;
2570
+ if (killed) {
2571
+ settled = true;
2572
+ clearTimers();
2573
+ return reject(timeoutError());
2574
+ }
2575
+ const out = stdout.trim();
2576
+ const trimmedErr = stderr.trim();
2577
+ const success = code === 0 && out && !stdoutIsError(out);
2578
+ if (draining) {
2579
+ if (success) {
2580
+ settled = true;
2581
+ clearTimers();
2582
+ process.stderr.write(
2583
+ "[deliberation] recovered agy answer via stdout drain after soft timeout (" + Math.round((Date.now() - spawnStartMs) / 1e3) + "s)\n"
2584
+ );
2585
+ return resolve({ response: out, threadId: resolveConversationId(effCwd) || "unknown", recovered: true });
2586
+ }
2587
+ return finishTimeout();
2588
+ }
2589
+ settled = true;
2590
+ clearTimers();
2591
+ if (success) {
2592
+ const threadId = resolveConversationId(effCwd);
2593
+ if (threadId == null) {
2594
+ process.stderr.write(
2595
+ "[deliberation] no conversation id found for cwd " + effCwd + '; returning threadId:"unknown" (resume will be unavailable)\n'
2596
+ );
2597
+ }
2598
+ return resolve({ response: out, threadId: threadId || "unknown" });
2599
+ }
2600
+ let message;
2601
+ if (trimmedErr) message = trimmedErr;
2602
+ else if (stdoutIsError(out)) message = out;
2603
+ else if (!out) message = `No output from agy`;
2604
+ else message = `agy exited with code ${code}`;
2605
+ const err = new Error(message);
2606
+ if (/timed out/i.test(message)) {
2607
+ err.code = "timeout";
2608
+ } else if (!trimmedErr && !out) {
2609
+ err.code = "parse";
2610
+ }
2611
+ reject(err);
2612
+ });
2613
+ });
2614
+ }
2615
+ var handlers = {
2616
+ "initialize": (id, _params, shouldRespond) => {
2617
+ if (!shouldRespond) return;
2618
+ sendResponse(id, {
2619
+ protocolVersion: "2024-11-05",
2620
+ capabilities: { tools: {} },
2621
+ serverInfo: { name: "deliberation-gemini", version: "1.6.0" }
2622
+ });
2623
+ },
2624
+ "tools/list": (id, _params, shouldRespond) => {
2625
+ if (!shouldRespond) return;
2626
+ sendResponse(id, {
2627
+ tools: [
2628
+ {
2629
+ name: "gemini",
2630
+ description: "Start a new Gemini expert session",
2631
+ inputSchema: {
2632
+ type: "object",
2633
+ properties: {
2634
+ prompt: { type: "string", description: "The delegation prompt" },
2635
+ "developer-instructions": { type: "string", description: "Expert system instructions" },
2636
+ sandbox: { type: "string", enum: ["read-only", "workspace-write"], default: "read-only" },
2637
+ cwd: { type: "string", description: "Current working directory" },
2638
+ model: { type: "string", default: DEFAULT_MODEL, description: "Advisory only; agy reads the model from ~/.gemini/settings.json (default auto-gemini-3)." },
2639
+ "include-directories": {
2640
+ type: "array",
2641
+ items: { type: "string" },
2642
+ description: "Additional workspace dirs; maps to repeated --add-dir on the Antigravity CLI (agy)."
2643
+ },
2644
+ timeout: { type: "number", description: "Soft timeout in ms. 1..600000. Default 300000. On expiry the bridge drains the streamed stdout and recovers a late answer instead of failing.", default: DEFAULT_TIMEOUT_MS },
2645
+ "recovery-grace": { type: "number", description: "Extra ms to keep agy alive after the soft timeout to drain a late answer from streamed stdout. 0..600000. Default 120000. 0 disables drain.", default: DEFAULT_RECOVERY_GRACE_MS }
2646
+ },
2647
+ required: ["prompt"]
2648
+ }
2649
+ },
2650
+ {
2651
+ name: "gemini-reply",
2652
+ description: "Continue an existing Gemini session",
2653
+ inputSchema: {
2654
+ type: "object",
2655
+ properties: {
2656
+ threadId: { type: "string", description: "Conversation ID returned by a previous gemini call" },
2657
+ prompt: { type: "string", description: "Follow-up prompt" },
2658
+ sandbox: { type: "string", enum: ["read-only", "workspace-write"], default: "read-only" },
2659
+ cwd: { type: "string" },
2660
+ "include-directories": {
2661
+ type: "array",
2662
+ items: { type: "string" },
2663
+ description: "Additional workspace dirs; maps to repeated --add-dir on the Antigravity CLI (agy)."
2664
+ },
2665
+ timeout: { type: "number", description: "Soft timeout in ms. 1..600000. Default 300000. On expiry the bridge drains the streamed stdout and recovers a late answer instead of failing.", default: DEFAULT_TIMEOUT_MS },
2666
+ "recovery-grace": { type: "number", description: "Extra ms to keep agy alive after the soft timeout to drain a late answer from streamed stdout. 0..600000. Default 120000. 0 disables drain.", default: DEFAULT_RECOVERY_GRACE_MS }
2667
+ },
2668
+ required: ["threadId", "prompt"]
2669
+ }
2670
+ }
2671
+ ]
2672
+ });
2673
+ },
2674
+ "tools/call": async (id, params, shouldRespond) => {
2675
+ if (!isObject(params)) {
2676
+ if (shouldRespond) sendError(id, -32602, "Invalid params: expected an object");
2677
+ return;
2678
+ }
2679
+ const { name, arguments: args } = params;
2680
+ if (!isNonEmptyString(name)) {
2681
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'name' must be a non-empty string");
2682
+ return;
2683
+ }
2684
+ if (!isObject(args)) {
2685
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'arguments' must be an object");
2686
+ return;
2687
+ }
2688
+ if (args.sandbox !== void 0 && !VALID_SANDBOX_VALUES.has(args.sandbox)) {
2689
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'sandbox' must be 'read-only' or 'workspace-write'");
2690
+ return;
2691
+ }
2692
+ if (args.cwd !== void 0 && !isNonEmptyString(args.cwd)) {
2693
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'cwd' must be a non-empty string when provided");
2694
+ return;
2695
+ }
2696
+ if (args.timeout !== void 0) {
2697
+ if (typeof args.timeout !== "number" || !Number.isFinite(args.timeout) || args.timeout <= 0 || args.timeout > MAX_MS) {
2698
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'timeout' must be a number > 0 and <= 600000 milliseconds");
2699
+ return;
2700
+ }
2701
+ }
2702
+ if (args["recovery-grace"] !== void 0) {
2703
+ const g = args["recovery-grace"];
2704
+ if (typeof g !== "number" || !Number.isFinite(g) || g < 0 || g > MAX_MS) {
2705
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'recovery-grace' must be a number >= 0 and <= 600000 milliseconds");
2706
+ return;
2707
+ }
2708
+ }
2709
+ if (args["include-directories"] !== void 0) {
2710
+ if (!Array.isArray(args["include-directories"]) || args["include-directories"].length === 0) {
2711
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'include-directories' must be a non-empty array of strings when provided");
2712
+ return;
2713
+ }
2714
+ for (const dir of args["include-directories"]) {
2715
+ if (!isNonEmptyString(dir)) {
2716
+ if (shouldRespond) sendError(id, -32602, "Invalid params: each entry in 'include-directories' must be a non-empty string");
2717
+ return;
2718
+ }
2719
+ }
2720
+ }
2721
+ try {
2722
+ const agyArgs = [];
2723
+ const sandboxFlags = [];
2724
+ if (args.sandbox === "workspace-write") sandboxFlags.push("--dangerously-skip-permissions");
2725
+ else sandboxFlags.push("--sandbox");
2726
+ const addDirFlags = [];
2727
+ if (args["include-directories"]) {
2728
+ for (const dir of args["include-directories"]) addDirFlags.push("--add-dir", dir);
2729
+ }
2730
+ if (name === "gemini") {
2731
+ if (args.model !== void 0 && !isNonEmptyString(args.model)) {
2732
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'model' must be a non-empty string when provided");
2733
+ return;
2734
+ }
2735
+ if (!isNonEmptyString(args.prompt)) {
2736
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'prompt' is required");
2737
+ return;
2738
+ }
2739
+ if (args["developer-instructions"] !== void 0 && typeof args["developer-instructions"] !== "string") {
2740
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'developer-instructions' must be a string when provided");
2741
+ return;
2742
+ }
2743
+ agyArgs.push(...buildAgyArgs({
2744
+ prompt: args.prompt,
2745
+ model: args.model,
2746
+ includeDirs: args["include-directories"],
2747
+ sandbox: args.sandbox,
2748
+ developerInstructions: args["developer-instructions"]
2749
+ }));
2750
+ } else if (name === "gemini-reply") {
2751
+ if (!isNonEmptyString(args.threadId)) {
2752
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'threadId' is required for gemini-reply");
2753
+ return;
2754
+ }
2755
+ const threadId2 = args.threadId.trim();
2756
+ if (threadId2 === "" || threadId2 === "latest" || threadId2 === "unknown") {
2757
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'threadId' must be an explicit conversation id, not '" + threadId2 + "'");
2758
+ return;
2759
+ }
2760
+ if (!isNonEmptyString(args.prompt)) {
2761
+ if (shouldRespond) sendError(id, -32602, "Invalid params: 'prompt' is required");
2762
+ return;
2763
+ }
2764
+ agyArgs.push("--conversation", threadId2, ...sandboxFlags, ...addDirFlags);
2765
+ agyArgs.push("-p", args.prompt);
2766
+ } else {
2767
+ if (shouldRespond) sendError(id, -32601, `Tool not found: ${name}`);
2768
+ return;
2769
+ }
2770
+ const timeoutMs = typeof args.timeout === "number" && args.timeout > 0 ? args.timeout : DEFAULT_TIMEOUT_MS;
2771
+ const recoveryGraceMs = typeof args["recovery-grace"] === "number" && args["recovery-grace"] >= 0 ? args["recovery-grace"] : DEFAULT_RECOVERY_GRACE_MS;
2772
+ const { response, threadId, recovered } = await runGemini(agyArgs, args.cwd, timeoutMs, recoveryGraceMs);
2773
+ if (shouldRespond) {
2774
+ sendResponse(id, {
2775
+ content: [{ type: "text", text: response }],
2776
+ threadId,
2777
+ ...recovered ? { recovered: true } : {}
2778
+ });
2779
+ }
2780
+ } catch (e) {
2781
+ const errMsg = e && e.message || String(e);
2782
+ const errCode = e && e.code;
2783
+ const { errorKind, retryable } = classifyGeminiError(errMsg, errCode);
2784
+ if (shouldRespond) {
2785
+ sendResponse(id, {
2786
+ content: [{ type: "text", text: `Error: ${errMsg}` }],
2787
+ isError: true,
2788
+ errorKind,
2789
+ retryable
2790
+ });
2791
+ }
2792
+ }
2793
+ },
2794
+ "notifications/initialized": () => {
2795
+ }
2796
+ };
2797
+ var buffer = "";
2798
+ async function processLine(line) {
2799
+ if (!line.trim()) return;
2800
+ let request;
2801
+ try {
2802
+ request = JSON.parse(line);
2803
+ } catch (e) {
2804
+ return;
2805
+ }
2806
+ const shouldRespond = hasRequestId(request);
2807
+ if (!isObject(request) || typeof request.method !== "string") {
2808
+ if (shouldRespond) sendError(request.id, -32600, "Invalid Request");
2809
+ return;
2810
+ }
2811
+ const handler = handlers[request.method];
2812
+ if (!handler) {
2813
+ if (shouldRespond) sendError(request.id, -32601, `Method not found: ${request.method}`);
2814
+ return;
2815
+ }
2816
+ try {
2817
+ await handler(request.id, request.params, shouldRespond);
2818
+ } catch (e) {
2819
+ if (shouldRespond) sendError(request.id, -32603, `Internal error: ${e.message}`);
2820
+ }
2821
+ }
2822
+ if (require.main === module2) {
2823
+ process.stdin.on("data", (chunk) => {
2824
+ buffer += chunk.toString();
2825
+ const lines = buffer.split("\n");
2826
+ buffer = lines.pop();
2827
+ for (const line of lines) void processLine(line);
2828
+ });
2829
+ try {
2830
+ execFileSync(AGY_BIN, ["--help"], { stdio: "ignore" });
2831
+ } catch (e) {
2832
+ console.error("Antigravity CLI (agy) not found. Install from https://antigravity.google and run `agy` once to sign in.");
2833
+ process.exit(1);
2834
+ }
2835
+ }
2836
+ if (typeof module2 !== "undefined" && module2.exports) {
2837
+ module2.exports.classifyGeminiError = classifyGeminiError;
2838
+ module2.exports.resolveConversationId = resolveConversationId;
2839
+ module2.exports.goDuration = goDuration;
2840
+ module2.exports.stdoutIsError = stdoutIsError;
2841
+ module2.exports.runGemini = runGemini;
2842
+ module2.exports.buildAgyArgs = buildAgyArgs;
2843
+ }
2844
+ }
2845
+ });
2846
+
2847
+ // ../../core/providers/antigravity.js
2848
+ var require_antigravity = __commonJS({
2849
+ "../../core/providers/antigravity.js"(exports2, module2) {
2850
+ "use strict";
2851
+ var { toErrorResult } = require_provider();
2852
+ function makeAntigravityProvider(opts = {}) {
2853
+ const bridge = (
2854
+ /** @type {any} */
2855
+ opts.bridge || require_gemini()
2856
+ );
2857
+ const model = opts.model || process.env.GEMINI_DEFAULT_MODEL || "auto-gemini-3";
2858
+ return {
2859
+ name: "gemini",
2860
+ capabilities: { canImplement: true, fileUpload: false, multiTurn: true },
2861
+ async health() {
2862
+ return typeof bridge.runGemini === "function" ? { ok: true } : { ok: false, reason: "agy bridge unavailable" };
2863
+ },
2864
+ async ask(req) {
2865
+ const started = Date.now();
2866
+ const args = bridge.buildAgyArgs({
2867
+ prompt: req.prompt,
2868
+ model,
2869
+ sandbox: "read-only",
2870
+ developerInstructions: req.developerInstructions,
2871
+ includeDirs: (req.files || []).filter((f) => f.dir).map((f) => f.dir)
2872
+ });
2873
+ try {
2874
+ const out = await bridge.runGemini(args, req.cwd, req.timeoutMs, void 0);
2875
+ return { provider: "gemini", model, text: out.response || "", threadId: out.threadId, isError: false, ms: Date.now() - started };
2876
+ } catch (e) {
2877
+ const err = (
2878
+ /** @type {any} */
2879
+ e
2880
+ );
2881
+ return toErrorResult(
2882
+ "gemini",
2883
+ model,
2884
+ started,
2885
+ err,
2886
+ (_status, code) => bridge.classifyGeminiError(err && err.message || "", code)
2887
+ );
2888
+ }
2889
+ }
2890
+ };
2891
+ }
2892
+ module2.exports = { makeAntigravityProvider };
2893
+ }
2894
+ });
2895
+
2896
+ // ../../core/providers/codex.js
2897
+ var require_codex = __commonJS({
2898
+ "../../core/providers/codex.js"(exports2, module2) {
2899
+ "use strict";
2900
+ var { spawn } = require("node:child_process");
2901
+ function classifyCodex(stderr) {
2902
+ const s = (stderr || "").toLowerCase();
2903
+ if (s.includes("auth") || s.includes("login")) return { errorKind: "auth", retryable: false };
2904
+ if (s.includes("timeout")) return { errorKind: "timeout", retryable: true };
2905
+ if (s.includes("rate")) return { errorKind: "rate-limit", retryable: true };
2906
+ return { errorKind: "unknown", retryable: false };
2907
+ }
2908
+ function defaultRun({ prompt, cwd, timeoutMs }) {
2909
+ return new Promise((resolve) => {
2910
+ const child = spawn("codex", ["exec", "--skip-git-repo-check"], { cwd: cwd || process.cwd() });
2911
+ let stdout = "", stderr = "", settled = false;
2912
+ const timer = timeoutMs ? setTimeout(() => child.kill("SIGKILL"), timeoutMs) : null;
2913
+ if (timer) timer.unref();
2914
+ child.stdout.on("data", (d) => stdout += d);
2915
+ child.stderr.on("data", (d) => stderr += d);
2916
+ child.on("error", (e) => {
2917
+ if (settled) return;
2918
+ settled = true;
2919
+ if (timer) clearTimeout(timer);
2920
+ resolve({ code: 127, stdout: "", stderr: String(e && e.message || e) });
2921
+ });
2922
+ child.on("close", (code) => {
2923
+ if (settled) return;
2924
+ settled = true;
2925
+ if (timer) clearTimeout(timer);
2926
+ resolve({ code: code == null ? 1 : code, stdout, stderr });
2927
+ });
2928
+ child.stdin.end(prompt);
2929
+ });
2930
+ }
2931
+ function makeCodexProvider(opts = {}) {
2932
+ const run = opts.run || defaultRun;
2933
+ const model = opts.model || "default";
2934
+ return {
2935
+ name: "codex",
2936
+ capabilities: { canImplement: true, fileUpload: false, multiTurn: false },
2937
+ // Option A: no threadId continuity
2938
+ async health() {
2939
+ return { ok: true };
2940
+ },
2941
+ async ask(req) {
2942
+ const started = Date.now();
2943
+ const full = req.developerInstructions ? `${req.developerInstructions}
2944
+
2945
+ ---
2946
+
2947
+ ${req.prompt}` : req.prompt;
2948
+ const { code, stdout, stderr } = await run({ prompt: full, cwd: req.cwd, timeoutMs: req.timeoutMs });
2949
+ if (code === 0) {
2950
+ return { provider: "codex", model, text: stdout.trim(), isError: false, ms: Date.now() - started };
2951
+ }
2952
+ const { errorKind, retryable } = classifyCodex(stderr);
2953
+ return {
2954
+ provider: "codex",
2955
+ model,
2956
+ isError: true,
2957
+ errorKind,
2958
+ retryable,
2959
+ // Error results carry no text; surface stdout/stderr diagnostics in message.
2960
+ message: stdout && stdout.trim() || stderr || void 0,
2961
+ ms: Date.now() - started
2962
+ };
2963
+ }
2964
+ };
2965
+ }
2966
+ module2.exports = { makeCodexProvider, classifyCodex };
2967
+ }
2968
+ });
2969
+
2970
+ // index.js
2971
+ var { makeRegistry } = require_registry();
2972
+ var { askAll, askOne, consensus } = require_orchestrate();
2973
+ var ADVISORY = { readOnlyHint: true };
2974
+ var ASK_PROVIDER = { "ask-gpt": "codex", "ask-gemini": "gemini", "ask-grok": "grok", "ask-openrouter": "openrouter" };
2975
+ var EXPERTS = ["architect", "plan-reviewer", "scope-analyst", "code-reviewer", "security-analyst", "researcher", "debugger"];
2976
+ function inputSchema() {
2977
+ return {
2978
+ type: "object",
2979
+ required: ["prompt"],
2980
+ properties: {
2981
+ prompt: { type: "string" },
2982
+ expert: { type: "string" },
2983
+ developerInstructions: { type: "string" },
2984
+ cwd: { type: "string" },
2985
+ reasoningEffort: { type: "string", enum: ["low", "medium", "high", "none"] }
2986
+ }
2987
+ };
2988
+ }
2989
+ function toolList() {
2990
+ const tools = [
2991
+ { name: "ask-all", description: "Fan out one question to all enabled providers in parallel (advisory).", inputSchema: inputSchema(), annotations: ADVISORY },
2992
+ { name: "consensus", description: "Fan out then run one arbiter pass for a synthesized verdict (advisory).", inputSchema: inputSchema(), annotations: ADVISORY }
2993
+ ];
2994
+ for (const t of Object.keys(ASK_PROVIDER)) {
2995
+ tools.push({ name: t, description: `Single-provider second opinion via ${ASK_PROVIDER[t]} (advisory).`, inputSchema: inputSchema(), annotations: ADVISORY });
2996
+ }
2997
+ for (const e of EXPERTS) {
2998
+ tools.push({ name: e, description: `Direct ${e} expert (advisory).`, inputSchema: inputSchema(), annotations: ADVISORY });
2999
+ }
3000
+ return tools;
3001
+ }
3002
+ function buildServer({ providers, getConfig }) {
3003
+ const registry = makeRegistry(providers);
3004
+ async function call(name, args) {
3005
+ const req = {
3006
+ prompt: args.prompt,
3007
+ expert: args.expert,
3008
+ developerInstructions: args.developerInstructions,
3009
+ cwd: args.cwd,
3010
+ reasoningEffort: args.reasoningEffort,
3011
+ files: args.files
3012
+ };
3013
+ if (name === "ask-all") {
3014
+ const { providers: selected, omitted } = registry.selectForAskAll({ config: getConfig(), expert: req.expert || "" });
3015
+ const results = await askAll(selected, req);
3016
+ return { content: [{ type: "text", text: JSON.stringify({ results, omitted }) }] };
3017
+ }
3018
+ if (name === "consensus") {
3019
+ const { providers: selected } = registry.selectForConsensus({ config: getConfig(), expert: req.expert || "" });
3020
+ const out = await consensus(selected, req);
3021
+ return { content: [{ type: "text", text: JSON.stringify(out) }] };
3022
+ }
3023
+ if (ASK_PROVIDER[name]) {
3024
+ const p = registry.get(ASK_PROVIDER[name]);
3025
+ if (!p) return { content: [{ type: "text", text: JSON.stringify({ error: `provider ${ASK_PROVIDER[name]} not registered` }) }] };
3026
+ const result = await askOne(p, req);
3027
+ return { content: [{ type: "text", text: JSON.stringify({ result }) }] };
3028
+ }
3029
+ if (EXPERTS.includes(name)) {
3030
+ const { providers: selected } = registry.selectForAskAll({ config: getConfig(), expert: name });
3031
+ const results = await askAll(selected, { ...req, expert: name });
3032
+ return { content: [{ type: "text", text: JSON.stringify({ results }) }] };
3033
+ }
3034
+ throw new Error(`unknown tool: ${name}`);
3035
+ }
3036
+ async function handle(msg) {
3037
+ try {
3038
+ if (msg.method === "initialize") {
3039
+ return { jsonrpc: "2.0", id: msg.id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "deliberation-mcp", version: "0.1.0" } } };
3040
+ }
3041
+ if (msg.method === "tools/list") return { jsonrpc: "2.0", id: msg.id, result: { tools: toolList() } };
3042
+ if (msg.method === "tools/call") {
3043
+ const result = await call(msg.params.name, msg.params.arguments || {});
3044
+ return { jsonrpc: "2.0", id: msg.id, result };
3045
+ }
3046
+ return { jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `method not found: ${msg.method}` } };
3047
+ } catch (e) {
3048
+ const err = (
3049
+ /** @type {any} */
3050
+ e
3051
+ );
3052
+ return { jsonrpc: "2.0", id: msg.id, error: { code: -32603, message: String(err && err.message || err) } };
3053
+ }
3054
+ }
3055
+ return { handle, toolList };
3056
+ }
3057
+ function startStdio() {
3058
+ const { makeOpenAICompatibleProvider } = require_openai_compatible();
3059
+ const { makeGrokProvider } = require_grok2();
3060
+ const { makeAntigravityProvider } = require_antigravity();
3061
+ const { makeCodexProvider } = require_codex();
3062
+ const configMod = (
3063
+ /** @type {any} */
3064
+ require_config()
3065
+ );
3066
+ const { makeConfigReader, DEFAULT_API_BASE, DEFAULT_API_KEY_ENV } = configMod;
3067
+ const reader = makeConfigReader(require_paths().resolveConfigPath());
3068
+ const getConfig = () => reader.get().resolved || { providers: {}, openrouter: {} };
3069
+ const initialOr = getConfig().openrouter || {};
3070
+ const providers = [
3071
+ makeCodexProvider({}),
3072
+ makeAntigravityProvider({}),
3073
+ makeGrokProvider({}),
3074
+ makeOpenAICompatibleProvider({
3075
+ name: "openrouter",
3076
+ apiBase: initialOr.apiBase || DEFAULT_API_BASE,
3077
+ apiKeyEnv: DEFAULT_API_KEY_ENV,
3078
+ resolveModel: (req) => req.model || getConfig().openrouter && getConfig().openrouter.defaultModel || ""
3079
+ })
3080
+ ];
3081
+ const srv = buildServer({ providers, getConfig });
3082
+ if (typeof globalThis.fetch !== "function") {
3083
+ console.error("deliberation-mcp requires Node 18+ (global fetch unavailable).");
3084
+ process.exit(1);
3085
+ }
3086
+ let buffer = "";
3087
+ process.stdin.on("data", async (chunk) => {
3088
+ buffer += chunk.toString();
3089
+ const lines = buffer.split("\n");
3090
+ buffer = lines.pop() || "";
3091
+ for (const line of lines) {
3092
+ const l = line.trim();
3093
+ if (!l) continue;
3094
+ let msg;
3095
+ try {
3096
+ msg = JSON.parse(l);
3097
+ } catch {
3098
+ continue;
3099
+ }
3100
+ const res = await srv.handle(msg);
3101
+ if (msg.id !== void 0) process.stdout.write(JSON.stringify(res) + "\n");
3102
+ }
3103
+ });
3104
+ }
3105
+ if (require.main === module) startStdio();
3106
+ module.exports = { buildServer, toolList };