import {
  syncAndCreateSession
} from "./chunk-NHIJESXU.js";
import {
  isDebugPromptRequested
} from "./chunk-5POPOYGQ.js";
import {
  resolveRemoteSessionId,
  upsertSync
} from "./chunk-WU2SRT5G.js";
import {
  interruptSession
} from "./chunk-DC2UMEQH.js";
import {
  getMemoryStore
} from "./chunk-2UKAINAN.js";
import {
  kickoffEnvironmentSetup
} from "./chunk-35RWSC6V.js";
import {
  assertResourceTenant,
  tenantFilter
} from "./chunk-K7DLILHK.js";
import {
  decodeCursor,
  jsonOk,
  paginatedOk,
  routeWrap
} from "./chunk-ZMMFMEXX.js";
import {
  forwardToAnthropic
} from "./chunk-VFSHH6I2.js";
import {
  getProxiedTenantId,
  isProxied,
  markProxied,
  unmarkProxied
} from "./chunk-DZIKCTP6.js";
import {
  dropActor,
  getActor
} from "./chunk-LAWTTG2E.js";
import {
  releaseSession
} from "./chunk-JUNQ5KRS.js";
import {
  appendEvent,
  dropEmitter
} from "./chunk-4NRHKTBW.js";
import {
  archiveSession,
  createSession,
  getSession,
  listSessions,
  setOutcomeCriteria,
  updateSessionMutable,
  updateSessionStatus
} from "./chunk-SEGKFEG5.js";
import {
  getEnvironment
} from "./chunk-GTYDSYTR.js";
import {
  getAgent
} from "./chunk-LGPJPAHU.js";
import {
  init_clock,
  nowMs
} from "./chunk-HFDLUBWN.js";
import {
  getDb,
  init_client
} from "./chunk-TLEKKGH7.js";
import {
  isAnthropicOAuthToken
} from "./chunk-J5Z5DSH4.js";
import {
  badRequest,
  notFound
} from "./chunk-EZYKRG4W.js";
import {
  __require
} from "./chunk-2ESYSVXG.js";

