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.
- package/README.md +1 -0
- package/index.js +13 -11
- package/mcp-agent-lib/init.sh +3 -0
- package/mcp-agent-lib/package-lock.json +14 -1
- package/mcp-agent-lib/package.json +4 -6
- package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
- package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
- package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
- package/mcp-agent-lib/sampleMCPHost/index.js +182 -63
- package/mcp-agent-lib/sampleMCPHost/mcp_config.json +7 -1
- package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
- package/mcp-agent-lib/src/mcp_client.js +129 -67
- package/mcp-agent-lib/src/mcp_message_logger.js +516 -0
- package/package.json +3 -1
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/src/LLMClient/client.js +992 -0
- package/src/LLMClient/converters/input-normalizer.js +238 -0
- package/src/LLMClient/converters/responses-to-claude.js +454 -0
- package/src/LLMClient/converters/responses-to-gemini.js +648 -0
- package/src/LLMClient/converters/responses-to-ollama.js +348 -0
- package/src/LLMClient/errors.js +372 -0
- package/src/LLMClient/index.js +31 -0
- package/src/commands/apikey.js +10 -22
- package/src/commands/model.js +28 -28
- package/src/commands/reasoning_effort.js +9 -23
- package/src/config/ai_models.js +212 -0
- package/src/config/feature_flags.js +1 -1
- package/src/frontend/App.js +5 -10
- package/src/frontend/components/CurrentModelView.js +0 -33
- package/src/frontend/components/Footer.js +3 -3
- package/src/frontend/components/ModelListView.js +30 -87
- package/src/frontend/components/ModelUpdatedView.js +7 -142
- package/src/frontend/components/SetupWizard.js +37 -32
- package/src/system/ai_request.js +57 -42
- package/src/util/config.js +26 -4
- package/src/util/setup_wizard.js +1 -6
- package/mcp-agent-lib/.claude/settings.local.json +0 -9
- package/src/config/openai_models.js +0 -152
- /package/payload_viewer/out/_next/static/{w4dMVYalgk7djrLxRxWiE → d0-fu2rgYnshgGFPxr1CR}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{w4dMVYalgk7djrLxRxWiE → d0-fu2rgYnshgGFPxr1CR}/_clientMiddlewareManifest.json +0 -0
- /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
|
+
}
|