@langgraph-js/sdk
Version:
The UI SDK for LangGraph - seamlessly integrate your AI agents with frontend interfaces
295 lines (257 loc) • 9.66 kB
text/typescript
import { LangGraphClient, LangGraphClientConfig } from "./LangGraphClient.js";
import type { Thread } from "@langchain/langgraph-sdk";
export interface SessionInfo {
/** 会话唯一标识,同时也是 threadId */
sessionId: string;
/** LangGraphClient 实例(懒加载) */
client?: LangGraphClient;
/** Thread 信息(懒加载,第一条消息后才创建) */
thread?: Thread<any>;
/** Agent 名称 */
agentName: string;
}
export interface CreateSessionOptions {
/** 会话 ID / Thread ID,不提供则自动生成 */
sessionId?: string;
/** Agent 名称 */
agentName?: string;
/** 是否从已有 Thread 恢复会话 */
restore?: boolean;
/** Graph ID */
graphId?: string;
}
/**
* @zh History 类用于管理多个 LangGraphClient 实例,支持多会话场景
* @en History class manages multiple LangGraphClient instances for multi-session scenarios
*/
export class History {
/** 存储所有会话的 Map */
private sessions: Map<string, SessionInfo> = new Map();
/** 当前活跃的会话 ID */
private activeSessionId: string | null = null;
/** 客户端配置,用于创建新的 LangGraphClient 实例 */
private clientConfig: LangGraphClientConfig;
/** 虚拟 Client,用于查询操作(不绑定特定 Thread) */
private virtualClient: LangGraphClient;
constructor(clientConfig: LangGraphClientConfig) {
this.clientConfig = clientConfig;
this.virtualClient = new LangGraphClient(clientConfig);
}
/**
* @zh 创建新会话(延迟创建 Thread,直到发送第一条消息)
* @en Creates a new session (lazy Thread creation until first message)
*/
async createSession(options: CreateSessionOptions = {}): Promise<SessionInfo> {
const sessionId = options.sessionId || this.generateSessionId();
const agentName = options.agentName || this.virtualClient.getCurrentAssistant()?.graph_id;
if (!agentName) {
throw new Error("Agent name is required. Please call init() first or provide agentName.");
}
if (this.sessions.has(sessionId)) {
throw new Error(`Session ${sessionId} already exists`);
}
const sessionInfo: SessionInfo = {
sessionId,
agentName,
};
// 如果是从已有 Thread 恢复,则立即获取 Thread
if (options.restore) {
sessionInfo.thread = (await this.virtualClient.threads.get(sessionId)) as Thread<any>;
}
// 否则 thread 为 undefined,等待第一条消息时由 Client 自动创建
this.sessions.set(sessionId, sessionInfo);
return sessionInfo;
}
/**
* @zh 激活指定会话(懒加载创建 Client)
* @en Activates the specified session (lazy load client)
*/
async activateSession(sessionId: string, mustResetStream = false): Promise<SessionInfo> {
const session: SessionInfo = this.sessions.get(sessionId) || {
sessionId,
agentName: this.virtualClient.getCurrentAssistant()?.graph_id!,
};
// 懒加载:只在激活时创建 Client
if (!session.client) {
const client = new LangGraphClient(this.clientConfig);
await client.initAssistant(session.agentName);
// 只有在有 thread 的情况下才重置(恢复已有会话)
// 新会话的 thread 会在发送第一条消息时自动创建
if (session.thread || mustResetStream) {
await client.resetThread(session.agentName, sessionId);
}
session.client = client;
}
const lastSession = this.activeSessionId && this.sessions.get(this.activeSessionId);
// 空闲的 client 就需要销毁
if (lastSession && lastSession.client?.status === "idle") {
lastSession.client?.reset();
lastSession.client = undefined;
}
this.activeSessionId = sessionId;
return session;
}
/**
* @zh 获取当前活跃的会话
* @en Gets the current active session
*/
getActiveSession(): SessionInfo | null {
if (!this.activeSessionId) {
return null;
}
return this.sessions.get(this.activeSessionId) || null;
}
/**
* @zh 获取指定会话
* @en Gets the specified session
*/
getSession(sessionId: string): SessionInfo | null {
return this.sessions.get(sessionId) || null;
}
/**
* @zh 获取所有会话
* @en Gets all sessions
*/
getAllSessions(): SessionInfo[] {
return Array.from(this.sessions.values());
}
/**
* @zh 删除指定会话
* @en Deletes the specified session
*/
async deleteSession(sessionId: string): Promise<boolean> {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
// 如果删除的是当前活跃会话,清空活跃会话
if (this.activeSessionId === sessionId) {
this.activeSessionId = null;
}
// 删除对应的远程 Thread
try {
await this.virtualClient.deleteThread(sessionId);
} catch (error) {
console.warn(`Failed to delete thread for session ${sessionId}:`, error);
}
this.sessions.delete(sessionId);
return true;
}
/**
* @zh 清空所有会话
* @en Clears all sessions
*/
async clearAllSessions(): Promise<void> {
const sessionIds = Array.from(this.sessions.keys());
for (const sessionId of sessionIds) {
await this.deleteSession(sessionId);
}
this.activeSessionId = null;
}
/**
* @zh 获取会话数量
* @en Gets the number of sessions
*/
getSessionCount(): number {
return this.sessions.size;
}
/**
* @zh 生成会话 ID (UUID v4 格式)
* @en Generates a session ID (UUID v4 format)
*/
private generateSessionId(): string {
// 优先使用 crypto.randomUUID (Node.js 15.6+, 浏览器支持)
if (typeof crypto !== "undefined" && crypto.randomUUID) {
return crypto.randomUUID();
}
// 降级方案:生成符合 UUID v4 格式的随机 ID
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
/**
* @zh 从已有的 Thread 添加会话(仅添加元数据,不创建 Client)
* @en Adds a session from an existing Thread (metadata only, no client created)
*/
async addSessionFromThread(threadId: string, agentName?: string): Promise<SessionInfo> {
const agent = agentName || this.virtualClient.getCurrentAssistant()?.graph_id;
if (!agent) {
throw new Error("Agent name is required. Please call init() first or provide agentName.");
}
return this.createSession({
sessionId: threadId,
agentName: agent,
restore: true,
});
}
/**
* @zh 获取当前活跃的客户端
* @en Gets the current active client
*/
getActiveClient(): LangGraphClient | null {
const session = this.getActiveSession();
return session?.client || null;
}
/**
* @zh 从远程列出所有会话
* @en Lists all sessions from remote
*/
async listRemoteSessions(
options: {
sortOrder?: "asc" | "desc";
sortBy?: "created_at" | "updated_at";
offset?: number;
limit?: number;
} = {}
) {
return this.virtualClient.listThreads(options);
}
/**
* @zh 从远程同步会话到本地(仅同步元数据,不创建 Client)
* @en Syncs sessions from remote to local (metadata only, no client created)
*/
async syncFromRemote(
options: {
limit?: number;
agentName?: string;
} = {}
): Promise<SessionInfo[]> {
const agentName = options.agentName || this.virtualClient.getCurrentAssistant()?.graph_id;
if (!agentName) {
throw new Error("Agent name is required. Please call init() first or provide agentName.");
}
const threads = await this.listRemoteSessions({
limit: options.limit || 10,
sortBy: "updated_at",
sortOrder: "desc",
});
const syncedSessions: SessionInfo[] = [];
for (const thread of threads) {
const threadId = thread.thread_id;
// 更新或创建会话信息
if (this.sessions.has(threadId)) {
const session = this.sessions.get(threadId)!;
session.thread = thread;
syncedSessions.push(session);
} else {
const sessionInfo: SessionInfo = {
sessionId: threadId,
thread,
agentName,
};
this.sessions.set(threadId, sessionInfo);
syncedSessions.push(sessionInfo);
}
}
return syncedSessions;
}
/**
* @zh 初始化 History(必须先调用)
* @en Initializes History (must be called first)
*/
async init(agentName?: string, config?: { fallbackToAvailableAssistants?: boolean }) {
return this.virtualClient.initAssistant(agentName, config);
}
}