import { randomUUID } from "node:crypto"; import { logger } from "../logger.js"; export class ChatSessionBridge { registry; runtime; // 这里额外保存 session -> 用户上下文,供工具桥在服务端代发真实后端请求时复用。 sessionContexts = new Map(); constructor(registry, runtime) { this.registry = registry; this.runtime = runtime; } async resolve(context) { const requestContext = { conversationId: context.conversationId?.trim() || `conv-${randomUUID().slice(0, 12)}`, accessToken: context.accessToken, projectId: context.projectId, traceId: context.traceId?.trim() || `trace-${randomUUID().slice(0, 12)}`, }; this.cleanupExpired(); const current = this.registry.get(requestContext); if (current) { this.sessionContexts.set(current.sessionId, requestContext); try { // 只有 opencode 侧 session 仍存在时,才复用本地映射。 await this.runtime.getSession(current.sessionId); return { binding: current, requestContext, created: false }; } catch (error) { logger.warn({ conversationId: requestContext.conversationId, sessionId: current.sessionId, err: error, }, "existing opencode session lookup failed, creating a new session"); } } const session = await this.runtime.createSession(requestContext.conversationId); const binding = this.registry.upsert(requestContext, session.id); this.sessionContexts.set(binding.sessionId, requestContext); return { binding, requestContext, created: true }; } count() { return this.registry.count(); } getSessionContext(sessionId) { return this.sessionContexts.get(sessionId) ?? null; } cleanupExpired() { const expiredSessionIds = this.registry.evictExpired(); for (const sessionId of expiredSessionIds) { this.sessionContexts.delete(sessionId); // 这里用 abort 做轻量清理;即使失败,也不阻断本地过期回收。 void this.runtime.abortSession(sessionId).catch((error) => { logger.debug({ sessionId, err: error }, "ignoring failed abort for expired session"); }); } } }