Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Openclaw/src/commands/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 37 kB image not shown  

Quelle  auth-choice.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import fs from "node:fs/promises";
import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { resolveAgentDir } from "../agents/agent-scope.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
import type { ModelProviderConfig } from "../config/types.models.js";
import * as providerAuthChoices from "../plugins/provider-auth-choices.js";
import type { ProviderAuthMethod, ProviderAuthResult, ProviderPlugin } from "../plugins/types.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { applyAuthChoice } from "./auth-choice.apply.js";
import {
  createAuthTestLifecycle,
  createExitThrowingRuntime,
  createWizardPrompter,
  requireOpenClawAgentDir,
  setupAuthTestEnv,
} from "./test-wizard-helpers.js";

type DetectZaiEndpoint = typeof import("../plugins/provider-zai-endpoint.js").detectZaiEndpoint;

const GOOGLE_GEMINI_DEFAULT_MODEL = "google/gemini-3.1-pro-preview";
const ZAI_CODING_GLOBAL_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
const ZAI_CODING_CN_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";

const resolvePluginProviders = vi.hoisted(() => vi.fn<() => ProviderPlugin[]>(() => []));
const runProviderModelSelectedHook = vi.hoisted(() => vi.fn(async () => {}));
vi.mock("../plugins/provider-auth-choice.runtime.js", () => {
  const normalizeProviderId = (value: string) => value.trim().toLowerCase();
  return {
    resolvePluginProviders,
    resolveProviderPluginChoice: (params: { providers: ProviderPlugin[]; choice: string }) => {
      const choice = params.choice.trim();
      if (!choice) {
        return null;
      }
      if (choice.startsWith("provider-plugin:")) {
        const payload = choice.slice("provider-plugin:".length);
        const separator = payload.indexOf(":");
        const providerId = separator >= 0 ? payload.slice(0, separator) : payload;
        const methodId = separator >= 0 ? payload.slice(separator + 1) : undefined;
        const provider = params.providers.find(
          (entry) => normalizeProviderId(entry.id) === normalizeProviderId(providerId),
        );
        const method = methodId
          ? provider?.auth.find((entry) => entry.id === methodId)
          : provider?.auth[0];
        return provider && method ? { provider, method } : null;
      }
      for (const provider of params.providers) {
        for (const method of provider.auth) {
          if (method.wizard?.choiceId === choice) {
            return { provider, method, wizard: method.wizard };
          }
        }
        if (normalizeProviderId(provider.id) === normalizeProviderId(choice) && provider.auth[0]) {
          return { provider, method: provider.auth[0] };
        }
      }
      return null;
    },
    runProviderModelSelectedHook,
  };
});

vi.mock("./auth-choice.apply.api-providers.js", () => {
  const normalizeProviderId = (value: string) => value.trim().toLowerCase();
  const resolveChoiceByKind = (params: {
    authChoice: string;
    kind: ProviderAuthMethod["kind"];
    tokenProvider?: string;
  }) => {
    const providerId = normalizeProviderId(params.tokenProvider ?? "");
    if (!providerId) {
      return params.authChoice;
    }
    const provider = resolvePluginProviders().find(
      (entry) => normalizeProviderId(entry.id) === providerId,
    );
    return (
      provider?.auth.find((method) => method.kind === params.kind)?.wizard?.choiceId ??
      params.authChoice
    );
  };
  return {
    applyAuthChoiceApiProviders: vi.fn(async () => null),
    normalizeApiKeyTokenProviderAuthChoice: (params: {
      authChoice: string;
      tokenProvider?: string;
    }) => {
      if (params.authChoice === "token" || params.authChoice === "setup-token") {
        return resolveChoiceByKind({ ...params, kind: "token" });
      }
      if (params.authChoice === "apiKey") {
        return resolveChoiceByKind({ ...params, kind: "api_key" });
      }
      return params.authChoice;
    },
  };
});

const detectZaiEndpoint = vi.hoisted(() => vi.fn<DetectZaiEndpoint>(async () => null));
vi.mock("../plugins/provider-zai-endpoint.js", () => ({
  detectZaiEndpoint,
}));

vi.mock("../agents/agent-paths.js", () => ({
  resolveOpenClawAgentDir: () => process.env.OPENCLAW_AGENT_DIR ?? "/tmp/openclaw-agent",
}));

vi.mock("../agents/agent-scope.js", () => ({
  resolveDefaultAgentId: () => "main",
  resolveAgentDir: (_config: unknown, agentId: string) => `/tmp/openclaw-agents/${agentId}`,
  resolveAgentWorkspaceDir: (_config: unknown, agentId: string) =>
    `/tmp/openclaw-workspaces/${agentId}`,
}));

vi.mock("../agents/workspace.js", () => ({
  resolveDefaultAgentWorkspaceDir: () => "/tmp/openclaw-workspace",
}));

vi.mock("../plugins/setup-browser.js", () => ({
  isRemoteEnvironment: () => false,
  openUrl: vi.fn(async () => {}),
}));

