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
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* March Agent SDK - LangGraph Extension
|
|
3
|
+
*
|
|
4
|
+
* HTTPCheckpointSaver for LangGraph that stores state via HTTP API.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MarchAgentApp } from '../app.js'
|
|
10
|
+
import { CheckpointClient } from '../checkpoint-client.js'
|
|
11
|
+
import type {
|
|
12
|
+
CheckpointConfig,
|
|
13
|
+
CheckpointData,
|
|
14
|
+
CheckpointMetadata as APICheckpointMetadata,
|
|
15
|
+
CheckpointTuple as APICheckpointTuple,
|
|
16
|
+
} from '../checkpoint-client.js'
|
|
17
|
+
|
|
18
|
+
// Type definitions for LangGraph (optional peer dependency)
|
|
19
|
+
// These are compatible with @langchain/langgraph-checkpoint
|
|
20
|
+
interface RunnableConfig {
|
|
21
|
+
configurable?: {
|
|
22
|
+
thread_id?: string
|
|
23
|
+
checkpoint_ns?: string
|
|
24
|
+
checkpoint_id?: string
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Checkpoint {
|
|
29
|
+
v?: number
|
|
30
|
+
id?: string
|
|
31
|
+
ts?: string
|
|
32
|
+
channel_values?: Record<string, unknown>
|
|
33
|
+
channel_versions?: Record<string, string>
|
|
34
|
+
versions_seen?: Record<string, Record<string, string>>
|
|
35
|
+
pending_sends?: unknown[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface CheckpointMetadata {
|
|
39
|
+
source?: string
|
|
40
|
+
step?: number
|
|
41
|
+
writes?: unknown
|
|
42
|
+
parents?: Record<string, string>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface CheckpointTuple {
|
|
46
|
+
config: RunnableConfig
|
|
47
|
+
checkpoint: Checkpoint
|
|
48
|
+
metadata: CheckpointMetadata
|
|
49
|
+
parent_config?: RunnableConfig
|
|
50
|
+
pending_writes?: unknown[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface PendingWrite {
|
|
54
|
+
[key: string]: unknown
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* HTTP-based checkpoint saver for LangGraph.
|
|
59
|
+
*
|
|
60
|
+
* Stores graph state via HTTP calls to the conversation-store checkpoint API,
|
|
61
|
+
* enabling distributed checkpoint storage without direct database access.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* import { MarchAgentApp } from 'march-ai-sdk'
|
|
66
|
+
* import { HTTPCheckpointSaver } from 'march-ai-sdk/extensions/langgraph'
|
|
67
|
+
* import { StateGraph } from '@langchain/langgraph'
|
|
68
|
+
*
|
|
69
|
+
* const app = new MarchAgentApp({
|
|
70
|
+
* gatewayUrl: 'agent-gateway:8080',
|
|
71
|
+
* apiKey: 'your-key',
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* const checkpointer = new HTTPCheckpointSaver(app)
|
|
75
|
+
*
|
|
76
|
+
* const graph = new StateGraph(MyState)
|
|
77
|
+
* // ... define graph ...
|
|
78
|
+
* const compiled = graph.compile({ checkpointer })
|
|
79
|
+
*
|
|
80
|
+
* const config = { configurable: { thread_id: 'my-thread' } }
|
|
81
|
+
* const result = await compiled.invoke({ messages: [...] }, config)
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export class HTTPCheckpointSaver {
|
|
85
|
+
private readonly client: CheckpointClient
|
|
86
|
+
|
|
87
|
+
constructor(app: MarchAgentApp) {
|
|
88
|
+
this.client = new CheckpointClient(app.gatewayClient.conversationStoreUrl)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get thread_id from config.
|
|
93
|
+
*/
|
|
94
|
+
private getThreadId(config: RunnableConfig): string {
|
|
95
|
+
const threadId = config.configurable?.thread_id
|
|
96
|
+
if (!threadId) {
|
|
97
|
+
throw new Error('Config must contain configurable.thread_id')
|
|
98
|
+
}
|
|
99
|
+
return threadId
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get checkpoint_ns from config.
|
|
104
|
+
*/
|
|
105
|
+
private getCheckpointNs(config: RunnableConfig): string {
|
|
106
|
+
return config.configurable?.checkpoint_ns ?? ''
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get checkpoint_id from config.
|
|
111
|
+
*/
|
|
112
|
+
private getCheckpointId(config: RunnableConfig): string | undefined {
|
|
113
|
+
return config.configurable?.checkpoint_id
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate a unique checkpoint ID.
|
|
118
|
+
*/
|
|
119
|
+
private generateCheckpointId(): string {
|
|
120
|
+
return new Date().toISOString()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Fetch a checkpoint tuple asynchronously.
|
|
125
|
+
*/
|
|
126
|
+
async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {
|
|
127
|
+
const threadId = this.getThreadId(config)
|
|
128
|
+
const checkpointNs = this.getCheckpointNs(config)
|
|
129
|
+
const checkpointId = this.getCheckpointId(config)
|
|
130
|
+
|
|
131
|
+
const result = await this.client.getTuple(threadId, checkpointNs, checkpointId)
|
|
132
|
+
|
|
133
|
+
if (!result) {
|
|
134
|
+
return undefined
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return this.responseToTuple(result)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* List checkpoints asynchronously.
|
|
142
|
+
*/
|
|
143
|
+
async *list(
|
|
144
|
+
config: RunnableConfig | undefined,
|
|
145
|
+
options?: {
|
|
146
|
+
filter?: Record<string, unknown>
|
|
147
|
+
before?: RunnableConfig
|
|
148
|
+
limit?: number
|
|
149
|
+
}
|
|
150
|
+
): AsyncGenerator<CheckpointTuple> {
|
|
151
|
+
const threadId = config?.configurable?.thread_id
|
|
152
|
+
const checkpointNs = config?.configurable?.checkpoint_ns
|
|
153
|
+
const beforeId = options?.before?.configurable?.checkpoint_id
|
|
154
|
+
|
|
155
|
+
const results = await this.client.list({
|
|
156
|
+
threadId,
|
|
157
|
+
checkpointNs,
|
|
158
|
+
before: beforeId,
|
|
159
|
+
limit: options?.limit,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
for (const result of results) {
|
|
163
|
+
const tuple = this.responseToTuple(result)
|
|
164
|
+
if (tuple) {
|
|
165
|
+
yield tuple
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Store a checkpoint asynchronously.
|
|
172
|
+
*/
|
|
173
|
+
async put(
|
|
174
|
+
config: RunnableConfig,
|
|
175
|
+
checkpoint: Checkpoint,
|
|
176
|
+
metadata: CheckpointMetadata,
|
|
177
|
+
newVersions?: Record<string, unknown>
|
|
178
|
+
): Promise<RunnableConfig> {
|
|
179
|
+
const threadId = this.getThreadId(config)
|
|
180
|
+
const checkpointNs = this.getCheckpointNs(config)
|
|
181
|
+
|
|
182
|
+
let checkpointId = this.getCheckpointId(config)
|
|
183
|
+
if (!checkpointId) {
|
|
184
|
+
checkpointId = checkpoint.id ?? this.generateCheckpointId()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const apiConfig: CheckpointConfig = {
|
|
188
|
+
configurable: {
|
|
189
|
+
thread_id: threadId,
|
|
190
|
+
checkpoint_ns: checkpointNs,
|
|
191
|
+
checkpoint_id: checkpointId,
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const checkpointData = this.checkpointToApi(checkpoint)
|
|
196
|
+
const metadataData = this.metadataToApi(metadata)
|
|
197
|
+
|
|
198
|
+
const result = await this.client.put(
|
|
199
|
+
apiConfig,
|
|
200
|
+
checkpointData,
|
|
201
|
+
metadataData,
|
|
202
|
+
newVersions ?? {}
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return result.config as RunnableConfig
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Store intermediate writes asynchronously.
|
|
210
|
+
*/
|
|
211
|
+
async putWrites(
|
|
212
|
+
_config: RunnableConfig,
|
|
213
|
+
_writes: PendingWrite[],
|
|
214
|
+
_taskId: string
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
// Stub - writes are not persisted separately
|
|
217
|
+
// They are included in the checkpoint metadata
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Delete all checkpoints for a thread.
|
|
222
|
+
*/
|
|
223
|
+
async deleteThread(threadId: string): Promise<void> {
|
|
224
|
+
await this.client.deleteThread(threadId)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Convert checkpoint to API format.
|
|
229
|
+
*/
|
|
230
|
+
private checkpointToApi(checkpoint: Checkpoint): CheckpointData {
|
|
231
|
+
return {
|
|
232
|
+
v: checkpoint.v ?? 1,
|
|
233
|
+
id: checkpoint.id ?? this.generateCheckpointId(),
|
|
234
|
+
ts: checkpoint.ts ?? new Date().toISOString(),
|
|
235
|
+
channel_values: this.serializeChannelValues(checkpoint.channel_values ?? {}),
|
|
236
|
+
channel_versions: checkpoint.channel_versions ?? {},
|
|
237
|
+
versions_seen: checkpoint.versions_seen ?? {},
|
|
238
|
+
pending_sends: checkpoint.pending_sends ?? [],
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Convert metadata to API format.
|
|
244
|
+
*/
|
|
245
|
+
private metadataToApi(metadata: CheckpointMetadata): APICheckpointMetadata {
|
|
246
|
+
return {
|
|
247
|
+
source: metadata.source ?? 'input',
|
|
248
|
+
step: metadata.step ?? -1,
|
|
249
|
+
writes: metadata.writes,
|
|
250
|
+
parents: metadata.parents ?? {},
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Convert API response to CheckpointTuple.
|
|
256
|
+
*/
|
|
257
|
+
private responseToTuple(response: APICheckpointTuple): CheckpointTuple {
|
|
258
|
+
return {
|
|
259
|
+
config: response.config as RunnableConfig,
|
|
260
|
+
checkpoint: this.deserializeCheckpoint(response.checkpoint),
|
|
261
|
+
metadata: response.metadata,
|
|
262
|
+
parent_config: response.parent_config as RunnableConfig | undefined,
|
|
263
|
+
pending_writes: response.pending_writes ?? [],
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Serialize channel values for transmission.
|
|
269
|
+
*/
|
|
270
|
+
private serializeChannelValues(values: Record<string, unknown>): Record<string, unknown> {
|
|
271
|
+
return this.serializeValue(values) as Record<string, unknown>
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Serialize a value for JSON transmission.
|
|
276
|
+
*/
|
|
277
|
+
private serializeValue(value: unknown, depth: number = 0): unknown {
|
|
278
|
+
const MAX_DEPTH = 100
|
|
279
|
+
if (depth > MAX_DEPTH) {
|
|
280
|
+
return { __max_depth_exceeded__: true }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (value === null || value === undefined) {
|
|
284
|
+
return value
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Handle Buffer/Uint8Array
|
|
288
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
289
|
+
return { __bytes__: Buffer.from(value).toString('base64') }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Handle arrays
|
|
293
|
+
if (Array.isArray(value)) {
|
|
294
|
+
return value.map(item => this.serializeValue(item, depth + 1))
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Handle objects
|
|
298
|
+
if (typeof value === 'object') {
|
|
299
|
+
const result: Record<string, unknown> = {}
|
|
300
|
+
for (const [key, val] of Object.entries(value)) {
|
|
301
|
+
result[key] = this.serializeValue(val, depth + 1)
|
|
302
|
+
}
|
|
303
|
+
return result
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return value
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Deserialize checkpoint data.
|
|
311
|
+
*/
|
|
312
|
+
private deserializeCheckpoint(data: CheckpointData): Checkpoint {
|
|
313
|
+
return {
|
|
314
|
+
...data,
|
|
315
|
+
channel_values: this.deserializeValue(data.channel_values) as Record<string, unknown>,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Deserialize a value.
|
|
321
|
+
*/
|
|
322
|
+
private deserializeValue(value: unknown): unknown {
|
|
323
|
+
if (value === null || value === undefined) {
|
|
324
|
+
return value
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
328
|
+
const obj = value as Record<string, unknown>
|
|
329
|
+
|
|
330
|
+
// Decode base64 bytes
|
|
331
|
+
if ('__bytes__' in obj && typeof obj.__bytes__ === 'string') {
|
|
332
|
+
return Buffer.from(obj.__bytes__, 'base64')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Recurse into object
|
|
336
|
+
const result: Record<string, unknown> = {}
|
|
337
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
338
|
+
result[key] = this.deserializeValue(val)
|
|
339
|
+
}
|
|
340
|
+
return result
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (Array.isArray(value)) {
|
|
344
|
+
return value.map(item => this.deserializeValue(item))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return value
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export type { RunnableConfig, Checkpoint, CheckpointMetadata, CheckpointTuple, PendingWrite }
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* March Agent SDK - Vercel AI SDK Extension
|
|
3
|
+
*
|
|
4
|
+
* VercelAIMessageStore for persistent message history with Vercel AI SDK.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MarchAgentApp } from '../app.js'
|
|
10
|
+
import { AgentStateClient } from '../agent-state-client.js'
|
|
11
|
+
|
|
12
|
+
// Type definitions compatible with Vercel AI SDK's CoreMessage
|
|
13
|
+
interface CoreMessage {
|
|
14
|
+
role: 'system' | 'user' | 'assistant' | 'tool'
|
|
15
|
+
content: string | Array<{ type: string;[key: string]: unknown }>
|
|
16
|
+
[key: string]: unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Persistent message store for Vercel AI SDK.
|
|
21
|
+
*
|
|
22
|
+
* Stores and retrieves AI SDK message history using the agent-state API.
|
|
23
|
+
* Messages are serialized as JSON for full fidelity.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import { MarchAgentApp } from 'march-ai-sdk'
|
|
28
|
+
* import { VercelAIMessageStore } from 'march-ai-sdk/extensions/vercel-ai'
|
|
29
|
+
* import { streamText } from 'ai'
|
|
30
|
+
* import { openai } from '@ai-sdk/openai'
|
|
31
|
+
*
|
|
32
|
+
* const app = new MarchAgentApp({
|
|
33
|
+
* gatewayUrl: 'agent-gateway:8080',
|
|
34
|
+
* apiKey: 'your-key',
|
|
35
|
+
* })
|
|
36
|
+
*
|
|
37
|
+
* const store = new VercelAIMessageStore(app)
|
|
38
|
+
* const agent = app.registerMe({ ... })
|
|
39
|
+
*
|
|
40
|
+
* agent.onMessage(async (message, sender) => {
|
|
41
|
+
* // Load message history
|
|
42
|
+
* const history = await store.load(message.conversationId)
|
|
43
|
+
*
|
|
44
|
+
* // Add user message
|
|
45
|
+
* const messages: CoreMessage[] = [
|
|
46
|
+
* ...history,
|
|
47
|
+
* { role: 'user', content: message.content }
|
|
48
|
+
* ]
|
|
49
|
+
*
|
|
50
|
+
* // Stream response
|
|
51
|
+
* const streamer = agent.streamer(message)
|
|
52
|
+
*
|
|
53
|
+
* const result = await streamText({
|
|
54
|
+
* model: openai('gpt-4o'),
|
|
55
|
+
* messages,
|
|
56
|
+
* onChunk: ({ chunk }) => {
|
|
57
|
+
* if (chunk.type === 'text-delta') {
|
|
58
|
+
* streamer.stream(chunk.textDelta)
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* })
|
|
62
|
+
*
|
|
63
|
+
* await streamer.finish()
|
|
64
|
+
*
|
|
65
|
+
* // Save updated history
|
|
66
|
+
* await store.save(message.conversationId, [
|
|
67
|
+
* ...messages,
|
|
68
|
+
* { role: 'assistant', content: result.text }
|
|
69
|
+
* ])
|
|
70
|
+
* })
|
|
71
|
+
*
|
|
72
|
+
* app.run()
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export class VercelAIMessageStore {
|
|
76
|
+
private static readonly NAMESPACE = 'vercel_ai'
|
|
77
|
+
private readonly client: AgentStateClient
|
|
78
|
+
|
|
79
|
+
constructor(app: MarchAgentApp) {
|
|
80
|
+
this.client = new AgentStateClient(app.gatewayClient.conversationStoreUrl)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Load message history for a conversation.
|
|
85
|
+
*
|
|
86
|
+
* @param conversationId - The conversation ID to load history for
|
|
87
|
+
* @returns Array of CoreMessage objects (empty array if no history)
|
|
88
|
+
*/
|
|
89
|
+
async load(conversationId: string): Promise<CoreMessage[]> {
|
|
90
|
+
const result = await this.client.get(conversationId, VercelAIMessageStore.NAMESPACE)
|
|
91
|
+
|
|
92
|
+
if (!result) {
|
|
93
|
+
return []
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const state = result.state ?? {}
|
|
97
|
+
const messages = state.messages as unknown[]
|
|
98
|
+
|
|
99
|
+
if (!Array.isArray(messages)) {
|
|
100
|
+
return []
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Validate and return messages
|
|
104
|
+
return messages.filter(this.isValidMessage) as CoreMessage[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Save message history for a conversation.
|
|
109
|
+
*
|
|
110
|
+
* @param conversationId - The conversation ID to save history for
|
|
111
|
+
* @param messages - Array of CoreMessage objects to save
|
|
112
|
+
*/
|
|
113
|
+
async save(conversationId: string, messages: CoreMessage[]): Promise<void> {
|
|
114
|
+
await this.client.put(conversationId, VercelAIMessageStore.NAMESPACE, {
|
|
115
|
+
messages: messages.map(this.serializeMessage),
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Clear message history for a conversation.
|
|
121
|
+
*
|
|
122
|
+
* @param conversationId - The conversation ID to clear history for
|
|
123
|
+
*/
|
|
124
|
+
async clear(conversationId: string): Promise<void> {
|
|
125
|
+
await this.client.delete(conversationId, VercelAIMessageStore.NAMESPACE)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Append messages to existing history.
|
|
130
|
+
*
|
|
131
|
+
* @param conversationId - The conversation ID
|
|
132
|
+
* @param newMessages - Messages to append
|
|
133
|
+
*/
|
|
134
|
+
async append(conversationId: string, newMessages: CoreMessage[]): Promise<void> {
|
|
135
|
+
const existing = await this.load(conversationId)
|
|
136
|
+
await this.save(conversationId, [...existing, ...newMessages])
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get the last N messages from history.
|
|
141
|
+
*
|
|
142
|
+
* @param conversationId - The conversation ID
|
|
143
|
+
* @param count - Number of messages to retrieve
|
|
144
|
+
*/
|
|
145
|
+
async getLastMessages(conversationId: string, count: number): Promise<CoreMessage[]> {
|
|
146
|
+
const history = await this.load(conversationId)
|
|
147
|
+
return history.slice(-count)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Validate that an object is a valid message.
|
|
152
|
+
*/
|
|
153
|
+
private isValidMessage(msg: unknown): msg is CoreMessage {
|
|
154
|
+
if (typeof msg !== 'object' || msg === null) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const m = msg as Record<string, unknown>
|
|
159
|
+
const validRoles = ['system', 'user', 'assistant', 'tool']
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
typeof m.role === 'string' &&
|
|
163
|
+
validRoles.includes(m.role) &&
|
|
164
|
+
(typeof m.content === 'string' || Array.isArray(m.content))
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Serialize a message for storage.
|
|
170
|
+
*/
|
|
171
|
+
private serializeMessage(msg: CoreMessage): Record<string, unknown> {
|
|
172
|
+
// Return a plain object copy
|
|
173
|
+
return { ...msg }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type { CoreMessage }
|