aiexecode 1.0.94 → 1.0.96
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 +210 -87
- package/index.js +33 -1
- package/package.json +3 -3
- 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/src/LLMClient/client.js +392 -16
- package/src/LLMClient/converters/responses-to-claude.js +67 -18
- package/src/LLMClient/converters/responses-to-zai.js +608 -0
- package/src/LLMClient/errors.js +18 -4
- package/src/LLMClient/index.js +5 -0
- package/src/ai_based/completion_judge.js +35 -4
- package/src/ai_based/orchestrator.js +146 -35
- package/src/commands/agents.js +70 -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 +43 -7
- package/src/commands/skills.js +46 -0
- package/src/config/ai_models.js +96 -5
- package/src/config/constants.js +71 -0
- package/src/frontend/components/HelpView.js +106 -2
- package/src/frontend/components/SetupWizard.js +53 -8
- package/src/frontend/utils/toolUIFormatter.js +261 -0
- package/src/system/agents_loader.js +289 -0
- package/src/system/ai_request.js +147 -9
- package/src/system/command_parser.js +33 -3
- package/src/system/conversation_state.js +265 -0
- package/src/system/custom_command_loader.js +386 -0
- package/src/system/session.js +59 -35
- package/src/system/skill_loader.js +318 -0
- package/src/system/tool_approval.js +10 -0
- package/src/tools/file_reader.js +49 -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 +38 -2
- package/src/util/config_migration.js +174 -0
- package/src/util/path_validator.js +178 -0
- package/src/util/prompt_loader.js +68 -1
- package/src/util/safe_fs.js +43 -3
- package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_ssgManifest.js +0 -0
package/src/LLMClient/client.js
CHANGED
|
@@ -12,8 +12,12 @@ import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
|
12
12
|
import { convertResponsesRequestToClaudeFormat, convertClaudeResponseToResponsesFormat } from './converters/responses-to-claude.js';
|
|
13
13
|
import { convertResponsesRequestToGeminiFormat, convertGeminiResponseToResponsesFormat } from './converters/responses-to-gemini.js';
|
|
14
14
|
import { convertResponsesRequestToOllamaFormat, convertOllamaResponseToResponsesFormat } from './converters/responses-to-ollama.js';
|
|
15
|
+
import { convertResponsesRequestToZaiFormat, convertZaiResponseToResponsesFormat } from './converters/responses-to-zai.js';
|
|
15
16
|
import { normalizeInput } from './converters/input-normalizer.js';
|
|
16
17
|
import { normalizeError, createErrorFromResponse } from './errors.js';
|
|
18
|
+
import { createDebugLogger } from '../util/debug_log.js';
|
|
19
|
+
|
|
20
|
+
const debugLog = createDebugLogger('llm_client.log', 'llm_client');
|
|
17
21
|
|
|
18
22
|
export class UnifiedLLMClient {
|
|
19
23
|
/**
|
|
@@ -41,17 +45,9 @@ export class UnifiedLLMClient {
|
|
|
41
45
|
// Default to openai if still no provider
|
|
42
46
|
this.provider = this.provider || 'openai';
|
|
43
47
|
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!fs.existsSync(this.logDir)) {
|
|
48
|
-
fs.mkdirSync(this.logDir, { recursive: true });
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error(`Failed to create log directory: ${error.message}`);
|
|
52
|
-
this.logDir = null; // Disable logging if directory creation fails
|
|
53
|
-
}
|
|
54
|
-
}
|
|
48
|
+
// 로그 디렉토리는 비동기로 생성 (초기화 시점에서는 동기 연산 사용하지 않음)
|
|
49
|
+
this._logDirInitialized = false;
|
|
50
|
+
this._initializeLogDir();
|
|
55
51
|
|
|
56
52
|
this._initializeClient();
|
|
57
53
|
}
|
|
@@ -66,6 +62,8 @@ export class UnifiedLLMClient {
|
|
|
66
62
|
return 'claude';
|
|
67
63
|
} else if (model.startsWith('gemini-')) {
|
|
68
64
|
return 'gemini';
|
|
65
|
+
} else if (model.startsWith('glm-')) {
|
|
66
|
+
return 'zai';
|
|
69
67
|
} else if (model.includes('llama') || model.includes('mistral') || model.includes('codellama')) {
|
|
70
68
|
return 'ollama';
|
|
71
69
|
}
|
|
@@ -80,6 +78,21 @@ export class UnifiedLLMClient {
|
|
|
80
78
|
model === 'o3' || model === 'o3-mini';
|
|
81
79
|
}
|
|
82
80
|
|
|
81
|
+
/**
|
|
82
|
+
* 로그 디렉토리를 비동기로 초기화합니다.
|
|
83
|
+
*/
|
|
84
|
+
async _initializeLogDir() {
|
|
85
|
+
if (!this.logDir || this._logDirInitialized) return;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await fs.promises.mkdir(this.logDir, { recursive: true });
|
|
89
|
+
this._logDirInitialized = true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`Failed to create log directory: ${error.message}`);
|
|
92
|
+
this.logDir = null; // Disable logging if directory creation fails
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
_initializeClient() {
|
|
84
97
|
switch (this.provider) {
|
|
85
98
|
case 'openai':
|
|
@@ -90,6 +103,18 @@ export class UnifiedLLMClient {
|
|
|
90
103
|
this.client = new Anthropic({ apiKey: this.apiKey });
|
|
91
104
|
break;
|
|
92
105
|
|
|
106
|
+
case 'zai':
|
|
107
|
+
// Z.AI uses Anthropic-compatible API with custom baseURL
|
|
108
|
+
// interleaved-thinking 헤더 추가로 thinking 블록 활성화
|
|
109
|
+
this.client = new Anthropic({
|
|
110
|
+
apiKey: this.apiKey,
|
|
111
|
+
baseURL: this.baseUrl || 'https://api.z.ai/api/anthropic',
|
|
112
|
+
defaultHeaders: {
|
|
113
|
+
'anthropic-beta': 'interleaved-thinking-2025-05-14'
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
|
|
93
118
|
case 'gemini':
|
|
94
119
|
this.client = new GoogleGenerativeAI(this.apiKey);
|
|
95
120
|
break;
|
|
@@ -110,9 +135,15 @@ export class UnifiedLLMClient {
|
|
|
110
135
|
* @param {string} type - 'REQ' or 'RES'
|
|
111
136
|
* @param {string} providerName - Provider name
|
|
112
137
|
*/
|
|
113
|
-
_logPayload(payload, type, providerName) {
|
|
138
|
+
async _logPayload(payload, type, providerName) {
|
|
114
139
|
if (!this.logDir) return;
|
|
115
140
|
|
|
141
|
+
// 로그 디렉토리가 초기화되지 않았으면 먼저 초기화
|
|
142
|
+
if (!this._logDirInitialized) {
|
|
143
|
+
await this._initializeLogDir();
|
|
144
|
+
if (!this.logDir) return; // 초기화 실패 시 리턴
|
|
145
|
+
}
|
|
146
|
+
|
|
116
147
|
try {
|
|
117
148
|
const now = new Date();
|
|
118
149
|
const year = now.getFullYear();
|
|
@@ -141,6 +172,9 @@ export class UnifiedLLMClient {
|
|
|
141
172
|
* @returns {Promise<Object>|AsyncGenerator} Responses API format response or stream
|
|
142
173
|
*/
|
|
143
174
|
async response(request, options = {}) {
|
|
175
|
+
const responseStartTime = Date.now();
|
|
176
|
+
debugLog(`[response] >>>>>>>>>> ENTRY: model=${request.model || this.defaultModel}, input_length=${request.input?.length || 0}`);
|
|
177
|
+
|
|
144
178
|
// Set default model if not provided
|
|
145
179
|
if (!request.model && this.defaultModel) {
|
|
146
180
|
request.model = this.defaultModel;
|
|
@@ -204,22 +238,37 @@ export class UnifiedLLMClient {
|
|
|
204
238
|
// Check if streaming is requested
|
|
205
239
|
const isStreaming = request.stream === true;
|
|
206
240
|
|
|
241
|
+
debugLog(`[response] Provider: ${effectiveProvider}, streaming: ${isStreaming}, prep_time: ${Date.now() - responseStartTime}ms`);
|
|
242
|
+
|
|
243
|
+
let result;
|
|
207
244
|
switch (effectiveProvider) {
|
|
208
245
|
case 'openai':
|
|
209
|
-
|
|
246
|
+
result = isStreaming ? this._responseOpenAIStream(request, options) : await this._responseOpenAI(request, options);
|
|
247
|
+
break;
|
|
210
248
|
|
|
211
249
|
case 'claude':
|
|
212
|
-
|
|
250
|
+
result = isStreaming ? this._responseClaudeStream(request, options) : await this._responseClaude(request, options);
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case 'zai':
|
|
254
|
+
result = isStreaming ? this._responseZaiStream(request, options) : await this._responseZai(request, options);
|
|
255
|
+
break;
|
|
213
256
|
|
|
214
257
|
case 'gemini':
|
|
215
|
-
|
|
258
|
+
result = isStreaming ? this._responseGeminiStream(request, options) : await this._responseGemini(request, options);
|
|
259
|
+
break;
|
|
216
260
|
|
|
217
261
|
case 'ollama':
|
|
218
|
-
|
|
262
|
+
result = isStreaming ? this._responseOllamaStream(request, options) : await this._responseOllama(request, options);
|
|
263
|
+
break;
|
|
219
264
|
|
|
220
265
|
default:
|
|
221
266
|
throw new Error(`Unsupported provider: ${this.provider}`);
|
|
222
267
|
}
|
|
268
|
+
|
|
269
|
+
const totalTime = Date.now() - responseStartTime;
|
|
270
|
+
debugLog(`[response] <<<<<<<<<< EXIT: ${totalTime}ms total, provider=${effectiveProvider}`);
|
|
271
|
+
return result;
|
|
223
272
|
}
|
|
224
273
|
|
|
225
274
|
async _responseOpenAI(request, options = {}) {
|
|
@@ -805,6 +854,333 @@ export class UnifiedLLMClient {
|
|
|
805
854
|
}
|
|
806
855
|
}
|
|
807
856
|
|
|
857
|
+
// ============================================
|
|
858
|
+
// Z.AI (GLM) Methods
|
|
859
|
+
// ============================================
|
|
860
|
+
|
|
861
|
+
async _responseZai(request, options = {}) {
|
|
862
|
+
const startTime = Date.now();
|
|
863
|
+
const timings = {};
|
|
864
|
+
|
|
865
|
+
debugLog(`[_responseZai] ========== START ==========`);
|
|
866
|
+
debugLog(`[_responseZai] Model: ${request.model}, System length: ${request.instructions?.length || 0}`);
|
|
867
|
+
|
|
868
|
+
try {
|
|
869
|
+
// Step 1: Convert request format
|
|
870
|
+
const convertStart = Date.now();
|
|
871
|
+
const zaiRequest = convertResponsesRequestToZaiFormat(request);
|
|
872
|
+
// Always use streaming for Z.AI API calls
|
|
873
|
+
zaiRequest.stream = true;
|
|
874
|
+
timings.convert_request = Date.now() - convertStart;
|
|
875
|
+
debugLog(`[_responseZai] [1] Request converted: ${timings.convert_request}ms, messages: ${zaiRequest.messages?.length}, system: ${zaiRequest.system?.length || 0} chars`);
|
|
876
|
+
|
|
877
|
+
// Log raw request payload before API call
|
|
878
|
+
this._logPayload(zaiRequest, 'REQ-ZAI-RAW', 'zai');
|
|
879
|
+
|
|
880
|
+
// Step 2: Prepare stream options
|
|
881
|
+
const streamOptions = {};
|
|
882
|
+
if (options.signal) {
|
|
883
|
+
streamOptions.signal = options.signal;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Step 3: Initiate API stream
|
|
887
|
+
const streamInitStart = Date.now();
|
|
888
|
+
debugLog(`[_responseZai] [2] Initiating API stream to Z.AI...`);
|
|
889
|
+
const stream = await this.client.messages.stream(zaiRequest, streamOptions);
|
|
890
|
+
timings.stream_init = Date.now() - streamInitStart;
|
|
891
|
+
debugLog(`[_responseZai] [3] Stream initiated: ${timings.stream_init}ms`);
|
|
892
|
+
|
|
893
|
+
// Step 4: Accumulate streaming response
|
|
894
|
+
let messageId = null;
|
|
895
|
+
let model = request.model || 'glm-4.7';
|
|
896
|
+
let stopReason = null;
|
|
897
|
+
let usage = { input_tokens: 0, output_tokens: 0, cache_read_input_tokens: 0 };
|
|
898
|
+
const contentBlocks = [];
|
|
899
|
+
let currentTextBlock = null;
|
|
900
|
+
let currentToolBlock = null;
|
|
901
|
+
let currentThinkingBlock = null; // thinking 블록 지원
|
|
902
|
+
|
|
903
|
+
let eventCount = 0;
|
|
904
|
+
let firstEventTime = null;
|
|
905
|
+
let lastEventTime = null;
|
|
906
|
+
const streamReadStart = Date.now();
|
|
907
|
+
|
|
908
|
+
debugLog(`[_responseZai] [4] Reading stream events...`);
|
|
909
|
+
|
|
910
|
+
for await (const event of stream) {
|
|
911
|
+
eventCount++;
|
|
912
|
+
const eventTime = Date.now();
|
|
913
|
+
if (!firstEventTime) {
|
|
914
|
+
firstEventTime = eventTime;
|
|
915
|
+
timings.first_event = firstEventTime - streamReadStart;
|
|
916
|
+
debugLog(`[_responseZai] [4.1] First event received: ${timings.first_event}ms, type: ${event.type}`);
|
|
917
|
+
}
|
|
918
|
+
lastEventTime = eventTime;
|
|
919
|
+
|
|
920
|
+
if (event.type === 'message_start') {
|
|
921
|
+
messageId = event.message.id;
|
|
922
|
+
model = event.message.model;
|
|
923
|
+
usage.input_tokens = event.message.usage?.input_tokens || 0;
|
|
924
|
+
usage.cache_read_input_tokens = event.message.usage?.cache_read_input_tokens || 0;
|
|
925
|
+
debugLog(`[_responseZai] [4.2] message_start: id=${messageId}, input_tokens=${usage.input_tokens}`);
|
|
926
|
+
} else if (event.type === 'content_block_start') {
|
|
927
|
+
if (event.content_block?.type === 'text') {
|
|
928
|
+
currentTextBlock = { type: 'text', text: '' };
|
|
929
|
+
} else if (event.content_block?.type === 'tool_use') {
|
|
930
|
+
currentToolBlock = {
|
|
931
|
+
type: 'tool_use',
|
|
932
|
+
id: event.content_block.id,
|
|
933
|
+
name: event.content_block.name,
|
|
934
|
+
input: {}
|
|
935
|
+
};
|
|
936
|
+
debugLog(`[_responseZai] [4.3] tool_use started: ${event.content_block.name}`);
|
|
937
|
+
} else if (event.content_block?.type === 'thinking') {
|
|
938
|
+
// Z.AI/GLM thinking 블록 지원
|
|
939
|
+
currentThinkingBlock = { type: 'thinking', thinking: '' };
|
|
940
|
+
debugLog(`[_responseZai] [4.3] thinking started`);
|
|
941
|
+
}
|
|
942
|
+
} else if (event.type === 'content_block_delta') {
|
|
943
|
+
if (event.delta?.type === 'text_delta') {
|
|
944
|
+
if (currentTextBlock) {
|
|
945
|
+
currentTextBlock.text += event.delta.text;
|
|
946
|
+
}
|
|
947
|
+
} else if (event.delta?.type === 'input_json_delta') {
|
|
948
|
+
if (currentToolBlock) {
|
|
949
|
+
if (!currentToolBlock._inputJson) {
|
|
950
|
+
currentToolBlock._inputJson = '';
|
|
951
|
+
}
|
|
952
|
+
currentToolBlock._inputJson += event.delta.partial_json;
|
|
953
|
+
}
|
|
954
|
+
} else if (event.delta?.type === 'thinking_delta') {
|
|
955
|
+
// Z.AI/GLM thinking delta 처리
|
|
956
|
+
if (currentThinkingBlock) {
|
|
957
|
+
currentThinkingBlock.thinking += event.delta.thinking || '';
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
} else if (event.type === 'content_block_stop') {
|
|
961
|
+
if (currentThinkingBlock) {
|
|
962
|
+
// thinking 블록은 contentBlocks에 추가하지만 output에는 포함하지 않음
|
|
963
|
+
contentBlocks.push(currentThinkingBlock);
|
|
964
|
+
debugLog(`[_responseZai] [4.4] thinking completed: ${currentThinkingBlock.thinking.length} chars`);
|
|
965
|
+
currentThinkingBlock = null;
|
|
966
|
+
} else if (currentTextBlock) {
|
|
967
|
+
contentBlocks.push(currentTextBlock);
|
|
968
|
+
currentTextBlock = null;
|
|
969
|
+
} else if (currentToolBlock) {
|
|
970
|
+
if (currentToolBlock._inputJson) {
|
|
971
|
+
try {
|
|
972
|
+
currentToolBlock.input = JSON.parse(currentToolBlock._inputJson);
|
|
973
|
+
} catch (e) {
|
|
974
|
+
currentToolBlock.input = {};
|
|
975
|
+
}
|
|
976
|
+
delete currentToolBlock._inputJson;
|
|
977
|
+
}
|
|
978
|
+
contentBlocks.push(currentToolBlock);
|
|
979
|
+
debugLog(`[_responseZai] [4.4] tool_use completed: ${currentToolBlock.name}`);
|
|
980
|
+
currentToolBlock = null;
|
|
981
|
+
}
|
|
982
|
+
} else if (event.type === 'message_delta') {
|
|
983
|
+
stopReason = event.delta?.stop_reason || stopReason;
|
|
984
|
+
usage.output_tokens = event.usage?.output_tokens || usage.output_tokens;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
timings.stream_read = Date.now() - streamReadStart;
|
|
989
|
+
timings.total_stream = lastEventTime ? lastEventTime - streamReadStart : 0;
|
|
990
|
+
debugLog(`[_responseZai] [5] Stream complete: ${timings.stream_read}ms, events: ${eventCount}, stop_reason: ${stopReason}`);
|
|
991
|
+
|
|
992
|
+
// Step 5: Construct response object
|
|
993
|
+
const constructStart = Date.now();
|
|
994
|
+
const zaiResponse = {
|
|
995
|
+
id: messageId || `msg_${Date.now()}`,
|
|
996
|
+
type: 'message',
|
|
997
|
+
role: 'assistant',
|
|
998
|
+
content: contentBlocks,
|
|
999
|
+
model: model,
|
|
1000
|
+
stop_reason: stopReason || 'end_turn',
|
|
1001
|
+
usage: usage
|
|
1002
|
+
};
|
|
1003
|
+
timings.construct_response = Date.now() - constructStart;
|
|
1004
|
+
|
|
1005
|
+
// 캐시 토큰 정보 로깅
|
|
1006
|
+
if (usage.cache_read_input_tokens > 0) {
|
|
1007
|
+
debugLog(`[_responseZai] Cache tokens used: ${usage.cache_read_input_tokens} (${Math.round(usage.cache_read_input_tokens / usage.input_tokens * 100)}% of input)`);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const elapsedTime = Date.now() - startTime;
|
|
1011
|
+
timings.total = elapsedTime;
|
|
1012
|
+
|
|
1013
|
+
debugLog(`[_responseZai] [6] Response constructed: ${timings.construct_response}ms, content blocks: ${contentBlocks.length}`);
|
|
1014
|
+
|
|
1015
|
+
// Log raw Z.AI API response before conversion (with timing)
|
|
1016
|
+
this._logPayload({
|
|
1017
|
+
...zaiResponse,
|
|
1018
|
+
_timing: { ...timings, elapsed_ms: elapsedTime, start: startTime, end: Date.now() }
|
|
1019
|
+
}, 'RES-ZAI-RAW', 'zai');
|
|
1020
|
+
|
|
1021
|
+
// Step 6: Convert response format
|
|
1022
|
+
const convertResStart = Date.now();
|
|
1023
|
+
const response = convertZaiResponseToResponsesFormat(zaiResponse, request.model, request);
|
|
1024
|
+
timings.convert_response = Date.now() - convertResStart;
|
|
1025
|
+
debugLog(`[_responseZai] [7] Response converted: ${timings.convert_response}ms`);
|
|
1026
|
+
|
|
1027
|
+
// Log response payload
|
|
1028
|
+
this._logPayload(response, 'RES', 'zai');
|
|
1029
|
+
|
|
1030
|
+
debugLog(`[_responseZai] ========== END: ${elapsedTime}ms total ==========`);
|
|
1031
|
+
debugLog(`[_responseZai] Timing breakdown: convert_req=${timings.convert_request}ms, stream_init=${timings.stream_init}ms, first_event=${timings.first_event}ms, stream_read=${timings.stream_read}ms, convert_res=${timings.convert_response}ms`);
|
|
1032
|
+
|
|
1033
|
+
return response;
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
const elapsedTime = Date.now() - startTime;
|
|
1036
|
+
debugLog(`[_responseZai] ========== ERROR after ${elapsedTime}ms ==========`);
|
|
1037
|
+
debugLog(`[_responseZai] Error: ${error.name} - ${error.message}`);
|
|
1038
|
+
debugLog(`[_responseZai] Timings so far: ${JSON.stringify(timings)}`);
|
|
1039
|
+
|
|
1040
|
+
// Log error for debugging (similar to Gemini)
|
|
1041
|
+
this._logPayload({
|
|
1042
|
+
error_message: error.message,
|
|
1043
|
+
error_stack: error.stack,
|
|
1044
|
+
error_name: error.name,
|
|
1045
|
+
error_status: error.status,
|
|
1046
|
+
_timing: { ...timings, elapsed_ms: elapsedTime, start: startTime, end: Date.now() },
|
|
1047
|
+
full_error: JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error)))
|
|
1048
|
+
}, 'ERROR-ZAI', 'zai');
|
|
1049
|
+
throw normalizeError(error, 'zai');
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Z.AI streaming response
|
|
1055
|
+
* @param {Object} request - Responses API format request
|
|
1056
|
+
* @param {Object} options - Additional options
|
|
1057
|
+
* @returns {AsyncGenerator} Responses API format stream
|
|
1058
|
+
*/
|
|
1059
|
+
async *_responseZaiStream(request, options = {}) {
|
|
1060
|
+
const chunks = [];
|
|
1061
|
+
const startTime = Date.now();
|
|
1062
|
+
const timings = {};
|
|
1063
|
+
|
|
1064
|
+
debugLog(`[_responseZaiStream] ========== START ==========`);
|
|
1065
|
+
debugLog(`[_responseZaiStream] Model: ${request.model}`);
|
|
1066
|
+
|
|
1067
|
+
try {
|
|
1068
|
+
const convertStart = Date.now();
|
|
1069
|
+
const zaiRequest = convertResponsesRequestToZaiFormat(request);
|
|
1070
|
+
// Always use streaming for Z.AI API calls
|
|
1071
|
+
zaiRequest.stream = true;
|
|
1072
|
+
timings.convert_request = Date.now() - convertStart;
|
|
1073
|
+
debugLog(`[_responseZaiStream] [1] Request converted: ${timings.convert_request}ms`);
|
|
1074
|
+
|
|
1075
|
+
// Log raw request payload before API call
|
|
1076
|
+
this._logPayload(zaiRequest, 'REQ-RAW', 'zai');
|
|
1077
|
+
|
|
1078
|
+
const streamOptions = {};
|
|
1079
|
+
if (options.signal) {
|
|
1080
|
+
streamOptions.signal = options.signal;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const streamInitStart = Date.now();
|
|
1084
|
+
debugLog(`[_responseZaiStream] [2] Initiating API stream...`);
|
|
1085
|
+
const stream = await this.client.messages.stream(zaiRequest, streamOptions);
|
|
1086
|
+
timings.stream_init = Date.now() - streamInitStart;
|
|
1087
|
+
debugLog(`[_responseZaiStream] [3] Stream initiated: ${timings.stream_init}ms`);
|
|
1088
|
+
|
|
1089
|
+
const streamId = `resp_${Date.now()}`;
|
|
1090
|
+
const created = Math.floor(Date.now() / 1000);
|
|
1091
|
+
let currentMessageId = `msg_${Date.now()}`;
|
|
1092
|
+
|
|
1093
|
+
let eventCount = 0;
|
|
1094
|
+
let firstEventTime = null;
|
|
1095
|
+
const streamReadStart = Date.now();
|
|
1096
|
+
|
|
1097
|
+
debugLog(`[_responseZaiStream] [4] Reading stream events...`);
|
|
1098
|
+
|
|
1099
|
+
let currentThinkingText = '';
|
|
1100
|
+
|
|
1101
|
+
for await (const event of stream) {
|
|
1102
|
+
eventCount++;
|
|
1103
|
+
if (!firstEventTime) {
|
|
1104
|
+
firstEventTime = Date.now();
|
|
1105
|
+
timings.first_event = firstEventTime - streamReadStart;
|
|
1106
|
+
debugLog(`[_responseZaiStream] [4.1] First event: ${timings.first_event}ms, type: ${event.type}`);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Thinking 블록 처리 (Z.AI/GLM interleaved-thinking)
|
|
1110
|
+
if (event.type === 'content_block_start' && event.content_block?.type === 'thinking') {
|
|
1111
|
+
currentThinkingText = '';
|
|
1112
|
+
debugLog(`[_responseZaiStream] [4.2] Thinking block started`);
|
|
1113
|
+
} else if (event.type === 'content_block_delta' && event.delta?.type === 'thinking_delta') {
|
|
1114
|
+
currentThinkingText += event.delta.thinking || '';
|
|
1115
|
+
// Thinking delta를 스트리밍으로 전송
|
|
1116
|
+
const thinkingChunk = {
|
|
1117
|
+
id: streamId,
|
|
1118
|
+
object: 'response.delta',
|
|
1119
|
+
created_at: created,
|
|
1120
|
+
model: request.model || 'glm-4.7',
|
|
1121
|
+
delta: {
|
|
1122
|
+
type: 'thinking',
|
|
1123
|
+
message_id: currentMessageId,
|
|
1124
|
+
thinking: event.delta.thinking || ''
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
chunks.push(thinkingChunk);
|
|
1128
|
+
yield thinkingChunk;
|
|
1129
|
+
} else if (event.type === 'content_block_stop' && currentThinkingText) {
|
|
1130
|
+
debugLog(`[_responseZaiStream] [4.3] Thinking block completed: ${currentThinkingText.length} chars`);
|
|
1131
|
+
currentThinkingText = '';
|
|
1132
|
+
} else if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
|
|
1133
|
+
const deltaChunk = {
|
|
1134
|
+
id: streamId,
|
|
1135
|
+
object: 'response.delta',
|
|
1136
|
+
created_at: created,
|
|
1137
|
+
model: request.model || 'glm-4.7',
|
|
1138
|
+
delta: {
|
|
1139
|
+
type: 'output_text',
|
|
1140
|
+
message_id: currentMessageId,
|
|
1141
|
+
text: event.delta.text
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
chunks.push(deltaChunk);
|
|
1145
|
+
yield deltaChunk;
|
|
1146
|
+
} else if (event.type === 'message_delta' && event.delta?.stop_reason) {
|
|
1147
|
+
timings.stream_read = Date.now() - streamReadStart;
|
|
1148
|
+
debugLog(`[_responseZaiStream] [5] Stream complete: ${timings.stream_read}ms, events: ${eventCount}`);
|
|
1149
|
+
|
|
1150
|
+
const doneChunk = {
|
|
1151
|
+
id: streamId,
|
|
1152
|
+
object: 'response.done',
|
|
1153
|
+
created_at: created,
|
|
1154
|
+
model: request.model || 'glm-4.7',
|
|
1155
|
+
status: 'completed'
|
|
1156
|
+
};
|
|
1157
|
+
chunks.push(doneChunk);
|
|
1158
|
+
this._logPayload(chunks, 'RES', 'zai');
|
|
1159
|
+
|
|
1160
|
+
const elapsedTime = Date.now() - startTime;
|
|
1161
|
+
debugLog(`[_responseZaiStream] ========== END: ${elapsedTime}ms total ==========`);
|
|
1162
|
+
|
|
1163
|
+
yield doneChunk;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
const elapsedTime = Date.now() - startTime;
|
|
1168
|
+
debugLog(`[_responseZaiStream] ========== ERROR after ${elapsedTime}ms ==========`);
|
|
1169
|
+
debugLog(`[_responseZaiStream] Error: ${error.name} - ${error.message}`);
|
|
1170
|
+
|
|
1171
|
+
// Log error for debugging
|
|
1172
|
+
this._logPayload({
|
|
1173
|
+
error_message: error.message,
|
|
1174
|
+
error_stack: error.stack,
|
|
1175
|
+
error_name: error.name,
|
|
1176
|
+
error_status: error.status,
|
|
1177
|
+
_timing: { ...timings, elapsed_ms: elapsedTime, start: startTime, end: Date.now() },
|
|
1178
|
+
full_error: JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error)))
|
|
1179
|
+
}, 'ERROR-ZAI-STREAM', 'zai');
|
|
1180
|
+
throw normalizeError(error, 'zai');
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
808
1184
|
/**
|
|
809
1185
|
* Gemini streaming response
|
|
810
1186
|
* @param {Object} request - Responses API format request
|
|
@@ -60,9 +60,14 @@ export function convertResponsesRequestToClaudeFormat(responsesRequest) {
|
|
|
60
60
|
}
|
|
61
61
|
} else if (item.type === 'function_call') {
|
|
62
62
|
// Function call from output - convert to assistant message with tool_use
|
|
63
|
+
// Add placeholder text for Claude API compatibility
|
|
63
64
|
messages.push({
|
|
64
65
|
role: 'assistant',
|
|
65
66
|
content: [
|
|
67
|
+
{
|
|
68
|
+
type: 'text',
|
|
69
|
+
text: '(no content)'
|
|
70
|
+
},
|
|
66
71
|
{
|
|
67
72
|
type: 'tool_use',
|
|
68
73
|
id: item.call_id || item.id,
|
|
@@ -72,16 +77,27 @@ export function convertResponsesRequestToClaudeFormat(responsesRequest) {
|
|
|
72
77
|
]
|
|
73
78
|
});
|
|
74
79
|
} else if (item.type === 'function_call_output') {
|
|
75
|
-
// Function call output - convert to tool_result
|
|
80
|
+
// Function call output - convert to tool_result with error handling
|
|
81
|
+
const toolResult = {
|
|
82
|
+
type: 'tool_result',
|
|
83
|
+
tool_use_id: item.call_id,
|
|
84
|
+
content: typeof item.output === 'string' ? item.output : JSON.stringify(item.output)
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Add is_error flag if the output indicates an error
|
|
88
|
+
if (item.is_error === true) {
|
|
89
|
+
toolResult.is_error = true;
|
|
90
|
+
} else if (typeof item.output === 'object' && item.output !== null) {
|
|
91
|
+
// Check for operation_successful: false pattern
|
|
92
|
+
if (item.output.operation_successful === false ||
|
|
93
|
+
item.output.stdout?.operation_successful === false) {
|
|
94
|
+
toolResult.is_error = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
76
98
|
messages.push({
|
|
77
99
|
role: 'user',
|
|
78
|
-
content: [
|
|
79
|
-
{
|
|
80
|
-
type: 'tool_result',
|
|
81
|
-
tool_use_id: item.call_id,
|
|
82
|
-
content: item.output
|
|
83
|
-
}
|
|
84
|
-
]
|
|
100
|
+
content: [toolResult]
|
|
85
101
|
});
|
|
86
102
|
}
|
|
87
103
|
// Skip other types like 'reasoning'
|
|
@@ -98,16 +114,21 @@ export function convertResponsesRequestToClaudeFormat(responsesRequest) {
|
|
|
98
114
|
: item.content;
|
|
99
115
|
claudeRequest.system = content;
|
|
100
116
|
} else if (item.role === 'tool') {
|
|
101
|
-
// Tool result
|
|
117
|
+
// Tool result with error handling
|
|
118
|
+
const toolResult = {
|
|
119
|
+
type: 'tool_result',
|
|
120
|
+
tool_use_id: item.tool_call_id || item.id,
|
|
121
|
+
content: typeof item.content === 'string' ? item.content : JSON.stringify(item.content)
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Add is_error flag if present
|
|
125
|
+
if (item.is_error === true) {
|
|
126
|
+
toolResult.is_error = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
102
129
|
messages.push({
|
|
103
130
|
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
|
-
]
|
|
131
|
+
content: [toolResult]
|
|
111
132
|
});
|
|
112
133
|
} else if (item.role === 'assistant' && Array.isArray(item.content)) {
|
|
113
134
|
// Assistant with output array (might contain function_call items)
|
|
@@ -136,6 +157,15 @@ export function convertResponsesRequestToClaudeFormat(responsesRequest) {
|
|
|
136
157
|
}
|
|
137
158
|
}
|
|
138
159
|
|
|
160
|
+
// If we have tool_use blocks but no text, add a placeholder text
|
|
161
|
+
// Claude API recommends having text content with tool calls
|
|
162
|
+
if (toolUseBlocks.length > 0 && textBlocks.length === 0) {
|
|
163
|
+
textBlocks.push({
|
|
164
|
+
type: 'text',
|
|
165
|
+
text: '(no content)'
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
139
169
|
// Claude requires text blocks to come before tool_use blocks
|
|
140
170
|
const claudeContent = [...textBlocks, ...toolUseBlocks];
|
|
141
171
|
|
|
@@ -394,18 +424,37 @@ export function convertClaudeResponseToResponsesFormat(claudeResponse, model = '
|
|
|
394
424
|
});
|
|
395
425
|
}
|
|
396
426
|
|
|
427
|
+
// Determine status and incomplete_details based on stop_reason
|
|
428
|
+
const stopReason = claudeResponse.stop_reason;
|
|
429
|
+
const isCompleted = stopReason === 'end_turn' || stopReason === 'tool_use';
|
|
430
|
+
const isMaxTokens = stopReason === 'max_tokens';
|
|
431
|
+
|
|
432
|
+
// Build incomplete_details if response was truncated
|
|
433
|
+
let incompleteDetails = null;
|
|
434
|
+
if (isMaxTokens) {
|
|
435
|
+
incompleteDetails = {
|
|
436
|
+
reason: 'max_output_tokens',
|
|
437
|
+
message: 'Response was truncated because it reached the maximum output token limit'
|
|
438
|
+
};
|
|
439
|
+
} else if (!isCompleted && stopReason) {
|
|
440
|
+
incompleteDetails = {
|
|
441
|
+
reason: stopReason,
|
|
442
|
+
message: `Response stopped with reason: ${stopReason}`
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
397
446
|
// Build Responses API response with ALL required fields
|
|
398
447
|
const responsesResponse = {
|
|
399
448
|
id: `resp_${claudeResponse.id}`,
|
|
400
449
|
object: 'response',
|
|
401
450
|
created_at: Math.floor(Date.now() / 1000),
|
|
402
|
-
status:
|
|
451
|
+
status: isCompleted ? 'completed' : 'incomplete',
|
|
403
452
|
background: false,
|
|
404
453
|
billing: {
|
|
405
454
|
payer: 'developer'
|
|
406
455
|
},
|
|
407
456
|
error: null,
|
|
408
|
-
incomplete_details:
|
|
457
|
+
incomplete_details: incompleteDetails,
|
|
409
458
|
instructions: originalRequest.instructions || null,
|
|
410
459
|
max_output_tokens: originalRequest.max_output_tokens || null,
|
|
411
460
|
max_tool_calls: null,
|