import { z } from "zod";
import { handleEditorRoutes } from "../../editor/api";
import { mintEditorToken } from "../../editor/session";
import { HttpError } from "../../errors";
import { credentialAllows } from "../../token-scope";
import type { McpCaller } from "../protocol";
import { defineTool, repoArgs, scopedRepo, withTrailers } from "./base";
import { resolveRef } from "./content";
const MAX_EDITS = 200;
/** The room answers stale edits with 409 plus the current text; tools return that to the agent. */
const STALE_STATUS = 409;
const path = z.string().min(1).max(4096).describe("File path in the repository, e.g. src/app.ts");
const ref = z.string().min(1).max(250).optional().describe("Branch to co-edit (default branch when omitted)");
/**
* Mints a short-lived editor token for this agent, so every tool call goes through the same
* /api/editor checks as browsers: live permission and PAT scope, branch protection, LFS and path guards.
*/
async function editorToken(caller: McpCaller, owner: string, repoName: string, refName: string | undefined) {
const { repo, permission } = await scopedRepo(caller, owner, repoName, "contents", "read");
const revision = await resolveRef(repo, refName);
if (revision.kind !== "branch") throw new HttpError(400, "Live editing needs a branch");
const grant = caller.credential.grant;
const minted = await mintEditorToken(caller.viewer, repo, revision.name, {
ceiling: permission,
canOpenPull: await credentialAllows(repo, caller.credential, "pulls", "write"),
patId: grant && grant.kind !== "deploy" ? grant.tokenId : null,
});
return minted.token;
}
async function editorCall(
caller: McpCaller,
token: string,
action: string,
body?: unknown,
search?: Record<string, string>,
) {
const url = new URL(`${caller.origin}/api/editor/${action}`);