@messenger-box/platform-server 10.0.3-alpha.72 → 10.0.3-alpha.74

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 (98) hide show
  1. package/lib/config/env-config.d.ts +7 -0
  2. package/lib/config/env-config.js +20 -0
  3. package/lib/config/env-config.js.map +1 -1
  4. package/lib/containers/containers.js +6 -1
  5. package/lib/containers/containers.js.map +1 -1
  6. package/lib/containers/context-services-from-container.js +4 -2
  7. package/lib/containers/context-services-from-container.js.map +1 -1
  8. package/lib/graphql/resolvers/ai-fragment.d.ts +3 -0
  9. package/lib/graphql/resolvers/ai-fragment.js +276 -0
  10. package/lib/graphql/resolvers/ai-fragment.js.map +1 -0
  11. package/lib/graphql/resolvers/channel.js +29 -0
  12. package/lib/graphql/resolvers/channel.js.map +1 -1
  13. package/lib/graphql/resolvers/index.js +1 -1
  14. package/lib/graphql/resolvers/index.js.map +1 -1
  15. package/lib/graphql/resolvers/post.js +187 -14
  16. package/lib/graphql/resolvers/post.js.map +1 -1
  17. package/lib/graphql/schema/ai-fragment.graphql +311 -0
  18. package/lib/graphql/schema/ai-fragment.graphql.js +1 -0
  19. package/lib/graphql/schema/ai-fragment.graphql.js.map +1 -0
  20. package/lib/graphql/schema/channel.graphql +19 -0
  21. package/lib/graphql/schema/channel.graphql.js +1 -1
  22. package/lib/graphql/schema/index.js +2 -2
  23. package/lib/graphql/schema/index.js.map +1 -1
  24. package/lib/graphql/schema/post.graphql +76 -0
  25. package/lib/graphql/schema/post.graphql.js +1 -1
  26. package/lib/graphql/schema/services.graphql +19 -0
  27. package/lib/index.js.map +1 -1
  28. package/lib/inngest/factory.d.ts +20 -0
  29. package/lib/inngest/factory.js +4 -0
  30. package/lib/inngest/factory.js.map +1 -0
  31. package/lib/inngest/functions.d.ts +235 -0
  32. package/lib/inngest/functions.js +1385 -0
  33. package/lib/inngest/functions.js.map +1 -0
  34. package/lib/inngest/index.d.ts +3 -0
  35. package/lib/inngest/prompt.d.ts +6 -0
  36. package/lib/inngest/prompt.js +871 -0
  37. package/lib/inngest/prompt.js.map +1 -0
  38. package/lib/inngest/utils.d.ts +5 -0
  39. package/lib/inngest/utils.js +32 -0
  40. package/lib/inngest/utils.js.map +1 -0
  41. package/lib/module.js +10 -3
  42. package/lib/module.js.map +1 -1
  43. package/lib/plugins/ai-fragment-moleculer-service.d.ts +29 -0
  44. package/lib/plugins/ai-fragment-moleculer-service.js +516 -0
  45. package/lib/plugins/ai-fragment-moleculer-service.js.map +1 -0
  46. package/lib/plugins/channel-moleculer-service.js +9 -0
  47. package/lib/plugins/channel-moleculer-service.js.map +1 -1
  48. package/lib/plugins/index.d.ts +1 -0
  49. package/lib/plugins/post-moleculer-service.js +116 -0
  50. package/lib/plugins/post-moleculer-service.js.map +1 -1
  51. package/lib/services/ai-fragment-service.d.ts +195 -0
  52. package/lib/services/ai-fragment-service.js +631 -0
  53. package/lib/services/ai-fragment-service.js.map +1 -0
  54. package/lib/services/channel-service.d.ts +4 -2
  55. package/lib/services/channel-service.js +24 -2
  56. package/lib/services/channel-service.js.map +1 -1
  57. package/lib/services/index.d.ts +2 -0
  58. package/lib/services/post-service.d.ts +9 -2
  59. package/lib/services/post-service.js +225 -5
  60. package/lib/services/post-service.js.map +1 -1
  61. package/lib/services/proxy-services/ai-fragment-microservice.d.ts +23 -0
  62. package/lib/services/proxy-services/ai-fragment-microservice.js +78 -0
  63. package/lib/services/proxy-services/ai-fragment-microservice.js.map +1 -0
  64. package/lib/services/proxy-services/channel-microservice.d.ts +2 -1
  65. package/lib/services/proxy-services/channel-microservice.js +6 -0
  66. package/lib/services/proxy-services/channel-microservice.js.map +1 -1
  67. package/lib/services/proxy-services/index.d.ts +1 -0
  68. package/lib/services/proxy-services/post-microservice.d.ts +22 -1
  69. package/lib/services/proxy-services/post-microservice.js +80 -0
  70. package/lib/services/proxy-services/post-microservice.js.map +1 -1
  71. package/lib/services/sandbox-error-service.d.ts +23 -0
  72. package/lib/services/sandbox-error-service.js +422 -0
  73. package/lib/services/sandbox-error-service.js.map +1 -0
  74. package/lib/store/models/ai-fragment.d.ts +4 -0
  75. package/lib/store/models/ai-fragment.js +125 -0
  76. package/lib/store/models/ai-fragment.js.map +1 -0
  77. package/lib/store/models/channel.js +5 -0
  78. package/lib/store/models/channel.js.map +1 -1
  79. package/lib/store/models/index.d.ts +1 -0
  80. package/lib/store/repositories/ai-fragment-repository.d.ts +15 -0
  81. package/lib/store/repositories/ai-fragment-repository.js +69 -0
  82. package/lib/store/repositories/ai-fragment-repository.js.map +1 -0
  83. package/lib/store/repositories/channel-repository.js +1 -1
  84. package/lib/store/repositories/channel-repository.js.map +1 -1
  85. package/lib/store/repositories/index.d.ts +1 -0
  86. package/lib/store/repositories/post-repository.js +1 -1
  87. package/lib/store/repositories/post-repository.js.map +1 -1
  88. package/lib/store/repositories/post-thread-repository.js +1 -1
  89. package/lib/store/repositories/post-thread-repository.js.map +1 -1
  90. package/lib/store/repositories/reaction-repository.js +1 -1
  91. package/lib/store/repositories/reaction-repository.js.map +1 -1
  92. package/lib/templates/constants/SERVER_TYPES.ts.template +4 -1
  93. package/lib/templates/repositories/AiFragmentRepository.ts.template +4 -0
  94. package/lib/templates/services/AiFragmentService.ts.template +123 -0
  95. package/lib/templates/services/ChannelService.ts.template +11 -1
  96. package/lib/templates/services/PostService.ts.template +82 -1
  97. package/lib/templates/services/SandboxErrorService.ts.template +125 -0
  98. package/package.json +12 -6
