aiexecode 1.0.90 → 1.0.92

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.

Potentially problematic release.


This version of aiexecode might be problematic. Click here for more details.

Files changed (50) hide show
  1. package/README.md +1 -0
  2. package/index.js +13 -11
  3. package/mcp-agent-lib/init.sh +3 -0
  4. package/mcp-agent-lib/package-lock.json +14 -1
  5. package/mcp-agent-lib/package.json +4 -6
  6. package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
  7. package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
  8. package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
  9. package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
  10. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
  11. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
  12. package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
  13. package/mcp-agent-lib/sampleMCPHost/index.js +182 -63
  14. package/mcp-agent-lib/sampleMCPHost/mcp_config.json +7 -1
  15. package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
  16. package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
  17. package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
  18. package/mcp-agent-lib/src/mcp_client.js +129 -67
  19. package/mcp-agent-lib/src/mcp_message_logger.js +516 -0
  20. package/package.json +3 -1
  21. package/payload_viewer/out/404/index.html +1 -1
  22. package/payload_viewer/out/404.html +1 -1
  23. package/payload_viewer/out/index.html +1 -1
  24. package/payload_viewer/out/index.txt +1 -1
  25. package/src/LLMClient/client.js +992 -0
  26. package/src/LLMClient/converters/input-normalizer.js +238 -0
  27. package/src/LLMClient/converters/responses-to-claude.js +454 -0
  28. package/src/LLMClient/converters/responses-to-gemini.js +648 -0
  29. package/src/LLMClient/converters/responses-to-ollama.js +348 -0
  30. package/src/LLMClient/errors.js +372 -0
  31. package/src/LLMClient/index.js +31 -0
  32. package/src/commands/apikey.js +10 -22
  33. package/src/commands/model.js +28 -28
  34. package/src/commands/reasoning_effort.js +9 -23
  35. package/src/config/ai_models.js +212 -0
  36. package/src/config/feature_flags.js +1 -1
  37. package/src/frontend/App.js +5 -10
  38. package/src/frontend/components/CurrentModelView.js +0 -33
  39. package/src/frontend/components/Footer.js +3 -3
  40. package/src/frontend/components/ModelListView.js +30 -87
  41. package/src/frontend/components/ModelUpdatedView.js +7 -142
  42. package/src/frontend/components/SetupWizard.js +37 -32
  43. package/src/system/ai_request.js +57 -42
  44. package/src/util/config.js +26 -4
  45. package/src/util/setup_wizard.js +1 -6
  46. package/mcp-agent-lib/.claude/settings.local.json +0 -9
  47. package/src/config/openai_models.js +0 -152
  48. /package/payload_viewer/out/_next/static/{w4dMVYalgk7djrLxRxWiE → d0-fu2rgYnshgGFPxr1CR}/_buildManifest.js +0 -0
  49. /package/payload_viewer/out/_next/static/{w4dMVYalgk7djrLxRxWiE → d0-fu2rgYnshgGFPxr1CR}/_clientMiddlewareManifest.json +0 -0
  50. /package/payload_viewer/out/_next/static/{w4dMVYalgk7djrLxRxWiE → d0-fu2rgYnshgGFPxr1CR}/_ssgManifest.js +0 -0