vi.mock("../plugins/provider-oauth-flow.js", () => ({
  createVpsAwareOAuthHandlers: vi.fn(),
}));

vi.mock("../plugins/provider-auth-helpers.js", () => ({
  applyAuthProfileConfig: (
    cfg: OpenClawConfig,
    params: {
      profileId: string;
      provider: string;
      mode: "api_key" | "oauth" | "token";
      email?: string;
      displayName?: string;
    },
  ): OpenClawConfig => ({
    ...cfg,
    auth: {
      ...cfg.auth,
      profiles: {
        ...cfg.auth?.profiles,
        [params.profileId]: {
          provider: params.provider,
          mode: params.mode,
          ...(params.email ? { email: params.email } : {}),
          ...(params.displayName ? { displayName: params.displayName } : {}),
        },
      },
    },
  }),
}));

type StoredAuthProfile = {
  key?: string;
  token?: string;
  keyRef?: { source: string; provider: string; id: string };
  access?: string;
  refresh?: string;
  expires?: number;
  provider?: string;
  type?: string;
  email?: string;
  metadata?: Record<string, string>;
};

const testAuthProfileStores = vi.hoisted(
  () => new Map<string, { profiles: Record<string, StoredAuthProfile> }>(),
);

// These tests verify profile payloads, not file locking; keep auth stores in memory.
function resolveTestAuthStoreKey(agentDir?: string): string {
  return agentDir?.trim() || process.env.OPENCLAW_AGENT_DIR || "__main__";
}

function readTestAuthProfileStore(agentDir?: string): {
  profiles: Record<string, StoredAuthProfile>;
} {
  return testAuthProfileStores.get(resolveTestAuthStoreKey(agentDir)) ?? { profiles: {} };
}

function seedTestAuthProfile(params: {
  profileId: string;
  credential: StoredAuthProfile;
  agentDir?: string;
}): void {
  const key = resolveTestAuthStoreKey(params.agentDir);
  const store = testAuthProfileStores.get(key) ?? { profiles: {} };
  store.profiles[params.profileId] = params.credential;
  testAuthProfileStores.set(key, store);
}

vi.mock("../agents/auth-profiles.js", () => ({
  upsertAuthProfile: (params: {
    profileId: string;
    credential: StoredAuthProfile;
    agentDir?: string;
  }) => {
    seedTestAuthProfile(params);
  },
}));

function normalizeText(value: unknown): string {
  return typeof value === "string" ? value.trim() : "";
}

function providerConfigPatch(
  providerId: string,
  patch: Record<string, unknown>,
): Partial<OpenClawConfig> {
  const providers: Record<string, ModelProviderConfig> = {
    [providerId]: patch as ModelProviderConfig,
  };
  return {
    models: {
      providers,
    },
  };
}

type TestSecretRef = { source: "env"; provider: string; id: string };
type TestSecretInput = string | TestSecretRef;

function normalizeProviderInput(value: unknown): string | undefined {
  const normalized = normalizeText(value).toLowerCase();
  return normalized || undefined;
}

function buildApiKeyCredential(
  provider: string,
  input: TestSecretInput,
  metadata?: Record<string, string>,
): {
  type: "api_key";
  provider: string;
  key?: string;
  keyRef?: TestSecretRef;
  metadata?: Record<string, string>;
} {
  if (typeof input === "string") {
    return { type: "api_key", provider, key: input, ...(metadata ? { metadata } : {}) };
  }
  return { type: "api_key", provider, keyRef: input, ...(metadata ? { metadata } : {}) };
}

async function resolveRefApiKeyInput(params: {
  env: NodeJS.ProcessEnv;
  envVar: string;
  prompter: WizardPrompter;
}): Promise<TestSecretInput> {
  if (typeof params.prompter.select === "function") {
    const source = await params.prompter.select({
      message: "Choose secret reference source",
      options: [
        { label: "Environment variable", value: "env" },
        { label: "Secret provider", value: "provider" },
      ],
    });
    if (source !== "env") {
      await params.prompter.text?.({ message: "Enter secret provider reference" });
      await params.prompter.note?.(
        "Could not validate provider reference; choose an environment variable instead.",
        "Reference check failed",
      );
    }
  }
  const envName =
    normalizeText(await params.prompter.text?.({ message: "Enter environment variable name" })) ||
    params.envVar;
  await params.prompter.note?.(`Validated environment variable ${envName}.`, "Reference validated");
  return { source: "env", provider: "default", id: envName };
}

