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.
@@ -0,0 +1,78 @@
1
+ /**
2
+ * March Agent SDK - Attachment Client
3
+ * Port of Python march_agent/attachment_client.py
4
+ */
5
+
6
+ import { APIException } from './exceptions.js'
7
+ import { AttachmentInfoSchema, type AttachmentInfo } from './types.js'
8
+
9
+ /**
10
+ * Re-export AttachmentInfo for convenience
11
+ */
12
+ export { type AttachmentInfo } from './types.js'
13
+
14
+ /**
15
+ * Create AttachmentInfo from API data
16
+ */
17
+ export function createAttachmentInfo(data: Record<string, unknown>): AttachmentInfo {
18
+ return AttachmentInfoSchema.parse({
19
+ url: data.url,
20
+ filename: data.filename || data.file_name,
21
+ contentType: data.content_type || data.contentType,
22
+ size: data.size,
23
+ fileType: data.file_type || data.fileType,
24
+ })
25
+ }
26
+
27
+ /**
28
+ * HTTP client for downloading attachments.
29
+ */
30
+ export class AttachmentClient {
31
+ private readonly baseUrl: string
32
+
33
+ constructor(baseUrl: string) {
34
+ this.baseUrl = baseUrl.replace(/\/$/, '')
35
+ }
36
+
37
+ /**
38
+ * Build full URL for an attachment.
39
+ */
40
+ private buildUrl(attachmentUrl: string): string {
41
+ // If already absolute URL, use as-is
42
+ if (attachmentUrl.startsWith('http://') || attachmentUrl.startsWith('https://')) {
43
+ return attachmentUrl
44
+ }
45
+ // Otherwise, prepend base URL
46
+ return `${this.baseUrl}${attachmentUrl.startsWith('/') ? '' : '/'}${attachmentUrl}`
47
+ }
48
+
49
+ /**
50
+ * Download attachment as bytes (Buffer).
51
+ */
52
+ async download(url: string): Promise<Buffer> {
53
+ const fullUrl = this.buildUrl(url)
54
+
55
+ try {
56
+ const response = await fetch(fullUrl)
57
+
58
+ if (!response.ok) {
59
+ throw new APIException(`Failed to download attachment: ${response.status}`, response.status)
60
+ }
61
+
62
+ const arrayBuffer = await response.arrayBuffer()
63
+ return Buffer.from(arrayBuffer)
64
+ } catch (error) {
65
+ if (error instanceof APIException) throw error
66
+ throw new APIException(`Failed to download attachment: ${error}`)
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Download attachment as base64 string.
72
+ * Useful for LLM vision APIs.
73
+ */
74
+ async downloadAsBase64(url: string): Promise<string> {
75
+ const buffer = await this.download(url)
76
+ return buffer.toString('base64')
77
+ }
78
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * March Agent SDK - Checkpoint Client
3
+ * Port of Python march_agent/checkpoint_client.py
4
+ */
5
+
6
+ import { APIException } from './exceptions.js'
7
+
8
+ export interface CheckpointConfig {
9
+ configurable: {
10
+ thread_id: string
11
+ checkpoint_ns?: string
12
+ checkpoint_id?: string
13
+ }
14
+ }
15
+
16
+ export interface CheckpointData {
17
+ v: number
18
+ id: string
19
+ ts: string
20
+ channel_values: Record<string, unknown>
21
+ channel_versions: Record<string, string>
22
+ versions_seen: Record<string, Record<string, string>>
23
+ pending_sends?: unknown[]
24
+ }
25
+
26
+ export interface CheckpointMetadata {
27
+ source: string
28
+ step: number
29
+ writes?: unknown
30
+ parents?: Record<string, string>
31
+ }
32
+
33
+ export interface CheckpointTuple {
34
+ config: CheckpointConfig
35
+ checkpoint: CheckpointData
36
+ metadata: CheckpointMetadata
37
+ parent_config?: CheckpointConfig
38
+ pending_writes?: unknown[]
39
+ }
40
+
41
+ export interface CheckpointListOptions {
42
+ threadId?: string
43
+ checkpointNs?: string
44
+ before?: string
45
+ limit?: number
46
+ }
47
+
48
+ /**
49
+ * Async HTTP client for checkpoint-store API.
50
+ * Used by LangGraph integration for persisting graph state.
51
+ */
52
+ export class CheckpointClient {
53
+ private readonly baseUrl: string
54
+
55
+ constructor(baseUrl: string) {
56
+ this.baseUrl = baseUrl.replace(/\/$/, '')
57
+ }
58
+
59
+ /**
60
+ * Store a checkpoint.
61
+ */
62
+ async put(
63
+ config: CheckpointConfig,
64
+ checkpoint: CheckpointData,
65
+ metadata: CheckpointMetadata,
66
+ newVersions: Record<string, unknown> = {}
67
+ ): Promise<{ config: CheckpointConfig }> {
68
+ const url = `${this.baseUrl}/checkpoints/`
69
+ const payload = {
70
+ config,
71
+ checkpoint,
72
+ metadata,
73
+ new_versions: newVersions,
74
+ }
75
+
76
+ try {
77
+ const response = await fetch(url, {
78
+ method: 'PUT',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify(payload),
81
+ })
82
+
83
+ if (!response.ok) {
84
+ const errorText = await response.text()
85
+ throw new APIException(`Failed to store checkpoint: ${response.status} - ${errorText}`, response.status)
86
+ }
87
+
88
+ return await response.json() as { config: CheckpointConfig }
89
+ } catch (error) {
90
+ if (error instanceof APIException) throw error
91
+ throw new APIException(`Failed to store checkpoint: ${error}`)
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Get a checkpoint tuple.
97
+ */
98
+ async getTuple(
99
+ threadId: string,
100
+ checkpointNs: string = '',
101
+ checkpointId?: string
102
+ ): Promise<CheckpointTuple | null> {
103
+ const url = new URL(`${this.baseUrl}/checkpoints/${threadId}`)
104
+ url.searchParams.set('checkpoint_ns', checkpointNs)
105
+ if (checkpointId) {
106
+ url.searchParams.set('checkpoint_id', checkpointId)
107
+ }
108
+
109
+ try {
110
+ const response = await fetch(url.toString())
111
+
112
+ if (response.status === 404) {
113
+ return null
114
+ }
115
+
116
+ if (!response.ok) {
117
+ const errorText = await response.text()
118
+ throw new APIException(`Failed to get checkpoint: ${response.status} - ${errorText}`, response.status)
119
+ }
120
+
121
+ const result = await response.json() as CheckpointTuple | null
122
+ return result || null
123
+ } catch (error) {
124
+ if (error instanceof APIException) throw error
125
+ throw new APIException(`Failed to get checkpoint: ${error}`)
126
+ }
127
+ }
128
+
129
+ /**
130
+ * List checkpoints.
131
+ */
132
+ async list(options: CheckpointListOptions = {}): Promise<CheckpointTuple[]> {
133
+ const url = new URL(`${this.baseUrl}/checkpoints/`)
134
+
135
+ if (options.threadId) url.searchParams.set('thread_id', options.threadId)
136
+ if (options.checkpointNs !== undefined) url.searchParams.set('checkpoint_ns', options.checkpointNs)
137
+ if (options.before) url.searchParams.set('before', options.before)
138
+ if (options.limit) url.searchParams.set('limit', String(options.limit))
139
+
140
+ try {
141
+ const response = await fetch(url.toString())
142
+
143
+ if (!response.ok) {
144
+ const errorText = await response.text()
145
+ throw new APIException(`Failed to list checkpoints: ${response.status} - ${errorText}`, response.status)
146
+ }
147
+
148
+ return await response.json() as CheckpointTuple[]
149
+ } catch (error) {
150
+ if (error instanceof APIException) throw error
151
+ throw new APIException(`Failed to list checkpoints: ${error}`)
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Delete all checkpoints for a thread.
157
+ */
158
+ async deleteThread(threadId: string): Promise<{ thread_id: string; deleted: number }> {
159
+ const url = `${this.baseUrl}/checkpoints/${threadId}`
160
+
161
+ try {
162
+ const response = await fetch(url, { method: 'DELETE' })
163
+
164
+ if (!response.ok) {
165
+ const errorText = await response.text()
166
+ throw new APIException(`Failed to delete checkpoints: ${response.status} - ${errorText}`, response.status)
167
+ }
168
+
169
+ return await response.json() as { thread_id: string; deleted: number }
170
+ } catch (error) {
171
+ if (error instanceof APIException) throw error
172
+ throw new APIException(`Failed to delete checkpoints: ${error}`)
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * March Agent SDK - Conversation Client
3
+ * Port of Python march_agent/conversation_client.py
4
+ */
5
+
6
+ import { APIException } from './exceptions.js'
7
+ import type { ConversationData, GetMessagesOptions, ConversationMessageData } from './types.js'
8
+
9
+ /**
10
+ * HTTP client for interacting with conversation-store API.
11
+ */
12
+ export class ConversationClient {
13
+ private readonly baseUrl: string
14
+
15
+ constructor(baseUrl: string) {
16
+ this.baseUrl = baseUrl.replace(/\/$/, '')
17
+ }
18
+
19
+ /**
20
+ * Get conversation metadata.
21
+ */
22
+ async getConversation(conversationId: string): Promise<ConversationData> {
23
+ const url = `${this.baseUrl}/conversations/${conversationId}`
24
+
25
+ try {
26
+ const response = await fetch(url)
27
+
28
+ if (response.status === 404) {
29
+ throw new APIException(`Conversation ${conversationId} not found`, 404)
30
+ }
31
+
32
+ if (!response.ok) {
33
+ throw new APIException(`Failed to fetch conversation: ${response.status}`, response.status)
34
+ }
35
+
36
+ return await response.json() as ConversationData
37
+ } catch (error) {
38
+ if (error instanceof APIException) throw error
39
+ throw new APIException(`Failed to fetch conversation: ${error}`)
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get messages from a conversation.
45
+ */
46
+ async getMessages(
47
+ conversationId: string,
48
+ options: GetMessagesOptions = {}
49
+ ): Promise<ConversationMessageData[]> {
50
+ const url = new URL(`${this.baseUrl}/conversations/${conversationId}/messages`)
51
+
52
+ const params: Record<string, string> = {
53
+ limit: String(Math.min(options.limit ?? 100, 1000)),
54
+ offset: String(options.offset ?? 0),
55
+ }
56
+
57
+ if (options.role) params.role = options.role
58
+ if (options.from) params.from = options.from
59
+ if (options.to) params.to = options.to
60
+
61
+ Object.entries(params).forEach(([key, value]) => {
62
+ url.searchParams.set(key, value)
63
+ })
64
+
65
+ try {
66
+ const response = await fetch(url.toString())
67
+
68
+ if (response.status === 404) {
69
+ throw new APIException(`Conversation ${conversationId} not found`, 404)
70
+ }
71
+
72
+ if (!response.ok) {
73
+ throw new APIException(`Failed to fetch messages: ${response.status}`, response.status)
74
+ }
75
+
76
+ return await response.json() as ConversationMessageData[]
77
+ } catch (error) {
78
+ if (error instanceof APIException) throw error
79
+ throw new APIException(`Failed to fetch messages: ${error}`)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Update conversation fields (PATCH).
85
+ */
86
+ async updateConversation(
87
+ conversationId: string,
88
+ data: Partial<ConversationData>
89
+ ): Promise<ConversationData> {
90
+ const url = `${this.baseUrl}/conversations/${conversationId}`
91
+
92
+ try {
93
+ const response = await fetch(url, {
94
+ method: 'PATCH',
95
+ headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify(data),
97
+ })
98
+
99
+ if (!response.ok) {
100
+ throw new APIException(`Failed to update conversation: ${response.status}`, response.status)
101
+ }
102
+
103
+ return await response.json() as ConversationData
104
+ } catch (error) {
105
+ if (error instanceof APIException) throw error
106
+ throw new APIException(`Failed to update conversation: ${error}`)
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * March Agent SDK - Conversation Message
3
+ * Port of Python march_agent/conversation_message.py
4
+ */
5
+
6
+ import type { ConversationMessageData } from './types.js'
7
+
8
+ /**
9
+ * Represents a message from conversation history.
10
+ */
11
+ export class ConversationMessage {
12
+ readonly id: string
13
+ readonly conversationId: string
14
+ readonly role: 'user' | 'assistant' | 'system'
15
+ readonly content: string
16
+ readonly from?: string
17
+ readonly to?: string
18
+ readonly createdAt: Date
19
+ readonly metadata?: Record<string, unknown>
20
+
21
+ constructor(data: ConversationMessageData) {
22
+ this.id = data.id
23
+ this.conversationId = data.conversationId
24
+ this.role = data.role
25
+ this.content = data.content
26
+ this.from = data.from
27
+ this.to = data.to
28
+ this.createdAt = new Date(data.createdAt)
29
+ this.metadata = data.metadata
30
+ }
31
+
32
+ /**
33
+ * Create from API response
34
+ */
35
+ static fromApiResponse(data: Record<string, unknown>): ConversationMessage {
36
+ return new ConversationMessage({
37
+ id: data.id as string,
38
+ conversationId: data.conversation_id as string,
39
+ role: data.role as 'user' | 'assistant' | 'system',
40
+ content: data.content as string,
41
+ from: data.from as string | undefined,
42
+ to: data.to as string | undefined,
43
+ createdAt: data.created_at as string,
44
+ metadata: data.metadata as Record<string, unknown> | undefined,
45
+ })
46
+ }
47
+
48
+ /**
49
+ * Check if this is a user message
50
+ */
51
+ isUser(): boolean {
52
+ return this.role === 'user'
53
+ }
54
+
55
+ /**
56
+ * Check if this is an assistant message
57
+ */
58
+ isAssistant(): boolean {
59
+ return this.role === 'assistant'
60
+ }
61
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * March Agent SDK - Conversation Helper
3
+ * Port of Python march_agent/conversation.py
4
+ */
5
+
6
+ import { ConversationClient } from './conversation-client.js'
7
+ import { ConversationMessage } from './conversation-message.js'
8
+ // Types are inlined to avoid unused import warnings
9
+
10
+ export interface GetHistoryOptions {
11
+ limit?: number
12
+ offset?: number
13
+ }
14
+
15
+ export interface GetAgentHistoryOptions extends GetHistoryOptions {
16
+ agentName?: string
17
+ }
18
+
19
+ /**
20
+ * Helper class for accessing conversation history.
21
+ * Provides convenient methods for fetching messages.
22
+ */
23
+ export class Conversation {
24
+ readonly conversationId: string
25
+ private readonly client: ConversationClient
26
+ private readonly agentName?: string
27
+
28
+ constructor(
29
+ conversationId: string,
30
+ client: ConversationClient,
31
+ agentName?: string
32
+ ) {
33
+ this.conversationId = conversationId
34
+ this.client = client
35
+ this.agentName = agentName
36
+ }
37
+
38
+ /**
39
+ * Get all messages in the conversation.
40
+ */
41
+ async getHistory(options: GetHistoryOptions = {}): Promise<ConversationMessage[]> {
42
+ const messages = await this.client.getMessages(this.conversationId, {
43
+ limit: options.limit ?? 100,
44
+ offset: options.offset ?? 0,
45
+ })
46
+
47
+ return messages.map((m) => ConversationMessage.fromApiResponse(m as unknown as Record<string, unknown>))
48
+ }
49
+
50
+ /**
51
+ * Get messages to/from the current agent.
52
+ * Useful for getting conversation history for a specific agent.
53
+ */
54
+ async getAgentHistory(options: GetAgentHistoryOptions = {}): Promise<ConversationMessage[]> {
55
+ const targetAgent = options.agentName ?? this.agentName
56
+ if (!targetAgent) {
57
+ // If no agent name, return all messages
58
+ return this.getHistory(options)
59
+ }
60
+
61
+ // Get messages where the agent is either sender or receiver
62
+ const [toAgent, fromAgent] = await Promise.all([
63
+ this.client.getMessages(this.conversationId, {
64
+ to: targetAgent,
65
+ limit: options.limit ?? 50,
66
+ offset: options.offset ?? 0,
67
+ }),
68
+ this.client.getMessages(this.conversationId, {
69
+ from: targetAgent,
70
+ limit: options.limit ?? 50,
71
+ offset: options.offset ?? 0,
72
+ }),
73
+ ])
74
+
75
+ // Combine and sort by creation time
76
+ const allMessages = [...toAgent, ...fromAgent]
77
+ const uniqueMessages = Array.from(
78
+ new Map(allMessages.map((m) => [m.id, m])).values()
79
+ )
80
+
81
+ // Sort by createdAt
82
+ uniqueMessages.sort((a, b) => {
83
+ const dateA = new Date(a.createdAt).getTime()
84
+ const dateB = new Date(b.createdAt).getTime()
85
+ return dateA - dateB
86
+ })
87
+
88
+ return uniqueMessages.map((m) =>
89
+ ConversationMessage.fromApiResponse(m as unknown as Record<string, unknown>)
90
+ )
91
+ }
92
+
93
+ /**
94
+ * Get the last N messages.
95
+ */
96
+ async getLastMessages(count: number): Promise<ConversationMessage[]> {
97
+ return this.getHistory({ limit: count })
98
+ }
99
+
100
+ /**
101
+ * Get user messages only.
102
+ */
103
+ async getUserMessages(options: GetHistoryOptions = {}): Promise<ConversationMessage[]> {
104
+ const messages = await this.client.getMessages(this.conversationId, {
105
+ ...options,
106
+ role: 'user',
107
+ })
108
+
109
+ return messages.map((m) => ConversationMessage.fromApiResponse(m as unknown as Record<string, unknown>))
110
+ }
111
+
112
+ /**
113
+ * Get assistant messages only.
114
+ */
115
+ async getAssistantMessages(options: GetHistoryOptions = {}): Promise<ConversationMessage[]> {
116
+ const messages = await this.client.getMessages(this.conversationId, {
117
+ ...options,
118
+ role: 'assistant',
119
+ })
120
+
121
+ return messages.map((m) => ConversationMessage.fromApiResponse(m as unknown as Record<string, unknown>))
122
+ }
123
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * March Agent SDK - Custom Errors
3
+ * Port of Python march_agent/exceptions.py
4
+ */
5
+
6
+ /**
7
+ * Base error class for March Agent SDK
8
+ */
9
+ export class MarchAgentError extends Error {
10
+ constructor(message: string) {
11
+ super(message)
12
+ this.name = 'MarchAgentError'
13
+ Error.captureStackTrace?.(this, this.constructor)
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Error during agent registration with AI Inventory
19
+ */
20
+ export class RegistrationError extends MarchAgentError {
21
+ constructor(message: string) {
22
+ super(message)
23
+ this.name = 'RegistrationError'
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Error during Kafka operations (produce/consume)
29
+ */
30
+ export class KafkaError extends MarchAgentError {
31
+ constructor(message: string) {
32
+ super(message)
33
+ this.name = 'KafkaError'
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Error in SDK configuration
39
+ */
40
+ export class ConfigurationError extends MarchAgentError {
41
+ constructor(message: string) {
42
+ super(message)
43
+ this.name = 'ConfigurationError'
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Error from API calls to backend services
49
+ */
50
+ export class APIException extends MarchAgentError {
51
+ statusCode?: number
52
+
53
+ constructor(message: string, statusCode?: number) {
54
+ super(message)
55
+ this.name = 'APIException'
56
+ this.statusCode = statusCode
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Error during heartbeat operations
62
+ */
63
+ export class HeartbeatError extends MarchAgentError {
64
+ constructor(message: string) {
65
+ super(message)
66
+ this.name = 'HeartbeatError'
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Error during gRPC connection/communication
72
+ */
73
+ export class GatewayError extends MarchAgentError {
74
+ constructor(message: string) {
75
+ super(message)
76
+ this.name = 'GatewayError'
77
+ }
78
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * March Agent SDK - Extensions
3
+ */
4
+
5
+ export { HTTPCheckpointSaver } from './langgraph.js'
6
+ export { VercelAIMessageStore } from './vercel-ai.js'