切换到使用pg数据库

This commit is contained in:
2026-05-28 18:22:39 +08:00
parent 4c47841483
commit 5ac50bfeaa
38 changed files with 2760 additions and 1331 deletions
+51 -71
View File
@@ -2,20 +2,17 @@ import { randomUUID } from "node:crypto";
import { logger } from "../logger.js";
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
import {
buildToolSessionScopeKey,
ToolSessionContextStore,
} from "../session/toolContextStore.js";
import { RuntimeSessionStore } from "../session/runtimeSessionStore.js";
import { toActorKey, toProjectKey } from "../utils/fileStore.js";
export type SessionBinding = {
clientSessionId: string;
sessionId: string;
runtimeSessionId: string;
startedAt: number;
};
export type SessionContext = {
clientSessionId: string;
sessionId: string;
accessToken?: string;
projectId?: string;
userId?: string;
@@ -28,15 +25,15 @@ export type ChatRequestContext = SessionContext & {
};
export class ChatSessionBridge {
// runtime session 仅在单次请求生命周期内有效;线程连续性由 clientSessionId 对应的持久状态承担。
// runtime session 仅在单次请求生命周期内有效;线程连续性由 sessionId 对应的持久状态承担。
private readonly activeRuntimeSessions = new Map<string, string>();
private readonly activeSensitiveContexts = new Map<string, ChatRequestContext>();
private readonly toolContextStore = new ToolSessionContextStore();
private readonly runtimeSessionStore = new RuntimeSessionStore();
constructor(private readonly runtime: OpencodeRuntimeAdapter) {}
async resolve(context: {
clientSessionId?: string;
sessionId?: string;
accessToken?: string;
projectId?: string;
traceId?: string;
@@ -47,30 +44,24 @@ export class ChatSessionBridge {
created: boolean;
}> {
const requestContext = this.buildRequestContext(context);
await this.abortActiveRuntime(requestContext.clientSessionId);
await this.abortActiveRuntime(requestContext.sessionId);
const session = await this.runtime.createSession(requestContext.clientSessionId);
const session = await this.runtime.createSession(requestContext.sessionId);
const binding: SessionBinding = {
clientSessionId: requestContext.clientSessionId,
sessionId: session.id,
sessionId: requestContext.sessionId,
runtimeSessionId: session.id,
startedAt: Date.now(),
};
const sessionScopeKey = buildToolSessionScopeKey(
requestContext.actorKey,
requestContext.projectKey,
requestContext.clientSessionId,
);
this.activeRuntimeSessions.set(requestContext.clientSessionId, session.id);
this.activeSensitiveContexts.set(sessionScopeKey, requestContext);
await this.toolContextStore.write({
this.activeRuntimeSessions.set(requestContext.sessionId, session.id);
this.activeSensitiveContexts.set(session.id, requestContext);
await this.runtimeSessionStore.write({
runtimeSessionId: session.id,
actorKey: requestContext.actorKey,
allowLearningWrite: true,
clientSessionId: requestContext.clientSessionId,
sessionId: requestContext.sessionId,
learningMode: "interactive",
projectId: requestContext.projectId,
projectKey: requestContext.projectKey,
sessionId: session.id,
sessionScopeKey,
traceId: requestContext.traceId,
});
@@ -81,58 +72,59 @@ export class ChatSessionBridge {
return this.activeRuntimeSessions.size;
}
createClientSessionId() {
createSessionId() {
return `agent-${randomUUID().slice(0, 12)}`;
}
getActiveSensitiveContext(sessionScopeKey: string) {
return this.activeSensitiveContexts.get(sessionScopeKey) ?? null;
getActiveSensitiveContext(runtimeSessionId: string) {
return this.activeSensitiveContexts.get(runtimeSessionId) ?? null;
}
async abort(context: {
clientSessionId?: string;
}): Promise<SessionBinding | null> {
const clientSessionId = context.clientSessionId?.trim();
if (!clientSessionId) {
return null;
}
const sessionId = this.activeRuntimeSessions.get(clientSessionId);
async abort(context: { sessionId?: string }): Promise<SessionBinding | null> {
const sessionId = context.sessionId?.trim();
if (!sessionId) {
return null;
}
await this.abortActiveRuntime(clientSessionId);
const runtimeSessionId = this.activeRuntimeSessions.get(sessionId);
if (!runtimeSessionId) {
return null;
}
await this.abortActiveRuntime(sessionId);
return {
clientSessionId,
sessionId,
runtimeSessionId,
startedAt: Date.now(),
};
}
async releaseRuntimeSession(clientSessionId: string, sessionId: string) {
const activeSessionId = this.activeRuntimeSessions.get(clientSessionId);
if (activeSessionId === sessionId) {
this.activeRuntimeSessions.delete(clientSessionId);
async releaseRuntimeSession(sessionId: string, runtimeSessionId: string) {
const activeSessionId = this.activeRuntimeSessions.get(sessionId);
if (activeSessionId === runtimeSessionId) {
this.activeRuntimeSessions.delete(sessionId);
}
this.activeSensitiveContexts.delete(findScopeKey(this.activeSensitiveContexts, clientSessionId));
await this.toolContextStore.remove(sessionId).catch((error) => {
logger.debug({ sessionId, err: error }, "failed to cleanup runtime tool context");
this.activeSensitiveContexts.delete(runtimeSessionId);
await this.runtimeSessionStore.release(runtimeSessionId).catch((error) => {
logger.debug(
{ runtimeSessionId, err: error },
"failed to cleanup persisted runtime session",
);
});
await this.runtime.abortSession(sessionId).catch((error) => {
logger.debug({ sessionId, err: error }, "failed to cleanup runtime session");
await this.runtime.abortSession(runtimeSessionId).catch((error) => {
logger.debug({ runtimeSessionId, err: error }, "failed to cleanup runtime session");
});
}
private buildRequestContext(context: {
clientSessionId?: string;
sessionId?: string;
accessToken?: string;
projectId?: string;
traceId?: string;
userId?: string;
}): ChatRequestContext {
return {
clientSessionId: context.clientSessionId?.trim() || this.createClientSessionId(),
sessionId: context.sessionId?.trim() || this.createSessionId(),
accessToken: context.accessToken,
actorKey: toActorKey(context.userId),
projectId: context.projectId,
@@ -142,37 +134,25 @@ export class ChatSessionBridge {
};
}
private async abortActiveRuntime(clientSessionId: string) {
const activeSessionId = this.activeRuntimeSessions.get(clientSessionId);
if (!activeSessionId) {
private async abortActiveRuntime(sessionId: string) {
const activeRuntimeSessionId = this.activeRuntimeSessions.get(sessionId);
if (!activeRuntimeSessionId) {
return;
}
this.activeRuntimeSessions.delete(clientSessionId);
this.activeSensitiveContexts.delete(findScopeKey(this.activeSensitiveContexts, clientSessionId));
await this.toolContextStore.remove(activeSessionId).catch(() => undefined);
await this.runtime.abortSession(activeSessionId).catch((error) => {
this.activeRuntimeSessions.delete(sessionId);
this.activeSensitiveContexts.delete(activeRuntimeSessionId);
await this.runtimeSessionStore.release(activeRuntimeSessionId).catch(() => undefined);
await this.runtime.abortSession(activeRuntimeSessionId).catch((error) => {
logger.warn(
{ clientSessionId, sessionId: activeSessionId, err: error },
{ sessionId, runtimeSessionId: activeRuntimeSessionId, err: error },
"failed to abort previous active runtime session",
);
});
await this.runtime.waitForSessionIdle(activeSessionId).catch((error) => {
await this.runtime.waitForSessionIdle(activeRuntimeSessionId).catch((error) => {
logger.warn(
{ clientSessionId, sessionId: activeSessionId, err: error },
{ sessionId, runtimeSessionId: activeRuntimeSessionId, err: error },
"failed while waiting for previous runtime session to become idle",
);
});
}
}
const findScopeKey = (
contexts: Map<string, ChatRequestContext>,
clientSessionId: string,
) => {
for (const [scopeKey, context] of contexts.entries()) {
if (context.clientSessionId === clientSessionId) {
return scopeKey;
}
}
return clientSessionId;
};