march-ai-sdk 0.3.0
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.
- package/README.md +732 -0
- package/dist/app-C_umwZXh.d.ts +790 -0
- package/dist/extensions/langgraph.d.ts +144 -0
- package/dist/extensions/langgraph.js +326 -0
- package/dist/extensions/langgraph.js.map +1 -0
- package/dist/extensions/vercel-ai.d.ts +124 -0
- package/dist/extensions/vercel-ai.js +177 -0
- package/dist/extensions/vercel-ai.js.map +1 -0
- package/dist/index.d.ts +260 -0
- package/dist/index.js +1695 -0
- package/dist/index.js.map +1 -0
- package/dist/proto/gateway.proto +99 -0
- package/package.json +83 -0
- package/src/agent-state-client.ts +115 -0
- package/src/agent.ts +293 -0
- package/src/api-paths.ts +60 -0
- package/src/app.ts +235 -0
- package/src/artifact.ts +59 -0
- package/src/attachment-client.ts +78 -0
- package/src/checkpoint-client.ts +175 -0
- package/src/conversation-client.ts +109 -0
- package/src/conversation-message.ts +61 -0
- package/src/conversation.ts +123 -0
- package/src/exceptions.ts +78 -0
- package/src/extensions/index.ts +6 -0
- package/src/extensions/langgraph.ts +351 -0
- package/src/extensions/vercel-ai.ts +177 -0
- package/src/gateway-client.ts +420 -0
- package/src/heartbeat.ts +89 -0
- package/src/index.ts +70 -0
- package/src/memory-client.ts +125 -0
- package/src/memory.ts +68 -0
- package/src/message.ts +178 -0
- package/src/proto/gateway.proto +99 -0
- package/src/streamer.ts +242 -0
- package/src/types.ts +196 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1695 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import * as grpc from '@grpc/grpc-js';
|
|
3
|
+
import * as protoLoader from '@grpc/proto-loader';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
|
|
7
|
+
// src/conversation-message.ts
|
|
8
|
+
var ConversationMessage = class _ConversationMessage {
|
|
9
|
+
id;
|
|
10
|
+
conversationId;
|
|
11
|
+
role;
|
|
12
|
+
content;
|
|
13
|
+
from;
|
|
14
|
+
to;
|
|
15
|
+
createdAt;
|
|
16
|
+
metadata;
|
|
17
|
+
constructor(data) {
|
|
18
|
+
this.id = data.id;
|
|
19
|
+
this.conversationId = data.conversationId;
|
|
20
|
+
this.role = data.role;
|
|
21
|
+
this.content = data.content;
|
|
22
|
+
this.from = data.from;
|
|
23
|
+
this.to = data.to;
|
|
24
|
+
this.createdAt = new Date(data.createdAt);
|
|
25
|
+
this.metadata = data.metadata;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create from API response
|
|
29
|
+
*/
|
|
30
|
+
static fromApiResponse(data) {
|
|
31
|
+
return new _ConversationMessage({
|
|
32
|
+
id: data.id,
|
|
33
|
+
conversationId: data.conversation_id,
|
|
34
|
+
role: data.role,
|
|
35
|
+
content: data.content,
|
|
36
|
+
from: data.from,
|
|
37
|
+
to: data.to,
|
|
38
|
+
createdAt: data.created_at,
|
|
39
|
+
metadata: data.metadata
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if this is a user message
|
|
44
|
+
*/
|
|
45
|
+
isUser() {
|
|
46
|
+
return this.role === "user";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if this is an assistant message
|
|
50
|
+
*/
|
|
51
|
+
isAssistant() {
|
|
52
|
+
return this.role === "assistant";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/conversation.ts
|
|
57
|
+
var Conversation = class {
|
|
58
|
+
conversationId;
|
|
59
|
+
client;
|
|
60
|
+
agentName;
|
|
61
|
+
constructor(conversationId, client, agentName) {
|
|
62
|
+
this.conversationId = conversationId;
|
|
63
|
+
this.client = client;
|
|
64
|
+
this.agentName = agentName;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get all messages in the conversation.
|
|
68
|
+
*/
|
|
69
|
+
async getHistory(options = {}) {
|
|
70
|
+
const messages = await this.client.getMessages(this.conversationId, {
|
|
71
|
+
limit: options.limit ?? 100,
|
|
72
|
+
offset: options.offset ?? 0
|
|
73
|
+
});
|
|
74
|
+
return messages.map((m) => ConversationMessage.fromApiResponse(m));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get messages to/from the current agent.
|
|
78
|
+
* Useful for getting conversation history for a specific agent.
|
|
79
|
+
*/
|
|
80
|
+
async getAgentHistory(options = {}) {
|
|
81
|
+
const targetAgent = options.agentName ?? this.agentName;
|
|
82
|
+
if (!targetAgent) {
|
|
83
|
+
return this.getHistory(options);
|
|
84
|
+
}
|
|
85
|
+
const [toAgent, fromAgent] = await Promise.all([
|
|
86
|
+
this.client.getMessages(this.conversationId, {
|
|
87
|
+
to: targetAgent,
|
|
88
|
+
limit: options.limit ?? 50,
|
|
89
|
+
offset: options.offset ?? 0
|
|
90
|
+
}),
|
|
91
|
+
this.client.getMessages(this.conversationId, {
|
|
92
|
+
from: targetAgent,
|
|
93
|
+
limit: options.limit ?? 50,
|
|
94
|
+
offset: options.offset ?? 0
|
|
95
|
+
})
|
|
96
|
+
]);
|
|
97
|
+
const allMessages = [...toAgent, ...fromAgent];
|
|
98
|
+
const uniqueMessages = Array.from(
|
|
99
|
+
new Map(allMessages.map((m) => [m.id, m])).values()
|
|
100
|
+
);
|
|
101
|
+
uniqueMessages.sort((a, b) => {
|
|
102
|
+
const dateA = new Date(a.createdAt).getTime();
|
|
103
|
+
const dateB = new Date(b.createdAt).getTime();
|
|
104
|
+
return dateA - dateB;
|
|
105
|
+
});
|
|
106
|
+
return uniqueMessages.map(
|
|
107
|
+
(m) => ConversationMessage.fromApiResponse(m)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the last N messages.
|
|
112
|
+
*/
|
|
113
|
+
async getLastMessages(count) {
|
|
114
|
+
return this.getHistory({ limit: count });
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get user messages only.
|
|
118
|
+
*/
|
|
119
|
+
async getUserMessages(options = {}) {
|
|
120
|
+
const messages = await this.client.getMessages(this.conversationId, {
|
|
121
|
+
...options,
|
|
122
|
+
role: "user"
|
|
123
|
+
});
|
|
124
|
+
return messages.map((m) => ConversationMessage.fromApiResponse(m));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get assistant messages only.
|
|
128
|
+
*/
|
|
129
|
+
async getAssistantMessages(options = {}) {
|
|
130
|
+
const messages = await this.client.getMessages(this.conversationId, {
|
|
131
|
+
...options,
|
|
132
|
+
role: "assistant"
|
|
133
|
+
});
|
|
134
|
+
return messages.map((m) => ConversationMessage.fromApiResponse(m));
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/memory.ts
|
|
139
|
+
var Memory = class {
|
|
140
|
+
userId;
|
|
141
|
+
conversationId;
|
|
142
|
+
client;
|
|
143
|
+
constructor(userId, conversationId, client) {
|
|
144
|
+
this.userId = userId;
|
|
145
|
+
this.conversationId = conversationId;
|
|
146
|
+
this.client = client;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Add messages to memory.
|
|
150
|
+
*/
|
|
151
|
+
async addMessages(messages) {
|
|
152
|
+
const result = await this.client.addMessages(
|
|
153
|
+
this.userId,
|
|
154
|
+
this.conversationId,
|
|
155
|
+
messages
|
|
156
|
+
);
|
|
157
|
+
return result.added;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Add a single message to memory.
|
|
161
|
+
*/
|
|
162
|
+
async addMessage(role, content) {
|
|
163
|
+
await this.addMessages([{ role, content }]);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Search memory for relevant content.
|
|
167
|
+
*/
|
|
168
|
+
async search(query, limit = 10) {
|
|
169
|
+
return this.client.search(this.userId, query, limit);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get user summary.
|
|
173
|
+
*/
|
|
174
|
+
async getSummary() {
|
|
175
|
+
return this.client.getUserSummary(this.userId);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Clear all memories for this user.
|
|
179
|
+
*/
|
|
180
|
+
async clear() {
|
|
181
|
+
const result = await this.client.clearMemory(this.userId);
|
|
182
|
+
return result.deleted;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/exceptions.ts
|
|
187
|
+
var MarchAgentError = class extends Error {
|
|
188
|
+
constructor(message) {
|
|
189
|
+
super(message);
|
|
190
|
+
this.name = "MarchAgentError";
|
|
191
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var RegistrationError = class extends MarchAgentError {
|
|
195
|
+
constructor(message) {
|
|
196
|
+
super(message);
|
|
197
|
+
this.name = "RegistrationError";
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
var KafkaError = class extends MarchAgentError {
|
|
201
|
+
constructor(message) {
|
|
202
|
+
super(message);
|
|
203
|
+
this.name = "KafkaError";
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var ConfigurationError = class extends MarchAgentError {
|
|
207
|
+
constructor(message) {
|
|
208
|
+
super(message);
|
|
209
|
+
this.name = "ConfigurationError";
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var APIException = class extends MarchAgentError {
|
|
213
|
+
statusCode;
|
|
214
|
+
constructor(message, statusCode) {
|
|
215
|
+
super(message);
|
|
216
|
+
this.name = "APIException";
|
|
217
|
+
this.statusCode = statusCode;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var HeartbeatError = class extends MarchAgentError {
|
|
221
|
+
constructor(message) {
|
|
222
|
+
super(message);
|
|
223
|
+
this.name = "HeartbeatError";
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var GatewayError = class extends MarchAgentError {
|
|
227
|
+
constructor(message) {
|
|
228
|
+
super(message);
|
|
229
|
+
this.name = "GatewayError";
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var AttachmentInfoSchema = z.object({
|
|
233
|
+
url: z.string(),
|
|
234
|
+
filename: z.string(),
|
|
235
|
+
contentType: z.string(),
|
|
236
|
+
size: z.number().optional(),
|
|
237
|
+
fileType: z.string().optional()
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// src/attachment-client.ts
|
|
241
|
+
function createAttachmentInfo(data) {
|
|
242
|
+
return AttachmentInfoSchema.parse({
|
|
243
|
+
url: data.url,
|
|
244
|
+
filename: data.filename || data.file_name,
|
|
245
|
+
contentType: data.content_type || data.contentType,
|
|
246
|
+
size: data.size,
|
|
247
|
+
fileType: data.file_type || data.fileType
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
var AttachmentClient = class {
|
|
251
|
+
baseUrl;
|
|
252
|
+
constructor(baseUrl) {
|
|
253
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Build full URL for an attachment.
|
|
257
|
+
*/
|
|
258
|
+
buildUrl(attachmentUrl) {
|
|
259
|
+
if (attachmentUrl.startsWith("http://") || attachmentUrl.startsWith("https://")) {
|
|
260
|
+
return attachmentUrl;
|
|
261
|
+
}
|
|
262
|
+
return `${this.baseUrl}${attachmentUrl.startsWith("/") ? "" : "/"}${attachmentUrl}`;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Download attachment as bytes (Buffer).
|
|
266
|
+
*/
|
|
267
|
+
async download(url) {
|
|
268
|
+
const fullUrl = this.buildUrl(url);
|
|
269
|
+
try {
|
|
270
|
+
const response = await fetch(fullUrl);
|
|
271
|
+
if (!response.ok) {
|
|
272
|
+
throw new APIException(`Failed to download attachment: ${response.status}`, response.status);
|
|
273
|
+
}
|
|
274
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
275
|
+
return Buffer.from(arrayBuffer);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (error instanceof APIException) throw error;
|
|
278
|
+
throw new APIException(`Failed to download attachment: ${error}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Download attachment as base64 string.
|
|
283
|
+
* Useful for LLM vision APIs.
|
|
284
|
+
*/
|
|
285
|
+
async downloadAsBase64(url) {
|
|
286
|
+
const buffer = await this.download(url);
|
|
287
|
+
return buffer.toString("base64");
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/message.ts
|
|
292
|
+
var Message = class _Message {
|
|
293
|
+
content;
|
|
294
|
+
conversationId;
|
|
295
|
+
userId;
|
|
296
|
+
headers;
|
|
297
|
+
rawBody;
|
|
298
|
+
conversation;
|
|
299
|
+
memory;
|
|
300
|
+
metadata;
|
|
301
|
+
schema;
|
|
302
|
+
attachment;
|
|
303
|
+
attachmentClient;
|
|
304
|
+
constructor(options) {
|
|
305
|
+
this.content = options.content;
|
|
306
|
+
this.conversationId = options.conversationId;
|
|
307
|
+
this.userId = options.userId;
|
|
308
|
+
this.headers = options.headers;
|
|
309
|
+
this.rawBody = options.rawBody;
|
|
310
|
+
this.conversation = options.conversation;
|
|
311
|
+
this.memory = options.memory;
|
|
312
|
+
this.metadata = options.metadata;
|
|
313
|
+
this.schema = options.schema;
|
|
314
|
+
this.attachment = options.attachment;
|
|
315
|
+
this.attachmentClient = options.attachmentClient;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Create Message from Kafka message data.
|
|
319
|
+
*/
|
|
320
|
+
static fromKafkaMessage(body, headers, options = {}) {
|
|
321
|
+
const conversationId = headers.conversationId ?? "";
|
|
322
|
+
const userId = headers.userId ?? "anonymous";
|
|
323
|
+
let metadata;
|
|
324
|
+
if (headers.messageMetadata) {
|
|
325
|
+
try {
|
|
326
|
+
metadata = JSON.parse(headers.messageMetadata);
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
let schema;
|
|
331
|
+
if (headers.messageSchema) {
|
|
332
|
+
try {
|
|
333
|
+
schema = JSON.parse(headers.messageSchema);
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
let attachment;
|
|
338
|
+
if (headers.attachment) {
|
|
339
|
+
try {
|
|
340
|
+
const attachmentData = JSON.parse(headers.attachment);
|
|
341
|
+
attachment = createAttachmentInfo(attachmentData);
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!attachment && body.attachment) {
|
|
346
|
+
try {
|
|
347
|
+
attachment = createAttachmentInfo(body.attachment);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
let conversation;
|
|
352
|
+
if (conversationId && options.conversationClient) {
|
|
353
|
+
conversation = new Conversation(
|
|
354
|
+
conversationId,
|
|
355
|
+
options.conversationClient,
|
|
356
|
+
options.agentName
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
let memory;
|
|
360
|
+
if (options.memoryClient && userId && conversationId) {
|
|
361
|
+
memory = new Memory(userId, conversationId, options.memoryClient);
|
|
362
|
+
}
|
|
363
|
+
return new _Message({
|
|
364
|
+
content: body.content ?? "",
|
|
365
|
+
conversationId,
|
|
366
|
+
userId,
|
|
367
|
+
headers,
|
|
368
|
+
rawBody: body,
|
|
369
|
+
conversation,
|
|
370
|
+
memory,
|
|
371
|
+
metadata,
|
|
372
|
+
schema,
|
|
373
|
+
attachment,
|
|
374
|
+
attachmentClient: options.attachmentClient
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Check if message has an attachment.
|
|
379
|
+
*/
|
|
380
|
+
hasAttachment() {
|
|
381
|
+
return this.attachment !== void 0;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Download attachment as bytes (Buffer).
|
|
385
|
+
*
|
|
386
|
+
* @throws Error if no attachment is available
|
|
387
|
+
*/
|
|
388
|
+
async getAttachmentBytes() {
|
|
389
|
+
if (!this.attachment) {
|
|
390
|
+
throw new Error("No attachment available");
|
|
391
|
+
}
|
|
392
|
+
if (!this.attachmentClient) {
|
|
393
|
+
throw new Error("AttachmentClient not available");
|
|
394
|
+
}
|
|
395
|
+
return this.attachmentClient.download(this.attachment.url);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get attachment as base64 string (for LLM vision APIs).
|
|
399
|
+
*
|
|
400
|
+
* @throws Error if no attachment is available
|
|
401
|
+
*/
|
|
402
|
+
async getAttachmentBase64() {
|
|
403
|
+
if (!this.attachment) {
|
|
404
|
+
throw new Error("No attachment available");
|
|
405
|
+
}
|
|
406
|
+
if (!this.attachmentClient) {
|
|
407
|
+
throw new Error("AttachmentClient not available");
|
|
408
|
+
}
|
|
409
|
+
return this.attachmentClient.downloadAsBase64(this.attachment.url);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
var ArtifactTypeSchema = z.enum([
|
|
413
|
+
"document",
|
|
414
|
+
"image",
|
|
415
|
+
"iframe",
|
|
416
|
+
"video",
|
|
417
|
+
"audio",
|
|
418
|
+
"code",
|
|
419
|
+
"link",
|
|
420
|
+
"file"
|
|
421
|
+
]);
|
|
422
|
+
var ArtifactSchema = z.object({
|
|
423
|
+
url: z.string(),
|
|
424
|
+
type: ArtifactTypeSchema,
|
|
425
|
+
title: z.string().optional(),
|
|
426
|
+
description: z.string().optional(),
|
|
427
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
428
|
+
});
|
|
429
|
+
function toArtifact(input) {
|
|
430
|
+
return ArtifactSchema.parse({
|
|
431
|
+
url: input.url,
|
|
432
|
+
type: input.type,
|
|
433
|
+
title: input.title,
|
|
434
|
+
description: input.description,
|
|
435
|
+
metadata: input.metadata
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/streamer.ts
|
|
440
|
+
var Streamer = class {
|
|
441
|
+
agentName;
|
|
442
|
+
originalMessage;
|
|
443
|
+
gatewayClient;
|
|
444
|
+
conversationClient;
|
|
445
|
+
sendTo;
|
|
446
|
+
awaiting;
|
|
447
|
+
responseSchema;
|
|
448
|
+
messageMetadata;
|
|
449
|
+
artifacts = [];
|
|
450
|
+
streamedContent = "";
|
|
451
|
+
firstChunkSent = false;
|
|
452
|
+
finished = false;
|
|
453
|
+
constructor(options) {
|
|
454
|
+
this.agentName = options.agentName;
|
|
455
|
+
this.originalMessage = options.originalMessage;
|
|
456
|
+
this.gatewayClient = options.gatewayClient;
|
|
457
|
+
this.conversationClient = options.conversationClient;
|
|
458
|
+
this.awaiting = options.awaiting ?? false;
|
|
459
|
+
this.sendTo = options.sendTo ?? "user";
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Set response schema for form rendering (fluent API).
|
|
463
|
+
*/
|
|
464
|
+
setResponseSchema(schema) {
|
|
465
|
+
this.responseSchema = schema;
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Set message metadata (fluent API).
|
|
470
|
+
*/
|
|
471
|
+
setMessageMetadata(metadata) {
|
|
472
|
+
this.messageMetadata = metadata;
|
|
473
|
+
return this;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Add an artifact to the message (fluent API).
|
|
477
|
+
*/
|
|
478
|
+
addArtifact(artifact) {
|
|
479
|
+
this.artifacts.push(toArtifact(artifact));
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Set all artifacts at once (replaces any existing).
|
|
484
|
+
*/
|
|
485
|
+
setArtifacts(artifacts) {
|
|
486
|
+
this.artifacts = artifacts.map(toArtifact);
|
|
487
|
+
return this;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Stream a content chunk.
|
|
491
|
+
*/
|
|
492
|
+
stream(content, options = {}) {
|
|
493
|
+
const { persist = true, eventType } = options;
|
|
494
|
+
if (this.finished) {
|
|
495
|
+
console.warn("Streamer.stream() called after finish()");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (persist) {
|
|
499
|
+
this.streamedContent += content;
|
|
500
|
+
}
|
|
501
|
+
this.send(content, false, persist, eventType);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Alias for stream() - write a content chunk.
|
|
505
|
+
*/
|
|
506
|
+
write(content, persist = true) {
|
|
507
|
+
this.stream(content, { persist });
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Finish streaming with done=true signal.
|
|
511
|
+
*/
|
|
512
|
+
async finish(awaitingOverride) {
|
|
513
|
+
if (this.finished) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
this.finished = true;
|
|
517
|
+
let finalAwaiting = awaitingOverride ?? this.awaiting;
|
|
518
|
+
if (this.responseSchema && awaitingOverride !== false) {
|
|
519
|
+
finalAwaiting = true;
|
|
520
|
+
}
|
|
521
|
+
this.send("", true, false);
|
|
522
|
+
if (this.responseSchema && this.conversationClient) {
|
|
523
|
+
await this.setPendingResponseSchema();
|
|
524
|
+
}
|
|
525
|
+
if (finalAwaiting && this.conversationClient) {
|
|
526
|
+
await this.setAwaitingRoute();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Send message to router via gateway.
|
|
531
|
+
*/
|
|
532
|
+
send(content, done, persist = true, eventType) {
|
|
533
|
+
const headers = {
|
|
534
|
+
conversationId: this.originalMessage.conversationId,
|
|
535
|
+
userId: this.originalMessage.userId,
|
|
536
|
+
from_: this.agentName,
|
|
537
|
+
to_: this.sendTo,
|
|
538
|
+
nextRoute: this.sendTo
|
|
539
|
+
};
|
|
540
|
+
if (eventType) {
|
|
541
|
+
headers.eventType = eventType;
|
|
542
|
+
}
|
|
543
|
+
if (!this.firstChunkSent) {
|
|
544
|
+
this.firstChunkSent = true;
|
|
545
|
+
if (this.messageMetadata) {
|
|
546
|
+
headers.messageMetadata = JSON.stringify(this.messageMetadata);
|
|
547
|
+
}
|
|
548
|
+
if (this.artifacts.length > 0) {
|
|
549
|
+
headers.artifacts = JSON.stringify(this.artifacts);
|
|
550
|
+
}
|
|
551
|
+
if (this.responseSchema) {
|
|
552
|
+
headers.responseSchema = JSON.stringify(this.responseSchema);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const body = {
|
|
556
|
+
content,
|
|
557
|
+
done,
|
|
558
|
+
persist
|
|
559
|
+
};
|
|
560
|
+
if (eventType) {
|
|
561
|
+
body.eventType = eventType;
|
|
562
|
+
}
|
|
563
|
+
this.gatewayClient.produce(
|
|
564
|
+
"router.inbox",
|
|
565
|
+
this.originalMessage.conversationId,
|
|
566
|
+
headers,
|
|
567
|
+
body
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Store response schema on conversation for form validation.
|
|
572
|
+
*/
|
|
573
|
+
async setPendingResponseSchema() {
|
|
574
|
+
if (!this.conversationClient || !this.responseSchema) return;
|
|
575
|
+
try {
|
|
576
|
+
await this.conversationClient.updateConversation(
|
|
577
|
+
this.originalMessage.conversationId,
|
|
578
|
+
{ pendingResponseSchema: this.responseSchema }
|
|
579
|
+
);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.error("Failed to set pending response schema:", error);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Set awaiting_route to this agent's name.
|
|
586
|
+
*/
|
|
587
|
+
async setAwaitingRoute() {
|
|
588
|
+
if (!this.conversationClient) return;
|
|
589
|
+
try {
|
|
590
|
+
await this.conversationClient.updateConversation(
|
|
591
|
+
this.originalMessage.conversationId,
|
|
592
|
+
{ awaitingRoute: this.agentName }
|
|
593
|
+
);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
console.error("Failed to set awaiting route:", error);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Get the accumulated streamed content.
|
|
600
|
+
*/
|
|
601
|
+
getStreamedContent() {
|
|
602
|
+
return this.streamedContent;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Support for async disposal (TypeScript 5.2+ "using" syntax).
|
|
606
|
+
*/
|
|
607
|
+
async [Symbol.asyncDispose]() {
|
|
608
|
+
if (!this.finished) {
|
|
609
|
+
await this.finish();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// src/api-paths.ts
|
|
615
|
+
var AI_INVENTORY_PATHS = {
|
|
616
|
+
/** Register a new agent */
|
|
617
|
+
AGENT_REGISTER: "/api/v1/agents/register",
|
|
618
|
+
/** Send heartbeat */
|
|
619
|
+
HEALTH_HEARTBEAT: "/api/v1/health/heartbeat"
|
|
620
|
+
};
|
|
621
|
+
var CONVERSATION_STORE_PATHS = {
|
|
622
|
+
/** Get/update conversation by ID */
|
|
623
|
+
CONVERSATION: (conversationId) => `/conversations/${conversationId}`,
|
|
624
|
+
/** Get messages for a conversation */
|
|
625
|
+
CONVERSATION_MESSAGES: (conversationId) => `/conversations/${conversationId}/messages`,
|
|
626
|
+
/** Checkpoints base path */
|
|
627
|
+
CHECKPOINTS: "/checkpoints/",
|
|
628
|
+
/** Checkpoint by thread ID */
|
|
629
|
+
CHECKPOINT_THREAD: (threadId) => `/checkpoints/${threadId}`,
|
|
630
|
+
/** Agent state by conversation ID */
|
|
631
|
+
AGENT_STATE: (conversationId) => `/agent-state/${conversationId}`
|
|
632
|
+
};
|
|
633
|
+
var MEMORY_PATHS = {
|
|
634
|
+
/** User memory base */
|
|
635
|
+
USER_MEMORY: (userId) => `/memory/${userId}`,
|
|
636
|
+
/** Add messages to memory */
|
|
637
|
+
USER_MESSAGES: (userId) => `/memory/${userId}/messages`,
|
|
638
|
+
/** Search user memory */
|
|
639
|
+
USER_SEARCH: (userId) => `/memory/${userId}/search`,
|
|
640
|
+
/** Get user summary */
|
|
641
|
+
USER_SUMMARY: (userId) => `/memory/${userId}/summary`
|
|
642
|
+
};
|
|
643
|
+
var SERVICES = {
|
|
644
|
+
AI_INVENTORY: "ai-inventory",
|
|
645
|
+
CONVERSATION_STORE: "conversation-store",
|
|
646
|
+
AI_MEMORY: "ai-memory",
|
|
647
|
+
ATTACHMENT: "attachment"
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// src/heartbeat.ts
|
|
651
|
+
var HeartbeatManager = class {
|
|
652
|
+
gatewayClient;
|
|
653
|
+
agentName;
|
|
654
|
+
intervalMs;
|
|
655
|
+
timer;
|
|
656
|
+
running = false;
|
|
657
|
+
constructor(gatewayClient, agentName, intervalSeconds = 60) {
|
|
658
|
+
this.gatewayClient = gatewayClient;
|
|
659
|
+
this.agentName = agentName;
|
|
660
|
+
this.intervalMs = intervalSeconds * 1e3;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Start sending heartbeats.
|
|
664
|
+
*/
|
|
665
|
+
start() {
|
|
666
|
+
if (this.running) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
this.running = true;
|
|
670
|
+
this.timer = setInterval(() => {
|
|
671
|
+
this.sendHeartbeat();
|
|
672
|
+
}, this.intervalMs);
|
|
673
|
+
this.sendHeartbeat();
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Stop sending heartbeats.
|
|
677
|
+
*/
|
|
678
|
+
stop() {
|
|
679
|
+
this.running = false;
|
|
680
|
+
if (this.timer) {
|
|
681
|
+
clearInterval(this.timer);
|
|
682
|
+
this.timer = void 0;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Send a single heartbeat via HTTP to AI Inventory.
|
|
687
|
+
*/
|
|
688
|
+
async sendHeartbeat() {
|
|
689
|
+
if (!this.running) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
try {
|
|
693
|
+
const response = await this.gatewayClient.httpPost(
|
|
694
|
+
SERVICES.AI_INVENTORY,
|
|
695
|
+
AI_INVENTORY_PATHS.HEALTH_HEARTBEAT,
|
|
696
|
+
{ name: this.agentName }
|
|
697
|
+
);
|
|
698
|
+
if (response.status === 404) {
|
|
699
|
+
console.warn(`Agent '${this.agentName}' not found. Re-registration may be needed.`);
|
|
700
|
+
} else if (!response.ok) {
|
|
701
|
+
console.warn(`Heartbeat returned status ${response.status}`);
|
|
702
|
+
}
|
|
703
|
+
} catch (error) {
|
|
704
|
+
console.error("Heartbeat failed:", error);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Check if heartbeat is running.
|
|
709
|
+
*/
|
|
710
|
+
isRunning() {
|
|
711
|
+
return this.running;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
// src/agent.ts
|
|
716
|
+
var SenderFilter = class {
|
|
717
|
+
_include = /* @__PURE__ */ new Set();
|
|
718
|
+
_exclude = /* @__PURE__ */ new Set();
|
|
719
|
+
matchAll;
|
|
720
|
+
constructor(senders) {
|
|
721
|
+
if (!senders || senders.length === 0) {
|
|
722
|
+
this.matchAll = true;
|
|
723
|
+
} else {
|
|
724
|
+
this.matchAll = false;
|
|
725
|
+
for (const sender of senders) {
|
|
726
|
+
if (sender.startsWith("~")) {
|
|
727
|
+
this._exclude.add(sender.slice(1));
|
|
728
|
+
} else {
|
|
729
|
+
this._include.add(sender);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Get included senders as an array (for compatibility with Python tests).
|
|
736
|
+
*/
|
|
737
|
+
get include() {
|
|
738
|
+
return Array.from(this._include);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Get excluded senders as an array (for compatibility with Python tests).
|
|
742
|
+
*/
|
|
743
|
+
get exclude() {
|
|
744
|
+
return Array.from(this._exclude);
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Check if sender matches this filter.
|
|
748
|
+
*/
|
|
749
|
+
matches(sender) {
|
|
750
|
+
if (this._exclude.has(sender)) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
if (this.matchAll || this._include.size === 0) {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
return this._include.has(sender);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
var Agent = class {
|
|
760
|
+
name;
|
|
761
|
+
agentData;
|
|
762
|
+
sendErrorResponses = true;
|
|
763
|
+
errorMessageTemplate;
|
|
764
|
+
gatewayClient;
|
|
765
|
+
conversationClient;
|
|
766
|
+
memoryClient;
|
|
767
|
+
attachmentClient;
|
|
768
|
+
heartbeatInterval;
|
|
769
|
+
messageHandlers = [];
|
|
770
|
+
heartbeatManager;
|
|
771
|
+
initialized = false;
|
|
772
|
+
running = false;
|
|
773
|
+
constructor(options) {
|
|
774
|
+
this.name = options.name;
|
|
775
|
+
this.gatewayClient = options.gatewayClient;
|
|
776
|
+
this.agentData = options.agentData;
|
|
777
|
+
this.heartbeatInterval = options.heartbeatInterval ?? 60;
|
|
778
|
+
this.conversationClient = options.conversationClient;
|
|
779
|
+
this.memoryClient = options.memoryClient;
|
|
780
|
+
this.attachmentClient = options.attachmentClient;
|
|
781
|
+
this.errorMessageTemplate = options.errorMessageTemplate ?? "I encountered an error while processing your message. Please try again or contact support if the issue persists.";
|
|
782
|
+
}
|
|
783
|
+
onMessage(handler, options) {
|
|
784
|
+
const filter = new SenderFilter(options?.senders);
|
|
785
|
+
this.messageHandlers.push([filter, handler]);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Initialize agent after gateway connection is established.
|
|
789
|
+
*/
|
|
790
|
+
initializeWithGateway() {
|
|
791
|
+
if (this.initialized) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const topic = `${this.name}.inbox`;
|
|
795
|
+
this.gatewayClient.registerHandler(topic, (msg) => {
|
|
796
|
+
this.handleKafkaMessage(msg);
|
|
797
|
+
});
|
|
798
|
+
this.heartbeatManager = new HeartbeatManager(
|
|
799
|
+
this.gatewayClient,
|
|
800
|
+
this.name,
|
|
801
|
+
this.heartbeatInterval
|
|
802
|
+
);
|
|
803
|
+
this.heartbeatManager.start();
|
|
804
|
+
this.initialized = true;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Get sender from message headers.
|
|
808
|
+
*/
|
|
809
|
+
getSender(headers) {
|
|
810
|
+
return headers.from_ ?? headers.from ?? "user";
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Find first handler that matches the sender.
|
|
814
|
+
*/
|
|
815
|
+
findMatchingHandler(sender) {
|
|
816
|
+
for (const [filter, handler] of this.messageHandlers) {
|
|
817
|
+
if (filter.matches(sender)) {
|
|
818
|
+
return handler;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return void 0;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Handle incoming Kafka message.
|
|
825
|
+
*/
|
|
826
|
+
handleKafkaMessage(kafkaMsg) {
|
|
827
|
+
this.handleMessageAsync(kafkaMsg).catch((error) => {
|
|
828
|
+
console.error("Error in message handler:", error);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Async message handling with error recovery.
|
|
833
|
+
*/
|
|
834
|
+
async handleMessageAsync(kafkaMsg) {
|
|
835
|
+
let message;
|
|
836
|
+
try {
|
|
837
|
+
message = Message.fromKafkaMessage(
|
|
838
|
+
kafkaMsg.body,
|
|
839
|
+
kafkaMsg.headers,
|
|
840
|
+
{
|
|
841
|
+
conversationClient: this.conversationClient,
|
|
842
|
+
memoryClient: this.memoryClient,
|
|
843
|
+
attachmentClient: this.attachmentClient,
|
|
844
|
+
agentName: this.name
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
const sender = this.getSender(kafkaMsg.headers);
|
|
848
|
+
const handler = this.findMatchingHandler(sender);
|
|
849
|
+
if (!handler) {
|
|
850
|
+
console.warn(`No handler matched for sender: ${sender}`);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
await handler(message, sender);
|
|
854
|
+
} catch (error) {
|
|
855
|
+
console.error("Error handling message:", error);
|
|
856
|
+
if (message && this.sendErrorResponses) {
|
|
857
|
+
await this.sendErrorResponse(message, error);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Send error response to user when handler fails.
|
|
863
|
+
*/
|
|
864
|
+
async sendErrorResponse(message, _error) {
|
|
865
|
+
try {
|
|
866
|
+
const streamer = this.streamer(message, { sendTo: "user" });
|
|
867
|
+
streamer.stream(this.errorMessageTemplate);
|
|
868
|
+
await streamer.finish();
|
|
869
|
+
} catch (err) {
|
|
870
|
+
console.error("Failed to send error response:", err);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Create a new Streamer for streaming responses.
|
|
875
|
+
*/
|
|
876
|
+
streamer(message, options = {}) {
|
|
877
|
+
return new Streamer({
|
|
878
|
+
agentName: this.name,
|
|
879
|
+
originalMessage: message,
|
|
880
|
+
gatewayClient: this.gatewayClient,
|
|
881
|
+
conversationClient: this.conversationClient,
|
|
882
|
+
awaiting: options.awaiting ?? false,
|
|
883
|
+
sendTo: options.sendTo ?? "user"
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Mark agent as ready to consume messages.
|
|
888
|
+
*/
|
|
889
|
+
startConsuming() {
|
|
890
|
+
if (this.messageHandlers.length === 0) {
|
|
891
|
+
throw new ConfigurationError("No message handlers registered");
|
|
892
|
+
}
|
|
893
|
+
if (!this.initialized) {
|
|
894
|
+
throw new ConfigurationError("Agent not initialized with gateway");
|
|
895
|
+
}
|
|
896
|
+
this.running = true;
|
|
897
|
+
console.log(`Agent ${this.name} is now consuming messages`);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Shutdown agent gracefully.
|
|
901
|
+
*/
|
|
902
|
+
shutdown() {
|
|
903
|
+
this.running = false;
|
|
904
|
+
if (this.heartbeatManager) {
|
|
905
|
+
this.heartbeatManager.stop();
|
|
906
|
+
}
|
|
907
|
+
console.log(`Agent ${this.name} shutdown`);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Check if agent is running.
|
|
911
|
+
*/
|
|
912
|
+
isRunning() {
|
|
913
|
+
return this.running;
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
var __filename$1 = fileURLToPath(import.meta.url);
|
|
917
|
+
var __dirname$1 = dirname(__filename$1);
|
|
918
|
+
var PROTO_PATH = join(__dirname$1, "proto", "gateway.proto");
|
|
919
|
+
var packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
|
920
|
+
keepCase: true,
|
|
921
|
+
longs: String,
|
|
922
|
+
enums: String,
|
|
923
|
+
defaults: true,
|
|
924
|
+
oneofs: true
|
|
925
|
+
});
|
|
926
|
+
var gatewayProto = grpc.loadPackageDefinition(packageDefinition);
|
|
927
|
+
var GatewayClient = class {
|
|
928
|
+
gatewayUrl;
|
|
929
|
+
apiKey;
|
|
930
|
+
secure;
|
|
931
|
+
client;
|
|
932
|
+
stream;
|
|
933
|
+
_connectionId;
|
|
934
|
+
messageQueue = [];
|
|
935
|
+
pendingProduceAcks = /* @__PURE__ */ new Map();
|
|
936
|
+
pendingSubscribeAcks = /* @__PURE__ */ new Map();
|
|
937
|
+
messageHandlers = /* @__PURE__ */ new Map();
|
|
938
|
+
correlationCounter = 0;
|
|
939
|
+
connected = false;
|
|
940
|
+
constructor(gatewayUrl, apiKey, secure = false) {
|
|
941
|
+
this.gatewayUrl = gatewayUrl;
|
|
942
|
+
this.apiKey = apiKey;
|
|
943
|
+
this.secure = secure;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* HTTP URL for AI Inventory service via proxy.
|
|
947
|
+
*/
|
|
948
|
+
get aiInventoryUrl() {
|
|
949
|
+
const protocol = this.secure ? "https" : "http";
|
|
950
|
+
return `${protocol}://${this.gatewayUrl}/s/ai-inventory`;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* HTTP URL for Conversation Store service via proxy.
|
|
954
|
+
*/
|
|
955
|
+
get conversationStoreUrl() {
|
|
956
|
+
const protocol = this.secure ? "https" : "http";
|
|
957
|
+
return `${protocol}://${this.gatewayUrl}/s/conversation-store`;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* HTTP URL for AI Memory service via proxy.
|
|
961
|
+
*/
|
|
962
|
+
get aiMemoryUrl() {
|
|
963
|
+
const protocol = this.secure ? "https" : "http";
|
|
964
|
+
return `${protocol}://${this.gatewayUrl}/s/ai-memory`;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* HTTP URL for Attachment service via proxy.
|
|
968
|
+
*/
|
|
969
|
+
get attachmentUrl() {
|
|
970
|
+
const protocol = this.secure ? "https" : "http";
|
|
971
|
+
return `${protocol}://${this.gatewayUrl}/s/attachment`;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Register a handler for a topic.
|
|
975
|
+
*/
|
|
976
|
+
registerHandler(topic, handler) {
|
|
977
|
+
this.messageHandlers.set(topic, handler);
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Connect to the gateway and authenticate.
|
|
981
|
+
*/
|
|
982
|
+
async connect(agentNames) {
|
|
983
|
+
const credentials2 = this.secure ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
|
984
|
+
this.client = new gatewayProto.gateway.AgentGateway(
|
|
985
|
+
this.gatewayUrl,
|
|
986
|
+
credentials2
|
|
987
|
+
);
|
|
988
|
+
this.stream = this.client.AgentStream();
|
|
989
|
+
this.stream.on("data", (msg) => {
|
|
990
|
+
this.handleServerMessage(msg);
|
|
991
|
+
});
|
|
992
|
+
this.stream.on("error", (err) => {
|
|
993
|
+
console.error("Gateway stream error:", err);
|
|
994
|
+
this.connected = false;
|
|
995
|
+
});
|
|
996
|
+
this.stream.on("end", () => {
|
|
997
|
+
console.log("Gateway stream ended");
|
|
998
|
+
this.connected = false;
|
|
999
|
+
});
|
|
1000
|
+
return new Promise((resolve, reject) => {
|
|
1001
|
+
const timeout = setTimeout(() => {
|
|
1002
|
+
reject(new GatewayError("Authentication timeout"));
|
|
1003
|
+
}, 1e4);
|
|
1004
|
+
const authHandler = (msg) => {
|
|
1005
|
+
if (msg.auth_response) {
|
|
1006
|
+
clearTimeout(timeout);
|
|
1007
|
+
this._connectionId = msg.auth_response.connection_id;
|
|
1008
|
+
this.connected = true;
|
|
1009
|
+
resolve(msg.auth_response.subscribed_topics);
|
|
1010
|
+
} else if (msg.error) {
|
|
1011
|
+
clearTimeout(timeout);
|
|
1012
|
+
reject(new GatewayError(`Authentication failed: ${msg.error.message}`));
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
const originalHandler = this.handleServerMessage.bind(this);
|
|
1016
|
+
this.handleServerMessage = (msg) => {
|
|
1017
|
+
if (msg.auth_response || msg.error) {
|
|
1018
|
+
authHandler(msg);
|
|
1019
|
+
this.handleServerMessage = originalHandler;
|
|
1020
|
+
} else {
|
|
1021
|
+
originalHandler(msg);
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
this.stream.write({
|
|
1025
|
+
auth: {
|
|
1026
|
+
api_key: this.apiKey,
|
|
1027
|
+
agent_names: agentNames
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Handle incoming server messages.
|
|
1034
|
+
*/
|
|
1035
|
+
handleServerMessage(msg) {
|
|
1036
|
+
if (msg.message) {
|
|
1037
|
+
const kafkaMsg = {
|
|
1038
|
+
topic: msg.message.topic,
|
|
1039
|
+
partition: msg.message.partition,
|
|
1040
|
+
offset: parseInt(msg.message.offset, 10),
|
|
1041
|
+
key: msg.message.key,
|
|
1042
|
+
headers: msg.message.headers,
|
|
1043
|
+
body: JSON.parse(msg.message.body.toString()),
|
|
1044
|
+
timestamp: parseInt(msg.message.timestamp, 10)
|
|
1045
|
+
};
|
|
1046
|
+
const handler = this.messageHandlers.get(kafkaMsg.topic);
|
|
1047
|
+
if (handler) {
|
|
1048
|
+
handler(kafkaMsg);
|
|
1049
|
+
} else {
|
|
1050
|
+
this.messageQueue.push(kafkaMsg);
|
|
1051
|
+
}
|
|
1052
|
+
} else if (msg.produce_ack) {
|
|
1053
|
+
const callback = this.pendingProduceAcks.get(msg.produce_ack.correlation_id);
|
|
1054
|
+
if (callback) {
|
|
1055
|
+
callback({
|
|
1056
|
+
topic: msg.produce_ack.topic,
|
|
1057
|
+
partition: msg.produce_ack.partition,
|
|
1058
|
+
offset: parseInt(msg.produce_ack.offset, 10),
|
|
1059
|
+
correlationId: msg.produce_ack.correlation_id
|
|
1060
|
+
});
|
|
1061
|
+
this.pendingProduceAcks.delete(msg.produce_ack.correlation_id);
|
|
1062
|
+
}
|
|
1063
|
+
} else if (msg.subscribe_ack) {
|
|
1064
|
+
const callback = this.pendingSubscribeAcks.get(msg.subscribe_ack.topic);
|
|
1065
|
+
if (callback) {
|
|
1066
|
+
callback(msg.subscribe_ack.topic);
|
|
1067
|
+
this.pendingSubscribeAcks.delete(msg.subscribe_ack.topic);
|
|
1068
|
+
}
|
|
1069
|
+
} else if (msg.error) {
|
|
1070
|
+
console.error("Gateway error:", msg.error.message);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Subscribe to an additional agent's topic.
|
|
1075
|
+
*/
|
|
1076
|
+
async subscribe(agentName) {
|
|
1077
|
+
if (!this.stream || !this.connected) {
|
|
1078
|
+
throw new GatewayError("Not connected");
|
|
1079
|
+
}
|
|
1080
|
+
return new Promise((resolve, reject) => {
|
|
1081
|
+
const timeout = setTimeout(() => {
|
|
1082
|
+
reject(new GatewayError("Subscribe timeout"));
|
|
1083
|
+
}, 5e3);
|
|
1084
|
+
const expectedTopic = `${agentName}.inbox`;
|
|
1085
|
+
this.pendingSubscribeAcks.set(expectedTopic, (topic) => {
|
|
1086
|
+
clearTimeout(timeout);
|
|
1087
|
+
resolve(topic);
|
|
1088
|
+
});
|
|
1089
|
+
this.stream.write({
|
|
1090
|
+
subscribe: { agent_name: agentName }
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Unsubscribe from an agent's topic.
|
|
1096
|
+
*/
|
|
1097
|
+
unsubscribe(agentName) {
|
|
1098
|
+
if (!this.stream || !this.connected) {
|
|
1099
|
+
throw new GatewayError("Not connected");
|
|
1100
|
+
}
|
|
1101
|
+
this.stream.write({
|
|
1102
|
+
unsubscribe: { agent_name: agentName }
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Produce a message to Kafka via the gateway.
|
|
1107
|
+
*/
|
|
1108
|
+
produce(topic, key, headers, body, correlationId) {
|
|
1109
|
+
if (!this.stream || !this.connected) {
|
|
1110
|
+
throw new GatewayError("Not connected");
|
|
1111
|
+
}
|
|
1112
|
+
const corrId = correlationId ?? `${++this.correlationCounter}`;
|
|
1113
|
+
this.stream.write({
|
|
1114
|
+
produce: {
|
|
1115
|
+
topic,
|
|
1116
|
+
key,
|
|
1117
|
+
headers,
|
|
1118
|
+
body: Buffer.from(JSON.stringify(body)),
|
|
1119
|
+
correlation_id: corrId
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Produce a message and wait for acknowledgment.
|
|
1125
|
+
*/
|
|
1126
|
+
async produceAndWait(topic, key, headers, body) {
|
|
1127
|
+
if (!this.stream || !this.connected) {
|
|
1128
|
+
throw new GatewayError("Not connected");
|
|
1129
|
+
}
|
|
1130
|
+
const correlationId = `${++this.correlationCounter}`;
|
|
1131
|
+
return new Promise((resolve, reject) => {
|
|
1132
|
+
const timeout = setTimeout(() => {
|
|
1133
|
+
this.pendingProduceAcks.delete(correlationId);
|
|
1134
|
+
reject(new GatewayError("Produce timeout"));
|
|
1135
|
+
}, 1e4);
|
|
1136
|
+
this.pendingProduceAcks.set(correlationId, (ack) => {
|
|
1137
|
+
clearTimeout(timeout);
|
|
1138
|
+
resolve(ack);
|
|
1139
|
+
});
|
|
1140
|
+
this.stream.write({
|
|
1141
|
+
produce: {
|
|
1142
|
+
topic,
|
|
1143
|
+
key,
|
|
1144
|
+
headers,
|
|
1145
|
+
body: Buffer.from(JSON.stringify(body)),
|
|
1146
|
+
correlation_id: correlationId
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Consume a single message (polling from queue).
|
|
1153
|
+
*/
|
|
1154
|
+
consumeOne(_timeout = 1e3) {
|
|
1155
|
+
if (this.messageQueue.length > 0) {
|
|
1156
|
+
return this.messageQueue.shift();
|
|
1157
|
+
}
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Send a ping to the gateway.
|
|
1162
|
+
*/
|
|
1163
|
+
ping() {
|
|
1164
|
+
if (!this.stream || !this.connected) {
|
|
1165
|
+
throw new GatewayError("Not connected");
|
|
1166
|
+
}
|
|
1167
|
+
this.stream.write({
|
|
1168
|
+
ping: { timestamp: String(Date.now()) }
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Make a sync POST request (used for registration).
|
|
1173
|
+
*/
|
|
1174
|
+
async httpPost(service, path, body) {
|
|
1175
|
+
const protocol = this.secure ? "https" : "http";
|
|
1176
|
+
const url = `${protocol}://${this.gatewayUrl}/s/${service}${path}`;
|
|
1177
|
+
return fetch(url, {
|
|
1178
|
+
method: "POST",
|
|
1179
|
+
headers: {
|
|
1180
|
+
"Content-Type": "application/json",
|
|
1181
|
+
"X-API-Key": this.apiKey
|
|
1182
|
+
},
|
|
1183
|
+
body: JSON.stringify(body)
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Check if connected.
|
|
1188
|
+
*/
|
|
1189
|
+
isConnected() {
|
|
1190
|
+
return this.connected;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Close the gateway connection.
|
|
1194
|
+
*/
|
|
1195
|
+
close() {
|
|
1196
|
+
if (this.stream) {
|
|
1197
|
+
this.stream.end();
|
|
1198
|
+
this.stream = void 0;
|
|
1199
|
+
}
|
|
1200
|
+
this.connected = false;
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
// src/conversation-client.ts
|
|
1205
|
+
var ConversationClient = class {
|
|
1206
|
+
baseUrl;
|
|
1207
|
+
constructor(baseUrl) {
|
|
1208
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Get conversation metadata.
|
|
1212
|
+
*/
|
|
1213
|
+
async getConversation(conversationId) {
|
|
1214
|
+
const url = `${this.baseUrl}/conversations/${conversationId}`;
|
|
1215
|
+
try {
|
|
1216
|
+
const response = await fetch(url);
|
|
1217
|
+
if (response.status === 404) {
|
|
1218
|
+
throw new APIException(`Conversation ${conversationId} not found`, 404);
|
|
1219
|
+
}
|
|
1220
|
+
if (!response.ok) {
|
|
1221
|
+
throw new APIException(`Failed to fetch conversation: ${response.status}`, response.status);
|
|
1222
|
+
}
|
|
1223
|
+
return await response.json();
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (error instanceof APIException) throw error;
|
|
1226
|
+
throw new APIException(`Failed to fetch conversation: ${error}`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Get messages from a conversation.
|
|
1231
|
+
*/
|
|
1232
|
+
async getMessages(conversationId, options = {}) {
|
|
1233
|
+
const url = new URL(`${this.baseUrl}/conversations/${conversationId}/messages`);
|
|
1234
|
+
const params = {
|
|
1235
|
+
limit: String(Math.min(options.limit ?? 100, 1e3)),
|
|
1236
|
+
offset: String(options.offset ?? 0)
|
|
1237
|
+
};
|
|
1238
|
+
if (options.role) params.role = options.role;
|
|
1239
|
+
if (options.from) params.from = options.from;
|
|
1240
|
+
if (options.to) params.to = options.to;
|
|
1241
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1242
|
+
url.searchParams.set(key, value);
|
|
1243
|
+
});
|
|
1244
|
+
try {
|
|
1245
|
+
const response = await fetch(url.toString());
|
|
1246
|
+
if (response.status === 404) {
|
|
1247
|
+
throw new APIException(`Conversation ${conversationId} not found`, 404);
|
|
1248
|
+
}
|
|
1249
|
+
if (!response.ok) {
|
|
1250
|
+
throw new APIException(`Failed to fetch messages: ${response.status}`, response.status);
|
|
1251
|
+
}
|
|
1252
|
+
return await response.json();
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
if (error instanceof APIException) throw error;
|
|
1255
|
+
throw new APIException(`Failed to fetch messages: ${error}`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Update conversation fields (PATCH).
|
|
1260
|
+
*/
|
|
1261
|
+
async updateConversation(conversationId, data) {
|
|
1262
|
+
const url = `${this.baseUrl}/conversations/${conversationId}`;
|
|
1263
|
+
try {
|
|
1264
|
+
const response = await fetch(url, {
|
|
1265
|
+
method: "PATCH",
|
|
1266
|
+
headers: { "Content-Type": "application/json" },
|
|
1267
|
+
body: JSON.stringify(data)
|
|
1268
|
+
});
|
|
1269
|
+
if (!response.ok) {
|
|
1270
|
+
throw new APIException(`Failed to update conversation: ${response.status}`, response.status);
|
|
1271
|
+
}
|
|
1272
|
+
return await response.json();
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
if (error instanceof APIException) throw error;
|
|
1275
|
+
throw new APIException(`Failed to update conversation: ${error}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
// src/memory-client.ts
|
|
1281
|
+
var MemoryClient = class {
|
|
1282
|
+
baseUrl;
|
|
1283
|
+
constructor(baseUrl) {
|
|
1284
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Add messages to memory for a user/conversation.
|
|
1288
|
+
*/
|
|
1289
|
+
async addMessages(userId, conversationId, messages) {
|
|
1290
|
+
const url = `${this.baseUrl}/memory/${userId}/messages`;
|
|
1291
|
+
const payload = {
|
|
1292
|
+
conversation_id: conversationId,
|
|
1293
|
+
messages
|
|
1294
|
+
};
|
|
1295
|
+
try {
|
|
1296
|
+
const response = await fetch(url, {
|
|
1297
|
+
method: "POST",
|
|
1298
|
+
headers: { "Content-Type": "application/json" },
|
|
1299
|
+
body: JSON.stringify(payload)
|
|
1300
|
+
});
|
|
1301
|
+
if (!response.ok) {
|
|
1302
|
+
const errorText = await response.text();
|
|
1303
|
+
throw new APIException(`Failed to add messages to memory: ${response.status} - ${errorText}`, response.status);
|
|
1304
|
+
}
|
|
1305
|
+
return await response.json();
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
if (error instanceof APIException) throw error;
|
|
1308
|
+
throw new APIException(`Failed to add messages to memory: ${error}`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Search memory for relevant content.
|
|
1313
|
+
*/
|
|
1314
|
+
async search(userId, query, limit = 10) {
|
|
1315
|
+
const url = new URL(`${this.baseUrl}/memory/${userId}/search`);
|
|
1316
|
+
url.searchParams.set("query", query);
|
|
1317
|
+
url.searchParams.set("limit", String(limit));
|
|
1318
|
+
try {
|
|
1319
|
+
const response = await fetch(url.toString());
|
|
1320
|
+
if (!response.ok) {
|
|
1321
|
+
const errorText = await response.text();
|
|
1322
|
+
throw new APIException(`Failed to search memory: ${response.status} - ${errorText}`, response.status);
|
|
1323
|
+
}
|
|
1324
|
+
return await response.json();
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
if (error instanceof APIException) throw error;
|
|
1327
|
+
throw new APIException(`Failed to search memory: ${error}`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Get user summary.
|
|
1332
|
+
*/
|
|
1333
|
+
async getUserSummary(userId) {
|
|
1334
|
+
const url = `${this.baseUrl}/memory/${userId}/summary`;
|
|
1335
|
+
try {
|
|
1336
|
+
const response = await fetch(url);
|
|
1337
|
+
if (response.status === 404) {
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
if (!response.ok) {
|
|
1341
|
+
const errorText = await response.text();
|
|
1342
|
+
throw new APIException(`Failed to get user summary: ${response.status} - ${errorText}`, response.status);
|
|
1343
|
+
}
|
|
1344
|
+
return await response.json();
|
|
1345
|
+
} catch (error) {
|
|
1346
|
+
if (error instanceof APIException) throw error;
|
|
1347
|
+
throw new APIException(`Failed to get user summary: ${error}`);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Clear user's memory.
|
|
1352
|
+
*/
|
|
1353
|
+
async clearMemory(userId) {
|
|
1354
|
+
const url = `${this.baseUrl}/memory/${userId}`;
|
|
1355
|
+
try {
|
|
1356
|
+
const response = await fetch(url, { method: "DELETE" });
|
|
1357
|
+
if (!response.ok) {
|
|
1358
|
+
const errorText = await response.text();
|
|
1359
|
+
throw new APIException(`Failed to clear memory: ${response.status} - ${errorText}`, response.status);
|
|
1360
|
+
}
|
|
1361
|
+
return await response.json();
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
if (error instanceof APIException) throw error;
|
|
1364
|
+
throw new APIException(`Failed to clear memory: ${error}`);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
// src/app.ts
|
|
1370
|
+
var MarchAgentApp = class {
|
|
1371
|
+
gatewayClient;
|
|
1372
|
+
conversationClient;
|
|
1373
|
+
memoryClient;
|
|
1374
|
+
attachmentClient;
|
|
1375
|
+
heartbeatInterval;
|
|
1376
|
+
_maxConcurrentTasks;
|
|
1377
|
+
errorMessageTemplate;
|
|
1378
|
+
agents = [];
|
|
1379
|
+
running = false;
|
|
1380
|
+
shutdownRequested = false;
|
|
1381
|
+
constructor(options) {
|
|
1382
|
+
this.heartbeatInterval = options.heartbeatInterval ?? 60;
|
|
1383
|
+
this._maxConcurrentTasks = options.maxConcurrentTasks ?? 100;
|
|
1384
|
+
this.errorMessageTemplate = options.errorMessageTemplate ?? "I encountered an error while processing your message. Please try again or contact support if the issue persists.";
|
|
1385
|
+
this.gatewayClient = new GatewayClient(
|
|
1386
|
+
options.gatewayUrl,
|
|
1387
|
+
options.apiKey,
|
|
1388
|
+
options.secure ?? false
|
|
1389
|
+
);
|
|
1390
|
+
this.conversationClient = new ConversationClient(
|
|
1391
|
+
this.gatewayClient.conversationStoreUrl
|
|
1392
|
+
);
|
|
1393
|
+
this.memoryClient = new MemoryClient(
|
|
1394
|
+
this.gatewayClient.aiMemoryUrl
|
|
1395
|
+
);
|
|
1396
|
+
this.attachmentClient = new AttachmentClient(
|
|
1397
|
+
this.gatewayClient.attachmentUrl
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Register an agent with the backend.
|
|
1402
|
+
*/
|
|
1403
|
+
async registerMe(options) {
|
|
1404
|
+
const agentData = await this.registerWithInventory(options);
|
|
1405
|
+
const agent = new Agent({
|
|
1406
|
+
name: options.name,
|
|
1407
|
+
gatewayClient: this.gatewayClient,
|
|
1408
|
+
agentData,
|
|
1409
|
+
heartbeatInterval: this.heartbeatInterval,
|
|
1410
|
+
conversationClient: this.conversationClient,
|
|
1411
|
+
memoryClient: this.memoryClient,
|
|
1412
|
+
attachmentClient: this.attachmentClient,
|
|
1413
|
+
errorMessageTemplate: this.errorMessageTemplate
|
|
1414
|
+
});
|
|
1415
|
+
this.agents.push(agent);
|
|
1416
|
+
console.log(`Registered agent: ${options.name}`);
|
|
1417
|
+
return agent;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Register agent with AI Inventory service.
|
|
1421
|
+
*/
|
|
1422
|
+
async registerWithInventory(options) {
|
|
1423
|
+
const payload = {
|
|
1424
|
+
name: options.name,
|
|
1425
|
+
about: options.about,
|
|
1426
|
+
document: options.document,
|
|
1427
|
+
representationName: options.representationName || options.name
|
|
1428
|
+
};
|
|
1429
|
+
if (options.baseUrl) {
|
|
1430
|
+
payload.baseUrl = options.baseUrl;
|
|
1431
|
+
}
|
|
1432
|
+
if (options.metadata) {
|
|
1433
|
+
payload.metadata = options.metadata;
|
|
1434
|
+
}
|
|
1435
|
+
if (options.relatedPages) {
|
|
1436
|
+
payload.relatedPages = options.relatedPages;
|
|
1437
|
+
}
|
|
1438
|
+
try {
|
|
1439
|
+
const response = await this.gatewayClient.httpPost(
|
|
1440
|
+
SERVICES.AI_INVENTORY,
|
|
1441
|
+
AI_INVENTORY_PATHS.AGENT_REGISTER,
|
|
1442
|
+
payload
|
|
1443
|
+
);
|
|
1444
|
+
if (!response.ok) {
|
|
1445
|
+
const errorText = await response.text();
|
|
1446
|
+
console.error("Registration failed:", response.status, errorText);
|
|
1447
|
+
throw new RegistrationError(
|
|
1448
|
+
`Failed to register agent ${options.name}: ${response.status}`
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
const data = await response.json();
|
|
1452
|
+
return {
|
|
1453
|
+
id: data.id,
|
|
1454
|
+
name: data.name,
|
|
1455
|
+
about: data.about,
|
|
1456
|
+
document: data.document,
|
|
1457
|
+
representationName: data.representationName,
|
|
1458
|
+
baseUrl: data.baseUrl,
|
|
1459
|
+
metadata: data.metadata,
|
|
1460
|
+
relatedPages: data.relatedPages
|
|
1461
|
+
};
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
if (error instanceof RegistrationError) throw error;
|
|
1464
|
+
throw new RegistrationError(`Failed to register agent ${options.name}: ${error}`);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Start all registered agents and block until shutdown.
|
|
1469
|
+
*/
|
|
1470
|
+
async run() {
|
|
1471
|
+
if (this.agents.length === 0) {
|
|
1472
|
+
throw new ConfigurationError("No agents registered");
|
|
1473
|
+
}
|
|
1474
|
+
const agentNames = this.agents.map((a) => a.name);
|
|
1475
|
+
console.log(`Connecting to gateway with agents: ${agentNames.join(", ")}`);
|
|
1476
|
+
try {
|
|
1477
|
+
const topics = await this.gatewayClient.connect(agentNames);
|
|
1478
|
+
console.log(`Connected. Subscribed to topics: ${topics.join(", ")}`);
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
throw new ConfigurationError(`Failed to connect to gateway: ${error}`);
|
|
1481
|
+
}
|
|
1482
|
+
for (const agent of this.agents) {
|
|
1483
|
+
agent.initializeWithGateway();
|
|
1484
|
+
agent.startConsuming();
|
|
1485
|
+
}
|
|
1486
|
+
this.running = true;
|
|
1487
|
+
console.log("Agent app is running. Press Ctrl+C to stop.");
|
|
1488
|
+
process.on("SIGINT", () => this.shutdown());
|
|
1489
|
+
process.on("SIGTERM", () => this.shutdown());
|
|
1490
|
+
await this.consumeLoop();
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Main consume loop.
|
|
1494
|
+
*/
|
|
1495
|
+
async consumeLoop() {
|
|
1496
|
+
while (this.running && !this.shutdownRequested) {
|
|
1497
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Shutdown all agents gracefully.
|
|
1502
|
+
*/
|
|
1503
|
+
shutdown() {
|
|
1504
|
+
if (this.shutdownRequested) {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
console.log("\nShutting down...");
|
|
1508
|
+
this.shutdownRequested = true;
|
|
1509
|
+
this.running = false;
|
|
1510
|
+
for (const agent of this.agents) {
|
|
1511
|
+
agent.shutdown();
|
|
1512
|
+
}
|
|
1513
|
+
this.gatewayClient.close();
|
|
1514
|
+
console.log("Shutdown complete");
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
|
|
1518
|
+
// src/checkpoint-client.ts
|
|
1519
|
+
var CheckpointClient = class {
|
|
1520
|
+
baseUrl;
|
|
1521
|
+
constructor(baseUrl) {
|
|
1522
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Store a checkpoint.
|
|
1526
|
+
*/
|
|
1527
|
+
async put(config, checkpoint, metadata, newVersions = {}) {
|
|
1528
|
+
const url = `${this.baseUrl}/checkpoints/`;
|
|
1529
|
+
const payload = {
|
|
1530
|
+
config,
|
|
1531
|
+
checkpoint,
|
|
1532
|
+
metadata,
|
|
1533
|
+
new_versions: newVersions
|
|
1534
|
+
};
|
|
1535
|
+
try {
|
|
1536
|
+
const response = await fetch(url, {
|
|
1537
|
+
method: "PUT",
|
|
1538
|
+
headers: { "Content-Type": "application/json" },
|
|
1539
|
+
body: JSON.stringify(payload)
|
|
1540
|
+
});
|
|
1541
|
+
if (!response.ok) {
|
|
1542
|
+
const errorText = await response.text();
|
|
1543
|
+
throw new APIException(`Failed to store checkpoint: ${response.status} - ${errorText}`, response.status);
|
|
1544
|
+
}
|
|
1545
|
+
return await response.json();
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
if (error instanceof APIException) throw error;
|
|
1548
|
+
throw new APIException(`Failed to store checkpoint: ${error}`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Get a checkpoint tuple.
|
|
1553
|
+
*/
|
|
1554
|
+
async getTuple(threadId, checkpointNs = "", checkpointId) {
|
|
1555
|
+
const url = new URL(`${this.baseUrl}/checkpoints/${threadId}`);
|
|
1556
|
+
url.searchParams.set("checkpoint_ns", checkpointNs);
|
|
1557
|
+
if (checkpointId) {
|
|
1558
|
+
url.searchParams.set("checkpoint_id", checkpointId);
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
const response = await fetch(url.toString());
|
|
1562
|
+
if (response.status === 404) {
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
if (!response.ok) {
|
|
1566
|
+
const errorText = await response.text();
|
|
1567
|
+
throw new APIException(`Failed to get checkpoint: ${response.status} - ${errorText}`, response.status);
|
|
1568
|
+
}
|
|
1569
|
+
const result = await response.json();
|
|
1570
|
+
return result || null;
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
if (error instanceof APIException) throw error;
|
|
1573
|
+
throw new APIException(`Failed to get checkpoint: ${error}`);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* List checkpoints.
|
|
1578
|
+
*/
|
|
1579
|
+
async list(options = {}) {
|
|
1580
|
+
const url = new URL(`${this.baseUrl}/checkpoints/`);
|
|
1581
|
+
if (options.threadId) url.searchParams.set("thread_id", options.threadId);
|
|
1582
|
+
if (options.checkpointNs !== void 0) url.searchParams.set("checkpoint_ns", options.checkpointNs);
|
|
1583
|
+
if (options.before) url.searchParams.set("before", options.before);
|
|
1584
|
+
if (options.limit) url.searchParams.set("limit", String(options.limit));
|
|
1585
|
+
try {
|
|
1586
|
+
const response = await fetch(url.toString());
|
|
1587
|
+
if (!response.ok) {
|
|
1588
|
+
const errorText = await response.text();
|
|
1589
|
+
throw new APIException(`Failed to list checkpoints: ${response.status} - ${errorText}`, response.status);
|
|
1590
|
+
}
|
|
1591
|
+
return await response.json();
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
if (error instanceof APIException) throw error;
|
|
1594
|
+
throw new APIException(`Failed to list checkpoints: ${error}`);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Delete all checkpoints for a thread.
|
|
1599
|
+
*/
|
|
1600
|
+
async deleteThread(threadId) {
|
|
1601
|
+
const url = `${this.baseUrl}/checkpoints/${threadId}`;
|
|
1602
|
+
try {
|
|
1603
|
+
const response = await fetch(url, { method: "DELETE" });
|
|
1604
|
+
if (!response.ok) {
|
|
1605
|
+
const errorText = await response.text();
|
|
1606
|
+
throw new APIException(`Failed to delete checkpoints: ${response.status} - ${errorText}`, response.status);
|
|
1607
|
+
}
|
|
1608
|
+
return await response.json();
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
if (error instanceof APIException) throw error;
|
|
1611
|
+
throw new APIException(`Failed to delete checkpoints: ${error}`);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
// src/agent-state-client.ts
|
|
1617
|
+
var AgentStateClient = class {
|
|
1618
|
+
baseUrl;
|
|
1619
|
+
constructor(baseUrl) {
|
|
1620
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Store or update agent state.
|
|
1624
|
+
*/
|
|
1625
|
+
async put(conversationId, namespace, state) {
|
|
1626
|
+
const url = `${this.baseUrl}/agent-state/${conversationId}`;
|
|
1627
|
+
const payload = {
|
|
1628
|
+
namespace,
|
|
1629
|
+
state
|
|
1630
|
+
};
|
|
1631
|
+
try {
|
|
1632
|
+
const response = await fetch(url, {
|
|
1633
|
+
method: "PUT",
|
|
1634
|
+
headers: { "Content-Type": "application/json" },
|
|
1635
|
+
body: JSON.stringify(payload)
|
|
1636
|
+
});
|
|
1637
|
+
if (!response.ok) {
|
|
1638
|
+
const errorText = await response.text();
|
|
1639
|
+
throw new APIException(`Failed to store agent state: ${response.status} - ${errorText}`, response.status);
|
|
1640
|
+
}
|
|
1641
|
+
return await response.json();
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
if (error instanceof APIException) throw error;
|
|
1644
|
+
throw new APIException(`Failed to store agent state: ${error}`);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Get agent state.
|
|
1649
|
+
*/
|
|
1650
|
+
async get(conversationId, namespace) {
|
|
1651
|
+
const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`);
|
|
1652
|
+
url.searchParams.set("namespace", namespace);
|
|
1653
|
+
try {
|
|
1654
|
+
const response = await fetch(url.toString());
|
|
1655
|
+
if (response.status === 404) {
|
|
1656
|
+
return null;
|
|
1657
|
+
}
|
|
1658
|
+
if (!response.ok) {
|
|
1659
|
+
const errorText = await response.text();
|
|
1660
|
+
throw new APIException(`Failed to get agent state: ${response.status} - ${errorText}`, response.status);
|
|
1661
|
+
}
|
|
1662
|
+
return await response.json();
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
if (error instanceof APIException) throw error;
|
|
1665
|
+
throw new APIException(`Failed to get agent state: ${error}`);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Delete agent state.
|
|
1670
|
+
*/
|
|
1671
|
+
async delete(conversationId, namespace) {
|
|
1672
|
+
const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`);
|
|
1673
|
+
if (namespace) {
|
|
1674
|
+
url.searchParams.set("namespace", namespace);
|
|
1675
|
+
}
|
|
1676
|
+
try {
|
|
1677
|
+
const response = await fetch(url.toString(), { method: "DELETE" });
|
|
1678
|
+
if (!response.ok) {
|
|
1679
|
+
const errorText = await response.text();
|
|
1680
|
+
throw new APIException(`Failed to delete agent state: ${response.status} - ${errorText}`, response.status);
|
|
1681
|
+
}
|
|
1682
|
+
return await response.json();
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
if (error instanceof APIException) throw error;
|
|
1685
|
+
throw new APIException(`Failed to delete agent state: ${error}`);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
|
|
1690
|
+
// src/index.ts
|
|
1691
|
+
var VERSION = "0.3.0";
|
|
1692
|
+
|
|
1693
|
+
export { AI_INVENTORY_PATHS, APIException, Agent, AgentStateClient, AttachmentClient, CONVERSATION_STORE_PATHS, CheckpointClient, ConfigurationError, Conversation, ConversationClient, ConversationMessage, GatewayClient, GatewayError, HeartbeatError, HeartbeatManager, KafkaError, MEMORY_PATHS, MarchAgentApp, MarchAgentError, Memory, MemoryClient, Message, RegistrationError, SERVICES, SenderFilter, Streamer, VERSION, createAttachmentInfo, toArtifact };
|
|
1694
|
+
//# sourceMappingURL=index.js.map
|
|
1695
|
+
//# sourceMappingURL=index.js.map
|