aiexecode 1.0.94 → 1.0.127
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 +198 -88
- package/index.js +310 -86
- package/mcp-agent-lib/src/mcp_message_logger.js +17 -16
- package/package.json +4 -4
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
- package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +3 -3
- package/payload_viewer/web_server.js +361 -0
- package/prompts/completion_judge.txt +4 -0
- package/prompts/orchestrator.txt +116 -3
- package/src/LLMClient/client.js +401 -18
- package/src/LLMClient/converters/responses-to-claude.js +67 -18
- package/src/LLMClient/converters/responses-to-zai.js +667 -0
- package/src/LLMClient/errors.js +30 -4
- package/src/LLMClient/index.js +5 -0
- package/src/ai_based/completion_judge.js +263 -186
- package/src/ai_based/orchestrator.js +171 -35
- package/src/commands/agents.js +70 -0
- package/src/commands/apikey.js +1 -1
- package/src/commands/bg.js +129 -0
- package/src/commands/commands.js +51 -0
- package/src/commands/debug.js +52 -0
- package/src/commands/help.js +11 -1
- package/src/commands/model.js +42 -7
- package/src/commands/reasoning_effort.js +2 -2
- package/src/commands/skills.js +46 -0
- package/src/config/ai_models.js +106 -6
- package/src/config/constants.js +71 -0
- package/src/config/feature_flags.js +6 -7
- package/src/frontend/App.js +108 -1
- package/src/frontend/components/AutocompleteMenu.js +7 -1
- package/src/frontend/components/BackgroundProcessList.js +175 -0
- package/src/frontend/components/ConversationItem.js +26 -10
- package/src/frontend/components/CurrentModelView.js +2 -2
- package/src/frontend/components/HelpView.js +106 -2
- package/src/frontend/components/Input.js +33 -11
- package/src/frontend/components/ModelListView.js +1 -1
- package/src/frontend/components/SetupWizard.js +51 -8
- package/src/frontend/hooks/useFileCompletion.js +467 -0
- package/src/frontend/utils/toolUIFormatter.js +261 -0
- package/src/system/agents_loader.js +289 -0
- package/src/system/ai_request.js +156 -12
- package/src/system/background_process.js +317 -0
- package/src/system/code_executer.js +496 -56
- package/src/system/command_parser.js +33 -3
- package/src/system/conversation_state.js +265 -0
- package/src/system/conversation_trimmer.js +132 -0
- package/src/system/custom_command_loader.js +386 -0
- package/src/system/file_integrity.js +73 -10
- package/src/system/log.js +10 -2
- package/src/system/output_helper.js +52 -9
- package/src/system/session.js +213 -58
- package/src/system/session_memory.js +30 -2
- package/src/system/skill_loader.js +318 -0
- package/src/system/system_info.js +254 -40
- package/src/system/tool_approval.js +10 -0
- package/src/system/tool_registry.js +15 -1
- package/src/system/ui_events.js +11 -0
- package/src/tools/code_editor.js +16 -10
- package/src/tools/file_reader.js +66 -9
- package/src/tools/glob.js +0 -3
- package/src/tools/ripgrep.js +5 -7
- package/src/tools/skill_tool.js +122 -0
- package/src/tools/web_downloader.js +0 -3
- package/src/util/clone.js +174 -0
- package/src/util/config.js +55 -2
- package/src/util/config_migration.js +174 -0
- package/src/util/debug_log.js +8 -2
- package/src/util/exit_handler.js +8 -0
- package/src/util/file_reference_parser.js +132 -0
- package/src/util/path_validator.js +178 -0
- package/src/util/prompt_loader.js +91 -1
- package/src/util/safe_fs.js +66 -3
- package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert Responses API format to Z.AI (GLM) format
|
|
3
|
+
* Z.AI uses Anthropic Messages API compatible interface
|
|
4
|
+
* Base URL: https://api.z.ai/api/anthropic
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getMaxTokens } from '../../config/ai_models.js';
|
|
8
|
+
import { createDebugLogger } from '../../util/debug_log.js';
|
|
9
|
+
|
|
10
|
+
const debugLog = createDebugLogger('zai_converter.log', 'zai_converter');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert snake_case to camelCase
|
|
14
|
+
* @param {string} str - snake_case string
|
|
15
|
+
* @returns {string} camelCase string
|
|
16
|
+
*/
|
|
17
|
+
function snakeToCamel(str) {
|
|
18
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert camelCase to snake_case
|
|
23
|
+
* @param {string} str - camelCase string
|
|
24
|
+
* @returns {string} snake_case string
|
|
25
|
+
*/
|
|
26
|
+
function camelToSnake(str) {
|
|
27
|
+
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Recursively add both snake_case and camelCase keys to an object
|
|
32
|
+
* This ensures compatibility with functions expecting either naming convention
|
|
33
|
+
* @param {any} obj - Object to process
|
|
34
|
+
* @returns {any} Object with both key formats
|
|
35
|
+
*/
|
|
36
|
+
function addBothCaseKeys(obj) {
|
|
37
|
+
if (obj === null || typeof obj !== 'object') {
|
|
38
|
+
return obj;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(obj)) {
|
|
41
|
+
return obj.map(item => addBothCaseKeys(item));
|
|
42
|
+
}
|
|
43
|
+
const result = {};
|
|
44
|
+
for (const key of Object.keys(obj)) {
|
|
45
|
+
const value = addBothCaseKeys(obj[key]);
|
|
46
|
+
// Add original key
|
|
47
|
+
result[key] = value;
|
|
48
|
+
// Add camelCase version if key is snake_case
|
|
49
|
+
const camelKey = snakeToCamel(key);
|
|
50
|
+
if (camelKey !== key) {
|
|
51
|
+
result[camelKey] = value;
|
|
52
|
+
}
|
|
53
|
+
// Add snake_case version if key is camelCase
|
|
54
|
+
const snakeKey = camelToSnake(key);
|
|
55
|
+
if (snakeKey !== key) {
|
|
56
|
+
result[snakeKey] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Convert Responses API request to Z.AI format
|
|
64
|
+
* @param {Object} responsesRequest - Responses API format request
|
|
65
|
+
* @returns {Object} Z.AI (Anthropic-compatible) format request
|
|
66
|
+
*/
|
|
67
|
+
export function convertResponsesRequestToZaiFormat(responsesRequest) {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
debugLog(`[convertRequest] START: model=${responsesRequest.model}, input_items=${responsesRequest.input?.length || 0}`);
|
|
70
|
+
|
|
71
|
+
const model = responsesRequest.model;
|
|
72
|
+
if (!model) {
|
|
73
|
+
throw new Error('Model name is required');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const defaultMaxTokens = getMaxTokens(model);
|
|
77
|
+
|
|
78
|
+
const zaiRequest = {
|
|
79
|
+
model: model,
|
|
80
|
+
max_tokens: responsesRequest.max_output_tokens || defaultMaxTokens
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Convert input to messages
|
|
84
|
+
const messages = [];
|
|
85
|
+
|
|
86
|
+
if (typeof responsesRequest.input === 'string') {
|
|
87
|
+
messages.push({
|
|
88
|
+
role: 'user',
|
|
89
|
+
content: responsesRequest.input
|
|
90
|
+
});
|
|
91
|
+
} else if (Array.isArray(responsesRequest.input)) {
|
|
92
|
+
for (const item of responsesRequest.input) {
|
|
93
|
+
// Handle output items (no role, has type)
|
|
94
|
+
if (!item.role && item.type) {
|
|
95
|
+
if (item.type === 'message') {
|
|
96
|
+
const textBlocks = [];
|
|
97
|
+
if (item.content && Array.isArray(item.content)) {
|
|
98
|
+
for (const contentBlock of item.content) {
|
|
99
|
+
if (contentBlock.type === 'output_text' && contentBlock.text) {
|
|
100
|
+
textBlocks.push({
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: contentBlock.text
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (textBlocks.length > 0) {
|
|
108
|
+
messages.push({
|
|
109
|
+
role: 'assistant',
|
|
110
|
+
content: textBlocks
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Note: If message has no text content, it will be handled by the
|
|
114
|
+
// subsequent function_call items which create their own assistant messages
|
|
115
|
+
} else if (item.type === 'function_call') {
|
|
116
|
+
// Add placeholder text with tool_use for Z.AI/Anthropic API compatibility
|
|
117
|
+
messages.push({
|
|
118
|
+
role: 'assistant',
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: '(no content)'
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'tool_use',
|
|
126
|
+
id: item.call_id || item.id,
|
|
127
|
+
name: item.name,
|
|
128
|
+
input: JSON.parse(item.arguments || '{}')
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
} else if (item.type === 'function_call_output') {
|
|
133
|
+
// Build tool_result object with proper error handling
|
|
134
|
+
const toolResult = {
|
|
135
|
+
type: 'tool_result',
|
|
136
|
+
tool_use_id: item.call_id,
|
|
137
|
+
content: typeof item.output === 'string' ? item.output : JSON.stringify(item.output)
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Add is_error flag if the output indicates an error
|
|
141
|
+
// Check for common error patterns in the output
|
|
142
|
+
if (item.is_error === true) {
|
|
143
|
+
toolResult.is_error = true;
|
|
144
|
+
} else if (typeof item.output === 'object' && item.output !== null) {
|
|
145
|
+
// Check for operation_successful: false pattern
|
|
146
|
+
if (item.output.operation_successful === false ||
|
|
147
|
+
item.output.stdout?.operation_successful === false) {
|
|
148
|
+
toolResult.is_error = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
messages.push({
|
|
153
|
+
role: 'user',
|
|
154
|
+
content: [toolResult]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (item.role && item.content) {
|
|
161
|
+
if (item.role === 'system') {
|
|
162
|
+
// Z.AI는 Anthropic API와 호환 - system을 배열로 지원 (캐시 제어 포함)
|
|
163
|
+
if (Array.isArray(item.content)) {
|
|
164
|
+
// 배열인 경우: 캐시 제어가 있는 블록들을 처리
|
|
165
|
+
const systemBlocks = item.content.map(c => {
|
|
166
|
+
const block = {
|
|
167
|
+
type: 'text',
|
|
168
|
+
text: c.type === 'input_text' || c.type === 'text' ? c.text : (typeof c === 'string' ? c : '')
|
|
169
|
+
};
|
|
170
|
+
// cache_control이 있으면 유지 (Claude Code 스타일)
|
|
171
|
+
if (c.cache_control) {
|
|
172
|
+
block.cache_control = c.cache_control;
|
|
173
|
+
}
|
|
174
|
+
return block;
|
|
175
|
+
}).filter(b => b.text);
|
|
176
|
+
|
|
177
|
+
// 캐시 제어가 있는 블록이 있으면 배열로, 없으면 단순 문자열로
|
|
178
|
+
const hasCacheControl = systemBlocks.some(b => b.cache_control);
|
|
179
|
+
if (hasCacheControl) {
|
|
180
|
+
zaiRequest.system = systemBlocks;
|
|
181
|
+
debugLog(`[convertRequest] System message with cache_control: ${systemBlocks.length} blocks`);
|
|
182
|
+
} else {
|
|
183
|
+
zaiRequest.system = systemBlocks.map(b => b.text).join('\n');
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
zaiRequest.system = item.content;
|
|
187
|
+
}
|
|
188
|
+
} else if (item.role === 'tool') {
|
|
189
|
+
const toolResult = {
|
|
190
|
+
type: 'tool_result',
|
|
191
|
+
tool_use_id: item.tool_call_id || item.id,
|
|
192
|
+
content: typeof item.content === 'string' ? item.content : JSON.stringify(item.content)
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Add is_error flag if present
|
|
196
|
+
if (item.is_error === true) {
|
|
197
|
+
toolResult.is_error = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
messages.push({
|
|
201
|
+
role: 'user',
|
|
202
|
+
content: [toolResult]
|
|
203
|
+
});
|
|
204
|
+
} else if (item.role === 'assistant' && Array.isArray(item.content)) {
|
|
205
|
+
const textBlocks = [];
|
|
206
|
+
const toolUseBlocks = [];
|
|
207
|
+
|
|
208
|
+
for (const outputItem of item.content) {
|
|
209
|
+
if (outputItem.type === 'message' && outputItem.content) {
|
|
210
|
+
for (const contentBlock of outputItem.content) {
|
|
211
|
+
if (contentBlock.type === 'output_text' && contentBlock.text) {
|
|
212
|
+
textBlocks.push({
|
|
213
|
+
type: 'text',
|
|
214
|
+
text: contentBlock.text
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else if (outputItem.type === 'function_call') {
|
|
219
|
+
toolUseBlocks.push({
|
|
220
|
+
type: 'tool_use',
|
|
221
|
+
id: outputItem.call_id || outputItem.id,
|
|
222
|
+
name: outputItem.name,
|
|
223
|
+
input: JSON.parse(outputItem.arguments || '{}')
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// If we have tool_use blocks but no text, add a placeholder text
|
|
229
|
+
// Z.AI/Anthropic API recommends having text content with tool calls
|
|
230
|
+
if (toolUseBlocks.length > 0 && textBlocks.length === 0) {
|
|
231
|
+
textBlocks.push({
|
|
232
|
+
type: 'text',
|
|
233
|
+
text: '(no content)'
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const zaiContent = [...textBlocks, ...toolUseBlocks];
|
|
238
|
+
|
|
239
|
+
if (zaiContent.length > 0) {
|
|
240
|
+
messages.push({
|
|
241
|
+
role: 'assistant',
|
|
242
|
+
content: zaiContent
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// 일반 user/assistant 메시지 처리
|
|
247
|
+
if (Array.isArray(item.content)) {
|
|
248
|
+
// 캐시 제어가 있는 content 블록 확인
|
|
249
|
+
const hasCacheControl = item.content.some(c => c.cache_control);
|
|
250
|
+
|
|
251
|
+
if (hasCacheControl) {
|
|
252
|
+
// 캐시 제어가 있는 경우: 블록 배열로 유지
|
|
253
|
+
const contentBlocks = item.content.map(c => {
|
|
254
|
+
const block = {
|
|
255
|
+
type: 'text',
|
|
256
|
+
text: c.type === 'input_text' || c.type === 'text' ? c.text : (typeof c === 'string' ? c : '')
|
|
257
|
+
};
|
|
258
|
+
if (c.cache_control) {
|
|
259
|
+
block.cache_control = c.cache_control;
|
|
260
|
+
}
|
|
261
|
+
return block;
|
|
262
|
+
}).filter(b => b.text);
|
|
263
|
+
|
|
264
|
+
messages.push({
|
|
265
|
+
role: item.role === 'assistant' ? 'assistant' : 'user',
|
|
266
|
+
content: contentBlocks
|
|
267
|
+
});
|
|
268
|
+
debugLog(`[convertRequest] User message with cache_control: ${contentBlocks.length} blocks`);
|
|
269
|
+
} else {
|
|
270
|
+
// 캐시 제어 없음: 단순 텍스트로 합침
|
|
271
|
+
const content = item.content.map(c => c.type === 'input_text' || c.type === 'text' ? c.text : c).filter(Boolean).join('\n');
|
|
272
|
+
messages.push({
|
|
273
|
+
role: item.role === 'assistant' ? 'assistant' : 'user',
|
|
274
|
+
content: content
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
messages.push({
|
|
279
|
+
role: item.role === 'assistant' ? 'assistant' : 'user',
|
|
280
|
+
content: item.content
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Merge consecutive messages with the same role
|
|
289
|
+
const mergedMessages = [];
|
|
290
|
+
for (let i = 0; i < messages.length; i++) {
|
|
291
|
+
const currentMsg = messages[i];
|
|
292
|
+
|
|
293
|
+
if (i < messages.length - 1 && messages[i + 1].role === currentMsg.role) {
|
|
294
|
+
const mergedContent = Array.isArray(currentMsg.content) ? [...currentMsg.content] : [currentMsg.content];
|
|
295
|
+
|
|
296
|
+
while (i < messages.length - 1 && messages[i + 1].role === currentMsg.role) {
|
|
297
|
+
i++;
|
|
298
|
+
const nextContent = messages[i].content;
|
|
299
|
+
if (Array.isArray(nextContent)) {
|
|
300
|
+
mergedContent.push(...nextContent);
|
|
301
|
+
} else {
|
|
302
|
+
mergedContent.push(nextContent);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
mergedMessages.push({
|
|
307
|
+
role: currentMsg.role,
|
|
308
|
+
content: mergedContent
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
mergedMessages.push(currentMsg);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Normalize content format for Z.AI/Anthropic API
|
|
316
|
+
// Content arrays must contain objects with {type: "text", text: "..."} format
|
|
317
|
+
for (const msg of mergedMessages) {
|
|
318
|
+
if (Array.isArray(msg.content)) {
|
|
319
|
+
msg.content = msg.content.map(item => {
|
|
320
|
+
// If item is already in correct format, keep it
|
|
321
|
+
if (typeof item === 'object' && item !== null && item.type) {
|
|
322
|
+
return item;
|
|
323
|
+
}
|
|
324
|
+
// Convert plain string to text block format
|
|
325
|
+
if (typeof item === 'string') {
|
|
326
|
+
return { type: 'text', text: item };
|
|
327
|
+
}
|
|
328
|
+
// Fallback: convert to string
|
|
329
|
+
return { type: 'text', text: String(item) };
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
zaiRequest.messages = mergedMessages;
|
|
335
|
+
|
|
336
|
+
// Handle instructions (system message)
|
|
337
|
+
if (responsesRequest.instructions) {
|
|
338
|
+
zaiRequest.system = responsesRequest.instructions;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Convert tools from Responses API format to Z.AI (Anthropic) format
|
|
342
|
+
if (responsesRequest.tools && Array.isArray(responsesRequest.tools)) {
|
|
343
|
+
zaiRequest.tools = responsesRequest.tools.map(tool => {
|
|
344
|
+
if (tool.type === 'function') {
|
|
345
|
+
if (tool.function) {
|
|
346
|
+
return {
|
|
347
|
+
name: tool.function.name,
|
|
348
|
+
description: tool.function.description || `Function: ${tool.function.name}`,
|
|
349
|
+
input_schema: tool.function.parameters || {
|
|
350
|
+
type: 'object',
|
|
351
|
+
properties: {}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
} else {
|
|
355
|
+
return {
|
|
356
|
+
name: tool.name,
|
|
357
|
+
description: tool.description || `Function: ${tool.name}`,
|
|
358
|
+
input_schema: tool.parameters || {
|
|
359
|
+
type: 'object',
|
|
360
|
+
properties: {}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
} else if (tool.type === 'custom') {
|
|
365
|
+
return {
|
|
366
|
+
name: tool.name,
|
|
367
|
+
description: tool.description || `Tool: ${tool.name}`,
|
|
368
|
+
input_schema: tool.input_schema || {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: {}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
name: tool.name,
|
|
376
|
+
description: tool.description,
|
|
377
|
+
input_schema: tool.input_schema
|
|
378
|
+
};
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Temperature - always set to 0 for consistent results
|
|
383
|
+
zaiRequest.temperature = 0;
|
|
384
|
+
|
|
385
|
+
// Stream - Z.AI requires this field (will be overridden by SDK if using stream method)
|
|
386
|
+
zaiRequest.stream = responsesRequest.stream || false;
|
|
387
|
+
|
|
388
|
+
// Tool choice
|
|
389
|
+
if (responsesRequest.tool_choice !== undefined) {
|
|
390
|
+
if (typeof responsesRequest.tool_choice === 'string') {
|
|
391
|
+
if (responsesRequest.tool_choice === 'auto') {
|
|
392
|
+
zaiRequest.tool_choice = { type: 'auto' };
|
|
393
|
+
} else if (responsesRequest.tool_choice === 'required') {
|
|
394
|
+
zaiRequest.tool_choice = { type: 'any' };
|
|
395
|
+
}
|
|
396
|
+
} else if (responsesRequest.tool_choice?.type === 'function' || responsesRequest.tool_choice?.type === 'custom') {
|
|
397
|
+
const toolName = responsesRequest.tool_choice.function?.name || responsesRequest.tool_choice.name;
|
|
398
|
+
zaiRequest.tool_choice = {
|
|
399
|
+
type: 'tool',
|
|
400
|
+
name: toolName
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Metadata
|
|
406
|
+
if (responsesRequest.metadata) {
|
|
407
|
+
zaiRequest.metadata = responsesRequest.metadata;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Assistant Prefill 지원
|
|
411
|
+
// Z.AI/GLM에서 JSON 응답을 유도하기 위해 assistant 메시지를 미리 추가
|
|
412
|
+
if (responsesRequest.assistant_prefill) {
|
|
413
|
+
zaiRequest.messages.push({
|
|
414
|
+
role: 'assistant',
|
|
415
|
+
content: [
|
|
416
|
+
{
|
|
417
|
+
type: 'text',
|
|
418
|
+
text: responsesRequest.assistant_prefill
|
|
419
|
+
}
|
|
420
|
+
]
|
|
421
|
+
});
|
|
422
|
+
debugLog(`[convertRequest] Assistant prefill added: ${responsesRequest.assistant_prefill}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Handle json_schema format by converting to tool use
|
|
426
|
+
if (responsesRequest.text?.format?.type === 'json_schema') {
|
|
427
|
+
const schemaName = responsesRequest.text.format.name || 'output';
|
|
428
|
+
const schema = responsesRequest.text.format.schema;
|
|
429
|
+
|
|
430
|
+
const syntheticTool = {
|
|
431
|
+
name: schemaName,
|
|
432
|
+
description: `Generate structured output matching the ${schemaName} schema`,
|
|
433
|
+
input_schema: schema
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
zaiRequest.tools = [syntheticTool];
|
|
437
|
+
|
|
438
|
+
zaiRequest.tool_choice = {
|
|
439
|
+
type: 'tool',
|
|
440
|
+
name: schemaName
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
if (zaiRequest.messages.length > 0 && zaiRequest.messages[zaiRequest.messages.length - 1].role === 'assistant') {
|
|
444
|
+
zaiRequest.messages.push({
|
|
445
|
+
role: 'user',
|
|
446
|
+
content: [{ type: 'text', text: 'Please provide the structured output.' }]
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const elapsed = Date.now() - startTime;
|
|
452
|
+
debugLog(`[convertRequest] END: ${elapsed}ms, messages=${zaiRequest.messages?.length}, system_len=${zaiRequest.system?.length || 0}, tools=${zaiRequest.tools?.length || 0}`);
|
|
453
|
+
|
|
454
|
+
return zaiRequest;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Convert Z.AI response to Responses API format
|
|
459
|
+
* @param {Object} zaiResponse - Z.AI (Anthropic-compatible) format response
|
|
460
|
+
* @param {string} model - Model name
|
|
461
|
+
* @param {Object} originalRequest - Original request for context
|
|
462
|
+
* @returns {Object} Responses API format response
|
|
463
|
+
*/
|
|
464
|
+
export function convertZaiResponseToResponsesFormat(zaiResponse, model = 'glm-4.7', originalRequest = {}) {
|
|
465
|
+
const startTime = Date.now();
|
|
466
|
+
debugLog(`[convertResponse] START: id=${zaiResponse.id}, content_blocks=${zaiResponse.content?.length || 0}`);
|
|
467
|
+
|
|
468
|
+
const output = [];
|
|
469
|
+
let outputText = '';
|
|
470
|
+
|
|
471
|
+
const wasJsonSchemaRequest = originalRequest.text?.format?.type === 'json_schema';
|
|
472
|
+
const schemaName = originalRequest.text?.format?.name;
|
|
473
|
+
|
|
474
|
+
// Process content blocks
|
|
475
|
+
if (zaiResponse.content && Array.isArray(zaiResponse.content)) {
|
|
476
|
+
const messageContent = [];
|
|
477
|
+
let jsonSchemaOutput = null; // json_schema 요청 시 tool_use의 input 저장
|
|
478
|
+
|
|
479
|
+
// 먼저 tool_use 블록에서 json_schema 출력 확인
|
|
480
|
+
if (wasJsonSchemaRequest) {
|
|
481
|
+
for (const block of zaiResponse.content) {
|
|
482
|
+
if (block.type === 'tool_use' && block.name === schemaName) {
|
|
483
|
+
jsonSchemaOutput = JSON.stringify(block.input);
|
|
484
|
+
debugLog(`[convertResponse] Found json_schema tool_use: ${jsonSchemaOutput}`);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
for (const block of zaiResponse.content) {
|
|
491
|
+
if (block.type === 'thinking') {
|
|
492
|
+
// Z.AI/GLM thinking 블록 → Responses API reasoning 타입으로 변환
|
|
493
|
+
output.push({
|
|
494
|
+
id: `reasoning_${zaiResponse.id}_${output.length}`,
|
|
495
|
+
type: 'reasoning',
|
|
496
|
+
status: 'completed',
|
|
497
|
+
content: [
|
|
498
|
+
{
|
|
499
|
+
type: 'thinking',
|
|
500
|
+
thinking: block.thinking || ''
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
});
|
|
504
|
+
debugLog(`[convertResponse] Converted thinking block: ${(block.thinking || '').length} chars`);
|
|
505
|
+
} else if (block.type === 'text') {
|
|
506
|
+
// json_schema 요청이고 tool_use가 있으면, text 블록은 output_text에 포함하지 않음
|
|
507
|
+
// (GLM 모델이 text + tool_use를 둘 다 반환하는 경우 대응)
|
|
508
|
+
if (wasJsonSchemaRequest && jsonSchemaOutput) {
|
|
509
|
+
debugLog(`[convertResponse] Skipping text block for json_schema request (tool_use found)`);
|
|
510
|
+
// text 블록은 messageContent에만 추가 (참고용)
|
|
511
|
+
messageContent.push({
|
|
512
|
+
type: 'output_text',
|
|
513
|
+
text: block.text,
|
|
514
|
+
annotations: []
|
|
515
|
+
});
|
|
516
|
+
// outputText에는 추가하지 않음!
|
|
517
|
+
} else {
|
|
518
|
+
messageContent.push({
|
|
519
|
+
type: 'output_text',
|
|
520
|
+
text: block.text,
|
|
521
|
+
annotations: []
|
|
522
|
+
});
|
|
523
|
+
outputText += block.text;
|
|
524
|
+
}
|
|
525
|
+
} else if (block.type === 'tool_use') {
|
|
526
|
+
if (wasJsonSchemaRequest && block.name === schemaName) {
|
|
527
|
+
// json_schema 요청의 tool_use는 outputText로만 변환
|
|
528
|
+
// Convert snake_case keys to camelCase
|
|
529
|
+
const convertedInput = addBothCaseKeys(block.input);
|
|
530
|
+
const jsonOutput = JSON.stringify(convertedInput);
|
|
531
|
+
messageContent.push({
|
|
532
|
+
type: 'output_text',
|
|
533
|
+
text: jsonOutput,
|
|
534
|
+
annotations: []
|
|
535
|
+
});
|
|
536
|
+
outputText = jsonOutput; // outputText를 JSON만으로 설정 (덮어쓰기)
|
|
537
|
+
} else {
|
|
538
|
+
// Z.AI uses 'call_' prefix for tool IDs
|
|
539
|
+
// Convert snake_case keys to camelCase (some models like GLM return snake_case)
|
|
540
|
+
const convertedInput = addBothCaseKeys(block.input);
|
|
541
|
+
const originalKeys = Object.keys(block.input || {}).join(',');
|
|
542
|
+
const convertedKeys = Object.keys(convertedInput || {}).join(',');
|
|
543
|
+
if (originalKeys !== convertedKeys) {
|
|
544
|
+
debugLog(`[convertResponse] Added both case keys: ${originalKeys} -> ${convertedKeys}`);
|
|
545
|
+
}
|
|
546
|
+
output.push({
|
|
547
|
+
id: `fc_${block.id}`,
|
|
548
|
+
type: 'function_call',
|
|
549
|
+
status: 'completed',
|
|
550
|
+
arguments: JSON.stringify(convertedInput),
|
|
551
|
+
call_id: block.id,
|
|
552
|
+
name: block.name
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (messageContent.length > 0) {
|
|
559
|
+
output.push({
|
|
560
|
+
id: `msg_${zaiResponse.id}`,
|
|
561
|
+
type: 'message',
|
|
562
|
+
status: 'completed',
|
|
563
|
+
role: 'assistant',
|
|
564
|
+
content: messageContent
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (output.length === 0) {
|
|
570
|
+
output.push({
|
|
571
|
+
id: `msg_${zaiResponse.id}`,
|
|
572
|
+
type: 'message',
|
|
573
|
+
status: 'completed',
|
|
574
|
+
role: 'assistant',
|
|
575
|
+
content: [
|
|
576
|
+
{
|
|
577
|
+
type: 'output_text',
|
|
578
|
+
text: outputText || ' ',
|
|
579
|
+
annotations: []
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Determine status and incomplete_details based on stop_reason
|
|
586
|
+
const stopReason = zaiResponse.stop_reason;
|
|
587
|
+
const isCompleted = stopReason === 'end_turn' || stopReason === 'tool_use';
|
|
588
|
+
const isMaxTokens = stopReason === 'max_tokens';
|
|
589
|
+
|
|
590
|
+
// Build incomplete_details if response was truncated
|
|
591
|
+
let incompleteDetails = null;
|
|
592
|
+
if (isMaxTokens) {
|
|
593
|
+
incompleteDetails = {
|
|
594
|
+
reason: 'max_output_tokens',
|
|
595
|
+
message: 'Response was truncated because it reached the maximum output token limit'
|
|
596
|
+
};
|
|
597
|
+
debugLog(`[convertResponse] WARNING: Response truncated due to max_tokens`);
|
|
598
|
+
} else if (!isCompleted && stopReason) {
|
|
599
|
+
incompleteDetails = {
|
|
600
|
+
reason: stopReason,
|
|
601
|
+
message: `Response stopped with reason: ${stopReason}`
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const responsesResponse = {
|
|
606
|
+
id: `resp_${zaiResponse.id}`,
|
|
607
|
+
object: 'response',
|
|
608
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
609
|
+
status: isCompleted ? 'completed' : 'incomplete',
|
|
610
|
+
background: false,
|
|
611
|
+
billing: {
|
|
612
|
+
payer: 'developer'
|
|
613
|
+
},
|
|
614
|
+
error: null,
|
|
615
|
+
incomplete_details: incompleteDetails,
|
|
616
|
+
instructions: originalRequest.instructions || null,
|
|
617
|
+
max_output_tokens: originalRequest.max_output_tokens || null,
|
|
618
|
+
max_tool_calls: null,
|
|
619
|
+
model: model,
|
|
620
|
+
output: output,
|
|
621
|
+
parallel_tool_calls: true,
|
|
622
|
+
previous_response_id: null,
|
|
623
|
+
prompt_cache_key: null,
|
|
624
|
+
prompt_cache_retention: null,
|
|
625
|
+
reasoning: {
|
|
626
|
+
effort: originalRequest.reasoning?.effort || null,
|
|
627
|
+
summary: originalRequest.reasoning?.summary || null
|
|
628
|
+
},
|
|
629
|
+
safety_identifier: null,
|
|
630
|
+
service_tier: zaiResponse.usage?.service_tier || 'standard',
|
|
631
|
+
store: originalRequest.store !== undefined ? originalRequest.store : true,
|
|
632
|
+
temperature: originalRequest.temperature !== undefined ? originalRequest.temperature : 1,
|
|
633
|
+
text: {
|
|
634
|
+
format: {
|
|
635
|
+
type: 'text'
|
|
636
|
+
},
|
|
637
|
+
verbosity: 'medium'
|
|
638
|
+
},
|
|
639
|
+
tool_choice: originalRequest.tool_choice || 'auto',
|
|
640
|
+
tools: originalRequest.tools || [],
|
|
641
|
+
top_logprobs: 0,
|
|
642
|
+
top_p: originalRequest.top_p !== undefined ? originalRequest.top_p : 1,
|
|
643
|
+
truncation: 'disabled',
|
|
644
|
+
usage: {
|
|
645
|
+
input_tokens: zaiResponse.usage?.input_tokens || 0,
|
|
646
|
+
input_tokens_details: {
|
|
647
|
+
cached_tokens: zaiResponse.usage?.cache_read_input_tokens || 0
|
|
648
|
+
},
|
|
649
|
+
output_tokens: zaiResponse.usage?.output_tokens || 0,
|
|
650
|
+
output_tokens_details: {
|
|
651
|
+
reasoning_tokens: 0
|
|
652
|
+
},
|
|
653
|
+
total_tokens: (zaiResponse.usage?.input_tokens || 0) + (zaiResponse.usage?.output_tokens || 0),
|
|
654
|
+
// Z.AI 추가 정보
|
|
655
|
+
cache_read_input_tokens: zaiResponse.usage?.cache_read_input_tokens || 0,
|
|
656
|
+
server_tool_use: zaiResponse.usage?.server_tool_use || null
|
|
657
|
+
},
|
|
658
|
+
user: null,
|
|
659
|
+
metadata: {},
|
|
660
|
+
output_text: outputText
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
const elapsed = Date.now() - startTime;
|
|
664
|
+
debugLog(`[convertResponse] END: ${elapsed}ms, output_items=${output.length}, output_text_len=${outputText.length}`);
|
|
665
|
+
|
|
666
|
+
return responsesResponse;
|
|
667
|
+
}
|