/** * March Agent SDK - Checkpoint Client * Port of Python march_agent/checkpoint_client.py */ import { APIException } from './exceptions.js' export interface CheckpointConfig { configurable: { thread_id: string checkpoint_ns?: string checkpoint_id?: string } } export interface CheckpointData { v: number id: string ts: string channel_values: Record channel_versions: Record versions_seen: Record> pending_sends?: unknown[] } export interface CheckpointMetadata { source: string step: number writes?: unknown parents?: Record } export interface CheckpointTuple { config: CheckpointConfig checkpoint: CheckpointData metadata: CheckpointMetadata parent_config?: CheckpointConfig pending_writes?: unknown[] } export interface CheckpointListOptions { threadId?: string checkpointNs?: string before?: string limit?: number } /** * Async HTTP client for checkpoint-store API. * Used by LangGraph integration for persisting graph state. */ export class CheckpointClient { private readonly baseUrl: string constructor(baseUrl: string) { this.baseUrl = baseUrl.replace(/\/$/, '') } /** * Store a checkpoint. */ async put( config: CheckpointConfig, checkpoint: CheckpointData, metadata: CheckpointMetadata, newVersions: Record = {} ): Promise<{ config: CheckpointConfig }> { const url = `${this.baseUrl}/checkpoints/` const payload = { config, checkpoint, metadata, new_versions: newVersions, } try { const response = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) if (!response.ok) { const errorText = await response.text() throw new APIException(`Failed to store checkpoint: ${response.status} - ${errorText}`, response.status) } return await response.json() as { config: CheckpointConfig } } catch (error) { if (error instanceof APIException) throw error throw new APIException(`Failed to store checkpoint: ${error}`) } } /** * Get a checkpoint tuple. */ async getTuple( threadId: string, checkpointNs: string = '', checkpointId?: string ): Promise { const url = new URL(`${this.baseUrl}/checkpoints/${threadId}`) url.searchParams.set('checkpoint_ns', checkpointNs) if (checkpointId) { url.searchParams.set('checkpoint_id', checkpointId) } try { const response = await fetch(url.toString()) if (response.status === 404) { return null } if (!response.ok) { const errorText = await response.text() throw new APIException(`Failed to get checkpoint: ${response.status} - ${errorText}`, response.status) } const result = await response.json() as CheckpointTuple | null return result || null } catch (error) { if (error instanceof APIException) throw error throw new APIException(`Failed to get checkpoint: ${error}`) } } /** * List checkpoints. */ async list(options: CheckpointListOptions = {}): Promise { const url = new URL(`${this.baseUrl}/checkpoints/`) if (options.threadId) url.searchParams.set('thread_id', options.threadId) if (options.checkpointNs !== undefined) url.searchParams.set('checkpoint_ns', options.checkpointNs) if (options.before) url.searchParams.set('before', options.before) if (options.limit) url.searchParams.set('limit', String(options.limit)) try { const response = await fetch(url.toString()) if (!response.ok) { const errorText = await response.text() throw new APIException(`Failed to list checkpoints: ${response.status} - ${errorText}`, response.status) } return await response.json() as CheckpointTuple[] } catch (error) { if (error instanceof APIException) throw error throw new APIException(`Failed to list checkpoints: ${error}`) } } /** * Delete all checkpoints for a thread. */ async deleteThread(threadId: string): Promise<{ thread_id: string; deleted: number }> { const url = `${this.baseUrl}/checkpoints/${threadId}` try { const response = await fetch(url, { method: 'DELETE' }) if (!response.ok) { const errorText = await response.text() throw new APIException(`Failed to delete checkpoints: ${response.status} - ${errorText}`, response.status) } return await response.json() as { thread_id: string; deleted: number } } catch (error) { if (error instanceof APIException) throw error throw new APIException(`Failed to delete checkpoints: ${error}`) } } }