init
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import { logger } from "../logger.js";
|
||||
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
|
||||
import { type SessionBinding, type SessionContext, SessionRegistry } from "../session/registry.js";
|
||||
|
||||
export type ChatRequestContext = SessionContext & {
|
||||
traceId: string;
|
||||
};
|
||||
|
||||
export class ChatSessionBridge {
|
||||
// 这里额外保存 session -> 用户上下文,供工具桥在服务端代发真实后端请求时复用。
|
||||
private readonly sessionContexts = new Map<string, ChatRequestContext>();
|
||||
|
||||
constructor(
|
||||
private readonly registry: SessionRegistry,
|
||||
private readonly runtime: OpencodeRuntimeAdapter,
|
||||
) {}
|
||||
|
||||
async resolve(context: {
|
||||
conversationId?: string;
|
||||
accessToken?: string;
|
||||
projectId?: string;
|
||||
traceId?: string;
|
||||
}): Promise<{
|
||||
binding: SessionBinding;
|
||||
requestContext: ChatRequestContext;
|
||||
created: boolean;
|
||||
}> {
|
||||
const requestContext: ChatRequestContext = {
|
||||
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(): number {
|
||||
return this.registry.count();
|
||||
}
|
||||
|
||||
getSessionContext(sessionId: string) {
|
||||
return this.sessionContexts.get(sessionId) ?? null;
|
||||
}
|
||||
|
||||
cleanupExpired(): void {
|
||||
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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user