359 lines · 14.1 KB
import { DurableObject, env } from "cloudflare:workers";
import { loadRepoById } from "../repos";
import { credentialAllows, patCredentialById, tokenIsLive } from "../token-scope";
import { readableEventTypes } from "./event-access";
import { Coordination } from "./coordination";
import type { ClaimRequest, ClaimResult, Lease, LeaseKind, RepoEvent } from "./coordination";
import { SessionLog, sseFrame } from "./hub-session";
import type { SessionInfo } from "./hub-session";
import { META, notification } from "./protocol";
import type { JsonRpcMessage, McpCaller } from "./protocol";

const INFO_KEY = "session";
const SESSION_IDLE_MS = 24 * 60 * 60 * 1000;
const STREAM_MAX_MS = 5 * 60 * 1000;
const KEEPALIVE_MS = 25 * 1000;
const RETRY_MS = 2000;
const SUBSCRIPTION_TTL_MS = SESSION_IDLE_MS;
const MAX_SOCKETS = 4;
const MAX_STREAMS = 4;
const encoder = new TextEncoder();
/** How long a session trusts a granted read check before re-reading the token and repo from D1. */
export const ACCESS_CACHE_MS = 60 * 1000;

type AccessEntry = { at: number; tokenId: string; types: Set<string> };

type Stream = {
  writer: WritableStreamDefaultWriter<Uint8Array>;
  timer: ReturnType<typeof setInterval>;
  deadline: ReturnType<typeof setTimeout> | null;
  onEnd: (() => void) | null;
};

export function sessionHub(sessionId: string) {
  return env.MCP_HUB.get(env.MCP_HUB.idFromName(`session:${sessionId}`));
}

/** Quota key for subscriber slots: always the user, so minting more tokens never buys more slots. */
export function subscriberOwner(caller: Pick<McpCaller, "viewer">): string {
  return caller.viewer.id;
}