nexusbert commited on
Commit
6e47f57
Β·
1 Parent(s): b63a158
src/routes/chatRoutes.ts CHANGED
@@ -42,9 +42,11 @@ const upload = multer({
42
  * Send a text message and/or files to an agent. The system handles communication with any agent endpoint format.
43
  *
44
  * **Agent Communication Protocol:**
45
- * - Files are uploaded to IPFS first, then URLs are sent to the agent endpoint
 
 
46
  * - For text-only messages: Sends JSON payload to agent endpoint
47
- * - For messages with files: Sends multipart/form-data with file URLs (IPFS hashes)
48
  * - Supports custom headers via agent metadata (agent.metadata.headers)
49
  * - Works with any agent endpoint format (REST, GraphQL, custom APIs)
50
  *
 
42
  * Send a text message and/or files to an agent. The system handles communication with any agent endpoint format.
43
  *
44
  * **Agent Communication Protocol:**
45
+ * - Files are sent directly to agent endpoints (not uploaded to IPFS)
46
+ * - Files are stored temporarily in memory during processing
47
+ * - File metadata is stored in chat history with 1-hour expiration
48
  * - For text-only messages: Sends JSON payload to agent endpoint
49
+ * - For messages with files: Sends multipart/form-data with actual file buffers
50
  * - Supports custom headers via agent metadata (agent.metadata.headers)
51
  * - Works with any agent endpoint format (REST, GraphQL, custom APIs)
52
  *
src/services/chatProtocol.ts CHANGED
@@ -3,7 +3,6 @@ import FormData from 'form-data';
3
  import { Agent } from '../entities/Agent';
4
  import { ChatMessage, MessageRole } from '../entities/ChatMessage';
5
  import { AppDataSource } from '../config/database';
6
- import { IpfsService } from './ipfsService';
7
 
8
  export interface ChatRequest {
9
  message: string;
@@ -34,32 +33,22 @@ export interface ChatResponse {
34
  */
35
  export class ChatProtocolService {
36
  private timeout: number;
37
- private ipfsService: IpfsService;
38
  private maxFileSize: number;
39
 
40
  constructor() {
41
  this.timeout = parseInt(process.env.AGENT_CHAT_TIMEOUT || '30000');
42
- this.ipfsService = new IpfsService();
43
  this.maxFileSize = parseInt(process.env.MAX_FILE_SIZE || '10485760'); // 10MB default
44
  }
45
 
46
  /**
47
- * Upload files to IPFS
 
48
  */
49
- private async uploadFiles(files: ChatRequest['files']): Promise<Array<{
50
- fieldname: string;
51
- originalname: string;
52
- mimetype: string;
53
- size: number;
54
- ipfsHash: string;
55
- url: string;
56
- }>> {
57
  if (!files || files.length === 0) {
58
- return [];
59
  }
60
 
61
- const uploadedFiles = [];
62
-
63
  for (const file of files) {
64
  // Validate file size
65
  if (file.size > this.maxFileSize) {
@@ -67,33 +56,7 @@ export class ChatProtocolService {
67
  `File ${file.originalname} exceeds maximum size of ${this.maxFileSize} bytes`
68
  );
69
  }
70
-
71
- // Upload to IPFS
72
- try {
73
- const fileObj = new File(
74
- [file.buffer],
75
- file.originalname,
76
- { type: file.mimetype }
77
- );
78
-
79
- const upload = await this.ipfsService.uploadFile(fileObj);
80
- const gatewayUrl = this.ipfsService.getGatewayUrl(upload.cid);
81
-
82
- uploadedFiles.push({
83
- fieldname: file.fieldname,
84
- originalname: file.originalname,
85
- mimetype: file.mimetype,
86
- size: file.size,
87
- ipfsHash: upload.cid,
88
- url: gatewayUrl,
89
- });
90
- } catch (error) {
91
- console.error(`Error uploading file ${file.originalname}:`, error);
92
- throw new Error(`Failed to upload file ${file.originalname} to IPFS`);
93
- }
94
  }
95
-
96
- return uploadedFiles;
97
  }
98
 
99
  /**
@@ -110,23 +73,39 @@ export class ChatProtocolService {
110
  throw new Error(`Message exceeds maximum length of ${maxLength} characters`);
111
  }
112
 
113
- // Upload files to IPFS if present
 
114
  let uploadedFiles: Array<{
115
  fieldname: string;
116
  originalname: string;
117
  mimetype: string;
118
  size: number;
119
- ipfsHash: string;
120
- url: string;
 
121
  }> = [];
122
 
123
  if (request.files && request.files.length > 0) {
124
- uploadedFiles = await this.uploadFiles(request.files);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
126
 
127
  const conversationId = request.conversationId || this.generateConversationId();
128
 
129
- // Build metadata with file information
130
  const metadata = {
131
  ...request.metadata,
132
  agentId: agent.id,
@@ -137,8 +116,8 @@ export class ChatProtocolService {
137
  originalname: f.originalname,
138
  mimetype: f.mimetype,
139
  size: f.size,
140
- ipfsHash: f.ipfsHash,
141
- url: f.url,
142
  })),
143
  }),
144
  };
@@ -164,13 +143,52 @@ export class ChatProtocolService {
164
  formData.append('systemPrompt', agent.promptTemplate);
165
  }
166
 
167
- // Add file information (not the actual files, just metadata with URLs)
168
- uploadedFiles.forEach((file, index) => {
169
- formData.append(`file_${index}_url`, file.url);
170
- formData.append(`file_${index}_hash`, file.ipfsHash);
171
- formData.append(`file_${index}_name`, file.originalname);
172
- formData.append(`file_${index}_type`, file.mimetype);
173
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  response = await axios.post(agent.endpoint, formData, {
176
  timeout: this.timeout,
@@ -207,7 +225,7 @@ export class ChatProtocolService {
207
  ? `\n[Files: ${uploadedFiles.map(f => f.originalname).join(', ')}]`
208
  : '';
209
 
210
- // Save message to database
211
  await this.saveMessage({
212
  agentId: agent.id,
213
  userId: request.userId,
@@ -215,7 +233,15 @@ export class ChatProtocolService {
215
  content: userContent + filesInfo,
216
  metadata: {
217
  ...metadata,
218
- files: uploadedFiles,
 
 
 
 
 
 
 
 
219
  },
220
  });
221
 
 
3
  import { Agent } from '../entities/Agent';
4
  import { ChatMessage, MessageRole } from '../entities/ChatMessage';
5
  import { AppDataSource } from '../config/database';
 
6
 
7
  export interface ChatRequest {
8
  message: string;
 
33
  */
34
  export class ChatProtocolService {
35
  private timeout: number;
 
36
  private maxFileSize: number;
37
 
38
  constructor() {
39
  this.timeout = parseInt(process.env.AGENT_CHAT_TIMEOUT || '30000');
 
40
  this.maxFileSize = parseInt(process.env.MAX_FILE_SIZE || '10485760'); // 10MB default
41
  }
42
 
43
  /**
44
+ * Validate files (no IPFS upload - files stored temporarily in memory)
45
+ * Files are kept in memory during request processing and sent directly to agent endpoints
46
  */
47
+ private validateFiles(files: ChatRequest['files']): void {
 
 
 
 
 
 
 
48
  if (!files || files.length === 0) {
49
+ return;
50
  }
51
 
 
 
52
  for (const file of files) {
53
  // Validate file size
54
  if (file.size > this.maxFileSize) {
 
56
  `File ${file.originalname} exceeds maximum size of ${this.maxFileSize} bytes`
57
  );
58
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
 
 
60
  }
61
 
62
  /**
 
73
  throw new Error(`Message exceeds maximum length of ${maxLength} characters`);
74
  }
75
 
76
+ // Store files temporarily (don't upload to IPFS)
77
+ // Files will be available for 1 hour before cleanup
78
  let uploadedFiles: Array<{
79
  fieldname: string;
80
  originalname: string;
81
  mimetype: string;
82
  size: number;
83
+ buffer: Buffer;
84
+ tempUrl?: string; // Temporary URL for chat viewing
85
+ expiresAt: Date; // Cleanup after 1 hour
86
  }> = [];
87
 
88
  if (request.files && request.files.length > 0) {
89
+ // Validate files first
90
+ this.validateFiles(request.files);
91
+
92
+ // Store files temporarily without IPFS upload
93
+ // Files are kept in memory during request processing
94
+ // Metadata stored in DB with 1-hour expiration timestamp
95
+ const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
96
+ uploadedFiles = request.files.map(file => ({
97
+ fieldname: file.fieldname,
98
+ originalname: file.originalname,
99
+ mimetype: file.mimetype,
100
+ size: file.size,
101
+ buffer: file.buffer,
102
+ expiresAt: oneHourFromNow,
103
+ }));
104
  }
105
 
106
  const conversationId = request.conversationId || this.generateConversationId();
107
 
108
+ // Build metadata with file information (temporary storage, no IPFS)
109
  const metadata = {
110
  ...request.metadata,
111
  agentId: agent.id,
 
116
  originalname: f.originalname,
117
  mimetype: f.mimetype,
118
  size: f.size,
119
+ expiresAt: f.expiresAt.toISOString(),
120
+ // Note: Files are stored temporarily, not on IPFS
121
  })),
122
  }),
123
  };
 
143
  formData.append('systemPrompt', agent.promptTemplate);
144
  }
145
 
146
+ // Send actual files directly to agent endpoint (no IPFS upload)
147
+ // Use smart field naming based on file type and count
148
+ if (request.files && request.files.length > 0) {
149
+ const audioFiles = request.files.filter(f => f.mimetype.startsWith('audio/'));
150
+
151
+ // If single audio file, use 'audio_file' (common for transcription agents)
152
+ if (audioFiles.length === 1 && request.files.length === 1) {
153
+ const audioFile = request.files.find(f => f.mimetype.startsWith('audio/'));
154
+ if (audioFile) {
155
+ formData.append('audio_file', audioFile.buffer, {
156
+ filename: audioFile.originalname,
157
+ contentType: audioFile.mimetype,
158
+ });
159
+ }
160
+ }
161
+ // If single file of any type, use 'file'
162
+ else if (request.files.length === 1) {
163
+ const file = request.files[0];
164
+ formData.append('file', file.buffer, {
165
+ filename: file.originalname,
166
+ contentType: file.mimetype,
167
+ });
168
+ }
169
+ // Multiple files - use 'files' array or indexed fields
170
+ else {
171
+ request.files.forEach((file, index) => {
172
+ // Try common field names first
173
+ if (file.mimetype.startsWith('audio/')) {
174
+ formData.append(`audio_file${index > 0 ? `_${index}` : ''}`, file.buffer, {
175
+ filename: file.originalname,
176
+ contentType: file.mimetype,
177
+ });
178
+ } else if (file.mimetype.startsWith('image/')) {
179
+ formData.append(`image_file${index > 0 ? `_${index}` : ''}`, file.buffer, {
180
+ filename: file.originalname,
181
+ contentType: file.mimetype,
182
+ });
183
+ } else {
184
+ formData.append(`file${index > 0 ? `_${index}` : ''}`, file.buffer, {
185
+ filename: file.originalname,
186
+ contentType: file.mimetype,
187
+ });
188
+ }
189
+ });
190
+ }
191
+ }
192
 
193
  response = await axios.post(agent.endpoint, formData, {
194
  timeout: this.timeout,
 
225
  ? `\n[Files: ${uploadedFiles.map(f => f.originalname).join(', ')}]`
226
  : '';
227
 
228
+ // Save message to database with file metadata (files stored temporarily, not on IPFS)
229
  await this.saveMessage({
230
  agentId: agent.id,
231
  userId: request.userId,
 
233
  content: userContent + filesInfo,
234
  metadata: {
235
  ...metadata,
236
+ files: uploadedFiles.map(f => ({
237
+ fieldname: f.fieldname,
238
+ originalname: f.originalname,
239
+ mimetype: f.mimetype,
240
+ size: f.size,
241
+ expiresAt: f.expiresAt.toISOString(),
242
+ // Note: File buffers are not stored in DB, only metadata
243
+ // Files are available temporarily in memory for 1 hour
244
+ })),
245
  },
246
  });
247