@@ -0,0 +1,648 @@
1
+ /**
2
+ * Convert Responses API format to Gemini format
3
+ */
4
+
5
+ /**
6
+ * Remove markdown code block wrapper from text
7
+ * Handles all possible cases mechanically without regex
8
+ * @param {string} text - Raw text possibly wrapped in markdown code block
9
+ * @returns {string} Cleaned text
10
+ */
11
+ function removeMarkdownCodeBlock(text) {
12
+ // Case 1: null, undefined, empty string, non-string
13
+ if (!text || typeof text !== 'string') {
14
+ return text;
15
+ }
16
+
17
+ let result = text;
18
+ let startIndex = 0;
19
+ let endIndex = result.length;
20
+
21
+ // ============================================
22
+ // PHASE 1: Process opening ``` marker
23
+ // ============================================
24
+
25
+ // Case 2: Text doesn't start with ``` at all
26
+ if (!result.startsWith('```')) {
27
+ // No opening marker - return as is (trimmed)
28
+ return result.trim();
29
+ }
30
+
31
+ // Case 3: Text starts with ``` - remove it
32
+ startIndex = 3;
33
+
34
+ // Now check what comes after ```
35
+ // Possible cases:
36
+ // - ```\n... (no language identifier)
37
+ // - ```json\n... (with language identifier)
38
+ // - ```json ... (language identifier with spaces)
39
+ // - ``` json\n... (spaces before language identifier)
40
+ // - ```{... (no newline, content starts immediately - edge case)
41
+
42
+ // Skip any spaces immediately after ```
43
+ while (startIndex < endIndex && result[startIndex] === ' ') {
44
+ startIndex++;
45
+ }
46
+
47
+ // Case 4: After ``` and optional spaces, look for newline
48
+ const firstNewlinePos = result.indexOf('\n', startIndex);
49
+
50
+ if (firstNewlinePos === -1) {
51
+ // Case 5: No newline found after ``` - entire rest is content
52
+ // Example: ```{"key":"value"}``` or ```{"key":"value"}
53
+ // Content starts right after ``` (and any spaces we skipped)
54
+ // Don't advance startIndex further - keep content starting position
55
+ } else {
56
+ // Case 6: Newline exists - check what's between ``` and \n
57
+ const betweenBackticksAndNewline = result.substring(startIndex, firstNewlinePos);
58
+
59
+ // Trim to check if it's a language identifier
60
+ const trimmed = betweenBackticksAndNewline.trim();
61
+
62
+ if (trimmed.length === 0) {
63
+ // Case 7: Nothing between ``` and \n (or only whitespace)
64
+ // Example: ```\n... or ``` \n...
65
+ // Content starts after the newline
66
+ startIndex = firstNewlinePos + 1;
67
+ } else {
68
+ // Case 8: Something exists between ``` and \n
69
+ // Check if it looks like a language identifier (alphanumeric, underscore, dash only)
70
+ let isLanguageIdentifier = true;
71
+ for (let i = 0; i < trimmed.length; i++) {
72
+ const char = trimmed[i];
73
+ const isValid = (char >= 'a' && char <= 'z') ||
74
+ (char >= 'A' && char <= 'Z') ||
75
+ (char >= '0' && char <= '9') ||
76
+ char === '_' || char === '-';
77
+ if (!isValid) {
78
+ isLanguageIdentifier = false;
79
+ break;
80
+ }
81
+ }
82
+
83
+ if (isLanguageIdentifier) {
84
+ // Case 9: It's a language identifier like "json", "javascript", etc.
85
+ // Skip it and the newline - content starts after newline
86
+ startIndex = firstNewlinePos + 1;
87
+ } else {
88
+ // Case 10: It's actual content (contains special chars like {, [, etc.)
89
+ // Example: ```{"key": "value"}\n...
90
+ // Keep this content - don't skip anything
91
+ // startIndex already points to start of this content
92
+ }
93
+ }
94
+ }
95
+
96
+ // ============================================
97
+ // PHASE 2: Process closing ``` marker
98
+ // ============================================
99
+
100
+ // Work backwards from end to find closing ```
101
+
102
+ // Case 11: Find where actual content ends (before any trailing ``` and whitespace)
103
+
104
+ // Step 1: Skip trailing whitespace from the end
105
+ let checkPos = endIndex - 1;
106
+ while (checkPos >= startIndex && (result[checkPos] === ' ' || result[checkPos] === '\t' ||
107
+ result[checkPos] === '\n' || result[checkPos] === '\r')) {
108
+ checkPos--;
109
+ }
110
+
111
+ // Step 2: Check if we have ``` at this position (working backwards)
112
+ if (checkPos >= startIndex + 2 &&
113
+ result[checkPos] === '`' &&
114
+ result[checkPos - 1] === '`' &&
115
+ result[checkPos - 2] === '`') {
116
+ // Case 12: Found closing ``` marker
117
+ // Move back before the ```
118
+ endIndex = checkPos - 2;
119
+
120
+ // Also trim any whitespace before the ```
121
+ while (endIndex > startIndex && (result[endIndex - 1] === ' ' || result[endIndex - 1] === '\t' ||
122
+ result[endIndex - 1] === '\n' || result[endIndex - 1] === '\r')) {
123
+ endIndex--;
124
+ }
125
+ } else {
126
+ // Case 13: No closing ``` found
127
+ // Use position after trimming trailing whitespace
128
+ // Add 1 because checkPos is the last non-whitespace character's index
129
+ endIndex = checkPos + 1;
130
+ }
131
+
132
+ // ============================================
133
+ // PHASE 3: Extract and return cleaned content
134
+ // ============================================
135
+
136
+ // Case 14: Extract the content between processed boundaries
137
+ if (startIndex >= endIndex) {
138
+ // Case 15: Nothing left after processing (empty content between markers)
139
+ return '';
140
+ }
141
+
142
+ result = result.substring(startIndex, endIndex);
143
+
144
+ // Case 16: Final trim to remove any remaining edge whitespace
145
+ return result.trim();
146
+ }
147
+
148
+ /**
149
+ * Remove Gemini-incompatible fields from JSON Schema
150
+ * @param {Object} schema - JSON Schema object
151
+ * @returns {Object} Cleaned schema
152
+ */
153
+ function cleanSchemaForGemini(schema) {
154
+ if (!schema || typeof schema !== 'object') {
155
+ return schema;
156
+ }
157
+
158
+ // Deep clone to avoid mutating original
159
+ const cleaned = JSON.parse(JSON.stringify(schema));
160
+
161
+ // Recursive function to clean schema objects
162
+ function cleanObject(obj) {
163
+ if (!obj || typeof obj !== 'object') {
164
+ return;
165
+ }
166
+
167
+ // Remove Gemini-incompatible fields
168
+ delete obj.additionalProperties;
169
+ delete obj.$schema;
170
+ delete obj.$id;
171
+ delete obj.$ref;
172
+ delete obj.definitions;
173
+ delete obj.$defs;
174
+
175
+ // Recursively clean nested objects
176
+ for (const key in obj) {
177
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
178
+ if (Array.isArray(obj[key])) {
179
+ obj[key].forEach(item => cleanObject(item));
180
+ } else {
181
+ cleanObject(obj[key]);
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ cleanObject(cleaned);
188
+ return cleaned;
189
+ }
190
+
191
+ /**
192
+ * Convert Responses API request to Gemini format
193
+ * @param {Object} responsesRequest - Responses API format request
194
+ * @returns {Object} Gemini format request
195
+ */
196
+ export function convertResponsesRequestToGeminiFormat(responsesRequest) {
197
+ const geminiRequest = {
198
+ contents: []
199
+ };
200
+
201
+ // Convert input to contents
202
+ if (typeof responsesRequest.input === 'string') {
203
+ // Simple string input
204
+ geminiRequest.contents.push({
205
+ role: 'user',
206
+ parts: [{ text: responsesRequest.input }]
207
+ });
208
+ } else if (Array.isArray(responsesRequest.input)) {
209
+ // Array input - could be messages or output items
210
+ for (const item of responsesRequest.input) {
211
+ // Handle output items (no role, has type)
212
+ if (!item.role && item.type) {
213
+ if (item.type === 'message') {
214
+ // Message item from output
215
+ const parts = [];
216
+ if (item.content && Array.isArray(item.content)) {
217
+ for (const contentBlock of item.content) {
218
+ if (contentBlock.type === 'output_text' && contentBlock.text) {
219
+ parts.push({ text: contentBlock.text });
220
+ }
221
+ }
222
+ }
223
+ if (parts.length > 0) {
224
+ geminiRequest.contents.push({
225
+ role: 'model',
226
+ parts: parts
227
+ });
228
+ }
229
+ } else if (item.type === 'function_call') {
230
+ // Function call from output - convert to Gemini functionCall
231
+ geminiRequest.contents.push({
232
+ role: 'model',
233
+ parts: [
234
+ {
235
+ functionCall: {
236
+ name: item.name,
237
+ args: JSON.parse(item.arguments || '{}')
238
+ }
239
+ }
240
+ ]
241
+ });
242
+ } else if (item.type === 'function_call_output') {
243
+ // Function call output - convert to functionResponse
244
+ let response;
245
+ if (typeof item.output === 'string') {
246
+ try {
247
+ // Try to parse as JSON
248
+ response = JSON.parse(item.output);
249
+ } catch {
250
+ // If not valid JSON, wrap plain text in object
251
+ response = { result: item.output };
252
+ }
253
+ } else {
254
+ response = item.output;
255
+ }
256
+
257
+ geminiRequest.contents.push({
258
+ role: 'function',
259
+ parts: [
260
+ {
261
+ functionResponse: {
262
+ name: geminiRequest.contents[geminiRequest.contents.length - 1]?.parts?.[0]?.functionCall?.name || 'unknown',
263
+ response: response
264
+ }
265
+ }
266
+ ]
267
+ });
268
+ }
269
+ // Skip other types like 'reasoning'
270
+ continue;
271
+ }
272
+
273
+ if (item.role && item.content) {
274
+ // Message format
275
+ // Handle content that might be an array (OpenAI Responses API format)
276
+ const content = Array.isArray(item.content)
277
+ ? item.content.map(c => c.type === 'input_text' || c.type === 'text' ? c.text : c).filter(Boolean).join('\n')
278
+ : item.content;
279
+
280
+ if (item.role === 'system') {
281
+ // System messages go to systemInstruction in Gemini
282
+ geminiRequest.systemInstruction = {
283
+ parts: [{ text: content }]
284
+ };
285
+ } else if (item.role === 'tool') {
286
+ // Tool result
287
+ const lastContent = geminiRequest.contents[geminiRequest.contents.length - 1];
288
+ if (lastContent && lastContent.role === 'model') {
289
+ // Add function response
290
+ let response;
291
+ if (typeof item.content === 'string') {
292
+ try {
293
+ // Try to parse as JSON
294
+ response = JSON.parse(item.content);
295
+ } catch {
296
+ // If not valid JSON, wrap plain text in object
297
+ response = { result: item.content };
298
+ }
299
+ } else {
300
+ response = item.content;
301
+ }
302
+
303
+ geminiRequest.contents.push({
304
+ role: 'function',
305
+ parts: [
306
+ {
307
+ functionResponse: {
308
+ name: item.name,
309
+ response: response
310
+ }
311
+ }
312
+ ]
313
+ });
314
+ }
315
+ } else if (item.role === 'assistant' && Array.isArray(item.content)) {
316
+ // Assistant with output array (might contain function_call items)
317
+ const parts = [];
318
+
319
+ for (const outputItem of item.content) {
320
+ if (outputItem.type === 'message' && outputItem.content) {
321
+ // Message item - extract text content
322
+ for (const contentBlock of outputItem.content) {
323
+ if (contentBlock.type === 'output_text' && contentBlock.text) {
324
+ parts.push({ text: contentBlock.text });
325
+ }
326
+ }
327
+ } else if (outputItem.type === 'function_call') {
328
+ // Function call - convert to Gemini functionCall
329
+ parts.push({
330
+ functionCall: {
331
+ name: outputItem.name,
332
+ args: JSON.parse(outputItem.arguments || '{}')
333
+ }
334
+ });
335
+ }
336
+ }
337
+
338
+ // Add message only if there's content
339
+ if (parts.length > 0) {
340
+ geminiRequest.contents.push({
341
+ role: 'model',
342
+ parts: parts
343
+ });
344
+ }
345
+ } else if (item.role === 'assistant') {
346
+ // Assistant message (simple text)
347
+ const content = Array.isArray(item.content)
348
+ ? item.content.map(c => c.type === 'input_text' || c.type === 'text' ? c.text : c).filter(Boolean).join('\n')
349
+ : item.content;
350
+ geminiRequest.contents.push({
351
+ role: 'model',
352
+ parts: [{ text: content }]
353
+ });
354
+ } else {
355
+ // User message
356
+ geminiRequest.contents.push({
357
+ role: 'user',
358
+ parts: [{ text: content }]
359
+ });
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ // Handle instructions (system message)
366
+ if (responsesRequest.instructions) {
367
+ geminiRequest.systemInstruction = {
368
+ parts: [{ text: responsesRequest.instructions }]
369
+ };
370
+ }
371
+
372
+ // Convert tools from Responses API format to Gemini format
373
+ // Responses API: { type: 'custom', name, description }
374
+ // Gemini: { functionDeclarations: [{ name, description, parameters }] }
375
+ if (responsesRequest.tools && Array.isArray(responsesRequest.tools)) {
376
+ geminiRequest.tools = [
377
+ {
378
+ functionDeclarations: responsesRequest.tools.map(tool => {
379
+ let parameters;
380
+
381
+ if (tool.type === 'function' && tool.function) {
382
+ // Chat Completions format with nested function object
383
+ parameters = tool.function.parameters || {
384
+ type: 'object',
385
+ properties: {}
386
+ };
387
+ } else if (tool.type === 'function' && !tool.function) {
388
+ // Chat Completions format without nested function object
389
+ parameters = tool.parameters || {
390
+ type: 'object',
391
+ properties: {}
392
+ };
393
+ } else if (tool.type === 'custom') {
394
+ // Responses API format
395
+ parameters = tool.input_schema || {
396
+ type: 'object',
397
+ properties: {}
398
+ };
399
+ } else if (tool.name && tool.description) {
400
+ // Already in Gemini format (name, description, parameters)
401
+ parameters = tool.parameters || {
402
+ type: 'object',
403
+ properties: {}
404
+ };
405
+ } else {
406
+ // Fallback: extract name, description, parameters
407
+ parameters = tool.parameters || tool.input_schema || {
408
+ type: 'object',
409
+ properties: {}
410
+ };
411
+ }
412
+
413
+ // Clean parameters for Gemini compatibility
414
+ const cleanedParameters = cleanSchemaForGemini(parameters);
415
+
416
+ // Build function declaration
417
+ if (tool.type === 'function' && tool.function) {
418
+ return {
419
+ name: tool.function.name,
420
+ description: tool.function.description || `Function: ${tool.function.name}`,
421
+ parameters: cleanedParameters
422
+ };
423
+ } else if (tool.type === 'function' && !tool.function) {
424
+ return {
425
+ name: tool.name,
426
+ description: tool.description || `Function: ${tool.name}`,
427
+ parameters: cleanedParameters
428
+ };
429
+ } else if (tool.type === 'custom') {
430
+ return {
431
+ name: tool.name,
432
+ description: tool.description || `Tool: ${tool.name}`,
433
+ parameters: cleanedParameters
434
+ };
435
+ } else if (tool.name && tool.description) {
436
+ return {
437
+ name: tool.name,
438
+ description: tool.description,
439
+ parameters: cleanedParameters
440
+ };
441
+ }
442
+
443
+ return {
444
+ name: tool.name || 'unknown',
445
+ description: tool.description || 'No description',
446
+ parameters: cleanedParameters
447
+ };
448
+ })
449
+ }
450
+ ];
451
+ }
452
+
453
+ // Generation config
454
+ geminiRequest.generationConfig = {};
455
+
456
+ // Max output tokens
457
+ if (responsesRequest.max_output_tokens !== undefined) {
458
+ geminiRequest.generationConfig.maxOutputTokens = responsesRequest.max_output_tokens;
459
+ }
460
+
461
+ // Temperature (Gemini supports this)
462
+ if (responsesRequest.temperature !== undefined) {
463
+ geminiRequest.generationConfig.temperature = responsesRequest.temperature;
464
+ }
465
+
466
+ // Top-p (Gemini supports this)
467
+ if (responsesRequest.top_p !== undefined) {
468
+ geminiRequest.generationConfig.topP = responsesRequest.top_p;
469
+ }
470
+
471
+ // Remove empty generationConfig
472
+ if (Object.keys(geminiRequest.generationConfig).length === 0) {
473
+ delete geminiRequest.generationConfig;
474
+ }
475
+
476
+ // Tool choice (Responses API to Gemini)
477
+ // Map tool_choice to Gemini's function_calling_config
478
+ if (responsesRequest.tool_choice !== undefined && geminiRequest.tools) {
479
+ geminiRequest.toolConfig = {
480
+ function_calling_config: {}
481
+ };
482
+
483
+ if (typeof responsesRequest.tool_choice === 'string') {
484
+ if (responsesRequest.tool_choice === 'none') {
485
+ // NONE mode: prohibit function calls
486
+ geminiRequest.toolConfig.function_calling_config.mode = 'NONE';
487
+ } else if (responsesRequest.tool_choice === 'required') {
488
+ // ANY mode: force function call
489
+ geminiRequest.toolConfig.function_calling_config.mode = 'ANY';
490
+ } else if (responsesRequest.tool_choice === 'auto') {
491
+ // AUTO mode: model decides (default, can be omitted)
492
+ geminiRequest.toolConfig.function_calling_config.mode = 'AUTO';
493
+ }
494
+ } else if (responsesRequest.tool_choice?.type === 'function' || responsesRequest.tool_choice?.type === 'custom') {
495
+ // Specific tool - use ANY mode with allowed_function_names
496
+ const toolName = responsesRequest.tool_choice.function?.name || responsesRequest.tool_choice.name;
497
+ geminiRequest.toolConfig.function_calling_config.mode = 'ANY';
498
+ geminiRequest.toolConfig.function_calling_config.allowed_function_names = [toolName];
499
+ }
500
+ }
501
+
502
+ return geminiRequest;
503
+ }
504
+
505
+ /**
506
+ * Convert Gemini response to Responses API format
507
+ * @param {Object} geminiResponse - Gemini format response
508
+ * @param {string} model - Model name
509
+ * @param {Object} originalRequest - Original request for context
510
+ * @returns {Object} Responses API format response
511
+ */
512
+ export function convertGeminiResponseToResponsesFormat(geminiResponse, model = 'gemini-2.5-flash', originalRequest = {}) {
513
+ const output = [];
514
+ let outputText = '';
515
+
516
+ // Process candidates
517
+ if (geminiResponse.candidates && geminiResponse.candidates.length > 0) {
518
+ const candidate = geminiResponse.candidates[0];
519
+
520
+ if (candidate.content && candidate.content.parts) {
521
+ // First pass: collect raw text
522
+ for (const part of candidate.content.parts) {
523
+ if (part.text) {
524
+ outputText += part.text;
525
+ }
526
+ }
527
+
528
+ // Clean up markdown code blocks from output text (Gemini often wraps JSON in ```json...```)
529
+ let cleanedText = outputText;
530
+ if (originalRequest.text?.format?.type === 'json_schema') {
531
+ cleanedText = removeMarkdownCodeBlock(outputText);
532
+ }
533
+
534
+ // Second pass: build message content with cleaned text
535
+ const messageContent = [];
536
+ let hasText = false;
537
+
538
+ for (const part of candidate.content.parts) {
539
+ if (part.text && !hasText) {
540
+ // Add cleaned text as a single content block (only once)
541
+ messageContent.push({
542
+ type: 'output_text',
543
+ text: cleanedText,
544
+ annotations: []
545
+ });
546
+ hasText = true;
547
+ } else if (part.functionCall) {
548
+ // Function call - add as separate function_call item
549
+ const callId = `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
550
+ output.push({
551
+ id: `fc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
552
+ type: 'function_call',
553
+ status: 'completed',
554
+ arguments: JSON.stringify(part.functionCall.args || {}),
555
+ call_id: callId,
556
+ name: part.functionCall.name
557
+ });
558
+ }
559
+ }
560
+
561
+ // Add message with text content if any
562
+ if (messageContent.length > 0) {
563
+ output.push({
564
+ id: `msg_${Date.now()}`,
565
+ type: 'message',
566
+ status: 'completed',
567
+ role: 'assistant',
568
+ content: messageContent
569
+ });
570
+ }
571
+ }
572
+ }
573
+
574
+ // If no output items, create empty message
575
+ if (output.length === 0) {
576
+ output.push({
577
+ id: `msg_${Date.now()}`,
578
+ type: 'message',
579
+ status: 'completed',
580
+ role: 'assistant',
581
+ content: []
582
+ });
583
+ }
584
+
585
+ // Use cleaned text for output_text field
586
+ let cleanedOutputText = outputText;
587
+ if (originalRequest.text?.format?.type === 'json_schema') {
588
+ cleanedOutputText = removeMarkdownCodeBlock(outputText);
589
+ }
590
+
591
+ // Build Responses API response with ALL required fields
592
+ const responsesResponse = {
593
+ id: `resp_${Date.now()}`,
594
+ object: 'response',
595
+ created_at: Math.floor(Date.now() / 1000),
596
+ status: 'completed',
597
+ background: false,
598
+ billing: {
599
+ payer: 'developer'
600
+ },
601
+ error: null,
602
+ incomplete_details: null,
603
+ instructions: originalRequest.instructions || null,
604
+ max_output_tokens: originalRequest.max_output_tokens || null,
605
+ max_tool_calls: null,
606
+ model: model,
607
+ output: output,
608
+ parallel_tool_calls: false,
609
+ previous_response_id: null,
610
+ prompt_cache_key: null,
611
+ prompt_cache_retention: null,
612
+ reasoning: {
613
+ effort: originalRequest.reasoning?.effort || null,
614
+ summary: originalRequest.reasoning?.summary || null
615
+ },
616
+ safety_identifier: null,
617
+ service_tier: 'default',
618
+ store: originalRequest.store !== undefined ? originalRequest.store : true,
619
+ temperature: originalRequest.temperature !== undefined ? originalRequest.temperature : 1,
620
+ text: originalRequest.text || {
621
+ format: {
622
+ type: 'text'
623
+ },
624
+ verbosity: 'medium'
625
+ },
626
+ tool_choice: originalRequest.tool_choice || 'auto',
627
+ tools: originalRequest.tools || [],
628
+ top_logprobs: 0,
629
+ top_p: originalRequest.top_p !== undefined ? originalRequest.top_p : 1,
630
+ truncation: 'disabled',
631
+ usage: {
632
+ input_tokens: geminiResponse.usageMetadata?.promptTokenCount || 0,
633
+ input_tokens_details: {
634
+ cached_tokens: 0
635
+ },
636
+ output_tokens: geminiResponse.usageMetadata?.candidatesTokenCount || 0,
637
+ output_tokens_details: {
638
+ reasoning_tokens: 0
639
+ },
640
+ total_tokens: geminiResponse.usageMetadata?.totalTokenCount || 0
641
+ },
642
+ user: null,
643
+ metadata: {},
644
+ output_text: cleanedOutputText
645
+ };
646
+
647
+ return responsesResponse;
648
+ }