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,238 @@
1
+ /**
2
+ * Normalize input to OpenAI Responses API format
3
+ *
4
+ * Converts various input formats to the standard Responses API format:
5
+ * input: [
6
+ * { type: 'message', content: 'text' },
7
+ * { type: 'function_call', call_id: 'xxx', name: 'tool', arguments: '{}' },
8
+ * { type: 'function_call_output', call_id: 'xxx', output: 'result' }
9
+ * ]
10
+ */
11
+
12
+ /**
13
+ * Convert input to OpenAI Responses API format
14
+ *
15
+ * OpenAI Responses API actually accepts:
16
+ * - String input directly
17
+ * - Array of messages in Chat Completions format (role-based)
18
+ *
19
+ * @param {string|Array} input - Input in various formats
20
+ * @returns {string|Array} Normalized input (string or role-based array)
21
+ */
22
+ export function normalizeInput(input) {
23
+ // Simple string input - return as-is
24
+ if (typeof input === 'string') {
25
+ return input;
26
+ }
27
+
28
+ // Array input
29
+ if (Array.isArray(input) && input.length > 0) {
30
+ const firstItem = input[0];
31
+
32
+ // Already in Chat Completions format (role-based) - return as-is
33
+ if (firstItem.role) {
34
+ return input;
35
+ }
36
+
37
+ // Convert from our custom format to Chat Completions format
38
+ if (firstItem.type === 'message' || firstItem.type === 'function_call' || firstItem.type === 'function_call_output') {
39
+ return convertResponsesInputToChatCompletions(input);
40
+ }
41
+ }
42
+
43
+ // Fallback to string
44
+ return String(input);
45
+ }
46
+
47
+ /**
48
+ * Convert our internal format to Chat Completions format
49
+ * @param {Array} items - Internal format items
50
+ * @returns {Array} Chat Completions format messages
51
+ */
52
+ function convertResponsesInputToChatCompletions(items) {
53
+ const messages = [];
54
+ const pendingToolCalls = [];
55
+ let currentAssistantContent = '';
56
+
57
+ for (const item of items) {
58
+ if (item.type === 'message') {
59
+ // If we have pending tool calls, create assistant message first
60
+ if (pendingToolCalls.length > 0) {
61
+ messages.push({
62
+ role: 'assistant',
63
+ content: currentAssistantContent || null,
64
+ tool_calls: [...pendingToolCalls]
65
+ });
66
+ pendingToolCalls.length = 0;
67
+ currentAssistantContent = '';
68
+ }
69
+
70
+ // Regular message
71
+ messages.push({
72
+ role: 'user',
73
+ content: item.content
74
+ });
75
+ } else if (item.type === 'function_call') {
76
+ // Tool call
77
+ pendingToolCalls.push({
78
+ id: item.call_id,
79
+ type: 'function',
80
+ function: {
81
+ name: item.name,
82
+ arguments: item.arguments
83
+ }
84
+ });
85
+ } else if (item.type === 'function_call_output') {
86
+ // If we have pending tool calls, create assistant message first
87
+ if (pendingToolCalls.length > 0) {
88
+ messages.push({
89
+ role: 'assistant',
90
+ content: null,
91
+ tool_calls: [...pendingToolCalls]
92
+ });
93
+ pendingToolCalls.length = 0;
94
+ }
95
+
96
+ // Tool result
97
+ messages.push({
98
+ role: 'tool',
99
+ tool_call_id: item.call_id,
100
+ content: item.output
101
+ });
102
+ }
103
+ }
104
+
105
+ // Flush any remaining tool calls
106
+ if (pendingToolCalls.length > 0) {
107
+ messages.push({
108
+ role: 'assistant',
109
+ content: null,
110
+ tool_calls: [...pendingToolCalls]
111
+ });
112
+ }
113
+
114
+ return messages;
115
+ }
116
+
117
+ /**
118
+ * LEGACY: Convert Chat Completions messages to Responses API input
119
+ * @param {Array} messages - Chat Completions format messages
120
+ * @returns {Array} Responses API format input
121
+ */
122
+ function convertChatCompletionsToResponsesInput(messages) {
123
+ const result = [];
124
+
125
+ for (const msg of messages) {
126
+ const { role, content } = msg;
127
+
128
+ if (role === 'system') {
129
+ // System messages become regular messages with system content
130
+ // Note: In real Responses API, system messages go to 'instructions' parameter
131
+ result.push({
132
+ type: 'message',
133
+ content: `[System]: ${content}`
134
+ });
135
+ } else if (role === 'user') {
136
+ result.push({
137
+ type: 'message',
138
+ content: content
139
+ });
140
+ } else if (role === 'assistant') {
141
+ // Assistant message with potential tool calls
142
+ if (Array.isArray(content)) {
143
+ // Content is array (Responses API style already)
144
+ for (const item of content) {
145
+ if (typeof item === 'string') {
146
+ result.push({ type: 'message', content: item });
147
+ } else if (item.type === 'text') {
148
+ result.push({ type: 'message', content: item.text });
149
+ } else if (item.type === 'function_call') {
150
+ result.push({
151
+ type: 'function_call',
152
+ call_id: item.call_id,
153
+ name: item.name,
154
+ arguments: typeof item.input === 'string' ? item.input : JSON.stringify(item.input)
155
+ });
156
+ }
157
+ }
158
+ } else if (typeof content === 'string') {
159
+ // Simple text content
160
+ result.push({ type: 'message', content: content });
161
+ }
162
+
163
+ // Check for tool_calls (Chat Completions format)
164
+ if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
165
+ for (const toolCall of msg.tool_calls) {
166
+ result.push({
167
+ type: 'function_call',
168
+ call_id: toolCall.id,
169
+ name: toolCall.function?.name || toolCall.name,
170
+ arguments: typeof toolCall.function?.arguments === 'string'
171
+ ? toolCall.function.arguments
172
+ : JSON.stringify(toolCall.function?.arguments || toolCall.arguments || {})
173
+ });
174
+ }
175
+ }
176
+ } else if (role === 'tool') {
177
+ // Tool result
178
+ result.push({
179
+ type: 'function_call_output',
180
+ call_id: msg.tool_call_id || msg.call_id,
181
+ output: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
182
+ });
183
+ }
184
+ }
185
+
186
+ return result;
187
+ }
188
+
189
+ /**
190
+ * Convert Responses API output back to Chat Completions messages
191
+ * Useful for compatibility
192
+ * @param {Array} output - Responses API output array
193
+ * @returns {Array} Chat Completions format messages
194
+ */
195
+ export function convertResponsesOutputToMessages(output) {
196
+ const messages = [];
197
+ const toolCalls = [];
198
+ let textContent = '';
199
+
200
+ for (const item of output) {
201
+ if (item.type === 'message') {
202
+ // Extract text from message
203
+ if (item.content && Array.isArray(item.content)) {
204
+ for (const contentItem of item.content) {
205
+ if (contentItem.type === 'output_text' && contentItem.text) {
206
+ textContent += contentItem.text;
207
+ }
208
+ }
209
+ }
210
+ } else if (item.type === 'function_call') {
211
+ // Tool call
212
+ toolCalls.push({
213
+ id: item.call_id,
214
+ type: 'function',
215
+ function: {
216
+ name: item.name,
217
+ arguments: typeof item.arguments === 'string'
218
+ ? item.arguments
219
+ : JSON.stringify(item.input || item.arguments || {})
220
+ }
221
+ });
222
+ }
223
+ }
224
+
225
+ // Create assistant message
226
+ const assistantMsg = {
227
+ role: 'assistant',
228
+ content: textContent || null
229
+ };
230
+
231
+ if (toolCalls.length > 0) {
232
+ assistantMsg.tool_calls = toolCalls;
233
+ }
234
+
235
+ messages.push(assistantMsg);
236
+
237
+ return messages;
238
+ }
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Convert Responses API format to Claude format
3
+ */
4
+
5
+ import { getMaxTokens } from '../../config/ai_models.js';
6
+
7
+ /**
8
+ * Convert Responses API request to Claude format
9
+ * @param {Object} responsesRequest - Responses API format request
10
+ * @returns {Object} Claude format request
11
+ */
12
+ export function convertResponsesRequestToClaudeFormat(responsesRequest) {
13
+ const model = responsesRequest.model;
14
+ if (!model) {
15
+ throw new Error('Model name is required');
16
+ }
17
+
18
+ const defaultMaxTokens = getMaxTokens(model);
19
+
20
+ const claudeRequest = {
21
+ model: model,
22
+ max_tokens: responsesRequest.max_output_tokens || defaultMaxTokens
23
+ };
24
+ // console.log(claudeRequest);
25
+
26
+ // Convert input to messages
27
+ // Responses API input can be: string, array of messages, or array of items
28
+ const messages = [];
29
+
30
+ if (typeof responsesRequest.input === 'string') {
31
+ // Simple string input
32
+ messages.push({
33
+ role: 'user',
34
+ content: responsesRequest.input
35
+ });
36
+ } else if (Array.isArray(responsesRequest.input)) {
37
+ // Array input - could be messages or output items
38
+ for (const item of responsesRequest.input) {
39
+ // Handle output items (no role, has type)
40
+ if (!item.role && item.type) {
41
+ if (item.type === 'message') {
42
+ // Message item from output
43
+ const textBlocks = [];
44
+ if (item.content && Array.isArray(item.content)) {
45
+ for (const contentBlock of item.content) {
46
+ if (contentBlock.type === 'output_text' && contentBlock.text) {
47
+ textBlocks.push({
48
+ type: 'text',
49
+ text: contentBlock.text
50
+ });
51
+ }
52
+ }
53
+ }
54
+ // Only include messages with actual content (skip empty messages)
55
+ if (textBlocks.length > 0) {
56
+ messages.push({
57
+ role: 'assistant',
58
+ content: textBlocks
59
+ });
60
+ }
61
+ } else if (item.type === 'function_call') {
62
+ // Function call from output - convert to assistant message with tool_use
63
+ messages.push({
64
+ role: 'assistant',
65
+ content: [
66
+ {
67
+ type: 'tool_use',
68
+ id: item.call_id || item.id,
69
+ name: item.name,
70
+ input: JSON.parse(item.arguments || '{}')
71
+ }
72
+ ]
73
+ });
74
+ } else if (item.type === 'function_call_output') {
75
+ // Function call output - convert to tool_result
76
+ messages.push({
77
+ role: 'user',
78
+ content: [
79
+ {
80
+ type: 'tool_result',
81
+ tool_use_id: item.call_id,
82
+ content: item.output
83
+ }
84
+ ]
85
+ });
86
+ }
87
+ // Skip other types like 'reasoning'
88
+ continue;
89
+ }
90
+
91
+ if (item.role && item.content) {
92
+ // Already in message format
93
+
94
+ if (item.role === 'system') {
95
+ // System messages go to separate field in Claude
96
+ const content = Array.isArray(item.content)
97
+ ? item.content.map(c => c.type === 'input_text' || c.type === 'text' ? c.text : c).filter(Boolean).join('\n')
98
+ : item.content;
99
+ claudeRequest.system = content;
100
+ } else if (item.role === 'tool') {
101
+ // Tool result
102
+ messages.push({
103
+ role: 'user',
104
+ content: [
105
+ {
106
+ type: 'tool_result',
107
+ tool_use_id: item.tool_call_id || item.id,
108
+ content: item.content
109
+ }
110
+ ]
111
+ });
112
+ } else if (item.role === 'assistant' && Array.isArray(item.content)) {
113
+ // Assistant with output array (might contain function_call items)
114
+ const textBlocks = [];
115
+ const toolUseBlocks = [];
116
+
117
+ for (const outputItem of item.content) {
118
+ if (outputItem.type === 'message' && outputItem.content) {
119
+ // Message item - extract text content
120
+ for (const contentBlock of outputItem.content) {
121
+ if (contentBlock.type === 'output_text' && contentBlock.text) {
122
+ textBlocks.push({
123
+ type: 'text',
124
+ text: contentBlock.text
125
+ });
126
+ }
127
+ }
128
+ } else if (outputItem.type === 'function_call') {
129
+ // Function call - convert to Claude tool_use
130
+ toolUseBlocks.push({
131
+ type: 'tool_use',
132
+ id: outputItem.call_id || outputItem.id,
133
+ name: outputItem.name,
134
+ input: JSON.parse(outputItem.arguments || '{}')
135
+ });
136
+ }
137
+ }
138
+
139
+ // Claude requires text blocks to come before tool_use blocks
140
+ const claudeContent = [...textBlocks, ...toolUseBlocks];
141
+
142
+ // Add message only if there's content
143
+ if (claudeContent.length > 0) {
144
+ messages.push({
145
+ role: 'assistant',
146
+ content: claudeContent
147
+ });
148
+ }
149
+ } else {
150
+ // Handle content that might be an array (OpenAI Responses API format)
151
+ const content = Array.isArray(item.content)
152
+ ? item.content.map(c => c.type === 'input_text' || c.type === 'text' ? c.text : c).filter(Boolean).join('\n')
153
+ : item.content;
154
+
155
+ messages.push({
156
+ role: item.role === 'assistant' ? 'assistant' : 'user',
157
+ content: content
158
+ });
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ // Merge consecutive messages with the same role (Claude doesn't allow this)
165
+ const mergedMessages = [];
166
+ for (let i = 0; i < messages.length; i++) {
167
+ const currentMsg = messages[i];
168
+
169
+ // Check if the next message has the same role
170
+ if (i < messages.length - 1 && messages[i + 1].role === currentMsg.role) {
171
+ // Merge content from consecutive same-role messages
172
+ const mergedContent = Array.isArray(currentMsg.content) ? [...currentMsg.content] : [currentMsg.content];
173
+
174
+ // Keep merging while the next message has the same role
175
+ while (i < messages.length - 1 && messages[i + 1].role === currentMsg.role) {
176
+ i++;
177
+ const nextContent = messages[i].content;
178
+ if (Array.isArray(nextContent)) {
179
+ mergedContent.push(...nextContent);
180
+ } else {
181
+ mergedContent.push(nextContent);
182
+ }
183
+ }
184
+
185
+ mergedMessages.push({
186
+ role: currentMsg.role,
187
+ content: mergedContent
188
+ });
189
+ } else {
190
+ mergedMessages.push(currentMsg);
191
+ }
192
+ }
193
+
194
+ claudeRequest.messages = mergedMessages;
195
+
196
+ // Handle instructions (system message)
197
+ if (responsesRequest.instructions) {
198
+ claudeRequest.system = responsesRequest.instructions;
199
+ }
200
+
201
+ // Convert tools from Responses API format to Claude format
202
+ // Responses API: { type: 'function', name, description, parameters }
203
+ // Claude: { name, description, input_schema } (NO type field)
204
+ if (responsesRequest.tools && Array.isArray(responsesRequest.tools)) {
205
+ claudeRequest.tools = responsesRequest.tools.map(tool => {
206
+ if (tool.type === 'function') {
207
+ if (tool.function) {
208
+ // Chat Completions format (for compatibility)
209
+ return {
210
+ name: tool.function.name,
211
+ description: tool.function.description || `Function: ${tool.function.name}`,
212
+ input_schema: tool.function.parameters || {
213
+ type: 'object',
214
+ properties: {}
215
+ }
216
+ };
217
+ } else {
218
+ // Responses API format with type: 'function'
219
+ return {
220
+ name: tool.name,
221
+ description: tool.description || `Function: ${tool.name}`,
222
+ input_schema: tool.parameters || {
223
+ type: 'object',
224
+ properties: {}
225
+ }
226
+ };
227
+ }
228
+ } else if (tool.type === 'custom') {
229
+ // Responses API custom format
230
+ return {
231
+ name: tool.name,
232
+ description: tool.description || `Tool: ${tool.name}`,
233
+ input_schema: tool.input_schema || {
234
+ type: 'object',
235
+ properties: {}
236
+ }
237
+ };
238
+ }
239
+ // If already in Claude format (no type field), pass through
240
+ return {
241
+ name: tool.name,
242
+ description: tool.description,
243
+ input_schema: tool.input_schema
244
+ };
245
+ });
246
+ }
247
+
248
+ // Temperature - always set to 0 for Claude
249
+ claudeRequest.temperature = 0;
250
+
251
+ // Top-p - do not set for Claude (removed)
252
+
253
+ // Tool choice (Responses API to Claude)
254
+ if (responsesRequest.tool_choice !== undefined) {
255
+ if (typeof responsesRequest.tool_choice === 'string') {
256
+ // 'auto', 'required', 'none' -> Claude format
257
+ if (responsesRequest.tool_choice === 'auto') {
258
+ claudeRequest.tool_choice = { type: 'auto' };
259
+ } else if (responsesRequest.tool_choice === 'required') {
260
+ claudeRequest.tool_choice = { type: 'any' };
261
+ }
262
+ // 'none' is handled by not setting tool_choice
263
+ } else if (responsesRequest.tool_choice?.type === 'function' || responsesRequest.tool_choice?.type === 'custom') {
264
+ // Specific tool -> Claude format
265
+ const toolName = responsesRequest.tool_choice.function?.name || responsesRequest.tool_choice.name;
266
+ claudeRequest.tool_choice = {
267
+ type: 'tool',
268
+ name: toolName
269
+ };
270
+ }
271
+ }
272
+
273
+ // Metadata (pass through as-is, Claude doesn't use it but won't error)
274
+ if (responsesRequest.metadata) {
275
+ claudeRequest.metadata = responsesRequest.metadata;
276
+ }
277
+
278
+ // Handle json_schema format by converting to tool use
279
+ // OpenAI Responses API: text.format.type = "json_schema" with schema
280
+ // Claude: Use a synthetic tool to enforce structured output
281
+ if (responsesRequest.text?.format?.type === 'json_schema') {
282
+ const schemaName = responsesRequest.text.format.name || 'output';
283
+ const schema = responsesRequest.text.format.schema;
284
+
285
+ // Create a synthetic tool that represents the JSON schema
286
+ const syntheticTool = {
287
+ name: schemaName,
288
+ description: `Generate structured output matching the ${schemaName} schema`,
289
+ input_schema: schema
290
+ };
291
+
292
+ // Replace tools array with only the synthetic tool (to ensure structured output)
293
+ claudeRequest.tools = [syntheticTool];
294
+
295
+ // Force tool use with this specific tool (ignore original tool_choice)
296
+ claudeRequest.tool_choice = {
297
+ type: 'tool',
298
+ name: schemaName
299
+ };
300
+
301
+ // Claude requires conversation to end with user message when tool_choice is set
302
+ // If last message is assistant, add a dummy user message
303
+ if (claudeRequest.messages.length > 0 && claudeRequest.messages[claudeRequest.messages.length - 1].role === 'assistant') {
304
+ claudeRequest.messages.push({
305
+ role: 'user',
306
+ content: [{ type: 'text', text: 'Please provide the structured output.' }]
307
+ });
308
+ }
309
+ }
310
+
311
+ return claudeRequest;
312
+ }
313
+
314
+ /**
315
+ * Convert Claude response to Responses API format
316
+ * @param {Object} claudeResponse - Claude format response
317
+ * @param {string} model - Model name
318
+ * @param {Object} originalRequest - Original request for context
319
+ * @returns {Object} Responses API format response
320
+ */
321
+ export function convertClaudeResponseToResponsesFormat(claudeResponse, model = 'claude-sonnet-4-5', originalRequest = {}) {
322
+ const output = [];
323
+ let outputText = '';
324
+
325
+ // Check if this was a json_schema request converted to tool use
326
+ const wasJsonSchemaRequest = originalRequest.text?.format?.type === 'json_schema';
327
+ const schemaName = originalRequest.text?.format?.name;
328
+
329
+ // Process content blocks
330
+ if (claudeResponse.content && Array.isArray(claudeResponse.content)) {
331
+ const messageContent = [];
332
+
333
+ for (const block of claudeResponse.content) {
334
+ if (block.type === 'text') {
335
+ // Text content
336
+ messageContent.push({
337
+ type: 'output_text',
338
+ text: block.text,
339
+ annotations: []
340
+ });
341
+ outputText += block.text;
342
+ } else if (block.type === 'tool_use') {
343
+ // Check if this is a synthetic tool for json_schema
344
+ if (wasJsonSchemaRequest && block.name === schemaName) {
345
+ // Convert tool_use back to plain text JSON (for json_schema requests)
346
+ const jsonOutput = JSON.stringify(block.input);
347
+ messageContent.push({
348
+ type: 'output_text',
349
+ text: jsonOutput,
350
+ annotations: []
351
+ });
352
+ outputText += jsonOutput;
353
+ } else {
354
+ // Regular tool call - add as separate function_call item
355
+ output.push({
356
+ id: `fc_${block.id}`,
357
+ type: 'function_call',
358
+ status: 'completed',
359
+ arguments: JSON.stringify(block.input),
360
+ call_id: block.id,
361
+ name: block.name
362
+ });
363
+ }
364
+ }
365
+ }
366
+
367
+ // Add message with text content if any
368
+ if (messageContent.length > 0) {
369
+ output.push({
370
+ id: `msg_${claudeResponse.id}`,
371
+ type: 'message',
372
+ status: 'completed',
373
+ role: 'assistant',
374
+ content: messageContent
375
+ });
376
+ }
377
+ }
378
+
379
+ // If no output items, create a message with placeholder text
380
+ // (Claude may return empty response for various reasons)
381
+ if (output.length === 0) {
382
+ output.push({
383
+ id: `msg_${claudeResponse.id}`,
384
+ type: 'message',
385
+ status: 'completed',
386
+ role: 'assistant',
387
+ content: [
388
+ {
389
+ type: 'output_text',
390
+ text: outputText || ' ',
391
+ annotations: []
392
+ }
393
+ ]
394
+ });
395
+ }
396
+
397
+ // Build Responses API response with ALL required fields
398
+ const responsesResponse = {
399
+ id: `resp_${claudeResponse.id}`,
400
+ object: 'response',
401
+ created_at: Math.floor(Date.now() / 1000),
402
+ status: claudeResponse.stop_reason === 'end_turn' || claudeResponse.stop_reason === 'tool_use' ? 'completed' : 'incomplete',
403
+ background: false,
404
+ billing: {
405
+ payer: 'developer'
406
+ },
407
+ error: null,
408
+ incomplete_details: null,
409
+ instructions: originalRequest.instructions || null,
410
+ max_output_tokens: originalRequest.max_output_tokens || null,
411
+ max_tool_calls: null,
412
+ model: model,
413
+ output: output,
414
+ parallel_tool_calls: true,
415
+ previous_response_id: null,
416
+ prompt_cache_key: null,
417
+ prompt_cache_retention: null,
418
+ reasoning: {
419
+ effort: originalRequest.reasoning?.effort || null,
420
+ summary: originalRequest.reasoning?.summary || null
421
+ },
422
+ safety_identifier: null,
423
+ service_tier: 'default',
424
+ store: originalRequest.store !== undefined ? originalRequest.store : true,
425
+ temperature: originalRequest.temperature !== undefined ? originalRequest.temperature : 1,
426
+ text: {
427
+ format: {
428
+ type: 'text'
429
+ },
430
+ verbosity: 'medium'
431
+ },
432
+ tool_choice: originalRequest.tool_choice || 'auto',
433
+ tools: originalRequest.tools || [],
434
+ top_logprobs: 0,
435
+ top_p: originalRequest.top_p !== undefined ? originalRequest.top_p : 1,
436
+ truncation: 'disabled',
437
+ usage: {
438
+ input_tokens: claudeResponse.usage?.input_tokens || 0,
439
+ input_tokens_details: {
440
+ cached_tokens: 0
441
+ },
442
+ output_tokens: claudeResponse.usage?.output_tokens || 0,
443
+ output_tokens_details: {
444
+ reasoning_tokens: 0
445
+ },
446
+ total_tokens: (claudeResponse.usage?.input_tokens || 0) + (claudeResponse.usage?.output_tokens || 0)
447
+ },
448
+ user: null,
449
+ metadata: {},
450
+ output_text: outputText
451
+ };
452
+
453
+ return responsesResponse;
454
+ }