Ctrl K
ComunidadGuías…

server/claude-bedrock.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { join } from "node:path";
import { describe, expect, it } from "vitest";

import {
  claudeBedrockSchema,
  claudeBedrockUpdateSchema,
  claudeBedrockInfo,
  isClaudeBedrock,
  newClaudeBedrock,
  updateClaudeBedrock,
  withClaudeBedrockInfo,
} from "./claude-bedrock.ts";
import { isClaudeEndpoint } from "./claude-endpoints.ts";
import { DATA_DIR } from "./config.ts";

const BASE = {
  displayName: "Bedrock US",
  awsProfile: "bedrock-prod",
  awsRegion: "us-east-1",
  model: "global.anthropic.claude-sonnet-5-5",
};

const cfg = () => ({
  instances: {
    claude: { driver: "claudeAgent", environment: { ANTHROPIC_AUTH_TOKEN: "parent-secret" }, config: { cli: "claude-wrapper" } },
    other: { driver: "codex", config: { cli: "codex-wrapper" } },
  },
}) as never;

describe("Claude on Amazon Bedrock", () => {
  it("creates its own instance with Bedrock routing, its own directory and no AWS/Anthropic secret", () => {
    const source = cfg();
    const { instanceId, instances } = newClaudeBedrock(source, { ...BASE, opusModel: "global.anthropic.claude-opus-5-5" });
    const entry = instances[instanceId];

    expect(instanceId).toMatch(/^claude-[0-9a-f-]{36}$/);
    expect(entry).toMatchObject({
      driver: "claudeAgent",
      displayName: "Bedrock US",
      access: "custom",
      config: {
        cli: "claude-wrapper",
        configDir: join(DATA_DIR, "providers", instanceId),
        bedrock: true,
      },
      environment: {
        CLAUDE_CODE_USE_BEDROCK: "1",
        AWS_PROFILE: "bedrock-prod",
        AWS_REGION: "us-east-1",
        ANTHROPIC_MODEL: "global.anthropic.claude-sonnet-5-5",
        ANTHROPIC_DEFAULT_OPUS_MODEL: "global.anthropic.claude-opus-5-5",
      },
    });
    expect(entry.environment).not.toHaveProperty("ANTHROPIC_API_KEY");
    expect(entry.environment).not.toHaveProperty("ANTHROPIC_AUTH_TOKEN");
    expect(entry.environment).not.toHaveProperty("requireApiKey");
    expect((entry.config as { requireApiKey?: boolean }).requireApiKey).toBeUndefined();
    // The catalog is the named model and each tier set, never guessed Bedrock ids.
    expect((entry.config as { endpointModels: string[] }).endpointModels).toEqual(["global.anthropic.claude-sonnet-5-5", "global.anthropic.claude-opus-5-5"]);
    // The caller's config is left as it was: the fleet is copied, not edited.
    expect(Object.keys((source as { instances: object }).instances)).toEqual(["claude", "other"]);
  });

  it("inherits the installed binary from whichever Claude account sets one, never a Bedrock sibling's", () => {
    const { instanceId, instances } = newClaudeBedrock({
      instances: {
        claude: { driver: "claudeAgent", config: {} },
        "claude-work": { driver: "claudeAgent", config: { cli: "/opt/claude/bin/claude" } },
        "claude-bedrock-other": { driver: "claudeAgent", config: { cli: "/opt/claude/bin/claude-bedrock-fixture", bedrock: true } },
      },
    } as never, BASE);
    expect((instances[instanceId].config as { cli?: string }).cli).toBe("/opt/claude/bin/claude");
  });

  it("uses an explicit model as the only catalog entry when none of the tiers are set", () => {
    const { instanceId, instances } = newClaudeBedrock(cfg(), BASE);
    expect((instances[instanceId].config as { endpointModels: string[] }).endpointModels).toEqual(["global.anthropic.claude-sonnet-5-5"]);
    expect(instances[instanceId].environment?.ANTHROPIC_MODEL).toBe("global.anthropic.claude-sonnet-5-5");
  });

  it("reports only the profile, region and models to a client; never an AWS or Anthropic secret", () => {
    const { instanceId, instances } = newClaudeBedrock(cfg(), BASE);
    const info = claudeBedrockInfo(instances[instanceId]);
    expect(info).toEqual({
      awsProfile: "bedrock-prod",
      awsRegion: "us-east-1",
      model: "global.anthropic.claude-sonnet-5-5",
      models: ["global.anthropic.claude-sonnet-5-5"],
      opusModel: "", sonnetModel: "", haikuModel: "", smallFastModel: "",
    });
    const described = withClaudeBedrockInfo([{ instanceId }, { instanceId: "claude" }], instances) as Array<Record<string, unknown>>;
    expect(JSON.stringify(described)).not.toMatch(/AKIA|aws_secret|session_token/i);
    expect(described[0]).toMatchObject({ claudeBedrock: { awsProfile: "bedrock-prod", awsRegion: "us-east-1" } });
    expect(described[0]).not.toHaveProperty("url");
    expect(described[1]).not.toHaveProperty("claudeBedrock");
    expect(isClaudeBedrock(instances[instanceId])).toBe(true);
    expect(isClaudeBedrock(instances.claude)).toBe(false);
  });

  it("is never mistaken for a custom endpoint, even though both set endpointModels", () => {
    const { instanceId, instances } = newClaudeBedrock(cfg(), BASE);
    expect(isClaudeEndpoint(instances[instanceId])).toBe(false);
    expect(isClaudeBedrock(instances[instanceId])).toBe(true);
  });

  it("edits in place; an omitted field keeps its saved profile, region and models", () => {
    const { instanceId, instances } = newClaudeBedrock(cfg(), { ...BASE, opusModel: "global.anthropic.claude-opus-5-5" });
    const updated = updateClaudeBedrock({ instances }, instanceId, { awsRegion: "eu-west-1" });
    const entry = updated.instances[instanceId];
    expect(entry.environment).toMatchObject({
      AWS_PROFILE: "bedrock-prod",
      AWS_REGION: "eu-west-1",
      ANTHROPIC_DEFAULT_OPUS_MODEL: "global.anthropic.claude-opus-5-5",
    });
    // Nothing else in the fleet moves.
    expect(updated.instances.claude).toEqual(instances.claude);
  });

  it("clears a model tier left empty", () => {
    const { instanceId, instances } = newClaudeBedrock(cfg(), { ...BASE, opusModel: "global.anthropic.claude-opus-5-5" });
    const updated = updateClaudeBedrock({ instances }, instanceId, { opusModel: "" });
    expect(updated.instances[instanceId].environment).not.toHaveProperty("ANTHROPIC_DEFAULT_OPUS_MODEL");
  });

  it("rejects a non-Bedrock Claude instance and an unknown id", () => {
    const { instances } = newClaudeBedrock(cfg(), BASE);
    expect(() => updateClaudeBedrock({ instances }, "claude", { awsRegion: "eu-west-1" })).toThrow(/not an Amazon Bedrock engine/);
    expect(() => updateClaudeBedrock({ instances }, "missing", { awsRegion: "eu-west-1" })).toThrow(/unknown instance/);
  });

  it("validates the profile, region, display name and model ids before anything is saved", () => {
    for (const body of [
      { ...BASE, awsProfile: "" },
      { ...BASE, awsProfile: "has space" },
      { ...BASE, awsRegion: "not-a-region" },
      { ...BASE, awsRegion: "US-EAST-1" },
      { ...BASE, model: "has space" },
      { ...BASE, model: "" },
      { displayName: BASE.displayName, awsProfile: BASE.awsProfile, awsRegion: BASE.awsRegion },
      { ...BASE, model: "default" },
      { ...BASE, displayName: "Bad\nname" },
      { ...BASE, displayName: "" },
      { ...BASE, token: "sk-should-not-exist" },
    ]) {
      expect(claudeBedrockSchema.safeParse(body).success, JSON.stringify(body)).toBe(false);
    }
    expect(claudeBedrockSchema.safeParse(BASE).success).toBe(true);
    expect(claudeBedrockUpdateSchema.safeParse({}).success).toBe(false);
    expect(claudeBedrockUpdateSchema.safeParse({ awsRegion: "not-a-region" }).success).toBe(false);
    expect(claudeBedrockUpdateSchema.safeParse({ awsRegion: "eu-west-1" }).success).toBe(true);
  });
});