@@ -0,0 +1,1385 @@
1
+ import {SERVER_TYPES,RoomType,PostTypeEnum,AiAgentMessageRole,AiAgentMessageType}from'common/server';import {TaggedType}from'@common-stack/core';import {createAgent,createTool,createState,createNetwork,gemini,anthropic,openai}from'@inngest/agent-kit';import {Sandbox}from'@e2b/code-interpreter';import {z}from'zod';import {lastAssistantTextMessageContent,getSandbox,parseAgentOutput}from'./utils.js';import {CODING_PROMPT,FRAGMENT_TITLE_PROMPT,RESPONSE_PROMPT,VUE_CODING_PROMPT,VITE_REACT_CODING_PROMPT}from'./prompt.js';import {config}from'../config/env-config.js';// E2B Configuration constants
2
+ const templateID = config.E2B_TEMPLATE_ID; // Next.js template (default)
3
+ const vueTemplateID = config.E2B_VUE_TEMPLATE_ID; // Vue template
4
+ const viteReactTemplateID = config.E2B_VITE_REACT_TEMPLATE_ID; // Vite+React template
5
+ const domain = config.E2B_DOMAIN;
6
+ const apiKey = config.E2B_API_KEY;
7
+ // Helper function to get the correct coding prompt based on template
8
+ const getCodingPrompt = modelConfig => {
9
+ if (modelConfig?.template === 'vue') {
10
+ return VUE_CODING_PROMPT;
11
+ }
12
+ if (modelConfig?.template === 'vite-react') {
13
+ return VITE_REACT_CODING_PROMPT;
14
+ }
15
+ return CODING_PROMPT; // Default Next.js prompt
16
+ };
17
+ // Helper function to get the correct template ID based on modal config
18
+ const getTemplateId = modelConfig => {
19
+ if (modelConfig?.template === 'vue' && vueTemplateID) {
20
+ return vueTemplateID;
21
+ }
22
+ if (modelConfig?.template === 'vite-react' && viteReactTemplateID) {
23
+ return viteReactTemplateID;
24
+ }
25
+ return templateID; // Default to Next.js template
26
+ };
27
+ // Helper function to normalize template names for database validation
28
+ const normalizeTemplateName = template => {
29
+ if (!template) return 'NEXTJS';
30
+ switch (template.toLowerCase()) {
31
+ case 'nextjs':
32
+ return 'NEXTJS';
33
+ case 'vue':
34
+ return 'VUE';
35
+ case 'vite-react':
36
+ case 'vitereact':
37
+ return 'VITE_REACT';
38
+ default:
39
+ return 'NEXTJS';
40
+ }
41
+ };
42
+ // Helper to create model instance from modelConfig (provider/model/apiKey)
43
+ const createModelInstance = modelConfig => {
44
+ if (!modelConfig || !modelConfig.apiKey) {
45
+ // Default to Gemini if nothing provided
46
+ return gemini({
47
+ model: 'gemini-2.5-flash'
48
+ });
49
+ }
50
+ switch (modelConfig.provider) {
51
+ case 'openai':
52
+ return openai({
53
+ model: modelConfig.model || 'gpt-4o',
54
+ apiKey: modelConfig.apiKey,
55
+ defaultParameters: {
56
+ temperature: 0.7
57
+ }
58
+ });
59
+ case 'anthropic':
60
+ return anthropic({
61
+ model: modelConfig.model || 'claude-3-5-sonnet-latest',
62
+ apiKey: modelConfig.apiKey,
63
+ defaultParameters: {
64
+ max_tokens: 4096,
65
+ temperature: 0.7
66
+ }
67
+ });
68
+ case 'gemini':
69
+ default:
70
+ return gemini({
71
+ model: modelConfig.model || 'gemini-2.5-flash',
72
+ apiKey: modelConfig.apiKey
73
+ });
74
+ }
75
+ };
76
+ const createChannelWithProjectId = (inngest, container) => inngest.createFunction({
77
+ id: 'create-channel-with-projectid'
78
+ }, {
79
+ event: 'channel-with-project-id.create'
80
+ }, async ({
81
+ event,
82
+ step
83
+ }) => {
84
+ console.log('=== Project create FUNCTION STARTED ===');
85
+ console.log('Event data:', JSON.stringify(event.data, null, 2));
86
+ const {
87
+ projectId,
88
+ channelInput
89
+ } = event.data || {};
90
+ const {
91
+ content,
92
+ createdBy,
93
+ orgName,
94
+ organization,
95
+ team,
96
+ files,
97
+ notificationParams,
98
+ modelConfig,
99
+ accountId,
100
+ teamId,
101
+ orgId
102
+ } = channelInput;
103
+ // Validate that projectId is provided
104
+ if (!projectId) {
105
+ throw new Error('Project ID is required but was not provided');
106
+ }
107
+ // Create channel in database with template immutability logic
108
+ const channelData = await step.run('ensure-channel', async () => {
109
+ const channelService = container.getNamed(SERVER_TYPES.ChannelService, TaggedType.MICROSERVICE);
110
+ // If a channel already exists for this project, reuse it
111
+ const existingList = await channelService.getAll({
112
+ criteria: {
113
+ projectId,
114
+ type: RoomType.Aiassistant
115
+ }
116
+ });
117
+ const existing = Array.isArray(existingList) ? existingList[0] : null;
118
+ if (existing) {
119
+ return existing;
120
+ }
121
+ const newChannel = await channelService.saveChannel({
122
+ type: RoomType.Aiassistant,
123
+ orgName: orgId,
124
+ organization: orgId,
125
+ creator: createdBy || accountId,
126
+ title: 'AI Assistant',
127
+ displayName: 'AI Assistant',
128
+ topic: 'AI Assistant',
129
+ team: teamId,
130
+ description: 'AI Assistant',
131
+ projectId,
132
+ _id: undefined
133
+ });
134
+ return await newChannel;
135
+ });
136
+ if (channelData) {
137
+ const channelId = channelData?.id?.toString?.() || channelData?._id?.toString?.() || '';
138
+ if (!channelId) {
139
+ throw new Error('Failed to resolve channel id for post creation');
140
+ }
141
+ await step.sendEvent('emit-send-message', {
142
+ name: 'channel.message.send',
143
+ data: {
144
+ channelId,
145
+ content,
146
+ createdBy,
147
+ files,
148
+ notificationParams,
149
+ projectId,
150
+ modelConfig,
151
+ accountId,
152
+ teamId,
153
+ orgId
154
+ }
155
+ });
156
+ }
157
+ return channelData;
158
+ });
159
+ const sendMessageHandler = (inngest, container) => inngest.createFunction({
160
+ id: 'channel-message-send'
161
+ }, {
162
+ event: 'channel.message.send'
163
+ }, async ({
164
+ event,
165
+ step
166
+ }) => {
167
+ console.log('=== Send Message Handler FUNCTION STARTED ===');
168
+ console.log('=== Send Message Handler Event data:', JSON.stringify(event.data, null, 2));
169
+ const {
170
+ projectId,
171
+ channelId,
172
+ content,
173
+ createdBy,
174
+ files,
175
+ notificationParams,
176
+ modelConfig,
177
+ accountId,
178
+ teamId,
179
+ orgId
180
+ } = event.data || {};
181
+ if (!channelId || !content || !createdBy || !accountId) {
182
+ throw new Error('channelId, content and createdBy are required');
183
+ }
184
+ const messageData = await step.run('persist-message', async () => {
185
+ const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
186
+ container.get('PubSub');
187
+ const postDoc = await postService.create({
188
+ channel: channelId,
189
+ message: content,
190
+ editedBy: createdBy || accountId,
191
+ author: createdBy || accountId,
192
+ // Convert props to propsConfiguration structure
193
+ props: {
194
+ notificationParams: notificationParams || {},
195
+ template: modelConfig?.template || 'vite-react',
196
+ projectId: projectId,
197
+ role: AiAgentMessageRole.USER,
198
+ fragment: {},
199
+ sendNotificationWithProjectId: false
200
+ },
201
+ type: PostTypeEnum.Simple,
202
+ files
203
+ });
204
+ // if (postDoc) {
205
+ // pubsub.publish(`POST_CREATED.${projectId}`, postDoc);
206
+ // }
207
+ return await postDoc;
208
+ });
209
+ await step.run('publish-subscription-update', async () => {
210
+ const pubsub = container.get('PubSub');
211
+ pubsub.publish(`POST_CREATED.${projectId}`, messageData);
212
+ });
213
+ // Prepare final model config with the template that was actually used
214
+ // const finalModelConfig = await step.run('prepare-final-model-config', async () => {
215
+ // let actualTemplate = modelConfig?.template || 'nextjs';
216
+ // if (modelConfig) {
217
+ // // Use provided modelConfig but ensure template is what was actually stored
218
+ // const finalConfig = {
219
+ // ...modelConfig,
220
+ // template: actualTemplate, // Always use the immutable template from DB
221
+ // };
222
+ // console.log('Final modelConfig for code generation:', finalConfig);
223
+ // return finalConfig;
224
+ // }
225
+ // return {
226
+ // template: actualTemplate,
227
+ // provider: 'gemini',
228
+ // model: 'gemini-1.5-flash',
229
+ // apiKey: '', // Will need to be provided via environment
230
+ // };
231
+ // });
232
+ // Trigger code generation
233
+ // await step.run('trigger-code-generation', async () => {
234
+ // await inngest.send({
235
+ // name: 'code-agent/run-code-creation',
236
+ // data: {
237
+ // value:messageData.message,
238
+ // projectId,
239
+ // messageId: messageData.id,
240
+ // owner: messageData?.author || createdBy || accountId,
241
+ // orgName: orgId,
242
+ // modelConfig: finalModelConfig,
243
+ // accountId,
244
+ // teamId,
245
+ // orgId,
246
+ // channelId
247
+ // },
248
+ // });
249
+ // });
250
+ // Send event after result is available
251
+ // await step.sendEvent('emit-ai-response-generate', {
252
+ // name: 'ai.response.generate',
253
+ // data: {
254
+ // projectId,
255
+ // post: result,
256
+ // modelConfig,
257
+ // },
258
+ // });
259
+ return messageData;
260
+ });
261
+ // Main code agent function - processes queries and generates code
262
+ const codeAgentFunction = (inngest, container) => inngest.createFunction({
263
+ id: 'code-agent'
264
+ }, {
265
+ event: 'code-agent/run-code-creation'
266
+ }, async ({
267
+ event,
268
+ step
269
+ }) => {
270
+ console.log('=== CODE AGENT FUNCTION STARTED ===');
271
+ console.log('Event data:', JSON.stringify(event.data, null, 2));
272
+ const {
273
+ sandboxId,
274
+ sandboxUrl
275
+ } = await step.run('create-sandbox-and-start-monitoring', async () => {
276
+ const selectedTemplateId = getTemplateId(event.data.modelConfig);
277
+ console.log(`Creating sandbox with template: ${selectedTemplateId} (requested: ${event.data.modelConfig?.template || 'nextjs'})`);
278
+ const sandbox = await Sandbox.create(selectedTemplateId, {
279
+ apiKey,
280
+ domain,
281
+ timeoutMs: 60_000 * 20
282
+ });
283
+ const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
284
+ // Start error monitoring immediately after sandbox creation
285
+ // const container = await initializeContainer();
286
+ const sandboxErrorService = container.get(SERVER_TYPES.SandboxErrorService);
287
+ console.log(`Starting immediate error monitoring for sandbox ${sandbox.sandboxId}`);
288
+ await sandboxErrorService.startErrorMonitoring(event.data.projectId, sandbox.sandboxId, sandboxUrl);
289
+ return {
290
+ sandboxId: sandbox.sandboxId,
291
+ sandboxUrl
292
+ };
293
+ });
294
+ // Only fetch previous messages if we don't have active fragment files
295
+ const previousMessages = event.data.activeFragmentFiles ? [] // Skip fetching previous messages when we have active fragment files
296
+ : await step.run('get-previous-messages', async () => {
297
+ const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
298
+ const result = await postService.getPreviousMessagesByProjectId(event.data.projectId, 5);
299
+ // Handle error case - return empty array if getPreviousMessagesByProjectId returns an Error
300
+ if (result instanceof Error) {
301
+ console.warn('Failed to get previous messages:', result.message);
302
+ return [];
303
+ }
304
+ return result;
305
+ });
306
+ const state = createState({
307
+ summary: '',
308
+ files: event.data.activeFragmentFiles || {},
309
+ // Use active fragment files if provided
310
+ canvasLayers: []
311
+ }, {
312
+ messages: previousMessages
313
+ });
314
+ // Get model configuration from event data
315
+ const {
316
+ modelConfig
317
+ } = event.data;
318
+ console.log('Model configuration received:', JSON.stringify(modelConfig, null, 2));
319
+ // Create a new agent with the template-specific coding prompt
320
+ const selectedCodingPrompt = getCodingPrompt(event.data.modelConfig);
321
+ console.log(`Using coding prompt for template: ${event.data.modelConfig?.template || 'nextjs'}`);
322
+ const codeAgent = createAgent({
323
+ name: 'code-agent',
324
+ description: 'An expert coding agent for page creation',
325
+ system: selectedCodingPrompt,
326
+ model: createModelInstance(modelConfig),
327
+ tools: [createTool({
328
+ name: 'terminal',
329
+ description: 'Use the terminal to run commands',
330
+ parameters: z.object({
331
+ command: z.string()
332
+ }),
333
+ handler: async ({
334
+ command
335
+ }, {
336
+ step
337
+ }) => await step?.run('terminal', async () => {
338
+ try {
339
+ const sandbox = await getSandbox(sandboxId);
340
+ const result = await sandbox.commands.run(command);
341
+ return result.stdout;
342
+ } catch (e) {
343
+ return `Command failed: ${e}`;
344
+ }
345
+ })
346
+ }), createTool({
347
+ name: 'createOrUpdateFiles',
348
+ description: 'Create or update files in the sandbox',
349
+ parameters: z.object({
350
+ files: z.union([z.array(z.object({
351
+ path: z.string(),
352
+ content: z.string()
353
+ })), z.string() // Accept JSON string as fallback
354
+ ])
355
+ }),
356
+ // handler: async ({ files }, { step, network }: Tool.Options<AgentState>) => {
357
+ handler: async ({
358
+ files
359
+ }, {
360
+ step,
361
+ network
362
+ }) => {
363
+ const newFiles = await step?.run('createOrUpdateFiles', async () => {
364
+ try {
365
+ console.log('createOrUpdateFiles - Raw files parameter type:', typeof files);
366
+ console.log('createOrUpdateFiles - Raw files parameter:', `${JSON.stringify(files).substring(0, 200)}...`);
367
+ // Parse files if it's a JSON string (fallback for AI model behavior)
368
+ let parsedFiles;
369
+ if (typeof files === 'string') {
370
+ try {
371
+ // First try standard JSON parsing
372
+ parsedFiles = JSON.parse(files);
373
+ console.log('createOrUpdateFiles - Successfully parsed files from JSON string');
374
+ } catch (parseError) {
375
+ console.log('createOrUpdateFiles - Standard JSON parse failed, trying template literal fix');
376
+ try {
377
+ // AI is using template literals in JSON - need more sophisticated parsing
378
+ console.log('createOrUpdateFiles - Attempting to fix template literals in JSON');
379
+ // Try to evaluate the string as JavaScript to handle template literals
380
+ // This is safe because we're in a sandboxed environment and only parsing our own AI's output
381
+ const evalString = `(${files})`;
382
+ console.log('createOrUpdateFiles - Evaluating as JavaScript array');
383
+ parsedFiles = eval(evalString);
384
+ console.log('createOrUpdateFiles - Successfully evaluated template literals');
385
+ } catch (secondError) {
386
+ console.error('createOrUpdateFiles - Both parsing attempts failed:');
387
+ console.error('Original error:', parseError.message);
388
+ console.error('Template literal fix error:', secondError.message);
389
+ console.error('Raw string (first 500 chars):', files.substring(0, 500));
390
+ return `Error: Failed to parse files JSON - ${parseError.message}`;
391
+ }
392
+ }
393
+ } else if (Array.isArray(files)) {
394
+ parsedFiles = files;
395
+ console.log('createOrUpdateFiles - Using files array directly');
396
+ } else {
397
+ console.error('createOrUpdateFiles - Files parameter is neither string nor array:', typeof files);
398
+ return `Error: Files parameter must be an array or JSON string, got ${typeof files}`;
399
+ }
400
+ // Validate that parsedFiles is an array
401
+ if (!Array.isArray(parsedFiles)) {
402
+ console.error('createOrUpdateFiles - Parsed files is not an array:', parsedFiles);
403
+ return `Error: Files must be an array after parsing, got ${typeof parsedFiles}`;
404
+ }
405
+ console.log(`createOrUpdateFiles - Processing ${parsedFiles.length} files`);
406
+ const updatedFiles = network.state.data.files || {};
407
+ const sandbox = await getSandbox(sandboxId);
408
+ for (const file of parsedFiles) {
409
+ if (!file.path || file.content === undefined) {
410
+ console.error('createOrUpdateFiles - Invalid file object (missing path or content):', file);
411
+ continue;
412
+ }
413
+ console.log(`createOrUpdateFiles - Writing file: ${file.path}`);
414
+ await sandbox.files.write(file.path, file.content);
415
+ updatedFiles[file.path] = file.content;
416
+ }
417
+ console.log(`createOrUpdateFiles - Successfully updated ${parsedFiles.length} files`);
418
+ return updatedFiles;
419
+ } catch (e) {
420
+ console.error('createOrUpdateFiles - Error:', e);
421
+ console.error('createOrUpdateFiles - Error stack:', e?.stack);
422
+ return `Error: ${e}`;
423
+ }
424
+ });
425
+ if (typeof newFiles === 'object' && !Array.isArray(newFiles) && typeof newFiles !== 'string') {
426
+ network.state.data.files = newFiles;
427
+ }
428
+ }
429
+ }), createTool({
430
+ name: 'readFiles',
431
+ description: 'Read files from the sandbox',
432
+ parameters: z.object({
433
+ files: z.array(z.string())
434
+ }),
435
+ handler: async ({
436
+ files
437
+ }, {
438
+ step
439
+ }) => await step?.run('readFiles', async () => {
440
+ try {
441
+ const sandbox = await getSandbox(sandboxId);
442
+ const contents = [];
443
+ for (const file of files) {
444
+ const content = await sandbox.files.read(file);
445
+ contents.push({
446
+ path: file,
447
+ content
448
+ });
449
+ }
450
+ return JSON.stringify(contents);
451
+ } catch (e) {
452
+ return `Error: ${e}`;
453
+ }
454
+ })
455
+ })],
456
+ lifecycle: {
457
+ onResponse: async ({
458
+ result,
459
+ network
460
+ }) => {
461
+ const lastAssistantMessageText = lastAssistantTextMessageContent(result);
462
+ if (lastAssistantMessageText && network) {
463
+ if (lastAssistantMessageText.includes('<task_summary>')) {
464
+ network.state.data.summary = lastAssistantMessageText;
465
+ // Extract canvas layers if present
466
+ const canvasLayersMatch = lastAssistantMessageText.match(/<canvas_layers>([\s\S]*?)<\/canvas_layers>/);
467
+ if (canvasLayersMatch) {
468
+ try {
469
+ const layersJson = canvasLayersMatch[1].trim();
470
+ network.state.data.canvasLayers = JSON.parse(layersJson);
471
+ } catch (error) {
472
+ console.error('Error parsing canvas layers:', error);
473
+ network.state.data.canvasLayers = null;
474
+ }
475
+ }
476
+ }
477
+ }
478
+ return result;
479
+ }
480
+ }
481
+ });
482
+ const network = createNetwork({
483
+ name: 'coding-agent-network',
484
+ agents: [codeAgent],
485
+ maxIter: 15,
486
+ defaultState: state,
487
+ router: async ({
488
+ network
489
+ }) => {
490
+ const {
491
+ summary
492
+ } = network.state.data;
493
+ if (summary) {
494
+ return;
495
+ }
496
+ return codeAgent;
497
+ }
498
+ });
499
+ // Prepare the enhanced input message that includes activeFragmentFiles context
500
+ let enhancedInput = event.data.value;
501
+ // If we have activeFragmentFiles, include them in the message to the AI
502
+ if (event.data.activeFragmentFiles && Object.keys(event.data.activeFragmentFiles).length > 0) {
503
+ console.log('📂 Including activeFragmentFiles context for AI agent:');
504
+ console.log('- File count:', Object.keys(event.data.activeFragmentFiles).length);
505
+ console.log('- Files:', Object.keys(event.data.activeFragmentFiles));
506
+ // Check if ViteVisualEditor exists and warn if it's missing for vite-react template
507
+ const hasViteVisualEditor = 'src/components/ViteVisualEditor.tsx' in event.data.activeFragmentFiles;
508
+ const isViteReact = event.data.modelConfig?.template === 'vite-react';
509
+ if (isViteReact && !hasViteVisualEditor) {
510
+ console.warn('⚠️ ViteVisualEditor.tsx missing from activeFragmentFiles for vite-react template');
511
+ console.warn('AI may create stub component instead of importing existing one');
512
+ }
513
+ enhancedInput = `${event.data.value}
514
+
515
+ ACTIVE FRAGMENT FILES CONTEXT:
516
+ The current sandbox contains the following files that you MUST include in your createOrUpdateFiles call:
517
+
518
+ ${Object.entries(event.data.activeFragmentFiles).map(([path, content]) => `FILE: ${path}\n${typeof content === 'string' ? content.substring(0, 500) + (content.length > 500 ? '...' : '') : '[Non-string content]'}`).join('\n\n')}
519
+
520
+ CRITICAL INSTRUCTIONS FOR VISUAL CHANGES:
521
+ - You MUST include ALL ${Object.keys(event.data.activeFragmentFiles).length} files from the active fragment files in your createOrUpdateFiles call
522
+ - Apply the visual changes described above to the appropriate files
523
+ - Preserve ALL existing files and their content except for the specific changes requested
524
+ - Use the complete file context provided above as your current codebase state
525
+ - DO NOT output only the changed file - output ALL files to rebuild the complete sandbox${isViteReact && !hasViteVisualEditor ? '\n- IMPORTANT: For vite-react template, you MUST import ViteVisualEditor from "./components/ViteVisualEditor" (it exists in the template), do NOT create a new component' : ''}`;
526
+ }
527
+ console.log('🤖 Running AI agent with enhanced input length:', enhancedInput.length);
528
+ const result = await network.run(enhancedInput, {
529
+ state
530
+ });
531
+ // Enhanced file validation with logging
532
+ const hasFiles = result.state.data.files && Object.keys(result.state.data.files).length > 0;
533
+ const hasSummary = result.state.data.summary && result.state.data.summary.trim().length > 0;
534
+ const outputFileCount = Object.keys(result.state.data.files || {}).length;
535
+ const inputFileCount = event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles).length : 0;
536
+ console.log('Agent result validation:', {
537
+ hasFiles,
538
+ outputFileCount,
539
+ inputFileCount,
540
+ fileCountMatch: inputFileCount > 0 ? outputFileCount === inputFileCount : true,
541
+ hasSummary,
542
+ summaryLength: result.state.data.summary?.length || 0,
543
+ outputFiles: Object.keys(result.state.data.files || {}),
544
+ inputFiles: event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles) : []
545
+ });
546
+ // Warning if file counts don't match for visual changes
547
+ if (event.data.activeFragmentFiles && inputFileCount > 0 && outputFileCount !== inputFileCount) {
548
+ console.warn(`⚠️ File count mismatch! Expected ${inputFileCount} files, got ${outputFileCount} files`);
549
+ console.warn('Missing files may cause incomplete sandbox rebuild');
550
+ }
551
+ const fragmentTitleGenerator = createAgent({
552
+ name: 'fragment-title-generator',
553
+ description: 'A fragment title generator',
554
+ system: FRAGMENT_TITLE_PROMPT,
555
+ model: createModelInstance(modelConfig)
556
+ });
557
+ const responseGenerator = createAgent({
558
+ name: 'response-generator',
559
+ description: 'A response generator',
560
+ system: RESPONSE_PROMPT,
561
+ model: createModelInstance(modelConfig)
562
+ });
563
+ // Run generators in parallel outside of steps (like lovable-clone)
564
+ const {
565
+ output: fragmentTitleOutput
566
+ } = await fragmentTitleGenerator.run(result.state.data.summary || 'Generated Page');
567
+ const {
568
+ output: responseOutput
569
+ } = await responseGenerator.run(result.state.data.summary || 'Code generated successfully');
570
+ const agentError = !result.state.data.summary || Object.keys(result.state.data.files || {}).length === 0;
571
+ // sandboxUrl is already available from the previous step
572
+ // Save to database using handler
573
+ const messageData = await step.run('save-result', async () => {
574
+ const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
575
+ // Normalize template name for database validation
576
+ const normalizedModelConfig = {
577
+ ...event.data.modelConfig,
578
+ template: normalizeTemplateName(event.data.modelConfig?.template)
579
+ };
580
+ return await postService.saveCodeAgentResult({
581
+ value: event.data.value,
582
+ projectId: event.data.projectId,
583
+ messageId: event.data.messageId,
584
+ owner: event.data.owner,
585
+ orgName: event.data.orgName,
586
+ modelConfig: normalizedModelConfig
587
+ }, sandboxUrl, parseAgentOutput(fragmentTitleOutput), parseAgentOutput(responseOutput), result.state.data.files || {}, result.state.data.summary || '', agentError, result.state.data.canvasLayers);
588
+ });
589
+ // Publish subscription update for real-time client updates
590
+ await step.run('publish-subscription-update', async () => {
591
+ // const responseData = {
592
+ // projectId: event.data.projectId,
593
+ // sandboxId: sandboxId,
594
+ // sandboxUrl: sandboxUrl,
595
+ // url: sandboxUrl, // Keep both for compatibility
596
+ // title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
597
+ // files: result.state.data.files,
598
+ // summary: result.state.data.summary,
599
+ // isError: agentError,
600
+ // messageId: agentError ? null : (messageData as any).id,
601
+ // canvasLayers: result.state.data.canvasLayers,
602
+ // template: normalizeTemplateName(event.data.modelConfig?.template),
603
+ // timestamp: new Date().toISOString(),
604
+ // };
605
+ const pubsub = container.get('PubSub');
606
+ // const post = event.data.post;
607
+ // const postData = {
608
+ // ...messageData,
609
+ // props: {
610
+ // ...messageData.props,
611
+ // fragment: responseData,
612
+ // },
613
+ // };
614
+ console.log('=== Publish to subscription Post Data:', JSON.stringify(messageData, null, 2));
615
+ pubsub.publish(`POST_CREATED.${event.data.projectId}`, messageData);
616
+ });
617
+ // Error monitoring was already started immediately after sandbox creation
618
+ // No need to start it again here
619
+ return {
620
+ projectId: event.data.projectId,
621
+ sandboxId: sandboxId,
622
+ sandboxUrl: sandboxUrl,
623
+ url: sandboxUrl,
624
+ // Keep both for compatibility
625
+ title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
626
+ files: result.state.data.files,
627
+ summary: result.state.data.summary,
628
+ isError: agentError,
629
+ canvasLayers: result.state.data.canvasLayers,
630
+ template: normalizeTemplateName(event.data.modelConfig?.template),
631
+ timestamp: new Date().toISOString()
632
+ };
633
+ });
634
+ const generateAIResponseWithSandbox = (inngest, container) => inngest.createFunction({
635
+ id: 'generate-ai-response-with-sandbox'
636
+ }, {
637
+ event: 'ai.response.generate'
638
+ }, async ({
639
+ event,
640
+ step
641
+ }) => {
642
+ console.log('=== AI Response Generation with Sandbox FUNCTION STARTED ===');
643
+ console.log('Event data:', JSON.stringify(event.data, null, 2));
644
+ const {
645
+ projectId,
646
+ post,
647
+ modelConfig
648
+ } = event.data || {};
649
+ const message = post.message;
650
+ // Validate required parameters
651
+ if (!projectId || !message) {
652
+ throw new Error('projectId and message are required');
653
+ }
654
+ // Create e2b sandbox
655
+ const sandbox = await step.run('create-sandbox', async () => {
656
+ try {
657
+ const selectedTemplateId = getTemplateId(modelConfig);
658
+ console.log(`Creating sandbox with template: ${selectedTemplateId} (requested: ${modelConfig?.template || 'vite-react'})`);
659
+ const sandbox = await Sandbox.create(selectedTemplateId, {
660
+ apiKey,
661
+ domain,
662
+ timeoutMs: 60_000 * 20 // 20 minutes timeout
663
+ });
664
+ return sandbox;
665
+ } catch (error) {
666
+ console.error('Failed to create sandbox:', error);
667
+ throw new Error(`Failed to create sandbox: ${error.message}`);
668
+ }
669
+ });
670
+ // Create the appropriate coding prompt based on framework
671
+ let codingPrompt = CODING_PROMPT;
672
+ if (modelConfig?.template === 'vue') {
673
+ codingPrompt = VUE_CODING_PROMPT;
674
+ } else if (modelConfig?.template === 'vite-react') {
675
+ codingPrompt = VITE_REACT_CODING_PROMPT;
676
+ }
677
+ // Create agent with sandbox tools (use modelConfig model if provided)
678
+ const agent = createAgent({
679
+ name: 'CodeGenerator',
680
+ system: codingPrompt,
681
+ model: createModelInstance(modelConfig?.modelConfig || modelConfig),
682
+ tools: [createTool({
683
+ name: 'createOrUpdateFiles',
684
+ description: 'Create or update files in the sandbox',
685
+ parameters: z.object({
686
+ files: z.array(z.object({
687
+ path: z.string(),
688
+ content: z.string()
689
+ }))
690
+ }),
691
+ handler: async ({
692
+ files
693
+ }) => {
694
+ try {
695
+ await sandbox.files.write(files);
696
+ return {
697
+ success: true,
698
+ message: `Updated ${files.length} files`
699
+ };
700
+ } catch (error) {
701
+ return {
702
+ success: false,
703
+ error: error.message
704
+ };
705
+ }
706
+ }
707
+ }), createTool({
708
+ name: 'readFiles',
709
+ description: 'Read files from the sandbox',
710
+ parameters: z.object({
711
+ paths: z.array(z.string())
712
+ }),
713
+ handler: async ({
714
+ paths
715
+ }) => {
716
+ try {
717
+ const files = await Promise.all(paths.map(async path => {
718
+ try {
719
+ const content = await sandbox.files.read(path);
720
+ return {
721
+ path,
722
+ content,
723
+ exists: true
724
+ };
725
+ } catch (error) {
726
+ return {
727
+ path,
728
+ content: '',
729
+ exists: false,
730
+ error: error.message
731
+ };
732
+ }
733
+ }));
734
+ return {
735
+ success: true,
736
+ files
737
+ };
738
+ } catch (error) {
739
+ return {
740
+ success: false,
741
+ error: error.message
742
+ };
743
+ }
744
+ }
745
+ }), createTool({
746
+ name: 'terminal',
747
+ description: 'Execute terminal commands in the sandbox',
748
+ parameters: z.object({
749
+ command: z.string()
750
+ }),
751
+ handler: async ({
752
+ command
753
+ }) => {
754
+ try {
755
+ const result = await sandbox.process.start({
756
+ cmd: command
757
+ });
758
+ const output = await result.finished;
759
+ return {
760
+ success: true,
761
+ output: output.stdout || output.stderr,
762
+ exitCode: output.exitCode
763
+ };
764
+ } catch (error) {
765
+ return {
766
+ success: false,
767
+ error: error.message
768
+ };
769
+ }
770
+ }
771
+ })]
772
+ });
773
+ // Generate AI response using agent-kit (completely outside of step.run to avoid nesting)
774
+ let aiResponse;
775
+ try {
776
+ console.log('=== Starting AI response generation ===');
777
+ console.log('Message:', message);
778
+ // Generate response
779
+ const result = await agent.run(message);
780
+ // Parse the response to extract task summary and canvas layers
781
+ const responseContent = lastAssistantTextMessageContent(result);
782
+ console.log('=== AI Response Content ===');
783
+ console.log('Response Content:', responseContent);
784
+ const taskSummary = responseContent?.match(/<task_summary>([\s\S]*?)<\/task_summary>/)?.[1]?.trim();
785
+ const canvasLayersMatch = responseContent?.match(/<canvas_layers>([\s\S]*?)<\/canvas_layers>/)?.[1];
786
+ let canvasLayers = null;
787
+ if (canvasLayersMatch) {
788
+ try {
789
+ canvasLayers = JSON.parse(canvasLayersMatch.trim());
790
+ } catch (error) {
791
+ console.error('Failed to parse canvas layers:', error);
792
+ }
793
+ }
794
+ aiResponse = {
795
+ success: true,
796
+ response: responseContent,
797
+ taskSummary,
798
+ canvasLayers,
799
+ agentResult: result
800
+ };
801
+ console.log('=== AI Response Object ===');
802
+ console.log('AI Response:', JSON.stringify(aiResponse, null, 2));
803
+ } catch (error) {
804
+ console.error('AI response generation failed:', error);
805
+ aiResponse = {
806
+ success: false,
807
+ error: error.message,
808
+ response: null,
809
+ taskSummary: null,
810
+ canvasLayers: null
811
+ };
812
+ }
813
+ // Generate sandbox URL
814
+ const sandboxUrl = await step.run('generate-sandbox-url', async () => {
815
+ try {
816
+ // Your domain pattern: https://3000-<sandboxId>.yarntra.ai
817
+ const url = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
818
+ console.log(`Generated sandbox URL: ${url}`);
819
+ return url;
820
+ } catch (error) {
821
+ console.error('Failed to generate sandbox URL:', error);
822
+ throw new Error(`Failed to generate sandbox URL: ${error.message}`);
823
+ }
824
+ });
825
+ console.log('sandboxUrl', sandboxUrl);
826
+ // Publish to subscription
827
+ await step.run('publish-to-subscription', async () => {
828
+ try {
829
+ const pubsub = container.get('PubSub');
830
+ const responseData = {
831
+ projectId,
832
+ sandboxId: sandbox.sandboxId,
833
+ sandboxUrl,
834
+ url: sandboxUrl,
835
+ // Keep both for compatibility
836
+ aiResponse: aiResponse.success ? aiResponse.response : null,
837
+ taskSummary: aiResponse.success ? aiResponse.taskSummary : null,
838
+ canvasLayers: aiResponse.success ? aiResponse.canvasLayers : null,
839
+ error: aiResponse.success ? null : aiResponse.error,
840
+ template: normalizeTemplateName(modelConfig?.template || 'vite-react'),
841
+ timestamp: new Date().toISOString()
842
+ };
843
+ console.log('=== Response Data ===');
844
+ console.log('Response Data:', JSON.stringify(responseData, null, 2));
845
+ console.log('AI Response Success:', aiResponse.success);
846
+ // console.log('AI Response Content:', aiResponse.response);
847
+ // pubsub.publish(`AI_RESPONSE_GENERATED.${projectId}`, responseData);
848
+ const postData = {
849
+ ...post,
850
+ props: {
851
+ ...post.props,
852
+ generatedResponse: responseData
853
+ }
854
+ };
855
+ console.log('=== Publish to subscription Post Data:', JSON.stringify(postData, null, 2));
856
+ pubsub.publish(`POST_CREATED.${projectId}`, postData);
857
+ return responseData;
858
+ } catch (error) {
859
+ console.error('Failed to publish to subscription:', error);
860
+ throw new Error(`Failed to publish to subscription: ${error.message}`);
861
+ }
862
+ });
863
+ return {
864
+ projectId,
865
+ sandboxId: sandbox.sandboxId,
866
+ sandboxUrl,
867
+ url: sandboxUrl,
868
+ // Keep both for compatibility
869
+ aiResponse: aiResponse.success ? aiResponse.response : null,
870
+ taskSummary: aiResponse.success ? aiResponse.taskSummary : null,
871
+ canvasLayers: aiResponse.success ? aiResponse.canvasLayers : null,
872
+ error: aiResponse.success ? null : aiResponse.error,
873
+ template: normalizeTemplateName(modelConfig?.template || 'vite-react'),
874
+ timestamp: new Date().toISOString()
875
+ };
876
+ });
877
+ const generateAiCodeFunction = (inngest, container) => inngest.createFunction({
878
+ id: 'ai-code-agent'
879
+ }, {
880
+ event: 'ai-code-agent/run-code-generation'
881
+ }, async ({
882
+ event,
883
+ step
884
+ }) => {
885
+ console.log('=== CODE AGENT FUNCTION STARTED ===');
886
+ console.log('Event data:', JSON.stringify(event.data, null, 2));
887
+ const {
888
+ messageId,
889
+ modelConfig
890
+ } = event.data || {};
891
+ const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
892
+ const post = await postService.get(messageId);
893
+ console.log('ai-event-post', JSON.stringify(post, null, 2));
894
+ if (!post) {
895
+ throw new Error('Post not found');
896
+ }
897
+ const {
898
+ sandboxId,
899
+ sandboxUrl
900
+ } = await step.run('create-sandbox-and-start-monitoring', async () => {
901
+ const selectedTemplateId = getTemplateId(modelConfig);
902
+ const sandbox = await Sandbox.create(selectedTemplateId, {
903
+ apiKey,
904
+ domain,
905
+ timeoutMs: 60_000 * 20
906
+ });
907
+ const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
908
+ // Start error monitoring immediately after sandbox creation
909
+ // const container = await initializeContainer();
910
+ // const sandboxErrorService = container.get<ISandboxErrorService>(SERVER_TYPES.SandboxErrorService);
911
+ // console.log(`Starting immediate error monitoring for sandbox ${sandbox.sandboxId}`);
912
+ // await sandboxErrorService.startErrorMonitoring(post.props.projectId, sandbox.sandboxId, sandboxUrl,messageId);
913
+ return {
914
+ sandboxId: sandbox.sandboxId,
915
+ sandboxUrl
916
+ };
917
+ });
918
+ // Only fetch previous messages if we don't have active fragment files
919
+ const previousMessages = event.data.activeFragmentFiles ? [] // Skip fetching previous messages when we have active fragment files
920
+ : await step.run('get-previous-messages', async () => {
921
+ const result = await postService.getPreviousMessagesByProjectId(post.props.projectId, 5);
922
+ console.log('previousMessages', JSON.stringify(result, null, 2));
923
+ // Handle error case - return empty array if getPreviousMessagesByProjectId returns an Error
924
+ if (result instanceof Error) {
925
+ console.warn('Failed to get previous messages:', result.message);
926
+ return [];
927
+ }
928
+ return result;
929
+ });
930
+ const state = createState({
931
+ summary: '',
932
+ files: event.data.activeFragmentFiles || {},
933
+ // Use active fragment files if provided
934
+ canvasLayers: []
935
+ }, {
936
+ messages: previousMessages
937
+ });
938
+ // Create a new agent with the template-specific coding prompt
939
+ const selectedCodingPrompt = getCodingPrompt(modelConfig);
940
+ const codeAgent = createAgent({
941
+ name: 'code-agent',
942
+ description: 'An expert coding agent for page creation',
943
+ system: selectedCodingPrompt,
944
+ model: createModelInstance(modelConfig),
945
+ tools: [createTool({
946
+ name: 'terminal',
947
+ description: 'Use the terminal to run commands',
948
+ parameters: z.object({
949
+ command: z.string()
950
+ }),
951
+ handler: async ({
952
+ command
953
+ }, {
954
+ step
955
+ }) => await step?.run('terminal', async () => {
956
+ try {
957
+ const sandbox = await getSandbox(sandboxId);
958
+ const result = await sandbox.commands.run(command);
959
+ return result.stdout;
960
+ } catch (e) {
961
+ return `Command failed: ${e}`;
962
+ }
963
+ })
964
+ }), createTool({
965
+ name: 'createOrUpdateFiles',
966
+ description: 'Create or update files in the sandbox',
967
+ parameters: z.object({
968
+ files: z.union([z.array(z.object({
969
+ path: z.string(),
970
+ content: z.string()
971
+ })), z.string() // Accept JSON string as fallback
972
+ ])
973
+ }),
974
+ // handler: async ({ files }, { step, network }: Tool.Options<AgentState>) => {
975
+ handler: async ({
976
+ files
977
+ }, {
978
+ step,
979
+ network
980
+ }) => {
981
+ const newFiles = await step?.run('createOrUpdateFiles', async () => {
982
+ try {
983
+ console.log('createOrUpdateFiles - Raw files parameter type:', typeof files);
984
+ console.log('createOrUpdateFiles - Raw files parameter:', `${JSON.stringify(files).substring(0, 200)}...`);
985
+ // Parse files if it's a JSON string (fallback for AI model behavior)
986
+ let parsedFiles;
987
+ if (typeof files === 'string') {
988
+ try {
989
+ // First try standard JSON parsing
990
+ parsedFiles = JSON.parse(files);
991
+ console.log('createOrUpdateFiles - Successfully parsed files from JSON string');
992
+ } catch (parseError) {
993
+ console.log('createOrUpdateFiles - Standard JSON parse failed, trying template literal fix');
994
+ try {
995
+ // AI is using template literals in JSON - need more sophisticated parsing
996
+ console.log('createOrUpdateFiles - Attempting to fix template literals in JSON');
997
+ // Try to evaluate the string as JavaScript to handle template literals
998
+ // This is safe because we're in a sandboxed environment and only parsing our own AI's output
999
+ const evalString = `(${files})`;
1000
+ console.log('createOrUpdateFiles - Evaluating as JavaScript array');
1001
+ parsedFiles = eval(evalString);
1002
+ console.log('createOrUpdateFiles - Successfully evaluated template literals');
1003
+ } catch (secondError) {
1004
+ console.error('createOrUpdateFiles - Both parsing attempts failed:');
1005
+ console.error('Original error:', parseError.message);
1006
+ console.error('Template literal fix error:', secondError.message);
1007
+ console.error('Raw string (first 500 chars):', files.substring(0, 500));
1008
+ return `Error: Failed to parse files JSON - ${parseError.message}`;
1009
+ }
1010
+ }
1011
+ } else if (Array.isArray(files)) {
1012
+ parsedFiles = files;
1013
+ console.log('createOrUpdateFiles - Using files array directly');
1014
+ } else {
1015
+ console.error('createOrUpdateFiles - Files parameter is neither string nor array:', typeof files);
1016
+ return `Error: Files parameter must be an array or JSON string, got ${typeof files}`;
1017
+ }
1018
+ // Validate that parsedFiles is an array
1019
+ if (!Array.isArray(parsedFiles)) {
1020
+ console.error('createOrUpdateFiles - Parsed files is not an array:', parsedFiles);
1021
+ return `Error: Files must be an array after parsing, got ${typeof parsedFiles}`;
1022
+ }
1023
+ console.log(`createOrUpdateFiles - Processing ${parsedFiles.length} files`);
1024
+ const updatedFiles = network.state.data.files || {};
1025
+ const sandbox = await getSandbox(sandboxId);
1026
+ for (const file of parsedFiles) {
1027
+ if (!file.path || file.content === undefined) {
1028
+ console.error('createOrUpdateFiles - Invalid file object (missing path or content):', file);
1029
+ continue;
1030
+ }
1031
+ console.log(`createOrUpdateFiles - Writing file: ${file.path}`);
1032
+ await sandbox.files.write(file.path, file.content);
1033
+ updatedFiles[file.path] = file.content;
1034
+ }
1035
+ console.log(`createOrUpdateFiles - Successfully updated ${parsedFiles.length} files`);
1036
+ return updatedFiles;
1037
+ } catch (e) {
1038
+ console.error('createOrUpdateFiles - Error:', e);
1039
+ console.error('createOrUpdateFiles - Error stack:', e?.stack);
1040
+ return `Error: ${e}`;
1041
+ }
1042
+ });
1043
+ if (typeof newFiles === 'object' && !Array.isArray(newFiles) && typeof newFiles !== 'string') {
1044
+ network.state.data.files = newFiles;
1045
+ }
1046
+ }
1047
+ }), createTool({
1048
+ name: 'readFiles',
1049
+ description: 'Read files from the sandbox',
1050
+ parameters: z.object({
1051
+ files: z.array(z.string())
1052
+ }),
1053
+ handler: async ({
1054
+ files
1055
+ }, {
1056
+ step
1057
+ }) => await step?.run('readFiles', async () => {
1058
+ try {
1059
+ const sandbox = await getSandbox(sandboxId);
1060
+ const contents = [];
1061
+ for (const file of files) {
1062
+ const content = await sandbox.files.read(file);
1063
+ contents.push({
1064
+ path: file,
1065
+ content
1066
+ });
1067
+ }
1068
+ return JSON.stringify(contents);
1069
+ } catch (e) {
1070
+ return `Error: ${e}`;
1071
+ }
1072
+ })
1073
+ })],
1074
+ lifecycle: {
1075
+ onResponse: async ({
1076
+ result,
1077
+ network
1078
+ }) => {
1079
+ const lastAssistantMessageText = lastAssistantTextMessageContent(result);
1080
+ if (lastAssistantMessageText && network) {
1081
+ if (lastAssistantMessageText.includes('<task_summary>')) {
1082
+ network.state.data.summary = lastAssistantMessageText;
1083
+ // Extract canvas layers if present
1084
+ const canvasLayersMatch = lastAssistantMessageText.match(/<canvas_layers>([\s\S]*?)<\/canvas_layers>/);
1085
+ if (canvasLayersMatch) {
1086
+ try {
1087
+ const layersJson = canvasLayersMatch[1].trim();
1088
+ network.state.data.canvasLayers = JSON.parse(layersJson);
1089
+ } catch (error) {
1090
+ console.error('Error parsing canvas layers:', error);
1091
+ network.state.data.canvasLayers = null;
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ return result;
1097
+ }
1098
+ }
1099
+ });
1100
+ const network = createNetwork({
1101
+ name: 'coding-agent-network',
1102
+ agents: [codeAgent],
1103
+ maxIter: 15,
1104
+ defaultState: state,
1105
+ router: async ({
1106
+ network
1107
+ }) => {
1108
+ const {
1109
+ summary
1110
+ } = network.state.data;
1111
+ if (summary) {
1112
+ return;
1113
+ }
1114
+ return codeAgent;
1115
+ }
1116
+ });
1117
+ // Prepare the enhanced input message that includes activeFragmentFiles context
1118
+ let enhancedInput = post.message;
1119
+ // If we have activeFragmentFiles, include them in the message to the AI
1120
+ if (event.data.activeFragmentFiles && Object.keys(event.data.activeFragmentFiles).length > 0) {
1121
+ // Check if ViteVisualEditor exists and warn if it's missing for vite-react template
1122
+ const hasViteVisualEditor = 'src/components/ViteVisualEditor.tsx' in event.data.activeFragmentFiles;
1123
+ const isViteReact = modelConfig?.template === 'vite-react';
1124
+ enhancedInput = `${post.message}
1125
+ ACTIVE FRAGMENT FILES CONTEXT:
1126
+ The current sandbox contains the following files that you MUST include in your createOrUpdateFiles call:
1127
+
1128
+ ${Object.entries(event.data.activeFragmentFiles).map(([path, content]) => `FILE: ${path}\n${typeof content === 'string' ? content.substring(0, 500) + (content.length > 500 ? '...' : '') : '[Non-string content]'}`).join('\n\n')}
1129
+
1130
+ CRITICAL INSTRUCTIONS FOR VISUAL CHANGES:
1131
+ - You MUST include ALL ${Object.keys(event.data.activeFragmentFiles).length} files from the active fragment files in your createOrUpdateFiles call
1132
+ - Apply the visual changes described above to the appropriate files
1133
+ - Preserve ALL existing files and their content except for the specific changes requested
1134
+ - Use the complete file context provided above as your current codebase state
1135
+ - DO NOT output only the changed file - output ALL files to rebuild the complete sandbox${isViteReact && !hasViteVisualEditor ? '\n- IMPORTANT: For vite-react template, you MUST import ViteVisualEditor from "./components/ViteVisualEditor" (it exists in the template), do NOT create a new component' : ''}`;
1136
+ }
1137
+ const result = await network.run(enhancedInput, {
1138
+ state
1139
+ });
1140
+ // Enhanced file validation with logging
1141
+ const hasFiles = result.state.data.files && Object.keys(result.state.data.files).length > 0;
1142
+ const hasSummary = result.state.data.summary && result.state.data.summary.trim().length > 0;
1143
+ const outputFileCount = Object.keys(result.state.data.files || {}).length;
1144
+ const inputFileCount = event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles).length : 0;
1145
+ console.log('Agent result validation:', {
1146
+ hasFiles,
1147
+ outputFileCount,
1148
+ inputFileCount,
1149
+ fileCountMatch: inputFileCount > 0 ? outputFileCount === inputFileCount : true,
1150
+ hasSummary,
1151
+ summaryLength: result.state.data.summary?.length || 0,
1152
+ outputFiles: Object.keys(result.state.data.files || {}),
1153
+ inputFiles: event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles) : []
1154
+ });
1155
+ // Warning if file counts don't match for visual changes
1156
+ if (event.data.activeFragmentFiles && inputFileCount > 0 && outputFileCount !== inputFileCount) {
1157
+ console.warn(`⚠️ File count mismatch! Expected ${inputFileCount} files, got ${outputFileCount} files`);
1158
+ console.warn('Missing files may cause incomplete sandbox rebuild');
1159
+ }
1160
+ const fragmentTitleGenerator = createAgent({
1161
+ name: 'fragment-title-generator',
1162
+ description: 'A fragment title generator',
1163
+ system: FRAGMENT_TITLE_PROMPT,
1164
+ model: createModelInstance(modelConfig)
1165
+ });
1166
+ const responseGenerator = createAgent({
1167
+ name: 'response-generator',
1168
+ description: 'A response generator',
1169
+ system: RESPONSE_PROMPT,
1170
+ model: createModelInstance(modelConfig)
1171
+ });
1172
+ // Run generators in parallel outside of steps (like lovable-clone)
1173
+ const {
1174
+ output: fragmentTitleOutput
1175
+ } = await fragmentTitleGenerator.run(result.state.data.summary || 'Generated Page');
1176
+ const {
1177
+ output: responseOutput
1178
+ } = await responseGenerator.run(result.state.data.summary || 'Code generated successfully');
1179
+ const agentError = !result.state.data.summary || Object.keys(result.state.data.files || {}).length === 0;
1180
+ // Save to database using handler
1181
+ const messageData = await step.run('save-ai-code-result', async () => {
1182
+ if (agentError) {
1183
+ return await postService.create({
1184
+ channel: post.channel,
1185
+ message: 'Something went wrong. Please try again.',
1186
+ type: PostTypeEnum.Simple,
1187
+ editedBy: post.editedBy,
1188
+ author: post.editedBy,
1189
+ files: [],
1190
+ props: {
1191
+ role: AiAgentMessageRole.ASSISTANT,
1192
+ projectId: post.props.projectId,
1193
+ template: modelConfig?.template || 'vite-react',
1194
+ sendNotificationWithProjectId: true,
1195
+ fragment: {
1196
+ sandboxUrl: sandboxUrl,
1197
+ title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
1198
+ files: result.state.data.files || [],
1199
+ summary: result.state.data.summary || '',
1200
+ canvasLayers: result.state.data.canvasLayers || [],
1201
+ type: AiAgentMessageType.ERROR,
1202
+ messageId: messageId,
1203
+ isError: agentError
1204
+ }
1205
+ }
1206
+ });
1207
+ }
1208
+ return await postService.create({
1209
+ channel: post.channel,
1210
+ message: parseAgentOutput(responseOutput) || 'Code generated successfully',
1211
+ type: PostTypeEnum.Simple,
1212
+ editedBy: post.editedBy,
1213
+ author: post.editedBy,
1214
+ files: [],
1215
+ props: {
1216
+ role: AiAgentMessageRole.ASSISTANT,
1217
+ projectId: post.props.projectId,
1218
+ template: modelConfig?.template || 'vite-react',
1219
+ sendNotificationWithProjectId: true,
1220
+ fragment: {
1221
+ sandboxUrl: sandboxUrl,
1222
+ title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
1223
+ files: result.state.data.files || [],
1224
+ summary: result.state.data.summary || '',
1225
+ canvasLayers: result.state.data.canvasLayers || [],
1226
+ type: AiAgentMessageType.RESULT,
1227
+ messageId: messageId,
1228
+ isError: agentError
1229
+ }
1230
+ }
1231
+ });
1232
+ });
1233
+ // Publish subscription update for real-time client updates
1234
+ // await step.run('publish-subscription-ai-code-result', async () => {
1235
+ // const pubsub = container.get<PubSub>('PubSub');
1236
+ // console.log('=== Publish to subscription Post Data:', JSON.stringify(messageData, null, 2));
1237
+ // pubsub.publish(`POST_CREATED.${post.props.projectId}`, messageData);
1238
+ // });
1239
+ // Error monitoring was already started immediately after sandbox creation
1240
+ // No need to start it again here
1241
+ return messageData;
1242
+ });
1243
+ // Sandbox recreation function - recreates sandbox with existing files from fragment
1244
+ const recreateSandboxUrlFunction = (inngest, container) => inngest.createFunction({
1245
+ id: 'recreate-sandbox-url-function'
1246
+ }, {
1247
+ event: 'recreate-sandbox-url'
1248
+ }, async ({
1249
+ event,
1250
+ step
1251
+ }) => {
1252
+ console.log('=== RECREATE SANDBOX FUNCTION STARTED ===');
1253
+ console.log('Event data:', JSON.stringify(event.data, null, 2));
1254
+ const {
1255
+ projectId,
1256
+ messageId
1257
+ } = event.data;
1258
+ // Fetch the fragment with existing files
1259
+ const messageData = await step.run('fetch-message', async () => {
1260
+ const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
1261
+ return await postService.get(messageId);
1262
+ });
1263
+ if (!messageData || !messageData?.props?.fragment?.files) {
1264
+ throw new Error(`Message ${messageId} not found or has no files`);
1265
+ }
1266
+ // Create new sandbox with existing files using the stored template
1267
+ const {
1268
+ sandboxId,
1269
+ sandboxUrl
1270
+ } = await step.run('create-new-sandbox', async () => {
1271
+ // Get the correct template ID based on the stored template
1272
+ const selectedTemplateId = getTemplateId({
1273
+ template: messageData.props.template
1274
+ });
1275
+ console.log(`Recreating sandbox with template: ${selectedTemplateId} (stored: ${messageData.props.template || 'react-vite'})`);
1276
+ const sandbox = await Sandbox.create(selectedTemplateId, {
1277
+ apiKey,
1278
+ domain
1279
+ });
1280
+ const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
1281
+ console.log(`Created new sandbox ${sandbox.sandboxId} for project ${projectId}`);
1282
+ // Write all existing files to the new sandbox
1283
+ for (const [filePath, fileContent] of Object.entries(messageData.props.fragment.files)) {
1284
+ await sandbox.files.write(filePath, fileContent);
1285
+ console.log(`Wrote file ${filePath} to new sandbox`);
1286
+ }
1287
+ // Start error monitoring for the new sandbox
1288
+ const sandboxErrorService = container.get(SERVER_TYPES.SandboxErrorService);
1289
+ console.log(`Starting error monitoring for recreated sandbox ${sandbox.sandboxId}`);
1290
+ await sandboxErrorService.startErrorMonitoring(projectId, sandbox.sandboxId, sandboxUrl, messageId);
1291
+ return {
1292
+ sandboxId: sandbox.sandboxId,
1293
+ sandboxUrl
1294
+ };
1295
+ });
1296
+ // Update the fragment with new sandbox URL
1297
+ const updatedMessage = await step.run('update-message', async () => {
1298
+ const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
1299
+ return await postService.update(messageId, {
1300
+ props: {
1301
+ ...messageData.props,
1302
+ fragment: {
1303
+ ...messageData.props.fragment,
1304
+ sandboxUrl: sandboxUrl
1305
+ }
1306
+ }
1307
+ });
1308
+ });
1309
+ // Publish update to notify clients of the new sandbox URL for the same fragment
1310
+ await step.run('notify-sandbox-recreation', async () => {
1311
+ const pubsub = container.get('PubSub');
1312
+ // Publish a fragment update instead of a project update
1313
+ await pubsub.publish(`POST_UPDATED.${projectId}`, updatedMessage);
1314
+ });
1315
+ return {
1316
+ success: true,
1317
+ projectId,
1318
+ messageId,
1319
+ newSandboxUrl: sandboxUrl,
1320
+ newSandboxId: sandboxId
1321
+ };
1322
+ });
1323
+ const regenerateAiCodeFunction = (inngest, container) => inngest.createFunction({
1324
+ id: 'regenerate-ai-code-agent'
1325
+ }, {
1326
+ event: 'regenerate-ai-code'
1327
+ }, async ({
1328
+ event,
1329
+ step
1330
+ }) => {
1331
+ console.log('=== REGENERATE AI CODE AGENT FUNCTION STARTED ===');
1332
+ console.log('Event data:', JSON.stringify(event.data, null, 2));
1333
+ const {
1334
+ messageId,
1335
+ modelConfig
1336
+ } = event.data || {};
1337
+ const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
1338
+ const post = await postService.get(messageId);
1339
+ if (!post) {
1340
+ throw new Error('Post not found');
1341
+ }
1342
+ const {
1343
+ sandboxId,
1344
+ sandboxUrl
1345
+ } = await step.run('create-sandbox-and-start-monitoring', async () => {
1346
+ const selectedTemplateId = getTemplateId(modelConfig);
1347
+ const sandbox = await Sandbox.create(selectedTemplateId, {
1348
+ apiKey,
1349
+ domain,
1350
+ timeoutMs: 60_000 * 20
1351
+ });
1352
+ const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
1353
+ // Start error monitoring immediately after sandbox creation
1354
+ // const container = await initializeContainer();
1355
+ // const sandboxErrorService = container.get<ISandboxErrorService>(SERVER_TYPES.SandboxErrorService);
1356
+ // console.log(`Starting immediate error monitoring for sandbox ${sandbox.sandboxId}`);
1357
+ // await sandboxErrorService.startErrorMonitoring(post.props.projectId, sandbox.sandboxId, sandboxUrl,messageId);
1358
+ return {
1359
+ sandboxId: sandbox.sandboxId,
1360
+ sandboxUrl
1361
+ };
1362
+ });
1363
+ // Save to database using handler
1364
+ const messageData = await step.run('save-ai-code-result', async () => {
1365
+ return await postService.update(messageId, {
1366
+ props: {
1367
+ ...post.props,
1368
+ sendNotificationWithProjectId: false,
1369
+ fragment: {
1370
+ sandboxUrl: sandboxUrl
1371
+ // files: result.state.data.files || [],
1372
+ }
1373
+ }
1374
+ });
1375
+ });
1376
+ // Publish subscription update for real-time client updates
1377
+ await step.run('publish-subscription-ai-code-result', async () => {
1378
+ const pubsub = container.get('PubSub');
1379
+ console.log('=== Publish to subscription Post Data:', JSON.stringify(messageData, null, 2));
1380
+ pubsub.publish(`POST_CREATED.${post.props.projectId}`, messageData);
1381
+ });
1382
+ // Error monitoring was already started immediately after sandbox creation
1383
+ // No need to start it again here
1384
+ return messageData;
1385
+ });export{codeAgentFunction,createChannelWithProjectId,generateAIResponseWithSandbox,generateAiCodeFunction,recreateSandboxUrlFunction,regenerateAiCodeFunction,sendMessageHandler};//# sourceMappingURL=functions.js.map