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.
Files changed (58) hide show
  1. package/.env.example +13 -3
  2. package/README.md +88 -44
  3. package/dist/src/config/model-constants.js +121 -91
  4. package/dist/src/config/model-defaults.js +35 -21
  5. package/dist/src/config/model-preferences.js +5 -4
  6. package/dist/src/config.js +2 -1
  7. package/dist/src/mcp-client.js +3 -3
  8. package/dist/src/modes/scout.js +2 -1
  9. package/dist/src/optimization/model-router.js +19 -16
  10. package/dist/src/orchestrator-instructions.js +1 -1
  11. package/dist/src/orchestrator-lite.js +1 -1
  12. package/dist/src/orchestrator.js +1 -1
  13. package/dist/src/profiles/balanced.js +1 -2
  14. package/dist/src/profiles/code_focus.js +1 -2
  15. package/dist/src/profiles/full.js +1 -2
  16. package/dist/src/profiles/minimal.js +1 -2
  17. package/dist/src/profiles/research_power.js +1 -2
  18. package/dist/src/server.js +13 -12
  19. package/dist/src/tools/gemini-tools.js +32 -16
  20. package/dist/src/tools/grok-enhanced.js +18 -17
  21. package/dist/src/tools/grok-tools.js +34 -20
  22. package/dist/src/tools/openai-tools.js +52 -61
  23. package/dist/src/tools/tool-router.js +53 -52
  24. package/dist/src/tools/unified-ai-provider.js +90 -9
  25. package/dist/src/tools/workflow-runner.js +16 -0
  26. package/dist/src/tools/workflow-validator-tool.js +1 -1
  27. package/dist/src/utils/api-keys.js +20 -0
  28. package/dist/src/utils/openrouter-gateway.js +117 -0
  29. package/dist/src/validators/interpolation-validator.js +4 -0
  30. package/dist/src/validators/tool-registry-validator.js +1 -1
  31. package/dist/src/validators/tool-types.js +0 -1
  32. package/dist/src/workflows/custom-workflows.js +4 -3
  33. package/dist/src/workflows/engine/VariableInterpolator.js +30 -3
  34. package/dist/src/workflows/engine/WorkflowExecutionEngine.js +2 -2
  35. package/dist/src/workflows/engine/WorkflowOutputFormatter.js +27 -4
  36. package/dist/src/workflows/fallback-strategies.js +2 -2
  37. package/dist/src/workflows/model-router.js +20 -11
  38. package/dist/src/workflows/tool-mapper.js +51 -24
  39. package/docs/API_KEYS.md +52 -18
  40. package/docs/CONFIGURATION.md +25 -8
  41. package/docs/TOOLS_REFERENCE.md +12 -48
  42. package/docs/TOOL_PARAMETERS.md +19 -16
  43. package/docs/WORKFLOWS.md +7 -7
  44. package/package.json +1 -1
  45. package/profiles/balanced.json +1 -2
  46. package/profiles/code_focus.json +1 -2
  47. package/profiles/debug_intensive.json +0 -1
  48. package/profiles/full.json +2 -3
  49. package/profiles/minimal.json +1 -2
  50. package/profiles/research_power.json +1 -2
  51. package/profiles/workflow_builder.json +1 -2
  52. package/tools.config.json +15 -3
  53. package/workflows/code-architecture-review.yaml +5 -3
  54. package/workflows/creative-brainstorm-yaml.yaml +1 -1
  55. package/workflows/pingpong.yaml +5 -3
  56. package/workflows/system/README.md +1 -1
  57. package/workflows/system/verifier.yaml +8 -5
  58. 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
- 'openai_compare', 'openai_brainstorm', 'openai_gpt5_reason', 'openai_code_review', 'openai_explain',
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
@@ -29,7 +29,6 @@ export const KNOWN_TOOLS = [
29
29
  'openai_brainstorm',
30
30
  'openai_analyze',
31
31
  'openai_reason',
32
- 'openai_compare',
33
32
  'gpt5',
34
33
  'gpt5_mini',
35
34
  'gpt5_nano',
@@ -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 previous ideas with creative twists" },
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: "Combine all ideas into top 5 recommendations" },
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
- console.error(`✓ Interpolated '${key}': using summary (${value.summary.length} chars)`);
81
- return { match: fullMatch, replacement: value.summary };
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
- return synthesisStep.output;
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?.output || "Workflow completed";
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: 'gpt-5-nano',
63
+ alternativeTool: 'gpt5_mini',
64
64
  fallbackUsed: 'cheaper-model',
65
- warning: 'Falling back to GPT-5 Nano for cost efficiency'
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.1', {
47
- id: 'grok-4.1',
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.1-fast', {
55
- id: 'grok-4.1-fast',
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: 8,
59
- reasoning: 10,
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.1-fast',
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.1',
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-2.5-pro', 'qwen3-coder-480b'],
199
- 'fact_check': ['perplexity-sonar-pro', 'gpt5', 'gemini-2.5-pro'],
200
- 'code_verify': ['qwen3-coder-480b', 'gpt5', 'gemini-2.5-pro'],
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, GeminiModel } from "../tools/gemini-tools.js";
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 { GPT51_MODELS, TOOL_DEFAULTS, } from "../config/model-constants.js";
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.requirements || // qwen_coder, task-specific
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 : GPT51_MODELS.CODEX_MINI), maxTokens = options.maxTokens ?? toolDefaults.maxTokens ?? 2000, temperature = options.temperature ?? toolDefaults.temperature ?? 0.7, systemPrompt, } = options;
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" ? GeminiModel.FLASH : GeminiModel.PRO;
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 || GPT51_MODELS.FULL);
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), GPT51_MODELS.CODEX_MINI, 0.7, maxTokens), GPT51_MODELS.CODEX_MINI);
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), GPT51_MODELS.CODEX_MINI, temperature, maxTokens), GPT51_MODELS.CODEX_MINI);
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
- case "gpt5_nano":
220
- // Map old names to new GPT-5.1 models
221
- const gpt51Model = GPT51_MODELS.CODEX_MINI; // Always use cost-efficient codex-mini
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
- actualModel = GrokModel.GROK_4_FAST_REASONING; // Using fast reasoning (2M context, $0.20/$0.50)
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."), GPT51_MODELS.CODEX_MINI, 0.7, 500), GPT51_MODELS.CODEX_MINI);
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."), GPT51_MODELS.FULL, 0.8, maxTokens), GPT51_MODELS.FULL);
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."), GPT51_MODELS.FULL, 0.5, maxTokens), GPT51_MODELS.FULL);
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."), GPT51_MODELS.FULL, 0.6, maxTokens), GPT51_MODELS.FULL);
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."), GPT51_MODELS.CODEX_MINI, 0.7, maxTokens), GPT51_MODELS.CODEX_MINI);
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), GPT51_MODELS.CODEX_MINI, temperature, maxTokens), GPT51_MODELS.CODEX_MINI);
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", "gpt5_nano");
411
+ tools.push("openai_brainstorm", "openai_reason", "openai_code_review", "openai_explain", "gpt5_analyze", "openai_reason", "gpt5", "gpt5_mini");
385
412
  }
386
- if (process.env.XAI_API_KEY) {
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