metheus-governance-mcp-cli 0.2.85 → 0.2.86
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.
- package/README.md +2 -1
- package/cli.mjs +3 -0
- package/lib/local-ai-adapters.mjs +156 -21
- package/lib/selftest-bot-commands.mjs +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -410,7 +410,8 @@ Recommended role mapping:
|
|
|
410
410
|
Role profile note:
|
|
411
411
|
- Claude maps `reasoning_effort` to `--effort`.
|
|
412
412
|
- Codex maps `reasoning_effort` to `-c model_reasoning_effort="..."`.
|
|
413
|
-
- Gemini
|
|
413
|
+
- Gemini applies `reasoning_effort` by injecting a temporary `~/.gemini/settings.json` override for the invocation.
|
|
414
|
+
- For Gemini 3 models, `low -> thinkingLevel LOW`, `medium -> THINKING_LEVEL_UNSPECIFIED` (Gemini default), and `high -> thinkingLevel HIGH`.
|
|
414
415
|
|
|
415
416
|
Trigger policy fields:
|
|
416
417
|
- `mentions_only`: in groups, react only when the bot is mentioned or when a message replies to the bot
|
package/cli.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import https from "node:https";
|
|
|
11
11
|
import {
|
|
12
12
|
DEFAULT_LOCAL_AI_CLIENT,
|
|
13
13
|
resolveLocalAIExecutionModel,
|
|
14
|
+
resolveGeminiReasoningConfig,
|
|
14
15
|
SUPPORTED_LOCAL_AI_CLIENTS,
|
|
15
16
|
normalizeLocalAIClientName,
|
|
16
17
|
normalizeLocalAIPermissionMode,
|
|
@@ -3358,6 +3359,7 @@ function buildBotCommandDeps() {
|
|
|
3358
3359
|
normalizeLocalAIClientName,
|
|
3359
3360
|
normalizeLocalAIPermissionMode,
|
|
3360
3361
|
normalizeLocalAIReasoningEffort,
|
|
3362
|
+
resolveGeminiReasoningConfig,
|
|
3361
3363
|
supportedLocalAIClients: SUPPORTED_LOCAL_AI_CLIENTS,
|
|
3362
3364
|
summarizeProviderSupport,
|
|
3363
3365
|
loadProviderEnvConfig,
|
|
@@ -5209,6 +5211,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
5209
5211
|
cliPath: fileURLToPath(import.meta.url),
|
|
5210
5212
|
parseSimpleEnvText,
|
|
5211
5213
|
resolveLocalAIExecutionModel,
|
|
5214
|
+
resolveGeminiReasoningConfig,
|
|
5212
5215
|
});
|
|
5213
5216
|
|
|
5214
5217
|
const payload = buildSelftestPayload(checks);
|
|
@@ -8,6 +8,15 @@ export const DEFAULT_LOCAL_AI_CLIENT = "codex";
|
|
|
8
8
|
|
|
9
9
|
const SUPPORTED_PERMISSION_MODES = ["read_only", "workspace_write", "danger_full_access"];
|
|
10
10
|
const SUPPORTED_REASONING_EFFORTS = ["low", "medium", "high"];
|
|
11
|
+
const GEMINI_HOME_SYNC_FILES = [
|
|
12
|
+
"google_accounts.json",
|
|
13
|
+
"oauth_creds.json",
|
|
14
|
+
"installation_id",
|
|
15
|
+
"state.json",
|
|
16
|
+
"trustedFolders.json",
|
|
17
|
+
"projects.json",
|
|
18
|
+
"settings.json",
|
|
19
|
+
];
|
|
11
20
|
const LOCAL_AI_MODEL_MAPPINGS = {
|
|
12
21
|
codex: [
|
|
13
22
|
{
|
|
@@ -80,6 +89,10 @@ function ensureArray(value) {
|
|
|
80
89
|
return Array.isArray(value) ? value : [];
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
function safeObject(value) {
|
|
93
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
function intFromRawAllowZero(rawValue, fallback = 0) {
|
|
84
97
|
const parsed = Number.parseInt(String(rawValue ?? "").trim(), 10);
|
|
85
98
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
@@ -330,6 +343,122 @@ function buildGeminiArgs({ promptText, model, permissionMode }) {
|
|
|
330
343
|
return args;
|
|
331
344
|
}
|
|
332
345
|
|
|
346
|
+
function isGemini3ExecutionModel(model) {
|
|
347
|
+
const normalizedModel = normalizeModelAliasText(model);
|
|
348
|
+
return normalizedModel === "auto-gemini-3" || normalizedModel.startsWith("gemini-3");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function readJsonFileIfExists(filePath) {
|
|
352
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
357
|
+
} catch {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function copyFileIfExists(sourcePath, targetPath) {
|
|
363
|
+
if (!sourcePath || !targetPath || !fs.existsSync(sourcePath)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
367
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function buildGeminiThinkingConfig(model, reasoningEffort) {
|
|
371
|
+
const normalizedReasoningEffort = normalizeLocalAIReasoningEffort(reasoningEffort, "");
|
|
372
|
+
if (!normalizedReasoningEffort) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
if (isGemini3ExecutionModel(model)) {
|
|
376
|
+
if (normalizedReasoningEffort === "low") {
|
|
377
|
+
return { thinkingLevel: "LOW" };
|
|
378
|
+
}
|
|
379
|
+
if (normalizedReasoningEffort === "high") {
|
|
380
|
+
return { thinkingLevel: "HIGH" };
|
|
381
|
+
}
|
|
382
|
+
return { thinkingLevel: "THINKING_LEVEL_UNSPECIFIED" };
|
|
383
|
+
}
|
|
384
|
+
const thinkingBudgetMap = {
|
|
385
|
+
low: 0,
|
|
386
|
+
medium: 4096,
|
|
387
|
+
high: 8192,
|
|
388
|
+
};
|
|
389
|
+
return {
|
|
390
|
+
thinkingBudget: thinkingBudgetMap[normalizedReasoningEffort] ?? thinkingBudgetMap.medium,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function resolveGeminiReasoningConfig(rawModelValue = "", reasoningEffort = "medium") {
|
|
395
|
+
const executionModel = resolveLocalAIExecutionModel("gemini", rawModelValue);
|
|
396
|
+
const thinkingConfig = buildGeminiThinkingConfig(executionModel, reasoningEffort);
|
|
397
|
+
if (!thinkingConfig) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
model: executionModel,
|
|
402
|
+
thinkingConfig,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildGeminiSettingsOverride(existingSettings, model, reasoningEffort) {
|
|
407
|
+
const resolved = resolveGeminiReasoningConfig(model, reasoningEffort);
|
|
408
|
+
if (!resolved) {
|
|
409
|
+
return safeObject(existingSettings);
|
|
410
|
+
}
|
|
411
|
+
const baseSettings = safeObject(existingSettings);
|
|
412
|
+
const baseModelConfigs = safeObject(baseSettings.modelConfigs);
|
|
413
|
+
const nextOverrides = ensureArray(baseModelConfigs.overrides).slice();
|
|
414
|
+
nextOverrides.push({
|
|
415
|
+
match: { model: resolved.model },
|
|
416
|
+
modelConfig: {
|
|
417
|
+
generateContentConfig: {
|
|
418
|
+
thinkingConfig: resolved.thinkingConfig,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
return {
|
|
423
|
+
...baseSettings,
|
|
424
|
+
modelConfigs: {
|
|
425
|
+
...baseModelConfigs,
|
|
426
|
+
overrides: nextOverrides,
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function prepareGeminiRuntimeEnv({ model, reasoningEffort, env }) {
|
|
432
|
+
const sourceHome = String(env?.GEMINI_CLI_HOME || os.homedir() || "").trim();
|
|
433
|
+
const sourceGeminiDir = sourceHome ? path.join(sourceHome, ".gemini") : "";
|
|
434
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-gemini-home-"));
|
|
435
|
+
const tempGeminiDir = path.join(tempHome, ".gemini");
|
|
436
|
+
fs.mkdirSync(tempGeminiDir, { recursive: true });
|
|
437
|
+
for (const fileName of GEMINI_HOME_SYNC_FILES) {
|
|
438
|
+
copyFileIfExists(
|
|
439
|
+
sourceGeminiDir ? path.join(sourceGeminiDir, fileName) : "",
|
|
440
|
+
path.join(tempGeminiDir, fileName),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
const mergedSettings = buildGeminiSettingsOverride(
|
|
444
|
+
readJsonFileIfExists(path.join(tempGeminiDir, "settings.json")),
|
|
445
|
+
model,
|
|
446
|
+
reasoningEffort,
|
|
447
|
+
);
|
|
448
|
+
fs.writeFileSync(path.join(tempGeminiDir, "settings.json"), `${JSON.stringify(mergedSettings, null, 2)}\n`, "utf8");
|
|
449
|
+
return {
|
|
450
|
+
env: {
|
|
451
|
+
...env,
|
|
452
|
+
GEMINI_CLI_HOME: tempHome,
|
|
453
|
+
},
|
|
454
|
+
cleanup() {
|
|
455
|
+
if (fs.existsSync(tempHome)) {
|
|
456
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
333
462
|
function runCodexAdapter({ promptText, workspaceDir, model, permissionMode, reasoningEffort, env }) {
|
|
334
463
|
const outputPath = path.join(os.tmpdir(), `metheus-runner-codex-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
|
|
335
464
|
const codexCommand = resolveLocalCliCommand("codex");
|
|
@@ -392,29 +521,34 @@ function runClaudeAdapter({ promptText, workspaceDir, model, permissionMode, rea
|
|
|
392
521
|
return normalizeCliOutput(result.stdout);
|
|
393
522
|
}
|
|
394
523
|
|
|
395
|
-
function runGeminiAdapter({ promptText, workspaceDir, model, permissionMode, env }) {
|
|
524
|
+
function runGeminiAdapter({ promptText, workspaceDir, model, permissionMode, reasoningEffort, env }) {
|
|
396
525
|
const geminiCommand = resolveLocalCliCommand("gemini");
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
526
|
+
const runtime = prepareGeminiRuntimeEnv({ model, reasoningEffort, env });
|
|
527
|
+
try {
|
|
528
|
+
const result = spawnCli(
|
|
529
|
+
geminiCommand,
|
|
530
|
+
buildGeminiArgs({
|
|
531
|
+
promptText,
|
|
532
|
+
model,
|
|
533
|
+
permissionMode,
|
|
534
|
+
}),
|
|
535
|
+
{
|
|
536
|
+
cwd: workspaceDir,
|
|
537
|
+
encoding: "utf8",
|
|
538
|
+
env: runtime.env,
|
|
539
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
540
|
+
},
|
|
541
|
+
);
|
|
542
|
+
if (result.error) {
|
|
543
|
+
throw new Error(String(result.error?.message || result.error));
|
|
544
|
+
}
|
|
545
|
+
if (result.status !== 0) {
|
|
546
|
+
throw new Error(String(result.stderr || result.stdout || `gemini exited with status ${result.status}`));
|
|
547
|
+
}
|
|
548
|
+
return normalizeCliOutput(result.stdout);
|
|
549
|
+
} finally {
|
|
550
|
+
runtime.cleanup();
|
|
416
551
|
}
|
|
417
|
-
return normalizeCliOutput(result.stdout);
|
|
418
552
|
}
|
|
419
553
|
|
|
420
554
|
function runSampleAdapter(payload) {
|
|
@@ -608,6 +742,7 @@ export function runLocalAIClient({
|
|
|
608
742
|
workspaceDir: resolvedWorkspaceDir,
|
|
609
743
|
model: resolvedExecutionModel,
|
|
610
744
|
permissionMode: normalizedPermissionMode,
|
|
745
|
+
reasoningEffort: normalizedReasoningEffort,
|
|
611
746
|
env: nextEnv,
|
|
612
747
|
});
|
|
613
748
|
}
|
|
@@ -229,6 +229,7 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
229
229
|
const cliPath = String(requireDependency(deps, "cliPath") || "").trim();
|
|
230
230
|
const parseSimpleEnvText = requireDependency(deps, "parseSimpleEnvText");
|
|
231
231
|
const resolveLocalAIExecutionModel = requireDependency(deps, "resolveLocalAIExecutionModel");
|
|
232
|
+
const resolveGeminiReasoningConfig = requireDependency(deps, "resolveGeminiReasoningConfig");
|
|
232
233
|
let tempHome = "";
|
|
233
234
|
let mock = null;
|
|
234
235
|
try {
|
|
@@ -253,6 +254,22 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
253
254
|
].join(" "),
|
|
254
255
|
);
|
|
255
256
|
|
|
257
|
+
const geminiLowReasoning = resolveGeminiReasoningConfig("gemini-3.1-pro", "low");
|
|
258
|
+
const geminiMediumReasoning = resolveGeminiReasoningConfig("gemini-3.1-pro", "medium");
|
|
259
|
+
const geminiHighReasoning = resolveGeminiReasoningConfig("gemini-3.1-pro", "high");
|
|
260
|
+
push(
|
|
261
|
+
"gemini_reasoning_effort_maps_to_runtime_settings_override",
|
|
262
|
+
String(geminiLowReasoning?.model || "") === "auto-gemini-3"
|
|
263
|
+
&& String(safeObject(geminiLowReasoning?.thinkingConfig).thinkingLevel || "") === "LOW"
|
|
264
|
+
&& String(safeObject(geminiMediumReasoning?.thinkingConfig).thinkingLevel || "") === "THINKING_LEVEL_UNSPECIFIED"
|
|
265
|
+
&& String(safeObject(geminiHighReasoning?.thinkingConfig).thinkingLevel || "") === "HIGH",
|
|
266
|
+
[
|
|
267
|
+
`low=${JSON.stringify(geminiLowReasoning?.thinkingConfig || {})}`,
|
|
268
|
+
`medium=${JSON.stringify(geminiMediumReasoning?.thinkingConfig || {})}`,
|
|
269
|
+
`high=${JSON.stringify(geminiHighReasoning?.thinkingConfig || {})}`,
|
|
270
|
+
].join(" "),
|
|
271
|
+
);
|
|
272
|
+
|
|
256
273
|
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-bot-selftest-"));
|
|
257
274
|
mock = await createMockServer().listen();
|
|
258
275
|
const env = buildSpawnEnv(tempHome);
|