tachibot-mcp 2.0.6 → 2.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.
- package/.env.example +13 -3
- package/README.md +88 -44
- package/dist/src/config/model-constants.js +121 -91
- package/dist/src/config/model-defaults.js +35 -21
- package/dist/src/config/model-preferences.js +5 -4
- package/dist/src/config.js +2 -1
- package/dist/src/mcp-client.js +3 -3
- package/dist/src/modes/scout.js +2 -1
- package/dist/src/optimization/model-router.js +19 -16
- package/dist/src/orchestrator-instructions.js +1 -1
- package/dist/src/orchestrator-lite.js +1 -1
- package/dist/src/orchestrator.js +1 -1
- package/dist/src/profiles/balanced.js +1 -2
- package/dist/src/profiles/code_focus.js +1 -2
- package/dist/src/profiles/full.js +1 -2
- package/dist/src/profiles/minimal.js +1 -2
- package/dist/src/profiles/research_power.js +1 -2
- package/dist/src/server.js +13 -12
- package/dist/src/tools/gemini-tools.js +32 -16
- package/dist/src/tools/grok-enhanced.js +18 -17
- package/dist/src/tools/grok-tools.js +34 -20
- package/dist/src/tools/openai-tools.js +52 -61
- package/dist/src/tools/tool-router.js +53 -52
- package/dist/src/tools/unified-ai-provider.js +90 -9
- package/dist/src/tools/workflow-runner.js +16 -0
- package/dist/src/tools/workflow-validator-tool.js +1 -1
- package/dist/src/utils/api-keys.js +20 -0
- package/dist/src/utils/openrouter-gateway.js +117 -0
- package/dist/src/validators/interpolation-validator.js +4 -0
- package/dist/src/validators/tool-registry-validator.js +1 -1
- package/dist/src/validators/tool-types.js +0 -1
- package/dist/src/workflows/custom-workflows.js +4 -3
- package/dist/src/workflows/engine/VariableInterpolator.js +30 -3
- package/dist/src/workflows/engine/WorkflowExecutionEngine.js +2 -2
- package/dist/src/workflows/engine/WorkflowOutputFormatter.js +27 -4
- package/dist/src/workflows/fallback-strategies.js +2 -2
- package/dist/src/workflows/model-router.js +20 -11
- package/dist/src/workflows/tool-mapper.js +51 -24
- package/docs/API_KEYS.md +52 -18
- package/docs/CONFIGURATION.md +25 -8
- package/docs/TOOLS_REFERENCE.md +12 -48
- package/docs/TOOL_PARAMETERS.md +19 -16
- package/docs/WORKFLOWS.md +7 -7
- package/package.json +1 -1
- package/profiles/balanced.json +1 -2
- package/profiles/code_focus.json +1 -2
- package/profiles/debug_intensive.json +0 -1
- package/profiles/full.json +2 -3
- package/profiles/minimal.json +1 -2
- package/profiles/research_power.json +1 -2
- package/profiles/workflow_builder.json +1 -2
- package/tools.config.json +15 -3
- package/workflows/code-architecture-review.yaml +5 -3
- package/workflows/creative-brainstorm-yaml.yaml +1 -1
- package/workflows/pingpong.yaml +5 -3
- package/workflows/system/README.md +1 -1
- package/workflows/system/verifier.yaml +8 -5
- package/workflows/ultra-creative-brainstorm.yaml +3 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized API Key Resolution
|
|
3
|
+
* Single source of truth for all API key lookups (SRP)
|
|
4
|
+
*/
|
|
5
|
+
// Grok/xAI - supports both XAI_API_KEY (new) and GROK_API_KEY (legacy)
|
|
6
|
+
export const getGrokApiKey = () => process.env.XAI_API_KEY || process.env.GROK_API_KEY;
|
|
7
|
+
export const hasGrokApiKey = () => !!(process.env.XAI_API_KEY || process.env.GROK_API_KEY);
|
|
8
|
+
// Other providers (single key each)
|
|
9
|
+
export const getOpenAIApiKey = () => process.env.OPENAI_API_KEY;
|
|
10
|
+
export const hasOpenAIApiKey = () => !!process.env.OPENAI_API_KEY;
|
|
11
|
+
export const getPerplexityApiKey = () => process.env.PERPLEXITY_API_KEY;
|
|
12
|
+
export const hasPerplexityApiKey = () => !!process.env.PERPLEXITY_API_KEY;
|
|
13
|
+
export const getGeminiApiKey = () => process.env.GOOGLE_API_KEY;
|
|
14
|
+
export const hasGeminiApiKey = () => !!process.env.GOOGLE_API_KEY;
|
|
15
|
+
export const getOpenRouterApiKey = () => process.env.OPENROUTER_API_KEY;
|
|
16
|
+
export const hasOpenRouterApiKey = () => !!process.env.OPENROUTER_API_KEY;
|
|
17
|
+
export const getQwenApiKey = () => process.env.QWEN_API_KEY;
|
|
18
|
+
export const hasQwenApiKey = () => !!process.env.QWEN_API_KEY;
|
|
19
|
+
export const getDeepSeekApiKey = () => process.env.DEEPSEEK_API_KEY;
|
|
20
|
+
export const hasDeepSeekApiKey = () => !!process.env.DEEPSEEK_API_KEY;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter Gateway Utility
|
|
3
|
+
* Routes API calls through OpenRouter when USE_OPENROUTER_GATEWAY=true
|
|
4
|
+
*/
|
|
5
|
+
import { config } from 'dotenv';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
config({ path: path.resolve(__dirname, '../../../.env') });
|
|
11
|
+
// Gateway configuration - NOT cached, read fresh each time
|
|
12
|
+
const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
13
|
+
// Models that should NEVER use gateway (direct API only)
|
|
14
|
+
const NO_GATEWAY_MODELS = new Set([
|
|
15
|
+
'sonar-pro',
|
|
16
|
+
'sonar-reasoning-pro',
|
|
17
|
+
'sonar',
|
|
18
|
+
]);
|
|
19
|
+
// Grok model mapping - our names → OpenRouter names
|
|
20
|
+
const GROK_MODEL_MAP = {
|
|
21
|
+
'grok-4-1-fast-reasoning': 'x-ai/grok-4.1-fast',
|
|
22
|
+
'grok-4-1-fast-non-reasoning': 'x-ai/grok-4.1-fast',
|
|
23
|
+
'grok-4-fast-reasoning': 'x-ai/grok-4-fast',
|
|
24
|
+
'grok-4-fast-non-reasoning': 'x-ai/grok-4-fast',
|
|
25
|
+
'grok-code-fast-1': 'x-ai/grok-4-fast',
|
|
26
|
+
'grok-4-0709': 'x-ai/grok-4',
|
|
27
|
+
'grok-3': 'x-ai/grok-3',
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Check if gateway mode is enabled
|
|
31
|
+
* Reads env vars fresh each time (not cached at module load)
|
|
32
|
+
*/
|
|
33
|
+
export function isGatewayEnabled() {
|
|
34
|
+
const enabled = process.env.USE_OPENROUTER_GATEWAY === 'true';
|
|
35
|
+
const hasKey = !!process.env.OPENROUTER_API_KEY;
|
|
36
|
+
return enabled && hasKey;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Map model name to OpenRouter format
|
|
40
|
+
* Returns null if model should skip gateway (use direct API)
|
|
41
|
+
*/
|
|
42
|
+
export function mapModelToOpenRouter(model) {
|
|
43
|
+
// Perplexity models - NEVER use gateway, direct API only
|
|
44
|
+
if (model.startsWith('sonar') || NO_GATEWAY_MODELS.has(model)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// Already has provider prefix (qwen/, moonshotai/, etc.) - pass through
|
|
48
|
+
if (model.includes('/')) {
|
|
49
|
+
return model;
|
|
50
|
+
}
|
|
51
|
+
// Grok models need explicit mapping (names differ from OpenRouter)
|
|
52
|
+
if (GROK_MODEL_MAP[model]) {
|
|
53
|
+
return GROK_MODEL_MAP[model];
|
|
54
|
+
}
|
|
55
|
+
// Add provider prefix based on model name
|
|
56
|
+
if (model.startsWith('gpt-')) {
|
|
57
|
+
return `openai/${model}`;
|
|
58
|
+
}
|
|
59
|
+
if (model.startsWith('gemini-')) {
|
|
60
|
+
return `google/${model}`;
|
|
61
|
+
}
|
|
62
|
+
if (model.startsWith('grok-')) {
|
|
63
|
+
return `x-ai/${model}`; // Fallback for unmapped grok models
|
|
64
|
+
}
|
|
65
|
+
// Unknown model - pass through as-is
|
|
66
|
+
return model;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Try to route request through OpenRouter gateway
|
|
70
|
+
* Returns response string if gateway used, null if gateway disabled/failed
|
|
71
|
+
*/
|
|
72
|
+
export async function tryOpenRouterGateway(model, messages, options = {}) {
|
|
73
|
+
// Check if gateway is enabled
|
|
74
|
+
if (!isGatewayEnabled()) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// Map model - returns null if model should skip gateway
|
|
78
|
+
const mappedModel = mapModelToOpenRouter(model);
|
|
79
|
+
if (!mappedModel) {
|
|
80
|
+
return null; // Skip gateway, use direct API
|
|
81
|
+
}
|
|
82
|
+
console.error(`🔀 [OpenRouter Gateway] Routing ${model} → ${mappedModel}`);
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(OPENROUTER_URL, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
|
89
|
+
'HTTP-Referer': 'https://tachibot-mcp.local',
|
|
90
|
+
'X-Title': 'TachiBot MCP Server'
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
model: mappedModel,
|
|
94
|
+
messages,
|
|
95
|
+
temperature: options.temperature ?? 0.7,
|
|
96
|
+
max_tokens: options.max_tokens ?? 4000,
|
|
97
|
+
stream: false
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
const error = await response.text();
|
|
102
|
+
console.error(`🔀 [OpenRouter Gateway] Error: ${error}`);
|
|
103
|
+
return null; // Fall back to direct API
|
|
104
|
+
}
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
const content = data.choices?.[0]?.message?.content;
|
|
107
|
+
if (typeof content !== 'string') {
|
|
108
|
+
console.error('🔀 [OpenRouter Gateway] Invalid response format');
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return content;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error('🔀 [OpenRouter Gateway] Error:', error instanceof Error ? error.message : String(error));
|
|
115
|
+
return null; // Fall back to direct API
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -117,6 +117,10 @@ export class InterpolationValidator {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
validateStringInterpolations(content, stepNames, variables, path, errors, currentStepIndex, stepOrder) {
|
|
120
|
+
// Safety check: return early if content is null/undefined
|
|
121
|
+
if (!content || typeof content !== 'string') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
120
124
|
const matches = content.matchAll(this.interpolationRegex);
|
|
121
125
|
for (const match of matches) {
|
|
122
126
|
const fullMatch = match[0]; // e.g., "${step1.output}"
|
|
@@ -62,7 +62,7 @@ export class ToolRegistryValidator {
|
|
|
62
62
|
// Grok
|
|
63
63
|
'grok_reason', 'grok_code', 'grok_debug', 'grok_architect', 'grok_brainstorm', 'grok_search',
|
|
64
64
|
// OpenAI
|
|
65
|
-
'
|
|
65
|
+
'openai_brainstorm', 'openai_reason', 'openai_code_review', 'openai_explain',
|
|
66
66
|
// Gemini
|
|
67
67
|
'gemini_brainstorm', 'gemini_analyze_code', 'gemini_analyze_text',
|
|
68
68
|
// Qwen
|
|
@@ -122,22 +122,23 @@ export class CustomWorkflowEngine {
|
|
|
122
122
|
{
|
|
123
123
|
name: "Gemini Ideas",
|
|
124
124
|
tool: "gemini_brainstorm",
|
|
125
|
+
input: { prompt: "${query}" },
|
|
125
126
|
maxTokens: 500,
|
|
126
127
|
},
|
|
127
128
|
{
|
|
128
129
|
name: "GPT-5 Creative",
|
|
129
130
|
tool: "gpt5_mini",
|
|
130
|
-
input: { prompt: "Build on
|
|
131
|
+
input: { prompt: "Build on these ideas with creative twists for: ${query}\n\nPrevious ideas: ${Gemini Ideas.output}" },
|
|
131
132
|
},
|
|
132
133
|
{
|
|
133
134
|
name: "Perplexity Research",
|
|
134
135
|
tool: "perplexity_research",
|
|
135
|
-
input: { prompt: "Find real-world examples and evidence" },
|
|
136
|
+
input: { prompt: "Find real-world examples and evidence for: ${query}" },
|
|
136
137
|
},
|
|
137
138
|
{
|
|
138
139
|
name: "Final Synthesis",
|
|
139
140
|
tool: "focus",
|
|
140
|
-
input: { prompt: "
|
|
141
|
+
input: { prompt: "Synthesize all brainstorming results into top 5 creative recommendations for: ${query}\n\nIdeas to combine:\n${Gemini Ideas.output}\n${GPT-5 Creative.output}\n${Perplexity Research.output}" },
|
|
141
142
|
},
|
|
142
143
|
],
|
|
143
144
|
},
|
|
@@ -18,6 +18,14 @@ export class VariableInterpolator {
|
|
|
18
18
|
* Interpolates variables and step outputs in template strings
|
|
19
19
|
*/
|
|
20
20
|
async interpolate(template, context) {
|
|
21
|
+
// Safety check: return empty string if template is null/undefined
|
|
22
|
+
if (template === null || template === undefined) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
// Ensure template is a string
|
|
26
|
+
if (typeof template !== 'string') {
|
|
27
|
+
return String(template);
|
|
28
|
+
}
|
|
21
29
|
const mergedContext = {
|
|
22
30
|
...context.variables,
|
|
23
31
|
...Object.fromEntries(context.fileReferences)
|
|
@@ -76,14 +84,33 @@ export class VariableInterpolator {
|
|
|
76
84
|
return { match: fullMatch, replacement: filename };
|
|
77
85
|
}
|
|
78
86
|
else if (property === 'output') {
|
|
79
|
-
// Handle ${step.output} by returning summary
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
// Handle ${step.output} by returning FULL content (not summary!)
|
|
88
|
+
// This is the primary interpolation method - must have complete output for chaining
|
|
89
|
+
const content = await value.getContent();
|
|
90
|
+
console.error(`✓ Interpolated '${key}': loaded full content (${content.length} chars)`);
|
|
91
|
+
return { match: fullMatch, replacement: content };
|
|
82
92
|
}
|
|
83
93
|
else {
|
|
84
94
|
throw new Error(`Unknown FileReference property: ${property}`);
|
|
85
95
|
}
|
|
86
96
|
}
|
|
97
|
+
// Handle objects that aren't FileReferences (convert to JSON or extract summary)
|
|
98
|
+
if (typeof value === 'object' && value !== null) {
|
|
99
|
+
// Check if it has a summary property (duck typing for FileReference-like objects)
|
|
100
|
+
if ('summary' in value && typeof value.summary === 'string') {
|
|
101
|
+
console.error(`✓ Interpolated '${key}': using summary from object (${value.summary.length} chars)`);
|
|
102
|
+
return { match: fullMatch, replacement: value.summary };
|
|
103
|
+
}
|
|
104
|
+
// Check if it has a content property
|
|
105
|
+
if ('content' in value && typeof value.content === 'string') {
|
|
106
|
+
console.error(`✓ Interpolated '${key}': using content from object (${value.content.length} chars)`);
|
|
107
|
+
return { match: fullMatch, replacement: value.content };
|
|
108
|
+
}
|
|
109
|
+
// Fallback: JSON stringify the object
|
|
110
|
+
const jsonStr = JSON.stringify(value, null, 2);
|
|
111
|
+
console.error(`✓ Interpolated '${key}': stringified object (${jsonStr.length} chars)`);
|
|
112
|
+
return { match: fullMatch, replacement: jsonStr };
|
|
113
|
+
}
|
|
87
114
|
// Handle primitive values
|
|
88
115
|
console.error(`✓ Interpolated '${key}': type=${typeof value}, length=${String(value).length}`);
|
|
89
116
|
return { match: fullMatch, replacement: String(value) };
|
|
@@ -137,7 +137,7 @@ async function executeWorkflowImpl(parent, workflowName, input, options) {
|
|
|
137
137
|
console.error(`🔍 Available variables: [${Object.keys(variables).join(', ')}]`);
|
|
138
138
|
console.error(`🔍 Available stepOutputs: [${Object.keys(stepOutputs).join(', ')}]`);
|
|
139
139
|
// Extract variables that this step wants to use
|
|
140
|
-
const inputStr = JSON.stringify(step.input);
|
|
140
|
+
const inputStr = step.input ? JSON.stringify(step.input) : '';
|
|
141
141
|
const usedVars = [...inputStr.matchAll(/\$\{([^}]+)\}/g)].map(m => m[1]);
|
|
142
142
|
if (usedVars.length > 0) {
|
|
143
143
|
console.error(`🔍 Variables needed by this step: [${usedVars.join(', ')}]`);
|
|
@@ -210,7 +210,7 @@ async function executeWorkflowImpl(parent, workflowName, input, options) {
|
|
|
210
210
|
if (workflow.settings?.optimization?.enabled && typeof stepInput === "string") {
|
|
211
211
|
const optimized = await tokenOptimizer.optimize({
|
|
212
212
|
prompt: stepInput,
|
|
213
|
-
model: model || "gpt-5-mini",
|
|
213
|
+
model: model || "gpt-5.1-codex-mini",
|
|
214
214
|
maxTokens: resolvedParams.maxTokens,
|
|
215
215
|
});
|
|
216
216
|
if (optimized.fromCache) {
|
|
@@ -11,7 +11,8 @@ export class WorkflowOutputFormatter {
|
|
|
11
11
|
const synthesisStep = execution.outputs.find(step => step.step === 'auto-synthesis');
|
|
12
12
|
if (synthesisStep) {
|
|
13
13
|
// Auto-synthesis ran - return only the synthesis output to prevent MCP 25k limit
|
|
14
|
-
|
|
14
|
+
// DEFENSIVE: Ensure output is a string
|
|
15
|
+
return this.ensureString(synthesisStep.output);
|
|
15
16
|
}
|
|
16
17
|
switch (format) {
|
|
17
18
|
case "json":
|
|
@@ -22,7 +23,7 @@ export class WorkflowOutputFormatter {
|
|
|
22
23
|
status: execution.status,
|
|
23
24
|
steps: execution.outputs.map(out => ({
|
|
24
25
|
step: out.step,
|
|
25
|
-
summary: out.output,
|
|
26
|
+
summary: this.ensureString(out.output),
|
|
26
27
|
filePath: out.filePath
|
|
27
28
|
}))
|
|
28
29
|
};
|
|
@@ -33,6 +34,28 @@ export class WorkflowOutputFormatter {
|
|
|
33
34
|
return this.formatSummary(execution);
|
|
34
35
|
}
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Ensures a value is converted to a string (handles FileReference and objects)
|
|
39
|
+
*/
|
|
40
|
+
ensureString(value) {
|
|
41
|
+
if (value === null || value === undefined) {
|
|
42
|
+
return '[No output]';
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'string') {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === 'object') {
|
|
48
|
+
// Handle FileReference objects - extract summary or content
|
|
49
|
+
if ('summary' in value && typeof value.summary === 'string') {
|
|
50
|
+
return value.summary;
|
|
51
|
+
}
|
|
52
|
+
if ('content' in value && typeof value.content === 'string') {
|
|
53
|
+
return value.content;
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify(value, null, 2);
|
|
56
|
+
}
|
|
57
|
+
return String(value);
|
|
58
|
+
}
|
|
36
59
|
/**
|
|
37
60
|
* Formats detailed output with all step information
|
|
38
61
|
*/
|
|
@@ -54,7 +77,7 @@ export class WorkflowOutputFormatter {
|
|
|
54
77
|
if (step.input && step.input !== '[cached]') {
|
|
55
78
|
output += `**Input:**\n${step.input}...\n\n`;
|
|
56
79
|
}
|
|
57
|
-
output += `${step.output}\n\n`;
|
|
80
|
+
output += `${this.ensureString(step.output)}\n\n`;
|
|
58
81
|
if (step.filePath) {
|
|
59
82
|
output += `📄 *Full output saved to: ${step.filePath}*\n\n`;
|
|
60
83
|
}
|
|
@@ -73,7 +96,7 @@ export class WorkflowOutputFormatter {
|
|
|
73
96
|
.filter(out => out.filePath)
|
|
74
97
|
.map(out => ` - ${out.step}: ${out.filePath}`)
|
|
75
98
|
.join('\n');
|
|
76
|
-
let result = lastOutput
|
|
99
|
+
let result = lastOutput ? this.ensureString(lastOutput.output) : "Workflow completed";
|
|
77
100
|
if (savedFiles) {
|
|
78
101
|
result += `\n\n**Files saved:**\n${savedFiles}\n\n`;
|
|
79
102
|
result += `Use Read tool to access full content for detailed analysis.`;
|
|
@@ -60,9 +60,9 @@ export class FallbackStrategies {
|
|
|
60
60
|
},
|
|
61
61
|
execute: async (context) => {
|
|
62
62
|
return {
|
|
63
|
-
alternativeTool: '
|
|
63
|
+
alternativeTool: 'gpt5_mini',
|
|
64
64
|
fallbackUsed: 'cheaper-model',
|
|
65
|
-
warning: 'Falling back to
|
|
65
|
+
warning: 'Falling back to gpt-5.1-codex-mini (cost-efficient coding model)'
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
});
|
|
@@ -9,6 +9,15 @@ export class ModelRouter {
|
|
|
9
9
|
reasoning: 10,
|
|
10
10
|
useFor: ['primary reasoning', 'code', 'analysis']
|
|
11
11
|
}],
|
|
12
|
+
['gemini-3-pro-preview', {
|
|
13
|
+
id: 'gemini-3-pro-preview',
|
|
14
|
+
cost: 10,
|
|
15
|
+
quality: 10,
|
|
16
|
+
speed: 8,
|
|
17
|
+
reasoning: 10,
|
|
18
|
+
context: '1M tokens',
|
|
19
|
+
useFor: ['latest Gemini (Nov 2025)', 'enhanced structured outputs', 'multimodal', 'complex analysis']
|
|
20
|
+
}],
|
|
12
21
|
['gemini-2.5-pro', {
|
|
13
22
|
id: 'gemini-2.5-pro',
|
|
14
23
|
cost: 10,
|
|
@@ -43,20 +52,20 @@ export class ModelRouter {
|
|
|
43
52
|
reasoning: 10,
|
|
44
53
|
useFor: ['complex reasoning with evidence']
|
|
45
54
|
}],
|
|
46
|
-
['grok-4
|
|
47
|
-
id: 'grok-4
|
|
55
|
+
['grok-4-1-fast-reasoning', {
|
|
56
|
+
id: 'grok-4-1-fast-reasoning',
|
|
48
57
|
cost: 9,
|
|
49
58
|
quality: 10,
|
|
50
59
|
speed: 7,
|
|
51
60
|
reasoning: 10,
|
|
52
61
|
useFor: ['enhanced reasoning', 'creativity', 'emotional intelligence', 'first-principles']
|
|
53
62
|
}],
|
|
54
|
-
['grok-4
|
|
55
|
-
id: 'grok-4
|
|
63
|
+
['grok-4-1-fast-non-reasoning', {
|
|
64
|
+
id: 'grok-4-1-fast-non-reasoning',
|
|
56
65
|
cost: 9,
|
|
57
66
|
quality: 10,
|
|
58
|
-
speed:
|
|
59
|
-
reasoning:
|
|
67
|
+
speed: 9,
|
|
68
|
+
reasoning: 8,
|
|
60
69
|
useFor: ['tool-calling', 'agentic workflows', 'code analysis', 'fast reasoning']
|
|
61
70
|
}],
|
|
62
71
|
['grok-4', {
|
|
@@ -150,14 +159,14 @@ export class ModelRouter {
|
|
|
150
159
|
return 'lmstudio-local';
|
|
151
160
|
}
|
|
152
161
|
const taskTypeMap = {
|
|
153
|
-
'code': task.complexity > 0.7 ? 'qwen3-coder-480b' : 'grok-4
|
|
162
|
+
'code': task.complexity > 0.7 ? 'qwen3-coder-480b' : 'grok-4-1-fast-non-reasoning',
|
|
154
163
|
'research': 'perplexity-sonar-pro',
|
|
155
164
|
'reasoning': task.complexity > 0.5 ? 'gpt5' : 'gpt5_mini',
|
|
156
165
|
'scout': 'multi-model',
|
|
157
166
|
'verifier': task.complexity > 0.5 ? 'gpt5' : 'gpt5_mini',
|
|
158
167
|
'challenger': 'gpt5_mini',
|
|
159
168
|
'auditor': 'perplexity-sonar-pro',
|
|
160
|
-
'architect': 'grok-4
|
|
169
|
+
'architect': 'grok-4-1-fast-reasoning',
|
|
161
170
|
'commit_guardian': 'gemini-2.5-flash'
|
|
162
171
|
};
|
|
163
172
|
return taskTypeMap[task.type] || this.selectByConstraints(task, constraints);
|
|
@@ -195,9 +204,9 @@ export class ModelRouter {
|
|
|
195
204
|
selectModelsForVerification(variant) {
|
|
196
205
|
const variants = {
|
|
197
206
|
'quick_verify': ['gpt5_mini', 'gemini-2.5-flash', 'qwen3-30b'],
|
|
198
|
-
'deep_verify': ['gpt5', 'qwq-32b', 'gpt5_reason', 'gemini-
|
|
199
|
-
'fact_check': ['perplexity-sonar-pro', 'gpt5', 'gemini-
|
|
200
|
-
'code_verify': ['qwen3-coder-480b', 'gpt5', 'gemini-
|
|
207
|
+
'deep_verify': ['gpt5', 'qwq-32b', 'gpt5_reason', 'gemini-3-pro-preview', 'qwen3-coder-480b'],
|
|
208
|
+
'fact_check': ['perplexity-sonar-pro', 'gpt5', 'gemini-3-pro-preview'],
|
|
209
|
+
'code_verify': ['qwen3-coder-480b', 'gpt5', 'gemini-3-pro-preview'],
|
|
201
210
|
'security_verify': ['gpt5', 'qwen3-coder-480b', 'grok-4']
|
|
202
211
|
};
|
|
203
212
|
return variants[variant] || variants['quick_verify'];
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Tool Mapper - Maps workflow tool names to actual MCP tool implementations
|
|
3
3
|
* Enables workflows to call real tools instead of returning placeholders
|
|
4
4
|
*/
|
|
5
|
-
import { callGemini
|
|
5
|
+
import { callGemini } from "../tools/gemini-tools.js";
|
|
6
6
|
import { getAllPerplexityTools } from "../tools/perplexity-tools.js";
|
|
7
7
|
import { callOpenAI } from "../tools/openai-tools.js";
|
|
8
8
|
import { callGrok, GrokModel } from "../tools/grok-tools.js";
|
|
9
|
-
import {
|
|
9
|
+
import { OPENAI_MODELS, TOOL_DEFAULTS, GEMINI_MODELS, } from "../config/model-constants.js";
|
|
10
10
|
import { validateToolInput } from "../utils/input-validator.js";
|
|
11
|
+
import { hasGrokApiKey } from "../utils/api-keys.js";
|
|
11
12
|
// Lazy load OpenRouter for Qwen models
|
|
12
13
|
let callOpenRouter = null;
|
|
13
14
|
let OpenRouterModel = null;
|
|
@@ -74,7 +75,8 @@ export async function executeWorkflowTool(toolName, input, options = {}) {
|
|
|
74
75
|
if (typeof input === "string")
|
|
75
76
|
return input;
|
|
76
77
|
// Try all common parameter names (order matters!)
|
|
77
|
-
return input.
|
|
78
|
+
return input.thought || // think tool reflection
|
|
79
|
+
input.requirements || // qwen_coder, task-specific
|
|
78
80
|
input.problem || // brainstorm, reasoning tools
|
|
79
81
|
input.query || // search/ask tools
|
|
80
82
|
input.topic || // research tools
|
|
@@ -121,7 +123,7 @@ export async function executeWorkflowTool(toolName, input, options = {}) {
|
|
|
121
123
|
maxTokens: 2000,
|
|
122
124
|
temperature: 0.7,
|
|
123
125
|
};
|
|
124
|
-
const { model = ('model' in toolDefaults ? toolDefaults.model :
|
|
126
|
+
const { model = ('model' in toolDefaults ? toolDefaults.model : OPENAI_MODELS.CODEX_MINI), maxTokens = options.maxTokens ?? toolDefaults.maxTokens ?? 2000, temperature = options.temperature ?? toolDefaults.temperature ?? 0.7, systemPrompt, } = options;
|
|
125
127
|
// Helper to convert to messages array format
|
|
126
128
|
const toMessages = (text, system) => {
|
|
127
129
|
const messages = [];
|
|
@@ -144,7 +146,7 @@ export async function executeWorkflowTool(toolName, input, options = {}) {
|
|
|
144
146
|
case "gemini_brainstorm":
|
|
145
147
|
case "gemini_analyze_code":
|
|
146
148
|
case "gemini_analyze_text":
|
|
147
|
-
actualModel = model === "flash" ?
|
|
149
|
+
actualModel = model === "flash" ? GEMINI_MODELS.FLASH : GEMINI_MODELS.GEMINI_3_PRO;
|
|
148
150
|
return buildResult(await callGemini(prompt, actualModel, systemPrompt, temperature, options.skipValidation || false), actualModel);
|
|
149
151
|
// ============ PERPLEXITY TOOLS ============
|
|
150
152
|
case "perplexity_ask":
|
|
@@ -205,30 +207,55 @@ export async function executeWorkflowTool(toolName, input, options = {}) {
|
|
|
205
207
|
// ============ OPENAI TOOLS ============
|
|
206
208
|
case "openai_brainstorm":
|
|
207
209
|
case "openai_analyze":
|
|
208
|
-
actualModel = (model ||
|
|
210
|
+
actualModel = (model || OPENAI_MODELS.FULL);
|
|
209
211
|
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt), actualModel, temperature, maxTokens, "low", // reasoningEffort
|
|
210
212
|
false, // requireConfirmation
|
|
211
213
|
options.skipValidation || false), actualModel);
|
|
214
|
+
case "openai_reason":
|
|
215
|
+
// GPT-5 Pro with high reasoning effort for complex reasoning
|
|
216
|
+
actualModel = OPENAI_MODELS.PRO;
|
|
217
|
+
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt), actualModel, temperature, maxTokens, "high", // reasoningEffort
|
|
218
|
+
false, // requireConfirmation
|
|
219
|
+
options.skipValidation || false), actualModel);
|
|
220
|
+
case "openai_code_review":
|
|
221
|
+
// GPT-5.1 codex-mini for code review (medium reasoning)
|
|
222
|
+
actualModel = OPENAI_MODELS.CODEX_MINI;
|
|
223
|
+
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt || "You are an expert code reviewer. Provide thorough code review with specific, actionable feedback."), actualModel, 0.3, // Low temperature for consistent code review
|
|
224
|
+
maxTokens, "medium", // reasoningEffort
|
|
225
|
+
false, options.skipValidation || false), actualModel);
|
|
226
|
+
case "openai_explain":
|
|
227
|
+
// GPT-5.1 codex-mini for explanations (low reasoning)
|
|
228
|
+
actualModel = OPENAI_MODELS.CODEX_MINI;
|
|
229
|
+
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt || "You are an expert educator. Provide clear, engaging explanations."), actualModel, temperature, maxTokens, "low", // reasoningEffort
|
|
230
|
+
false, options.skipValidation || false), actualModel);
|
|
212
231
|
case "gpt5_analyze":
|
|
213
|
-
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt),
|
|
232
|
+
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt), OPENAI_MODELS.CODEX_MINI, 0.7, maxTokens), OPENAI_MODELS.CODEX_MINI);
|
|
214
233
|
case "openai_reason":
|
|
215
|
-
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt),
|
|
234
|
+
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt), OPENAI_MODELS.CODEX_MINI, temperature, maxTokens), OPENAI_MODELS.CODEX_MINI);
|
|
216
235
|
// ============ GPT-5 TOOLS ============
|
|
217
236
|
case "gpt5":
|
|
237
|
+
// Map to flagship gpt-5.1
|
|
238
|
+
const gpt5Full = OPENAI_MODELS.FULL; // gpt-5.1 flagship
|
|
239
|
+
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt), gpt5Full, 0.7, maxTokens, "medium"), gpt5Full);
|
|
218
240
|
case "gpt5_mini":
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt), gpt51Model, 0.7, maxTokens, "low"), gpt51Model);
|
|
241
|
+
// Map to gpt-5.1-codex-mini for code tasks (most workflows use for code)
|
|
242
|
+
const gpt5CodexMini = OPENAI_MODELS.CODEX_MINI; // gpt-5.1-codex-mini
|
|
243
|
+
return buildResult(await callOpenAI(toMessages(prompt, systemPrompt), gpt5CodexMini, 0.7, maxTokens, "low"), gpt5CodexMini);
|
|
223
244
|
// ============ GROK TOOLS ============
|
|
224
245
|
case "grok":
|
|
225
246
|
case "grok_reason":
|
|
226
|
-
case "grok_code":
|
|
227
|
-
case "grok_debug":
|
|
228
247
|
case "grok_brainstorm":
|
|
229
|
-
case "grok_heavy": // Grok Heavy is just grok-4-0709 with more backend resources
|
|
230
248
|
case "grok_search":
|
|
231
|
-
|
|
249
|
+
// Use reasoning model for reasoning/creative tasks
|
|
250
|
+
actualModel = GrokModel.GROK_4_1_FAST_REASONING; // Latest 4.1 (2M context, $0.20/$0.50)
|
|
251
|
+
return buildResult(await callGrok(toMessages(prompt, systemPrompt), actualModel, temperature, maxTokens), actualModel);
|
|
252
|
+
case "grok_code":
|
|
253
|
+
case "grok_debug":
|
|
254
|
+
// Use non-reasoning model for code/debug (tool-calling optimized)
|
|
255
|
+
actualModel = GrokModel.GROK_4_1_FAST; // Latest 4.1 non-reasoning (2M context, $0.20/$0.50)
|
|
256
|
+
return buildResult(await callGrok(toMessages(prompt, systemPrompt), actualModel, temperature, maxTokens), actualModel);
|
|
257
|
+
case "grok_heavy": // Grok Heavy is grok-4-0709 with extended context
|
|
258
|
+
actualModel = GrokModel.GROK_4_HEAVY; // Expensive $3/$15
|
|
232
259
|
return buildResult(await callGrok(toMessages(prompt, systemPrompt), actualModel, temperature, maxTokens), actualModel);
|
|
233
260
|
// ============ ADVANCED MODES ============
|
|
234
261
|
case "verifier":
|
|
@@ -340,20 +367,20 @@ export async function executeWorkflowTool(toolName, input, options = {}) {
|
|
|
340
367
|
// ============ META TOOLS ============
|
|
341
368
|
case "think":
|
|
342
369
|
// Simple reflection tool - uses GPT-5.1-codex-mini for cost efficiency
|
|
343
|
-
return buildResult(await callOpenAI(toMessages(`Reflect on the following and provide brief insights:\n\n${prompt}`, "You are a reflective thinking assistant. Provide concise, insightful analysis."),
|
|
370
|
+
return buildResult(await callOpenAI(toMessages(`Reflect on the following and provide brief insights:\n\n${prompt}`, "You are a reflective thinking assistant. Provide concise, insightful analysis."), OPENAI_MODELS.CODEX_MINI, 0.7, 500), OPENAI_MODELS.CODEX_MINI);
|
|
344
371
|
case "focus":
|
|
345
372
|
// Deep analysis tool - uses GPT-5.1
|
|
346
|
-
return buildResult(await callOpenAI(toMessages(`Perform deep analysis and synthesis:\n\n${prompt}`, "You are an advanced analytical assistant. Provide comprehensive, synthesized insights."),
|
|
373
|
+
return buildResult(await callOpenAI(toMessages(`Perform deep analysis and synthesis:\n\n${prompt}`, "You are an advanced analytical assistant. Provide comprehensive, synthesized insights."), OPENAI_MODELS.FULL, 0.8, maxTokens), OPENAI_MODELS.FULL);
|
|
347
374
|
case "code_reviewer":
|
|
348
|
-
return buildResult(await callOpenAI(toMessages(`Perform thorough code review:\n\n${prompt}`, "You are an expert code reviewer. Analyze for bugs, security issues, performance, and best practices."),
|
|
375
|
+
return buildResult(await callOpenAI(toMessages(`Perform thorough code review:\n\n${prompt}`, "You are an expert code reviewer. Analyze for bugs, security issues, performance, and best practices."), OPENAI_MODELS.FULL, 0.5, maxTokens), OPENAI_MODELS.FULL);
|
|
349
376
|
case "test_architect":
|
|
350
|
-
return buildResult(await callOpenAI(toMessages(`Design comprehensive tests:\n\n${prompt}`, "You are a testing expert. Design thorough test suites with edge cases."),
|
|
377
|
+
return buildResult(await callOpenAI(toMessages(`Design comprehensive tests:\n\n${prompt}`, "You are a testing expert. Design thorough test suites with edge cases."), OPENAI_MODELS.FULL, 0.6, maxTokens), OPENAI_MODELS.FULL);
|
|
351
378
|
case "documentation_writer":
|
|
352
|
-
return buildResult(await callOpenAI(toMessages(`Create clear documentation:\n\n${prompt}`, "You are a technical writer. Create clear, comprehensive documentation."),
|
|
379
|
+
return buildResult(await callOpenAI(toMessages(`Create clear documentation:\n\n${prompt}`, "You are a technical writer. Create clear, comprehensive documentation."), OPENAI_MODELS.CODEX_MINI, 0.7, maxTokens), OPENAI_MODELS.CODEX_MINI);
|
|
353
380
|
// ============ DEFAULT ============
|
|
354
381
|
default:
|
|
355
382
|
console.warn(`⚠️ Unknown tool: ${toolName}, falling back to GPT-5.1-codex-mini`);
|
|
356
|
-
return buildResult(await callOpenAI(toMessages(prompt),
|
|
383
|
+
return buildResult(await callOpenAI(toMessages(prompt), OPENAI_MODELS.CODEX_MINI, temperature, maxTokens), OPENAI_MODELS.CODEX_MINI);
|
|
357
384
|
}
|
|
358
385
|
}
|
|
359
386
|
catch (error) {
|
|
@@ -381,9 +408,9 @@ export function getAvailableTools() {
|
|
|
381
408
|
tools.push("perplexity_ask", "perplexity_research", "perplexity_reason", "perplexity_code_search");
|
|
382
409
|
}
|
|
383
410
|
if (process.env.OPENAI_API_KEY) {
|
|
384
|
-
tools.push("openai_brainstorm", "gpt5_analyze", "openai_reason", "gpt5", "gpt5_mini"
|
|
411
|
+
tools.push("openai_brainstorm", "openai_reason", "openai_code_review", "openai_explain", "gpt5_analyze", "openai_reason", "gpt5", "gpt5_mini");
|
|
385
412
|
}
|
|
386
|
-
if (
|
|
413
|
+
if (hasGrokApiKey()) {
|
|
387
414
|
tools.push("grok", "grok_reason", "grok_code", "grok_debug", "grok_brainstorm", "grok_search");
|
|
388
415
|
}
|
|
389
416
|
// Add modes if available
|