async function resolveApiKeyInput(params: {
  ctx: Parameters<ProviderAuthMethod["run"]>[0];
  providerId: string;
  expectedProviders: string[];
  optionKey: string;
  envVar: string;
  promptMessage: string;
  noteMessage?: string;
  noteTitle?: string;
}): Promise<{ input: TestSecretInput; mode?: "plaintext" | "ref" }> {
  const opts = (params.ctx.opts ?? {}) as Record<string, unknown>;
  const flagValue = normalizeText(opts[params.optionKey]);
  const token = flagValue || normalizeText(params.ctx.opts?.token);
  const tokenProvider = normalizeProviderInput(
    flagValue ? params.providerId : params.ctx.opts?.tokenProvider,
  );
  const expectedProviders = params.expectedProviders.map((provider) => provider.toLowerCase());
  if (token && tokenProvider && expectedProviders.includes(tokenProvider)) {
    return { input: token, mode: params.ctx.secretInputMode };
  }

  if (params.noteMessage) {
    await params.ctx.prompter.note(params.noteMessage, params.noteTitle);
  }

  const env = params.ctx.env ?? process.env;
  if (params.ctx.secretInputMode === "ref") {
    return {
      input: await resolveRefApiKeyInput({
        env,
        envVar: params.envVar,
        prompter: params.ctx.prompter,
      }),
      mode: "ref",
    };
  }

  const envValue = normalizeText(env[params.envVar]);
  if (envValue) {
    const useEnv = await params.ctx.prompter.confirm?.({
      message: `Use ${params.envVar} from environment?`,
    });
    if (useEnv) {
      return { input: envValue, mode: "plaintext" };
    }
  }

  return {
    input: normalizeText(await params.ctx.prompter.text({ message: params.promptMessage })),
    mode: "plaintext",
  };
}

async function createApiKeyProvider(params: {
  providerId: string;
  label: string;
  choiceId: string;
  optionKey: string;
  flagName: `--${string}`;
  envVar: string;
  promptMessage: string;
  defaultModel?: string;
  profileId?: string;
  profileIds?: string[];
  expectedProviders?: string[];
  noteMessage?: string;
  noteTitle?: string;
  applyConfig?: Partial<OpenClawConfig>;
}): Promise<ProviderPlugin> {
  const profileIds =
    params.profileIds && params.profileIds.length > 0
      ? params.profileIds
      : [params.profileId ?? `${params.providerId}:default`];
  return {
    id: params.providerId,
    label: params.label,
    auth: [
      {
        id: "api-key",
        label: params.label,
        kind: "api_key",
        wizard: {
          choiceId: params.choiceId,
          choiceLabel: params.label,
          groupId: params.providerId,
          groupLabel: params.label,
        },
        run: async (ctx) => {
          const { input } = await resolveApiKeyInput({
            ctx,
            providerId: params.providerId,
            expectedProviders: params.expectedProviders ?? [params.providerId],
            optionKey: params.optionKey,
            envVar: params.envVar,
            promptMessage: params.promptMessage,
            noteMessage: params.noteMessage,
            noteTitle: params.noteTitle,
          });
          return {
            profiles: profileIds.map((profileId) => ({
              profileId,
              credential: buildApiKeyCredential(
                profileId.split(":", 1)[0] || params.providerId,
                input,
              ),
            })),
            ...(params.applyConfig ? { configPatch: params.applyConfig as OpenClawConfig } : {}),
            ...(params.defaultModel ? { defaultModel: params.defaultModel } : {}),
          };
        },
      },
    ],
  };
}

function createFixedChoiceProvider(params: {
  providerId: string;
  label: string;
  choiceId: string;
  method: ProviderAuthMethod;
}): ProviderPlugin {
  return {
    id: params.providerId,
    label: params.label,
    auth: [
      {
        ...params.method,
        wizard: {
          choiceId: params.choiceId,
          choiceLabel: params.label,
          groupId: params.providerId,
          groupLabel: params.label,
        },
      },
    ],
  };
}

