import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import { getAgentByName, routeAgentRequest } from "agents";
import {
loadCustomerConfig,
loadCustomerSecrets,
} from "../configuration/customer";
import { ConfigurationError } from "../configuration/validation";
import { handleCustomerBridge } from "./bridge";
import type { Env } from "./personal-agent";
import { authorizeRuntimeRequest, privateResponse } from "./session";
import { PERSONAL_PATH, conversationIdFromPath } from "./runtime-path";
import { isMcpAuthPath } from "./mcp-http";
import { updateOnOwnerVisit } from "./update-on-visit";
class RuntimeUnavailable extends Data.TaggedError("RuntimeUnavailable") {}
function runtimeCall(operation: () => PromiseLike) {
return Effect.tryPromise({
try: operation,
catch: () => new RuntimeUnavailable(),
});
}
function configuredDomain(env: Env) {
return Effect.gen(function* () {
const personal = yield* runtimeCall(() =>
getAgentByName(env.PersonalAgent, "personal"),
);
return yield* runtimeCall(() => personal.configuredDomainOrigin());
});
}
function routePersonal(
request: Request,
env: Env,
conversationId: string | undefined,
) {
return Effect.gen(function* () {
// Readiness must fail before the native router accepts an error-reporting socket.
const personal = yield* runtimeCall(() =>
getAgentByName(env.PersonalAgent, "personal"),
);
if (
conversationId &&
!(yield* runtimeCall(() => personal.prepareConversation(conversationId)))
)
return privateResponse("Not found", 404);
const response = yield* runtimeCall(() =>
routeAgentRequest(request, { PersonalAgent: env.PersonalAgent }),
);
if (!response || response.status >= 500)
return privateResponse("Personal runtime unavailable", 503);
if (response.status === 101) return response;
const reply = new Response(response.body, response);
reply.headers.set("Cache-Control", "no-store");
return reply;
}).pipe(
Effect.catchTag("RuntimeUnavailable", () =>
Effect.succeed(privateResponse("Personal runtime unavailable", 503)),
),
);
}
export function handleRuntimeRequest(
request: Request,
env: Env,
render: () => Promise,
) {
return Effect.gen(function* () {
let config;
let secrets;
try {
config = loadCustomerConfig(env);
secrets = loadCustomerSecrets(env);
} catch (error) {
if (!(error instanceof ConfigurationError)) throw error;
console.error(error.message);
return privateResponse(
"Flarebot configuration is invalid. Check Worker configuration logs.",
503,
);
}
const management = config.effectiveInstallation;
const url = new URL(request.url);
let installation = management;
let approvedDomain: string | null = null;
if (url.origin !== management.runtimeOrigin) {
const domain = yield* Effect.exit(configuredDomain(env));
if (Exit.isFailure(domain)) return privateResponse("Forbidden", 403);
approvedDomain = domain.value;
if (approvedDomain === url.origin)
installation = { ...management, runtimeOrigin: url.origin };
}
const bridge = yield* handleCustomerBridge(
request,
env,
installation,
secrets.sessionSecret,
);
if (bridge) return bridge;
const update = yield* updateOnOwnerVisit(
request,
installation,
secrets.sessionSecret,
config.environment === "production",
);
if (update) return update;
const path = url.pathname;
if (path === "/api/domain" && request.method === "GET") {
const denied = yield* authorizeRuntimeRequest(
request,
secrets.sessionSecret,
installation,
);
if (denied) return denied;
const origin = approvedDomain ?? (yield* configuredDomain(env));
const setup = new URL("/connect", installation.controlPlaneOrigin);
setup.searchParams.set("configureDomain", installation.installationId);
return Response.json(
{ setupUrl: setup.href, origin },
{ headers: { "Cache-Control": "no-store" } },
);
}
if (
path.startsWith("/agents/") ||
path === "/api" ||
path.startsWith("/api/")
) {
const denied = yield* authorizeRuntimeRequest(
request,
secrets.sessionSecret,
installation,
);
if (denied) return denied;
if (isMcpAuthPath(path)) {
const personal = yield* runtimeCall(() =>
getAgentByName(env.PersonalAgent, "personal"),
);
return yield* runtimeCall(() => personal.mcpAuthRequest(request));
}
if (path.startsWith("/api/skills/")) {
const personal = yield* runtimeCall(() =>
getAgentByName(env.PersonalAgent, "personal"),
);
return yield* runtimeCall(() => personal.skillRequest(request));
}
const attachmentRoute =
/^\/api\/conversations\/([0-9a-f-]{36})\/attachments(?:\/([0-9a-f-]{36}))?$/.exec(
path,
);
if (attachmentRoute) {
const personal = yield* runtimeCall(() =>
getAgentByName(env.PersonalAgent, "personal"),
);
if (
!(yield* runtimeCall(() =>
personal.prepareConversation(attachmentRoute[1]),
))
)
return privateResponse("Not found", 404);
return yield* runtimeCall(() =>
personal.attachmentRequest(attachmentRoute[1], request),
);
}
const conversationId = conversationIdFromPath(path);
if (
path !== PERSONAL_PATH &&
path !== `${PERSONAL_PATH}/status` &&
!conversationId
)
return privateResponse("Not found", 404);
if (conversationId && request.method !== "GET")
return privateResponse("Method not allowed", 405);
return yield* routePersonal(request, env, conversationId);
}
return yield* Effect.promise(render);
});
}