feat: initialize experimental agent repo
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import { logger } from "../logger.js";
|
||||
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
|
||||
import {
|
||||
removeRuntimeSessionContext,
|
||||
setRuntimeSessionContext,
|
||||
} from "../runtime/sessionContext.js";
|
||||
import { toActorKey, toProjectKey } from "../utils/fileStore.js";
|
||||
|
||||
export type SessionBinding = {
|
||||
clientSessionId: string;
|
||||
sessionId: string;
|
||||
startedAt: number;
|
||||
};
|
||||
|
||||
export type SessionContext = {
|
||||
clientSessionId: string;
|
||||
network?: string;
|
||||
projectId?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
export type ChatRequestContext = SessionContext & {
|
||||
actorKey: string;
|
||||
projectKey: string;
|
||||
traceId: string;
|
||||
};
|
||||
|
||||
export class ChatSessionBridge {
|
||||
private readonly abortControllers = new Map<string, AbortController>();
|
||||
|
||||
constructor(private readonly runtime: OpencodeRuntimeAdapter) {}
|
||||
|
||||
async resolve(context: {
|
||||
sessionId?: string;
|
||||
network?: string;
|
||||
projectId?: string;
|
||||
traceId?: string;
|
||||
userId?: string;
|
||||
}): Promise<{
|
||||
binding: SessionBinding;
|
||||
requestContext: ChatRequestContext;
|
||||
created: boolean;
|
||||
}> {
|
||||
let requestContext = this.buildRequestContext(context);
|
||||
const existingSessionId = context.sessionId?.trim();
|
||||
await this.abortActiveRuntime(requestContext.clientSessionId, existingSessionId);
|
||||
|
||||
let sessionId = existingSessionId;
|
||||
let created = false;
|
||||
if (!sessionId) {
|
||||
const session = await this.runtime.createSession();
|
||||
sessionId = session.id;
|
||||
requestContext = {
|
||||
...requestContext,
|
||||
clientSessionId: sessionId,
|
||||
};
|
||||
created = true;
|
||||
}
|
||||
const binding: SessionBinding = {
|
||||
clientSessionId: requestContext.clientSessionId,
|
||||
sessionId,
|
||||
startedAt: Date.now(),
|
||||
};
|
||||
setRuntimeSessionContext({
|
||||
actorKey: requestContext.actorKey,
|
||||
allowLearningWrite: true,
|
||||
clientSessionId: requestContext.clientSessionId,
|
||||
learningMode: "interactive",
|
||||
network: requestContext.network,
|
||||
projectId: requestContext.projectId,
|
||||
projectKey: requestContext.projectKey,
|
||||
sessionId,
|
||||
traceId: requestContext.traceId,
|
||||
});
|
||||
|
||||
return { binding, requestContext, created };
|
||||
}
|
||||
|
||||
count(): number {
|
||||
return this.abortControllers.size;
|
||||
}
|
||||
|
||||
createClientSessionId() {
|
||||
return `agent-${randomUUID().slice(0, 12)}`;
|
||||
}
|
||||
|
||||
registerAbortController(clientSessionId: string, controller: AbortController) {
|
||||
this.abortControllers.set(clientSessionId, controller);
|
||||
}
|
||||
|
||||
finalizeRequest(clientSessionId: string) {
|
||||
this.abortControllers.delete(clientSessionId);
|
||||
}
|
||||
|
||||
async abort(context: {
|
||||
clientSessionId?: string;
|
||||
sessionId?: string;
|
||||
}): Promise<SessionBinding | null> {
|
||||
const clientSessionId = context.clientSessionId?.trim();
|
||||
const sessionId = context.sessionId?.trim();
|
||||
if (!clientSessionId || !sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.abortActiveRuntime(clientSessionId, sessionId);
|
||||
return {
|
||||
clientSessionId,
|
||||
sessionId,
|
||||
startedAt: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
async deleteSession(context: {
|
||||
clientSessionId: string;
|
||||
sessionId: string;
|
||||
}) {
|
||||
const clientSessionId = context.clientSessionId.trim();
|
||||
const sessionId = context.sessionId.trim();
|
||||
const controller = this.abortControllers.get(clientSessionId);
|
||||
if (controller) {
|
||||
this.abortControllers.delete(clientSessionId);
|
||||
controller.abort();
|
||||
}
|
||||
await this.runtime.abortSession(sessionId).catch((error) => {
|
||||
logger.warn(
|
||||
{ clientSessionId, sessionId, err: error },
|
||||
"failed to abort runtime session",
|
||||
);
|
||||
});
|
||||
await this.runtime.waitForSessionIdle(sessionId).catch((error) => {
|
||||
logger.warn(
|
||||
{ clientSessionId, sessionId, err: error },
|
||||
"failed while waiting for runtime session to become idle",
|
||||
);
|
||||
});
|
||||
removeRuntimeSessionContext(sessionId);
|
||||
}
|
||||
|
||||
private buildRequestContext(context: {
|
||||
sessionId?: string;
|
||||
network?: string;
|
||||
projectId?: string;
|
||||
traceId?: string;
|
||||
userId?: string;
|
||||
}): ChatRequestContext {
|
||||
const sessionId = context.sessionId?.trim();
|
||||
return {
|
||||
clientSessionId: sessionId || this.createClientSessionId(),
|
||||
actorKey: toActorKey(context.userId),
|
||||
network: context.network,
|
||||
projectId: context.projectId,
|
||||
projectKey: toProjectKey(context.projectId),
|
||||
traceId: context.traceId?.trim() || `trace-${randomUUID().slice(0, 12)}`,
|
||||
userId: context.userId?.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
private async abortActiveRuntime(clientSessionId: string, sessionId?: string) {
|
||||
const controller = this.abortControllers.get(clientSessionId);
|
||||
if (controller) {
|
||||
this.abortControllers.delete(clientSessionId);
|
||||
controller.abort();
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
await this.runtime.abortSession(sessionId).catch((error) => {
|
||||
logger.warn(
|
||||
{ clientSessionId, sessionId, err: error },
|
||||
"failed to abort active runtime session",
|
||||
);
|
||||
});
|
||||
await this.runtime.waitForSessionIdle(sessionId).catch((error) => {
|
||||
logger.warn(
|
||||
{ clientSessionId, sessionId, err: error },
|
||||
"failed while waiting for active runtime session to become idle",
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user