async function createDefaultProviderPlugins(): Promise<ProviderPlugin[]> {
  const createZaiMethod = (choiceId: "zai-api-key" | "zai-coding-global"): ProviderAuthMethod => ({
    id: choiceId === "zai-api-key" ? "api-key" : "coding-global",
    label: "Z.AI API key",
    kind: "api_key",
    wizard: {
      choiceId,
      choiceLabel: "Z.AI API key",
      groupId: "zai",
      groupLabel: "Z.AI",
    },
    run: async (ctx) => {
      const token = normalizeText(await ctx.prompter.text({ message: "Enter Z.AI API key" }));
      const detectResult = await detectZaiEndpoint(
        choiceId === "zai-coding-global"
          ? { apiKey: token, endpoint: "coding-global" }
          : { apiKey: token },
      );
      let baseUrl = detectResult?.baseUrl;
      let modelId = detectResult?.modelId;
      if (!baseUrl || !modelId) {
        if (choiceId === "zai-coding-global") {
          baseUrl = ZAI_CODING_GLOBAL_BASE_URL;
          modelId = "glm-5";
        } else {
          const endpoint = await ctx.prompter.select({
            message: "Select Z.AI endpoint",
            initialValue: "global",
            options: [
              { label: "Global", value: "global" },
              { label: "Coding CN", value: "coding-cn" },
            ],
          });
          baseUrl = endpoint === "coding-cn" ? ZAI_CODING_CN_BASE_URL : ZAI_CODING_GLOBAL_BASE_URL;
          modelId = "glm-5";
        }
      }
      return {
        profiles: [
          {
            profileId: "zai:default",
            credential: buildApiKeyCredential("zai", token),
          },
        ],
        configPatch: providerConfigPatch("zai", { baseUrl }) as OpenClawConfig,
        defaultModel: `zai/${modelId}`,
      };
    },
  });

  return [
    await createApiKeyProvider({
      providerId: "google",
      label: "Gemini API key",
      choiceId: "gemini-api-key",
      optionKey: "geminiApiKey",
      flagName: "--gemini-api-key",
      envVar: "GEMINI_API_KEY",
      promptMessage: "Enter Gemini API key",
      defaultModel: GOOGLE_GEMINI_DEFAULT_MODEL,
    }),
    await createApiKeyProvider({
      providerId: "huggingface",
      label: "Hugging Face API key",
      choiceId: "huggingface-api-key",
      optionKey: "huggingfaceApiKey",
      flagName: "--huggingface-api-key",
      envVar: "HUGGINGFACE_HUB_TOKEN",
      promptMessage: "Enter Hugging Face API key",
      defaultModel: "huggingface/Qwen/Qwen3-Coder-480B-A35B-Instruct",
    }),
    await createApiKeyProvider({
      providerId: "openai",
      label: "OpenAI API key",
      choiceId: "openai-api-key",
      optionKey: "openaiApiKey",
      flagName: "--openai-api-key",
      envVar: "OPENAI_API_KEY",
      promptMessage: "Enter OpenAI API key",
      defaultModel: "openai/gpt-5.5",
    }),
    await createApiKeyProvider({
      providerId: "opencode",
      label: "OpenCode Zen",
      choiceId: "opencode-zen",
      optionKey: "opencodeZenApiKey",
      flagName: "--opencode-zen-api-key",
      envVar: "OPENCODE_API_KEY",
      promptMessage: "Enter OpenCode API key",
      profileIds: ["opencode:default", "opencode-go:default"],
      defaultModel: "opencode/claude-opus-4-6",
      expectedProviders: ["opencode", "opencode-go"],
      noteMessage: "OpenCode uses one API key across the Zen and Go catalogs.",
      noteTitle: "OpenCode",
    }),
    await createApiKeyProvider({
      providerId: "opencode-go",
      label: "OpenCode Go",
      choiceId: "opencode-go",
      optionKey: "opencodeGoApiKey",
      flagName: "--opencode-go-api-key",
      envVar: "OPENCODE_API_KEY",
      promptMessage: "Enter OpenCode API key",
      profileIds: ["opencode-go:default", "opencode:default"],
      defaultModel: "opencode-go/kimi-k2.6",
      expectedProviders: ["opencode", "opencode-go"],
      noteMessage: "OpenCode uses one API key across the Zen and Go catalogs.",
      noteTitle: "OpenCode",
    }),
    await createApiKeyProvider({
      providerId: "openrouter",
      label: "OpenRouter API key",
      choiceId: "openrouter-api-key",
      optionKey: "openrouterApiKey",
      flagName: "--openrouter-api-key",
      envVar: "OPENROUTER_API_KEY",
      promptMessage: "Enter OpenRouter API key",
      defaultModel: "openrouter/auto",
    }),
    await createApiKeyProvider({
      providerId: "synthetic",
      label: "Synthetic API key",
      choiceId: "synthetic-api-key",
      optionKey: "syntheticApiKey",
      flagName: "--synthetic-api-key",
      envVar: "SYNTHETIC_API_KEY",
      promptMessage: "Enter Synthetic API key",
      defaultModel: "synthetic/Synthetic-1",
    }),
    {
      id: "zai",
      label: "Z.AI",
      auth: [createZaiMethod("zai-api-key"), createZaiMethod("zai-coding-global")],
    },
  ];
}

