converse-mcp-server 1.0.2 → 1.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/README.md +10 -10
- package/docs/API.md +1 -1
- package/docs/ARCHITECTURE.md +3 -3
- package/package.json +1 -1
- package/src/config.js +5 -13
- package/src/index.js +16 -4
- package/src/providers/google.js +75 -18
- package/src/providers/openai.js +171 -48
- package/src/providers/xai.js +44 -5
- package/src/tools/chat.js +29 -11
- package/src/tools/consensus.js +39 -12
- package/src/transport/httpTransport.js +1 -1
- package/src/utils/fileValidator.js +90 -0
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ GOOGLE_API_KEY=your_google_api_key_here
|
|
|
54
54
|
XAI_API_KEY=xai-your_xai_key_here
|
|
55
55
|
|
|
56
56
|
# Optional: Server configuration
|
|
57
|
-
PORT=
|
|
57
|
+
PORT=3157
|
|
58
58
|
LOG_LEVEL=info
|
|
59
59
|
MAX_MCP_OUTPUT_TOKENS=200000
|
|
60
60
|
|
|
@@ -172,14 +172,14 @@ LOG_LEVEL=debug npm run dev
|
|
|
172
172
|
|
|
173
173
|
```bash
|
|
174
174
|
# Server management
|
|
175
|
-
npm start # Start server (auto-kills existing server on port
|
|
175
|
+
npm start # Start server (auto-kills existing server on port 3157)
|
|
176
176
|
npm run start:clean # Start server without killing existing processes
|
|
177
177
|
npm run start:port # Start server on port 3001 (avoids port conflicts)
|
|
178
178
|
npm run dev # Development with hot reload (auto-kills existing server)
|
|
179
179
|
npm run dev:clean # Development without killing existing processes
|
|
180
180
|
npm run dev:port # Development on port 3001 (avoids port conflicts)
|
|
181
181
|
npm run dev:quiet # Development with minimal logging
|
|
182
|
-
npm run kill-server # Kill any server running on port
|
|
182
|
+
npm run kill-server # Kill any server running on port 3157
|
|
183
183
|
|
|
184
184
|
# Testing
|
|
185
185
|
npm test # Run all tests
|
|
@@ -198,15 +198,15 @@ npm run validate # Full validation (lint + test)
|
|
|
198
198
|
npm run build # Build for production
|
|
199
199
|
npm run debug # Start with debugger
|
|
200
200
|
npm run check-deps # Check for outdated dependencies
|
|
201
|
-
npm run kill-server # Kill any server running on port
|
|
201
|
+
npm run kill-server # Kill any server running on port 3157
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
### 💡 Development Notes
|
|
205
205
|
|
|
206
|
-
**Port Management**: The server runs on port
|
|
206
|
+
**Port Management**: The server runs on port 3157 by default for HTTP transport. If you encounter "EADDRINUSE" errors:
|
|
207
207
|
|
|
208
|
-
1. **Automatic cleanup**: `npm start` and `npm run dev` will automatically attempt to kill existing processes on port
|
|
209
|
-
2. **Manual cleanup**: Run `npm run kill-server` to manually free up port
|
|
208
|
+
1. **Automatic cleanup**: `npm start` and `npm run dev` will automatically attempt to kill existing processes on port 3157
|
|
209
|
+
2. **Manual cleanup**: Run `npm run kill-server` to manually free up port 3157
|
|
210
210
|
3. **Clean start**: Use `:clean` variants (`npm run start:clean`, `npm run dev:clean`) to skip auto-cleanup
|
|
211
211
|
4. **Persistent issues**: If port conflicts persist, manually kill Node.js processes or restart your terminal
|
|
212
212
|
|
|
@@ -223,7 +223,7 @@ npm start -- --transport=stdio
|
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
**Transport Modes**:
|
|
226
|
-
- **HTTP Transport** (default): `http://localhost:
|
|
226
|
+
- **HTTP Transport** (default): `http://localhost:3157/mcp` - Better for development and debugging
|
|
227
227
|
- **Stdio Transport**: Use `--transport=stdio` or set `MCP_TRANSPORT=stdio` for traditional stdio communication
|
|
228
228
|
|
|
229
229
|
### Testing with Real APIs
|
|
@@ -263,7 +263,7 @@ node final-integration-test.js
|
|
|
263
263
|
```
|
|
264
264
|
|
|
265
265
|
**Expected Results:**
|
|
266
|
-
- Server starts without errors on port
|
|
266
|
+
- Server starts without errors on port 3157
|
|
267
267
|
- All unit tests pass
|
|
268
268
|
- Real API tests connect successfully (if keys configured)
|
|
269
269
|
- Integration tests achieve >70% success rate
|
|
@@ -363,7 +363,7 @@ converse/
|
|
|
363
363
|
|
|
364
364
|
| Variable | Description | Default | Example |
|
|
365
365
|
|----------|-------------|---------|---------|
|
|
366
|
-
| `PORT` | Server port | `
|
|
366
|
+
| `PORT` | Server port | `3157` | `3157` |
|
|
367
367
|
| `LOG_LEVEL` | Logging level | `info` | `debug`, `info`, `error` |
|
|
368
368
|
| `MAX_MCP_OUTPUT_TOKENS` | Token response limit | `25000` | `200000` |
|
|
369
369
|
| `GOOGLE_LOCATION` | Google API region | `us-central1` | `us-central1` |
|
package/docs/API.md
CHANGED
|
@@ -12,7 +12,7 @@ The Converse MCP Server provides two main tools through the Model Context Protoc
|
|
|
12
12
|
The server supports two transport modes:
|
|
13
13
|
|
|
14
14
|
### HTTP Transport (Default)
|
|
15
|
-
- **Endpoint**: `http://localhost:
|
|
15
|
+
- **Endpoint**: `http://localhost:3157/mcp`
|
|
16
16
|
- **Protocol**: HTTP streaming with JSON-RPC 2.0
|
|
17
17
|
- **Usage**: Best for development, debugging, and web integrations
|
|
18
18
|
- **Features**: Health endpoints, CORS support, session management
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -65,10 +65,10 @@ const server = new Server(
|
|
|
65
65
|
{ capabilities: { tools: {} } }
|
|
66
66
|
);
|
|
67
67
|
|
|
68
|
-
// HTTP transport on port
|
|
68
|
+
// HTTP transport on port 3157 (default)
|
|
69
69
|
const httpTransport = new StreamableHTTPServerTransport({
|
|
70
70
|
host: 'localhost',
|
|
71
|
-
port:
|
|
71
|
+
port: 3157
|
|
72
72
|
});
|
|
73
73
|
```
|
|
74
74
|
|
|
@@ -288,7 +288,7 @@ export const config = {
|
|
|
288
288
|
|
|
289
289
|
// Server settings
|
|
290
290
|
server: {
|
|
291
|
-
port: parseInt(process.env.PORT) ||
|
|
291
|
+
port: parseInt(process.env.PORT) || 3157,
|
|
292
292
|
logLevel: process.env.LOG_LEVEL || 'info',
|
|
293
293
|
maxOutputTokens: parseInt(process.env.MAX_MCP_OUTPUT_TOKENS) || 25000
|
|
294
294
|
},
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -36,7 +36,7 @@ const logger = createLogger('config');
|
|
|
36
36
|
const CONFIG_SCHEMA = {
|
|
37
37
|
// Server configuration
|
|
38
38
|
server: {
|
|
39
|
-
PORT: { type: 'number', default:
|
|
39
|
+
PORT: { type: 'number', default: 3157, description: 'Server port' },
|
|
40
40
|
HOST: { type: 'string', default: 'localhost', description: 'Server host' },
|
|
41
41
|
NODE_ENV: { type: 'string', default: 'development', description: 'Environment mode' },
|
|
42
42
|
LOG_LEVEL: { type: 'string', default: 'info', description: 'Logging level' },
|
|
@@ -47,7 +47,7 @@ const CONFIG_SCHEMA = {
|
|
|
47
47
|
MCP_TRANSPORT: { type: 'string', default: 'http', description: 'MCP transport type (http or stdio)' },
|
|
48
48
|
|
|
49
49
|
// HTTP server settings
|
|
50
|
-
HTTP_PORT: { type: 'number', default:
|
|
50
|
+
HTTP_PORT: { type: 'number', default: 3157, description: 'HTTP server port' },
|
|
51
51
|
HTTP_HOST: { type: 'string', default: 'localhost', description: 'HTTP server host' },
|
|
52
52
|
HTTP_REQUEST_TIMEOUT: { type: 'number', default: 300000, description: 'HTTP request timeout in milliseconds (5 minutes)' },
|
|
53
53
|
HTTP_MAX_REQUEST_SIZE: { type: 'string', default: '10mb', description: 'Maximum HTTP request body size' },
|
|
@@ -219,16 +219,8 @@ export async function loadConfig() {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
const value = validateEnvVar(key, process.env[key], schema);
|
|
226
|
-
const configKey = key.toLowerCase().replace(/_/g, '');
|
|
227
|
-
config.providers[configKey] = value;
|
|
228
|
-
} catch (error) {
|
|
229
|
-
errors.push(error.message);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
222
|
+
// Initialize providers object (no provider-specific config currently)
|
|
223
|
+
config.providers = {};
|
|
232
224
|
|
|
233
225
|
// Load MCP configuration
|
|
234
226
|
for (const [key, schema] of Object.entries(CONFIG_SCHEMA.mcp)) {
|
|
@@ -303,7 +295,7 @@ export function getHttpTransportConfig(config) {
|
|
|
303
295
|
|
|
304
296
|
return {
|
|
305
297
|
// Server settings
|
|
306
|
-
port: transport.port ||
|
|
298
|
+
port: transport.port || 3157,
|
|
307
299
|
host: transport.host || 'localhost',
|
|
308
300
|
requestTimeout: transport.requesttimeout || 300000,
|
|
309
301
|
maxRequestSize: transport.maxrequestsize || '10mb',
|
package/src/index.js
CHANGED
|
@@ -34,7 +34,7 @@ Options:
|
|
|
34
34
|
|
|
35
35
|
Environment Variables:
|
|
36
36
|
MCP_TRANSPORT Transport type (http or stdio)
|
|
37
|
-
PORT HTTP server port (default:
|
|
37
|
+
PORT HTTP server port (default: 3157)
|
|
38
38
|
HOST HTTP server host (default: localhost)
|
|
39
39
|
|
|
40
40
|
Examples:
|
|
@@ -91,6 +91,20 @@ async function main() {
|
|
|
91
91
|
process.exit(0);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Determine transport type first to configure logging appropriately
|
|
95
|
+
const transportType = getTransportType();
|
|
96
|
+
|
|
97
|
+
// Set environment variable early for stdio transport to suppress console output
|
|
98
|
+
if (transportType === 'stdio') {
|
|
99
|
+
process.env.MCP_TRANSPORT = 'stdio';
|
|
100
|
+
// Reconfigure logger with updated environment
|
|
101
|
+
const { configureLogger } = await import('./utils/logger.js');
|
|
102
|
+
configureLogger({
|
|
103
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
104
|
+
isDevelopment: process.env.NODE_ENV === 'development'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
const serverTimer = startTimer('server-startup', 'server');
|
|
95
109
|
|
|
96
110
|
try {
|
|
@@ -102,9 +116,7 @@ async function main() {
|
|
|
102
116
|
|
|
103
117
|
// Get MCP client configuration
|
|
104
118
|
const mcpConfig = getMcpClientConfig(config);
|
|
105
|
-
|
|
106
|
-
// Determine transport type
|
|
107
|
-
const transportType = getTransportType();
|
|
119
|
+
|
|
108
120
|
logger.info('Using transport type', { data: { transport: transportType } });
|
|
109
121
|
|
|
110
122
|
logger.debug('Creating MCP server instance', {
|
package/src/providers/google.js
CHANGED
|
@@ -19,9 +19,10 @@ const SUPPORTED_MODELS = {
|
|
|
19
19
|
supportsImages: true,
|
|
20
20
|
supportsTemperature: true,
|
|
21
21
|
supportsThinking: true,
|
|
22
|
+
supportsWebSearch: true,
|
|
22
23
|
maxThinkingTokens: 24576,
|
|
23
24
|
timeout: 300000,
|
|
24
|
-
description: 'Gemini 2.0 Flash (1M context) - Latest fast model with experimental thinking, supports audio/video input',
|
|
25
|
+
description: 'Gemini 2.0 Flash (1M context) - Latest fast model with experimental thinking, supports audio/video input and grounding',
|
|
25
26
|
aliases: ['flash-2.0', 'flash2']
|
|
26
27
|
},
|
|
27
28
|
'gemini-2.0-flash-lite': {
|
|
@@ -33,9 +34,10 @@ const SUPPORTED_MODELS = {
|
|
|
33
34
|
supportsImages: false,
|
|
34
35
|
supportsTemperature: true,
|
|
35
36
|
supportsThinking: false,
|
|
37
|
+
supportsWebSearch: true,
|
|
36
38
|
maxThinkingTokens: 0,
|
|
37
39
|
timeout: 300000,
|
|
38
|
-
description: 'Gemini 2.0 Flash Lite (1M context) - Lightweight fast model, text-only',
|
|
40
|
+
description: 'Gemini 2.0 Flash Lite (1M context) - Lightweight fast model, text-only with grounding',
|
|
39
41
|
aliases: ['flashlite', 'flash-lite']
|
|
40
42
|
},
|
|
41
43
|
'gemini-2.5-flash': {
|
|
@@ -47,9 +49,10 @@ const SUPPORTED_MODELS = {
|
|
|
47
49
|
supportsImages: true,
|
|
48
50
|
supportsTemperature: true,
|
|
49
51
|
supportsThinking: true,
|
|
52
|
+
supportsWebSearch: true,
|
|
50
53
|
maxThinkingTokens: 24576,
|
|
51
54
|
timeout: 300000,
|
|
52
|
-
description: 'Ultra-fast (1M context) - Quick analysis, simple queries, rapid iterations',
|
|
55
|
+
description: 'Ultra-fast (1M context) - Quick analysis, simple queries, rapid iterations with grounding',
|
|
53
56
|
aliases: ['flash', 'flash2.5', 'gemini-flash', 'gemini-flash-2.5']
|
|
54
57
|
},
|
|
55
58
|
'gemini-2.5-pro': {
|
|
@@ -58,6 +61,7 @@ const SUPPORTED_MODELS = {
|
|
|
58
61
|
contextWindow: 1048576, // 1M tokens
|
|
59
62
|
maxOutputTokens: 65536,
|
|
60
63
|
supportsStreaming: true,
|
|
64
|
+
supportsWebSearch: true,
|
|
61
65
|
supportsImages: true,
|
|
62
66
|
supportsTemperature: true,
|
|
63
67
|
supportsThinking: true,
|
|
@@ -160,18 +164,63 @@ function convertMessagesToGemini(messages) {
|
|
|
160
164
|
// Google Gemini handles system prompts differently - they are typically prepended to the first user message
|
|
161
165
|
systemPrompt = content;
|
|
162
166
|
} else if (role === 'user') {
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
const parts = [];
|
|
168
|
+
|
|
169
|
+
// Handle complex content structure (array with text and images)
|
|
170
|
+
if (Array.isArray(content)) {
|
|
171
|
+
let textContent = '';
|
|
172
|
+
|
|
173
|
+
for (const item of content) {
|
|
174
|
+
if (item.type === 'text') {
|
|
175
|
+
textContent += item.text;
|
|
176
|
+
} else if (item.type === 'image' && item.source) {
|
|
177
|
+
// Convert Anthropic/Claude format to Google Gemini format
|
|
178
|
+
parts.push({
|
|
179
|
+
inlineData: {
|
|
180
|
+
mimeType: item.source.media_type,
|
|
181
|
+
data: item.source.data
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
debugLog(`[Google] Converting image: ${item.source.media_type}, data length: ${item.source.data.length}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Combine system prompt with text content if present
|
|
189
|
+
const finalTextContent = systemPrompt ? `${systemPrompt}\n\n${textContent}` : textContent;
|
|
190
|
+
if (finalTextContent) {
|
|
191
|
+
parts.unshift({ text: finalTextContent });
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// Simple string content
|
|
195
|
+
const userContent = systemPrompt ? `${systemPrompt}\n\n${content}` : content;
|
|
196
|
+
parts.push({ text: userContent });
|
|
197
|
+
}
|
|
198
|
+
|
|
165
199
|
contents.push({
|
|
166
200
|
role: 'user',
|
|
167
|
-
parts:
|
|
201
|
+
parts: parts
|
|
168
202
|
});
|
|
169
203
|
systemPrompt = null; // Only use system prompt once
|
|
170
204
|
} else if (role === 'assistant') {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
parts
|
|
174
|
-
|
|
205
|
+
// Handle assistant messages
|
|
206
|
+
if (Array.isArray(content)) {
|
|
207
|
+
const parts = [];
|
|
208
|
+
for (const item of content) {
|
|
209
|
+
if (item.type === 'text') {
|
|
210
|
+
parts.push({ text: item.text });
|
|
211
|
+
}
|
|
212
|
+
// Assistant messages typically don't have images, but handle if needed
|
|
213
|
+
}
|
|
214
|
+
contents.push({
|
|
215
|
+
role: 'model', // Google uses 'model' instead of 'assistant'
|
|
216
|
+
parts: parts
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
contents.push({
|
|
220
|
+
role: 'model',
|
|
221
|
+
parts: [{ text: content }]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
175
224
|
}
|
|
176
225
|
}
|
|
177
226
|
|
|
@@ -181,12 +230,12 @@ function convertMessagesToGemini(messages) {
|
|
|
181
230
|
/**
|
|
182
231
|
* Calculate thinking budget for models that support it
|
|
183
232
|
*/
|
|
184
|
-
function calculateThinkingBudget(modelConfig,
|
|
233
|
+
function calculateThinkingBudget(modelConfig, reasoning_effort) {
|
|
185
234
|
if (!modelConfig.supportsThinking || !modelConfig.maxThinkingTokens) {
|
|
186
235
|
return 0;
|
|
187
236
|
}
|
|
188
237
|
|
|
189
|
-
const budget = THINKING_BUDGETS[
|
|
238
|
+
const budget = THINKING_BUDGETS[reasoning_effort] || THINKING_BUDGETS.medium;
|
|
190
239
|
return Math.floor(modelConfig.maxThinkingTokens * budget);
|
|
191
240
|
}
|
|
192
241
|
|
|
@@ -281,7 +330,8 @@ export const googleProvider = {
|
|
|
281
330
|
temperature = 0.7,
|
|
282
331
|
maxTokens = null,
|
|
283
332
|
stream: _unused_stream = false, // Acknowledged but not used yet
|
|
284
|
-
|
|
333
|
+
reasoning_effort = 'medium',
|
|
334
|
+
use_websearch = false,
|
|
285
335
|
config,
|
|
286
336
|
...otherOptions
|
|
287
337
|
} = options;
|
|
@@ -321,15 +371,20 @@ export const googleProvider = {
|
|
|
321
371
|
}
|
|
322
372
|
|
|
323
373
|
// Add thinking configuration for models that support it
|
|
324
|
-
if (modelConfig.supportsThinking &&
|
|
325
|
-
const thinkingBudget = calculateThinkingBudget(modelConfig,
|
|
374
|
+
if (modelConfig.supportsThinking && reasoning_effort) {
|
|
375
|
+
const thinkingBudget = calculateThinkingBudget(modelConfig, reasoning_effort);
|
|
326
376
|
if (thinkingBudget > 0) {
|
|
327
377
|
generationConfig.thinkingConfig = { thinkingBudget };
|
|
328
378
|
}
|
|
329
379
|
}
|
|
330
380
|
|
|
381
|
+
// Add web search grounding if requested and model supports it
|
|
382
|
+
if (use_websearch && modelConfig.supportsWebSearch) {
|
|
383
|
+
generationConfig.tools = [{ googleSearch: {} }];
|
|
384
|
+
}
|
|
385
|
+
|
|
331
386
|
try {
|
|
332
|
-
debugLog(`[Google] Calling ${resolvedModel} with ${messages.length} messages`);
|
|
387
|
+
debugLog(`[Google] Calling ${resolvedModel} with ${messages.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`);
|
|
333
388
|
|
|
334
389
|
const startTime = Date.now();
|
|
335
390
|
|
|
@@ -371,8 +426,10 @@ export const googleProvider = {
|
|
|
371
426
|
usage,
|
|
372
427
|
response_time_ms: responseTime,
|
|
373
428
|
finish_reason: finishReason,
|
|
374
|
-
reasoning_effort: modelConfig.supportsThinking ?
|
|
375
|
-
provider: 'google'
|
|
429
|
+
reasoning_effort: modelConfig.supportsThinking ? reasoning_effort : null,
|
|
430
|
+
provider: 'google',
|
|
431
|
+
web_search_used: use_websearch && modelConfig.supportsWebSearch,
|
|
432
|
+
grounding_metadata: response.groundingMetadata || null
|
|
376
433
|
}
|
|
377
434
|
};
|
|
378
435
|
|
package/src/providers/openai.js
CHANGED
|
@@ -18,6 +18,8 @@ const SUPPORTED_MODELS = {
|
|
|
18
18
|
supportsStreaming: true,
|
|
19
19
|
supportsImages: true,
|
|
20
20
|
supportsTemperature: false,
|
|
21
|
+
supportsWebSearch: true,
|
|
22
|
+
supportsResponsesAPI: true,
|
|
21
23
|
timeout: 300000, // 5 minutes
|
|
22
24
|
description: 'Strong reasoning (200K context) - Logical problems, code generation, systematic analysis'
|
|
23
25
|
},
|
|
@@ -29,6 +31,8 @@ const SUPPORTED_MODELS = {
|
|
|
29
31
|
supportsStreaming: true,
|
|
30
32
|
supportsImages: true,
|
|
31
33
|
supportsTemperature: false,
|
|
34
|
+
supportsWebSearch: false, // o3-mini does not support web search
|
|
35
|
+
supportsResponsesAPI: true,
|
|
32
36
|
timeout: 300000,
|
|
33
37
|
description: 'Fast O3 variant (200K context) - Balanced performance/speed, moderate complexity',
|
|
34
38
|
aliases: ['o3mini', 'o3 mini']
|
|
@@ -41,6 +45,8 @@ const SUPPORTED_MODELS = {
|
|
|
41
45
|
supportsStreaming: true,
|
|
42
46
|
supportsImages: true,
|
|
43
47
|
supportsTemperature: false,
|
|
48
|
+
supportsWebSearch: true,
|
|
49
|
+
supportsResponsesAPI: true,
|
|
44
50
|
timeout: 1800000, // 30 minutes
|
|
45
51
|
description: 'Professional-grade reasoning (200K context) - EXTREMELY EXPENSIVE: Only for the most complex problems',
|
|
46
52
|
aliases: ['o3-pro', 'o3pro', 'o3 pro']
|
|
@@ -52,7 +58,9 @@ const SUPPORTED_MODELS = {
|
|
|
52
58
|
maxOutputTokens: 100000,
|
|
53
59
|
supportsStreaming: true,
|
|
54
60
|
supportsImages: true,
|
|
55
|
-
supportsTemperature:
|
|
61
|
+
supportsTemperature: false,
|
|
62
|
+
supportsWebSearch: true,
|
|
63
|
+
supportsResponsesAPI: true,
|
|
56
64
|
timeout: 180000, // 3 minutes
|
|
57
65
|
description: 'Latest reasoning model (200K context) - Optimized for shorter contexts, rapid reasoning',
|
|
58
66
|
aliases: ['o4mini', 'o4', 'o4 mini']
|
|
@@ -65,6 +73,8 @@ const SUPPORTED_MODELS = {
|
|
|
65
73
|
supportsStreaming: true,
|
|
66
74
|
supportsImages: true,
|
|
67
75
|
supportsTemperature: true,
|
|
76
|
+
supportsWebSearch: true,
|
|
77
|
+
supportsResponsesAPI: true,
|
|
68
78
|
timeout: 300000,
|
|
69
79
|
description: 'GPT-4.1 (1M context) - Advanced reasoning model with large context window',
|
|
70
80
|
aliases: ['gpt4.1', 'gpt-4.1', 'gpt 4.1']
|
|
@@ -77,6 +87,8 @@ const SUPPORTED_MODELS = {
|
|
|
77
87
|
supportsStreaming: true,
|
|
78
88
|
supportsImages: true,
|
|
79
89
|
supportsTemperature: true,
|
|
90
|
+
supportsWebSearch: true,
|
|
91
|
+
supportsResponsesAPI: true,
|
|
80
92
|
timeout: 180000,
|
|
81
93
|
description: 'GPT-4o (128K context) - Multimodal flagship model with vision capabilities',
|
|
82
94
|
aliases: ['gpt4o', 'gpt 4o', '4o']
|
|
@@ -89,6 +101,8 @@ const SUPPORTED_MODELS = {
|
|
|
89
101
|
supportsStreaming: true,
|
|
90
102
|
supportsImages: true,
|
|
91
103
|
supportsTemperature: true,
|
|
104
|
+
supportsWebSearch: true,
|
|
105
|
+
supportsResponsesAPI: true,
|
|
92
106
|
timeout: 120000,
|
|
93
107
|
description: 'GPT-4o-mini (128K context) - Fast and efficient multimodal model',
|
|
94
108
|
aliases: ['gpt4o-mini', 'gpt 4o mini', '4o mini', '4o-mini']
|
|
@@ -148,9 +162,9 @@ function validateApiKey(apiKey) {
|
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
/**
|
|
151
|
-
* Convert messages to OpenAI format
|
|
165
|
+
* Convert messages to OpenAI format, handling both Responses API and Chat Completions API
|
|
152
166
|
*/
|
|
153
|
-
function convertMessages(messages) {
|
|
167
|
+
function convertMessages(messages, useResponsesAPI = false) {
|
|
154
168
|
if (!Array.isArray(messages)) {
|
|
155
169
|
throw new OpenAIProviderError('Messages must be an array', 'INVALID_MESSAGES');
|
|
156
170
|
}
|
|
@@ -170,6 +184,60 @@ function convertMessages(messages) {
|
|
|
170
184
|
throw new OpenAIProviderError(`Message content is required at index ${index}`, 'MISSING_CONTENT');
|
|
171
185
|
}
|
|
172
186
|
|
|
187
|
+
// Handle complex content structure (array with text and images)
|
|
188
|
+
if (Array.isArray(content)) {
|
|
189
|
+
debugLog(`[OpenAI] Processing complex content array with ${content.length} items for ${useResponsesAPI ? 'Responses API' : 'Chat Completions API'}`);
|
|
190
|
+
if (useResponsesAPI) {
|
|
191
|
+
// Convert to Responses API format
|
|
192
|
+
const convertedContent = [];
|
|
193
|
+
|
|
194
|
+
for (const item of content) {
|
|
195
|
+
if (item.type === 'text') {
|
|
196
|
+
convertedContent.push({
|
|
197
|
+
type: 'input_text',
|
|
198
|
+
text: item.text
|
|
199
|
+
});
|
|
200
|
+
} else if (item.type === 'image' && item.source) {
|
|
201
|
+
// Convert Anthropic/Claude format to OpenAI Responses API format
|
|
202
|
+
const imageUrl = `data:${item.source.media_type};base64,${item.source.data}`;
|
|
203
|
+
debugLog(`[OpenAI] Converting image for Responses API: ${item.source.media_type}, data length: ${item.source.data.length}`);
|
|
204
|
+
convertedContent.push({
|
|
205
|
+
type: 'input_image',
|
|
206
|
+
image_url: imageUrl
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { role, content: convertedContent };
|
|
212
|
+
} else {
|
|
213
|
+
// Convert to Chat Completions API format
|
|
214
|
+
const convertedContent = [];
|
|
215
|
+
|
|
216
|
+
for (const item of content) {
|
|
217
|
+
if (item.type === 'text') {
|
|
218
|
+
convertedContent.push({
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: item.text
|
|
221
|
+
});
|
|
222
|
+
} else if (item.type === 'image' && item.source) {
|
|
223
|
+
// Convert Anthropic/Claude format to OpenAI Chat Completions format
|
|
224
|
+
const imageUrl = `data:${item.source.media_type};base64,${item.source.data}`;
|
|
225
|
+
debugLog(`[OpenAI] Converting image for Chat Completions API: ${item.source.media_type}, data length: ${item.source.data.length}`);
|
|
226
|
+
convertedContent.push({
|
|
227
|
+
type: 'image_url',
|
|
228
|
+
image_url: {
|
|
229
|
+
url: imageUrl,
|
|
230
|
+
detail: 'auto'
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { role, content: convertedContent };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Simple string content
|
|
173
241
|
return { role, content };
|
|
174
242
|
});
|
|
175
243
|
}
|
|
@@ -190,7 +258,8 @@ export const openaiProvider = {
|
|
|
190
258
|
temperature = 0.7,
|
|
191
259
|
maxTokens = null,
|
|
192
260
|
stream = false,
|
|
193
|
-
|
|
261
|
+
reasoning_effort = 'medium',
|
|
262
|
+
use_websearch = false,
|
|
194
263
|
config,
|
|
195
264
|
...otherOptions
|
|
196
265
|
} = options;
|
|
@@ -213,75 +282,129 @@ export const openaiProvider = {
|
|
|
213
282
|
const resolvedModel = resolveModelName(model);
|
|
214
283
|
const modelConfig = SUPPORTED_MODELS[resolvedModel] || {};
|
|
215
284
|
|
|
285
|
+
// Always use Responses API since all OpenAI models support it
|
|
286
|
+
// Only fallback to Chat Completions API if Responses API is explicitly not supported
|
|
287
|
+
const shouldUseResponsesAPI = modelConfig.supportsResponsesAPI !== false;
|
|
288
|
+
|
|
216
289
|
// Convert and validate messages
|
|
217
|
-
const openaiMessages = convertMessages(messages);
|
|
218
|
-
|
|
219
|
-
// Build request payload
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
290
|
+
const openaiMessages = convertMessages(messages, shouldUseResponsesAPI);
|
|
291
|
+
|
|
292
|
+
// Build request payload based on API type
|
|
293
|
+
let requestPayload;
|
|
294
|
+
|
|
295
|
+
if (shouldUseResponsesAPI) {
|
|
296
|
+
// Build Responses API payload
|
|
297
|
+
requestPayload = {
|
|
298
|
+
model: resolvedModel,
|
|
299
|
+
input: openaiMessages,
|
|
300
|
+
stream,
|
|
301
|
+
...otherOptions
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Add web search tools only if requested and model supports it
|
|
305
|
+
if (use_websearch && modelConfig.supportsWebSearch) {
|
|
306
|
+
requestPayload.tools = [{ type: 'web_search' }];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Add temperature if model supports it
|
|
310
|
+
if (modelConfig.supportsTemperature !== false && temperature !== undefined) {
|
|
311
|
+
requestPayload.temperature = Math.max(0, Math.min(2, temperature));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Add reasoning effort for thinking models (o3 series only)
|
|
315
|
+
if (resolvedModel.startsWith('o3') && reasoning_effort) {
|
|
316
|
+
requestPayload.reasoning = { effort: reasoning_effort };
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
// Build Chat Completions API payload
|
|
320
|
+
const { reasoning_effort: _unused, ...cleanOptions } = otherOptions;
|
|
321
|
+
requestPayload = {
|
|
322
|
+
model: resolvedModel,
|
|
323
|
+
messages: openaiMessages,
|
|
324
|
+
stream,
|
|
325
|
+
...cleanOptions
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// Add temperature if model supports it
|
|
329
|
+
if (modelConfig.supportsTemperature !== false && temperature !== undefined) {
|
|
330
|
+
requestPayload.temperature = Math.max(0, Math.min(2, temperature));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Add reasoning effort for thinking models (o3 series only)
|
|
334
|
+
if (resolvedModel.startsWith('o3') && reasoning_effort) {
|
|
335
|
+
requestPayload.reasoning_effort = reasoning_effort;
|
|
336
|
+
}
|
|
231
337
|
}
|
|
232
|
-
|
|
233
|
-
// Add max tokens if specified
|
|
338
|
+
|
|
339
|
+
// Add max tokens if specified (both APIs)
|
|
234
340
|
if (maxTokens) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
requestPayload.reasoning_effort = reasoningEffort;
|
|
341
|
+
if (shouldUseResponsesAPI) {
|
|
342
|
+
requestPayload.max_output_tokens = Math.min(maxTokens, modelConfig.maxOutputTokens || 100000);
|
|
343
|
+
} else {
|
|
344
|
+
requestPayload.max_tokens = Math.min(maxTokens, modelConfig.maxOutputTokens || 100000);
|
|
345
|
+
}
|
|
241
346
|
}
|
|
242
|
-
// Note: GPT-4o and other models don't support reasoning_effort parameter
|
|
243
|
-
// Only O3 series models support this parameter
|
|
244
347
|
|
|
245
348
|
try {
|
|
246
|
-
|
|
349
|
+
const apiType = shouldUseResponsesAPI ? 'Responses API' : 'Chat Completions API';
|
|
350
|
+
debugLog(`[OpenAI] Calling ${resolvedModel} via ${apiType} with ${openaiMessages.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with web search)' : ''}`);
|
|
247
351
|
|
|
248
352
|
const startTime = Date.now();
|
|
249
353
|
|
|
250
|
-
// Make the API call
|
|
251
|
-
|
|
354
|
+
// Make the API call based on API type
|
|
355
|
+
let response;
|
|
356
|
+
if (shouldUseResponsesAPI) {
|
|
357
|
+
response = await openai.responses.create(requestPayload);
|
|
358
|
+
} else {
|
|
359
|
+
response = await openai.chat.completions.create(requestPayload);
|
|
360
|
+
}
|
|
252
361
|
|
|
253
362
|
const responseTime = Date.now() - startTime;
|
|
254
363
|
debugLog(`[OpenAI] Response received in ${responseTime}ms`);
|
|
255
364
|
|
|
256
|
-
// Extract response data
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
365
|
+
// Extract response data based on API type
|
|
366
|
+
let content, stopReason, usage;
|
|
367
|
+
|
|
368
|
+
if (shouldUseResponsesAPI) {
|
|
369
|
+
// Handle Responses API response format
|
|
370
|
+
if (!response.output_text) {
|
|
371
|
+
throw new OpenAIProviderError('No output_text in Responses API response', 'NO_RESPONSE_CONTENT');
|
|
372
|
+
}
|
|
373
|
+
content = response.output_text;
|
|
374
|
+
stopReason = response.status || 'stop';
|
|
375
|
+
usage = response.usage || {};
|
|
376
|
+
} else {
|
|
377
|
+
// Handle Chat Completions API response format
|
|
378
|
+
const choice = response.choices[0];
|
|
379
|
+
if (!choice) {
|
|
380
|
+
throw new OpenAIProviderError('No response choice received from OpenAI', 'NO_RESPONSE_CHOICE');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
content = choice.message?.content;
|
|
384
|
+
if (!content) {
|
|
385
|
+
throw new OpenAIProviderError('No content in response from OpenAI', 'NO_RESPONSE_CONTENT');
|
|
386
|
+
}
|
|
387
|
+
stopReason = choice.finish_reason || 'stop';
|
|
388
|
+
usage = response.usage || {};
|
|
265
389
|
}
|
|
266
390
|
|
|
267
|
-
// Extract usage information
|
|
268
|
-
const usage = response.usage || {};
|
|
269
|
-
|
|
270
391
|
// Return unified response format
|
|
271
392
|
return {
|
|
272
393
|
content,
|
|
273
|
-
stop_reason:
|
|
394
|
+
stop_reason: stopReason,
|
|
274
395
|
rawResponse: response,
|
|
275
396
|
metadata: {
|
|
276
397
|
model: response.model || resolvedModel,
|
|
277
398
|
usage: {
|
|
278
|
-
input_tokens: usage.prompt_tokens || 0,
|
|
279
|
-
output_tokens: usage.completion_tokens || 0,
|
|
399
|
+
input_tokens: usage.prompt_tokens || usage.input_tokens || 0,
|
|
400
|
+
output_tokens: usage.completion_tokens || usage.output_tokens || 0,
|
|
280
401
|
total_tokens: usage.total_tokens || 0
|
|
281
402
|
},
|
|
282
403
|
response_time_ms: responseTime,
|
|
283
|
-
finish_reason:
|
|
284
|
-
provider: 'openai'
|
|
404
|
+
finish_reason: stopReason,
|
|
405
|
+
provider: 'openai',
|
|
406
|
+
api_type: apiType,
|
|
407
|
+
web_search_used: use_websearch && modelConfig.supportsWebSearch
|
|
285
408
|
}
|
|
286
409
|
};
|
|
287
410
|
|
package/src/providers/xai.js
CHANGED
|
@@ -18,8 +18,9 @@ const SUPPORTED_MODELS = {
|
|
|
18
18
|
supportsStreaming: true,
|
|
19
19
|
supportsImages: true,
|
|
20
20
|
supportsTemperature: true,
|
|
21
|
+
supportsWebSearch: true,
|
|
21
22
|
timeout: 300000, // 5 minutes
|
|
22
|
-
description: 'GROK-4 (256K context) - Latest advanced model from X.AI with image support',
|
|
23
|
+
description: 'GROK-4 (256K context) - Latest advanced model from X.AI with image support and live search',
|
|
23
24
|
aliases: ['grok', 'grok4', 'grok-4', 'grok-4-latest', 'grok 4', 'grok 4 latest']
|
|
24
25
|
},
|
|
25
26
|
'grok-3': {
|
|
@@ -30,6 +31,7 @@ const SUPPORTED_MODELS = {
|
|
|
30
31
|
supportsStreaming: true,
|
|
31
32
|
supportsImages: false,
|
|
32
33
|
supportsTemperature: true,
|
|
34
|
+
supportsWebSearch: false,
|
|
33
35
|
timeout: 300000,
|
|
34
36
|
description: 'GROK-3 (131K context) - Previous generation reasoning model from X.AI',
|
|
35
37
|
aliases: ['grok3', 'grok 3']
|
|
@@ -42,6 +44,7 @@ const SUPPORTED_MODELS = {
|
|
|
42
44
|
supportsStreaming: true,
|
|
43
45
|
supportsImages: false,
|
|
44
46
|
supportsTemperature: true,
|
|
47
|
+
supportsWebSearch: false,
|
|
45
48
|
timeout: 300000,
|
|
46
49
|
description: 'GROK-3 Fast (131K context) - Higher performance variant, faster processing but more expensive',
|
|
47
50
|
aliases: ['grok3fast', 'grok3-fast', 'grok 3 fast']
|
|
@@ -123,6 +126,33 @@ function convertMessages(messages) {
|
|
|
123
126
|
throw new XAIProviderError(`Message content is required at index ${index}`, 'MISSING_CONTENT');
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
// Handle complex content structure (array with text and images)
|
|
130
|
+
if (Array.isArray(content)) {
|
|
131
|
+
const convertedContent = [];
|
|
132
|
+
|
|
133
|
+
for (const item of content) {
|
|
134
|
+
if (item.type === 'text') {
|
|
135
|
+
convertedContent.push({
|
|
136
|
+
type: 'text',
|
|
137
|
+
text: item.text
|
|
138
|
+
});
|
|
139
|
+
} else if (item.type === 'image' && item.source) {
|
|
140
|
+
// Convert Anthropic/Claude format to OpenAI format for XAI
|
|
141
|
+
convertedContent.push({
|
|
142
|
+
type: 'image_url',
|
|
143
|
+
image_url: {
|
|
144
|
+
url: `data:${item.source.media_type};base64,${item.source.data}`,
|
|
145
|
+
detail: 'auto'
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
debugLog(`[XAI] Converting image: ${item.source.media_type}, data length: ${item.source.data.length}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { role, content: convertedContent };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Simple string content
|
|
126
156
|
return { role, content };
|
|
127
157
|
});
|
|
128
158
|
}
|
|
@@ -143,7 +173,8 @@ export const xaiProvider = {
|
|
|
143
173
|
temperature = 0.7,
|
|
144
174
|
maxTokens = null,
|
|
145
175
|
stream = false,
|
|
146
|
-
|
|
176
|
+
reasoning_effort = 'medium',
|
|
177
|
+
use_websearch = false,
|
|
147
178
|
config,
|
|
148
179
|
...otherOptions
|
|
149
180
|
} = options;
|
|
@@ -174,7 +205,7 @@ export const xaiProvider = {
|
|
|
174
205
|
const xaiMessages = convertMessages(messages);
|
|
175
206
|
|
|
176
207
|
// Filter out unsupported parameters for XAI/Grok models
|
|
177
|
-
const { reasoning_effort
|
|
208
|
+
const { reasoning_effort: _unused_reasoning_effort, ...supportedOptions } = otherOptions;
|
|
178
209
|
|
|
179
210
|
// Build request payload
|
|
180
211
|
const requestPayload = {
|
|
@@ -194,11 +225,18 @@ export const xaiProvider = {
|
|
|
194
225
|
requestPayload.max_tokens = Math.min(maxTokens, modelConfig.maxOutputTokens || 256000);
|
|
195
226
|
}
|
|
196
227
|
|
|
228
|
+
// Add web search parameters if requested and model supports it
|
|
229
|
+
if (use_websearch && modelConfig.supportsWebSearch) {
|
|
230
|
+
requestPayload.search_parameters = {
|
|
231
|
+
mode: 'auto' // Let the model decide when to use web search
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
197
235
|
// Note: XAI/Grok models don't currently support reasoning_effort parameter
|
|
198
236
|
// We silently ignore it for API consistency (no need to log warnings in tests)
|
|
199
237
|
|
|
200
238
|
try {
|
|
201
|
-
debugLog(`[XAI] Calling ${resolvedModel} with ${xaiMessages.length} messages`);
|
|
239
|
+
debugLog(`[XAI] Calling ${resolvedModel} with ${xaiMessages.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with live search)' : ''}`);
|
|
202
240
|
|
|
203
241
|
const startTime = Date.now();
|
|
204
242
|
|
|
@@ -236,7 +274,8 @@ export const xaiProvider = {
|
|
|
236
274
|
},
|
|
237
275
|
response_time_ms: responseTime,
|
|
238
276
|
finish_reason: choice.finish_reason,
|
|
239
|
-
provider: 'xai'
|
|
277
|
+
provider: 'xai',
|
|
278
|
+
web_search_used: use_websearch && modelConfig.supportsWebSearch
|
|
240
279
|
}
|
|
241
280
|
};
|
|
242
281
|
|
package/src/tools/chat.js
CHANGED
|
@@ -12,6 +12,7 @@ import { debugLog, debugError } from '../utils/console.js';
|
|
|
12
12
|
import { createLogger } from '../utils/logger.js';
|
|
13
13
|
import { CHAT_PROMPT } from '../systemPrompts.js';
|
|
14
14
|
import { applyTokenLimit, getTokenLimit } from '../utils/tokenLimiter.js';
|
|
15
|
+
import { validateAllPaths } from '../utils/fileValidator.js';
|
|
15
16
|
|
|
16
17
|
const logger = createLogger('chat');
|
|
17
18
|
|
|
@@ -65,6 +66,15 @@ export async function chatTool(args, dependencies) {
|
|
|
65
66
|
continuationId = generateContinuationId();
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// Validate file paths before processing
|
|
70
|
+
if (files.length > 0 || images.length > 0) {
|
|
71
|
+
const validation = await validateAllPaths({ files, images });
|
|
72
|
+
if (!validation.valid) {
|
|
73
|
+
logger.error('File validation failed', { errors: validation.errors });
|
|
74
|
+
return validation.errorResponse;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
// Process context (files, images, web search)
|
|
69
79
|
let contextMessage = null;
|
|
70
80
|
if (files.length > 0 || images.length > 0 || use_websearch) {
|
|
@@ -77,9 +87,10 @@ export async function chatTool(args, dependencies) {
|
|
|
77
87
|
|
|
78
88
|
const contextResult = await contextProcessor.processUnifiedContext(contextRequest);
|
|
79
89
|
|
|
80
|
-
// Create context message from files
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
// Create context message from files and images
|
|
91
|
+
const allProcessedFiles = [...contextResult.files, ...contextResult.images];
|
|
92
|
+
if (allProcessedFiles.length > 0) {
|
|
93
|
+
contextMessage = createFileContext(allProcessedFiles, {
|
|
83
94
|
includeMetadata: true,
|
|
84
95
|
includeErrors: true
|
|
85
96
|
});
|
|
@@ -111,16 +122,22 @@ export async function chatTool(args, dependencies) {
|
|
|
111
122
|
// Add conversation history
|
|
112
123
|
messages.push(...conversationHistory);
|
|
113
124
|
|
|
114
|
-
// Add
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
// Add user prompt with context
|
|
126
|
+
const userMessage = {
|
|
127
|
+
role: 'user',
|
|
128
|
+
content: prompt // default to simple string content
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// If we have context (files/images), create complex content array
|
|
132
|
+
if (contextMessage && contextMessage.content) {
|
|
133
|
+
// Create complex content array
|
|
134
|
+
userMessage.content = [
|
|
135
|
+
...contextMessage.content, // Include all file/image parts
|
|
136
|
+
{ type: 'text', text: prompt } // Add the user prompt as text
|
|
137
|
+
];
|
|
117
138
|
}
|
|
118
139
|
|
|
119
|
-
|
|
120
|
-
messages.push({
|
|
121
|
-
role: 'user',
|
|
122
|
-
content: prompt
|
|
123
|
-
});
|
|
140
|
+
messages.push(userMessage);
|
|
124
141
|
|
|
125
142
|
// Select provider
|
|
126
143
|
let selectedProvider;
|
|
@@ -160,6 +177,7 @@ export async function chatTool(args, dependencies) {
|
|
|
160
177
|
model: resolvedModel,
|
|
161
178
|
temperature,
|
|
162
179
|
reasoning_effort,
|
|
180
|
+
use_websearch: use_websearch,
|
|
163
181
|
config
|
|
164
182
|
};
|
|
165
183
|
|
package/src/tools/consensus.js
CHANGED
|
@@ -12,6 +12,7 @@ import { debugLog, debugError } from '../utils/console.js';
|
|
|
12
12
|
import { createLogger } from '../utils/logger.js';
|
|
13
13
|
import { CONSENSUS_PROMPT } from '../systemPrompts.js';
|
|
14
14
|
import { applyTokenLimit, getTokenLimit } from '../utils/tokenLimiter.js';
|
|
15
|
+
import { validateAllPaths } from '../utils/fileValidator.js';
|
|
15
16
|
|
|
16
17
|
const logger = createLogger('consensus');
|
|
17
18
|
|
|
@@ -44,7 +45,8 @@ export async function consensusTool(args, dependencies) {
|
|
|
44
45
|
enable_cross_feedback = true,
|
|
45
46
|
cross_feedback_prompt,
|
|
46
47
|
temperature = 0.2,
|
|
47
|
-
reasoning_effort = 'medium'
|
|
48
|
+
reasoning_effort = 'medium',
|
|
49
|
+
use_websearch = false
|
|
48
50
|
} = args;
|
|
49
51
|
|
|
50
52
|
let conversationHistory = [];
|
|
@@ -70,6 +72,18 @@ export async function consensusTool(args, dependencies) {
|
|
|
70
72
|
continuationId = generateContinuationId();
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
// Validate file paths before processing
|
|
76
|
+
if (relevant_files.length > 0 || images.length > 0) {
|
|
77
|
+
const validation = await validateAllPaths({
|
|
78
|
+
files: relevant_files,
|
|
79
|
+
images: images
|
|
80
|
+
});
|
|
81
|
+
if (!validation.valid) {
|
|
82
|
+
logger.error('File validation failed', { errors: validation.errors });
|
|
83
|
+
return validation.errorResponse;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
// Process context (files and images)
|
|
74
88
|
let contextMessage = null;
|
|
75
89
|
if (relevant_files.length > 0 || images.length > 0) {
|
|
@@ -81,9 +95,10 @@ export async function consensusTool(args, dependencies) {
|
|
|
81
95
|
|
|
82
96
|
const contextResult = await contextProcessor.processUnifiedContext(contextRequest);
|
|
83
97
|
|
|
84
|
-
// Create context message from files
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
// Create context message from files and images
|
|
99
|
+
const allProcessedFiles = [...contextResult.files, ...contextResult.images];
|
|
100
|
+
if (allProcessedFiles.length > 0) {
|
|
101
|
+
contextMessage = createFileContext(allProcessedFiles, {
|
|
87
102
|
includeMetadata: true,
|
|
88
103
|
includeErrors: true
|
|
89
104
|
});
|
|
@@ -107,16 +122,22 @@ export async function consensusTool(args, dependencies) {
|
|
|
107
122
|
// Add conversation history
|
|
108
123
|
messages.push(...conversationHistory);
|
|
109
124
|
|
|
110
|
-
// Add
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
// Add user prompt with context
|
|
126
|
+
const userMessage = {
|
|
127
|
+
role: 'user',
|
|
128
|
+
content: prompt // default to simple string content
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// If we have context (files/images), create complex content array
|
|
132
|
+
if (contextMessage && contextMessage.content) {
|
|
133
|
+
// Create complex content array
|
|
134
|
+
userMessage.content = [
|
|
135
|
+
...contextMessage.content, // Include all file/image parts
|
|
136
|
+
{ type: 'text', text: prompt } // Add the user prompt as text
|
|
137
|
+
];
|
|
113
138
|
}
|
|
114
139
|
|
|
115
|
-
|
|
116
|
-
messages.push({
|
|
117
|
-
role: 'user',
|
|
118
|
-
content: prompt
|
|
119
|
-
});
|
|
140
|
+
messages.push(userMessage);
|
|
120
141
|
|
|
121
142
|
// Resolve model specifications to provider calls
|
|
122
143
|
const providerCalls = [];
|
|
@@ -165,6 +186,7 @@ export async function consensusTool(args, dependencies) {
|
|
|
165
186
|
model: resolvedModelName, // Use resolved model name for API call
|
|
166
187
|
temperature,
|
|
167
188
|
reasoning_effort,
|
|
189
|
+
use_websearch: use_websearch,
|
|
168
190
|
config,
|
|
169
191
|
...modelSpec // Allow model-specific overrides
|
|
170
192
|
}
|
|
@@ -476,6 +498,11 @@ consensusTool.inputSchema = {
|
|
|
476
498
|
description: 'Reasoning depth for thinking models. Examples: "medium" (balanced - default), "high" (complex analysis), "max" (thorough evaluation). Default: "medium"',
|
|
477
499
|
default: 'medium'
|
|
478
500
|
},
|
|
501
|
+
use_websearch: {
|
|
502
|
+
type: 'boolean',
|
|
503
|
+
description: 'Enable web search for current information and best practices. Only works with models that support web search (OpenAI, XAI, Google). Example: true for recent developments, false for analysis. Default: false',
|
|
504
|
+
default: false
|
|
505
|
+
},
|
|
479
506
|
},
|
|
480
507
|
required: ['prompt', 'models'],
|
|
481
508
|
};
|
|
@@ -22,7 +22,7 @@ export class HTTPTransportServer {
|
|
|
22
22
|
constructor(config = {}) {
|
|
23
23
|
this.config = {
|
|
24
24
|
// Server settings
|
|
25
|
-
port: config.port ||
|
|
25
|
+
port: config.port || 3157,
|
|
26
26
|
host: config.host || 'localhost',
|
|
27
27
|
requestTimeout: config.requestTimeout || 300000,
|
|
28
28
|
maxRequestSize: config.maxRequestSize || '10mb',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Validator Utility
|
|
3
|
+
*
|
|
4
|
+
* Validates that file paths exist before processing them.
|
|
5
|
+
* Returns early with clear error messages if any files are not found.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { access, constants } from 'fs/promises';
|
|
9
|
+
import { resolve, isAbsolute } from 'path';
|
|
10
|
+
import { createToolError } from '../tools/index.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validate that all provided file paths exist
|
|
14
|
+
* @param {string[]} filePaths - Array of file paths to validate
|
|
15
|
+
* @param {string} fileType - Type of files being validated (e.g., 'file', 'image')
|
|
16
|
+
* @returns {Promise<{valid: boolean, missingPaths: string[], error?: object}>}
|
|
17
|
+
*/
|
|
18
|
+
export async function validateFilePaths(filePaths, fileType = 'file') {
|
|
19
|
+
if (!Array.isArray(filePaths) || filePaths.length === 0) {
|
|
20
|
+
return { valid: true, missingPaths: [] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const missingPaths = [];
|
|
24
|
+
|
|
25
|
+
for (const filePath of filePaths) {
|
|
26
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
27
|
+
missingPaths.push(`Invalid path: ${filePath}`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Convert to absolute path if needed
|
|
32
|
+
const absolutePath = isAbsolute(filePath)
|
|
33
|
+
? filePath
|
|
34
|
+
: resolve(process.cwd(), filePath);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// Check if file exists and is readable
|
|
38
|
+
await access(absolutePath, constants.R_OK);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// Keep the original path in the error message for clarity
|
|
41
|
+
missingPaths.push(filePath);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (missingPaths.length > 0) {
|
|
46
|
+
const errorMessage = `The following ${fileType}${missingPaths.length > 1 ? 's' : ''} could not be found: ${missingPaths.join(', ')}`;
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
missingPaths,
|
|
50
|
+
error: createToolError(errorMessage)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { valid: true, missingPaths: [] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validate both files and images together
|
|
59
|
+
* @param {object} paths - Object containing files and images arrays
|
|
60
|
+
* @returns {Promise<{valid: boolean, errors: string[], errorResponse?: object}>}
|
|
61
|
+
*/
|
|
62
|
+
export async function validateAllPaths({ files = [], images = [] }) {
|
|
63
|
+
const errors = [];
|
|
64
|
+
|
|
65
|
+
// Validate regular files
|
|
66
|
+
if (files.length > 0) {
|
|
67
|
+
const fileValidation = await validateFilePaths(files, 'file');
|
|
68
|
+
if (!fileValidation.valid) {
|
|
69
|
+
errors.push(`Files not found: ${fileValidation.missingPaths.join(', ')}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Validate image files
|
|
74
|
+
if (images.length > 0) {
|
|
75
|
+
const imageValidation = await validateFilePaths(images, 'image');
|
|
76
|
+
if (!imageValidation.valid) {
|
|
77
|
+
errors.push(`Images not found: ${imageValidation.missingPaths.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (errors.length > 0) {
|
|
82
|
+
return {
|
|
83
|
+
valid: false,
|
|
84
|
+
errors,
|
|
85
|
+
errorResponse: createToolError(errors.join('. '))
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { valid: true, errors: [] };
|
|
90
|
+
}
|