tachibot-mcp 2.22.0 → 2.23.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.
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +1 -1
- package/dist/src/server.js +16 -105
- package/dist/src/tools/factory/base-schemas.js +61 -0
- package/dist/src/tools/factory/define-model-tool.js +37 -0
- package/dist/src/tools/gemini-tools.js +24 -22
- package/dist/src/tools/grok-tools.js +17 -15
- package/dist/src/tools/jury-tool.js +23 -18
- package/dist/src/tools/local-tools.js +15 -2
- package/dist/src/tools/openai-tools.js +23 -21
- package/dist/src/tools/openrouter-tools.js +42 -40
- package/dist/src/tools/perplexity-tools.js +15 -13
- package/dist/src/tools/planner-tools.js +10 -8
- package/dist/src/tools/prompt-technique-tools.js +7 -6
- package/dist/src/tools/registry.js +147 -0
- package/dist/src/tools/tachi-tool.js +3 -2
- package/dist/src/tools/workflow-validator-tool.js +5 -4
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to TachiBot MCP will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.23.0] - 2026-06-11
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Tool registration via central scan registry** — merged the tool-standardization refactor (PR #5): all provider tools register through `src/tools/registry.ts` + `defineModelTool` factory, with golden wire-contract tests (57 tool schemas snapshot-locked) and a plop `add-tool` generator. No behavior change; the registered tool surface is byte-identical.
|
|
12
|
+
- **Jury: removed the `hermes` juror** — it was a persona variant of `local` calling the same `LOCAL_LLM_MODEL` weights while claiming "You are Hermes" (a false-role prompt). Council-reviewed rationale: jury independence comes from different model weights, not different system prompts on the same backend. `hermes` is kept as a **legacy alias** of `local`; panels are deduped after alias mapping, so `jurors: "hermes,local"` now yields one local vote instead of two correlated ones. 12 jurors total.
|
|
13
|
+
- Docs now describe the Hermes connection honestly (verified via grok_search against Nous docs/GitHub): the local juror runs whatever `LOCAL_LLM_MODEL` points at, e.g. a Nous Hermes build via Ollama. The Hermes *agent* is model-agnostic — it consumes 300+ backends (GPT, Claude, Gemini, self-hosted Ollama/vLLM); it is not an OpenAI-compatible endpoint to point `LOCAL_LLM_BASE_URL` at.
|
|
14
|
+
|
|
8
15
|
## [2.22.0] - 2026-06-10
|
|
9
16
|
|
|
10
17
|
### Added
|
package/CONTRIBUTING.md
CHANGED
|
@@ -47,6 +47,10 @@ Use the issue templates and include:
|
|
|
47
47
|
- Open a [Discussion](https://github.com/pavveu/tachibot-mcp/discussions)
|
|
48
48
|
- Create an issue
|
|
49
49
|
|
|
50
|
+
## Adding a tool
|
|
51
|
+
|
|
52
|
+
Run `npm run add-tool` and answer the prompts (provider, wire name, description). It scaffolds a `defineModelTool(...)` in the provider file at the `// plop:tools`/`// plop:register` anchors and a test stub. Fill in the `parameters` and `execute` TODOs. The emitted-schema golden test (`npm run test:golden`) guards the tool-name/schema wire contract.
|
|
53
|
+
|
|
50
54
|
## License
|
|
51
55
|
|
|
52
56
|
By submitting code to this project, you agree that your contributions will be licensed under the AGPL-3.0 license and you grant Pawel Pawlowski the right to relicense your contributions under alternative licenses (including commercial licenses).
|
package/README.md
CHANGED
|
@@ -202,7 +202,7 @@ See [Installation Guide](docs/INSTALLATION_BOTH.md) for detailed instructions.
|
|
|
202
202
|
`list_prompt_techniques` · `preview_prompt_technique` · `execute_prompt_technique`
|
|
203
203
|
|
|
204
204
|
### Local Models (1)
|
|
205
|
-
`local_query` — any OpenAI-compatible local server (Ollama / LM Studio / llama.cpp / vLLM). Zero-cost, offline, private; also available as `
|
|
205
|
+
`local_query` — any OpenAI-compatible local server (Ollama / LM Studio / llama.cpp / vLLM). Zero-cost, offline, private; also available as the `local` jury juror (`hermes` is accepted as a legacy alias). Runs whatever `LOCAL_LLM_MODEL` points at — e.g. a Nous Hermes build (`ollama pull hermes3`). Note the [Hermes agent](https://hermes-agent.nousresearch.com) itself is model-agnostic — it runs on 300+ backends (GPT, Claude, Gemini, DeepSeek, or self-hosted Ollama/vLLM) — so "Hermes" was never a guarantee of distinct weights.
|
|
206
206
|
|
|
207
207
|
### Advanced Modes (bonus)
|
|
208
208
|
- **Challenger** — Critical analysis with multi-model fact-checking
|
package/dist/src/server.js
CHANGED
|
@@ -65,16 +65,14 @@ import { getProviderInfo } from "./tools/unified-ai-provider.js";
|
|
|
65
65
|
import { getAllPerplexityTools, isPerplexityAvailable } from "./tools/perplexity-tools.js";
|
|
66
66
|
import { getAllGrokTools, isGrokAvailable } from "./tools/grok-tools.js";
|
|
67
67
|
import { registerWorkflowTools } from "./tools/workflow-runner.js";
|
|
68
|
-
import {
|
|
68
|
+
import { getAllTools } from "./tools/registry.js";
|
|
69
69
|
import { canRunFocusDeep } from "./focus-deep.js";
|
|
70
70
|
import { loadConfig } from "./config.js";
|
|
71
71
|
// import { registerSessionTools } from "./session/session-tools.js"; // Removed - not needed for minimal tool set
|
|
72
72
|
import { getAllAdvancedTools, areAdvancedModesAvailable } from "./tools/advanced-modes.js";
|
|
73
73
|
import { isOpenAIAvailable, getAllOpenAITools } from "./tools/openai-tools.js";
|
|
74
|
-
import { isGeminiAvailable
|
|
74
|
+
import { isGeminiAvailable } from "./tools/gemini-tools.js";
|
|
75
75
|
import { isOpenRouterAvailable } from "./tools/openrouter-tools.js";
|
|
76
|
-
import { getTachiTools } from "./tools/tachi-tool.js";
|
|
77
|
-
import { getPromptTechniqueTools } from "./tools/prompt-technique-tools.js";
|
|
78
76
|
import { withParamAliases } from "./utils/param-aliases.js";
|
|
79
77
|
// import { registerGPT5Tools, isGPT5Available } from "./tools/openai-gpt5-fixed.js"; // DISABLED - using regular openai-tools.ts
|
|
80
78
|
import { initializeOptimizations } from "./optimization/index.js";
|
|
@@ -553,114 +551,27 @@ const availableProviders = Object.entries(providerInfo)
|
|
|
553
551
|
.filter(([_, info]) => info.available)
|
|
554
552
|
.map(([name]) => name);
|
|
555
553
|
console.error(`✅ Available AI providers: ${availableProviders.join(', ')}`);
|
|
556
|
-
// Register Perplexity tools separately (custom API, not OpenAI-compatible)
|
|
557
|
-
if (isPerplexityAvailable()) {
|
|
558
|
-
const perplexityTools = getAllPerplexityTools();
|
|
559
|
-
perplexityTools.forEach(tool => {
|
|
560
|
-
safeAddTool(tool);
|
|
561
|
-
});
|
|
562
|
-
console.error(`✅ Registered ${perplexityTools.length} Perplexity tools (custom API)`);
|
|
563
|
-
}
|
|
564
|
-
// Register Grok tools separately (custom API, not OpenAI-compatible)
|
|
565
|
-
if (isGrokAvailable()) {
|
|
566
|
-
const grokTools = getAllGrokTools();
|
|
567
|
-
grokTools.forEach(tool => {
|
|
568
|
-
safeAddTool(tool);
|
|
569
|
-
});
|
|
570
|
-
console.error(`✅ Registered ${grokTools.length} Grok tools (custom API)`);
|
|
571
|
-
}
|
|
572
|
-
// Register all OpenAI tools (includes openai_reason, openai_brainstorm, etc.)
|
|
573
|
-
if (isOpenAIAvailable()) {
|
|
574
|
-
const openaiTools = getAllOpenAITools();
|
|
575
|
-
openaiTools.forEach(tool => {
|
|
576
|
-
safeAddTool(tool);
|
|
577
|
-
});
|
|
578
|
-
console.error(`✅ Registered ${openaiTools.length} OpenAI tools (GPT-5 suite)`);
|
|
579
|
-
}
|
|
580
554
|
// Async initialization function to handle dynamic imports and startup
|
|
581
555
|
async function initializeServer() {
|
|
582
556
|
try {
|
|
583
|
-
// Register
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
safeAddTool(tool);
|
|
595
|
-
});
|
|
596
|
-
console.error(`✅ Registered ${geminiTools.length} Gemini tools (brainstorm, code, text, judge, search)`);
|
|
597
|
-
// Register Jury tool (multi-model panel with Gemini judge)
|
|
598
|
-
const { juryTool } = await import("./tools/jury-tool.js");
|
|
599
|
-
safeAddTool(juryTool);
|
|
600
|
-
console.error(`✅ Registered jury tool (multi-model panel)`);
|
|
601
|
-
}
|
|
602
|
-
// Register OpenRouter tools (Qwen, Kimi, MiniMax - filtered by profile via safeAddTool)
|
|
603
|
-
if (isOpenRouterAvailable()) {
|
|
604
|
-
const { qwenCoderTool, qwenAlgoTool, qwqReasoningTool, qwenCompetitiveTool, kimiThinkingTool, kimiCodeTool, kimiDecomposeTool, kimiLongContextTool, qwenReasonTool, minimaxCodeTool, minimaxAgentTool, deepseekReasonTool, deepseekAlgoTool, glmReasonTool, stepfunReasonTool, ernieReasonTool } = await import("./tools/openrouter-tools.js");
|
|
605
|
-
// safeAddTool checks isToolEnabled internally
|
|
606
|
-
safeAddTool(qwenCoderTool);
|
|
607
|
-
safeAddTool(qwenAlgoTool);
|
|
608
|
-
safeAddTool(qwqReasoningTool); // QwQ-32B - multi-perspective deliberation (free tier)
|
|
609
|
-
safeAddTool(qwenCompetitiveTool);
|
|
610
|
-
safeAddTool(kimiThinkingTool);
|
|
611
|
-
safeAddTool(kimiCodeTool); // SWE-focused code (Kimi K2.5 - 76.8% SWE-Bench)
|
|
612
|
-
safeAddTool(kimiDecomposeTool); // Task decomposition (Kimi K2.5 Agent Swarm)
|
|
613
|
-
safeAddTool(kimiLongContextTool); // Long-context analysis (Kimi K2.5 - 256K)
|
|
614
|
-
safeAddTool(qwenReasonTool); // Heavy reasoning (Qwen3-Max-Thinking >1T params)
|
|
615
|
-
safeAddTool(minimaxCodeTool); // MiniMax M2.7 - SWE-Pro 56.22%, #1 AI Intelligence Index
|
|
616
|
-
safeAddTool(minimaxAgentTool); // MiniMax M2.7 - agentic workflows, self-evolving
|
|
617
|
-
safeAddTool(deepseekReasonTool); // DeepSeek V4 Pro - frontier reasoning/math (open-weight)
|
|
618
|
-
safeAddTool(deepseekAlgoTool); // DeepSeek V4 Pro - algorithmic code review (top AIME/CodeElo)
|
|
619
|
-
safeAddTool(glmReasonTool); // Zhipu GLM-5.1 - agentic reasoning (SWE-Bench Pro leader)
|
|
620
|
-
safeAddTool(stepfunReasonTool); // StepFun Step 3.7 Flash - efficient reasoning
|
|
621
|
-
safeAddTool(ernieReasonTool); // Baidu ERNIE 4.5 VL - broad-knowledge reasoning
|
|
622
|
-
console.error(`✅ Registered OpenRouter tools (Qwen, QwQ, Kimi x4, MiniMax, DeepSeek x2, GLM, StepFun, ERNIE)`);
|
|
623
|
-
// Register planner tools (multi-model council for plan creation/execution)
|
|
624
|
-
const { plannerMakerTool, plannerRunnerTool, listPlansTool } = await import("./tools/planner-tools.js");
|
|
625
|
-
safeAddTool(plannerMakerTool); // Council-based plan creation
|
|
626
|
-
safeAddTool(plannerRunnerTool); // Plan execution with checkpoints
|
|
627
|
-
safeAddTool(listPlansTool); // List recent plans
|
|
628
|
-
console.error(`✅ Registered planner tools (planner_maker, planner_runner, list_plans)`);
|
|
557
|
+
// Register ALL provider tools via the central scan registry. getAllTools()
|
|
558
|
+
// evaluates the SAME is*Available() guards, in the SAME order, with the SAME
|
|
559
|
+
// async dynamic-import timing the per-provider blocks used to use inline here
|
|
560
|
+
// — so the registered set is byte-identical across every API-key scenario.
|
|
561
|
+
// This single loop replaces the former Perplexity/Grok/OpenAI sync blocks and
|
|
562
|
+
// the Gemini+jury / OpenRouter+planner / local / workflow-validator /
|
|
563
|
+
// advanced / tachi / prompt-technique async blocks. The 5 inline tools
|
|
564
|
+
// (think/focus/nextThought/usage_stats/continue_focus) are registered above
|
|
565
|
+
// and are intentionally NOT returned by the registry, so there is no overlap.
|
|
566
|
+
for (const t of await getAllTools()) {
|
|
567
|
+
safeAddTool(t);
|
|
629
568
|
}
|
|
630
|
-
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
const { localQueryTool } = await import("./tools/local-tools.js");
|
|
634
|
-
safeAddTool(localQueryTool);
|
|
635
|
-
console.error(`✅ Registered local-model tools (local_query)`);
|
|
636
|
-
// Register workflow tools
|
|
569
|
+
console.error(`✅ Registered provider tools via central registry`);
|
|
570
|
+
// Register workflow tools (registers directly onto FastMCP, not via
|
|
571
|
+
// safeAddTool — intentionally OUT of the registry, kept in its original spot).
|
|
637
572
|
registerWorkflowTools(server);
|
|
638
573
|
console.error(`✅ Registered workflow tools (execute, list, create, visualize)`);
|
|
639
|
-
// Register workflow validator tools
|
|
640
|
-
safeAddTool(validateWorkflowTool);
|
|
641
|
-
safeAddTool(validateWorkflowFileTool);
|
|
642
|
-
console.error(`✅ Registered workflow validator tools`);
|
|
643
574
|
// Session management tools removed - not needed for minimal TachiBot
|
|
644
|
-
// Register advanced mode tools (Verifier, Challenger, Scout, etc.)
|
|
645
|
-
if (areAdvancedModesAvailable()) {
|
|
646
|
-
const advancedTools = getAllAdvancedTools();
|
|
647
|
-
advancedTools.forEach(tool => {
|
|
648
|
-
safeAddTool(tool);
|
|
649
|
-
});
|
|
650
|
-
console.error(`✅ Registered ${advancedTools.length} advanced mode tools`);
|
|
651
|
-
}
|
|
652
|
-
// Register tachi tools (smart auto-routing AI assistant)
|
|
653
|
-
const tachiTools = getTachiTools();
|
|
654
|
-
tachiTools.forEach(tool => {
|
|
655
|
-
safeAddTool(tool);
|
|
656
|
-
});
|
|
657
|
-
console.error(`✅ Registered tachi tools (tachi, focus alias)`);
|
|
658
|
-
// Register prompt technique tools (transparent prompt engineering)
|
|
659
|
-
const promptTechniqueTools = getPromptTechniqueTools();
|
|
660
|
-
promptTechniqueTools.forEach(tool => {
|
|
661
|
-
safeAddTool(tool);
|
|
662
|
-
});
|
|
663
|
-
console.error(`✅ Registered ${promptTechniqueTools.length} prompt technique tools`);
|
|
664
575
|
// Log startup information
|
|
665
576
|
const perplexityCount = isPerplexityAvailable() ? getAllPerplexityTools().length : 0;
|
|
666
577
|
const grokCount = isGrokAvailable() ? getAllGrokTools().length : 0;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Zod base schema partials — Task 0.3.
|
|
3
|
+
*
|
|
4
|
+
* Each export is a plain object meant to be SPREAD into a `z.object({...})`.
|
|
5
|
+
* Spreading creates a fresh `ZodObject` whose shape owns a copy of each
|
|
6
|
+
* field reference; two tools that spread the same partial do NOT alias a
|
|
7
|
+
* single mutable ZodObject — they each get an independent schema instance.
|
|
8
|
+
*
|
|
9
|
+
* SELECTION CRITERIA (grep-verified across all src/tools/*-tools.ts):
|
|
10
|
+
* A field is factored HERE only if its FULL definition
|
|
11
|
+
* (type + .describe() text + optionality) is **byte-identical** across
|
|
12
|
+
* every use site (outliers with different describe text stay inline).
|
|
13
|
+
*
|
|
14
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
15
|
+
* filesField
|
|
16
|
+
* 30 of 31 occurrences across gemini/grok/openai/openrouter/perplexity/
|
|
17
|
+
* planner tools share this exact definition. The one outlier
|
|
18
|
+
* (openrouter-tools.ts line ~758) has a different describe text and stays
|
|
19
|
+
* inline in its tool.
|
|
20
|
+
*
|
|
21
|
+
* reasoningContextField
|
|
22
|
+
* 7 occurrences across openai/openrouter/perplexity tools share this exact
|
|
23
|
+
* definition. The remaining ~7 context fields have different describe text
|
|
24
|
+
* (e.g. "Additional context for the problem", "Additional context about the
|
|
25
|
+
* environment or conditions", "Additional context") and stay inline.
|
|
26
|
+
*
|
|
27
|
+
* REJECTED CANDIDATES (non-identical definitions — do NOT factor here):
|
|
28
|
+
* temperature — 3 defs, all differ (defaults 0.4 vs 0.7, z.coerce.number
|
|
29
|
+
* vs z.number, different describe text).
|
|
30
|
+
* query — many defs with varying describe text across tools.
|
|
31
|
+
* problem — varying describe text.
|
|
32
|
+
* prompt — varying describe text.
|
|
33
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
34
|
+
*/
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
/**
|
|
37
|
+
* `files` — file-paths-as-code-context field.
|
|
38
|
+
*
|
|
39
|
+
* Identical across 30 tool definitions (verified by grep, Task 0.3 recon).
|
|
40
|
+
* Spread into z.object({...filesField, ...}) to include this field.
|
|
41
|
+
*/
|
|
42
|
+
export const filesField = {
|
|
43
|
+
files: z
|
|
44
|
+
.array(z.string())
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("File paths to read as code context. Supports line ranges: 'src/foo.ts:100-200'. Model sees ACTUAL CODE."),
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* `context` — additional reasoning context field.
|
|
50
|
+
*
|
|
51
|
+
* Identical across 7 tool definitions in openai/openrouter/perplexity tools
|
|
52
|
+
* (verified by grep, Task 0.3 recon). Context fields with different describe
|
|
53
|
+
* text stay inline in their respective tools.
|
|
54
|
+
* Spread into z.object({...reasoningContextField, ...}) to include this field.
|
|
55
|
+
*/
|
|
56
|
+
export const reasoningContextField = {
|
|
57
|
+
context: z
|
|
58
|
+
.string()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Additional context for the reasoning task"),
|
|
61
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PURE pass-through. Adds types + a single definition site — NO schema
|
|
3
|
+
* transformation, NO added .describe()/defaults. The emitted JSON schema MUST
|
|
4
|
+
* be unchanged. The body is exactly `return tool;` and must stay that way.
|
|
5
|
+
*
|
|
6
|
+
* IDENTITY-PRESERVING: the RETURN type is the concrete tool type `T`, not
|
|
7
|
+
* `ModelTool<S>`. Wrapping a tool is therefore type-transparent — the wrapped
|
|
8
|
+
* const keeps the *exact* concrete type it would have unwrapped (e.g.
|
|
9
|
+
* `execute`'s real `Promise<string>` return and its concrete input where
|
|
10
|
+
* `.default()` fields are still optional at the call site). Returning
|
|
11
|
+
* `ModelTool<S>` instead would widen `execute` to
|
|
12
|
+
* `(input: z.infer<S>, ctx) => Promise<unknown>` and break direct callers such
|
|
13
|
+
* as `src/tools/prompt-technique-tools.ts` that invoke `.execute(...)` outside
|
|
14
|
+
* FastMCP's parse step.
|
|
15
|
+
*
|
|
16
|
+
* Two type params are required for BOTH goals at once:
|
|
17
|
+
* - `S` is inferred from `tool.parameters` so the shape constraint's
|
|
18
|
+
* `execute` input is `z.infer<S>` — i.e. the tool's OWN output type, which
|
|
19
|
+
* its concrete `execute` is assignable to (contravariantly). Constraining
|
|
20
|
+
* to the generic `ModelTool<z.ZodObject<z.ZodRawShape>>` instead resolves
|
|
21
|
+
* the input to `{ [x: string]: any }` (an index signature with no required
|
|
22
|
+
* keys), to which a concrete `execute(input: { prompt: string })` is NOT
|
|
23
|
+
* assignable — so a single `T extends ModelTool<…>` param wrongly rejects
|
|
24
|
+
* every real tool.
|
|
25
|
+
* - `T extends ModelTool<S>` captures and returns the concrete tool type.
|
|
26
|
+
* The `ModelTool<S>` bound still validates the shape (rejects a missing
|
|
27
|
+
* `name`/`description`/`execute` or a non-`ZodObject` `parameters`).
|
|
28
|
+
*
|
|
29
|
+
* C3: a future tool whose top-level `parameters` uses `.refine()` /
|
|
30
|
+
* `.superRefine()` / `.transform()` would produce a `ZodEffects` (not a
|
|
31
|
+
* `ZodObject`) and would require widening the `S` constraint here. No such
|
|
32
|
+
* tool exists today (grep-confirmed), so the `z.ZodObject` constraint is
|
|
33
|
+
* correct for now.
|
|
34
|
+
*/
|
|
35
|
+
export function defineModelTool(tool) {
|
|
36
|
+
return tool;
|
|
37
|
+
}
|
|
@@ -12,6 +12,8 @@ import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
|
|
|
12
12
|
import { withHeartbeat } from "../utils/streaming-helper.js";
|
|
13
13
|
import { getTimeoutConfig } from "../config/timeout-config.js";
|
|
14
14
|
import { readFilesIntoContext } from "../utils/file-reader.js";
|
|
15
|
+
import { defineModelTool } from "./factory/define-model-tool.js";
|
|
16
|
+
import { filesField } from "./factory/base-schemas.js";
|
|
15
17
|
// Note: renderOutput is applied centrally in server.ts safeAddTool() - no need to import here
|
|
16
18
|
// NOTE: dotenv is loaded in server.ts before any imports
|
|
17
19
|
// No need to reload here - just read from process.env
|
|
@@ -164,7 +166,7 @@ export async function callGemini(prompt, model = GEMINI_MODELS.GEMINI_3_PRO, sys
|
|
|
164
166
|
* Gemini Query Tool
|
|
165
167
|
* Direct querying of Gemini models for general information
|
|
166
168
|
*/
|
|
167
|
-
export const geminiQueryTool = {
|
|
169
|
+
export const geminiQueryTool = defineModelTool({
|
|
168
170
|
name: "gemini_query",
|
|
169
171
|
description: "Query Gemini. Put your PROMPT in the 'prompt' parameter.",
|
|
170
172
|
parameters: z.object({
|
|
@@ -173,7 +175,7 @@ export const geminiQueryTool = {
|
|
|
173
175
|
.optional()
|
|
174
176
|
.default("gemini-3")
|
|
175
177
|
.describe("Model variant - must be one of: gemini-3 (default), pro, flash"),
|
|
176
|
-
|
|
178
|
+
...filesField
|
|
177
179
|
}),
|
|
178
180
|
execute: async (args, { log, reportProgress }) => {
|
|
179
181
|
let model = GEMINI_MODELS.GEMINI_3_PRO; // Default to Gemini 3
|
|
@@ -189,7 +191,7 @@ export const geminiQueryTool = {
|
|
|
189
191
|
const result = await withHeartbeat(() => callGemini(args.prompt + fileContext, model, undefined, 0.7, 'llm-orchestration'), reportFn);
|
|
190
192
|
return stripFormatting(result);
|
|
191
193
|
}
|
|
192
|
-
};
|
|
194
|
+
});
|
|
193
195
|
/**
|
|
194
196
|
* Gemini Brainstorm Tool
|
|
195
197
|
* Collaborative problem-solving and ideation
|
|
@@ -197,14 +199,14 @@ export const geminiQueryTool = {
|
|
|
197
199
|
* Note: skipValidation is used for internal LLM-to-LLM calls where input
|
|
198
200
|
* has already been validated at the MCP entry point (server.ts).
|
|
199
201
|
*/
|
|
200
|
-
export const geminiBrainstormTool = {
|
|
202
|
+
export const geminiBrainstormTool = defineModelTool({
|
|
201
203
|
name: "gemini_brainstorm",
|
|
202
204
|
description: "Convergent synthesis: cluster, refine, and prioritize raw ideas into structured hierarchies. Use AFTER divergent ideation to organize and rank ideas by impact/feasibility. Put your PROMPT in the 'prompt' parameter.",
|
|
203
205
|
parameters: z.object({
|
|
204
206
|
prompt: z.string().describe("The ideas or topic to organize and refine (REQUIRED - put raw ideas or topic here)"),
|
|
205
207
|
claudeThoughts: z.string().optional().describe("Claude's initial thoughts or raw ideas to cluster and refine"),
|
|
206
208
|
maxClusters: z.number().optional().default(5).describe("Number of idea clusters to create (default: 5)"),
|
|
207
|
-
|
|
209
|
+
...filesField
|
|
208
210
|
}),
|
|
209
211
|
execute: async (args, { log, reportProgress }) => {
|
|
210
212
|
const systemPrompt = `Convergent synthesis engine. Output consumed by automated toolchain.
|
|
@@ -237,18 +239,18 @@ ${FORMAT_INSTRUCTION}`;
|
|
|
237
239
|
const response = await withHeartbeat(() => callGemini(args.prompt + fileContext, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.7, 'llm-orchestration'), reportFn);
|
|
238
240
|
return stripFormatting(response);
|
|
239
241
|
}
|
|
240
|
-
};
|
|
242
|
+
});
|
|
241
243
|
/**
|
|
242
244
|
* Gemini Analyze Code Tool
|
|
243
245
|
* Code quality and performance analysis
|
|
244
246
|
*/
|
|
245
|
-
export const geminiAnalyzeCodeTool = {
|
|
247
|
+
export const geminiAnalyzeCodeTool = defineModelTool({
|
|
246
248
|
name: "gemini_analyze_code",
|
|
247
249
|
description: "Analyze code for bugs, quality, security, or performance issues. Put the CODE in the 'code' parameter, NOT in 'focus'.",
|
|
248
250
|
parameters: z.object({
|
|
249
251
|
code: z.string().describe("The actual source code to analyze (REQUIRED - put your code here)"),
|
|
250
252
|
language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
|
|
251
|
-
|
|
253
|
+
...filesField,
|
|
252
254
|
focus: z.string().optional().default("general").describe("Analysis focus (e.g., quality, security, performance, bugs, general)")
|
|
253
255
|
}),
|
|
254
256
|
execute: async (args, { log, reportProgress }) => {
|
|
@@ -271,17 +273,17 @@ ${FORMAT_INSTRUCTION}`;
|
|
|
271
273
|
const result = await withHeartbeat(() => callGemini(`Analyze this code:\n\n\`\`\`${args.language || ''}\n${args.code}\n\`\`\`${fileContext}`, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
|
|
272
274
|
return stripFormatting(result);
|
|
273
275
|
}
|
|
274
|
-
};
|
|
276
|
+
});
|
|
275
277
|
/**
|
|
276
278
|
* Gemini Analyze Text Tool
|
|
277
279
|
* Text analysis and sentiment detection
|
|
278
280
|
*/
|
|
279
|
-
export const geminiAnalyzeTextTool = {
|
|
281
|
+
export const geminiAnalyzeTextTool = defineModelTool({
|
|
280
282
|
name: "gemini_analyze_text",
|
|
281
283
|
description: "Rhetorical analysis: dissect arguments for bias, logical fallacies, and persuasion tactics. Use for evaluating claims, detecting manipulation, or understanding argument structure. Put the TEXT in the 'text' parameter.",
|
|
282
284
|
parameters: z.object({
|
|
283
285
|
text: z.string().describe("The text to analyze (REQUIRED - put your text here)"),
|
|
284
|
-
|
|
286
|
+
...filesField,
|
|
285
287
|
type: z.string()
|
|
286
288
|
.optional()
|
|
287
289
|
.default("rhetoric")
|
|
@@ -325,12 +327,12 @@ ${FORMAT_INSTRUCTION}`;
|
|
|
325
327
|
const result = await withHeartbeat(() => callGemini(`Analyze this text:\n\n${args.text}${fileContext}`, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
|
|
326
328
|
return stripFormatting(result);
|
|
327
329
|
}
|
|
328
|
-
};
|
|
330
|
+
});
|
|
329
331
|
/**
|
|
330
332
|
* Gemini Summarize Tool
|
|
331
333
|
* Content summarization at different levels
|
|
332
334
|
*/
|
|
333
|
-
export const geminiSummarizeTool = {
|
|
335
|
+
export const geminiSummarizeTool = defineModelTool({
|
|
334
336
|
name: "gemini_summarize",
|
|
335
337
|
description: "Summarization. Put the CONTENT in the 'content' parameter.",
|
|
336
338
|
parameters: z.object({
|
|
@@ -343,7 +345,7 @@ export const geminiSummarizeTool = {
|
|
|
343
345
|
.optional()
|
|
344
346
|
.default("paragraph")
|
|
345
347
|
.describe("Output format - must be one of: paragraph, bullet-points, outline"),
|
|
346
|
-
|
|
348
|
+
...filesField
|
|
347
349
|
}),
|
|
348
350
|
execute: async (args, { log, reportProgress }) => {
|
|
349
351
|
const lengthGuides = {
|
|
@@ -370,12 +372,12 @@ ${FORMAT_INSTRUCTION}`;
|
|
|
370
372
|
const result = await withHeartbeat(() => callGemini(`Summarize this content:\n\n${args.content}` + fileContext, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
|
|
371
373
|
return stripFormatting(result);
|
|
372
374
|
}
|
|
373
|
-
};
|
|
375
|
+
});
|
|
374
376
|
/**
|
|
375
377
|
* Gemini Image Prompt Tool
|
|
376
378
|
* Create detailed prompts for image generation
|
|
377
379
|
*/
|
|
378
|
-
export const geminiImagePromptTool = {
|
|
380
|
+
export const geminiImagePromptTool = defineModelTool({
|
|
379
381
|
name: "gemini_image_prompt",
|
|
380
382
|
description: "Image prompt generation. Put the DESCRIPTION in the 'description' parameter.",
|
|
381
383
|
parameters: z.object({
|
|
@@ -407,7 +409,7 @@ ${args.details ? `Additional details: ${args.details}` : ''}`;
|
|
|
407
409
|
const result = await withHeartbeat(() => callGemini(userPrompt, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.7, 'llm-orchestration'), reportFn);
|
|
408
410
|
return stripFormatting(result);
|
|
409
411
|
}
|
|
410
|
-
};
|
|
412
|
+
});
|
|
411
413
|
/**
|
|
412
414
|
* Gemini Judge Tool
|
|
413
415
|
* Multi-perspective evaluation and synthesis (LLM-as-a-Judge)
|
|
@@ -417,7 +419,7 @@ ${args.details ? `Additional details: ${args.details}` : ''}`;
|
|
|
417
419
|
* - Position bias mitigation (don't favor first/last)
|
|
418
420
|
* - Extract-then-synthesize (not pick-a-winner)
|
|
419
421
|
*/
|
|
420
|
-
export const geminiJudgeTool = {
|
|
422
|
+
export const geminiJudgeTool = defineModelTool({
|
|
421
423
|
name: "gemini_judge",
|
|
422
424
|
description: "Evaluate and synthesize multiple AI perspectives into a unified verdict. Put CONTENT in the 'perspectives' parameter.",
|
|
423
425
|
parameters: z.object({
|
|
@@ -429,7 +431,7 @@ export const geminiJudgeTool = {
|
|
|
429
431
|
.optional()
|
|
430
432
|
.default("synthesize")
|
|
431
433
|
.describe("Judge mode: synthesize (merge best), evaluate (score each), rank (order by quality), resolve (settle conflicts)"),
|
|
432
|
-
|
|
434
|
+
...filesField
|
|
433
435
|
}),
|
|
434
436
|
execute: async (args, { log, reportProgress }) => {
|
|
435
437
|
// Resolve perspectives from fallback params (AI clients sometimes use wrong param name)
|
|
@@ -500,13 +502,13 @@ ${FORMAT_INSTRUCTION}`;
|
|
|
500
502
|
const result = await withHeartbeat(() => callGemini(userPrompt + fileContext, GEMINI_MODELS.GEMINI_3_PRO, systemPrompt, 0.3, 'llm-orchestration'), reportFn);
|
|
501
503
|
return stripFormatting(result);
|
|
502
504
|
}
|
|
503
|
-
};
|
|
505
|
+
});
|
|
504
506
|
/**
|
|
505
507
|
* Gemini Search Tool
|
|
506
508
|
* Web search using Google Search grounding
|
|
507
509
|
* Uses google_search_retrieval with dynamic retrieval config
|
|
508
510
|
*/
|
|
509
|
-
export const geminiSearchTool = {
|
|
511
|
+
export const geminiSearchTool = defineModelTool({
|
|
510
512
|
name: "gemini_search",
|
|
511
513
|
description: "Web search via Gemini with Google Search grounding",
|
|
512
514
|
parameters: z.object({
|
|
@@ -634,7 +636,7 @@ IMPORTANT:
|
|
|
634
636
|
return `[Gemini Search error: ${error instanceof Error ? error.message : String(error)}]`;
|
|
635
637
|
}
|
|
636
638
|
}
|
|
637
|
-
};
|
|
639
|
+
});
|
|
638
640
|
/**
|
|
639
641
|
* Check if Gemini is available
|
|
640
642
|
*/
|
|
@@ -7,6 +7,8 @@ import { config } from "dotenv";
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { grokSearchTool } from './grok-enhanced.js';
|
|
10
|
+
import { defineModelTool } from './factory/define-model-tool.js';
|
|
11
|
+
import { filesField } from './factory/base-schemas.js';
|
|
10
12
|
import { validateToolInput } from "../utils/input-validator.js";
|
|
11
13
|
import { getGrokApiKey, hasGrokApiKey } from "../utils/api-keys.js";
|
|
12
14
|
import { stripFormatting } from "../utils/format-stripper.js";
|
|
@@ -143,7 +145,7 @@ forceVisibleOutput = true, validationContext = 'llm-orchestration', reasoningEff
|
|
|
143
145
|
* Grok Reasoning Tool
|
|
144
146
|
* Deep logical reasoning with first principles thinking
|
|
145
147
|
*/
|
|
146
|
-
export const grokReasonTool = {
|
|
148
|
+
export const grokReasonTool = defineModelTool({
|
|
147
149
|
name: "grok_reason",
|
|
148
150
|
description: "Deep reasoning. Put your PROBLEM or QUESTION in the 'problem' parameter.",
|
|
149
151
|
parameters: z.object({
|
|
@@ -152,7 +154,7 @@ export const grokReasonTool = {
|
|
|
152
154
|
.optional()
|
|
153
155
|
.describe("Reasoning approach (e.g., analytical, creative, systematic, first-principles)"),
|
|
154
156
|
context: z.string().optional().describe("Additional context for the problem"),
|
|
155
|
-
|
|
157
|
+
...filesField,
|
|
156
158
|
useHeavy: z.boolean().optional().describe("Use expensive Grok 4 Heavy model ($3/$15) for complex tasks")
|
|
157
159
|
}),
|
|
158
160
|
execute: async (args, { log, reportProgress }) => {
|
|
@@ -188,12 +190,12 @@ ${FORMAT_INSTRUCTION}`
|
|
|
188
190
|
const result = await withHeartbeat(() => callGrok(messages, model, 0.7, maxTokens, true, 'llm-orchestration'), reportFn);
|
|
189
191
|
return stripFormatting(result);
|
|
190
192
|
}
|
|
191
|
-
};
|
|
193
|
+
});
|
|
192
194
|
/**
|
|
193
195
|
* Grok Code Tool
|
|
194
196
|
* Code analysis, optimization, and debugging
|
|
195
197
|
*/
|
|
196
|
-
export const grokCodeTool = {
|
|
198
|
+
export const grokCodeTool = defineModelTool({
|
|
197
199
|
name: "grok_code",
|
|
198
200
|
description: "Code analysis. Put the CODE in the 'code' parameter, NOT in 'task'.",
|
|
199
201
|
parameters: z.object({
|
|
@@ -201,7 +203,7 @@ export const grokCodeTool = {
|
|
|
201
203
|
.describe("Code task (e.g., analyze, optimize, debug, review, refactor)"),
|
|
202
204
|
code: z.string().describe("The actual source code to analyze (REQUIRED - put your code here)"),
|
|
203
205
|
language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
|
|
204
|
-
|
|
206
|
+
...filesField,
|
|
205
207
|
requirements: z.string().optional().describe("Specific requirements or focus areas")
|
|
206
208
|
}),
|
|
207
209
|
execute: async (args, { log, reportProgress }) => {
|
|
@@ -236,19 +238,19 @@ ${FORMAT_INSTRUCTION}`
|
|
|
236
238
|
const result = await withHeartbeat(() => callGrok(messages, GrokModel.GROK_4_20_NON_REASONING, 0.2, 4000, true, 'code-analysis'), reportFn);
|
|
237
239
|
return stripFormatting(result);
|
|
238
240
|
}
|
|
239
|
-
};
|
|
241
|
+
});
|
|
240
242
|
/**
|
|
241
243
|
* Grok Debug Tool
|
|
242
244
|
* Specialized debugging assistance
|
|
243
245
|
*/
|
|
244
|
-
export const grokDebugTool = {
|
|
246
|
+
export const grokDebugTool = defineModelTool({
|
|
245
247
|
name: "grok_debug",
|
|
246
248
|
description: "Debug assistance. Describe the ISSUE in the 'issue' parameter.",
|
|
247
249
|
parameters: z.object({
|
|
248
250
|
issue: z.string().describe("Description of the issue or bug (REQUIRED - put your problem here)"),
|
|
249
251
|
code: z.string().optional().describe("Relevant code that has the issue"),
|
|
250
252
|
error: z.string().optional().describe("Error message or stack trace"),
|
|
251
|
-
|
|
253
|
+
...filesField,
|
|
252
254
|
context: z.string().optional().describe("Additional context about the environment or conditions")
|
|
253
255
|
}),
|
|
254
256
|
execute: async (args, { log, reportProgress }) => {
|
|
@@ -288,12 +290,12 @@ ${FORMAT_INSTRUCTION}`
|
|
|
288
290
|
const result = await withHeartbeat(() => callGrok(messages, GrokModel.GROK_4_20_NON_REASONING, 0.3, 3000, true, 'code-analysis'), reportFn);
|
|
289
291
|
return stripFormatting(result);
|
|
290
292
|
}
|
|
291
|
-
};
|
|
293
|
+
});
|
|
292
294
|
/**
|
|
293
295
|
* Grok Architect Tool
|
|
294
296
|
* System architecture and design
|
|
295
297
|
*/
|
|
296
|
-
export const grokArchitectTool = {
|
|
298
|
+
export const grokArchitectTool = defineModelTool({
|
|
297
299
|
name: "grok_architect",
|
|
298
300
|
description: "Architecture design. Put your REQUIREMENTS in the 'requirements' parameter.",
|
|
299
301
|
parameters: z.object({
|
|
@@ -302,7 +304,7 @@ export const grokArchitectTool = {
|
|
|
302
304
|
scale: z.string()
|
|
303
305
|
.optional()
|
|
304
306
|
.describe("Expected scale (e.g., small, medium, large, enterprise)"),
|
|
305
|
-
|
|
307
|
+
...filesField,
|
|
306
308
|
}),
|
|
307
309
|
execute: async (args, { log, reportProgress }) => {
|
|
308
310
|
const { requirements, constraints, scale } = args;
|
|
@@ -329,12 +331,12 @@ ${FORMAT_INSTRUCTION}`
|
|
|
329
331
|
const result = await withHeartbeat(() => callGrok(messages, GrokModel.GROK_4_20_MULTI_AGENT, 0.6, 4000, true, 'llm-orchestration', 'high'), reportFn);
|
|
330
332
|
return stripFormatting(result);
|
|
331
333
|
}
|
|
332
|
-
};
|
|
334
|
+
});
|
|
333
335
|
/**
|
|
334
336
|
* Grok Brainstorm Tool
|
|
335
337
|
* Creative brainstorming with Grok 4 Heavy
|
|
336
338
|
*/
|
|
337
|
-
export const grokBrainstormTool = {
|
|
339
|
+
export const grokBrainstormTool = defineModelTool({
|
|
338
340
|
name: "grok_brainstorm",
|
|
339
341
|
description: "Contrarian first-principles brainstorming: deconstruct a topic to atomic truths, challenge every assumption, then rebuild radical alternatives. Use when conventional thinking has stalled. Put your TOPIC in the 'topic' parameter.",
|
|
340
342
|
parameters: z.object({
|
|
@@ -342,7 +344,7 @@ export const grokBrainstormTool = {
|
|
|
342
344
|
constraints: z.string().optional().describe("Any constraints or requirements to consider"),
|
|
343
345
|
numIdeas: z.number().optional().describe("Number of radical rebuilds to generate (default: 5)"),
|
|
344
346
|
forceHeavy: z.boolean().optional().describe("Use expensive Grok 4 Heavy model ($3/$15) for deeper creativity"),
|
|
345
|
-
|
|
347
|
+
...filesField,
|
|
346
348
|
}),
|
|
347
349
|
execute: async (args, { log, reportProgress }) => {
|
|
348
350
|
const { topic, constraints, numIdeas = 5, forceHeavy = false } = args;
|
|
@@ -383,7 +385,7 @@ ${FORMAT_INSTRUCTION}`
|
|
|
383
385
|
const result = await withHeartbeat(() => callGrok(messages, model, 0.95, 4000, true, 'llm-orchestration'), reportFn);
|
|
384
386
|
return stripFormatting(result);
|
|
385
387
|
}
|
|
386
|
-
};
|
|
388
|
+
});
|
|
387
389
|
/**
|
|
388
390
|
* Check if Grok is available
|
|
389
391
|
*/
|