import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema"; import { resolveAttachmentPathById } from "../attachmentStore.ts";import * as ServerConfig from "../config.ts";import * as TerminalManager from "../terminal/Manager.ts"; export class ResourceCleanupError extends Schema.TaggedErrorClass<ResourceCleanupError>()( "ResourceCleanupError", { operation: Schema.Literals(["terminal", "attachment"]), threadId: Schema.optional(Schema.String), attachmentId: Schema.optional(Schema.String), cause: Schema.Defect(), },) {} export class ResourceCleanupService extends Context.Reference<{ readonly cleanupTerminals: (threadId: string) => Effect.Effect<void, ResourceCleanupError>; readonly cleanupAttachments: ( attachmentIds: ReadonlyArray<string>, ) => Effect.Effect<void, ResourceCleanupError>;}>("t3/orchestration-v2/ResourceCleanupService", { defaultValue: () => ({ cleanupTerminals: () => Effect.void, cleanupAttachments: () => Effect.void, }),}) {} export const live = Layer.effect( ResourceCleanupService, Effect.gen(function* () { const terminals = yield* TerminalManager.TerminalManager; const fileSystem = yield* FileSystem.FileSystem; const config = yield* ServerConfig.ServerConfig; return { cleanupTerminals: (threadId: string) => terminals .close({ threadId, deleteHistory: true }) .pipe( Effect.mapError( (cause) => new ResourceCleanupError({ operation: "terminal", threadId, cause }), ), ), cleanupAttachments: (attachmentIds: ReadonlyArray<string>) => Effect.forEach( attachmentIds, (attachmentId) => { const path = resolveAttachmentPathById({ attachmentsDir: config.attachmentsDir, attachmentId, }); return path === null ? Effect.void : fileSystem .remove(path, { force: true }) .pipe( Effect.mapError( (cause) => new ResourceCleanupError({ operation: "attachment", attachmentId, cause }), ), ); }, { discard: true, concurrency: 4 }, ), }; }),);
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import { resolveAttachmentPathById } from "../attachmentStore.ts";
import * as ServerConfig from "../config.ts";
import * as TerminalManager from "../terminal/Manager.ts";
export class ResourceCleanupError extends Schema.TaggedErrorClass<ResourceCleanupError>()(
"ResourceCleanupError",
{
operation: Schema.Literals(["terminal", "attachment"]),
threadId: Schema.optional(Schema.String),
attachmentId: Schema.optional(Schema.String),
cause: Schema.Defect(),
},
) {}
export class ResourceCleanupService extends Context.Reference<{
readonly cleanupTerminals: (threadId: string) => Effect.Effect<void, ResourceCleanupError>;
readonly cleanupAttachments: (
attachmentIds: ReadonlyArray<string>,
) => Effect.Effect<void, ResourceCleanupError>;
}>("t3/orchestration-v2/ResourceCleanupService", {
defaultValue: () => ({
cleanupTerminals: () => Effect.void,
cleanupAttachments: () => Effect.void,
}),
}) {}
export const live = Layer.effect(
ResourceCleanupService,
Effect.gen(function* () {
const terminals = yield* TerminalManager.TerminalManager;
const fileSystem = yield* FileSystem.FileSystem;
const config = yield* ServerConfig.ServerConfig;
return {
cleanupTerminals: (threadId: string) =>
terminals
.close({ threadId, deleteHistory: true })
.pipe(
Effect.mapError(
(cause) => new ResourceCleanupError({ operation: "terminal", threadId, cause }),
),
),
cleanupAttachments: (attachmentIds: ReadonlyArray<string>) =>
Effect.forEach(
attachmentIds,
(attachmentId) => {
const path = resolveAttachmentPathById({
attachmentsDir: config.attachmentsDir,
attachmentId,
});
return path === null
? Effect.void
: fileSystem
.remove(path, { force: true })
.pipe(
Effect.mapError(
(cause) =>
new ResourceCleanupError({ operation: "attachment", attachmentId, cause }),
),
);
},
{ discard: true, concurrency: 4 },
),
};
}),
);