converse-mcp-server 2.22.1 → 2.22.3
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 +4 -0
- package/package.json +10 -10
- package/src/config.js +6 -0
- package/src/providers/codex.js +71 -31
package/.env.example
CHANGED
|
@@ -77,6 +77,10 @@ OPENROUTER_REFERER=https://github.com/FallDownTheSystem/converse
|
|
|
77
77
|
# WARNING: Interactive policies may cause hangs in server/headless mode
|
|
78
78
|
# CODEX_APPROVAL_POLICY=never
|
|
79
79
|
|
|
80
|
+
# Default Codex model (default: gpt-5.5)
|
|
81
|
+
# Options: gpt-5.5, gpt-5.3-codex, gpt-5-codex
|
|
82
|
+
# CODEX_MODEL=gpt-5.5
|
|
83
|
+
|
|
80
84
|
# ============================================
|
|
81
85
|
# Server Configuration
|
|
82
86
|
# ============================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "converse-mcp-server",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.3",
|
|
4
4
|
"description": "Converse MCP Server - Converse with other LLMs with chat and consensus tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -94,14 +94,14 @@
|
|
|
94
94
|
".env.example"
|
|
95
95
|
],
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
98
|
-
"@anthropic-ai/sdk": "^0.
|
|
97
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.118",
|
|
98
|
+
"@anthropic-ai/sdk": "^0.90.0",
|
|
99
99
|
"@github/copilot-sdk": "^0.2.2",
|
|
100
100
|
"@google/genai": "^1.50.1",
|
|
101
|
-
"@mistralai/mistralai": "^2.2.
|
|
101
|
+
"@mistralai/mistralai": "^2.2.1",
|
|
102
102
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
103
|
-
"@openai/codex-sdk": "^0.
|
|
104
|
-
"ai": "^6.0.
|
|
103
|
+
"@openai/codex-sdk": "^0.123.0",
|
|
104
|
+
"ai": "^6.0.168",
|
|
105
105
|
"ai-sdk-provider-gemini-cli": "^2.0.1",
|
|
106
106
|
"cors": "^2.8.6",
|
|
107
107
|
"dotenv": "^17.4.2",
|
|
@@ -110,14 +110,14 @@
|
|
|
110
110
|
"nanoid": "^5.1.9",
|
|
111
111
|
"openai": "^6.34.0",
|
|
112
112
|
"p-limit": "^7.3.0",
|
|
113
|
-
"vite": "^8.0.
|
|
113
|
+
"vite": "^8.0.10"
|
|
114
114
|
},
|
|
115
115
|
"devDependencies": {
|
|
116
|
-
"@vitest/coverage-v8": "^4.1.
|
|
116
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
117
117
|
"cross-env": "^10.1.0",
|
|
118
|
-
"eslint": "^10.2.
|
|
118
|
+
"eslint": "^10.2.1",
|
|
119
119
|
"prettier": "^3.8.3",
|
|
120
120
|
"rimraf": "^6.1.3",
|
|
121
|
-
"vitest": "^4.1.
|
|
121
|
+
"vitest": "^4.1.5"
|
|
122
122
|
}
|
|
123
123
|
}
|
package/src/config.js
CHANGED
|
@@ -276,6 +276,12 @@ const CONFIG_SCHEMA = {
|
|
|
276
276
|
description:
|
|
277
277
|
'Approval policy (never | untrusted | on-failure | on-request)',
|
|
278
278
|
},
|
|
279
|
+
CODEX_MODEL: {
|
|
280
|
+
type: 'string',
|
|
281
|
+
default: 'gpt-5.5',
|
|
282
|
+
description:
|
|
283
|
+
'Default Codex model (e.g., gpt-5.5, gpt-5.3-codex, gpt-5-codex)',
|
|
284
|
+
},
|
|
279
285
|
|
|
280
286
|
// Copilot configuration
|
|
281
287
|
COPILOT_TOOL_ACCESS: {
|
package/src/providers/codex.js
CHANGED
|
@@ -21,22 +21,24 @@ import { normalizeExtendedPath } from '../utils/pathUtils.js';
|
|
|
21
21
|
const SUPPORTED_MODELS = {
|
|
22
22
|
codex: {
|
|
23
23
|
modelName: 'codex',
|
|
24
|
-
friendlyName: 'OpenAI Codex (GPT-5.
|
|
24
|
+
friendlyName: 'OpenAI Codex (GPT-5.5)',
|
|
25
25
|
contextWindow: 400000,
|
|
26
26
|
maxOutputTokens: 128000,
|
|
27
27
|
supportsStreaming: true,
|
|
28
|
-
supportsImages:
|
|
28
|
+
supportsImages: true, // Codex SDK 0.118+ supports images via --image (local_image input)
|
|
29
29
|
supportsTemperature: false, // Codex manages temperature internally
|
|
30
30
|
supportsWebSearch: false, // Codex accesses files directly, not web
|
|
31
31
|
timeout: 600000, // 10 minutes
|
|
32
32
|
description:
|
|
33
|
-
'OpenAI Codex agentic coding assistant with local file access and tool execution (GPT-5.
|
|
33
|
+
'OpenAI Codex agentic coding assistant with local file access and tool execution (GPT-5.5)',
|
|
34
34
|
aliases: [
|
|
35
35
|
'gpt-5-codex',
|
|
36
36
|
'gpt5-codex',
|
|
37
37
|
'gpt-5.2-codex',
|
|
38
38
|
'gpt-5.3-codex',
|
|
39
39
|
'gpt5.3-codex',
|
|
40
|
+
'gpt-5.5',
|
|
41
|
+
'gpt5.5',
|
|
40
42
|
],
|
|
41
43
|
},
|
|
42
44
|
};
|
|
@@ -91,14 +93,19 @@ async function getCodexSDK() {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
/**
|
|
94
|
-
* Convert message array to
|
|
95
|
-
* Codex expects single prompts
|
|
96
|
+
* Convert message array to Codex SDK Input (string | UserInput[])
|
|
97
|
+
* Codex expects single prompts (new thread) or incremental input (resumed thread);
|
|
98
|
+
* history is managed SDK-side.
|
|
96
99
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
+
* Returns a plain string when the last user message is text-only, or an array
|
|
101
|
+
* of { type: 'text' | 'local_image' } parts when images are present. The SDK
|
|
102
|
+
* passes local_image paths to the CLI via --image.
|
|
103
|
+
*
|
|
104
|
+
* Images must be on-disk files — Converse stores the original path in
|
|
105
|
+
* metadata.path (chat.js / consensus.js set includeMetadata: true). Images
|
|
106
|
+
* without a path (e.g. pasted base64 with no metadata) are skipped.
|
|
100
107
|
*/
|
|
101
|
-
function
|
|
108
|
+
function convertMessagesToCodexInput(messages) {
|
|
102
109
|
if (!Array.isArray(messages)) {
|
|
103
110
|
throw new CodexProviderError(
|
|
104
111
|
'Messages must be an array',
|
|
@@ -113,7 +120,6 @@ function convertMessagesToPrompt(messages) {
|
|
|
113
120
|
);
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
// Find last user message
|
|
117
123
|
const lastUserMessage = messages.filter((m) => m.role === 'user').pop();
|
|
118
124
|
|
|
119
125
|
if (!lastUserMessage) {
|
|
@@ -123,28 +129,46 @@ function convertMessagesToPrompt(messages) {
|
|
|
123
129
|
);
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
// Extract text content from message
|
|
127
132
|
if (typeof lastUserMessage.content === 'string') {
|
|
128
133
|
return lastUserMessage.content;
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
// Handle array content (multimodal format)
|
|
132
136
|
if (Array.isArray(lastUserMessage.content)) {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
const parts = [];
|
|
138
|
+
let droppedImages = 0;
|
|
139
|
+
for (const item of lastUserMessage.content) {
|
|
140
|
+
if (item.type === 'text' && item.text) {
|
|
141
|
+
parts.push({ type: 'text', text: item.text });
|
|
142
|
+
} else if (item.type === 'image') {
|
|
143
|
+
const imagePath = item.metadata?.path || item.metadata?.originalPath;
|
|
144
|
+
if (imagePath) {
|
|
145
|
+
parts.push({ type: 'local_image', path: imagePath });
|
|
146
|
+
} else {
|
|
147
|
+
droppedImages += 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
136
151
|
|
|
137
|
-
|
|
138
|
-
const hasImages = lastUserMessage.content.some(
|
|
139
|
-
(item) => item.type === 'image',
|
|
140
|
-
);
|
|
141
|
-
if (hasImages) {
|
|
152
|
+
if (droppedImages > 0) {
|
|
142
153
|
debugLog(
|
|
143
|
-
|
|
154
|
+
`[Codex] Skipped ${droppedImages} image(s) without a file path — Codex requires on-disk images`,
|
|
144
155
|
);
|
|
145
156
|
}
|
|
146
157
|
|
|
147
|
-
|
|
158
|
+
if (parts.length === 0) {
|
|
159
|
+
throw new CodexProviderError(
|
|
160
|
+
'Message contained no usable text or image parts',
|
|
161
|
+
ErrorCodes.INVALID_MESSAGES,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Collapse to plain string when there are no images — keeps the non-image
|
|
166
|
+
// path identical to the legacy behavior and slightly simpler for the SDK.
|
|
167
|
+
if (parts.every((p) => p.type === 'text')) {
|
|
168
|
+
return parts.map((p) => p.text).join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return parts;
|
|
148
172
|
}
|
|
149
173
|
|
|
150
174
|
throw new CodexProviderError(
|
|
@@ -153,6 +177,18 @@ function convertMessagesToPrompt(messages) {
|
|
|
153
177
|
);
|
|
154
178
|
}
|
|
155
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Extract the combined text from a Codex SDK Input for prompt-based checks
|
|
182
|
+
* like $imagegen detection.
|
|
183
|
+
*/
|
|
184
|
+
function extractPromptText(input) {
|
|
185
|
+
if (typeof input === 'string') return input;
|
|
186
|
+
return input
|
|
187
|
+
.filter((p) => p.type === 'text')
|
|
188
|
+
.map((p) => p.text)
|
|
189
|
+
.join('\n\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
156
192
|
/**
|
|
157
193
|
* Get thread ID from continuation metadata
|
|
158
194
|
* Codex thread IDs are stored in continuation store for resumption
|
|
@@ -185,12 +221,14 @@ function mapReasoningEffort(effort) {
|
|
|
185
221
|
}
|
|
186
222
|
|
|
187
223
|
/**
|
|
188
|
-
* Create stream generator for Codex streaming responses
|
|
189
|
-
*
|
|
224
|
+
* Create stream generator for Codex streaming responses.
|
|
225
|
+
* `input` is the Codex SDK Input (string | UserInput[]) — strings for plain
|
|
226
|
+
* text turns, arrays when images are attached.
|
|
227
|
+
* Yields raw Codex SDK events that will be normalized by ProviderStreamNormalizer.
|
|
190
228
|
*/
|
|
191
|
-
async function* createStreamingGenerator(thread,
|
|
229
|
+
async function* createStreamingGenerator(thread, input, signal) {
|
|
192
230
|
try {
|
|
193
|
-
const { events } = await thread.runStreamed(
|
|
231
|
+
const { events } = await thread.runStreamed(input, { signal });
|
|
194
232
|
|
|
195
233
|
for await (const event of events) {
|
|
196
234
|
// Check for cancellation
|
|
@@ -256,8 +294,9 @@ export const codexProvider = {
|
|
|
256
294
|
// Get Codex SDK
|
|
257
295
|
const Codex = await getCodexSDK();
|
|
258
296
|
|
|
259
|
-
// Convert messages to
|
|
260
|
-
const
|
|
297
|
+
// Convert messages to Codex SDK input (string or structured parts with images)
|
|
298
|
+
const input = convertMessagesToCodexInput(messages);
|
|
299
|
+
const promptText = extractPromptText(input);
|
|
261
300
|
|
|
262
301
|
// Get thread ID if resuming conversation
|
|
263
302
|
const threadId =
|
|
@@ -289,7 +328,7 @@ export const codexProvider = {
|
|
|
289
328
|
// into image generation via $imagegen — otherwise Codex can't save the
|
|
290
329
|
// generated file. Leave higher modes (workspace-write, danger-full-access)
|
|
291
330
|
// alone so an explicit user choice is never downgraded or escalated.
|
|
292
|
-
const wantsImageGen = /\$imagegen\b/i.test(
|
|
331
|
+
const wantsImageGen = /\$imagegen\b/i.test(promptText);
|
|
293
332
|
const sandboxMode =
|
|
294
333
|
wantsImageGen && configuredSandboxMode === 'read-only'
|
|
295
334
|
? 'workspace-write'
|
|
@@ -307,6 +346,7 @@ export const codexProvider = {
|
|
|
307
346
|
|
|
308
347
|
// Create or resume thread
|
|
309
348
|
const threadOptions = {
|
|
349
|
+
model: config.providers?.codexmodel,
|
|
310
350
|
workingDirectory,
|
|
311
351
|
sandboxMode,
|
|
312
352
|
skipGitRepoCheck,
|
|
@@ -324,12 +364,12 @@ export const codexProvider = {
|
|
|
324
364
|
// WORKAROUND: SDK's thread.run() hangs due to missing break after turn.completed
|
|
325
365
|
// Always use streaming internally, consume synchronously when stream=false
|
|
326
366
|
if (stream) {
|
|
327
|
-
return createStreamingGenerator(thread,
|
|
367
|
+
return createStreamingGenerator(thread, input, signal);
|
|
328
368
|
}
|
|
329
369
|
|
|
330
370
|
// Synchronous mode: consume streaming internally and return complete response
|
|
331
371
|
const startTime = Date.now();
|
|
332
|
-
const generator = createStreamingGenerator(thread,
|
|
372
|
+
const generator = createStreamingGenerator(thread, input, signal);
|
|
333
373
|
|
|
334
374
|
let content = '';
|
|
335
375
|
let usage = null;
|