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/src/agent.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* March Agent SDK - Agent
|
|
3
|
+
* Port of Python march_agent/agent.py
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Message } from './message.js'
|
|
7
|
+
import { Streamer } from './streamer.js'
|
|
8
|
+
import { HeartbeatManager } from './heartbeat.js'
|
|
9
|
+
import { ConfigurationError } from './exceptions.js'
|
|
10
|
+
import type { GatewayClient } from './gateway-client.js'
|
|
11
|
+
import type { ConversationClient } from './conversation-client.js'
|
|
12
|
+
import type { MemoryClient } from './memory-client.js'
|
|
13
|
+
import type { AttachmentClient } from './attachment-client.js'
|
|
14
|
+
import type {
|
|
15
|
+
AgentRegistrationData,
|
|
16
|
+
MessageHandler,
|
|
17
|
+
SenderFilterOptions,
|
|
18
|
+
StreamerOptions,
|
|
19
|
+
KafkaMessage,
|
|
20
|
+
} from './types.js'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Filter for matching message senders.
|
|
24
|
+
*/
|
|
25
|
+
export class SenderFilter {
|
|
26
|
+
private readonly _include: Set<string> = new Set()
|
|
27
|
+
private readonly _exclude: Set<string> = new Set()
|
|
28
|
+
readonly matchAll: boolean
|
|
29
|
+
|
|
30
|
+
constructor(senders?: string[]) {
|
|
31
|
+
if (!senders || senders.length === 0) {
|
|
32
|
+
this.matchAll = true
|
|
33
|
+
} else {
|
|
34
|
+
this.matchAll = false
|
|
35
|
+
for (const sender of senders) {
|
|
36
|
+
if (sender.startsWith('~')) {
|
|
37
|
+
this._exclude.add(sender.slice(1))
|
|
38
|
+
} else {
|
|
39
|
+
this._include.add(sender)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get included senders as an array (for compatibility with Python tests).
|
|
47
|
+
*/
|
|
48
|
+
get include(): string[] {
|
|
49
|
+
return Array.from(this._include)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get excluded senders as an array (for compatibility with Python tests).
|
|
54
|
+
*/
|
|
55
|
+
get exclude(): string[] {
|
|
56
|
+
return Array.from(this._exclude)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if sender matches this filter.
|
|
61
|
+
*/
|
|
62
|
+
matches(sender: string): boolean {
|
|
63
|
+
// If excluded, reject
|
|
64
|
+
if (this._exclude.has(sender)) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
// If match all or explicitly included
|
|
68
|
+
if (this.matchAll || this._include.size === 0) {
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
return this._include.has(sender)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Message handlers stored as tuples: [SenderFilter, MessageHandler]
|
|
76
|
+
type RegisteredHandler = [SenderFilter, MessageHandler]
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Core agent class that handles messaging via the Agent Gateway.
|
|
80
|
+
*/
|
|
81
|
+
export class Agent {
|
|
82
|
+
readonly name: string
|
|
83
|
+
readonly agentData: AgentRegistrationData
|
|
84
|
+
sendErrorResponses: boolean = true
|
|
85
|
+
errorMessageTemplate: string
|
|
86
|
+
|
|
87
|
+
private readonly gatewayClient: GatewayClient
|
|
88
|
+
private readonly conversationClient?: ConversationClient
|
|
89
|
+
private readonly memoryClient?: MemoryClient
|
|
90
|
+
private readonly attachmentClient?: AttachmentClient
|
|
91
|
+
private readonly heartbeatInterval: number
|
|
92
|
+
|
|
93
|
+
private messageHandlers: RegisteredHandler[] = []
|
|
94
|
+
private heartbeatManager?: HeartbeatManager
|
|
95
|
+
private initialized: boolean = false
|
|
96
|
+
private running: boolean = false
|
|
97
|
+
|
|
98
|
+
constructor(options: {
|
|
99
|
+
name: string
|
|
100
|
+
gatewayClient: GatewayClient
|
|
101
|
+
agentData: AgentRegistrationData
|
|
102
|
+
heartbeatInterval?: number
|
|
103
|
+
conversationClient?: ConversationClient
|
|
104
|
+
memoryClient?: MemoryClient
|
|
105
|
+
attachmentClient?: AttachmentClient
|
|
106
|
+
errorMessageTemplate?: string
|
|
107
|
+
}) {
|
|
108
|
+
this.name = options.name
|
|
109
|
+
this.gatewayClient = options.gatewayClient
|
|
110
|
+
this.agentData = options.agentData
|
|
111
|
+
this.heartbeatInterval = options.heartbeatInterval ?? 60
|
|
112
|
+
this.conversationClient = options.conversationClient
|
|
113
|
+
this.memoryClient = options.memoryClient
|
|
114
|
+
this.attachmentClient = options.attachmentClient
|
|
115
|
+
this.errorMessageTemplate = options.errorMessageTemplate ??
|
|
116
|
+
'I encountered an error while processing your message. Please try again or contact support if the issue persists.'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Register a message handler.
|
|
121
|
+
*
|
|
122
|
+
* Usage:
|
|
123
|
+
* agent.onMessage(async (message, sender) => { ... })
|
|
124
|
+
* agent.onMessage(handler, { senders: ['user'] })
|
|
125
|
+
*/
|
|
126
|
+
onMessage(handler: MessageHandler): void
|
|
127
|
+
onMessage(handler: MessageHandler, options: SenderFilterOptions): void
|
|
128
|
+
onMessage(
|
|
129
|
+
handler: MessageHandler,
|
|
130
|
+
options?: SenderFilterOptions
|
|
131
|
+
): void {
|
|
132
|
+
const filter = new SenderFilter(options?.senders)
|
|
133
|
+
this.messageHandlers.push([filter, handler])
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Initialize agent after gateway connection is established.
|
|
138
|
+
*/
|
|
139
|
+
initializeWithGateway(): void {
|
|
140
|
+
if (this.initialized) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Register message handler with gateway
|
|
145
|
+
const topic = `${this.name}.inbox`
|
|
146
|
+
this.gatewayClient.registerHandler(topic, (msg) => {
|
|
147
|
+
this.handleKafkaMessage(msg)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Start heartbeat
|
|
151
|
+
this.heartbeatManager = new HeartbeatManager(
|
|
152
|
+
this.gatewayClient,
|
|
153
|
+
this.name,
|
|
154
|
+
this.heartbeatInterval
|
|
155
|
+
)
|
|
156
|
+
this.heartbeatManager.start()
|
|
157
|
+
|
|
158
|
+
this.initialized = true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get sender from message headers.
|
|
163
|
+
*/
|
|
164
|
+
private getSender(headers: Record<string, string>): string {
|
|
165
|
+
return headers.from_ ?? headers.from ?? 'user'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Find first handler that matches the sender.
|
|
170
|
+
*/
|
|
171
|
+
private findMatchingHandler(sender: string): MessageHandler | undefined {
|
|
172
|
+
for (const [filter, handler] of this.messageHandlers) {
|
|
173
|
+
if (filter.matches(sender)) {
|
|
174
|
+
return handler
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return undefined
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Handle incoming Kafka message.
|
|
182
|
+
*/
|
|
183
|
+
private handleKafkaMessage(kafkaMsg: KafkaMessage): void {
|
|
184
|
+
// Run handler asynchronously
|
|
185
|
+
this.handleMessageAsync(kafkaMsg).catch((error) => {
|
|
186
|
+
console.error('Error in message handler:', error)
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Async message handling with error recovery.
|
|
192
|
+
*/
|
|
193
|
+
private async handleMessageAsync(kafkaMsg: KafkaMessage): Promise<void> {
|
|
194
|
+
let message: Message | undefined
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// Create message from Kafka data
|
|
198
|
+
message = Message.fromKafkaMessage(
|
|
199
|
+
kafkaMsg.body,
|
|
200
|
+
kafkaMsg.headers,
|
|
201
|
+
{
|
|
202
|
+
conversationClient: this.conversationClient,
|
|
203
|
+
memoryClient: this.memoryClient,
|
|
204
|
+
attachmentClient: this.attachmentClient,
|
|
205
|
+
agentName: this.name,
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
// Find matching handler
|
|
210
|
+
const sender = this.getSender(kafkaMsg.headers)
|
|
211
|
+
const handler = this.findMatchingHandler(sender)
|
|
212
|
+
|
|
213
|
+
if (!handler) {
|
|
214
|
+
console.warn(`No handler matched for sender: ${sender}`)
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Call handler
|
|
219
|
+
await handler(message, sender)
|
|
220
|
+
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('Error handling message:', error)
|
|
223
|
+
|
|
224
|
+
// Send error response if we have a message
|
|
225
|
+
if (message && this.sendErrorResponses) {
|
|
226
|
+
await this.sendErrorResponse(message, error as Error)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Send error response to user when handler fails.
|
|
233
|
+
*/
|
|
234
|
+
private async sendErrorResponse(message: Message, _error: Error): Promise<void> {
|
|
235
|
+
try {
|
|
236
|
+
const streamer = this.streamer(message, { sendTo: 'user' })
|
|
237
|
+
streamer.stream(this.errorMessageTemplate)
|
|
238
|
+
await streamer.finish()
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.error('Failed to send error response:', err)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Create a new Streamer for streaming responses.
|
|
246
|
+
*/
|
|
247
|
+
streamer(message: Message, options: StreamerOptions = {}): Streamer {
|
|
248
|
+
return new Streamer({
|
|
249
|
+
agentName: this.name,
|
|
250
|
+
originalMessage: message,
|
|
251
|
+
gatewayClient: this.gatewayClient,
|
|
252
|
+
conversationClient: this.conversationClient,
|
|
253
|
+
awaiting: options.awaiting ?? false,
|
|
254
|
+
sendTo: options.sendTo ?? 'user',
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Mark agent as ready to consume messages.
|
|
260
|
+
*/
|
|
261
|
+
startConsuming(): void {
|
|
262
|
+
if (this.messageHandlers.length === 0) {
|
|
263
|
+
throw new ConfigurationError('No message handlers registered')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!this.initialized) {
|
|
267
|
+
throw new ConfigurationError('Agent not initialized with gateway')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.running = true
|
|
271
|
+
console.log(`Agent ${this.name} is now consuming messages`)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Shutdown agent gracefully.
|
|
276
|
+
*/
|
|
277
|
+
shutdown(): void {
|
|
278
|
+
this.running = false
|
|
279
|
+
|
|
280
|
+
if (this.heartbeatManager) {
|
|
281
|
+
this.heartbeatManager.stop()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
console.log(`Agent ${this.name} shutdown`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if agent is running.
|
|
289
|
+
*/
|
|
290
|
+
isRunning(): boolean {
|
|
291
|
+
return this.running
|
|
292
|
+
}
|
|
293
|
+
}
|
package/src/api-paths.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* March Agent SDK - API Paths Configuration
|
|
3
|
+
*
|
|
4
|
+
* Centralized configuration for all API endpoint paths.
|
|
5
|
+
* This allows easy configuration if API versions or paths change.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* API paths for AI Inventory service.
|
|
10
|
+
*/
|
|
11
|
+
export const AI_INVENTORY_PATHS = {
|
|
12
|
+
/** Register a new agent */
|
|
13
|
+
AGENT_REGISTER: '/api/v1/agents/register',
|
|
14
|
+
/** Send heartbeat */
|
|
15
|
+
HEALTH_HEARTBEAT: '/api/v1/health/heartbeat',
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* API paths for Conversation Store service.
|
|
20
|
+
* Note: These paths don't have /api/v1/ prefix.
|
|
21
|
+
*/
|
|
22
|
+
export const CONVERSATION_STORE_PATHS = {
|
|
23
|
+
/** Get/update conversation by ID */
|
|
24
|
+
CONVERSATION: (conversationId: string) => `/conversations/${conversationId}`,
|
|
25
|
+
/** Get messages for a conversation */
|
|
26
|
+
CONVERSATION_MESSAGES: (conversationId: string) => `/conversations/${conversationId}/messages`,
|
|
27
|
+
/** Checkpoints base path */
|
|
28
|
+
CHECKPOINTS: '/checkpoints/',
|
|
29
|
+
/** Checkpoint by thread ID */
|
|
30
|
+
CHECKPOINT_THREAD: (threadId: string) => `/checkpoints/${threadId}`,
|
|
31
|
+
/** Agent state by conversation ID */
|
|
32
|
+
AGENT_STATE: (conversationId: string) => `/agent-state/${conversationId}`,
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* API paths for AI Memory service.
|
|
37
|
+
* Note: These paths don't have /api/v1/ prefix.
|
|
38
|
+
*/
|
|
39
|
+
export const MEMORY_PATHS = {
|
|
40
|
+
/** User memory base */
|
|
41
|
+
USER_MEMORY: (userId: string) => `/memory/${userId}`,
|
|
42
|
+
/** Add messages to memory */
|
|
43
|
+
USER_MESSAGES: (userId: string) => `/memory/${userId}/messages`,
|
|
44
|
+
/** Search user memory */
|
|
45
|
+
USER_SEARCH: (userId: string) => `/memory/${userId}/search`,
|
|
46
|
+
/** Get user summary */
|
|
47
|
+
USER_SUMMARY: (userId: string) => `/memory/${userId}/summary`,
|
|
48
|
+
} as const
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Service names for gateway proxy routing.
|
|
52
|
+
*/
|
|
53
|
+
export const SERVICES = {
|
|
54
|
+
AI_INVENTORY: 'ai-inventory',
|
|
55
|
+
CONVERSATION_STORE: 'conversation-store',
|
|
56
|
+
AI_MEMORY: 'ai-memory',
|
|
57
|
+
ATTACHMENT: 'attachment',
|
|
58
|
+
} as const
|
|
59
|
+
|
|
60
|
+
export type ServiceName = typeof SERVICES[keyof typeof SERVICES]
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* March Agent SDK - Main Application
|
|
3
|
+
* Port of Python march_agent/app.py
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Agent } from './agent.js'
|
|
7
|
+
import { GatewayClient } from './gateway-client.js'
|
|
8
|
+
import { ConversationClient } from './conversation-client.js'
|
|
9
|
+
import { MemoryClient } from './memory-client.js'
|
|
10
|
+
import { AttachmentClient } from './attachment-client.js'
|
|
11
|
+
import { RegistrationError, ConfigurationError } from './exceptions.js'
|
|
12
|
+
import { AI_INVENTORY_PATHS, SERVICES } from './api-paths.js'
|
|
13
|
+
import type { AppOptions, RegisterOptions, AgentRegistrationData } from './types.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Main application class for March AI Agent framework.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { MarchAgentApp } from 'march-ai-sdk'
|
|
21
|
+
*
|
|
22
|
+
* const app = new MarchAgentApp({
|
|
23
|
+
* gatewayUrl: 'agent-gateway:8080',
|
|
24
|
+
* apiKey: 'your-api-key',
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* const agent = app.registerMe({
|
|
28
|
+
* name: 'my-agent',
|
|
29
|
+
* about: 'A helpful assistant',
|
|
30
|
+
* document: 'Detailed description...',
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* agent.onMessage(async (message, sender) => {
|
|
34
|
+
* const streamer = agent.streamer(message)
|
|
35
|
+
* streamer.stream('Hello!')
|
|
36
|
+
* await streamer.finish()
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* app.run()
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export class MarchAgentApp {
|
|
43
|
+
readonly gatewayClient: GatewayClient
|
|
44
|
+
readonly conversationClient: ConversationClient
|
|
45
|
+
readonly memoryClient: MemoryClient
|
|
46
|
+
readonly attachmentClient: AttachmentClient
|
|
47
|
+
|
|
48
|
+
private readonly heartbeatInterval: number
|
|
49
|
+
private readonly _maxConcurrentTasks: number
|
|
50
|
+
private readonly errorMessageTemplate: string
|
|
51
|
+
|
|
52
|
+
private agents: Agent[] = []
|
|
53
|
+
private running: boolean = false
|
|
54
|
+
private shutdownRequested: boolean = false
|
|
55
|
+
|
|
56
|
+
constructor(options: AppOptions) {
|
|
57
|
+
this.heartbeatInterval = options.heartbeatInterval ?? 60
|
|
58
|
+
this._maxConcurrentTasks = options.maxConcurrentTasks ?? 100
|
|
59
|
+
this.errorMessageTemplate = options.errorMessageTemplate ??
|
|
60
|
+
'I encountered an error while processing your message. Please try again or contact support if the issue persists.'
|
|
61
|
+
|
|
62
|
+
// Create gateway client
|
|
63
|
+
this.gatewayClient = new GatewayClient(
|
|
64
|
+
options.gatewayUrl,
|
|
65
|
+
options.apiKey,
|
|
66
|
+
options.secure ?? false
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// Create HTTP clients using gateway proxy URLs
|
|
70
|
+
this.conversationClient = new ConversationClient(
|
|
71
|
+
this.gatewayClient.conversationStoreUrl
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
this.memoryClient = new MemoryClient(
|
|
75
|
+
this.gatewayClient.aiMemoryUrl
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
this.attachmentClient = new AttachmentClient(
|
|
79
|
+
this.gatewayClient.attachmentUrl
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Register an agent with the backend.
|
|
85
|
+
*/
|
|
86
|
+
async registerMe(options: RegisterOptions): Promise<Agent> {
|
|
87
|
+
// Register with AI Inventory
|
|
88
|
+
const agentData = await this.registerWithInventory(options)
|
|
89
|
+
|
|
90
|
+
// Create agent instance
|
|
91
|
+
const agent = new Agent({
|
|
92
|
+
name: options.name,
|
|
93
|
+
gatewayClient: this.gatewayClient,
|
|
94
|
+
agentData,
|
|
95
|
+
heartbeatInterval: this.heartbeatInterval,
|
|
96
|
+
conversationClient: this.conversationClient,
|
|
97
|
+
memoryClient: this.memoryClient,
|
|
98
|
+
attachmentClient: this.attachmentClient,
|
|
99
|
+
errorMessageTemplate: this.errorMessageTemplate,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
this.agents.push(agent)
|
|
103
|
+
console.log(`Registered agent: ${options.name}`)
|
|
104
|
+
|
|
105
|
+
return agent
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Register agent with AI Inventory service.
|
|
110
|
+
*/
|
|
111
|
+
private async registerWithInventory(options: RegisterOptions): Promise<AgentRegistrationData> {
|
|
112
|
+
// Build registration payload (API expects camelCase)
|
|
113
|
+
const payload: Record<string, unknown> = {
|
|
114
|
+
name: options.name,
|
|
115
|
+
about: options.about,
|
|
116
|
+
document: options.document,
|
|
117
|
+
representationName: options.representationName || options.name,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (options.baseUrl) {
|
|
121
|
+
payload.baseUrl = options.baseUrl
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (options.metadata) {
|
|
125
|
+
payload.metadata = options.metadata
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (options.relatedPages) {
|
|
129
|
+
payload.relatedPages = options.relatedPages
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Register via gateway HTTP proxy
|
|
133
|
+
try {
|
|
134
|
+
const response = await this.gatewayClient.httpPost(
|
|
135
|
+
SERVICES.AI_INVENTORY,
|
|
136
|
+
AI_INVENTORY_PATHS.AGENT_REGISTER,
|
|
137
|
+
payload
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
const errorText = await response.text()
|
|
142
|
+
console.error('Registration failed:', response.status, errorText)
|
|
143
|
+
throw new RegistrationError(
|
|
144
|
+
`Failed to register agent ${options.name}: ${response.status}`
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const data = await response.json() as Record<string, unknown>
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
id: data.id as string,
|
|
152
|
+
name: data.name as string,
|
|
153
|
+
about: data.about as string,
|
|
154
|
+
document: data.document as string,
|
|
155
|
+
representationName: data.representationName as string | undefined,
|
|
156
|
+
baseUrl: data.baseUrl as string | undefined,
|
|
157
|
+
metadata: data.metadata as Record<string, unknown> | undefined,
|
|
158
|
+
relatedPages: data.relatedPages as { name: string; endpoint: string }[] | undefined,
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof RegistrationError) throw error
|
|
162
|
+
throw new RegistrationError(`Failed to register agent ${options.name}: ${error}`)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Start all registered agents and block until shutdown.
|
|
168
|
+
*/
|
|
169
|
+
async run(): Promise<void> {
|
|
170
|
+
if (this.agents.length === 0) {
|
|
171
|
+
throw new ConfigurationError('No agents registered')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Connect to gateway
|
|
175
|
+
const agentNames = this.agents.map((a) => a.name)
|
|
176
|
+
console.log(`Connecting to gateway with agents: ${agentNames.join(', ')}`)
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const topics = await this.gatewayClient.connect(agentNames)
|
|
180
|
+
console.log(`Connected. Subscribed to topics: ${topics.join(', ')}`)
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new ConfigurationError(`Failed to connect to gateway: ${error}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Initialize all agents
|
|
186
|
+
for (const agent of this.agents) {
|
|
187
|
+
agent.initializeWithGateway()
|
|
188
|
+
agent.startConsuming()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.running = true
|
|
192
|
+
console.log('Agent app is running. Press Ctrl+C to stop.')
|
|
193
|
+
|
|
194
|
+
// Set up shutdown handlers
|
|
195
|
+
process.on('SIGINT', () => this.shutdown())
|
|
196
|
+
process.on('SIGTERM', () => this.shutdown())
|
|
197
|
+
|
|
198
|
+
// Keep the process alive
|
|
199
|
+
await this.consumeLoop()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Main consume loop.
|
|
204
|
+
*/
|
|
205
|
+
private async consumeLoop(): Promise<void> {
|
|
206
|
+
while (this.running && !this.shutdownRequested) {
|
|
207
|
+
// The gateway client handles message dispatch via callbacks
|
|
208
|
+
// We just need to keep the event loop alive
|
|
209
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Shutdown all agents gracefully.
|
|
215
|
+
*/
|
|
216
|
+
shutdown(): void {
|
|
217
|
+
if (this.shutdownRequested) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log('\nShutting down...')
|
|
222
|
+
this.shutdownRequested = true
|
|
223
|
+
this.running = false
|
|
224
|
+
|
|
225
|
+
// Shutdown all agents
|
|
226
|
+
for (const agent of this.agents) {
|
|
227
|
+
agent.shutdown()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Close gateway connection
|
|
231
|
+
this.gatewayClient.close()
|
|
232
|
+
|
|
233
|
+
console.log('Shutdown complete')
|
|
234
|
+
}
|
|
235
|
+
}
|
package/src/artifact.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* March Agent SDK - Artifact Types
|
|
3
|
+
* Port of Python march_agent/artifact.py
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Valid artifact types for message attachments
|
|
10
|
+
*/
|
|
11
|
+
export const ArtifactTypeSchema = z.enum([
|
|
12
|
+
'document',
|
|
13
|
+
'image',
|
|
14
|
+
'iframe',
|
|
15
|
+
'video',
|
|
16
|
+
'audio',
|
|
17
|
+
'code',
|
|
18
|
+
'link',
|
|
19
|
+
'file',
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
export type ArtifactType = z.infer<typeof ArtifactTypeSchema>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Schema for artifact validation
|
|
26
|
+
*/
|
|
27
|
+
export const ArtifactSchema = z.object({
|
|
28
|
+
url: z.string(),
|
|
29
|
+
type: ArtifactTypeSchema,
|
|
30
|
+
title: z.string().optional(),
|
|
31
|
+
description: z.string().optional(),
|
|
32
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export type Artifact = z.infer<typeof ArtifactSchema>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Input type for adding artifacts (without strict validation)
|
|
39
|
+
*/
|
|
40
|
+
export interface ArtifactInput {
|
|
41
|
+
url: string
|
|
42
|
+
type: ArtifactType | string
|
|
43
|
+
title?: string
|
|
44
|
+
description?: string
|
|
45
|
+
metadata?: Record<string, unknown>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert artifact input to validated artifact
|
|
50
|
+
*/
|
|
51
|
+
export function toArtifact(input: ArtifactInput): Artifact {
|
|
52
|
+
return ArtifactSchema.parse({
|
|
53
|
+
url: input.url,
|
|
54
|
+
type: input.type,
|
|
55
|
+
title: input.title,
|
|
56
|
+
description: input.description,
|
|
57
|
+
metadata: input.metadata,
|
|
58
|
+
})
|
|
59
|
+
}
|