describe("applyAuthChoice", () => {
  const lifecycle = createAuthTestLifecycle([
    "OPENCLAW_STATE_DIR",
    "OPENCLAW_AGENT_DIR",
    "PI_CODING_AGENT_DIR",
    "ANTHROPIC_API_KEY",
    "OPENROUTER_API_KEY",
    "HF_TOKEN",
    "HUGGINGFACE_HUB_TOKEN",
    "GEMINI_API_KEY",
    "OPENCODE_API_KEY",
    "SYNTHETIC_API_KEY",
  ]);
  let authTestRoot: string | null = null;
  let authStateCounter = 0;
  async function setupTempState() {
    if (!authTestRoot) {
      throw new Error("auth test root not initialized");
    }
    testAuthProfileStores.clear();
    const stateDir = path.join(authTestRoot, `state-${++authStateCounter}`);
    const agentDir = path.join(stateDir, "agent");
    process.env.OPENCLAW_STATE_DIR = stateDir;
    process.env.OPENCLAW_AGENT_DIR = agentDir;
    process.env.PI_CODING_AGENT_DIR = agentDir;
  }
  function createPrompter(overrides: Partial<WizardPrompter>): WizardPrompter {
    return createWizardPrompter(overrides, { defaultSelect: "" });
  }
  function createSelectFirstOption(): WizardPrompter["select"] {
    return vi.fn(async (params) => params.options[0]?.value as never);
  }
  function createNoopMultiselect(): WizardPrompter["multiselect"] {
    return vi.fn(async () => []);
  }
  function createApiKeyPromptHarness(
    overrides: Partial<Pick<WizardPrompter, "select" | "multiselect" | "text" | "confirm">> = {},
  ): {
    select: WizardPrompter["select"];
    multiselect: WizardPrompter["multiselect"];
    prompter: WizardPrompter;
    runtime: ReturnType<typeof createExitThrowingRuntime>;
  } {
    const select = overrides.select ?? createSelectFirstOption();
    const multiselect = overrides.multiselect ?? createNoopMultiselect();
    return {
      select,
      multiselect,
      prompter: createPrompter({ ...overrides, select, multiselect }),
      runtime: createExitThrowingRuntime(),
    };
  }
  async function readAuthProfiles() {
    return readTestAuthProfileStore(requireOpenClawAgentDir());
  }
  async function readAuthProfilesForAgentDir(agentDir: string) {
    return readTestAuthProfileStore(agentDir);
  }
  async function readAuthProfile(profileId: string) {
    return (await readAuthProfiles()).profiles?.[profileId];
  }

  let defaultProviderPlugins: ProviderPlugin[] = [];

  beforeAll(async () => {
    authTestRoot = (await setupAuthTestEnv("openclaw-auth-")).stateDir;
    defaultProviderPlugins = await createDefaultProviderPlugins();
    resolvePluginProviders.mockReturnValue(defaultProviderPlugins);
  });

  afterAll(async () => {
    if (authTestRoot) {
      await fs.rm(authTestRoot, { recursive: true, force: true });
    }
  });

  afterEach(async () => {
    vi.unstubAllGlobals();
    resolvePluginProviders.mockReset();
    resolvePluginProviders.mockReturnValue(defaultProviderPlugins);
    runProviderModelSelectedHook.mockClear();
    detectZaiEndpoint.mockReset();
    detectZaiEndpoint.mockResolvedValue(null);
    testAuthProfileStores.clear();
    await lifecycle.cleanup();
  });

  it("applies Anthropic setup-token auth when the provider exposes the setup flow", async () => {
    await setupTempState();

    resolvePluginProviders.mockReturnValue([
      createFixedChoiceProvider({
        providerId: "anthropic",
        label: "Anthropic",
        choiceId: "setup-token",
        method: {
          id: "setup-token",
          label: "Anthropic setup-token",
          kind: "token",
          run: vi.fn(
            async (): Promise<ProviderAuthResult> => ({
              profiles: [
                {
                  profileId: "anthropic:default",
                  credential: {
                    type: "token",
                    provider: "anthropic",
                    token: `sk-ant-oat01-${"a".repeat(80)}`,
                  },
                },
              ],
              defaultModel: "anthropic/claude-sonnet-4-6",
            }),
          ),
        },
      }),
    ]);

    const result = await applyAuthChoice({
      authChoice: "token",
      config: {} as OpenClawConfig,
      prompter: createPrompter({}),
      runtime: createExitThrowingRuntime(),
      setDefaultModel: true,
      opts: {
        tokenProvider: "anthropic",
        token: `sk-ant-oat01-${"a".repeat(80)}`,
      },
    });

    expect(result.config.auth?.profiles?.["anthropic:default"]).toMatchObject({
      provider: "anthropic",
      mode: "token",
    });
    expect(resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)).toBe(
      "anthropic/claude-sonnet-4-6",
    );
    expect((await readAuthProfile("anthropic:default"))?.token).toBe(
      `sk-ant-oat01-${"a".repeat(80)}`,
    );
  });

  it("fails fast when a removed provider auth choice is passed to the interactive flow", async () => {
    await expect(
      applyAuthChoice({
        authChoice: "openai-codex-import",
        config: {},
        prompter: createPrompter({}),
        runtime: createExitThrowingRuntime(),
        setDefaultModel: true,
      }),
    ).rejects.toThrow(
      'Auth choice "openai-codex-import" is no longer supported. Use "openai-codex" instead.',
    );
  });

  it("escapes removed provider auth choice guidance for terminal output", async () => {
    const spy = vi
      .spyOn(providerAuthChoices, "resolveManifestDeprecatedProviderAuthChoice")
      .mockReturnValueOnce({
        choiceId: "modern\nchoice",
      } as never);
    try {
      await expect(
        applyAuthChoice({
          authChoice: "legacy\u001b[31mchoice",
          config: {},
          prompter: createPrompter({}),
          runtime: createExitThrowingRuntime(),
          setDefaultModel: true,
        }),
      ).rejects.toThrow(
        'Auth choice "legacy\\u001b[31mchoice" is no longer supported. Use "modern\\nchoice" instead.',
      );
    } finally {
      spy.mockRestore();
    }
  });

  it("prompts and writes provider API key profiles for common providers", async () => {
    const scenarios: Array<{
      authChoice: "huggingface-api-key";
      promptContains: string;
      profileId: string;
      provider: string;
      token: string;
    }> = [
      {
        authChoice: "huggingface-api-key" as const,
        promptContains: "Hugging Face",
        profileId: "huggingface:default",
        provider: "huggingface",
        token: "hf-test-token",
      },
    ];
    await setupTempState();
    for (const scenario of scenarios) {
      const text = vi.fn().mockResolvedValue(scenario.token);
      const { prompter, runtime } = createApiKeyPromptHarness({ text });

      const result = await applyAuthChoice({
        authChoice: scenario.authChoice,
        config: {},
        prompter,
        runtime,
        setDefaultModel: true,
      });

      expect(text).toHaveBeenCalledWith(
        expect.objectContaining({ message: expect.stringContaining(scenario.promptContains) }),
      );
      expect(result.config.auth?.profiles?.[scenario.profileId]).toMatchObject({
        provider: scenario.provider,
        mode: "api_key",
      });
      expect((await readAuthProfile(scenario.profileId))?.key).toBe(scenario.token);
    }
  });

  it("uses Z.AI endpoint detection and prompts in the auth flow", async () => {
    const scenarios: Array<{
      authChoice: "zai-api-key" | "zai-coding-global";
      token: string;
      endpointSelection?: "coding-cn" | "global";
      detectResult?: {
        endpoint: "coding-global" | "coding-cn";
        modelId: string;
        baseUrl: string;
        note: string;
      };
      shouldPromptForEndpoint: boolean;
      expectedDetectCall?: { apiKey: string; endpoint?: "coding-global" | "coding-cn" };
    }> = [
      {
        authChoice: "zai-api-key",
        token: "zai-test-key",
        endpointSelection: "coding-cn",
        shouldPromptForEndpoint: true,
      },
      {
        authChoice: "zai-coding-global",
        token: "zai-test-key",
        detectResult: {
          endpoint: "coding-global",
          modelId: "glm-4.7",
          baseUrl: ZAI_CODING_GLOBAL_BASE_URL,
          note: "Detected coding-global endpoint with GLM-4.7 fallback",
        },
        shouldPromptForEndpoint: false,
        expectedDetectCall: { apiKey: "zai-test-key", endpoint: "coding-global" },
      },
    ];
    await setupTempState();
    for (const scenario of scenarios) {
      detectZaiEndpoint.mockReset();
      detectZaiEndpoint.mockResolvedValue(null);
      if (scenario.detectResult) {
        detectZaiEndpoint.mockResolvedValueOnce(scenario.detectResult);
      }

      const text = vi.fn().mockResolvedValue(scenario.token);
      const select = vi.fn(async (params: { message: string }) => {
        if (params.message === "Select Z.AI endpoint") {
          return scenario.endpointSelection ?? "global";
        }
        return "default";
      });
      const { prompter, runtime } = createApiKeyPromptHarness({
        select: select as WizardPrompter["select"],
        text,
      });

      const result = await applyAuthChoice({
        authChoice: scenario.authChoice,
        config: {},
        prompter,
        runtime,
        setDefaultModel: true,
      });

      if (scenario.expectedDetectCall) {
        expect(detectZaiEndpoint).toHaveBeenCalledWith(scenario.expectedDetectCall);
      }
      if (scenario.shouldPromptForEndpoint) {
        expect(select).toHaveBeenCalledWith(
          expect.objectContaining({ message: "Select Z.AI endpoint", initialValue: "global" }),
        );
      } else {
        expect(select).not.toHaveBeenCalledWith(
          expect.objectContaining({ message: "Select Z.AI endpoint" }),
        );
      }
      expect(result.config.auth?.profiles?.["zai:default"]).toMatchObject({
        provider: "zai",
        mode: "api_key",
      });
      expect((await readAuthProfile("zai:default"))?.key).toBe(scenario.token);
    }
  });

  it("uses provided tokens without prompting across alias and direct provider choices", async () => {
    const scenarios: Array<{
      authChoice: "apiKey" | "gemini-api-key";
      config?: OpenClawConfig;
      setDefaultModel: boolean;
      tokenProvider: string;
      token: string;
      profileId: string;
      provider: string;
      expectedModel?: string;
      expectedModelPrefix?: string;
      expectedAgentModelOverride?: string;
      extraProfiles?: string[];
    }> = [
      {
        authChoice: "apiKey",
        setDefaultModel: true,
        tokenProvider: " GOOGLE  ",
        token: "sk-gemini-token-provider-test",
        profileId: "google:default",
        provider: "google",
        expectedModel: GOOGLE_GEMINI_DEFAULT_MODEL,
      },
      {
        authChoice: "gemini-api-key",
        config: { agents: { defaults: { model: { primary: "openai/gpt-4o-mini" } } } },
        setDefaultModel: false,
        tokenProvider: "google",
        token: "sk-gemini-test",
        profileId: "google:default",
        provider: "google",
        expectedModel: "openai/gpt-4o-mini",
        expectedAgentModelOverride: GOOGLE_GEMINI_DEFAULT_MODEL,
      },
    ];
    await setupTempState();
    for (const scenario of scenarios) {
      delete process.env.HF_TOKEN;
      delete process.env.HUGGINGFACE_HUB_TOKEN;

      const text = vi.fn().mockResolvedValue("should-not-be-used");
      const confirm = vi.fn(async () => false);
      const { prompter, runtime } = createApiKeyPromptHarness({ text, confirm });

      const result = await applyAuthChoice({
        authChoice: scenario.authChoice,
        config: scenario.config ?? {},
        prompter,
        runtime,
        setDefaultModel: scenario.setDefaultModel,
        opts: {
          tokenProvider: scenario.tokenProvider,
          token: scenario.token,
        },
      });

      expect(text).not.toHaveBeenCalled();
      expect(confirm).not.toHaveBeenCalled();
      expect(result.config.auth?.profiles?.[scenario.profileId]).toMatchObject({
        provider: scenario.provider,
        mode: "api_key",
      });
      const selectedModel = resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model);
      if (scenario.expectedModel) {
        expect(selectedModel).toBe(scenario.expectedModel);
      }
      if (scenario.expectedModelPrefix) {
        expect(selectedModel?.startsWith(scenario.expectedModelPrefix)).toBe(true);
      }
      if (scenario.expectedAgentModelOverride) {
        expect(result.agentModelOverride).toBe(scenario.expectedAgentModelOverride);
      }
      expect((await readAuthProfile(scenario.profileId))?.key).toBe(scenario.token);
      for (const extraProfile of scenario.extraProfiles ?? []) {
        expect((await readAuthProfile(extraProfile))?.key).toBe(scenario.token);
      }
    }
  });

  it("uses existing env API keys for selected providers", async () => {
    const scenarios: Array<{
      authChoice: "openrouter-api-key";
      envKey: "OPENROUTER_API_KEY";
      envValue: string;
      profileId: string;
      provider: string;
      expectEnvPrompt: boolean;
      expectedTextCalls: number;
      expectedKey?: string;
      expectedModel?: string;
    }> = [
      {
        authChoice: "openrouter-api-key",
        envKey: "OPENROUTER_API_KEY",
        envValue: "sk-openrouter-test",
        profileId: "openrouter:default",
        provider: "openrouter",
        expectEnvPrompt: true,
        expectedTextCalls: 0,
        expectedKey: "sk-openrouter-test",
        expectedModel: "openrouter/auto",
      },
    ];
    await setupTempState();
    for (const scenario of scenarios) {
      delete process.env.SYNTHETIC_API_KEY;
      delete process.env.OPENROUTER_API_KEY;
      process.env[scenario.envKey] = scenario.envValue;

      const text = vi.fn();
      const confirm = vi.fn(async () => true);
      const { prompter, runtime } = createApiKeyPromptHarness({ text, confirm });

      const result = await applyAuthChoice({
        authChoice: scenario.authChoice,
        config: {},
        prompter,
        runtime,
        setDefaultModel: true,
      });

      if (scenario.expectEnvPrompt) {
        expect(confirm).toHaveBeenCalledWith(
          expect.objectContaining({
            message: expect.stringContaining(scenario.envKey),
          }),
        );
      } else {
        expect(confirm).not.toHaveBeenCalled();
      }
      expect(text).toHaveBeenCalledTimes(scenario.expectedTextCalls);
      expect(result.config.auth?.profiles?.[scenario.profileId]).toMatchObject({
        provider: scenario.provider,
        mode: "api_key",
      });
      if (scenario.expectedModel) {
        expect(resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)).toBe(
          scenario.expectedModel,
        );
      }
      const profile = await readAuthProfile(scenario.profileId);
      expect(profile?.key).toBe(scenario.expectedKey);
      expect(profile?.keyRef).toBeUndefined();
    }
  });

  it("keeps an existing default model when configure re-applies provider auth", async () => {
    await setupTempState();
    vi.stubEnv("OPENROUTER_API_KEY", "sk-openrouter-test");
    const note = vi.fn();
    const confirm = vi.fn(async () => true);
    const text = vi.fn();
    const existingPrimary = "anthropic/claude-opus-4-6";
    const prompter = createPrompter({ text, confirm, note });

    const result = await applyAuthChoice({
      authChoice: "openrouter-api-key",
      config: { agents: { defaults: { model: { primary: existingPrimary } } } },
      prompter,
      runtime: createExitThrowingRuntime(),
      setDefaultModel: true,
      preserveExistingDefaultModel: true,
    });

    expect(resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)).toBe(
      existingPrimary,
    );
    expect(result.config.agents?.defaults?.models?.["openrouter/auto"]).toEqual({});
    expect(runProviderModelSelectedHook).not.toHaveBeenCalled();
    expect(note).toHaveBeenCalledWith(
      "Kept existing default model anthropic/claude-opus-4-6; openrouter/auto is available.",
      "Model configured",
    );
  });

  it("uses explicit env for plugin auth resolution instead of host env", async () => {
    await setupTempState();
    process.env.OPENAI_API_KEY = "sk-openai-host"; // pragma: allowlist secret
    const env = { OPENAI_API_KEY: "sk-openai-explicit" } as NodeJS.ProcessEnv; // pragma: allowlist secret
    const text = vi.fn().mockResolvedValue("should-not-be-used");
    const confirm = vi.fn(async () => true);
    const { prompter, runtime } = createApiKeyPromptHarness({ text, confirm });

    const result = await applyAuthChoice({
      authChoice: "openai-api-key",
      config: {},
      env,
      prompter,
      runtime,
      setDefaultModel: false,
    });

    expect(resolvePluginProviders).toHaveBeenCalledWith(
      expect.objectContaining({
        env,
        mode: "setup",
      }),
    );
    expect(confirm).toHaveBeenCalledWith(
      expect.objectContaining({
        message: expect.stringContaining("OPENAI_API_KEY"),
      }),
    );
    expect(text).not.toHaveBeenCalled();
    expect(result.config.auth?.profiles?.["openai:default"]).toMatchObject({
      provider: "openai",
      mode: "api_key",
    });
    expect((await readAuthProfile("openai:default"))?.key).toBe("sk-openai-explicit");
  });

  it("keeps existing default model for explicit provider keys when setDefaultModel=false", async () => {
    const scenarios: Array<{
      authChoice: "synthetic-api-key" | "opencode-zen";
      token: string | undefined;
      promptMessage: string;
      existingPrimary: string;
      expectedOverride: string;
      profileId?: string;
      profileProvider?: string;
      expectedStoredKey?: string;
      extraProfileId?: string;
      expectProviderConfigUndefined?: "opencode";
      agentId?: string;
    }> = [
      {
        authChoice: "synthetic-api-key",
        token: undefined,
        promptMessage: "Enter Synthetic API key",
        existingPrimary: "openai/gpt-4o-mini",
        expectedOverride: "synthetic/Synthetic-1",
        profileId: "synthetic:default",
        profileProvider: "synthetic",
        expectedStoredKey: "",
        agentId: "agent-1",
      },
      {
        authChoice: "opencode-zen",
        token: "sk-opencode-zen-test",
        promptMessage: "Enter OpenCode API key",
        existingPrimary: "anthropic/claude-opus-4-5",
        expectedOverride: "opencode/claude-opus-4-6",
        profileId: "opencode:default",
        profileProvider: "opencode",
        extraProfileId: "opencode-go:default",
        expectProviderConfigUndefined: "opencode",
      },
    ];
    await setupTempState();
    for (const scenario of scenarios) {
      const text = vi.fn().mockResolvedValue(scenario.token);
      const { prompter, runtime } = createApiKeyPromptHarness({ text });

      const result = await applyAuthChoice({
        authChoice: scenario.authChoice,
        config: { agents: { defaults: { model: { primary: scenario.existingPrimary } } } },
        prompter,
        runtime,
        setDefaultModel: false,
        agentId: scenario.agentId,
      });

      expect(text).toHaveBeenCalledWith(
        expect.objectContaining({ message: scenario.promptMessage }),
      );
      expect(resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)).toBe(
        scenario.existingPrimary,
      );
      expect(result.agentModelOverride).toBe(scenario.expectedOverride);
      if (scenario.profileId && scenario.profileProvider) {
        expect(result.config.auth?.profiles?.[scenario.profileId]).toMatchObject({
          provider: scenario.profileProvider,
          mode: "api_key",
        });
        const profileStore =
          scenario.agentId && scenario.agentId !== "default"
            ? await readAuthProfilesForAgentDir(resolveAgentDir(result.config, scenario.agentId))
            : await readAuthProfiles();
        expect(profileStore.profiles?.[scenario.profileId]?.key).toBe(
          scenario.expectedStoredKey ?? scenario.token,
        );
        expect(profileStore.profiles?.[scenario.profileId]?.key).not.toBe("undefined");
      }
      if (scenario.extraProfileId) {
        const profileStore =
          scenario.agentId && scenario.agentId !== "default"
            ? await readAuthProfilesForAgentDir(resolveAgentDir(result.config, scenario.agentId))
            : await readAuthProfiles();
        expect(profileStore.profiles?.[scenario.extraProfileId]?.key).toBe(scenario.token);
      }
      if (scenario.expectProviderConfigUndefined) {
        expect(
          result.config.models?.providers?.[scenario.expectProviderConfigUndefined],
        ).toBeUndefined();
      }
    }
  });
});

¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.