114 lines · 4.9 KB
import { z } from "zod";
import { forbidden } from "../../errors";
import { limitWrite } from "../../limits";
import { hasPermission } from "../../repos";
import { assertAreasWithinGrant, credentialCoversRepo, scopedRepoPermission, TOKEN_AREAS } from "../../token-scope";
import type { TokenArea, TokenPermissions } from "../../token-scope";
import type { Permission } from "~/lib/types";
import { mintFineGrainedToken, tokenLineage } from "../grants";
import { defineTool, repoArgs, scopedRepo, ToolError } from "./base";

const MAX_TOKEN_DAYS = 7;
const DAY_MS = 24 * 60 * 60 * 1000;
const CONFIRM_KEY = "confirm_token";

function needed(area: TokenArea, access: "read" | "write"): Permission {
  if (area === "admin") return access === "write" ? "admin" : "read";
  return access;
}

const createTokenTool = defineTool({
  name: "create_repo_token",
  title: "Create scoped repository token",
  description: `Mint a fine-grained token for one repository (e.g. for a sub-agent), valid up to ${MAX_TOKEN_DAYS} days but never longer than your own token, and revoked with it. It can never exceed this grant's own access and cannot delegate further. Clients that support elicitation are asked to confirm (advisory: the confirmation comes from the client).`,
  input: z.object({
    ...repoArgs,
    name: z.string().trim().min(1).max(80),
    permissions: z.object(Object.fromEntries(TOKEN_AREAS.map((area) => [area, z.enum(["read", "write"]).optional()]))),
    expires_in_days: z.number().int().min(1).max(MAX_TOKEN_DAYS).default(1),
  }),
  write: true,
  async run({ caller, inputResponses }, args) {
    await limitWrite(caller.viewer);
    const lineage = caller.tokenId ? await tokenLineage(caller.tokenId) : null;
    if (!caller.tokenId || !lineage) throw new ToolError("A token is required to delegate", true);
    if (lineage.parent_token_id) throw new ToolError("Delegated tokens cannot delegate further", true);
    const { repo } = await scopedRepo(caller, args.owner, args.repo, "contents", "read");
    const requested: TokenPermissions = {};
    for (const area of TOKEN_AREAS) {
      const access = args.permissions[area];
      if (access === "read" || access === "write") requested[area] = access;