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.

Files changed (51) hide show
  1. package/README.md +210 -87
  2. package/index.js +33 -1
  3. package/package.json +3 -3
  4. package/payload_viewer/out/404/index.html +1 -1
  5. package/payload_viewer/out/404.html +1 -1
  6. package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
  7. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  8. package/payload_viewer/out/index.html +1 -1
  9. package/payload_viewer/out/index.txt +3 -3
  10. package/payload_viewer/web_server.js +361 -0
  11. package/src/LLMClient/client.js +392 -16
  12. package/src/LLMClient/converters/responses-to-claude.js +67 -18
  13. package/src/LLMClient/converters/responses-to-zai.js +608 -0
  14. package/src/LLMClient/errors.js +18 -4
  15. package/src/LLMClient/index.js +5 -0
  16. package/src/ai_based/completion_judge.js +35 -4
  17. package/src/ai_based/orchestrator.js +146 -35
  18. package/src/commands/agents.js +70 -0
  19. package/src/commands/commands.js +51 -0
  20. package/src/commands/debug.js +52 -0
  21. package/src/commands/help.js +11 -1
  22. package/src/commands/model.js +43 -7
  23. package/src/commands/skills.js +46 -0
  24. package/src/config/ai_models.js +96 -5
  25. package/src/config/constants.js +71 -0
  26. package/src/frontend/components/HelpView.js +106 -2
  27. package/src/frontend/components/SetupWizard.js +53 -8
  28. package/src/frontend/utils/toolUIFormatter.js +261 -0
  29. package/src/system/agents_loader.js +289 -0
  30. package/src/system/ai_request.js +147 -9
  31. package/src/system/command_parser.js +33 -3
  32. package/src/system/conversation_state.js +265 -0
  33. package/src/system/custom_command_loader.js +386 -0
  34. package/src/system/session.js +59 -35
  35. package/src/system/skill_loader.js +318 -0
  36. package/src/system/tool_approval.js +10 -0
  37. package/src/tools/file_reader.js +49 -9
  38. package/src/tools/glob.js +0 -3
  39. package/src/tools/ripgrep.js +5 -7
  40. package/src/tools/skill_tool.js +122 -0
  41. package/src/tools/web_downloader.js +0 -3
  42. package/src/util/clone.js +174 -0
  43. package/src/util/config.js +38 -2
  44. package/src/util/config_migration.js +174 -0
  45. package/src/util/path_validator.js +178 -0
  46. package/src/util/prompt_loader.js +68 -1
  47. package/src/util/safe_fs.js +43 -3
  48. package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
  49. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_buildManifest.js +0 -0
  50. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_clientMiddlewareManifest.json +0 -0
  51. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_ssgManifest.js +0 -0
@@ -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
- // Create log directory if specified and doesn't exist
45
- if (this.logDir) {
46
- try {
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
- return isStreaming ? this._responseOpenAIStream(request, options) : await this._responseOpenAI(request, options);
246
+ result = isStreaming ? this._responseOpenAIStream(request, options) : await this._responseOpenAI(request, options);
247
+ break;
210
248
 
211
249
  case 'claude':
212
- return isStreaming ? this._responseClaudeStream(request, options) : await this._responseClaude(request, options);
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
- return isStreaming ? this._responseGeminiStream(request, options) : await this._responseGemini(request, options);
258
+ result = isStreaming ? this._responseGeminiStream(request, options) : await this._responseGemini(request, options);
259
+ break;
216
260
 
217
261
  case 'ollama':
218
- return isStreaming ? this._responseOllamaStream(request, options) : await this._responseOllama(request, options);
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: claudeResponse.stop_reason === 'end_turn' || claudeResponse.stop_reason === 'tool_use' ? 'completed' : 'incomplete',
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: null,
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,