import {
  assertResourceTenant,
  resolveCreateTenant,
  tenantFilter
} from "./chunk-K7DLILHK.js";
import {
  decodeCursor,
  jsonOk,
  paginatedOk,
  routeWrap
} from "./chunk-QSHK4MUE.js";
import {
  forwardToAnthropic,
  validateAnthropicProxy
} from "./chunk-VFSHH6I2.js";
import {
  getProxiedTenantId,
  isProxied,
  markProxied,
  unmarkProxied
} from "./chunk-DZIKCTP6.js";
import {
  ENGINE_NAMES
} from "./chunk-4GUD6MFS.js";
import {
  archiveAgent,
  createAgent,
  getAgent,
  listAgentVersions,
  listAgents,
  updateAgent
} from "./chunk-LGPJPAHU.js";
import {
  resolveBackend
} from "./chunk-N7MLFZPQ.js";
import {
  getDb,
  init_client
} from "./chunk-TLEKKGH7.js";
import {
  badRequest,
  conflict,
  notFound
} from "./chunk-EZYKRG4W.js";

// src/handlers/anthropic-compat/agents.ts
import { z } from "zod";
init_client();
var ENGINE_ENUM = [...ENGINE_NAMES, "anthropic"];
function getAgentTenantId(id) {
  const row = getDb().prepare(`SELECT tenant_id FROM agents WHERE id = ?`).get(id);
  return row?.tenant_id;
}
function loadAgentForCaller(auth, id, version) {
  const tenantId = getAgentTenantId(id);
  if (tenantId === void 0) throw notFound(`agent ${id} not found`);
  assertResourceTenant(auth, tenantId, `agent ${id} not found`);
  const agent = getAgent(id, version);
  if (!agent) throw notFound(`agent ${id} not found`);
  return agent;
}
function assertProxiedAgentTenant(auth, id) {
  const proxied = getProxiedTenantId(id);
  if (proxied === void 0) return;
  assertResourceTenant(auth, proxied, `agent ${id} not found`);
}
var InlineSkillSchema = z.object({
  name: z.string().min(1),
  source: z.string().min(1),
  content: z.string().min(1).max(256 * 1024),
  // 256KB per skill
  files: z.record(z.string(), z.string()).optional(),
  installed_at: z.string().optional()
});
var RefSkillSchema = z.object({
  skill_id: z.string().min(1),
  type: z.enum(["custom", "anthropic"]).optional(),
  version: z.string().optional()
});
var SkillSchema = z.union([InlineSkillSchema, RefSkillSchema]);
var ToolSchema = z.union([
  z.object({
    type: z.literal("agent_toolset_20260401"),
    configs: z.array(z.object({ name: z.string(), enabled: z.boolean().optional() })).optional(),
    default_config: z.object({ enabled: z.boolean().optional() }).optional()
  }),
  z.object({
    type: z.literal("custom"),
    name: z.string().min(1),
    description: z.string(),
    input_schema: z.record(z.unknown())
  })
]);
var ModelConfigSchema = z.object({
  speed: z.enum(["standard", "fast"]).optional()
});
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
  ".md",
  ".txt",
  ".py",
  ".js",
  ".ts",
  ".sh",
  ".json",
  ".yaml",
  ".yml",
  ".html",
  ".css",
  ".xml",
  ".csv",
  ".toml",
  ".cfg",
  ".ini",
  ".sql",
  ".jsx",
  ".tsx",
  ".mjs",
  ".cjs",
  ".rb",
  ".go",
  ".rs",
  ".java"
]);
async function fetchAnthropicSkill(skillName) {
  const rawBase = `https://raw.githubusercontent.com/anthropics/skills/main/skills/${skillName}`;
  const files = {};
  const treeResp = await fetch(
    `https://api.github.com/repos/anthropics/skills/git/trees/main?recursive=1`,
    { signal: AbortSignal.timeout(15e3) }
  );
  if (!treeResp.ok) {
    const mdResp = await fetch(`${rawBase}/SKILL.md`, { signal: AbortSignal.timeout(1e4) });
    if (!mdResp.ok) throw new Error(`SKILL.md not found for ${skillName}`);
    const content = await mdResp.text();
    return { content, files: { "SKILL.md": content } };
  }
  const tree = await treeResp.json();
  const prefix = `skills/${skillName}/`;
  const skillFiles = tree.tree.filter(
    (f) => f.type === "blob" && f.path.startsWith(prefix)
  );
  if (skillFiles.length === 0) {
    throw new Error(`No files found for skill "${skillName}"`);
  }
  let skillMdContent = "";
  await Promise.all(
    skillFiles.map(async (f) => {
      const relativePath = f.path.slice(prefix.length);
      const ext = relativePath.substring(relativePath.lastIndexOf(".")).toLowerCase();
      const isText = TEXT_EXTENSIONS.has(ext) || relativePath === "LICENSE.txt";
      try {
        const resp = await fetch(`${rawBase}/${relativePath}`, {
          signal: AbortSignal.timeout(1e4)
        });
        if (!resp.ok) return;
        if (isText) {
          const text = await resp.text();
          files[relativePath] = text;
          if (relativePath === "SKILL.md") skillMdContent = text;
        } else {
          const buf = Buffer.from(await resp.arrayBuffer());
          files[relativePath] = `base64:${buf.toString("base64")}`;
        }
      } catch {
      }
    })
  );
  if (!skillMdContent) {
    throw new Error(`No SKILL.md found in anthropics/skills/skills/${skillName}`);
  }
  return { content: skillMdContent, files };
}
async function resolveSkillInputs(skills, nowIso) {
  if (!skills) return void 0;
  const resolved = [];
  for (const s of skills) {
    if ("skill_id" in s && s.skill_id) {
      const { getSkill: dbGetSkill, getSkillVersion: dbGetSkillVersion } = await import("./db/skills.js");
      const dbSkill = dbGetSkill(s.skill_id);
      if (dbSkill) {
        const version = s.version ?? dbSkill.current_version;
        const sv = dbGetSkillVersion(s.skill_id, version);
        if (!sv) throw badRequest(`skill version ${version} not found for skill ${s.skill_id}`);
        resolved.push({
          name: dbSkill.name,
          source: `skill:${s.skill_id}@${version}`,
          content: sv.content,
          ...sv.files && Object.keys(sv.files).length > 0 ? { files: sv.files } : {},
          installed_at: nowIso
        });
      } else if (s.type === "anthropic" || !s.type) {
        const skillName = s.skill_id;
        try {
          const result = await fetchAnthropicSkill(skillName);
          resolved.push({
            name: skillName,
            source: `anthropic:${skillName}`,
            content: result.content,
            ...Object.keys(result.files).length > 0 ? { files: result.files } : {},
            installed_at: nowIso
          });
        } catch (err) {
          const msg = err instanceof Error ? err.message : String(err);
          console.warn(`[agents] failed to fetch Anthropic skill "${skillName}":`, msg);
          throw badRequest(
            `skill "${skillName}" not found in local DB or Anthropic skills repo (${msg}). Upload it via POST /v1/skills or check the skill_id. Available Anthropic skills: docx, pdf, pptx, xlsx, mcp-builder, frontend-design`
          );
        }
      } else {
        throw badRequest(`skill "${s.skill_id}" not found`);
      }
    } else {
      const inline = s;
      resolved.push({
        name: inline.name,
        source: inline.source,
        content: inline.content,
        ...inline.files && Object.keys(inline.files).length > 0 ? { files: inline.files } : {},
        installed_at: inline.installed_at ?? nowIso
      });
    }
  }
  return resolved;
}
var CreateSchema = z.object({
  name: z.string().min(1),
  model: z.union([
    z.string().min(1).transform((s) => ({ id: s })),
    z.object({ id: z.string().min(1), speed: z.enum(["standard", "fast"]).optional() })
  ]),
  description: z.string().max(2048).optional(),
  metadata: z.record(z.string(), z.string().max(512)).optional(),
  system: z.string().nullish(),
  tools: z.array(ToolSchema).optional(),
  mcp_servers: z.array(
    z.object({
      name: z.string(),
      type: z.string().optional(),
      url: z.string().optional()
    }).passthrough()
  ).optional(),
  engine: z.enum(ENGINE_ENUM).optional(),
  webhook_url: z.string().url().optional(),
  webhook_events: z.array(z.string()).optional(),
  /**
   * v0.5: shared secret for HMAC signing of webhook deliveries. Min
   * length 32 to nudge toward strong entropy; values shorter than that
   * are almost always a typo or misplaced username.
   */
  webhook_secret: z.string().min(32).max(512).optional(),
  threads_enabled: z.boolean().optional(),
  confirmation_mode: z.boolean().optional(),
  callable_agents: z.array(z.object({
    type: z.literal("agent"),
    id: z.string(),
    version: z.number().int().optional()
  })).optional(),
  multiagent: z.object({
    type: z.literal("coordinator"),
    agents: z.array(z.union([
      z.object({ type: z.literal("agent"), id: z.string(), version: z.number().int().optional() }),
      z.object({ type: z.literal("self") })
    ])).max(20)
  }).optional(),
  permission_policy: z.object({
    always_allow: z.array(z.string()).optional(),
    always_ask: z.array(z.string()).optional()
  }).optional(),
  skills: z.array(SkillSchema).max(20).optional(),
  model_config: ModelConfigSchema.optional(),
  /** v0.5: required for global admin, ignored for tenant users. */
  tenant_id: z.string().optional()
}).refine((data) => {
  if (!data.metadata) return true;
  return Object.keys(data.metadata).length <= 16;
}, "metadata exceeds 16 key limit").refine((data) => {
  if (!data.skills) return true;
  const total = data.skills.reduce((sum, s) => {
    return sum + (s.content?.length ?? 0);
  }, 0);
  return total <= 1024 * 1024;
}, "Total skills content exceeds 1MB limit");
var UpdateSchema = z.object({
  version: z.number().int().min(1),
  name: z.string().min(1).optional(),
  model: z.union([
    z.string().min(1).transform((s) => ({ id: s })),
    z.object({ id: z.string().min(1), speed: z.enum(["standard", "fast"]).optional() })
  ]).optional(),
  description: z.string().max(2048).optional(),
  metadata: z.record(z.string(), z.string().max(512)).optional(),
  system: z.string().nullish(),
  tools: z.array(z.unknown()).optional(),
  mcp_servers: z.array(
    z.object({
      name: z.string(),
      type: z.string().optional(),
      url: z.string().optional()
    }).passthrough()
  ).optional(),
  webhook_url: z.string().url().nullish(),
  webhook_events: z.array(z.string()).optional(),
  /** Null clears the secret (unsigned webhooks); string rotates it. */
  webhook_secret: z.string().min(32).max(512).nullish(),
  confirmation_mode: z.boolean().optional(),
  callable_agents: z.array(z.object({
    type: z.literal("agent"),
    id: z.string(),
    version: z.number().int().optional()
  })).optional(),
  multiagent: z.object({
    type: z.literal("coordinator"),
    agents: z.array(z.union([
      z.object({ type: z.literal("agent"), id: z.string(), version: z.number().int().optional() }),
      z.object({ type: z.literal("self") })
    ])).max(20)
  }).nullish(),
  permission_policy: z.object({
    always_allow: z.array(z.string()).optional(),
    always_ask: z.array(z.string()).optional()
  }).nullish(),
  skills: z.array(SkillSchema).max(20).optional(),
  model_config: ModelConfigSchema.optional()
}).refine((data) => {
  if (!data.metadata) return true;
  return Object.keys(data.metadata).length <= 16;
}, "metadata exceeds 16 key limit").refine((data) => {
  if (!data.skills) return true;
  const total = data.skills.reduce((sum, s) => {
    return sum + (s.content?.length ?? 0);
  }, 0);
  return total <= 1024 * 1024;
}, "Total skills content exceeds 1MB limit");
function handleCreateAgent(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(`invalid body: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
    }
    const createTenantId = resolveCreateTenant(auth, parsed.data.tenant_id);
    const existing = listAgents({ limit: 1e3, tenantFilter: tenantFilter(auth) });
    if (existing.some((a) => a.name === parsed.data.name)) {
      throw conflict(`Agent with name "${parsed.data.name}" already exists`);
    }
    const { inferEngineFromModel } = await import("./backends/models.js");
    const backendName = parsed.data.engine ?? inferEngineFromModel(parsed.data.model.id) ?? "claude";
    if (backendName === "anthropic") {
      const proxyErr = validateAnthropicProxy();
      if (proxyErr) throw badRequest(proxyErr);
      const proxyRes = await forwardToAnthropic(request, "/v1/agents", { body: rawBody });
      if (proxyRes.ok) {
        try {
          const data = await proxyRes.clone().json();
          markProxied(data.id, "agent", createTenantId);
        } catch {
        }
      }
      return proxyRes;
    }
    const backend = resolveBackend(backendName);
    const modelId = parsed.data.model.id;
    const modelSpeed = "speed" in parsed.data.model ? parsed.data.model.speed : void 0;
    const { isValidModelForEngine, FALLBACK_MODELS } = await import("./backends/models.js");
    if (!isValidModelForEngine(backendName, modelId)) {
      throw badRequest(
        `Model "${modelId}" is not supported by the ${backendName} engine. Supported models: ${(FALLBACK_MODELS[backendName] ?? []).join(", ")}`
      );
    }
    if (backendName !== "claude" && parsed.data.tools && parsed.data.tools.length > 0) {
      throw badRequest(
        `${backendName} backend does not use agent tool configs; tools are managed by the backend's internal permission system. Omit the tools field for ${backendName} agents.`
      );
    }
    const createErr = backend.validateAgentCreation?.();
    if (createErr) throw badRequest(createErr);
    const mcpInput = parsed.data.mcp_servers;
    let mcpRecord = {};
    if (mcpInput) {
      for (const s of mcpInput) {
        const { name, ...rest } = s;
        mcpRecord[name] = { type: rest.type ?? "url", ...rest };
      }
    }
    const mergedModelConfig = {
      ...parsed.data.model_config ?? {},
      ...modelSpeed ? { speed: modelSpeed } : {}
    };
    const nowIso = (/* @__PURE__ */ new Date()).toISOString();
    const agent = createAgent({
      name: parsed.data.name,
      model: modelId,
      description: parsed.data.description,
      metadata: parsed.data.metadata,
      system: parsed.data.system ?? null,
      tools: parsed.data.tools ?? [{ type: "agent_toolset_20260401" }],
      mcp_servers: mcpRecord,
      backend: backendName,
      webhook_url: parsed.data.webhook_url ?? null,
      webhook_events: parsed.data.webhook_events,
      webhook_secret: parsed.data.webhook_secret ?? null,
      threads_enabled: parsed.data.threads_enabled ?? (parsed.data.multiagent ? true : false),
      confirmation_mode: parsed.data.confirmation_mode ?? false,
      callable_agents: parsed.data.callable_agents,
      multiagent: parsed.data.multiagent,
      permission_policy: parsed.data.permission_policy,
      skills: await resolveSkillInputs(parsed.data.skills, nowIso),
      model_config: mergedModelConfig,
      tenant_id: createTenantId
    });
    return jsonOk(agent, 201);
  });
}
function handleListAgents(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 requestedLimit = limit ? Number(limit) : 20;
    const data = listAgents({
      limit: requestedLimit,
      order: order ?? void 0,
      includeArchived,
      cursor,
      tenantFilter: tenantFilter(auth)
    });
    return paginatedOk(data, requestedLimit);
  });
}
function handleGetAgent(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedAgentTenant(auth, id);
      return forwardToAnthropic(request, `/v1/agents/${id}`);
    }
    const url = new URL(request.url);
    const versionParam = url.searchParams.get("version");
    const version = versionParam ? Number(versionParam) : void 0;
    const agent = loadAgentForCaller(auth, id, version);
    return jsonOk(agent);
  });
}
function handleUpdateAgent(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedAgentTenant(auth, id);
      return forwardToAnthropic(request, `/v1/agents/${id}`);
    }
    const current = loadAgentForCaller(auth, id);
    const body = await request.json().catch(() => null);
    if (body && typeof body === "object" && "backend" in body) {
      throw badRequest("backend cannot be changed after agent creation");
    }
    const parsed = UpdateSchema.safeParse(body);
    if (!parsed.success) throw badRequest(parsed.error.message);
    if (current.version !== parsed.data.version) {
      throw conflict(`Version mismatch: expected ${current.version}, got ${parsed.data.version}`);
    }
    const modelId = parsed.data.model?.id;
    const modelSpeed = parsed.data.model && "speed" in parsed.data.model ? parsed.data.model.speed : void 0;
    let mcpRecord;
    if (parsed.data.mcp_servers !== void 0) {
      mcpRecord = {};
      for (const s of parsed.data.mcp_servers) {
        const { name, ...rest } = s;
        mcpRecord[name] = { type: rest.type ?? "url", ...rest };
      }
    }
    const mergedModelConfig = parsed.data.model_config || modelSpeed ? { ...parsed.data.model_config ?? {}, ...modelSpeed ? { speed: modelSpeed } : {} } : void 0;
    const nowIso = (/* @__PURE__ */ new Date()).toISOString();
    const updated = updateAgent(id, {
      name: parsed.data.name,
      model: modelId,
      description: parsed.data.description,
      metadata: parsed.data.metadata,
      system: parsed.data.system,
      tools: parsed.data.tools,
      mcp_servers: mcpRecord,
      webhook_url: parsed.data.webhook_url,
      webhook_events: parsed.data.webhook_events,
      webhook_secret: parsed.data.webhook_secret,
      confirmation_mode: parsed.data.confirmation_mode,
      callable_agents: parsed.data.callable_agents,
      multiagent: parsed.data.multiagent,
      permission_policy: parsed.data.permission_policy,
      skills: await resolveSkillInputs(parsed.data.skills, nowIso),
      model_config: mergedModelConfig
    });
    if (!updated) throw notFound(`agent ${id} not found`);
    return jsonOk(updated);
  });
}
function handleDeleteAgent(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedAgentTenant(auth, id);
      const res = await forwardToAnthropic(request, `/v1/agents/${id}`);
      if (res.ok) unmarkProxied(id);
      return res;
    }
    loadAgentForCaller(auth, id);
    const ok = archiveAgent(id);
    if (!ok) throw notFound(`agent ${id} not found`);
    return jsonOk({ id, type: "agent_deleted" });
  });
}
function handleArchiveAgent(request, id) {
  return routeWrap(request, async ({ auth }) => {
    if (isProxied(id)) {
      assertProxiedAgentTenant(auth, id);
      const res = await forwardToAnthropic(request, `/v1/agents/${id}/archive`);
      if (res.ok) unmarkProxied(id);
      return res;
    }
    loadAgentForCaller(auth, id);
    const ok = archiveAgent(id);
    if (!ok) throw notFound(`agent ${id} not found`);
    const agent = getAgent(id);
    return jsonOk(agent);
  });
}
function handleListAgentVersions(request, id) {
  return routeWrap(request, async ({ auth, request: req }) => {
    loadAgentForCaller(auth, id);
    const url = new URL(req.url);
    const limit = url.searchParams.get("limit");
    const cursorRaw = decodeCursor(url.searchParams.get("after_id") ?? url.searchParams.get("page"));
    const cursor = cursorRaw ? Number(cursorRaw) : void 0;
    const requestedLimit = limit ? Number(limit) : 20;
    const data = listAgentVersions(id, {
      limit: requestedLimit,
      cursor
    });
    const hasMore = data.length === requestedLimit;
    const firstId = data.length > 0 ? String(data[0].version) : null;
    const lastId = data.length > 0 ? String(data[data.length - 1].version) : null;
    return jsonOk({ data, has_more: hasMore, first_id: firstId, last_id: lastId });
  });
}

export {
  handleCreateAgent,
  handleListAgents,
  handleGetAgent,
  handleUpdateAgent,
  handleDeleteAgent,
  handleArchiveAgent,
  handleListAgentVersions
};
