aiexecode 1.0.157

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.
Files changed (188) hide show
  1. package/LICENSE +68 -0
  2. package/README.md +347 -0
  3. package/config_template/mcp_config.json +3 -0
  4. package/config_template/package_name_store.json +5 -0
  5. package/config_template/settings.json +5 -0
  6. package/index.js +879 -0
  7. package/mcp-agent-lib/example/01-basic-usage.js +82 -0
  8. package/mcp-agent-lib/example/02-quick-start.js +52 -0
  9. package/mcp-agent-lib/example/03-http-server.js +76 -0
  10. package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
  11. package/mcp-agent-lib/example/05-error-handling.js +116 -0
  12. package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
  13. package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
  14. package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
  15. package/mcp-agent-lib/example/README.md +346 -0
  16. package/mcp-agent-lib/index.js +19 -0
  17. package/mcp-agent-lib/init.sh +3 -0
  18. package/mcp-agent-lib/package-lock.json +1216 -0
  19. package/mcp-agent-lib/package.json +53 -0
  20. package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
  21. package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
  22. package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
  23. package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
  24. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
  25. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
  26. package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
  27. package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
  28. package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
  29. package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
  30. package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
  31. package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
  32. package/mcp-agent-lib/src/mcp_client.js +1860 -0
  33. package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
  34. package/package.json +72 -0
  35. package/payload_viewer/out/404/index.html +1 -0
  36. package/payload_viewer/out/404.html +1 -0
  37. package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
  38. package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
  39. package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
  40. package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
  41. package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
  42. package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
  43. package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
  44. package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
  45. package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  46. package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
  47. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  48. package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
  49. package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
  50. package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
  51. package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
  52. package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
  53. package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
  54. package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
  55. package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
  56. package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
  57. package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  58. package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  59. package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  60. package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  61. package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  62. package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  63. package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
  64. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
  65. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
  66. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
  67. package/payload_viewer/out/favicon.ico +0 -0
  68. package/payload_viewer/out/file.svg +1 -0
  69. package/payload_viewer/out/globe.svg +1 -0
  70. package/payload_viewer/out/index.html +1 -0
  71. package/payload_viewer/out/index.txt +23 -0
  72. package/payload_viewer/out/next.svg +1 -0
  73. package/payload_viewer/out/vercel.svg +1 -0
  74. package/payload_viewer/out/window.svg +1 -0
  75. package/payload_viewer/web_server.js +861 -0
  76. package/prompts/completion_judge.txt +128 -0
  77. package/prompts/orchestrator.txt +1213 -0
  78. package/src/LLMClient/client.js +1375 -0
  79. package/src/LLMClient/converters/input-normalizer.js +238 -0
  80. package/src/LLMClient/converters/responses-to-claude.js +503 -0
  81. package/src/LLMClient/converters/responses-to-gemini.js +648 -0
  82. package/src/LLMClient/converters/responses-to-ollama.js +348 -0
  83. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  84. package/src/LLMClient/errors.js +398 -0
  85. package/src/LLMClient/index.js +36 -0
  86. package/src/ai_based/completion_judge.js +421 -0
  87. package/src/ai_based/orchestrator.js +527 -0
  88. package/src/ai_based/pip_package_installer.js +173 -0
  89. package/src/ai_based/pip_package_lookup.js +197 -0
  90. package/src/cli/mcp_cli.js +70 -0
  91. package/src/cli/mcp_commands.js +255 -0
  92. package/src/commands/agents.js +18 -0
  93. package/src/commands/apikey.js +55 -0
  94. package/src/commands/bg.js +140 -0
  95. package/src/commands/commands.js +56 -0
  96. package/src/commands/debug.js +54 -0
  97. package/src/commands/exit.js +19 -0
  98. package/src/commands/help.js +35 -0
  99. package/src/commands/mcp.js +128 -0
  100. package/src/commands/model.js +176 -0
  101. package/src/commands/setup.js +13 -0
  102. package/src/commands/skills.js +51 -0
  103. package/src/commands/tools.js +165 -0
  104. package/src/commands/viewer.js +147 -0
  105. package/src/config/ai_models.js +312 -0
  106. package/src/config/config.js +10 -0
  107. package/src/config/constants.js +71 -0
  108. package/src/config/feature_flags.js +15 -0
  109. package/src/frontend/App.js +1263 -0
  110. package/src/frontend/README.md +81 -0
  111. package/src/frontend/components/AutocompleteMenu.js +47 -0
  112. package/src/frontend/components/BackgroundProcessList.js +175 -0
  113. package/src/frontend/components/BlankLine.js +62 -0
  114. package/src/frontend/components/ConversationItem.js +893 -0
  115. package/src/frontend/components/CurrentModelView.js +43 -0
  116. package/src/frontend/components/FileDiffViewer.js +616 -0
  117. package/src/frontend/components/Footer.js +25 -0
  118. package/src/frontend/components/Header.js +42 -0
  119. package/src/frontend/components/HelpView.js +154 -0
  120. package/src/frontend/components/Input.js +344 -0
  121. package/src/frontend/components/LoadingIndicator.js +31 -0
  122. package/src/frontend/components/ModelListView.js +49 -0
  123. package/src/frontend/components/ModelUpdatedView.js +22 -0
  124. package/src/frontend/components/SessionSpinner.js +66 -0
  125. package/src/frontend/components/SetupWizard.js +242 -0
  126. package/src/frontend/components/StreamOutput.js +34 -0
  127. package/src/frontend/components/TodoList.js +56 -0
  128. package/src/frontend/components/ToolApprovalPrompt.js +452 -0
  129. package/src/frontend/design/themeColors.js +42 -0
  130. package/src/frontend/hooks/useCompletion.js +84 -0
  131. package/src/frontend/hooks/useFileCompletion.js +467 -0
  132. package/src/frontend/hooks/useKeypress.js +145 -0
  133. package/src/frontend/index.js +65 -0
  134. package/src/frontend/utils/GridRenderer.js +140 -0
  135. package/src/frontend/utils/InlineFormatter.js +156 -0
  136. package/src/frontend/utils/diffUtils.js +235 -0
  137. package/src/frontend/utils/inputBuffer.js +441 -0
  138. package/src/frontend/utils/markdownParser.js +377 -0
  139. package/src/frontend/utils/outputRedirector.js +47 -0
  140. package/src/frontend/utils/renderInkComponent.js +42 -0
  141. package/src/frontend/utils/syntaxHighlighter.js +149 -0
  142. package/src/frontend/utils/toolUIFormatter.js +261 -0
  143. package/src/system/agents_loader.js +170 -0
  144. package/src/system/ai_request.js +737 -0
  145. package/src/system/background_process.js +317 -0
  146. package/src/system/code_executer.js +1233 -0
  147. package/src/system/command_loader.js +40 -0
  148. package/src/system/command_parser.js +133 -0
  149. package/src/system/conversation_state.js +265 -0
  150. package/src/system/conversation_trimmer.js +265 -0
  151. package/src/system/custom_command_loader.js +395 -0
  152. package/src/system/file_integrity.js +466 -0
  153. package/src/system/import_analyzer.py +174 -0
  154. package/src/system/log.js +82 -0
  155. package/src/system/mcp_integration.js +304 -0
  156. package/src/system/output_helper.js +89 -0
  157. package/src/system/session.js +1393 -0
  158. package/src/system/session_memory.js +481 -0
  159. package/src/system/skill_loader.js +324 -0
  160. package/src/system/system_info.js +483 -0
  161. package/src/system/tool_approval.js +160 -0
  162. package/src/system/tool_registry.js +184 -0
  163. package/src/system/ui_events.js +279 -0
  164. package/src/tools/code_editor.js +792 -0
  165. package/src/tools/file_reader.js +385 -0
  166. package/src/tools/glob.js +263 -0
  167. package/src/tools/response_message.js +30 -0
  168. package/src/tools/ripgrep.js +554 -0
  169. package/src/tools/skill_tool.js +122 -0
  170. package/src/tools/todo_write.js +182 -0
  171. package/src/tools/web_download.py +74 -0
  172. package/src/tools/web_downloader.js +83 -0
  173. package/src/util/clone.js +174 -0
  174. package/src/util/config.js +203 -0
  175. package/src/util/config_migration.js +174 -0
  176. package/src/util/debug_log.js +49 -0
  177. package/src/util/exit_handler.js +53 -0
  178. package/src/util/file_reference_parser.js +132 -0
  179. package/src/util/mcp_config_manager.js +159 -0
  180. package/src/util/output_formatter.js +50 -0
  181. package/src/util/path_helper.js +27 -0
  182. package/src/util/path_validator.js +178 -0
  183. package/src/util/prompt_loader.js +184 -0
  184. package/src/util/rag_helper.js +101 -0
  185. package/src/util/safe_fs.js +645 -0
  186. package/src/util/setup_wizard.js +62 -0
  187. package/src/util/text_formatter.js +33 -0
  188. package/src/util/version_check.js +116 -0
