mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 01:52:04 +00:00
fix: restore provider auth and build checks
This commit is contained in:
@@ -65,7 +65,7 @@ describe("compaction identifier-preservation instructions", () => {
|
||||
}
|
||||
|
||||
function firstSummaryInstructions() {
|
||||
return mockGenerateSummary.mock.calls[0]?.[6];
|
||||
return mockGenerateSummary.mock.calls[0]?.[5];
|
||||
}
|
||||
|
||||
it("injects identifier-preservation guidance even without custom instructions", async () => {
|
||||
@@ -101,7 +101,7 @@ describe("compaction identifier-preservation instructions", () => {
|
||||
|
||||
expect(mockGenerateSummary.mock.calls.length).toBeGreaterThan(1);
|
||||
for (const call of mockGenerateSummary.mock.calls) {
|
||||
expect(call[6]).toContain("Preserve all opaque identifiers exactly as written");
|
||||
expect(call[5]).toContain("Preserve all opaque identifiers exactly as written");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -114,7 +114,7 @@ describe("compaction identifier-preservation instructions", () => {
|
||||
});
|
||||
|
||||
const mergedCall = mockGenerateSummary.mock.calls.at(-1);
|
||||
const instructions = mergedCall?.[6] ?? "";
|
||||
const instructions = mergedCall?.[5] ?? "";
|
||||
expect(instructions).toContain("Merge these partial summaries into a single cohesive summary.");
|
||||
expect(instructions).toContain("Prioritize customer-visible regressions.");
|
||||
expect((instructions.match(/Additional focus:/g) ?? []).length).toBe(1);
|
||||
|
||||
@@ -56,7 +56,7 @@ describe("compaction retry integration", () => {
|
||||
} as unknown as NonNullable<ExtensionContext["model"]>;
|
||||
|
||||
const invokeGenerateSummary = (signal = new AbortController().signal) =>
|
||||
mockGenerateSummary(testMessages, testModel, 1000, "test-api-key", undefined, signal);
|
||||
mockGenerateSummary(testMessages, testModel, 1000, "test-api-key", signal);
|
||||
|
||||
const runSummaryRetry = (options: Parameters<typeof retryAsync>[1]) =>
|
||||
retryAsync(() => invokeGenerateSummary(), options);
|
||||
|
||||
@@ -19,7 +19,13 @@ vi.mock("@mariozechner/pi-coding-agent", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
import { isOversizedForSummary, summarizeWithFallback } from "./compaction.js";
|
||||
let isOversizedForSummary: typeof import("./compaction.js").isOversizedForSummary;
|
||||
let summarizeWithFallback: typeof import("./compaction.js").summarizeWithFallback;
|
||||
|
||||
async function loadFreshCompactionModuleForTest() {
|
||||
vi.resetModules();
|
||||
({ isOversizedForSummary, summarizeWithFallback } = await import("./compaction.js"));
|
||||
}
|
||||
|
||||
function makeAssistantToolCall(timestamp: number): AssistantMessage {
|
||||
return makeAgentAssistantMessage({
|
||||
@@ -43,8 +49,12 @@ function makeToolResultWithDetails(timestamp: number): ToolResultMessage<{ raw:
|
||||
}
|
||||
|
||||
describe("compaction toolResult details stripping", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
beforeEach(async () => {
|
||||
await loadFreshCompactionModuleForTest();
|
||||
piCodingAgentMocks.generateSummary.mockReset();
|
||||
piCodingAgentMocks.generateSummary.mockResolvedValue("summary");
|
||||
piCodingAgentMocks.estimateTokens.mockReset();
|
||||
piCodingAgentMocks.estimateTokens.mockImplementation((_message: unknown) => 1);
|
||||
});
|
||||
|
||||
it("does not pass toolResult.details into generateSummary", async () => {
|
||||
|
||||
@@ -1649,7 +1649,7 @@ describe("compaction-safeguard extension model fallback", () => {
|
||||
messageText: "test message",
|
||||
tokensBefore: 1000,
|
||||
});
|
||||
const { result, getApiKeyAndHeadersMock } = await runCompactionScenario({
|
||||
const { result, getApiKeyMock } = await runCompactionScenario({
|
||||
sessionManager,
|
||||
event: mockEvent,
|
||||
apiKey: null,
|
||||
@@ -1660,7 +1660,7 @@ describe("compaction-safeguard extension model fallback", () => {
|
||||
// KEY ASSERTION: Prove the fallback path was exercised
|
||||
// The handler should have resolved request auth with runtime.model
|
||||
// (via ctx.model ?? runtime?.model).
|
||||
expect(getApiKeyAndHeadersMock).toHaveBeenCalledWith(model);
|
||||
expect(getApiKeyMock).toHaveBeenCalledWith(model);
|
||||
|
||||
// Verify runtime.model is still available (for completeness)
|
||||
const retrieved = getCompactionSafeguardRuntime(sessionManager);
|
||||
@@ -1676,7 +1676,7 @@ describe("compaction-safeguard extension model fallback", () => {
|
||||
messageText: "test",
|
||||
tokensBefore: 500,
|
||||
});
|
||||
const { result, getApiKeyAndHeadersMock } = await runCompactionScenario({
|
||||
const { result, getApiKeyMock } = await runCompactionScenario({
|
||||
sessionManager,
|
||||
event: mockEvent,
|
||||
apiKey: null,
|
||||
@@ -1685,7 +1685,7 @@ describe("compaction-safeguard extension model fallback", () => {
|
||||
expect(result).toEqual({ cancel: true });
|
||||
|
||||
// Verify early return: request auth should NOT have been resolved when both models are missing.
|
||||
expect(getApiKeyAndHeadersMock).not.toHaveBeenCalled();
|
||||
expect(getApiKeyMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1706,7 +1706,7 @@ describe("compaction-safeguard double-compaction guard", () => {
|
||||
customInstructions: "",
|
||||
signal: new AbortController().signal,
|
||||
};
|
||||
const { result, getApiKeyAndHeadersMock } = await runCompactionScenario({
|
||||
const { result, getApiKeyMock } = await runCompactionScenario({
|
||||
sessionManager,
|
||||
event: mockEvent,
|
||||
apiKey: "sk-test", // pragma: allowlist secret
|
||||
@@ -1720,7 +1720,7 @@ describe("compaction-safeguard double-compaction guard", () => {
|
||||
expect(compaction.summary).toContain("## Open TODOs");
|
||||
expect(compaction.firstKeptEntryId).toBe("entry-1");
|
||||
expect(compaction.tokensBefore).toBe(1500);
|
||||
expect(getApiKeyAndHeadersMock).not.toHaveBeenCalled();
|
||||
expect(getApiKeyMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns compaction result with structured fallback summary sections", async () => {
|
||||
@@ -1831,13 +1831,13 @@ describe("compaction-safeguard double-compaction guard", () => {
|
||||
messageText: "real message",
|
||||
tokensBefore: 1500,
|
||||
});
|
||||
const { result, getApiKeyAndHeadersMock } = await runCompactionScenario({
|
||||
const { result, getApiKeyMock } = await runCompactionScenario({
|
||||
sessionManager,
|
||||
event: mockEvent,
|
||||
apiKey: null,
|
||||
});
|
||||
expect(result).toEqual({ cancel: true });
|
||||
expect(getApiKeyAndHeadersMock).toHaveBeenCalled();
|
||||
expect(getApiKeyMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats tool results as real conversation only when linked to a meaningful user ask", async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Skill } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export function resolveSkillSource(skill: Skill): string {
|
||||
return skill.sourceInfo.source;
|
||||
return skill.source;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { normalizeWhatsAppTarget } from "../../../plugin-sdk/whatsapp-shared.js";
|
||||
import { normalizeWhatsAppTarget } from "../../../plugin-sdk/whatsapp-targets.js";
|
||||
import { looksLikeHandleOrPhoneTarget, trimMessagingTarget } from "./shared.js";
|
||||
|
||||
export function normalizeWhatsAppMessagingTarget(raw: string): string | undefined {
|
||||
|
||||
@@ -53,6 +53,7 @@ function createPromptAndCredentialSpies(params?: { confirmResult?: boolean; text
|
||||
|
||||
async function ensureMinimaxApiKey(params: {
|
||||
config?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["config"];
|
||||
env?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["env"];
|
||||
confirm: WizardPrompter["confirm"];
|
||||
note?: WizardPrompter["note"];
|
||||
select?: WizardPrompter["select"];
|
||||
@@ -62,6 +63,7 @@ async function ensureMinimaxApiKey(params: {
|
||||
}) {
|
||||
return await ensureMinimaxApiKeyInternal({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
prompter: createPrompter({
|
||||
confirm: params.confirm,
|
||||
note: params.note,
|
||||
@@ -75,12 +77,14 @@ async function ensureMinimaxApiKey(params: {
|
||||
|
||||
async function ensureMinimaxApiKeyInternal(params: {
|
||||
config?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["config"];
|
||||
env?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["env"];
|
||||
prompter: WizardPrompter;
|
||||
secretInputMode?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["secretInputMode"];
|
||||
setCredential: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["setCredential"];
|
||||
}) {
|
||||
return await ensureApiKeyFromEnvOrPrompt({
|
||||
config: params.config ?? {},
|
||||
env: params.env,
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: "Enter key",
|
||||
@@ -94,6 +98,7 @@ async function ensureMinimaxApiKeyInternal(params: {
|
||||
|
||||
async function ensureMinimaxApiKeyWithEnvRefPrompter(params: {
|
||||
config?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["config"];
|
||||
env?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["env"];
|
||||
note: WizardPrompter["note"];
|
||||
select: WizardPrompter["select"];
|
||||
setCredential: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["setCredential"];
|
||||
@@ -101,6 +106,7 @@ async function ensureMinimaxApiKeyWithEnvRefPrompter(params: {
|
||||
}) {
|
||||
return await ensureMinimaxApiKeyInternal({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
prompter: createPrompter({ select: params.select, text: params.text, note: params.note }),
|
||||
secretInputMode: "ref", // pragma: allowlist secret
|
||||
setCredential: params.setCredential,
|
||||
@@ -287,6 +293,28 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
expect(setCredential).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses explicit env for ref fallback instead of host process env", async () => {
|
||||
process.env.MINIMAX_API_KEY = "host-key"; // pragma: allowlist secret
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
const env = { MINIMAX_API_KEY: "explicit-key" } as NodeJS.ProcessEnv;
|
||||
|
||||
const { confirm, text, setCredential } = createPromptAndCredentialSpies({
|
||||
confirmResult: true,
|
||||
textResult: "prompt-key",
|
||||
});
|
||||
|
||||
const result = await ensureMinimaxApiKey({
|
||||
confirm,
|
||||
text,
|
||||
env,
|
||||
secretInputMode: "ref", // pragma: allowlist secret
|
||||
setCredential,
|
||||
});
|
||||
|
||||
expect(result).toBe("explicit-key");
|
||||
expectMinimaxEnvRefCredentialStored(setCredential);
|
||||
});
|
||||
|
||||
it("re-prompts after provider ref validation failure and succeeds with env ref", async () => {
|
||||
process.env.MINIMAX_API_KEY = "env-key"; // pragma: allowlist secret
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { AuthChoice, OnboardOptions } from "./onboard-types.js";
|
||||
export type ApplyAuthChoiceParams = {
|
||||
authChoice: AuthChoice;
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
prompter: WizardPrompter;
|
||||
runtime: RuntimeEnv;
|
||||
agentDir?: string;
|
||||
@@ -30,13 +31,13 @@ export async function applyAuthChoice(
|
||||
const normalizedAuthChoice =
|
||||
normalizeLegacyOnboardAuthChoice(params.authChoice, {
|
||||
config: params.config,
|
||||
env: process.env,
|
||||
env: params.env,
|
||||
}) ?? params.authChoice;
|
||||
const normalizedProviderAuthChoice = normalizeApiKeyTokenProviderAuthChoice({
|
||||
authChoice: normalizedAuthChoice,
|
||||
tokenProvider: params.opts?.tokenProvider,
|
||||
config: params.config,
|
||||
env: process.env,
|
||||
env: params.env,
|
||||
});
|
||||
const normalizedParams =
|
||||
normalizedProviderAuthChoice === params.authChoice
|
||||
|
||||
@@ -67,9 +67,38 @@ describe("resolvePreferredProviderForAuthChoice", () => {
|
||||
expect(resolvePluginProviders).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to static core choices when no provider plugin claims the choice", async () => {
|
||||
it("passes explicit env through legacy auth normalization", async () => {
|
||||
const env = { OPENCLAW_AUTH_CHOICE_TEST: "1" } as NodeJS.ProcessEnv;
|
||||
resolveManifestDeprecatedProviderAuthChoice.mockReturnValue({
|
||||
choiceId: "anthropic-cli",
|
||||
choiceLabel: "Anthropic Claude CLI",
|
||||
});
|
||||
resolveManifestProviderAuthChoice.mockReturnValue({
|
||||
pluginId: "anthropic",
|
||||
providerId: "anthropic",
|
||||
methodId: "cli",
|
||||
choiceId: "anthropic-cli",
|
||||
choiceLabel: "Anthropic Claude CLI",
|
||||
});
|
||||
|
||||
await expect(
|
||||
resolvePreferredProviderForAuthChoice({ choice: "claude-cli", env }),
|
||||
).resolves.toBe("anthropic");
|
||||
expect(resolveManifestDeprecatedProviderAuthChoice).toHaveBeenCalledWith("claude-cli", { env });
|
||||
});
|
||||
|
||||
it("uses manifest metadata for plugin-owned choices", async () => {
|
||||
resolveManifestProviderAuthChoice.mockReturnValue({
|
||||
pluginId: "chutes",
|
||||
providerId: "chutes",
|
||||
methodId: "oauth",
|
||||
choiceId: "chutes",
|
||||
choiceLabel: "Chutes OAuth",
|
||||
});
|
||||
|
||||
await expect(resolvePreferredProviderForAuthChoice({ choice: "chutes" })).resolves.toBe(
|
||||
"chutes",
|
||||
);
|
||||
expect(resolvePluginProviders).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1394,6 +1394,42 @@ describe("applyAuthChoice", () => {
|
||||
});
|
||||
});
|
||||
|
||||
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({
|
||||
config: {},
|
||||
env,
|
||||
}),
|
||||
);
|
||||
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: "xai-api-key" | "opencode-zen" | "opencode-go";
|
||||
|
||||
@@ -276,6 +276,7 @@ async function runProviderAuthMethod(params: {
|
||||
|
||||
const result = await params.method.run({
|
||||
config: params.config,
|
||||
env: process.env,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: params.workspaceDir,
|
||||
prompter: params.prompter,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
resolveSessionDeliveryTarget,
|
||||
} from "../../infra/outbound/targets.js";
|
||||
import { readChannelAllowFromStoreSync } from "../../pairing/pairing-store.js";
|
||||
import { normalizeWhatsAppTarget } from "../../plugin-sdk/whatsapp-shared.js";
|
||||
import { normalizeWhatsAppTarget } from "../../plugin-sdk/whatsapp-targets.js";
|
||||
import { resolveWhatsAppAccount } from "../../plugin-sdk/whatsapp.js";
|
||||
import { buildChannelAccountBindings } from "../../routing/bindings.js";
|
||||
import { normalizeAccountId, normalizeAgentId } from "../../routing/session-key.js";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { telegramOutbound, whatsappOutbound } from "../../../test/channel-outbounds.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { parseTelegramTarget } from "../../plugin-sdk/telegram.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../plugin-sdk/whatsapp-shared.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../plugin-sdk/whatsapp-targets.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { resolveOutboundTarget } from "./targets.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { telegramOutbound, whatsappOutbound } from "../../../test/channel-outbou
|
||||
import type { ChannelOutboundAdapter } from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SessionEntry } from "../../config/sessions/types.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../plugin-sdk/whatsapp-shared.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../plugin-sdk/whatsapp-targets.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import {
|
||||
|
||||
@@ -71,7 +71,7 @@ export function withBundledPluginVitestCompat(params: {
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): PluginLoadOptions["config"] {
|
||||
const env = params.env ?? process.env;
|
||||
const isVitest = Boolean(env.VITEST || process.env.VITEST);
|
||||
const isVitest = Boolean(env.VITEST);
|
||||
if (
|
||||
!isVitest ||
|
||||
hasExplicitPluginConfig(params.config?.plugins) ||
|
||||
@@ -80,12 +80,20 @@ export function withBundledPluginVitestCompat(params: {
|
||||
return params.config;
|
||||
}
|
||||
|
||||
const entries = Object.fromEntries(
|
||||
params.pluginIds.map((pluginId) => [pluginId, { enabled: true } satisfies PluginEntryConfig]),
|
||||
);
|
||||
|
||||
return {
|
||||
...params.config,
|
||||
plugins: {
|
||||
...params.config?.plugins,
|
||||
enabled: true,
|
||||
allow: [...params.pluginIds],
|
||||
entries: {
|
||||
...entries,
|
||||
...params.config?.plugins?.entries,
|
||||
},
|
||||
slots: {
|
||||
...params.config?.plugins?.slots,
|
||||
memory: "none",
|
||||
|
||||
@@ -46,10 +46,7 @@ export function resolvePluginSnapshotCacheTtlMs(env: NodeJS.ProcessEnv): number
|
||||
return Math.min(discoveryCacheMs, manifestCacheMs);
|
||||
}
|
||||
|
||||
export function buildPluginSnapshotCacheEnvKey(
|
||||
env: NodeJS.ProcessEnv,
|
||||
options: { includeProcessVitestFallback?: boolean } = {},
|
||||
) {
|
||||
export function buildPluginSnapshotCacheEnvKey(env: NodeJS.ProcessEnv) {
|
||||
return {
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: env.OPENCLAW_BUNDLED_PLUGINS_DIR ?? "",
|
||||
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: env.OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE ?? "",
|
||||
@@ -61,8 +58,6 @@ export function buildPluginSnapshotCacheEnvKey(
|
||||
OPENCLAW_CONFIG_PATH: env.OPENCLAW_CONFIG_PATH ?? "",
|
||||
HOME: env.HOME ?? "",
|
||||
USERPROFILE: env.USERPROFILE ?? "",
|
||||
VITEST: options.includeProcessVitestFallback
|
||||
? (env.VITEST ?? process.env.VITEST ?? "")
|
||||
: (env.VITEST ?? ""),
|
||||
VITEST: env.VITEST ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,6 +63,17 @@ function writePluginPackageManifest(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function writePluginManifest(params: { pluginDir: string; id: string }) {
|
||||
fs.writeFileSync(
|
||||
path.join(params.pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: params.id,
|
||||
configSchema: { type: "object" },
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
}
|
||||
|
||||
function expectEscapesPackageDiagnostic(diagnostics: Array<{ message: string }>) {
|
||||
expect(diagnostics.some((entry) => entry.message.includes("escapes package directory"))).toBe(
|
||||
true,
|
||||
@@ -205,6 +216,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
packageName: "@openclaw/ollama-provider",
|
||||
extensions: ["./src/index.ts"],
|
||||
});
|
||||
writePluginManifest({ pluginDir: globalExt, id: "ollama" });
|
||||
fs.writeFileSync(
|
||||
path.join(globalExt, "src", "index.ts"),
|
||||
"export default function () {}",
|
||||
@@ -232,11 +244,13 @@ describe("discoverOpenClawPlugins", () => {
|
||||
packageName: "@openclaw/elevenlabs-speech",
|
||||
extensions: ["./src/index.ts"],
|
||||
});
|
||||
writePluginManifest({ pluginDir: elevenlabsDir, id: "elevenlabs" });
|
||||
writePluginPackageManifest({
|
||||
packageDir: microsoftDir,
|
||||
packageName: "@openclaw/microsoft-speech",
|
||||
extensions: ["./src/index.ts"],
|
||||
});
|
||||
writePluginManifest({ pluginDir: microsoftDir, id: "microsoft" });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(elevenlabsDir, "src", "index.ts"),
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import {
|
||||
DEFAULT_PLUGIN_ENTRY_CANDIDATES,
|
||||
getPackageManifestMetadata,
|
||||
loadPluginManifest,
|
||||
type PluginManifest,
|
||||
resolvePackageExtensionEntries,
|
||||
type OpenClawPackageManifest,
|
||||
@@ -21,14 +22,6 @@ import type { PluginBundleFormat, PluginDiagnostic, PluginFormat, PluginOrigin }
|
||||
|
||||
const EXTENSION_EXTS = new Set([".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"]);
|
||||
|
||||
const CANONICAL_PACKAGE_ID_ALIASES: Record<string, string> = {
|
||||
"elevenlabs-speech": "elevenlabs",
|
||||
"microsoft-speech": "microsoft",
|
||||
"ollama-provider": "ollama",
|
||||
"sglang-provider": "sglang",
|
||||
"vllm-provider": "vllm",
|
||||
};
|
||||
|
||||
export type PluginCandidate = {
|
||||
idHint: string;
|
||||
source: string;
|
||||
@@ -338,10 +331,15 @@ function readPackageManifest(dir: string, rejectHardlinks = true): PackageManife
|
||||
|
||||
function deriveIdHint(params: {
|
||||
filePath: string;
|
||||
manifestId?: string;
|
||||
packageName?: string;
|
||||
hasMultipleExtensions: boolean;
|
||||
}): string {
|
||||
const base = path.basename(params.filePath, path.extname(params.filePath));
|
||||
const rawManifestId = params.manifestId?.trim();
|
||||
if (rawManifestId) {
|
||||
return params.hasMultipleExtensions ? `${rawManifestId}/${base}` : rawManifestId;
|
||||
}
|
||||
const rawPackageName = params.packageName?.trim();
|
||||
if (!rawPackageName) {
|
||||
return base;
|
||||
@@ -352,11 +350,10 @@ function deriveIdHint(params: {
|
||||
const unscoped = rawPackageName.includes("/")
|
||||
? (rawPackageName.split("/").pop() ?? rawPackageName)
|
||||
: rawPackageName;
|
||||
const canonicalPackageId = CANONICAL_PACKAGE_ID_ALIASES[unscoped] ?? unscoped;
|
||||
const normalizedPackageId =
|
||||
canonicalPackageId.endsWith("-provider") && canonicalPackageId.length > "-provider".length
|
||||
? canonicalPackageId.slice(0, -"-provider".length)
|
||||
: canonicalPackageId;
|
||||
unscoped.endsWith("-provider") && unscoped.length > "-provider".length
|
||||
? unscoped.slice(0, -"-provider".length)
|
||||
: unscoped;
|
||||
|
||||
if (!params.hasMultipleExtensions) {
|
||||
return normalizedPackageId;
|
||||
@@ -364,6 +361,11 @@ function deriveIdHint(params: {
|
||||
return `${normalizedPackageId}/${base}`;
|
||||
}
|
||||
|
||||
function resolveIdHintManifestId(rootDir: string, rejectHardlinks: boolean): string | undefined {
|
||||
const manifest = loadPluginManifest(rootDir, rejectHardlinks);
|
||||
return manifest.ok ? manifest.manifest.id : undefined;
|
||||
}
|
||||
|
||||
function addCandidate(params: {
|
||||
candidates: PluginCandidate[];
|
||||
diagnostics: PluginDiagnostic[];
|
||||
@@ -558,6 +560,7 @@ function discoverInDirectory(params: {
|
||||
const manifest = readPackageManifest(fullPath, rejectHardlinks);
|
||||
const extensionResolution = resolvePackageExtensionEntries(manifest ?? undefined);
|
||||
const extensions = extensionResolution.status === "ok" ? extensionResolution.entries : [];
|
||||
const manifestId = resolveIdHintManifestId(fullPath, rejectHardlinks);
|
||||
const setupEntryPath = getPackageManifestMetadata(manifest ?? undefined)?.setupEntry;
|
||||
const setupSource =
|
||||
typeof setupEntryPath === "string" && setupEntryPath.trim().length > 0
|
||||
@@ -588,6 +591,7 @@ function discoverInDirectory(params: {
|
||||
seen: params.seen,
|
||||
idHint: deriveIdHint({
|
||||
filePath: resolved,
|
||||
manifestId,
|
||||
packageName: manifest?.name,
|
||||
hasMultipleExtensions: extensions.length > 1,
|
||||
}),
|
||||
@@ -688,6 +692,7 @@ function discoverFromPath(params: {
|
||||
const manifest = readPackageManifest(resolved, rejectHardlinks);
|
||||
const extensionResolution = resolvePackageExtensionEntries(manifest ?? undefined);
|
||||
const extensions = extensionResolution.status === "ok" ? extensionResolution.entries : [];
|
||||
const manifestId = resolveIdHintManifestId(resolved, rejectHardlinks);
|
||||
const setupEntryPath = getPackageManifestMetadata(manifest ?? undefined)?.setupEntry;
|
||||
const setupSource =
|
||||
typeof setupEntryPath === "string" && setupEntryPath.trim().length > 0
|
||||
@@ -718,6 +723,7 @@ function discoverFromPath(params: {
|
||||
seen: params.seen,
|
||||
idHint: deriveIdHint({
|
||||
filePath: source,
|
||||
manifestId,
|
||||
packageName: manifest?.name,
|
||||
hasMultipleExtensions: extensions.length > 1,
|
||||
}),
|
||||
|
||||
@@ -111,6 +111,7 @@ export function createProviderApiKeyAuthMethod(
|
||||
? (ctx.secretInputMode ?? "plaintext")
|
||||
: ctx.secretInputMode,
|
||||
config: ctx.config,
|
||||
env: ctx.env,
|
||||
expectedProviders: params.expectedProviders ?? [params.providerId],
|
||||
provider: params.providerId,
|
||||
envLabel: params.envVar,
|
||||
|
||||
@@ -2,13 +2,8 @@ import { normalizeLegacyOnboardAuthChoice } from "../commands/auth-choice-legacy
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveManifestProviderAuthChoice } from "./provider-auth-choices.js";
|
||||
|
||||
const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<string, string>> = {
|
||||
chutes: "chutes",
|
||||
"custom-api-key": "custom",
|
||||
};
|
||||
|
||||
function normalizeLegacyAuthChoice(choice: string): string {
|
||||
return normalizeLegacyOnboardAuthChoice(choice, { env: process.env }) ?? choice;
|
||||
function normalizeLegacyAuthChoice(choice: string, env?: NodeJS.ProcessEnv): string {
|
||||
return normalizeLegacyOnboardAuthChoice(choice, { env }) ?? choice;
|
||||
}
|
||||
|
||||
export async function resolvePreferredProviderForAuthChoice(params: {
|
||||
@@ -17,7 +12,7 @@ export async function resolvePreferredProviderForAuthChoice(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<string | undefined> {
|
||||
const choice = normalizeLegacyAuthChoice(params.choice) ?? params.choice;
|
||||
const choice = normalizeLegacyAuthChoice(params.choice, params.env) ?? params.choice;
|
||||
const manifestResolved = resolveManifestProviderAuthChoice(choice, params);
|
||||
if (manifestResolved) {
|
||||
return manifestResolved.providerId;
|
||||
@@ -40,5 +35,8 @@ export async function resolvePreferredProviderForAuthChoice(params: {
|
||||
return pluginResolved.provider.id;
|
||||
}
|
||||
|
||||
return PREFERRED_PROVIDER_BY_AUTH_CHOICE[choice];
|
||||
if (choice === "custom-api-key") {
|
||||
return "custom";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { ProviderAuthMethod, ProviderAuthOptionBag } from "./types.js";
|
||||
export type ApplyProviderAuthChoiceParams = {
|
||||
authChoice: string;
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
prompter: WizardPrompter;
|
||||
runtime: RuntimeEnv;
|
||||
agentDir?: string;
|
||||
@@ -83,6 +84,7 @@ async function loadPluginProviderRuntime() {
|
||||
|
||||
export async function runProviderPluginAuthMethod(params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtime: RuntimeEnv;
|
||||
prompter: WizardPrompter;
|
||||
method: ProviderAuthMethod;
|
||||
@@ -108,6 +110,7 @@ export async function runProviderPluginAuthMethod(params: {
|
||||
|
||||
const result = await params.method.run({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
agentDir,
|
||||
workspaceDir,
|
||||
prompter: params.prompter,
|
||||
@@ -170,6 +173,7 @@ export async function applyAuthChoiceLoadedPluginProvider(
|
||||
const providers = resolvePluginProviders({
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env: params.env,
|
||||
bundledProviderAllowlistCompat: true,
|
||||
bundledProviderVitestCompat: true,
|
||||
});
|
||||
@@ -183,6 +187,7 @@ export async function applyAuthChoiceLoadedPluginProvider(
|
||||
|
||||
const applied = await runProviderPluginAuthMethod({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
runtime: params.runtime,
|
||||
prompter: params.prompter,
|
||||
method: resolved.method,
|
||||
@@ -250,6 +255,7 @@ export async function applyAuthChoicePluginProvider(
|
||||
const providers = resolvePluginProviders({
|
||||
config: nextConfig,
|
||||
workspaceDir,
|
||||
env: params.env,
|
||||
bundledProviderAllowlistCompat: true,
|
||||
bundledProviderVitestCompat: true,
|
||||
});
|
||||
@@ -270,6 +276,7 @@ export async function applyAuthChoicePluginProvider(
|
||||
|
||||
const applied = await runProviderPluginAuthMethod({
|
||||
config: nextConfig,
|
||||
env: params.env,
|
||||
runtime: params.runtime,
|
||||
prompter: params.prompter,
|
||||
method,
|
||||
|
||||
@@ -119,6 +119,7 @@ export async function ensureApiKeyFromOptionEnvOrPrompt(params: {
|
||||
tokenProvider: string | undefined;
|
||||
secretInputMode?: SecretInputMode;
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
expectedProviders: string[];
|
||||
provider: string;
|
||||
envLabel: string;
|
||||
@@ -148,6 +149,7 @@ export async function ensureApiKeyFromOptionEnvOrPrompt(params: {
|
||||
|
||||
return await ensureApiKeyFromEnvOrPrompt({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
provider: params.provider,
|
||||
envLabel: params.envLabel,
|
||||
promptMessage: params.promptMessage,
|
||||
@@ -161,6 +163,7 @@ export async function ensureApiKeyFromOptionEnvOrPrompt(params: {
|
||||
|
||||
export async function ensureApiKeyFromEnvOrPrompt(params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
provider: string;
|
||||
envLabel: string;
|
||||
promptMessage: string;
|
||||
@@ -174,7 +177,8 @@ export async function ensureApiKeyFromEnvOrPrompt(params: {
|
||||
prompter: params.prompter,
|
||||
explicitMode: params.secretInputMode,
|
||||
});
|
||||
const envKey = resolveEnvApiKey(params.provider);
|
||||
const env = params.env ?? process.env;
|
||||
const envKey = resolveEnvApiKey(params.provider, env);
|
||||
|
||||
if (selectedMode === "ref") {
|
||||
if (typeof params.prompter.select !== "function") {
|
||||
@@ -182,6 +186,7 @@ export async function ensureApiKeyFromEnvOrPrompt(params: {
|
||||
config: params.config,
|
||||
provider: params.provider,
|
||||
preferredEnvVar: envKey?.source ? extractEnvVarFromSourceLabel(envKey.source) : undefined,
|
||||
env,
|
||||
});
|
||||
await params.setCredential(fallback.ref, selectedMode);
|
||||
return fallback.resolvedValue;
|
||||
@@ -191,6 +196,7 @@ export async function ensureApiKeyFromEnvOrPrompt(params: {
|
||||
config: params.config,
|
||||
prompter: params.prompter,
|
||||
preferredEnvVar: envKey?.source ? extractEnvVarFromSourceLabel(envKey.source) : undefined,
|
||||
env,
|
||||
});
|
||||
await params.setCredential(resolved.ref, selectedMode);
|
||||
return resolved.resolvedValue;
|
||||
|
||||
@@ -57,6 +57,7 @@ export function resolveRefFallbackInput(params: {
|
||||
config: OpenClawConfig;
|
||||
provider: string;
|
||||
preferredEnvVar?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): { ref: SecretRef; resolvedValue: string } {
|
||||
const fallbackEnvVar = params.preferredEnvVar ?? resolveDefaultProviderEnvVar(params.provider);
|
||||
if (!fallbackEnvVar) {
|
||||
@@ -64,7 +65,8 @@ export function resolveRefFallbackInput(params: {
|
||||
`No default environment variable mapping found for provider "${params.provider}". Set a provider-specific env var, or re-run setup in an interactive terminal to configure a ref.`,
|
||||
);
|
||||
}
|
||||
const value = process.env[fallbackEnvVar]?.trim();
|
||||
const env = params.env ?? process.env;
|
||||
const value = env[fallbackEnvVar]?.trim();
|
||||
if (!value) {
|
||||
throw new Error(
|
||||
`Environment variable "${fallbackEnvVar}" is required for --secret-input-mode ref in non-interactive setup.`,
|
||||
@@ -88,7 +90,9 @@ async function promptEnvSecretRefForSetup(params: {
|
||||
prompter: WizardPrompter;
|
||||
defaultEnvVar: string;
|
||||
copy?: SecretRefSetupPromptCopy;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{ ref: SecretRef; resolvedValue: string }> {
|
||||
const env = params.env ?? process.env;
|
||||
const envVarRaw = await params.prompter.text({
|
||||
message: params.copy?.envVarMessage ?? "Environment variable name",
|
||||
initialValue: params.defaultEnvVar || undefined,
|
||||
@@ -101,7 +105,7 @@ async function promptEnvSecretRefForSetup(params: {
|
||||
'Use an env var name like "OPENAI_API_KEY" (uppercase letters, numbers, underscores).'
|
||||
);
|
||||
}
|
||||
if (!process.env[candidate]?.trim()) {
|
||||
if (!env[candidate]?.trim()) {
|
||||
return (
|
||||
params.copy?.envVarMissingError?.(candidate) ??
|
||||
`Environment variable "${candidate}" is missing or empty in this session.`
|
||||
@@ -118,7 +122,7 @@ async function promptEnvSecretRefForSetup(params: {
|
||||
`No valid environment variable name provided for provider "${params.provider}".`,
|
||||
);
|
||||
}
|
||||
const resolvedValue = process.env[envVar]?.trim();
|
||||
const resolvedValue = env[envVar]?.trim();
|
||||
if (!resolvedValue) {
|
||||
throw new Error(`Environment variable "${envVar}" is missing or empty in this session.`);
|
||||
}
|
||||
@@ -143,6 +147,7 @@ async function promptProviderSecretRefForSetup(params: {
|
||||
prompter: WizardPrompter;
|
||||
defaultFilePointer: string;
|
||||
copy?: SecretRefSetupPromptCopy;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{ ref: SecretRef; resolvedValue: string }> {
|
||||
const externalProviders = Object.entries(params.config.secrets?.providers ?? {}).filter(
|
||||
([, provider]) => provider?.source === "file" || provider?.source === "exec",
|
||||
@@ -229,7 +234,7 @@ async function promptProviderSecretRefForSetup(params: {
|
||||
const { resolveSecretRefString } = await loadSecretResolve();
|
||||
const resolvedValue = await resolveSecretRefString(ref, {
|
||||
config: params.config,
|
||||
env: process.env,
|
||||
env: params.env ?? process.env,
|
||||
});
|
||||
await params.prompter.note(
|
||||
params.copy?.providerValidatedMessage?.(selectedProvider, id, providerEntry.source) ??
|
||||
@@ -256,6 +261,7 @@ export async function promptSecretRefForSetup(params: {
|
||||
prompter: WizardPrompter;
|
||||
preferredEnvVar?: string;
|
||||
copy?: SecretRefSetupPromptCopy;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{ ref: SecretRef; resolvedValue: string }> {
|
||||
const defaultEnvVar =
|
||||
params.preferredEnvVar ?? resolveDefaultProviderEnvVar(params.provider) ?? "";
|
||||
@@ -289,6 +295,7 @@ export async function promptSecretRefForSetup(params: {
|
||||
prompter: params.prompter,
|
||||
defaultEnvVar,
|
||||
copy: params.copy,
|
||||
env: params.env,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -299,6 +306,7 @@ export async function promptSecretRefForSetup(params: {
|
||||
prompter: params.prompter,
|
||||
defaultFilePointer,
|
||||
copy: params.copy,
|
||||
env: params.env,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "retry") {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
writeOAuthCredentials,
|
||||
type WriteOAuthCredentialsOptions,
|
||||
} from "./provider-auth-helpers.js";
|
||||
import { KILOCODE_DEFAULT_MODEL_REF } from "./provider-model-kilocode.js";
|
||||
|
||||
const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir();
|
||||
const ZAI_DEFAULT_MODEL_REF = "zai/glm-5";
|
||||
@@ -53,16 +52,21 @@ function createProviderApiKeySetter(
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
HUGGINGFACE_DEFAULT_MODEL_REF,
|
||||
KILOCODE_DEFAULT_MODEL_REF,
|
||||
LITELLM_DEFAULT_MODEL_REF,
|
||||
OPENROUTER_DEFAULT_MODEL_REF,
|
||||
TOGETHER_DEFAULT_MODEL_REF,
|
||||
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
XIAOMI_DEFAULT_MODEL_REF,
|
||||
ZAI_DEFAULT_MODEL_REF,
|
||||
type ProviderApiKeySetterSpec = {
|
||||
provider: string;
|
||||
resolveKey?: (key: SecretInput) => SecretInput;
|
||||
};
|
||||
|
||||
function createProviderApiKeySetters<const T extends Record<string, ProviderApiKeySetterSpec>>(
|
||||
specs: T,
|
||||
): { [K in keyof T]: ProviderApiKeySetter } {
|
||||
const entries = Object.entries(specs).map(([name, spec]) => [
|
||||
name,
|
||||
createProviderApiKeySetter(spec.provider, spec.resolveKey),
|
||||
]);
|
||||
return Object.fromEntries(entries) as { [K in keyof T]: ProviderApiKeySetter };
|
||||
}
|
||||
|
||||
export {
|
||||
buildApiKeyCredential,
|
||||
type ApiKeyStorageOptions,
|
||||
@@ -70,9 +74,78 @@ export {
|
||||
type WriteOAuthCredentialsOptions,
|
||||
};
|
||||
|
||||
export const setAnthropicApiKey = createProviderApiKeySetter("anthropic");
|
||||
export const setOpenaiApiKey = createProviderApiKeySetter("openai");
|
||||
export const setGeminiApiKey = createProviderApiKeySetter("google");
|
||||
const {
|
||||
setAnthropicApiKey,
|
||||
setOpenaiApiKey,
|
||||
setGeminiApiKey,
|
||||
setMoonshotApiKey,
|
||||
setKimiCodingApiKey,
|
||||
setVolcengineApiKey,
|
||||
setByteplusApiKey,
|
||||
setSyntheticApiKey,
|
||||
setVeniceApiKey,
|
||||
setZaiApiKey,
|
||||
setXiaomiApiKey,
|
||||
setOpenrouterApiKey,
|
||||
setLitellmApiKey,
|
||||
setVercelAiGatewayApiKey,
|
||||
setTogetherApiKey,
|
||||
setHuggingfaceApiKey,
|
||||
setQianfanApiKey,
|
||||
setModelStudioApiKey,
|
||||
setXaiApiKey,
|
||||
setMistralApiKey,
|
||||
setKilocodeApiKey,
|
||||
} = createProviderApiKeySetters({
|
||||
setAnthropicApiKey: { provider: "anthropic" },
|
||||
setOpenaiApiKey: { provider: "openai" },
|
||||
setGeminiApiKey: { provider: "google" },
|
||||
setMoonshotApiKey: { provider: "moonshot" },
|
||||
setKimiCodingApiKey: { provider: "kimi" },
|
||||
setVolcengineApiKey: { provider: "volcengine" },
|
||||
setByteplusApiKey: { provider: "byteplus" },
|
||||
setSyntheticApiKey: { provider: "synthetic" },
|
||||
setVeniceApiKey: { provider: "venice" },
|
||||
setZaiApiKey: { provider: "zai" },
|
||||
setXiaomiApiKey: { provider: "xiaomi" },
|
||||
setOpenrouterApiKey: {
|
||||
provider: "openrouter",
|
||||
resolveKey: (key) => (typeof key === "string" && key === "undefined" ? "" : key),
|
||||
},
|
||||
setLitellmApiKey: { provider: "litellm" },
|
||||
setVercelAiGatewayApiKey: { provider: "vercel-ai-gateway" },
|
||||
setTogetherApiKey: { provider: "together" },
|
||||
setHuggingfaceApiKey: { provider: "huggingface" },
|
||||
setQianfanApiKey: { provider: "qianfan" },
|
||||
setModelStudioApiKey: { provider: "modelstudio" },
|
||||
setXaiApiKey: { provider: "xai" },
|
||||
setMistralApiKey: { provider: "mistral" },
|
||||
setKilocodeApiKey: { provider: "kilocode" },
|
||||
});
|
||||
|
||||
export {
|
||||
setAnthropicApiKey,
|
||||
setOpenaiApiKey,
|
||||
setGeminiApiKey,
|
||||
setMoonshotApiKey,
|
||||
setKimiCodingApiKey,
|
||||
setVolcengineApiKey,
|
||||
setByteplusApiKey,
|
||||
setSyntheticApiKey,
|
||||
setVeniceApiKey,
|
||||
setZaiApiKey,
|
||||
setXiaomiApiKey,
|
||||
setOpenrouterApiKey,
|
||||
setLitellmApiKey,
|
||||
setVercelAiGatewayApiKey,
|
||||
setTogetherApiKey,
|
||||
setHuggingfaceApiKey,
|
||||
setQianfanApiKey,
|
||||
setModelStudioApiKey,
|
||||
setXaiApiKey,
|
||||
setMistralApiKey,
|
||||
setKilocodeApiKey,
|
||||
};
|
||||
|
||||
export async function setMinimaxApiKey(
|
||||
key: SecretInput,
|
||||
@@ -84,18 +157,6 @@ export async function setMinimaxApiKey(
|
||||
upsertProviderApiKeyProfile({ provider, key, agentDir, options, profileId });
|
||||
}
|
||||
|
||||
export const setMoonshotApiKey = createProviderApiKeySetter("moonshot");
|
||||
export const setKimiCodingApiKey = createProviderApiKeySetter("kimi");
|
||||
export const setVolcengineApiKey = createProviderApiKeySetter("volcengine");
|
||||
export const setByteplusApiKey = createProviderApiKeySetter("byteplus");
|
||||
export const setSyntheticApiKey = createProviderApiKeySetter("synthetic");
|
||||
export const setVeniceApiKey = createProviderApiKeySetter("venice");
|
||||
export const setZaiApiKey = createProviderApiKeySetter("zai");
|
||||
export const setXiaomiApiKey = createProviderApiKeySetter("xiaomi");
|
||||
export const setOpenrouterApiKey = createProviderApiKeySetter("openrouter", (key) =>
|
||||
typeof key === "string" && key === "undefined" ? "" : key,
|
||||
);
|
||||
|
||||
export async function setCloudflareAiGatewayConfig(
|
||||
accountId: string,
|
||||
gatewayId: string,
|
||||
@@ -117,9 +178,6 @@ export async function setCloudflareAiGatewayConfig(
|
||||
});
|
||||
}
|
||||
|
||||
export const setLitellmApiKey = createProviderApiKeySetter("litellm");
|
||||
export const setVercelAiGatewayApiKey = createProviderApiKeySetter("vercel-ai-gateway");
|
||||
|
||||
export async function setOpencodeZenApiKey(
|
||||
key: SecretInput,
|
||||
agentDir?: string,
|
||||
@@ -145,11 +203,3 @@ async function setSharedOpencodeApiKey(
|
||||
upsertProviderApiKeyProfile({ provider, key, agentDir, options });
|
||||
}
|
||||
}
|
||||
|
||||
export const setTogetherApiKey = createProviderApiKeySetter("together");
|
||||
export const setHuggingfaceApiKey = createProviderApiKeySetter("huggingface");
|
||||
export const setQianfanApiKey = createProviderApiKeySetter("qianfan");
|
||||
export const setModelStudioApiKey = createProviderApiKeySetter("modelstudio");
|
||||
export const setXaiApiKey = createProviderApiKeySetter("xai");
|
||||
export const setMistralApiKey = createProviderApiKeySetter("mistral");
|
||||
export const setKilocodeApiKey = createProviderApiKeySetter("kilocode");
|
||||
|
||||
@@ -41,20 +41,19 @@ export function resolvePluginProviders(params: {
|
||||
pluginIds: bundledProviderCompatPluginIds,
|
||||
})
|
||||
: params.config;
|
||||
const maybeVitestCompat = params.bundledProviderVitestCompat
|
||||
? withBundledProviderVitestCompat({
|
||||
const allowlistCompatConfig = params.bundledProviderAllowlistCompat
|
||||
? withBundledPluginEnablementCompat({
|
||||
config: maybeAllowlistCompat,
|
||||
pluginIds: bundledProviderCompatPluginIds,
|
||||
})
|
||||
: maybeAllowlistCompat;
|
||||
const config = params.bundledProviderVitestCompat
|
||||
? withBundledProviderVitestCompat({
|
||||
config: allowlistCompatConfig,
|
||||
pluginIds: bundledProviderCompatPluginIds,
|
||||
env: params.env,
|
||||
})
|
||||
: maybeAllowlistCompat;
|
||||
const config =
|
||||
params.bundledProviderAllowlistCompat || params.bundledProviderVitestCompat
|
||||
? withBundledPluginEnablementCompat({
|
||||
config: maybeVitestCompat,
|
||||
pluginIds: bundledProviderCompatPluginIds,
|
||||
})
|
||||
: maybeVitestCompat;
|
||||
: allowlistCompatConfig;
|
||||
const registry = loadOpenClawPlugins({
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
|
||||
@@ -106,6 +106,30 @@ describe("resolvePluginProviders", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not leak host Vitest env into an explicit non-Vitest env", () => {
|
||||
const previousVitest = process.env.VITEST;
|
||||
process.env.VITEST = "1";
|
||||
try {
|
||||
resolvePluginProviders({
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
bundledProviderVitestCompat: true,
|
||||
});
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: undefined,
|
||||
env: {},
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
if (previousVitest === undefined) {
|
||||
delete process.env.VITEST;
|
||||
} else {
|
||||
process.env.VITEST = previousVitest;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("does not reintroduce the retired google auth plugin id into compat allowlists", () => {
|
||||
resolvePluginProviders({
|
||||
config: {
|
||||
|
||||
@@ -169,6 +169,7 @@ export type ProviderAuthResult = {
|
||||
/** Interactive auth context passed to provider login/setup methods. */
|
||||
export type ProviderAuthContext = {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
agentDir?: string;
|
||||
workspaceDir?: string;
|
||||
prompter: WizardPrompter;
|
||||
|
||||
@@ -276,7 +276,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("invalidates the snapshot cache when global Vitest fallback changes", () => {
|
||||
it("does not leak host Vitest env into an explicit non-Vitest cache key", () => {
|
||||
const originalVitest = process.env.VITEST;
|
||||
const config = {};
|
||||
const env = { OPENCLAW_HOME: "/tmp/openclaw-home" } as NodeJS.ProcessEnv;
|
||||
@@ -305,7 +305,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(2);
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("expires web-search snapshot memoization after the shortest plugin cache ttl", () => {
|
||||
|
||||
@@ -44,7 +44,6 @@ function buildWebSearchSnapshotCacheKey(params: {
|
||||
onlyPluginIds?: readonly string[];
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): string {
|
||||
const effectiveVitest = params.env.VITEST ?? process.env.VITEST ?? "";
|
||||
return JSON.stringify({
|
||||
workspaceDir: params.workspaceDir ?? "",
|
||||
bundledAllowlistCompat: params.bundledAllowlistCompat === true,
|
||||
@@ -52,9 +51,7 @@ function buildWebSearchSnapshotCacheKey(params: {
|
||||
left.localeCompare(right),
|
||||
),
|
||||
config: params.config ?? null,
|
||||
env: buildPluginSnapshotCacheEnvKey(params.env, {
|
||||
includeProcessVitestFallback: effectiveVitest !== (params.env.VITEST ?? ""),
|
||||
}),
|
||||
env: buildPluginSnapshotCacheEnvKey(params.env),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user