// src/handlers/anthropic-compat/sessions.ts
import { z } from "zod";
init_client();
init_clock();
function getAgentTenantId(id) {
  const row = getDb().prepare(`SELECT tenant_id FROM agents WHERE id = ?`).get(id);
  return row?.tenant_id;
}
function getEnvironmentTenantId(id) {
  const row = getDb().prepare(`SELECT tenant_id FROM environments WHERE id = ?`).get(id);
  return row?.tenant_id;
}
function getSessionTenantId(id) {
  const row = getDb().prepare(`SELECT tenant_id FROM sessions WHERE id = ?`).get(id);
  return row?.tenant_id;
}
function loadSessionForCaller(auth, id) {
  const tenantId = getSessionTenantId(id);
  if (tenantId === void 0) throw notFound(`session ${id} not found`);
  assertResourceTenant(auth, tenantId, `session ${id} not found`);
  const session = getSession(id);
  if (!session) throw notFound(`session ${id} not found`);
  return session;
}
function assertProxiedSessionTenant(auth, id) {
  const local = getSessionTenantId(id);
  if (local !== void 0) {
    assertResourceTenant(auth, local, `session ${id} not found`);
    return;
  }
  const proxied = getProxiedTenantId(id);
  if (proxied === void 0) return;
  assertResourceTenant(auth, proxied, `session ${id} not found`);
}
var ALLOWED_STATUSES = ["idle", "running", "rescheduling", "terminated"];
var AgentRef = z.union([
  z.string(),
  z.object({ id: z.string(), version: z.number().int(), type: z.literal("agent").optional() })
]);
var ResourceSchema = z.object({
  type: z.enum(["uri", "text", "file", "github_repository", "memory_store"]),
  uri: z.string().optional(),
  content: z.string().optional(),
  file_id: z.string().optional(),
  mount_path: z.string().optional(),
  // Accept both internal (repository_url, branch, commit) and
  // Anthropic API (url, checkout, authorization_token) field names.
  url: z.string().optional(),
  repository_url: z.string().optional(),
  branch: z.string().optional(),
  commit: z.string().optional(),
  checkout: z.object({
    type: z.string(),
    name: z.string().optional(),
    sha: z.string().optional()
  }).optional(),
  authorization_token: z.string().optional(),
  // Memory store resource fields
  memory_store_id: z.string().optional(),
  access: z.enum(["read_only", "read_write"]).optional(),
  instructions: z.string().optional()
});
var CreateSchema = z.object({
  agent: AgentRef,
  environment_id: z.string(),
  title: z.string().nullish(),
  metadata: z.record(z.unknown()).optional(),
  max_budget_usd: z.number().positive().optional(),
  max_tokens: z.number().int().positive().optional(),
  max_wall_duration_ms: z.number().int().positive().optional(),
  resources: z.array(ResourceSchema).optional(),
  vault_ids: z.array(z.string()).optional(),
  user_profile_id: z.string().optional(),
  outcomes: z.object({
    description: z.string().min(1),
    rubric: z.array(z.object({
      name: z.string(),
      description: z.string(),
      weight: z.number().optional()
    })).optional(),
    max_iterations: z.number().int().min(1).max(10).optional()
  }).optional()
});
var UpdateSchema = z.object({
  title: z.string().nullish(),
  metadata: z.record(z.unknown()).optional(),
  vault_ids: z.array(z.string()).optional()
});
function shouldFallback(err) {
  const msg = err instanceof Error ? err.message : String(err);
  if (/environment is not ready/i.test(msg)) return true;
  const upstreamMatch = /Anthropic API .* failed \((\d+)\)/i.exec(msg);
  if (upstreamMatch) {
    const code = Number(upstreamMatch[1]);
    if (code >= 500 || code === 429) return true;
    return false;
  }
  try {
    const { classifyError } = __require("../sessions/errors");
    return classifyError(msg).retryable;
  } catch {
    return false;
  }
}
function parseFallbackJson(raw) {
  if (!raw) return [];
  try {
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) return [];
    const out = [];
    for (const item of parsed) {
      if (item && typeof item.agent_id === "string" && typeof item.environment_id === "string") {
        out.push({ agent_id: item.agent_id, environment_id: item.environment_id });
      }
    }
    return out;
  } catch {
    return [];
  }
}
function handleCreateSession(request) {
  return routeWrap(request, async ({ auth }) => {
    const rawBody = await request.text();
    const body = rawBody ? JSON.parse(rawBody) : null;
    const parsed = CreateSchema.safeParse(body);
    if (!parsed.success) throw badRequest(parsed.error.message);
    const data = parsed.data;
    const debugCapture = isDebugPromptRequested(request);
    const initialAgentId = typeof data.agent === "string" ? data.agent : data.agent.id;
    if (isProxied(initialAgentId)) {
      const { getProxiedTenantId: getProxiedTenantId2 } = await import("./db/proxy.js");
      const agentTenant = getProxiedTenantId2(initialAgentId);
      assertResourceTenant(auth, agentTenant ?? null, `agent not found: ${initialAgentId}`);
      const proxyRes = await forwardToAnthropic(request, "/v1/sessions", { body: rawBody });
      if (proxyRes.ok) {
        try {
          const data2 = await proxyRes.clone().json();
          markProxied(data2.id, "session", agentTenant ?? null);
        } catch {
        }
      }
      return proxyRes;
    }
    const initialAgentVersion = typeof data.agent === "string" ? void 0 : data.agent.version;
    const { checkResourceScope } = await import("./auth/scope.js");
    async function tryCreate(agentId, envId, agentVersion) {
      const agentTenantId = getAgentTenantId(agentId);
      if (agentTenantId === void 0) throw notFound(`agent not found: ${agentId}`);
      assertResourceTenant(auth, agentTenantId, `agent not found: ${agentId}`);
      const envTenantId = getEnvironmentTenantId(envId);
      if (envTenantId === void 0) throw notFound(`environment not found: ${envId}`);
      assertResourceTenant(auth, envTenantId, `environment not found: ${envId}`);
      if (agentTenantId !== envTenantId) {
        throw badRequest(
          `agent and environment belong to different tenants (${agentTenantId} vs ${envTenantId})`
        );
      }
      const agent = getAgent(agentId, agentVersion);
      if (!agent) throw notFound(`agent not found: ${agentId}`);
      const env = getEnvironment(envId);
      if (!env) throw notFound(`environment not found: ${envId}`);
      checkResourceScope(auth, {
        agent: agent.id,
        env: env.id,
        vaults: data.vault_ids
      });
      if ((env.config?.type === "cloud" || env.config?.provider === "anthropic") && agent.engine !== "claude") {
        throw badRequest(
          `${agent.engine} engine cannot run on the anthropic provider \u2014 Anthropic's managed agents API only supports Claude models. Use a container provider (docker, e2b, fly, etc.) instead.`
        );
      }
      if (data.vault_ids?.length) {
        for (const vid of data.vault_ids) {
          const vaultRow = getDb().prepare(`SELECT agent_id, tenant_id FROM vaults WHERE id = ?`).get(vid);
          if (!vaultRow) throw badRequest(`vault not found: ${vid}`);
          assertResourceTenant(auth, vaultRow.tenant_id, `vault not found: ${vid}`);
          if (vaultRow.agent_id && vaultRow.agent_id !== agent.id) {
            throw badRequest(
              `vault ${vid} belongs to a different agent \u2014 vaults are scoped per-agent`
            );
          }
        }
      }
      if (data.user_profile_id) {
        const { getUserProfile } = await import("./db/user-profiles.js");
        const profile = getUserProfile(data.user_profile_id);
        if (!profile) throw badRequest(`user profile not found: ${data.user_profile_id}`);
        if (profile.tenant_id && profile.tenant_id !== agentTenantId) {
          throw badRequest(`user profile does not belong to this tenant`);
        }
      }
      if (data.resources?.length) {
        const memStoreResources = data.resources.filter((r) => r.type === "memory_store");
        if (memStoreResources.length > 8) {
          throw badRequest(`max 8 memory_store resources per session (got ${memStoreResources.length})`);
        }
        for (const r of memStoreResources) {
          if (!r.memory_store_id) {
            throw badRequest(`memory_store resource requires memory_store_id`);
          }
          const store = getMemoryStore(r.memory_store_id);
          if (!store) throw notFound(`memory store not found: ${r.memory_store_id}`);
          if (store.archived_at) throw badRequest(`memory store ${r.memory_store_id} is archived`);
        }
      }
      if (env.config?.type === "cloud" || env.config?.provider === "anthropic") {
        const { resolveAnthropicKey } = await import("./providers/upstream-keys.js");
        const resolved = resolveAnthropicKey({ vaultIds: data.vault_ids ?? void 0 });
        if (!resolved) {
          const { getConfig } = await import("./config/index.js");
          const { listEntries } = await import("./db/vaults.js");
          const cfg = getConfig();
          let hasOAuth = isAnthropicOAuthToken(cfg.anthropicApiKey) || !!cfg.claudeToken;
          if (!hasOAuth && data.vault_ids?.length) {
            for (const vid of data.vault_ids) {
              const entries = listEntries(vid);
              if (entries.some((e) => e.key === "ANTHROPIC_API_KEY" && isAnthropicOAuthToken(e.value))) {
                hasOAuth = true;
                break;
              }
            }
          }
          throw badRequest(
            hasOAuth ? "OAuth tokens (sk-ant-oat*) are not supported by the anthropic provider. Use a standard API key (sk-ant-api*) or switch to a container provider (docker, apple-container, etc.)." : "ANTHROPIC_API_KEY required for anthropic provider (add to vault, upstream-key pool, or .env)"
          );
        }
        const session2 = createSession({
          agent_id: agent.id,
          agent_version: agent.version,
          environment_id: env.id,
          title: data.title ?? null,
          metadata: data.metadata ?? {},
          max_budget_usd: data.max_budget_usd ?? null,
          max_tokens: data.max_tokens ?? null,
          max_wall_duration_ms: data.max_wall_duration_ms ?? null,
          resources: data.resources?.length ? data.resources : null,
          vault_ids: data.vault_ids?.length ? data.vault_ids : null,
          user_profile_id: data.user_profile_id ?? null,
          api_key_id: auth.keyId,
          tenant_id: agentTenantId,
          debug_capture: debugCapture
        });
        if (data.resources?.length) {
          const { createResource } = await import("./db/session-resources.js");
          for (const r of data.resources) {
            let repoUrl = r.type === "github_repository" ? r.repository_url ?? r.url : r.uri;
            if (r.type === "github_repository" && r.authorization_token && repoUrl) {
              try {
                const u = new URL(repoUrl);
                u.username = "x-access-token";
                u.password = r.authorization_token;
                repoUrl = u.toString();
              } catch {
              }
            }
            createResource(session2.id, {
              type: r.type,
              file_id: r.file_id,
              mount_path: r.mount_path,
              url: repoUrl,
              checkout: r.checkout ? { type: r.checkout.type, name: r.checkout.name ?? r.checkout.sha ?? "" } : r.branch ? { type: "branch", name: r.branch } : r.commit ? { type: "commit", name: r.commit } : void 0
            });
          }
        }
        const { remoteSessionId } = await syncAndCreateSession({
          agentId: agent.id,
          agentVersion: agent.version,
          environmentId: env.id,
          vaultIds: data.vault_ids ?? void 0,
          sessionId: session2.id,
          title: data.title ?? void 0,
          apiKey: resolved.value
        });
        updateSessionMutable(session2.id, {
          metadata: { ...data.metadata, _anthropic_session_id: remoteSessionId }
        });
        upsertSync(session2.id, "session", remoteSessionId);
        markProxied(session2.id, "session", agentTenantId);
        if (data.outcomes) {
          setOutcomeCriteria(session2.id, {
            description: data.outcomes.description,
            rubric: data.outcomes.rubric ?? [],
            max_iterations: data.outcomes.max_iterations ?? 3,
            status: "running"
          });
        }
        getActor(session2.id);
        return jsonOk(session2, 201);
      }
      if (env.state !== "ready") {
        if (env.state === "preparing") {
          kickoffEnvironmentSetup(env.id);
        }
        throw badRequest(
          `environment is not ready (state=${env.state}). ` + (env.state === "preparing" ? "Setup has been triggered \u2014 retry in a few seconds." : env.state_message ?? "")
        );
      }
      const session = createSession({
        agent_id: agent.id,
        agent_version: agent.version,
        environment_id: env.id,
        title: data.title ?? null,
        metadata: data.metadata ?? {},
        max_budget_usd: data.max_budget_usd ?? null,
        max_tokens: data.max_tokens ?? null,
        max_wall_duration_ms: data.max_wall_duration_ms ?? null,
        resources: data.resources?.length ? data.resources : null,
        vault_ids: data.vault_ids?.length ? data.vault_ids : null,
        user_profile_id: data.user_profile_id ?? null,
        api_key_id: auth.keyId,
        tenant_id: agentTenantId,
        debug_capture: debugCapture
      });
      if (data.resources?.length) {
        const { createResource } = await import("./db/session-resources.js");
        for (const r of data.resources) {
          if (r.type === "memory_store") {
            createResource(session.id, {
              type: "memory_store",
              memory_store_id: r.memory_store_id,
              access: r.access ?? "read_write",
              instructions: r.instructions
            });
            continue;
          }
          let repoUrl = r.type === "github_repository" ? r.repository_url ?? r.url : r.uri;
          if (r.type === "github_repository" && r.authorization_token && repoUrl) {
            try {
              const u = new URL(repoUrl);
              u.username = "x-access-token";
              u.password = r.authorization_token;
              repoUrl = u.toString();
            } catch {
            }
          }
          createResource(session.id, {
            type: r.type,
            file_id: r.file_id,
            mount_path: r.mount_path,
            url: repoUrl,
            checkout: r.checkout ? { type: r.checkout.type, name: r.checkout.name ?? r.checkout.sha ?? "" } : r.branch ? { type: "branch", name: r.branch } : r.commit ? { type: "commit", name: r.commit } : void 0
          });
        }
      }
      if (data.outcomes) {
        setOutcomeCriteria(session.id, {
          description: data.outcomes.description,
          rubric: data.outcomes.rubric ?? [],
          max_iterations: data.outcomes.max_iterations ?? 3,
          status: "running"
        });
      }
      getActor(session.id);
      return jsonOk(session, 201);
    }
    const MAX_HOPS = 3;
    const primary = {
      agent_id: initialAgentId,
      environment_id: data.environment_id
    };
    const anchorTenantId = getAgentTenantId(initialAgentId);
    const primaryAgent = getAgent(initialAgentId, initialAgentVersion);
    const candidates = [primary];
    const skippedCrossTenant = [];
    if (primaryAgent) {
      for (const fb of parseFallbackJson(primaryAgent.fallback_json ?? null)) {
        const fbAgentTenant = getAgentTenantId(fb.agent_id);
        const fbEnvTenant = getEnvironmentTenantId(fb.environment_id);
        if (fbAgentTenant === void 0) {
          skippedCrossTenant.push({ ...fb, reason: "fallback agent not found" });
          continue;
        }
        if (fbEnvTenant === void 0) {
          skippedCrossTenant.push({ ...fb, reason: "fallback environment not found" });
          continue;
        }
        if (fbAgentTenant !== anchorTenantId) {
          skippedCrossTenant.push({ ...fb, reason: "fallback agent is in a different tenant" });
          continue;
        }
        if (fbEnvTenant !== anchorTenantId) {
          skippedCrossTenant.push({ ...fb, reason: "fallback environment is in a different tenant" });
          continue;
        }
        candidates.push(fb);
        if (candidates.length >= MAX_HOPS) break;
      }
    }
    const attempted = [];
    const visited = /* @__PURE__ */ new Set();
    let lastError = null;
    for (let i = 0; i < candidates.length; i++) {
      const cand = candidates[i];
      const visitKey = `${cand.agent_id}:${cand.environment_id}`;
      if (visited.has(visitKey)) continue;
      visited.add(visitKey);
      try {
        const res = await tryCreate(
          cand.agent_id,
          cand.environment_id,
          i === 0 ? initialAgentVersion : void 0
        );
        if (i > 0) {
          try {
            const sessionJson = await res.clone().json();
            const sessionId = sessionJson.id;
            appendEvent(sessionId, {
              type: "session.fallback_used",
              payload: { from: primary, to: cand, attempted },
              origin: "server",
              processedAt: nowMs()
            });
          } catch {
          }
        }
        return res;
      } catch (err) {
        lastError = err;
        const msg2 = err instanceof Error ? err.message : String(err);
        attempted.push({ ...cand, error: msg2 });
        if (!shouldFallback(err)) {
          throw err;
        }
      }
    }
    const tail = attempted[attempted.length - 1]?.error ?? (lastError instanceof Error ? lastError.message : "unknown");
    const chain = attempted.map((a) => `${a.agent_id}/${a.environment_id}`).join(" \u2192 ");
    const skipped = skippedCrossTenant.length ? ` Skipped cross-tenant fallbacks: ${skippedCrossTenant.map((s) => `${s.agent_id}/${s.environment_id} (${s.reason})`).join(", ")}.` : "";
    const msg = `session creation failed after ${attempted.length} attempts. Attempted: ${chain}. Last error: ${tail}.${skipped}`;
    throw badRequest(msg);
  });
}
function handleListSessions(request) {
  return routeWrap(request, async ({ auth, request: req }) => {
    const url = new URL(req.url);
    const limit = url.searchParams.get("limit");
    const order = url.searchParams.get("order");
    const includeArchived = url.searchParams.get("include_archived") === "true";
    const cursor = decodeCursor(url.searchParams.get("after_id") ?? url.searchParams.get("page"));
    const agentId = url.searchParams.get("agent_id") ?? void 0;
    const agentVersion = url.searchParams.get("agent_version");
    const environmentId = url.searchParams.get("environment_id") ?? void 0;
    const statusesRaw = url.searchParams.getAll("statuses");
    const statusRaw = url.searchParams.get("status");
    let statuses;
    let status;
    if (statusesRaw.length > 0) {
      const values = statusesRaw.flatMap((v) => v.split(","));
      for (const v of values) {
        if (!ALLOWED_STATUSES.includes(v)) {
          throw badRequest(
            `invalid statuses value: ${v} (allowed: ${ALLOWED_STATUSES.join(",")})`
          );
        }
      }
      statuses = values;
    } else if (statusRaw != null) {
      if (!ALLOWED_STATUSES.includes(statusRaw)) {
        throw badRequest(
          `invalid status value: ${statusRaw} (allowed: ${ALLOWED_STATUSES.join(",")})`
        );
      }
      status = statusRaw;
    }
    const requestedLimit = limit ? Number(limit) : 50;
    const data = listSessions({
      agent_id: agentId,
      agent_version: agentVersion ? Number(agentVersion) : void 0,
      environmentId,
      status,
      statuses,
      limit: requestedLimit,
      order: order ?? void 0,
      includeArchived,
      cursor,
      createdGt: parseMs(url.searchParams.get("created_at[gt]")),
      createdGte: parseMs(url.searchParams.get("created_at[gte]")),
      createdLt: parseMs(url.searchParams.get("created_at[lt]")),
      createdLte: parseMs(url.searchParams.get("created_at[lte]")),
      tenantFilter: tenantFilter(auth)
    });
    return paginatedOk(data, requestedLimit);
  });
}
function handleGetSession(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedSessionTenant(auth, id);
      const localSession = getSession(id);
      if (localSession) return jsonOk(localSession);
      return forwardToAnthropic(request, `/v1/sessions/${resolveRemoteSessionId(id)}`);
    }
    return jsonOk(loadSessionForCaller(auth, id));
  });
}
function handleUpdateSession(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedSessionTenant(auth, id);
      return forwardToAnthropic(request, `/v1/sessions/${resolveRemoteSessionId(id)}`);
    }
    loadSessionForCaller(auth, id);
    const body = await request.json().catch(() => null);
    const parsed = UpdateSchema.safeParse(body);
    if (!parsed.success) throw badRequest(parsed.error.message);
    const updated = updateSessionMutable(id, {
      title: parsed.data.title,
      metadata: parsed.data.metadata
    });
    if (!updated) throw notFound(`session ${id} not found`);
    return jsonOk(updated);
  });
}
function handleDeleteSession(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedSessionTenant(auth, id);
      const res = await forwardToAnthropic(request, `/v1/sessions/${resolveRemoteSessionId(id)}`);
      if (res.ok) unmarkProxied(id);
      return res;
    }
    loadSessionForCaller(auth, id);
    const actor = getActor(id);
    await actor.enqueue(async () => {
      interruptSession(id);
      await releaseSession(id);
      appendEvent(id, {
        type: "session.status_terminated",
        payload: { reason: "deleted" },
        origin: "server",
        processedAt: nowMs()
      });
      updateSessionStatus(id, "terminated", "deleted");
    });
    dropActor(id);
    dropEmitter(id);
    return jsonOk({ id, type: "session_deleted" });
  });
}
function handleArchiveSession(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedSessionTenant(auth, id);
      const res = await forwardToAnthropic(request, `/v1/sessions/${resolveRemoteSessionId(id)}/archive`);
      if (res.ok) unmarkProxied(id);
      return res;
    }
    loadSessionForCaller(auth, id);
    const actor = getActor(id);
    await actor.enqueue(async () => {
      await releaseSession(id);
      archiveSession(id);
    });
    return jsonOk(getSession(id));
  });
}
function parseMs(v) {
  if (!v) return void 0;
  const n = Number(v);
  if (Number.isFinite(n)) return n;
  const t = Date.parse(v);
  return Number.isFinite(t) ? t : void 0;
}

export {
  handleCreateSession,
  handleListSessions,
  handleGetSession,
  handleUpdateSession,
  handleDeleteSession,
  handleArchiveSession
};