@@ -0,0 +1,861 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import OpenAI from 'openai';
6
+ import Anthropic from '@anthropic-ai/sdk';
7
+ function consolelog() { }
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ // Get home directory
13
+ function getHomeDirectory() {
14
+ return process.env.HOME || process.env.USERPROFILE || '/home/ubuntu';
15
+ }
16
+
17
+ // Get AI Agent config directory
18
+ const CONFIG_DIR = path.join(getHomeDirectory(), '.aiexe');
19
+ const PAYLOAD_LOG_DIR = path.join(CONFIG_DIR, 'payload_log');
20
+
21
+ // Load AI Agent settings
22
+ function loadAIAgentSettings() {
23
+ try {
24
+ const settingsPath = path.join(CONFIG_DIR, 'settings.json');
25
+ if (fs.existsSync(settingsPath)) {
26
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
27
+ consolelog('✅ AI Agent settings loaded from ~/.aiexe/settings.json');
28
+ return settings;
29
+ }
30
+ } catch (error) {
31
+ consolelog('⚠️ Could not load AI Agent settings:', error.message);
32
+ }
33
+ return {};
34
+ }
35
+
36
+ const aiAgentSettings = loadAIAgentSettings();
37
+
38
+ // Initialize OpenAI client (check multiple sources for API key)
39
+ let openai = null;
40
+ const openaiApiKey = process.env.OPENAI_API_KEY || aiAgentSettings.OPENAI_API_KEY;
41
+
42
+ if (openaiApiKey) {
43
+ openai = new OpenAI({
44
+ apiKey: openaiApiKey,
45
+ });
46
+ consolelog('✅ OpenAI client initialized');
47
+ } else {
48
+ consolelog('⚠️ OpenAI API key not found - API testing will return mock responses');
49
+ consolelog(' Checked: process.env.OPENAI_API_KEY and ~/.aiexe/settings.json');
50
+ }
51
+
52
+ // Initialize Z.AI client (Anthropic SDK with Z.AI base URL)
53
+ let zaiClient = null;
54
+ const zaiApiKey = process.env.ZAI_API_KEY || aiAgentSettings.API_KEY;
55
+ const ZAI_BASE_URL = 'https://api.z.ai/api/anthropic';
56
+
57
+ // Check if API key is Z.AI format (32-char hex + '.' + 16-char alphanumeric)
58
+ function isZaiApiKey(apiKey) {
59
+ if (!apiKey || typeof apiKey !== 'string') return false;
60
+ return /^[a-f0-9]{32}\.[A-Za-z0-9]{16}$/.test(apiKey);
61
+ }
62
+
63
+ if (zaiApiKey && isZaiApiKey(zaiApiKey)) {
64
+ zaiClient = new Anthropic({
65
+ apiKey: zaiApiKey,
66
+ baseURL: ZAI_BASE_URL,
67
+ });
68
+ consolelog('✅ Z.AI client initialized (Anthropic SDK with Z.AI base URL)');
69
+ } else {
70
+ consolelog('⚠️ Z.AI API key not found or invalid format - Z.AI testing will return mock responses');
71
+ }
72
+
73
+ // Initialize Anthropic client
74
+ let anthropicClient = null;
75
+ const anthropicApiKey = process.env.ANTHROPIC_API_KEY || aiAgentSettings.ANTHROPIC_API_KEY;
76
+
77
+ // Check if API key is Anthropic format (sk-ant-...)
78
+ function isAnthropicApiKey(apiKey) {
79
+ if (!apiKey || typeof apiKey !== 'string') return false;
80
+ return apiKey.startsWith('sk-ant-');
81
+ }
82
+
83
+ if (anthropicApiKey && isAnthropicApiKey(anthropicApiKey)) {
84
+ anthropicClient = new Anthropic({
85
+ apiKey: anthropicApiKey,
86
+ });
87
+ consolelog('✅ Anthropic client initialized');
88
+ } else {
89
+ consolelog('⚠️ Anthropic API key not found or invalid format - Anthropic testing will return mock responses');
90
+ }
91
+
92
+
93
+ // ID generation function
94
+ function generateId() {
95
+ return Math.random().toString(36).substring(2, 12) + Date.now().toString(36);
96
+ }
97
+
98
+ function startWebServer(port = 3000) {
99
+ const app = express();
100
+
101
+ // Parse JSON bodies (increase limit for large AI request/response payloads)
102
+ app.use(express.json({ limit: '50mb' }));
103
+
104
+ // Serve static files from Next.js build
105
+ const outPath = path.join(__dirname, 'out');
106
+ const isProduction = true; // Always production mode
107
+
108
+ // Serve all static files from 'out' directory (Next.js export output)
109
+ if (fs.existsSync(outPath)) {
110
+ app.use(express.static(outPath));
111
+ consolelog(`📁 Serving Next.js static files from: ${outPath}`);
112
+ } else {
113
+ consolelog(`⚠️ Warning: 'out' directory not found at ${outPath}`);
114
+ consolelog(` Please run: npm run build`);
115
+ }
116
+
117
+ // Helper function to parse log filename
118
+ function parseLogFileName(fileName) {
119
+ const match = fileName.match(/^(\d{4}-\d{2}-\d{2})_(\d{9})_(.+)_(REQ|RES)\.json$/);
120
+
121
+ if (match) {
122
+ const [, date, timestamp, component, type] = match;
123
+ const hour = timestamp.substring(0, 2);
124
+ const minute = timestamp.substring(2, 4);
125
+ const second = timestamp.substring(4, 6);
126
+ const millisecond = timestamp.substring(6, 9);
127
+
128
+ return {
129
+ date,
130
+ timestamp,
131
+ component,
132
+ type,
133
+ datetime: new Date(`${date}T${hour}:${minute}:${second}.${millisecond}Z`)
134
+ };
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ // Helper function to create workflow groups (improved version from payload_viewer_old)
141
+ function createWorkflowGroups(files) {
142
+ const workflows = new Map();
143
+
144
+ files.forEach(file => {
145
+ if (!file.parsedInfo) return;
146
+
147
+ const { date, timestamp } = file.parsedInfo;
148
+ const workflowKey = `${date}_${timestamp.substring(0, 6)}`; // Group by date and time up to seconds
149
+
150
+ if (!workflows.has(workflowKey)) {
151
+ workflows.set(workflowKey, {
152
+ id: workflowKey,
153
+ date: date,
154
+ startTime: file.parsedInfo.datetime,
155
+ files: [],
156
+ components: new Set(),
157
+ status: 'unknown'
158
+ });
159
+ }
160
+
161
+ const workflow = workflows.get(workflowKey);
162
+ workflow.files.push(file);
163
+ workflow.components.add(file.parsedInfo.component);
164
+
165
+ // Determine workflow status
166
+ if (workflow.files.some(f => f.parsedInfo.component === 'verifier' && f.parsedInfo.type === 'RES')) {
167
+ workflow.status = 'completed';
168
+ } else if (workflow.files.length > 0) {
169
+ workflow.status = 'in_progress';
170
+ }
171
+ });
172
+
173
+ // Convert Map to array and sort by time
174
+ return Array.from(workflows.values())
175
+ .sort((a, b) => b.startTime - a.startTime)
176
+ .map(workflow => ({
177
+ ...workflow,
178
+ components: Array.from(workflow.components),
179
+ duration: calculateWorkflowDuration(workflow.files),
180
+ startTime: workflow.startTime,
181
+ endTime: workflow.files.length > 0 ?
182
+ new Date(Math.max(...workflow.files.map(f => f.parsedInfo ? new Date(f.parsedInfo.datetime).getTime() : 0))).toISOString() :
183
+ workflow.startTime
184
+ }));
185
+ }
186
+
187
+ // Helper function to calculate workflow duration
188
+ function calculateWorkflowDuration(files) {
189
+ if (files.length < 2) return 0;
190
+
191
+ const times = files
192
+ .filter(f => f.parsedInfo)
193
+ .map(f => new Date(f.parsedInfo.datetime))
194
+ .sort((a, b) => a.getTime() - b.getTime());
195
+
196
+ if (times.length < 2) return 0;
197
+
198
+ return times[times.length - 1].getTime() - times[0].getTime();
199
+ }
200
+
201
+ // API routes for payload logs
202
+ app.get('/api/logs', (req, res) => {
203
+ try {
204
+ const logDir = PAYLOAD_LOG_DIR;
205
+ if (!fs.existsSync(logDir)) {
206
+ return res.json({ files: [], workflows: [] });
207
+ }
208
+
209
+ const files = fs.readdirSync(logDir)
210
+ .filter(file => file.endsWith('.json'))
211
+ .map(file => {
212
+ const filePath = path.join(logDir, file);
213
+ const stats = fs.statSync(filePath);
214
+ const parsedInfo = parseLogFileName(file);
215
+
216
+ const fileInfo = {
217
+ name: file,
218
+ size: stats.size,
219
+ modified: stats.mtime.toISOString(),
220
+ created: stats.birthtime.toISOString(),
221
+ extension: path.extname(file),
222
+ parsedInfo: parsedInfo || undefined
223
+ };
224
+
225
+ // Convert datetime to ISO string if it exists
226
+ if (fileInfo.parsedInfo && fileInfo.parsedInfo.datetime) {
227
+ fileInfo.parsedInfo.datetime = fileInfo.parsedInfo.datetime.toISOString();
228
+ }
229
+
230
+ return fileInfo;
231
+ });
232
+
233
+ // Sort files by time
234
+ files.sort((a, b) => {
235
+ const timeA = a.parsedInfo?.datetime ? new Date(a.parsedInfo.datetime) : new Date(a.created);
236
+ const timeB = b.parsedInfo?.datetime ? new Date(b.parsedInfo.datetime) : new Date(b.created);
237
+ return timeB.getTime() - timeA.getTime();
238
+ });
239
+
240
+ const workflows = createWorkflowGroups(files);
241
+
242
+ res.json({ files, workflows });
243
+ } catch (error) {
244
+ consolelog('Error reading log files:', error);
245
+ res.status(500).json({ error: 'Failed to read log files' });
246
+ }
247
+ });
248
+
249
+ app.get('/api/logs/:filename', (req, res) => {
250
+ try {
251
+ const filename = decodeURIComponent(req.params.filename);
252
+ const filePath = path.join(PAYLOAD_LOG_DIR, filename);
253
+
254
+ // Security check
255
+ if (!filePath.startsWith(PAYLOAD_LOG_DIR)) {
256
+ return res.status(400).json({ error: 'Invalid file path' });
257
+ }
258
+
259
+ if (!fs.existsSync(filePath)) {
260
+ return res.status(404).json({ error: 'File not found' });
261
+ }
262
+
263
+ const content = fs.readFileSync(filePath, 'utf-8');
264
+ const data = JSON.parse(content);
265
+ res.json(data);
266
+ } catch (error) {
267
+ consolelog('Error reading file:', error);
268
+ if (error instanceof SyntaxError) {
269
+ res.status(400).json({ error: 'Invalid JSON file' });
270
+ } else {
271
+ res.status(500).json({ error: 'Failed to read file' });
272
+ }
273
+ }
274
+ });
275
+
276
+ app.put('/api/logs/:filename', (req, res) => {
277
+ try {
278
+ const filename = decodeURIComponent(req.params.filename);
279
+ const filePath = path.join(PAYLOAD_LOG_DIR, filename);
280
+
281
+ // Security check
282
+ if (!filePath.startsWith(PAYLOAD_LOG_DIR)) {
283
+ return res.status(400).json({ error: 'Invalid file path' });
284
+ }
285
+
286
+ const body = JSON.stringify(req.body, null, 2);
287
+
288
+ // Backup original file
289
+ const backupPath = `${filePath}.backup.${Date.now()}`;
290
+ if (fs.existsSync(filePath)) {
291
+ fs.copyFileSync(filePath, backupPath);
292
+ }
293
+
294
+ // Save file
295
+ fs.writeFileSync(filePath, body, 'utf-8');
296
+
297
+ res.json({ success: true });
298
+ } catch (error) {
299
+ consolelog('Error saving file:', error);
300
+ res.status(500).json({ error: 'Failed to save file' });
301
+ }
302
+ });
303
+
304
+ // OpenAI API test endpoint
305
+ app.post('/api/test-openai', async (req, res) => {
306
+ try {
307
+ const payload = req.body;
308
+
309
+ // Actual OpenAI API call (if API key is available)
310
+ if (openai && openaiApiKey) {
311
+ // Use OpenAI responses.create (pass payload as is)
312
+ let response = await openai.responses.create(payload);
313
+ let allToolOutputs = [];
314
+ let currentPayload = payload;
315
+ let allResponses = [response];
316
+
317
+ // Handle function_calls iteratively
318
+ let maxIterations = 10; // Prevent infinite loops
319
+ let iteration = 0;
320
+ let hasProcessedFunctionCalls = false;
321
+
322
+ // Process function_calls while they exist
323
+ while (iteration < maxIterations) {
324
+ // Check response structure (response or response.data)
325
+ const responseData = response.data || response;
326
+
327
+ if (!responseData || !responseData.output || !Array.isArray(responseData.output)) {
328
+ break;
329
+ }
330
+
331
+ const functionCalls = responseData.output.filter(item => item.type === 'function_call');
332
+
333
+ if (functionCalls.length === 0) {
334
+ if (hasProcessedFunctionCalls) {
335
+ break; // No more function_calls to execute
336
+ } else {
337
+ break; // No function_calls in first response
338
+ }
339
+ }
340
+
341
+ hasProcessedFunctionCalls = true;
342
+ const currentIterationOutputs = [];
343
+
344
+ // Execute each function_call sequentially
345
+ for (const functionCall of functionCalls) {
346
+ try {
347
+ // Mock function execution for now
348
+ const toolOutput = {
349
+ tool_call_id: functionCall.call_id || functionCall.id || generateId(),
350
+ output: JSON.stringify({
351
+ result: "Mock function execution result",
352
+ status: "success",
353
+ timestamp: new Date().toISOString()
354
+ })
355
+ };
356
+ currentIterationOutputs.push(toolOutput);
357
+ allToolOutputs.push(toolOutput);
358
+ } catch (error) {
359
+ consolelog(`Function call execution failed: ${error.message}`);
360
+ const errorOutput = {
361
+ tool_call_id: functionCall.call_id || functionCall.id || generateId(),
362
+ output: JSON.stringify({
363
+ error: `Execution failed: ${error.message}`,
364
+ status: "failed",
365
+ timestamp: new Date().toISOString()
366
+ })
367
+ };
368
+ currentIterationOutputs.push(errorOutput);
369
+ allToolOutputs.push(errorOutput);
370
+ }
371
+ }
372
+
373
+ // Continuation request with tool_outputs
374
+ if (currentIterationOutputs.length > 0) {
375
+ currentPayload = {
376
+ ...payload,
377
+ tool_outputs: [...allToolOutputs],
378
+ response_id: response?.id || responseData?.id,
379
+ messages: [
380
+ ...(payload.messages || []),
381
+ ]
382
+ };
383
+
384
+ try {
385
+ response = await openai.responses.create(currentPayload);
386
+ allResponses.push(response);
387
+ iteration++;
388
+ } catch (error) {
389
+ consolelog(`Continuation request failed: ${error.message}`);
390
+ break;
391
+ }
392
+ } else {
393
+ break;
394
+ }
395
+ }
396
+
397
+ // Final response composition
398
+ const finalResponse = allResponses[0];
399
+ const finalResponseData = finalResponse.data || finalResponse;
400
+
401
+ res.json({
402
+ success: true,
403
+ provider: 'openai',
404
+ status: 200,
405
+ data: {
406
+ ...finalResponseData,
407
+ provider: 'openai',
408
+ tool_outputs: allToolOutputs,
409
+ all_responses: allResponses,
410
+ iterations_executed: iteration,
411
+ function_calls_processed: allToolOutputs.length
412
+ },
413
+ tool_outputs: allToolOutputs,
414
+ iterations: iteration,
415
+ originalPayload: payload,
416
+ convertedPayload: payload
417
+ });
418
+ } else {
419
+ // Mock response when API key is not available
420
+ res.json({
421
+ success: false,
422
+ provider: 'openai',
423
+ error: 'OPENAI_API_KEY environment variable not set',
424
+ mockResponse: {
425
+ taskName: "api_test_RES",
426
+ timestamp: new Date().toISOString(),
427
+ data: {
428
+ id: `resp_${generateId()}`,
429
+ object: "response",
430
+ created_at: Math.floor(Date.now() / 1000),
431
+ status: "completed",
432
+ model: payload.model || 'gpt-5-mini',
433
+ provider: 'openai',
434
+ output: [{
435
+ id: `msg_${generateId()}`,
436
+ type: "message",
437
+ status: "completed",
438
+ content: [{
439
+ type: "output_text",
440
+ text: 'This is a mock response. Set OPENAI_API_KEY to get real responses.'
441
+ }],
442
+ role: "assistant"
443
+ }],
444
+ usage: {
445
+ input_tokens: 10,
446
+ output_tokens: 15,
447
+ total_tokens: 25
448
+ }
449
+ }
450
+ },
451
+ originalPayload: payload
452
+ });
453
+ }
454
+ } catch (error) {
455
+ res.status(500).json({
456
+ success: false,
457
+ provider: 'openai',
458
+ error: error.message,
459
+ originalPayload: req.body
460
+ });
461
+ }
462
+ });
463
+
464
+ // Anthropic API test endpoint
465
+ app.post('/api/test-anthropic', async (req, res) => {
466
+ try {
467
+ const payload = req.body;
468
+
469
+ if (anthropicClient && anthropicApiKey && isAnthropicApiKey(anthropicApiKey)) {
470
+ // Convert Responses API format to Anthropic Messages API format if needed
471
+ let anthropicPayload = { ...payload };
472
+
473
+ // If payload uses Responses API format (input array), convert to messages
474
+ if (payload.input && Array.isArray(payload.input)) {
475
+ const messages = [];
476
+ let systemMessage = null;
477
+
478
+ for (const item of payload.input) {
479
+ if (item.role === 'system') {
480
+ if (Array.isArray(item.content)) {
481
+ systemMessage = item.content.map(c => c.text || c).join('\n');
482
+ } else if (typeof item.content === 'string') {
483
+ systemMessage = item.content;
484
+ }
485
+ } else if (item.role === 'user' || item.role === 'assistant') {
486
+ let content;
487
+ if (Array.isArray(item.content)) {
488
+ content = item.content.map(c => ({
489
+ type: 'text',
490
+ text: c.text || c
491
+ }));
492
+ } else {
493
+ content = [{ type: 'text', text: item.content || '' }];
494
+ }
495
+ messages.push({ role: item.role, content });
496
+ }
497
+ }
498
+
499
+ anthropicPayload = {
500
+ model: payload.model || 'claude-3-haiku-20240307',
501
+ max_tokens: payload.max_output_tokens || payload.max_tokens || 4096,
502
+ messages: messages,
503
+ };
504
+
505
+ if (systemMessage) {
506
+ anthropicPayload.system = systemMessage;
507
+ }
508
+ }
509
+
510
+ // Ensure required fields
511
+ if (!anthropicPayload.model) anthropicPayload.model = 'claude-3-haiku-20240307';
512
+ if (!anthropicPayload.max_tokens) anthropicPayload.max_tokens = 4096;
513
+
514
+ // Make the API call
515
+ const response = await anthropicClient.messages.create(anthropicPayload);
516
+
517
+ // Convert Anthropic response to Responses API format
518
+ const output = [];
519
+ let outputText = '';
520
+
521
+ if (response.content && Array.isArray(response.content)) {
522
+ for (const block of response.content) {
523
+ if (block.type === 'text') {
524
+ outputText += block.text;
525
+ } else if (block.type === 'tool_use') {
526
+ output.push({
527
+ type: 'function_call',
528
+ id: block.id,
529
+ call_id: block.id,
530
+ name: block.name,
531
+ arguments: JSON.stringify(block.input)
532
+ });
533
+ }
534
+ }
535
+ }
536
+
537
+ if (outputText) {
538
+ output.unshift({
539
+ type: 'message',
540
+ id: `msg_${generateId()}`,
541
+ status: 'completed',
542
+ role: 'assistant',
543
+ content: [{
544
+ type: 'output_text',
545
+ text: outputText
546
+ }]
547
+ });
548
+ }
549
+
550
+ const convertedResponse = {
551
+ id: response.id,
552
+ object: 'response',
553
+ created_at: Math.floor(Date.now() / 1000),
554
+ status: response.stop_reason === 'end_turn' ? 'completed' : 'incomplete',
555
+ model: response.model,
556
+ provider: 'anthropic',
557
+ output: output,
558
+ usage: {
559
+ input_tokens: response.usage?.input_tokens || 0,
560
+ output_tokens: response.usage?.output_tokens || 0,
561
+ total_tokens: (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0),
562
+ cache_creation_input_tokens: response.usage?.cache_creation_input_tokens,
563
+ cache_read_input_tokens: response.usage?.cache_read_input_tokens
564
+ },
565
+ stop_reason: response.stop_reason
566
+ };
567
+
568
+ res.json({
569
+ success: true,
570
+ provider: 'anthropic',
571
+ status: 200,
572
+ data: convertedResponse,
573
+ originalPayload: payload,
574
+ convertedPayload: anthropicPayload,
575
+ rawResponse: response
576
+ });
577
+ } else {
578
+ // Mock response when API key is not available
579
+ res.json({
580
+ success: false,
581
+ provider: 'anthropic',
582
+ error: 'Anthropic API key not found or invalid format',
583
+ hint: 'Anthropic API key format: sk-ant-...',
584
+ mockResponse: {
585
+ taskName: "api_test_RES",
586
+ timestamp: new Date().toISOString(),
587
+ data: {
588
+ id: `resp_${generateId()}`,
589
+ object: "response",
590
+ created_at: Math.floor(Date.now() / 1000),
591
+ status: "completed",
592
+ model: payload.model || 'claude-3-haiku-20240307',
593
+ provider: 'anthropic',
594
+ output: [{
595
+ id: `msg_${generateId()}`,
596
+ type: "message",
597
+ status: "completed",
598
+ content: [{
599
+ type: "output_text",
600
+ text: 'This is a mock response. Set a valid Anthropic API key to get real responses.'
601
+ }],
602
+ role: "assistant"
603
+ }],
604
+ usage: {
605
+ input_tokens: 10,
606
+ output_tokens: 15,
607
+ total_tokens: 25
608
+ }
609
+ }
610
+ },
611
+ originalPayload: payload
612
+ });
613
+ }
614
+ } catch (error) {
615
+ res.status(500).json({
616
+ success: false,
617
+ provider: 'anthropic',
618
+ error: error.message,
619
+ originalPayload: req.body
620
+ });
621
+ }
622
+ });
623
+
624
+ // Z.AI (GLM) API test endpoint - Uses Anthropic SDK with Z.AI base URL
625
+ app.post('/api/test-zai', async (req, res) => {
626
+ try {
627
+ const payload = req.body;
628
+
629
+ if (zaiClient && zaiApiKey && isZaiApiKey(zaiApiKey)) {
630
+ // Convert Responses API format to Anthropic Messages API format if needed
631
+ let zaiPayload = { ...payload };
632
+
633
+ // If payload uses Responses API format (input array), convert to messages
634
+ if (payload.input && Array.isArray(payload.input)) {
635
+ const messages = [];
636
+ let systemMessage = null;
637
+
638
+ for (const item of payload.input) {
639
+ if (item.role === 'system') {
640
+ // Extract system message content
641
+ if (Array.isArray(item.content)) {
642
+ systemMessage = item.content.map(c => c.text || c).join('\n');
643
+ } else if (typeof item.content === 'string') {
644
+ systemMessage = item.content;
645
+ }
646
+ } else if (item.role === 'user' || item.role === 'assistant') {
647
+ let content;
648
+ if (Array.isArray(item.content)) {
649
+ content = item.content.map(c => ({
650
+ type: 'text',
651
+ text: c.text || c
652
+ }));
653
+ } else {
654
+ content = [{ type: 'text', text: item.content || '' }];
655
+ }
656
+ messages.push({ role: item.role, content });
657
+ }
658
+ }
659
+
660
+ zaiPayload = {
661
+ model: payload.model || 'glm-4.5',
662
+ max_tokens: payload.max_output_tokens || payload.max_tokens || 8192,
663
+ messages: messages,
664
+ };
665
+
666
+ if (systemMessage) {
667
+ zaiPayload.system = systemMessage;
668
+ }
669
+ }
670
+
671
+ // Ensure required fields
672
+ if (!zaiPayload.model) zaiPayload.model = 'glm-4.5';
673
+ if (!zaiPayload.max_tokens) zaiPayload.max_tokens = 8192;
674
+
675
+ // Make the API call
676
+ const response = await zaiClient.messages.create(zaiPayload);
677
+
678
+ // Convert Anthropic response to Responses API format
679
+ const output = [];
680
+ let outputText = '';
681
+
682
+ if (response.content && Array.isArray(response.content)) {
683
+ for (const block of response.content) {
684
+ if (block.type === 'text') {
685
+ outputText += block.text;
686
+ } else if (block.type === 'tool_use') {
687
+ output.push({
688
+ type: 'function_call',
689
+ id: block.id,
690
+ call_id: block.id,
691
+ name: block.name,
692
+ arguments: JSON.stringify(block.input)
693
+ });
694
+ }
695
+ }
696
+ }
697
+
698
+ if (outputText) {
699
+ output.unshift({
700
+ type: 'message',
701
+ id: `msg_${generateId()}`,
702
+ status: 'completed',
703
+ role: 'assistant',
704
+ content: [{
705
+ type: 'output_text',
706
+ text: outputText
707
+ }]
708
+ });
709
+ }
710
+
711
+ const convertedResponse = {
712
+ id: response.id,
713
+ object: 'response',
714
+ created_at: Math.floor(Date.now() / 1000),
715
+ status: response.stop_reason === 'end_turn' ? 'completed' : 'incomplete',
716
+ model: response.model,
717
+ provider: 'zai',
718
+ output: output,
719
+ usage: {
720
+ input_tokens: response.usage?.input_tokens || 0,
721
+ output_tokens: response.usage?.output_tokens || 0,
722
+ total_tokens: (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0),
723
+ cache_creation_input_tokens: response.usage?.cache_creation_input_tokens,
724
+ cache_read_input_tokens: response.usage?.cache_read_input_tokens
725
+ },
726
+ stop_reason: response.stop_reason
727
+ };
728
+
729
+ res.json({
730
+ success: true,
731
+ provider: 'zai',
732
+ status: 200,
733
+ data: convertedResponse,
734
+ originalPayload: payload,
735
+ convertedPayload: zaiPayload,
736
+ rawResponse: response
737
+ });
738
+ } else {
739
+ // Mock response when API key is not available
740
+ res.json({
741
+ success: false,
742
+ provider: 'zai',
743
+ error: 'Z.AI API key not found or invalid format',
744
+ hint: 'Z.AI API key format: 32-char hex + "." + 16-char alphanumeric',
745
+ mockResponse: {
746
+ taskName: "api_test_RES",
747
+ timestamp: new Date().toISOString(),
748
+ data: {
749
+ id: `resp_${generateId()}`,
750
+ object: "response",
751
+ created_at: Math.floor(Date.now() / 1000),
752
+ status: "completed",
753
+ model: payload.model || 'glm-4.5',
754
+ provider: 'zai',
755
+ output: [{
756
+ id: `msg_${generateId()}`,
757
+ type: "message",
758
+ status: "completed",
759
+ content: [{
760
+ type: "output_text",
761
+ text: 'This is a mock response. Set a valid Z.AI API key to get real responses.'
762
+ }],
763
+ role: "assistant"
764
+ }],
765
+ usage: {
766
+ input_tokens: 10,
767
+ output_tokens: 15,
768
+ total_tokens: 25
769
+ }
770
+ }
771
+ },
772
+ originalPayload: payload
773
+ });
774
+ }
775
+ } catch (error) {
776
+ res.status(500).json({
777
+ success: false,
778
+ provider: 'zai',
779
+ error: error.message,
780
+ originalPayload: req.body
781
+ });
782
+ }
783
+ });
784
+
785
+ // Health check endpoint
786
+ app.get('/api/health', (req, res) => {
787
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
788
+ });
789
+
790
+ // Fallback to index.html for SPA routing (production mode)
791
+ app.use((req, res) => {
792
+ const indexPath = path.join(__dirname, 'out', 'index.html');
793
+ if (fs.existsSync(indexPath)) {
794
+ res.sendFile(indexPath);
795
+ } else {
796
+ res.status(404).send(`
797
+ <html>
798
+ <head>
799
+ <title>Frontend Not Built</title>
800
+ <style>
801
+ body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
802
+ .container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
803
+ h1 { color: #e74c3c; }
804
+ pre { background: #f8f8f8; padding: 15px; border-radius: 4px; overflow-x: auto; }
805
+ </style>
806
+ </head>
807
+ <body>
808
+ <div class="container">
809
+ <h1>⚠️ Frontend Not Built</h1>
810
+ <p>The Next.js frontend has not been built yet.</p>
811
+
812
+ <h2>To build the frontend:</h2>
813
+ <pre>cd payload_viewer && npm install && npm run build</pre>
814
+
815
+ <h2>Or use the start script:</h2>
816
+ <pre>cd payload_viewer && ./start.sh</pre>
817
+
818
+ <p>After building, refresh this page.</p>
819
+ </div>
820
+ </body>
821
+ </html>
822
+ `);
823
+ }
824
+ });
825
+
826
+ return new Promise((resolve, reject) => {
827
+ try {
828
+ const server = app.listen(port, () => {
829
+ console.log(`🌐 Payload viewer available at: http://localhost:${port}`);
830
+ resolve(server);
831
+ });
832
+
833
+ server.on('error', (err) => {
834
+ if (err.code === 'EADDRINUSE') {
835
+ console.log(`⚠️ Port ${port} is already in use. Trying port ${port + 1}...`);
836
+ server.close();
837
+ // Try next port
838
+ const nextServer = app.listen(port + 1, () => {
839
+ console.log(`🌐 Payload viewer available at: http://localhost:${port + 1}`);
840
+ resolve(nextServer);
841
+ });
842
+ } else {
843
+ reject(err);
844
+ }
845
+ });
846
+ } catch (error) {
847
+ reject(error);
848
+ }
849
+ });
850
+ }
851
+
852
+ // Start the server if this file is run directly
853
+ if (import.meta.url === `file://${process.argv[1]}`) {
854
+ const port = parseInt(process.argv[2], 10) || 3000;
855
+ startWebServer(port).catch(err => {
856
+ console.error('Failed to start server:', err);
857
+ process.exit(1);
858
+ });
859
+ }
860
+
861
+ export { startWebServer };