containerManager.ts•1.89 kB
import { DurableObject } from 'cloudflare:workers'
import { getEnv } from '@repo/mcp-common/src/env'
import { MetricsTracker } from '@repo/mcp-observability'
import { ContainerEvent } from './metrics'
import type { Env } from './sandbox.server.context'
const env = getEnv<Env>()
export class ContainerManager extends DurableObject<Env> {
	metrics = new MetricsTracker(env.MCP_METRICS, {
		name: env.MCP_SERVER_NAME,
		version: env.MCP_SERVER_VERSION,
	})
	constructor(
		public ctx: DurableObjectState,
		public env: Env
	) {
		super(ctx, env)
	}
	async trackContainer(id: string) {
		await this.ctx.storage.put(id, new Date())
	}
	async killContainer(id: string) {
		await this.ctx.storage.delete(id)
	}
	async tryKillOldContainers() {
		const activeContainers = await this.ctx.storage.list<Date>()
		for (const c of activeContainers) {
			const id = c[0]
			const now = new Date()
			const time = c[1]
			console.log(id, time, now, now.valueOf() - time.valueOf())
			// 15m timeout for container lifetime
			if (now.valueOf() - time.valueOf() > 15 * 60 * 1000) {
				await this.killContainer(id)
				// TODO: Figure out why we were running in to invalid durable object id the id does not match this durable object class error
				const doId = this.env.USER_CONTAINER.idFromString(id)
				const stub = this.env.USER_CONTAINER.get(doId)
				await stub.destroyContainer()
			}
		}
	}
	async listActive(): Promise<string[]> {
		const activeContainers = await this.ctx.storage.list()
		const activeIds: string[] = []
		for (const c of activeContainers.keys()) {
			activeIds.push(c)
		}
		this.metrics.logEvent(
			new ContainerEvent({
				active: activeIds.length,
			})
		)
		return activeIds
	}
}
export function getContainerManager(env: Env): DurableObjectStub<ContainerManager> {
	const id = env.CONTAINER_MANAGER.idFromName('manager')
	return env.CONTAINER_MANAGER.get(id)
}