From fdbab4ce99a641570bcb631901e50571adb23b3d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 18 Sep 2026 03:00:18 -0700 Subject: [PATCH] improve: reduce repeated provider auth lookup work (#151570) * improve: reduce repeated provider auth lookup work * test: complete provider auth metadata fixtures * fix: preserve released provider env snapshot inputs * fix: distinguish provider env core implementations * test: align image provider env mock with core export --- src/acp/client-helpers.ts | 4 +- src/acp/client.test.ts | 2 +- src/agents/ai-transport-runtime-host.test.ts | 1 + .../compact.hooks.metadata.test-support.ts | 1 + .../run.overflow-compaction.harness.ts | 14 +-- .../attempt-spawn-workspace.test-support.ts | 15 +--- src/agents/live-auth-keys.ts | 4 +- src/agents/model-auth-availability.test.ts | 21 ++--- src/agents/model-auth-env-vars.ts | 4 +- .../model-auth-env.provider-aliases.test.ts | 54 +++++------- src/agents/model-fallback.probe.test.ts | 1 + ...providers.implicit.discovery-scope.test.ts | 1 + ...pared-model-runtime.startup-static.test.ts | 1 + .../prepared-model-runtime.test-harness.ts | 1 + ...ider-attribution.catalog-endpoints.test.ts | 25 +++--- src/agents/provider-attribution.test.ts | 13 +-- src/agents/provider-auth-aliases.test.ts | 41 ++++++--- src/agents/provider-request-config.test.ts | 2 + .../sessions/model-registry.sources.test.ts | 1 + .../sessions/model-registry.test-support.ts | 1 + .../embedded-agent-runner-e2e-mocks.ts | 1 + src/agents/tools/image-generate-tool.test.ts | 2 +- .../media-generate-tool-actions-shared.ts | 6 +- src/cli/capability-cli.test.ts | 14 +-- src/cli/capability-cli/audio.ts | 4 +- src/cli/capability-cli/model.ts | 4 +- src/cli/capability-cli/shared.ts | 4 +- src/commands/agent-exec.ts | 4 +- .../doctor-config-preflight.test-support.ts | 4 +- src/commands/doctor-memory-search.ts | 4 +- src/commands/onboard-auth.test.ts | 2 +- src/commands/onboard-inference.test.ts | 1 + src/config/io.context.plugin-metadata.test.ts | 3 + src/config/plugin-auto-enable.test-helpers.ts | 5 +- src/config/shell-env-expected-keys.ts | 4 +- .../gateway-cron-scheduler.live.test.ts | 6 +- .../gateway-cron-tools-allow.live.test.ts | 4 +- ...-list-result.openai-routes.test-support.ts | 7 +- src/gateway/server-methods/models.test.ts | 16 ++-- src/gateway/server-plugins.test.ts | 17 +--- .../server-startup-config.recovery.test.ts | 1 + src/gateway/server-startup-plugins.test.ts | 1 + .../server.chat.gateway-server-chat-b.test.ts | 14 +-- src/image-generation/runtime.ts | 4 +- src/infra/dotenv-workspace-blocklist.test.ts | 10 ++- src/infra/dotenv.ts | 4 +- src/infra/heartbeat-runner.live.test.ts | 6 +- src/infra/provider-usage.auth.plugin.test.ts | 4 +- src/infra/provider-usage.auth.ts | 4 +- src/media-generation/runtime-shared.ts | 6 +- src/music-generation/runtime.ts | 4 +- ...ent-runtime-model-catalog-contract.test.ts | 5 +- src/plugin-sdk/agent-runtime.ts | 14 ++- src/plugin-sdk/image-generation-core.ts | 2 +- src/plugin-sdk/provider-auth.ts | 6 +- src/plugin-sdk/provider-env-vars.test.ts | 70 +++++++++++++++ src/plugin-sdk/provider-env-vars.ts | 69 +++++++++++++-- .../current-plugin-metadata-snapshot.test.ts | 5 +- .../current-plugin-metadata.test-support.ts | 1 + src/plugins/plugin-metadata-provider-facts.ts | 27 +++++- src/plugins/plugin-metadata-snapshot.types.ts | 9 ++ src/plugins/provider-auth-env-trust.test.ts | 10 +-- src/plugins/provider-auth-helpers.ts | 4 +- src/plugins/provider-auth-ref.ts | 6 +- .../provider-model-compat.prepared.test.ts | 1 + src/plugins/providers.test.ts | 11 ++- .../load-context.current-snapshot.test.ts | 1 + src/plugins/runtime/load-context.test.ts | 1 + src/secrets/configure.ts | 4 +- src/secrets/provider-env-vars.dynamic.test.ts | 87 ++++++++++++------- src/secrets/provider-env-vars.test.ts | 23 ++--- src/secrets/provider-env-vars.ts | 43 ++++----- .../loading/workspace-skill-loader.test.ts | 5 +- .../generation-live-test-helpers.ts | 4 +- src/video-generation/runtime.ts | 4 +- .../media/hosted-media-provider-live.ts | 4 +- test/helpers/gateway-codex-harness.ts | 6 +- .../sqlite-sessions-transcripts-flip-proof.ts | 6 +- 78 files changed, 493 insertions(+), 312 deletions(-) create mode 100644 src/plugin-sdk/provider-env-vars.test.ts diff --git a/src/acp/client-helpers.ts b/src/acp/client-helpers.ts index 32937d37521f..323867b8a5fc 100644 --- a/src/acp/client-helpers.ts +++ b/src/acp/client-helpers.ts @@ -11,7 +11,7 @@ import { resolveWindowsSpawnProgram, } from "../plugin-sdk/windows-spawn.js"; import { - listKnownProviderAuthEnvVarNames, + listKnownProviderAuthEnvVarNamesCore, omitEnvKeysCaseInsensitive, } from "../secrets/provider-env-vars.js"; import { classifyAcpToolApproval, type AcpApprovalClass } from "./approval-classifier.js"; @@ -198,7 +198,7 @@ export function buildAcpClientStripKeys(params: { }): Set { const stripKeys = new Set(params.activeSkillEnvKeys ?? []); if (params.stripProviderAuthEnvVars) { - for (const key of listKnownProviderAuthEnvVarNames()) { + for (const key of listKnownProviderAuthEnvVarNamesCore()) { stripKeys.add(key); } } diff --git a/src/acp/client.test.ts b/src/acp/client.test.ts index b2f79396347c..a36eff17c2b9 100644 --- a/src/acp/client.test.ts +++ b/src/acp/client.test.ts @@ -6,7 +6,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; vi.mock("../secrets/provider-env-vars.js", () => ({ - listKnownProviderAuthEnvVarNames: () => [ + listKnownProviderAuthEnvVarNamesCore: () => [ "OPENAI_API_KEY", "OPENAI_ADMIN_KEY", "ANTHROPIC_ADMIN_KEY", diff --git a/src/agents/ai-transport-runtime-host.test.ts b/src/agents/ai-transport-runtime-host.test.ts index 4085f474faf1..b5fae407d688 100644 --- a/src/agents/ai-transport-runtime-host.test.ts +++ b/src/agents/ai-transport-runtime-host.test.ts @@ -21,6 +21,7 @@ function buildOwners(): PluginMetadataSnapshotOwnerMaps { setupProviders: empty, commandAliases: empty, contracts: empty, + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), providerEndpoints: [ { endpointClass: "openai-public", hosts: ["prepared.example"] }, diff --git a/src/agents/embedded-agent-runner/compact.hooks.metadata.test-support.ts b/src/agents/embedded-agent-runner/compact.hooks.metadata.test-support.ts index 1c7ad036019e..20760ca7ed5e 100644 --- a/src/agents/embedded-agent-runner/compact.hooks.metadata.test-support.ts +++ b/src/agents/embedded-agent-runner/compact.hooks.metadata.test-support.ts @@ -36,6 +36,7 @@ export const emptyPluginMetadataSnapshot: PluginMetadataSnapshot = { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts index b846af8a75b8..1c5440f63143 100644 --- a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts +++ b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts @@ -1,11 +1,13 @@ /** * Test harness mocks for embedded-run overflow compaction coverage. */ + import { matchesContextOverflowMessage } from "@openclaw/ai/internal/runtime"; import { type Mock, vi } from "vitest"; import type { ThinkLevel } from "../../auto-reply/thinking.js"; import type { ContextEngine, ContextEngineSessionTarget } from "../../context-engine/types.js"; import { formatErrorMessage } from "../../infra/errors.js"; +import { makeEmptyPluginMetadataOwners } from "../../plugins/current-plugin-metadata.test-support.js"; import type { PluginHookBeforeAgentFinalizeEvent, PluginHookBeforeAgentFinalizeResult, @@ -98,17 +100,7 @@ const emptyPluginMetadataSnapshot: PluginMetadataSnapshot = { byPluginId: new Map(), normalizePluginId: (pluginId: string) => pluginId, declaredProviderOwners: new Map(), - owners: { - channels: new Map(), - channelConfigs: new Map(), - providers: new Map(), - modelCatalogProviders: new Map(), - cliBackends: new Map(), - setupProviders: new Map(), - commandAliases: new Map(), - contracts: new Map(), - modelIdNormalizationPolicies: new Map(), - }, + owners: makeEmptyPluginMetadataOwners(), metrics: { registrySnapshotMs: 0, manifestRegistryMs: 0, diff --git a/src/agents/embedded-agent-runner/run/attempt-spawn-workspace.test-support.ts b/src/agents/embedded-agent-runner/run/attempt-spawn-workspace.test-support.ts index 024d2f5ce68a..dd4e603d37f3 100644 --- a/src/agents/embedded-agent-runner/run/attempt-spawn-workspace.test-support.ts +++ b/src/agents/embedded-agent-runner/run/attempt-spawn-workspace.test-support.ts @@ -1,4 +1,3 @@ -// Shared harness and mocks for embedded attempt spawn-workspace tests. import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -20,6 +19,8 @@ import type { import { formatErrorMessage } from "../../../infra/errors.js"; import { bindStreamLlmRuntime } from "../../../llm/model-runtime-binding.js"; import type { Model } from "../../../llm/types.js"; +// Shared harness and mocks for embedded attempt spawn-workspace tests. +import { makeEmptyPluginMetadataOwners } from "../../../plugins/current-plugin-metadata.test-support.js"; import type { PluginMetadataSnapshot } from "../../../plugins/plugin-metadata-snapshot.js"; import { createLazyPromise } from "../../../shared/lazy-runtime.js"; import { prepareSystemAgentRunAdmission } from "../../admitted-run-context.js"; @@ -287,17 +288,7 @@ const emptyPluginMetadataSnapshot: PluginMetadataSnapshot = { byPluginId: new Map(), normalizePluginId: (pluginId: string) => pluginId, declaredProviderOwners: new Map(), - owners: { - channels: new Map(), - channelConfigs: new Map(), - providers: new Map(), - modelCatalogProviders: new Map(), - cliBackends: new Map(), - setupProviders: new Map(), - commandAliases: new Map(), - contracts: new Map(), - modelIdNormalizationPolicies: new Map(), - }, + owners: makeEmptyPluginMetadataOwners(), metrics: { registrySnapshotMs: 0, manifestRegistryMs: 0, diff --git a/src/agents/live-auth-keys.ts b/src/agents/live-auth-keys.ts index 071587f8804d..a17897216483 100644 --- a/src/agents/live-auth-keys.ts +++ b/src/agents/live-auth-keys.ts @@ -6,7 +6,7 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; import { classifyFailoverSignal } from "./failover/classify.js"; const KEY_SPLIT_RE = /[\s,;]+/g; @@ -132,7 +132,7 @@ export function collectProviderApiKeys( const fallback = config.fallbackVars .map((envVar) => normalizeOptionalString(env[envVar])) .filter(Boolean) as string[]; - const manifestEnvVars = options.providerEnvVars ?? getProviderEnvVars(normalizedProvider); + const manifestEnvVars = options.providerEnvVars ?? getProviderEnvVarsCore(normalizedProvider); const manifestFallback = manifestEnvVars .map((envVar) => normalizeOptionalString(env[envVar])) .filter(Boolean) as string[]; diff --git a/src/agents/model-auth-availability.test.ts b/src/agents/model-auth-availability.test.ts index 39068348bb6b..afa982cd8e24 100644 --- a/src/agents/model-auth-availability.test.ts +++ b/src/agents/model-auth-availability.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { SecretRef } from "../config/types.secrets.js"; import type { ProviderModelRouteCandidate } from "../plugin-sdk/provider-model-types.js"; -import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js"; +import { createPluginMetadataSnapshotFixture } from "../plugins/plugin-metadata.test-support.js"; import { createApiKeyCredential } from "./auth-profiles/credential-fixtures.test-support.js"; import { createModelAuthAvailabilityResolver } from "./model-auth-availability.js"; import { @@ -52,17 +52,7 @@ describe("createModelAuthAvailabilityResolver", () => { }); it("canonicalizes prepared runtime auth through provider aliases", () => { - const metadataSnapshot = { - index: { - plugins: [ - { - pluginId: "external-cloud", - origin: "global", - enabled: true, - enabledByDefault: true, - }, - ], - }, + const metadataSnapshot = createPluginMetadataSnapshotFixture({ plugins: [ { id: "external-cloud", @@ -70,7 +60,7 @@ describe("createModelAuthAvailabilityResolver", () => { providerAuthAliases: { "cloud-alias": "external-cloud" }, }, ], - } as unknown as PluginMetadataSnapshot; + }); const resolver = createModelAuthAvailabilityResolver({ cfg: {}, authStore: authStore(), @@ -103,8 +93,7 @@ describe("createModelAuthAvailabilityResolver", () => { }); it("keeps prepared native-runtime authentication scoped to its exact owner", () => { - const metadataSnapshot = { - index: { plugins: [] }, + const metadataSnapshot = createPluginMetadataSnapshotFixture({ plugins: [ { id: "anthropic", @@ -112,7 +101,7 @@ describe("createModelAuthAvailabilityResolver", () => { providerAuthAliases: { "claude-cli": "anthropic" }, }, ], - } as unknown as PluginMetadataSnapshot; + }); const resolver = createModelAuthAvailabilityResolver({ cfg: {}, authStore: authStore(), diff --git a/src/agents/model-auth-env-vars.ts b/src/agents/model-auth-env-vars.ts index d2e47fdb0527..5b7b71e94d56 100644 --- a/src/agents/model-auth-env-vars.ts +++ b/src/agents/model-auth-env-vars.ts @@ -4,7 +4,7 @@ * helper names to model/auth modules. */ import { - listKnownProviderAuthEnvVarNames, + listKnownProviderAuthEnvVarNamesCore, resolveProviderAuthLookupMaps, } from "../secrets/provider-env-vars.js"; import type { @@ -33,5 +33,5 @@ export function listProviderEnvAuthLookupKeys(params: { /** Lists known provider API-key env var names for redaction and marker matching. */ export function listKnownProviderEnvApiKeyNames(): string[] { - return listKnownProviderAuthEnvVarNames(); + return listKnownProviderAuthEnvVarNamesCore(); } diff --git a/src/agents/model-auth-env.provider-aliases.test.ts b/src/agents/model-auth-env.provider-aliases.test.ts index 94a2379b8be2..10ff40d3cb2c 100644 --- a/src/agents/model-auth-env.provider-aliases.test.ts +++ b/src/agents/model-auth-env.provider-aliases.test.ts @@ -1,41 +1,28 @@ // Verifies env API-key lookup through plugin provider-auth aliases. import { beforeEach, describe, expect, it, vi } from "vitest"; +import { createPluginMetadataSnapshotFixture } from "../plugins/plugin-metadata.test-support.js"; import { resolveEnvApiKey, resolveProviderDirectAuthPlanningEvidence, resolveProviderEnvAuthEvidence, } from "./model-auth-env.js"; -const pluginMetadataMocks = vi.hoisted(() => { - const snapshot = { - index: { - plugins: [ - { - pluginId: "external-cloud", - origin: "global", - enabled: true, - enabledByDefault: true, - }, - ], - }, - plugins: [ - { - id: "external-cloud", - origin: "global", - providerAuthAliases: { - "cloud-alias": "external-cloud", - }, - setup: { - providers: [{ id: "external-cloud", envVars: ["EXTERNAL_CLOUD_API_KEY"] }], - }, +const pluginMetadataMocks = vi.hoisted(() => ({ + getCurrentPluginMetadataSnapshot: vi.fn(), + loadPluginMetadataSnapshot: vi.fn(), +})); + +const snapshot = createPluginMetadataSnapshotFixture({ + plugins: [ + { + id: "external-cloud", + origin: "global", + providerAuthAliases: { "cloud-alias": "external-cloud" }, + setup: { + providers: [{ id: "external-cloud", envVars: ["EXTERNAL_CLOUD_API_KEY"] }], }, - ], - }; - return { - snapshot, - getCurrentPluginMetadataSnapshot: vi.fn(() => snapshot), - loadPluginMetadataSnapshot: vi.fn(() => snapshot), - }; + }, + ], }); const setupRegistryMocks = vi.hoisted(() => ({ @@ -47,7 +34,8 @@ vi.mock("../plugins/current-plugin-metadata-snapshot.js", async (importOriginal) getCurrentPluginMetadataSnapshot: pluginMetadataMocks.getCurrentPluginMetadataSnapshot, })); -vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ +vi.mock("../plugins/plugin-metadata-snapshot.js", async (importOriginal) => ({ + ...(await importOriginal()), loadPluginMetadataSnapshot: pluginMetadataMocks.loadPluginMetadataSnapshot, })); @@ -58,11 +46,9 @@ vi.mock("../plugins/setup-registry.js", () => ({ describe("resolveEnvApiKey provider auth aliases", () => { beforeEach(() => { pluginMetadataMocks.getCurrentPluginMetadataSnapshot.mockReset(); - pluginMetadataMocks.getCurrentPluginMetadataSnapshot.mockReturnValue( - pluginMetadataMocks.snapshot, - ); + pluginMetadataMocks.getCurrentPluginMetadataSnapshot.mockReturnValue(snapshot); pluginMetadataMocks.loadPluginMetadataSnapshot.mockReset(); - pluginMetadataMocks.loadPluginMetadataSnapshot.mockReturnValue(pluginMetadataMocks.snapshot); + pluginMetadataMocks.loadPluginMetadataSnapshot.mockReturnValue(snapshot); setupRegistryMocks.resolvePluginSetupProviderCore.mockReset(); setupRegistryMocks.resolvePluginSetupProviderCore.mockReturnValue(undefined); }); diff --git a/src/agents/model-fallback.probe.test.ts b/src/agents/model-fallback.probe.test.ts index 8378b1464d4d..05da9e012d56 100644 --- a/src/agents/model-fallback.probe.test.ts +++ b/src/agents/model-fallback.probe.test.ts @@ -100,6 +100,7 @@ const emptyPluginMetadataSnapshot = vi.hoisted(() => ({ setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/agents/models-config.providers.implicit.discovery-scope.test.ts b/src/agents/models-config.providers.implicit.discovery-scope.test.ts index f963fcf4bdee..1902699ffc44 100644 --- a/src/agents/models-config.providers.implicit.discovery-scope.test.ts +++ b/src/agents/models-config.providers.implicit.discovery-scope.test.ts @@ -89,6 +89,7 @@ function metadataOwners( setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), ...overrides, }; diff --git a/src/agents/prepared-model-runtime.startup-static.test.ts b/src/agents/prepared-model-runtime.startup-static.test.ts index 38f8cbec6c1c..453d31406e30 100644 --- a/src/agents/prepared-model-runtime.startup-static.test.ts +++ b/src/agents/prepared-model-runtime.startup-static.test.ts @@ -26,6 +26,7 @@ const mocks = vi.hoisted(() => { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, }; diff --git a/src/agents/prepared-model-runtime.test-harness.ts b/src/agents/prepared-model-runtime.test-harness.ts index 7c7345d0558f..72df647e60da 100644 --- a/src/agents/prepared-model-runtime.test-harness.ts +++ b/src/agents/prepared-model-runtime.test-harness.ts @@ -35,6 +35,7 @@ const preparedModelRuntimeMocks = vi.hoisted(() => ({ setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, }, diff --git a/src/agents/provider-attribution.catalog-endpoints.test.ts b/src/agents/provider-attribution.catalog-endpoints.test.ts index 483c12455108..7bed91127966 100644 --- a/src/agents/provider-attribution.catalog-endpoints.test.ts +++ b/src/agents/provider-attribution.catalog-endpoints.test.ts @@ -4,20 +4,21 @@ import { describe, expect, it, vi } from "vitest"; // Simulates a built dist tree: externalized provider metadata comes from the // catalog, while one installed manifest proves first-match precedence. vi.mock("../plugins/plugin-metadata-snapshot-required.js", async (importOriginal) => { - const { buildPluginMetadataProviderFacts } = - await import("../plugins/plugin-metadata-provider-facts.js"); + const { createPluginMetadataSnapshotFixture } = + await import("../plugins/plugin-metadata.test-support.js"); return { ...(await importOriginal()), - getCurrentPluginMetadataSnapshotRequiredRuntime: () => ({ - owners: buildPluginMetadataProviderFacts([ - { - id: "installed-conflict-fixture", - providerEndpoints: [ - { endpointClass: "openai-public", hosts: ["coding.dashscope.aliyuncs.com"] }, - ], - } as never, - ]), - }), + getCurrentPluginMetadataSnapshotRequiredRuntime: () => + createPluginMetadataSnapshotFixture({ + plugins: [ + { + id: "installed-conflict-fixture", + providerEndpoints: [ + { endpointClass: "openai-public", hosts: ["coding.dashscope.aliyuncs.com"] }, + ], + }, + ], + }), }; }); diff --git a/src/agents/provider-attribution.test.ts b/src/agents/provider-attribution.test.ts index 7117e6814403..e6b59559010b 100644 --- a/src/agents/provider-attribution.test.ts +++ b/src/agents/provider-attribution.test.ts @@ -1,5 +1,6 @@ -// Verifies provider attribution headers and endpoint classification policies. import { afterEach, describe, expect, it, vi } from "vitest"; +// Verifies provider attribution headers and endpoint classification policies. +import { makeEmptyPluginMetadataOwners } from "../plugins/current-plugin-metadata.test-support.js"; function expectRecordFields(record: unknown, expected: Record) { // Policy helpers return broad records; assertions pin only the relevant fields. @@ -326,15 +327,7 @@ describe("provider attribution", () => { providerMetadataState.pluginIdScoped = true; providerMetadataState.snapshot = undefined; const providerMetadataOwners = { - channels: new Map(), - channelConfigs: new Map(), - providers: new Map(), - modelCatalogProviders: new Map(), - cliBackends: new Map(), - setupProviders: new Map(), - commandAliases: new Map(), - contracts: new Map(), - modelIdNormalizationPolicies: new Map(), + ...makeEmptyPluginMetadataOwners(), providerEndpoints: [ { endpointClass: "anthropic-public" as const, diff --git a/src/agents/provider-auth-aliases.test.ts b/src/agents/provider-auth-aliases.test.ts index bd8184bb2865..4a806051c546 100644 --- a/src/agents/provider-auth-aliases.test.ts +++ b/src/agents/provider-auth-aliases.test.ts @@ -3,7 +3,9 @@ * Verifies plugin metadata aliases, origin priority, trust, and cache behavior. */ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { buildPluginMetadataProviderFacts } from "../plugins/plugin-metadata-provider-facts.js"; import { buildDeclaredProviderOwnerIndex } from "../plugins/provider-owner-index.js"; +import { resolveProviderAuthLookupMaps } from "../secrets/provider-env-vars.js"; const pluginRegistryMocks = vi.hoisted(() => { const loadManifestRegistry = vi.fn(); @@ -14,17 +16,12 @@ const pluginRegistryMocks = vi.hoisted(() => { resolveInstalledManifestRegistryIndexFingerprint: vi.fn(() => "test-index"), loadPluginMetadataSnapshot: vi.fn((params: unknown) => { const registry = loadManifestRegistry(params) ?? { plugins: [], diagnostics: [] }; - return { - index: { - plugins: registry.plugins.map((plugin: { id: string; origin?: string }) => ({ - pluginId: plugin.id, - origin: plugin.origin ?? "global", - enabled: true, - enabledByDefault: true, - })), - }, - plugins: registry.plugins, - }; + return createPluginMetadataSnapshot({ + plugins: registry.plugins.map( + (plugin: Partial & Pick) => + createPluginManifestRecord({ ...plugin, origin: plugin.origin ?? "global" }), + ), + }); }), }; }); @@ -135,7 +132,10 @@ function createPluginMetadataSnapshot(params: { byPluginId: new Map(params.plugins.map((plugin) => [plugin.id, plugin])), normalizePluginId: (pluginId) => pluginId, declaredProviderOwners: buildDeclaredProviderOwnerIndex(params.plugins), - owners: makeEmptyPluginMetadataOwners(), + owners: { + ...makeEmptyPluginMetadataOwners(), + ...buildPluginMetadataProviderFacts(params.plugins), + }, metrics: { registrySnapshotMs: 0, manifestRegistryMs: 0, @@ -457,12 +457,13 @@ describe("provider auth aliases", () => { expect(resolveProviderIdForAuth("added", { metadataSnapshot })).toBe("added-provider"); }); - it("retains alias ownership through worker cloning, projection and metadata replacement", async () => { + it("retains auth contributions through worker cloning, projection and metadata replacement", async () => { const { metadata, snapshot } = await prepareAliasSnapshot([ createPluginManifestRecord({ id: "first", origin: "bundled", providerAuthAliases: { fixture: "first-provider" }, + setup: { providers: [{ id: "first-provider", envVars: ["FIRST_API_KEY"] }] }, }), createPluginManifestRecord({ id: "second", @@ -476,7 +477,14 @@ describe("provider auth aliases", () => { fixture: "first-provider", second: "second-provider", }); + expect( + resolveProviderAuthLookupMaps({ metadataSnapshot: restored }).envCandidateMap.fixture, + ).toEqual(["FIRST_API_KEY"]); + expect(Object.isFrozen(restored.owners.providerAuthContributions)).toBe(true); const projected = metadata.projectPluginMetadataSnapshot(restored, ["second"]); + expect( + resolveProviderAuthLookupMaps({ metadataSnapshot: projected }).envCandidateMap.fixture, + ).toBeUndefined(); expect(resolveProviderIdForAuth("fixture", { metadataSnapshot: projected })).toBe("fixture"); expect(resolveProviderIdForAuth("second", { metadataSnapshot: projected })).toBe( "second-provider", @@ -487,6 +495,7 @@ describe("provider auth aliases", () => { id: "first", origin: "bundled", providerAuthAliases: { fixture: "replacement-provider" }, + setup: { providers: [{ id: "replacement-provider", envVars: ["REPLACED_API_KEY"] }] }, }), ], diagnostics: [], @@ -494,6 +503,12 @@ describe("provider auth aliases", () => { expect(resolveProviderIdForAuth("fixture", { metadataSnapshot: replacement })).toBe( "replacement-provider", ); + expect( + resolveProviderAuthLookupMaps({ metadataSnapshot: replacement }).envCandidateMap.fixture, + ).toEqual(["REPLACED_API_KEY"]); + expect( + resolveProviderAuthLookupMaps({ metadataSnapshot: restored }).envCandidateMap.fixture, + ).toEqual(["FIRST_API_KEY"]); expect(resolveProviderIdForAuth("fixture", { metadataSnapshot: restored })).toBe( "first-provider", ); diff --git a/src/agents/provider-request-config.test.ts b/src/agents/provider-request-config.test.ts index 2aac206e4715..420ff1f2eb4b 100644 --- a/src/agents/provider-request-config.test.ts +++ b/src/agents/provider-request-config.test.ts @@ -33,6 +33,7 @@ function buildProviderMetadataOwners( setupProviders: empty, commandAliases: empty, contracts: empty, + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), providerEndpoints: endpoints, providerRequests: requests, @@ -50,6 +51,7 @@ describe("provider request config", () => { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), providerEndpoints: [], providerRequests: new Map([["prepared", { family: "prepared-family" }]]), diff --git a/src/agents/sessions/model-registry.sources.test.ts b/src/agents/sessions/model-registry.sources.test.ts index f05a0dfcf926..441899ca03e6 100644 --- a/src/agents/sessions/model-registry.sources.test.ts +++ b/src/agents/sessions/model-registry.sources.test.ts @@ -72,6 +72,7 @@ function createRegistry( setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, }, diff --git a/src/agents/sessions/model-registry.test-support.ts b/src/agents/sessions/model-registry.test-support.ts index 999b54f6ea25..625f83b80180 100644 --- a/src/agents/sessions/model-registry.test-support.ts +++ b/src/agents/sessions/model-registry.test-support.ts @@ -88,6 +88,7 @@ export function pluginOwnerSnapshotEntries( setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, }; diff --git a/src/agents/test-helpers/embedded-agent-runner-e2e-mocks.ts b/src/agents/test-helpers/embedded-agent-runner-e2e-mocks.ts index db64317b3123..8f7e18640d82 100644 --- a/src/agents/test-helpers/embedded-agent-runner-e2e-mocks.ts +++ b/src/agents/test-helpers/embedded-agent-runner-e2e-mocks.ts @@ -64,6 +64,7 @@ export function createEmptyPluginMetadataSnapshot(workspaceDir?: string): Plugin setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/agents/tools/image-generate-tool.test.ts b/src/agents/tools/image-generate-tool.test.ts index 6da4cf1c52b6..981e4c796fb1 100644 --- a/src/agents/tools/image-generate-tool.test.ts +++ b/src/agents/tools/image-generate-tool.test.ts @@ -346,7 +346,7 @@ describe("createImageGenerateTool", () => { ); return { ...actual, - getProviderEnvVars: (providerId: string) => { + getProviderEnvVarsCore: (providerId: string) => { if (providerId === "google") { return ["GEMINI_API_KEY", "GOOGLE_API_KEY"]; } diff --git a/src/agents/tools/media-generate-tool-actions-shared.ts b/src/agents/tools/media-generate-tool-actions-shared.ts index 2771cba0083c..6a6b45531a98 100644 --- a/src/agents/tools/media-generate-tool-actions-shared.ts +++ b/src/agents/tools/media-generate-tool-actions-shared.ts @@ -9,7 +9,7 @@ import { type MediaGenerationCatalogKind, } from "../../../packages/media-generation-core/src/catalog.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { getProviderEnvVars } from "../../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../../secrets/provider-env-vars.js"; import type { AuthProfileStore } from "../auth-profiles/types.js"; import { isCapabilityProviderConfigured } from "./media-tool-shared.js"; @@ -99,7 +99,7 @@ export function createMediaGenerateProviderListActionResult< agentDir: params.agentDir, authStore: params.authStore, }), - authEnvVars: getProviderEnvVars(provider.id), + authEnvVars: getProviderEnvVarsCore(provider.id), capabilities: provider.capabilities, // Catalog entries are generated for model browser/search without invoking provider code. catalog: synthesizeMediaGenerationCatalogEntries({ @@ -116,7 +116,7 @@ export function createMediaGenerateProviderListActionResult< if (!provider) { return []; } - const authHints = getProviderEnvVars(provider.id); + const authHints = getProviderEnvVarsCore(provider.id); const capabilities = params.summarizeCapabilities(provider); const modelLine = details.models.length > 0 ? details.models.join(", ") : "unknown"; const authHint = diff --git a/src/cli/capability-cli.test.ts b/src/cli/capability-cli.test.ts index 7fe7f1a2a9f0..b41dc77138b1 100644 --- a/src/cli/capability-cli.test.ts +++ b/src/cli/capability-cli.test.ts @@ -206,7 +206,7 @@ const mocks = vi.hoisted(() => ({ : {}), }), ), - getProviderEnvVars: vi.fn((providerId: string) => [ + getProviderEnvVarsCore: vi.fn((providerId: string) => [ `${providerId.toUpperCase().replaceAll("-", "_")}_API_KEY`, ]), embedBatch: vi.fn(async (inputs: unknown[], options?: { inputType?: string }) => @@ -292,7 +292,7 @@ vi.mock("../runtime.js", async (importOriginal) => ({ })); vi.mock("../secrets/provider-env-vars.js", () => ({ - getProviderEnvVars: mocks.getProviderEnvVars, + getProviderEnvVarsCore: mocks.getProviderEnvVarsCore, resolveProviderAuthLookupMaps: () => ({ aliasMap: {}, envCandidateMap: {}, @@ -698,7 +698,7 @@ describe("capability cli", () => { mocks.getTtsProvider.mockReset().mockReturnValue("openai"); mocks.listSpeechProviders.mockReset().mockReturnValue([]); mocks.resolveExplicitTtsOverrides.mockClear(); - mocks.getProviderEnvVars + mocks.getProviderEnvVarsCore .mockReset() .mockImplementation((providerId: string) => [ `${providerId.toUpperCase().replaceAll("-", "_")}_API_KEY`, @@ -1032,7 +1032,7 @@ describe("capability cli", () => { expect(providers).toContainEqual( expect.objectContaining({ provider: "openai", configured: true }), ); - expect(mocks.getProviderEnvVars).toHaveBeenCalledWith("openai"); + expect(mocks.getProviderEnvVarsCore).toHaveBeenCalledWith("openai"); }); it("scopes provider state and model selection to an explicit agent", async () => { @@ -4351,7 +4351,7 @@ describe("capability cli", () => { it("marks env-backed image providers as configured", async () => { vi.stubEnv("FAL_KEY", "fal-test-key"); - mocks.getProviderEnvVars.mockReturnValueOnce(["FAL_KEY"]); + mocks.getProviderEnvVarsCore.mockReturnValueOnce(["FAL_KEY"]); mocks.listRuntimeImageGenerationProviders.mockReturnValueOnce([ { id: "fal", label: "fal", defaultModel: "fal-ai/flux", models: [] }, ] as never); @@ -4366,7 +4366,7 @@ describe("capability cli", () => { it("marks env-backed video generation and description providers as configured", async () => { vi.stubEnv("RUNWAYML_API_SECRET", "runway-test-key"); vi.stubEnv("GEMINI_API_KEY", "gemini-test-key"); - mocks.getProviderEnvVars.mockImplementation((providerId: string) => + mocks.getProviderEnvVarsCore.mockImplementation((providerId: string) => providerId === "runway" ? ["RUNWAYML_API_SECRET"] : ["GEMINI_API_KEY"], ); mocks.listRuntimeVideoGenerationProviders.mockReturnValueOnce([ @@ -4395,7 +4395,7 @@ describe("capability cli", () => { it("marks env-backed TTS providers as configured", async () => { vi.stubEnv("XAI_API_KEY", "xai-test-key"); - mocks.getProviderEnvVars.mockReturnValueOnce(["XAI_API_KEY"]); + mocks.getProviderEnvVarsCore.mockReturnValueOnce(["XAI_API_KEY"]); mocks.listSpeechProviders.mockReturnValueOnce([ { id: "xai", label: "xAI", models: [], voices: [] }, ] as never); diff --git a/src/cli/capability-cli/audio.ts b/src/cli/capability-cli/audio.ts index dd65666b9f51..8feaace147ab 100644 --- a/src/cli/capability-cli/audio.ts +++ b/src/cli/capability-cli/audio.ts @@ -5,7 +5,7 @@ import { inspectLocalAudioSelection } from "../../media-understanding/local-audi import { buildMediaUnderstandingRegistry } from "../../media-understanding/provider-registry.js"; import { transcribeAudioFile } from "../../media-understanding/runtime.js"; import { defaultRuntime } from "../../runtime.js"; -import { getProviderEnvVars } from "../../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../../secrets/provider-env-vars.js"; import { runCommandWithRuntime } from "../cli-utils.js"; import { getModelsCommandSecretTargetIds } from "../command-secret-targets.js"; import { prepareLocalCapabilityAccountSecrets } from "./local-account-secrets.js"; @@ -102,7 +102,7 @@ export function registerAudioCapabilityCommands(capability: Command): void { cfg, providerId: provider.id, agentId, - envVars: getProviderEnvVars(provider.id, { + envVars: getProviderEnvVarsCore(provider.id, { config: cfg, includeUntrustedWorkspacePlugins: false, }), diff --git a/src/cli/capability-cli/model.ts b/src/cli/capability-cli/model.ts index 39341fd77f33..15eb93916e93 100644 --- a/src/cli/capability-cli/model.ts +++ b/src/cli/capability-cli/model.ts @@ -36,7 +36,7 @@ import { callGateway, randomIdempotencyKey } from "../../gateway/call.js"; import { ADMIN_SCOPE } from "../../gateway/operator-scopes.js"; import { convertHeicToJpeg } from "../../media/media-services.js"; import { defaultRuntime } from "../../runtime.js"; -import { getProviderEnvVars } from "../../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../../secrets/provider-env-vars.js"; import { AsyncWorkScope, captureAsyncWorkTracker } from "../../shared/async-work-scope.js"; import { createDeferredCore } from "../../shared/deferred.js"; import { runCommandWithRuntime } from "../cli-utils.js"; @@ -393,7 +393,7 @@ async function buildModelProviders(rawAgentId?: string) { cfg, providerId: entry.provider, agentId, - envVars: getProviderEnvVars(entry.provider), + envVars: getProviderEnvVarsCore(entry.provider), }), selected: selectedProvider === entry.provider, }; diff --git a/src/cli/capability-cli/shared.ts b/src/cli/capability-cli/shared.ts index de24a32e0f74..ff4e4ce9fcb7 100644 --- a/src/cli/capability-cli/shared.ts +++ b/src/cli/capability-cli/shared.ts @@ -19,7 +19,7 @@ import { } from "../../config/config.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { defaultRuntime } from "../../runtime.js"; -import { getProviderEnvVars } from "../../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../../secrets/provider-env-vars.js"; import { runCommandWithRuntime } from "../cli-utils.js"; import { resolveCommandConfigWithSecrets } from "../command-config-resolution.js"; import { inheritOptionFromParent } from "../command-options.js"; @@ -133,7 +133,7 @@ export function providerHasGenericConfig(params: { const ttsProviders = (params.cfg.tts?.providers ?? {}) as Record; const envVars = params.envVars ?? - getProviderEnvVars(params.providerId, { + getProviderEnvVarsCore(params.providerId, { config: params.cfg, includeUntrustedWorkspacePlugins: false, }); diff --git a/src/commands/agent-exec.ts b/src/commands/agent-exec.ts index 32aa77c5aace..7b7339d438b7 100644 --- a/src/commands/agent-exec.ts +++ b/src/commands/agent-exec.ts @@ -398,7 +398,7 @@ export async function agentExecCommand( const [ { withAuthProfileStoreAgentDir, withEnvOnlyAuthProfileStore }, { withHostExecInheritedEnvOmitted }, - { listKnownProviderAuthEnvVarNames }, + { listKnownProviderAuthEnvVarNamesCore }, runAgent, ] = await Promise.all([ import("../agents/auth-profiles.js"), @@ -481,7 +481,7 @@ export async function agentExecCommand( } return await toolBudget.run(() => withHostExecInheritedEnvOmitted( - listKnownProviderAuthEnvVarNames({ env: process.env }), + listKnownProviderAuthEnvVarNamesCore({ env: process.env }), runWithAuthScope, ), ); diff --git a/src/commands/doctor-config-preflight.test-support.ts b/src/commands/doctor-config-preflight.test-support.ts index 96a9d7ca9fdc..93da10ff3271 100644 --- a/src/commands/doctor-config-preflight.test-support.ts +++ b/src/commands/doctor-config-preflight.test-support.ts @@ -4,7 +4,7 @@ import { vi } from "vitest"; import { withTempHome } from "../config/test-helpers.js"; import { withStateDatabaseCoordinatorRuntimeDirectory } from "../infra/state-database-coordinator.js"; import * as temporaryState from "../infra/tmp-openclaw-dir.js"; -import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../secrets/provider-env-vars.js"; import { withEnvAsync } from "../test-utils/env.js"; /** Keep real preflight fixtures from provisioning plugins for the developer's credentials. */ @@ -18,7 +18,7 @@ export async function withDoctorConfigPreflightHome( .spyOn(temporaryState, "resolvePreferredOpenClawTmpDir") .mockReturnValue(control); const providerEnv = Object.fromEntries( - listKnownProviderAuthEnvVarNames({ config: {}, env: process.env }).map((key) => [ + listKnownProviderAuthEnvVarNamesCore({ config: {}, env: process.env }).map((key) => [ key, undefined, ]), diff --git a/src/commands/doctor-memory-search.ts b/src/commands/doctor-memory-search.ts index c154733be728..138c19daac04 100644 --- a/src/commands/doctor-memory-search.ts +++ b/src/commands/doctor-memory-search.ts @@ -57,7 +57,7 @@ import { loadProviderPolicyArtifacts, } from "../plugins/provider-public-artifacts.js"; import { defaultSlotIdForKey } from "../plugins/slots.js"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; import { resolveUserPath } from "../utils.js"; import { formatLocalRuntimeDoctorNote, @@ -845,7 +845,7 @@ function resolvePrimaryMemoryProviderEnvVar(provider: string): string { return "OPENAI_API_KEY"; } const authProviderId = MEMORY_EMBEDDING_PROVIDER_AUTH_IDS.get(provider); - const envVar = authProviderId ? getProviderEnvVars(authProviderId)[0] : undefined; + const envVar = authProviderId ? getProviderEnvVarsCore(authProviderId)[0] : undefined; return envVar ?? `${provider.toUpperCase()}_API_KEY`; } diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts index f69400e84339..2f3c59ef5fde 100644 --- a/src/commands/onboard-auth.test.ts +++ b/src/commands/onboard-auth.test.ts @@ -45,7 +45,7 @@ vi.mock("../agents/provider-auth-aliases.js", () => ({ })); vi.mock("../secrets/provider-env-vars.js", () => ({ - getProviderEnvVars: vi.fn((provider: string) => providerEnvVarsById[provider] ?? []), + getProviderEnvVarsCore: vi.fn((provider: string) => providerEnvVarsById[provider] ?? []), resolveProviderAuthLookupMaps: () => ({ aliasMap: {}, envCandidateMap: {}, diff --git a/src/commands/onboard-inference.test.ts b/src/commands/onboard-inference.test.ts index e8276530ff90..439509f0df2a 100644 --- a/src/commands/onboard-inference.test.ts +++ b/src/commands/onboard-inference.test.ts @@ -40,6 +40,7 @@ const emptyPluginMetadataSnapshot = vi.hoisted(() => ({ setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/config/io.context.plugin-metadata.test.ts b/src/config/io.context.plugin-metadata.test.ts index 98af54ab3de4..e58c31fc253f 100644 --- a/src/config/io.context.plugin-metadata.test.ts +++ b/src/config/io.context.plugin-metadata.test.ts @@ -6,6 +6,7 @@ import { import type { InstalledPluginIndex } from "../plugins/installed-plugin-index-types.js"; import type { PluginManifestRecord } from "../plugins/manifest-registry.js"; import { clearPluginMetadataLifecycleCaches } from "../plugins/plugin-metadata-lifecycle.js"; +import { buildPluginMetadataProviderFacts } from "../plugins/plugin-metadata-provider-facts.js"; import { restorePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { buildDeclaredProviderOwnerIndex } from "../plugins/provider-owner-index.js"; @@ -100,6 +101,8 @@ function workspaceSnapshot( setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: + buildPluginMetadataProviderFacts(plugins).providerAuthContributions, modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/config/plugin-auto-enable.test-helpers.ts b/src/config/plugin-auto-enable.test-helpers.ts index 1396afc56657..5a57d6a86f5b 100644 --- a/src/config/plugin-auto-enable.test-helpers.ts +++ b/src/config/plugin-auto-enable.test-helpers.ts @@ -1,8 +1,9 @@ -// Provides fixtures for plugin auto-enable config tests. import path from "node:path"; import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js"; import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; import { clearPluginMetadataLifecycleCaches } from "../plugins/plugin-metadata-lifecycle.js"; +// Provides fixtures for plugin auto-enable config tests. +import { buildPluginMetadataProviderFacts } from "../plugins/plugin-metadata-provider-facts.js"; import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import type { PluginOrigin } from "../plugins/plugin-origin.types.js"; import { buildDeclaredProviderOwnerIndex } from "../plugins/provider-owner-index.js"; @@ -117,6 +118,8 @@ export function createPluginMetadataSnapshot(params: { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: buildPluginMetadataProviderFacts(params.manifestRegistry.plugins) + .providerAuthContributions, modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/config/shell-env-expected-keys.ts b/src/config/shell-env-expected-keys.ts index 90200222a50c..372207144e3b 100644 --- a/src/config/shell-env-expected-keys.ts +++ b/src/config/shell-env-expected-keys.ts @@ -1,7 +1,7 @@ // Lists expected shell environment keys for config validation. import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { listKnownChannelEnvVarNames } from "../secrets/channel-env-vars.js"; -import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../secrets/provider-env-vars.js"; import type { OpenClawConfig } from "./types.openclaw.js"; const CORE_SHELL_ENV_EXPECTED_KEYS = ["OPENCLAW_GATEWAY_TOKEN", "OPENCLAW_GATEWAY_PASSWORD"]; @@ -12,7 +12,7 @@ export function resolveShellEnvExpectedKeys( config?: OpenClawConfig, ): string[] { return uniqueStrings([ - ...listKnownProviderAuthEnvVarNames({ config, env }), + ...listKnownProviderAuthEnvVarNamesCore({ config, env }), ...listKnownChannelEnvVarNames({ config, env }), ...CORE_SHELL_ENV_EXPECTED_KEYS, ]); diff --git a/src/gateway/gateway-cron-scheduler.live.test.ts b/src/gateway/gateway-cron-scheduler.live.test.ts index 919420991050..5476c3318824 100644 --- a/src/gateway/gateway-cron-scheduler.live.test.ts +++ b/src/gateway/gateway-cron-scheduler.live.test.ts @@ -11,7 +11,7 @@ import { runQaGatewayFixture } from "../../test/helpers/qa-gateway-cleanup.js"; import { isLiveTestEnabled, logLiveProgress } from "../agents/live-test-helpers.js"; import type { CronRunLogEntry } from "../cron/run-log-types.js"; import type { CronJob } from "../cron/types.js"; -import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../secrets/provider-env-vars.js"; const describeLive = isLiveTestEnabled() ? describe : describe.skip; @@ -52,7 +52,9 @@ describeLive("cron scheduling through an isolated Gateway", () => { const instance = await createOpenClawTestInstance({ name: "cron-scheduler", env: { - ...Object.fromEntries(listKnownProviderAuthEnvVarNames().map((name) => [name, undefined])), + ...Object.fromEntries( + listKnownProviderAuthEnvVarNamesCore().map((name) => [name, undefined]), + ), OPENCLAW_SKIP_CRON: "0", OPENCLAW_TEST_MINIMAL_GATEWAY: "0", OPENCLAW_AGENT_RUNTIME: undefined, diff --git a/src/gateway/gateway-cron-tools-allow.live.test.ts b/src/gateway/gateway-cron-tools-allow.live.test.ts index 91222a6422f4..8bffa26ff193 100644 --- a/src/gateway/gateway-cron-tools-allow.live.test.ts +++ b/src/gateway/gateway-cron-tools-allow.live.test.ts @@ -16,7 +16,7 @@ import type { OpenClawConfig } from "../config/config.js"; import type { CronRunLogEntry } from "../cron/run-log-types.js"; import type { CronJob } from "../cron/types.js"; import type { Message } from "../llm/types.js"; -import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../secrets/provider-env-vars.js"; const describeLive = isLiveTestEnabled() && process.env.OPENAI_API_KEY?.trim() ? describe : describe.skip; @@ -41,7 +41,7 @@ describeLive("cron tool allowlists through live harnesses", () => { name: `cron-tools-${runtime}`, env: { ...Object.fromEntries( - listKnownProviderAuthEnvVarNames().map((name) => [name, undefined]), + listKnownProviderAuthEnvVarNamesCore().map((name) => [name, undefined]), ), OPENAI_API_KEY: process.env.OPENAI_API_KEY, OPENAI_BASE_URL: undefined, diff --git a/src/gateway/server-methods/models-list-result.openai-routes.test-support.ts b/src/gateway/server-methods/models-list-result.openai-routes.test-support.ts index 53adc1c95a69..4e65cda0d2f0 100644 --- a/src/gateway/server-methods/models-list-result.openai-routes.test-support.ts +++ b/src/gateway/server-methods/models-list-result.openai-routes.test-support.ts @@ -5,6 +5,7 @@ import type { createOpenAIModelRoutesResolver } from "../../agents/openai-model- import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { loadManifestMetadataSnapshot } from "../../plugins/manifest-contract-eligibility.js"; import type { PluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.types.js"; +import { createPluginMetadataSnapshotFixture } from "../../plugins/plugin-metadata.test-support.js"; import type { PluginRegistry } from "../../plugins/registry-types.js"; import { type PreparedGatewayModelCatalogSnapshot, @@ -134,13 +135,11 @@ export async function listModels(params: ListModelsParams) { cfg: config, agentId, snapshot: { entries: params.catalog, routeVariants: params.catalog }, - metadataSnapshot: { - index: { plugins: [] }, - manifestRegistry: { plugins: [] }, + metadataSnapshot: createPluginMetadataSnapshotFixture({ plugins: [ { id: "test-provider", modelCatalog: { discovery: params.discoveryModes } }, ], - } as never, + }), preparedAuthStore: { version: 1, profiles: {} }, }), } diff --git a/src/gateway/server-methods/models.test.ts b/src/gateway/server-methods/models.test.ts index 63269b689d2a..c440940b0f62 100644 --- a/src/gateway/server-methods/models.test.ts +++ b/src/gateway/server-methods/models.test.ts @@ -39,8 +39,12 @@ const OPENCLAW_DEVICE_PLACEMENT: NonNullable { + const { buildPluginMetadataProviderFacts } = + await import("../../plugins/plugin-metadata-provider-facts.js"); + const { makeEmptyPluginMetadataOwners } = + await import("../../plugins/current-plugin-metadata.test-support.js"); const { buildDeclaredProviderOwnerIndex } = await import("../../plugins/provider-owner-index.js"); - const plugins = [ + const plugins: PluginMetadataSnapshot["manifestRegistry"]["plugins"] = [ { id: "anthropic", channels: [], @@ -130,20 +134,16 @@ const modelPluginMetadataSnapshot = await vi.hoisted(async () => { normalizePluginId: (pluginId: string) => pluginId, declaredProviderOwners: buildDeclaredProviderOwnerIndex(plugins), owners: { - channels: new Map(), - channelConfigs: new Map(), + ...makeEmptyPluginMetadataOwners(), + providerAuthContributions: + buildPluginMetadataProviderFacts(plugins).providerAuthContributions, providers: new Map([ ["anthropic", ["anthropic"]], ["byteplus", ["byteplus"]], ["byteplus-plan", ["byteplus"]], ["github-copilot", ["github-copilot"]], ]), - modelCatalogProviders: new Map(), cliBackends: new Map([["claude-cli", ["anthropic"]]]), - setupProviders: new Map(), - commandAliases: new Map(), - contracts: new Map(), - modelIdNormalizationPolicies: new Map(), }, metrics: { registrySnapshotMs: 0, diff --git a/src/gateway/server-plugins.test.ts b/src/gateway/server-plugins.test.ts index 3a2741945d86..388447c1539d 100644 --- a/src/gateway/server-plugins.test.ts +++ b/src/gateway/server-plugins.test.ts @@ -1,5 +1,3 @@ -// Gateway plugin tests cover plugin loading, auto-enable, runtime registry setup, -// request-scope injection, diagnostics, and handler dispatch integration. import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -8,6 +6,9 @@ import { isRecord } from "@openclaw/normalization-core/record-coerce"; import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import { createTerminalTool } from "../agents/tools/terminal-tool.js"; +// Gateway plugin tests cover plugin loading, auto-enable, runtime registry setup, +// request-scope injection, diagnostics, and handler dispatch integration. +import { makeEmptyPluginMetadataOwners } from "../plugins/current-plugin-metadata.test-support.js"; import { getGlobalPluginRegistry, initializeGlobalHookRunner, @@ -219,17 +220,7 @@ function createLookUpTableForTest(params: { byPluginId: new Map(), normalizePluginId: (pluginId) => pluginId, declaredProviderOwners: buildDeclaredProviderOwnerIndex(params.manifestRegistry?.plugins ?? []), - owners: { - channels: new Map(), - channelConfigs: new Map(), - providers: new Map(), - modelCatalogProviders: new Map(), - cliBackends: new Map(), - setupProviders: new Map(), - commandAliases: new Map(), - contracts: new Map(), - modelIdNormalizationPolicies: new Map(), - }, + owners: makeEmptyPluginMetadataOwners(), startup: { channelPluginIds: [], pluginIds: params.pluginIds ?? [], diff --git a/src/gateway/server-startup-config.recovery.test.ts b/src/gateway/server-startup-config.recovery.test.ts index 28e6aee0652e..78a2f40eb9b0 100644 --- a/src/gateway/server-startup-config.recovery.test.ts +++ b/src/gateway/server-startup-config.recovery.test.ts @@ -28,6 +28,7 @@ const pluginMetadataSnapshot = vi.hoisted((): PluginMetadataSnapshot => { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }; const zeroMetrics = { diff --git a/src/gateway/server-startup-plugins.test.ts b/src/gateway/server-startup-plugins.test.ts index a61496243f91..fc9cc7e03160 100644 --- a/src/gateway/server-startup-plugins.test.ts +++ b/src/gateway/server-startup-plugins.test.ts @@ -88,6 +88,7 @@ const pluginMetadataSnapshot = vi.hoisted((): PluginMetadataSnapshot => { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/gateway/server.chat.gateway-server-chat-b.test.ts b/src/gateway/server.chat.gateway-server-chat-b.test.ts index 76fbaba23938..6d815bc7ae62 100644 --- a/src/gateway/server.chat.gateway-server-chat-b.test.ts +++ b/src/gateway/server.chat.gateway-server-chat-b.test.ts @@ -1,5 +1,6 @@ // Gateway chat integration tests cover dashboard chat requests, transcript // history limits, model overrides, inbound dispatch, and streaming event fanout. + import { randomUUID } from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; @@ -53,6 +54,7 @@ import { readPersistedMediaFacts } from "../media/media-facts.js"; import { resolveMediaReferenceLocalPath } from "../media/media-reference.js"; import { getMediaDir } from "../media/store.js"; import { withPluginMetadataSnapshotScope } from "../plugins/current-plugin-metadata-snapshot.js"; +import { makeEmptyPluginMetadataOwners } from "../plugins/current-plugin-metadata.test-support.js"; import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js"; import { rebasePluginMetadataSnapshotManifestRegistry } from "../plugins/plugin-metadata-snapshot.js"; import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js"; @@ -205,17 +207,7 @@ function createGatewayPluginMetadataSnapshot(config: OpenClawConfig): PluginMeta byPluginId: new Map(), normalizePluginId: (pluginId) => pluginId, declaredProviderOwners: new Map(), - owners: { - channels: new Map(), - channelConfigs: new Map(), - providers: new Map(), - modelCatalogProviders: new Map(), - cliBackends: new Map(), - setupProviders: new Map(), - commandAliases: new Map(), - contracts: new Map(), - modelIdNormalizationPolicies: new Map(), - }, + owners: makeEmptyPluginMetadataOwners(), metrics: { registrySnapshotMs: 0, manifestRegistryMs: 0, diff --git a/src/image-generation/runtime.ts b/src/image-generation/runtime.ts index 8b2f48015152..5df9e269eb21 100644 --- a/src/image-generation/runtime.ts +++ b/src/image-generation/runtime.ts @@ -20,7 +20,7 @@ import { buildCapabilityProviderIndex, normalizeCapabilityProviderId, } from "../plugins/provider-registry-shared.js"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; import { resolveImageGenerationMaxInputImages } from "./capabilities.js"; import { resolveImageGenerationOverrides } from "./normalization.js"; import type { GenerateImageParams, GenerateImageRuntimeResult } from "./runtime-types.js"; @@ -34,7 +34,7 @@ const log = createSubsystemLogger("image-generation"); type ImageGenerationRuntimeDeps = { getProvider?: typeof getImageGenerationProvider; listProviders?: typeof listImageGenerationProviders; - getProviderEnvVars?: typeof getProviderEnvVars; + getProviderEnvVars?: typeof getProviderEnvVarsCore; log?: Pick; }; diff --git a/src/infra/dotenv-workspace-blocklist.test.ts b/src/infra/dotenv-workspace-blocklist.test.ts index e6d4c43a6378..5237abc260e3 100644 --- a/src/infra/dotenv-workspace-blocklist.test.ts +++ b/src/infra/dotenv-workspace-blocklist.test.ts @@ -10,9 +10,10 @@ import { import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js"; import type { PluginManifestRecord } from "../plugins/manifest-registry.js"; import { clearPluginMetadataLifecycleCaches } from "../plugins/plugin-metadata-lifecycle.js"; +import { buildPluginMetadataProviderFacts } from "../plugins/plugin-metadata-provider-facts.js"; import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js"; import { buildDeclaredProviderOwnerIndex } from "../plugins/provider-owner-index.js"; -import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../secrets/provider-env-vars.js"; import { captureFullEnv, deleteTestEnvValue, setTestEnvValue } from "../test-utils/env.js"; import { loadDotEnv, loadWorkspaceDotEnvFile } from "./dotenv.js"; @@ -79,7 +80,10 @@ function createManifestBackedProviderSnapshot( byPluginId: new Map([[plugin.id, plugin]]), normalizePluginId: (pluginId: string) => pluginId, declaredProviderOwners: buildDeclaredProviderOwnerIndex([plugin]), - owners: makeEmptyPluginMetadataOwners(), + owners: { + ...makeEmptyPluginMetadataOwners(), + ...buildPluginMetadataProviderFacts([plugin]), + }, metrics: { registrySnapshotMs: 0, manifestRegistryMs: 0, @@ -140,7 +144,7 @@ describe("workspace .env blocklist completeness", () => { it("keeps registered provider auth vars from trusted global dotenv", async () => { await withIsolatedEnvAndCwd(async () => { await withDotEnvFixture(async ({ cwdDir, stateDir }) => { - const providerAuthKeys = listKnownProviderAuthEnvVarNames().toSorted(); + const providerAuthKeys = listKnownProviderAuthEnvVarNamesCore().toSorted(); await writeEnvFile( path.join(cwdDir, ".env"), `${providerAuthKeys.map((key) => `${key}=workspace-${key}`).join("\n")}\n`, diff --git a/src/infra/dotenv.ts b/src/infra/dotenv.ts index 1e37ea1160c6..a77d3d12a23f 100644 --- a/src/infra/dotenv.ts +++ b/src/infra/dotenv.ts @@ -1,7 +1,7 @@ // Loads dotenv files while blocking unsafe workspace env keys. import path from "node:path"; import { - listKnownProviderAuthEnvVarNames, + listKnownProviderAuthEnvVarNamesCore, listKnownProviderAuthEnvVarNamesAsync, } from "../secrets/provider-env-vars.js"; import { @@ -293,7 +293,7 @@ export function loadWorkspaceDotEnvFile( let providerAuthBlockedKeys: ReadonlySet | undefined; const getProviderAuthBlockedKeys = () => { providerAuthBlockedKeys ??= buildProviderAuthWorkspaceDotEnvBlocklist( - listKnownProviderAuthEnvVarNames({ env, includeUntrustedWorkspacePlugins: false }), + listKnownProviderAuthEnvVarNamesCore({ env, includeUntrustedWorkspacePlugins: false }), ); return providerAuthBlockedKeys; }; diff --git a/src/infra/heartbeat-runner.live.test.ts b/src/infra/heartbeat-runner.live.test.ts index 5ee88a0c967e..5a547dba5697 100644 --- a/src/infra/heartbeat-runner.live.test.ts +++ b/src/infra/heartbeat-runner.live.test.ts @@ -16,7 +16,7 @@ import { import { readSessionMessagesAsync } from "../gateway/session-transcript-readers.js"; import { loadGatewaySessionEntryReadOnly } from "../gateway/session-utils.js"; import { extractPayloadText } from "../gateway/test-helpers.agent-results.js"; -import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../secrets/provider-env-vars.js"; const enabled = isLiveTestEnabled() && process.env.OPENCLAW_LIVE_SESSION_EVENT_WAKE === "1"; const describeLive = enabled ? describe : describe.skip; @@ -46,7 +46,9 @@ describeLive("session event wake through a live Gateway", () => { const instance = await createOpenClawTestInstance({ name: "live-session-event-wake", env: { - ...Object.fromEntries(listKnownProviderAuthEnvVarNames().map((name) => [name, undefined])), + ...Object.fromEntries( + listKnownProviderAuthEnvVarNamesCore().map((name) => [name, undefined]), + ), OPENAI_API_KEY: process.env.OPENAI_API_KEY, OPENCLAW_AGENT_RUNTIME: "openclaw", OPENCLAW_ALLOW_SLOW_REPLY_TESTS: "1", diff --git a/src/infra/provider-usage.auth.plugin.test.ts b/src/infra/provider-usage.auth.plugin.test.ts index a20ff60c3ea2..077fdc7d3bd3 100644 --- a/src/infra/provider-usage.auth.plugin.test.ts +++ b/src/infra/provider-usage.auth.plugin.test.ts @@ -62,12 +62,12 @@ vi.mock("../plugins/manifest-contract-eligibility.js", () => ({ })); vi.mock("../secrets/provider-env-vars.js", () => ({ - listKnownProviderAuthEnvVarNames: () => [ + listKnownProviderAuthEnvVarNamesCore: () => [ "ANTHROPIC_API_KEY", "MINIMAX_CODE_PLAN_KEY", "OPENAI_API_KEY", ], - resolveProviderAuthEnvVarCandidates: () => ({ + resolveProviderAuthEnvVarCandidatesCore: () => ({ anthropic: ["ANTHROPIC_API_KEY"], minimax: ["MINIMAX_CODE_PLAN_KEY"], openai: ["OPENAI_API_KEY"], diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index 11e971dc21b1..cbc3129f4d51 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -21,7 +21,7 @@ import { } from "../plugins/manifest-owner-policy.js"; import type { PluginManifestRecord } from "../plugins/manifest-registry.js"; import { resolveProviderUsageAuthWithPlugin } from "../plugins/provider-runtime.js"; -import { resolveProviderAuthEnvVarCandidates } from "../secrets/provider-env-vars.js"; +import { resolveProviderAuthEnvVarCandidatesCore } from "../secrets/provider-env-vars.js"; import { normalizeSecretInput } from "../utils/normalize-secret-input.js"; import { isOAuthOnlyUsageProvider } from "./provider-usage.shared.js"; import type { UsageProviderId } from "./provider-usage.types.js"; @@ -90,7 +90,7 @@ function hasProviderAuthEnvCredentialSource(params: { state: UsageAuthState; providerIds: string[]; }): boolean { - const candidates = resolveProviderAuthEnvVarCandidates({ + const candidates = resolveProviderAuthEnvVarCandidatesCore({ config: params.state.cfg, env: { ...(process.env.VITEST ? process.env : {}), diff --git a/src/media-generation/runtime-shared.ts b/src/media-generation/runtime-shared.ts index c83ec97ee9d3..44cd474890eb 100644 --- a/src/media-generation/runtime-shared.ts +++ b/src/media-generation/runtime-shared.ts @@ -14,7 +14,7 @@ import type { AgentModelConfig } from "../config/types.agents-shared.js"; import type { OpenClawConfig } from "../config/types.js"; import { formatErrorMessage, toErrorObject } from "../infra/errors.js"; import { isProviderApiKeyConfigured } from "../plugin-sdk/provider-auth.js"; -import { getProviderEnvVars as getDefaultProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; // Shared media-generation runtime helpers for provider fallback, request // timeout normalization, model selection, and capability value normalization. @@ -693,9 +693,9 @@ export function buildNoCapabilityModelConfiguredMessage(params: { modelConfigKey: string; providers: Array<{ id: string; defaultModel?: string | null }>; fallbackSampleRef?: string; - getProviderEnvVars?: typeof getDefaultProviderEnvVars; + getProviderEnvVars?: typeof getProviderEnvVarsCore; }): string { - const getProviderEnvVars = params.getProviderEnvVars ?? getDefaultProviderEnvVars; + const getProviderEnvVars = params.getProviderEnvVars ?? getProviderEnvVarsCore; const sampleModel = params.providers.find( (provider) => normalizeOptionalString(provider.id) && normalizeOptionalString(provider.defaultModel), diff --git a/src/music-generation/runtime.ts b/src/music-generation/runtime.ts index 2403122b12af..7a9fee1a538b 100644 --- a/src/music-generation/runtime.ts +++ b/src/music-generation/runtime.ts @@ -19,7 +19,7 @@ import { buildCapabilityProviderIndex, normalizeCapabilityProviderId, } from "../plugins/provider-registry-shared.js"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; import { resolveMusicGenerationOverrides } from "./normalization.js"; import type { GenerateMusicParams, GenerateMusicRuntimeResult } from "./runtime-types.js"; import type { MusicGenerationResult } from "./types.js"; @@ -37,7 +37,7 @@ const log = createSubsystemLogger("music-generation"); type MusicGenerationRuntimeDeps = { getProvider?: typeof getMusicGenerationProvider; listProviders?: typeof listMusicGenerationProviders; - getProviderEnvVars?: typeof getProviderEnvVars; + getProviderEnvVars?: typeof getProviderEnvVarsCore; log?: Pick; }; diff --git a/src/plugin-sdk/agent-runtime-model-catalog-contract.test.ts b/src/plugin-sdk/agent-runtime-model-catalog-contract.test.ts index b7f619cc76cb..ae5e9ddd5404 100644 --- a/src/plugin-sdk/agent-runtime-model-catalog-contract.test.ts +++ b/src/plugin-sdk/agent-runtime-model-catalog-contract.test.ts @@ -103,7 +103,10 @@ describe("agent-runtime model catalog compatibility", () => { PluginMetadataSnapshot, "owners" | "declaredProviderOwners" > & { - owners: Omit; + owners: Omit< + PluginMetadataSnapshot["owners"], + "modelIdNormalizationPolicies" | "providerAuthContributions" + >; }; type AcceptedMetadataSnapshot = NonNullable< NonNullable[0]>["metadataSnapshot"] diff --git a/src/plugin-sdk/agent-runtime.ts b/src/plugin-sdk/agent-runtime.ts index c432f2b2544d..954d2c1bdd2a 100644 --- a/src/plugin-sdk/agent-runtime.ts +++ b/src/plugin-sdk/agent-runtime.ts @@ -48,10 +48,18 @@ type LoadModelCatalogCompatibilityParams = LoadPreparedModelCatalogParams & { cacheOnly?: boolean; /** @deprecated Plugin metadata belongs to the published lifecycle generation. */ metadataSnapshot?: Omit & { - // Shipped snapshots may predate prepared provider ownership and normalization policies. + // Shipped snapshots may predate prepared provider ownership, auth contributions, and normalization policies. declaredProviderOwners?: PluginMetadataSnapshot["declaredProviderOwners"]; - owners: Omit & - Partial>; + owners: Omit< + PluginMetadataSnapshot["owners"], + "modelIdNormalizationPolicies" | "providerAuthContributions" + > & + Partial< + Pick< + PluginMetadataSnapshot["owners"], + "modelIdNormalizationPolicies" | "providerAuthContributions" + > + >; }; }; diff --git a/src/plugin-sdk/image-generation-core.ts b/src/plugin-sdk/image-generation-core.ts index eb8e11d7bc06..3fd0b3ace297 100644 --- a/src/plugin-sdk/image-generation-core.ts +++ b/src/plugin-sdk/image-generation-core.ts @@ -33,7 +33,7 @@ export { export { parseImageGenerationModelRef } from "../media-generation/model-ref.js"; export { createSubsystemLogger } from "../logging/subsystem.js"; export { normalizeGooglePreviewModelId as normalizeGoogleModelId } from "./provider-model-shared.js"; -export { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +export { getProviderEnvVars } from "./provider-env-vars.js"; /** Default OpenAI image model used when image-generation provider config omits one. */ export const OPENAI_DEFAULT_IMAGE_MODEL = "gpt-image-2"; diff --git a/src/plugin-sdk/provider-auth.ts b/src/plugin-sdk/provider-auth.ts index 1f42195d21f6..51a13e2d745c 100644 --- a/src/plugin-sdk/provider-auth.ts +++ b/src/plugin-sdk/provider-auth.ts @@ -91,10 +91,8 @@ export { normalizeOptionalSecretInput, normalizeSecretInput, } from "../utils/normalize-secret-input.js"; -export { - listKnownProviderAuthEnvVarNames, - omitEnvKeysCaseInsensitive, -} from "../secrets/provider-env-vars.js"; +export { listKnownProviderAuthEnvVarNames } from "./provider-env-vars.js"; +export { omitEnvKeysCaseInsensitive } from "../secrets/provider-env-vars.js"; export { buildOauthProviderAuthResult } from "./provider-auth-result.js"; export { buildOpenAICodexCredentialExtra, diff --git a/src/plugin-sdk/provider-env-vars.test.ts b/src/plugin-sdk/provider-env-vars.test.ts new file mode 100644 index 000000000000..4353a05ae7e1 --- /dev/null +++ b/src/plugin-sdk/provider-env-vars.test.ts @@ -0,0 +1,70 @@ +import { getProviderEnvVars as getImageProviderEnvVars } from "openclaw/plugin-sdk/image-generation-core"; +import { listKnownProviderAuthEnvVarNames as listAuthEnvVarNames } from "openclaw/plugin-sdk/provider-auth"; +import { + getProviderEnvVars, + listKnownProviderAuthEnvVarNames, + resolveProviderAuthEnvVarCandidates, +} from "openclaw/plugin-sdk/provider-env-vars"; +import { describe, expect, it } from "vitest"; +import { createPluginMetadataSnapshotFixture } from "../plugins/plugin-metadata.test-support.js"; + +type LookupParams = NonNullable[0]>; +const readers = [ + { + name: "provider-env-vars/getProviderEnvVars", + read: (params: LookupParams) => getProviderEnvVars("compat-provider", params), + expected: ["COMPAT_PROVIDER_KEY"], + }, + { + name: "image-generation-core/getProviderEnvVars", + read: (params: LookupParams) => getImageProviderEnvVars("compat-provider", params), + expected: ["COMPAT_PROVIDER_KEY"], + }, + { + name: "provider-env-vars/resolveProviderAuthEnvVarCandidates", + read: (params: LookupParams) => + resolveProviderAuthEnvVarCandidates(params)["compat-provider"] ?? [], + expected: ["COMPAT_PROVIDER_KEY"], + }, + { + name: "provider-env-vars/listKnownProviderAuthEnvVarNames", + read: listKnownProviderAuthEnvVarNames, + expected: ["COMPAT_PROVIDER_KEY", "COMPAT_USAGE_KEY"], + }, + { + name: "provider-auth/listKnownProviderAuthEnvVarNames", + read: listAuthEnvVarNames, + expected: ["COMPAT_PROVIDER_KEY", "COMPAT_USAGE_KEY"], + }, +]; + +describe.each(["current", "v2026.9.4"])("provider environment SDK with %s snapshots", (version) => { + it.each(readers)( + "preserves manifest names and trust filtering through $name", + ({ read, expected }) => { + const snapshot = createPluginMetadataSnapshotFixture({ + plugins: [ + { + id: "compat-provider", + origin: "workspace", + setup: { providers: [{ id: "compat-provider", envVars: ["COMPAT_PROVIDER_KEY"] }] }, + providerUsageAuthEnvVars: { "compat-provider": ["COMPAT_USAGE_KEY"] }, + }, + ], + }); + const { providerAuthContributions: _contributions, ...releasedOwners } = snapshot.owners; + const metadataSnapshot = + version === "current" ? snapshot : { ...snapshot, owners: Object.freeze(releasedOwners) }; + const params = { metadataSnapshot } satisfies LookupParams; + + expect(read(params)).toEqual(expect.arrayContaining(expected)); + const untrustedNames = read({ + ...params, + config: {}, + includeUntrustedWorkspacePlugins: false, + }); + expect(untrustedNames).not.toContain("COMPAT_PROVIDER_KEY"); + expect(untrustedNames).not.toContain("COMPAT_USAGE_KEY"); + }, + ); +}); diff --git a/src/plugin-sdk/provider-env-vars.ts b/src/plugin-sdk/provider-env-vars.ts index 1845a3a7fbd1..a13654d90b5e 100644 --- a/src/plugin-sdk/provider-env-vars.ts +++ b/src/plugin-sdk/provider-env-vars.ts @@ -1,8 +1,65 @@ // Public provider auth environment variable helpers for plugin runtimes. - -export { - getProviderEnvVars, - listKnownProviderAuthEnvVarNames, - omitEnvKeysCaseInsensitive, - resolveProviderAuthEnvVarCandidates, +import { buildPluginMetadataProviderFacts } from "../plugins/plugin-metadata-provider-facts.js"; +import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js"; +import { + getProviderEnvVarsCore, + listKnownProviderAuthEnvVarNamesCore, + resolveProviderAuthEnvVarCandidatesCore, + type ProviderEnvVarLookupParams as CoreLookupParams, } from "../secrets/provider-env-vars.js"; + +export { omitEnvKeysCaseInsensitive } from "../secrets/provider-env-vars.js"; + +type ProviderEnvVarLookupParams = + | CoreLookupParams + | (Omit & { + metadataSnapshot: Omit & { + owners: Omit; + }; + }); + +function hasPreparedLookupParams(params: ProviderEnvVarLookupParams): params is CoreLookupParams { + const snapshot = params.metadataSnapshot; + return ( + snapshot === undefined || + ("providerAuthContributions" in snapshot.owners && + snapshot.owners.providerAuthContributions !== undefined) + ); +} + +// v2026.9.4 accepted snapshots before these prepared facts existed. Keep this +// adaptation at the SDK boundary until an approved SDK-breaking release. +function prepareLookupParams(params?: ProviderEnvVarLookupParams): CoreLookupParams | undefined { + if (!params || hasPreparedLookupParams(params)) { + return params; + } + const snapshot = params.metadataSnapshot; + return { + ...params, + metadataSnapshot: { + ...snapshot, + owners: { + ...snapshot.owners, + providerAuthContributions: buildPluginMetadataProviderFacts(snapshot.plugins) + .providerAuthContributions, + }, + }, + }; +} + +export function getProviderEnvVars( + providerId: string, + params?: ProviderEnvVarLookupParams, +): string[] { + return getProviderEnvVarsCore(providerId, prepareLookupParams(params)); +} + +export function listKnownProviderAuthEnvVarNames(params?: ProviderEnvVarLookupParams): string[] { + return listKnownProviderAuthEnvVarNamesCore(prepareLookupParams(params)); +} + +export function resolveProviderAuthEnvVarCandidates( + params?: ProviderEnvVarLookupParams, +): Record { + return resolveProviderAuthEnvVarCandidatesCore(prepareLookupParams(params)); +} diff --git a/src/plugins/current-plugin-metadata-snapshot.test.ts b/src/plugins/current-plugin-metadata-snapshot.test.ts index 2bccf03aae4f..4a1411633082 100644 --- a/src/plugins/current-plugin-metadata-snapshot.test.ts +++ b/src/plugins/current-plugin-metadata-snapshot.test.ts @@ -1,4 +1,3 @@ -// Covers current plugin metadata snapshot generation. import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -34,6 +33,8 @@ import { clearPluginMetadataLifecycleCaches, retainGatewayPluginMetadata, } from "./plugin-metadata-lifecycle.js"; +// Covers current plugin metadata snapshot generation. +import { buildPluginMetadataProviderFacts } from "./plugin-metadata-provider-facts.js"; import { restorePluginMetadataSnapshot, type PluginMetadataSnapshot, @@ -113,6 +114,8 @@ function createSnapshot( setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: + buildPluginMetadataProviderFacts(plugins).providerAuthContributions, modelIdNormalizationPolicies: collectManifestModelIdNormalizationPolicies(plugins), }, metrics: { diff --git a/src/plugins/current-plugin-metadata.test-support.ts b/src/plugins/current-plugin-metadata.test-support.ts index b25e58b73182..d07b29dec11e 100644 --- a/src/plugins/current-plugin-metadata.test-support.ts +++ b/src/plugins/current-plugin-metadata.test-support.ts @@ -73,6 +73,7 @@ export function makeEmptyPluginMetadataOwners(): PluginMetadataSnapshot["owners" setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }; } diff --git a/src/plugins/plugin-metadata-provider-facts.ts b/src/plugins/plugin-metadata-provider-facts.ts index 61ef028c4a9d..692d15277e2e 100644 --- a/src/plugins/plugin-metadata-provider-facts.ts +++ b/src/plugins/plugin-metadata-provider-facts.ts @@ -12,8 +12,12 @@ import type { PluginManifestProviderRequestProvider, } from "./manifest.js"; import { listOfficialExternalProviderEndpointManifests } from "./official-external-provider-endpoints.js"; -import type { PluginProviderAuthAliasCandidate } from "./plugin-metadata-snapshot.types.js"; +import type { + PluginProviderAuthAliasCandidate, + PluginProviderAuthContribution, +} from "./plugin-metadata-snapshot.types.js"; import type { PluginOrigin } from "./plugin-origin.types.js"; +import { listSetupProviderIds } from "./setup-descriptors.js"; const PROVIDER_ENDPOINT_CLASSES = new Set( "anthropic-public cerebras-native chutes-native deepseek-native github-copilot-native groq-native meta-native mistral-public minimax-native moonshot-native modelstudio-native nvidia-native openai-public openai opencode-native opencode-go-native azure-openai openrouter xai-native xiaomi-native zai-native google-generative-ai google-vertex".split( @@ -157,7 +161,27 @@ export function buildPluginMetadataProviderFacts(plugins: readonly PluginManifes prepareProviderEndpoints(plugin.providerEndpoints), ); const providerRequests = new Map(); + const providerAuthContributions: PluginProviderAuthContribution[] = []; for (const plugin of plugins) { + // Package declarations are stable; readers still decide eligibility against current config. + const envProviders = (plugin.setup?.providers ?? []).filter( + (provider) => provider.envVars?.length, + ); + const evidenceProviders = (plugin.setup?.providers ?? []).filter( + (provider) => provider.authEvidence?.length, + ); + const fallbackProviderRefs = + plugin.setup?.requiresRuntime !== false + ? listSetupProviderIds(plugin).map(normalizeProviderId).filter(Boolean) + : []; + if (envProviders.length || evidenceProviders.length || fallbackProviderRefs.length) { + providerAuthContributions.push({ + plugin, + envProviders, + evidenceProviders, + fallbackProviderRefs, + }); + } const requests = isRecord(plugin.providerRequest?.providers) ? plugin.providerRequest.providers : {}; @@ -191,6 +215,7 @@ export function buildPluginMetadataProviderFacts(plugins: readonly PluginManifes return { providerEndpoints, providerRequests, + providerAuthContributions, modelIdNormalizationPolicies: collectManifestModelIdNormalizationPolicies(plugins), providerAuthAliases: buildPluginMetadataProviderAuthAliases(plugins), }; diff --git a/src/plugins/plugin-metadata-snapshot.types.ts b/src/plugins/plugin-metadata-snapshot.types.ts index fe6a03bed355..1f780af50d2f 100644 --- a/src/plugins/plugin-metadata-snapshot.types.ts +++ b/src/plugins/plugin-metadata-snapshot.types.ts @@ -9,6 +9,7 @@ import type { PluginManifestModelIdNormalizationProvider, PluginManifestProviderEndpoint, PluginManifestProviderRequestProvider, + PluginManifestSetupProvider, } from "./manifest-types.js"; import type { PluginRegistrySnapshotDiagnostic, @@ -28,6 +29,13 @@ export type PluginProviderAuthAliasCandidate = { order: number; }; +export type PluginProviderAuthContribution = { + plugin: PluginManifestRecord; + envProviders: readonly PluginManifestSetupProvider[]; + evidenceProviders: readonly PluginManifestSetupProvider[]; + fallbackProviderRefs: readonly string[]; +}; + export type PluginMetadataSnapshotOwnerMaps = { channels: ReadonlyMap; channelAccountKeyPolicies?: ReadonlyMap; @@ -40,6 +48,7 @@ export type PluginMetadataSnapshotOwnerMaps = { contracts: ReadonlyMap; /** Empty views must not fall through to process-current model normalization policies. */ modelIdNormalizationPolicies: ReadonlyMap; + providerAuthContributions: readonly PluginProviderAuthContribution[]; providerAuthAliases?: ReadonlyMap; providerEndpoints?: readonly PluginManifestProviderEndpoint[]; providerRequests?: ReadonlyMap; diff --git a/src/plugins/provider-auth-env-trust.test.ts b/src/plugins/provider-auth-env-trust.test.ts index 50a76c635e05..923f31c0b284 100644 --- a/src/plugins/provider-auth-env-trust.test.ts +++ b/src/plugins/provider-auth-env-trust.test.ts @@ -1,10 +1,10 @@ // Verifies provider auth environment trust decisions. import { describe, expect, it, vi } from "vitest"; -const getProviderEnvVars = vi.hoisted(() => vi.fn(() => ["WHISPERX_API_KEY"])); +const getProviderEnvVarsCore = vi.hoisted(() => vi.fn(() => ["WHISPERX_API_KEY"])); vi.mock("../secrets/provider-env-vars.js", () => ({ - getProviderEnvVars, + getProviderEnvVarsCore, resolveProviderAuthLookupMaps: () => ({ aliasMap: {}, envCandidateMap: {}, @@ -22,7 +22,7 @@ describe("provider auth env trust", () => { config, }); - expect(getProviderEnvVars).toHaveBeenCalledWith("whisperx", { + expect(getProviderEnvVarsCore).toHaveBeenCalledWith("whisperx", { config, includeUntrustedWorkspacePlugins: false, }); @@ -85,7 +85,7 @@ describe("provider auth env trust", () => { env: { WHISPERX_API_KEY: "test-secret" }, }); - expect(getProviderEnvVars).toHaveBeenCalledWith("whisperx", { + expect(getProviderEnvVarsCore).toHaveBeenCalledWith("whisperx", { config, includeUntrustedWorkspacePlugins: false, }); @@ -111,7 +111,7 @@ describe("provider auth env trust", () => { env: { WHISPERX_API_KEY: "test-secret" }, }); - expect(getProviderEnvVars).toHaveBeenCalledWith("whisperx", { + expect(getProviderEnvVarsCore).toHaveBeenCalledWith("whisperx", { config, includeUntrustedWorkspacePlugins: false, }); diff --git a/src/plugins/provider-auth-helpers.ts b/src/plugins/provider-auth-helpers.ts index eddd1ddf70b6..ca1637ced255 100644 --- a/src/plugins/provider-auth-helpers.ts +++ b/src/plugins/provider-auth-helpers.ts @@ -21,7 +21,7 @@ import { } from "../config/types.secrets.js"; import { safeRealpathSync } from "../infra/boundary-path.js"; import type { OAuthCredentials } from "../llm/oauth.js"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; import { isValidSecretRef } from "../secrets/ref-contract.js"; import { normalizeSecretInput } from "../utils/normalize-secret-input.js"; import type { SecretInputMode } from "./provider-auth-types.js"; @@ -45,7 +45,7 @@ function buildEnvSecretRef(id: string): SecretRef { } function resolveProviderDefaultEnvSecretRef(provider: string, config?: OpenClawConfig): SecretRef { - const envVars = getProviderEnvVars(provider, { + const envVars = getProviderEnvVarsCore(provider, { ...(config ? { config } : {}), includeUntrustedWorkspacePlugins: false, }); diff --git a/src/plugins/provider-auth-ref.ts b/src/plugins/provider-auth-ref.ts index 024c89942877..0edf5bd73944 100644 --- a/src/plugins/provider-auth-ref.ts +++ b/src/plugins/provider-auth-ref.ts @@ -7,7 +7,7 @@ import type { OpenClawConfig } from "../config/types.js"; import { isValidEnvSecretRefId, type SecretRef } from "../config/types.secrets.js"; import { formatErrorMessage } from "../infra/errors.js"; import { encodeJsonPointerToken } from "../secrets/json-pointer.js"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; import { formatExecSecretRefIdValidationMessage, isValidExecSecretRefId, @@ -53,7 +53,7 @@ function resolveDefaultProviderEnvVar( provider: string, config?: OpenClawConfig, ): string | undefined { - const envVars = getProviderEnvVars(provider, { + const envVars = getProviderEnvVarsCore(provider, { ...(config ? { config } : {}), includeUntrustedWorkspacePlugins: false, }); @@ -72,7 +72,7 @@ export function resolveRefFallbackInput(params: { }): { ref: SecretRef; resolvedValue: string } { const fallbackEnvVar = params.preferredEnvVar ?? - getProviderEnvVars(params.provider, { + getProviderEnvVarsCore(params.provider, { config: params.config, includeUntrustedWorkspacePlugins: false, }).find((candidate) => normalizeOptionalString(candidate) !== undefined); diff --git a/src/plugins/provider-model-compat.prepared.test.ts b/src/plugins/provider-model-compat.prepared.test.ts index 2aeed29cfc59..433ad0ad57ee 100644 --- a/src/plugins/provider-model-compat.prepared.test.ts +++ b/src/plugins/provider-model-compat.prepared.test.ts @@ -27,6 +27,7 @@ function makeOwners(provider: string): PluginMetadataSnapshotOwnerMaps { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }; } diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index d0e1041075f1..27f5cc80c13a 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -2,8 +2,10 @@ import { sortUniqueStrings } from "@openclaw/normalization-core/string-normaliza import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import type { PluginAutoEnableResult } from "../config/plugin-auto-enable.js"; +import { makeEmptyPluginMetadataOwners } from "./current-plugin-metadata.test-support.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; import type { OpenClawPackageManifest } from "./manifest.js"; +import { buildPluginMetadataProviderFacts } from "./plugin-metadata-provider-facts.js"; import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js"; import type { PluginRegistrySnapshot } from "./plugin-registry.js"; import { createEmptyPluginRegistry } from "./registry-empty.js"; @@ -192,8 +194,9 @@ function createMetadataSnapshotFixture( }, byPluginId: new Map(plugins.map((plugin) => [plugin.id, plugin])), owners: { - channels: ownerMap([]), - channelConfigs: ownerMap([]), + ...makeEmptyPluginMetadataOwners(), + providerAuthContributions: + buildPluginMetadataProviderFacts(plugins).providerAuthContributions, providers: ownerMap( plugins.flatMap((plugin) => plugin.providers.map((providerId) => [providerId, [plugin.id]] as const), @@ -213,10 +216,6 @@ function createMetadataSnapshotFixture( ), ), ), - setupProviders: ownerMap([]), - commandAliases: ownerMap([]), - contracts: ownerMap([]), - modelIdNormalizationPolicies: new Map(), }, }; } diff --git a/src/plugins/runtime/load-context.current-snapshot.test.ts b/src/plugins/runtime/load-context.current-snapshot.test.ts index 166729573a0f..29c822afc55d 100644 --- a/src/plugins/runtime/load-context.current-snapshot.test.ts +++ b/src/plugins/runtime/load-context.current-snapshot.test.ts @@ -54,6 +54,7 @@ function createSnapshot(params: { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/plugins/runtime/load-context.test.ts b/src/plugins/runtime/load-context.test.ts index d70c39dc8c43..dc05f13ecba4 100644 --- a/src/plugins/runtime/load-context.test.ts +++ b/src/plugins/runtime/load-context.test.ts @@ -48,6 +48,7 @@ const metadataSnapshot: PluginMetadataSnapshot = { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: [], modelIdNormalizationPolicies: new Map(), }, metrics: { diff --git a/src/secrets/configure.ts b/src/secrets/configure.ts index 0f657d4ca621..f426537b74d6 100644 --- a/src/secrets/configure.ts +++ b/src/secrets/configure.ts @@ -38,7 +38,7 @@ import { } from "./configure-plan.js"; import { getSkippedExecRefStaticError } from "./exec-resolution-policy.js"; import type { SecretsApplyPlan } from "./plan.js"; -import { getProviderEnvVars } from "./provider-env-vars.js"; +import { getProviderEnvVarsCore } from "./provider-env-vars.js"; import { listSecretProviderIntegrationPresets, type SecretProviderIntegrationPreset, @@ -297,7 +297,7 @@ function resolveSuggestedEnvSecretId(candidate: ConfigureCandidate): string | un if (!hintedProvider) { return undefined; } - const envCandidates = getProviderEnvVars(hintedProvider); + const envCandidates = getProviderEnvVarsCore(hintedProvider); if (!Array.isArray(envCandidates) || envCandidates.length === 0) { return undefined; } diff --git a/src/secrets/provider-env-vars.dynamic.test.ts b/src/secrets/provider-env-vars.dynamic.test.ts index 2ebda7e7b94b..52e64f5ee49f 100644 --- a/src/secrets/provider-env-vars.dynamic.test.ts +++ b/src/secrets/provider-env-vars.dynamic.test.ts @@ -3,18 +3,20 @@ import fs from "node:fs"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { sanitizeEnvVars } from "../agents/sandbox/sanitize-env-vars.js"; import * as pluginConfigState from "../plugins/config-state.js"; +import type { PluginManifestRecord } from "../plugins/manifest-registry.js"; +import { buildPluginMetadataProviderFacts } from "../plugins/plugin-metadata-provider-facts.js"; import { resolveLocalProviderAuthEvidence } from "./provider-auth-evidence.js"; import { - getProviderEnvVars, - listKnownProviderAuthEnvVarNames, + getProviderEnvVarsCore, + listKnownProviderAuthEnvVarNamesCore, listKnownSecretEnvVarNames, - resolveProviderAuthEnvVarCandidates, + resolveProviderAuthEnvVarCandidatesCore, resolveProviderAuthLookupMaps, } from "./provider-env-vars.js"; type MockManifestPlugin = { id: string; - origin: string; + origin: PluginManifestRecord["origin"]; enabled?: boolean; enabledByDefault?: boolean; kind?: "memory" | "context-engine" | Array<"memory" | "context-engine">; @@ -78,7 +80,7 @@ function manifestRegistry(...plugins: MockManifestPlugin[]): MockManifestRegistr function setupPlugin( id: string, - origin: string, + origin: PluginManifestRecord["origin"], provider: MockSetupProvider, extra: Omit = {}, ): MockManifestPlugin { @@ -86,7 +88,19 @@ function setupPlugin( } function metadataSnapshot(...plugins: MockManifestPlugin[]) { + const records: PluginManifestRecord[] = plugins.map((plugin) => ({ + channels: [], + providers: [], + cliBackends: [], + skills: [], + hooks: [], + rootDir: `/plugins/${plugin.id}`, + source: `/plugins/${plugin.id}/index.js`, + manifestPath: `/plugins/${plugin.id}/openclaw.plugin.json`, + ...plugin, + })); return { + owners: buildPluginMetadataProviderFacts(records), index: { plugins: plugins.map((plugin) => ({ pluginId: plugin.id, @@ -112,7 +126,7 @@ function useInstalledPlugins(...plugins: MockManifestPlugin[]): void { function useInstalledSetupPlugin( id: string, - origin: string, + origin: PluginManifestRecord["origin"], provider: MockSetupProvider, extra?: Omit, ): void { @@ -125,7 +139,11 @@ function useRegistryPlugins(...plugins: MockManifestPlugin[]): void { ); } -function useRegistrySetupPlugin(id: string, origin: string, provider: MockSetupProvider): void { +function useRegistrySetupPlugin( + id: string, + origin: PluginManifestRecord["origin"], + provider: MockSetupProvider, +): void { useRegistryPlugins(setupPlugin(id, origin, provider)); } @@ -168,9 +186,11 @@ describe("provider env vars dynamic manifest metadata", () => { { providerAuthAliases: { "fireworks-plan": "fireworks" } }, ); - expect(getProviderEnvVars("fireworks", { config: {} })).toEqual(["FIREWORKS_ALT_API_KEY"]); - expect(getProviderEnvVars("fireworks-plan", { config: {} })).toEqual(["FIREWORKS_ALT_API_KEY"]); - expect(listKnownProviderAuthEnvVarNames()).toContain("FIREWORKS_ALT_API_KEY"); + expect(getProviderEnvVarsCore("fireworks", { config: {} })).toEqual(["FIREWORKS_ALT_API_KEY"]); + expect(getProviderEnvVarsCore("fireworks-plan", { config: {} })).toEqual([ + "FIREWORKS_ALT_API_KEY", + ]); + expect(listKnownProviderAuthEnvVarNamesCore()).toContain("FIREWORKS_ALT_API_KEY"); expect(listKnownSecretEnvVarNames()).toContain("FIREWORKS_ALT_API_KEY"); }); @@ -184,10 +204,10 @@ describe("provider env vars dynamic manifest metadata", () => { }, }); - expect(listKnownProviderAuthEnvVarNames()).toContain("PROVIDER_BILLING_CREDENTIAL"); + expect(listKnownProviderAuthEnvVarNamesCore()).toContain("PROVIDER_BILLING_CREDENTIAL"); expect(listKnownSecretEnvVarNames()).toContain("PROVIDER_BILLING_CREDENTIAL"); - expect(resolveProviderAuthEnvVarCandidates()["provider-billing"]).toBeUndefined(); - expect(getProviderEnvVars("provider-billing")).toStrictEqual([]); + expect(resolveProviderAuthEnvVarCandidatesCore()["provider-billing"]).toBeUndefined(); + expect(getProviderEnvVarsCore("provider-billing")).toStrictEqual([]); expect( sanitizeEnvVars({ PROVIDER_BILLING_CREDENTIAL: "billing-secret", SAFE_VALUE: "ok" }), ).toMatchObject({ @@ -199,6 +219,7 @@ describe("provider env vars dynamic manifest metadata", () => { it("scrubs usage credentials using host metadata rather than the candidate sandbox env", () => { const configuredSnapshot = { workspaceDir: "/workspace", + owners: buildPluginMetadataProviderFacts([]), index: { plugins: [ { @@ -251,7 +272,7 @@ describe("provider env vars dynamic manifest metadata", () => { }); it("lets openai bootstrap from Codex app-server API-key env", () => { - expect(resolveProviderAuthEnvVarCandidates()["openai"]).toEqual([ + expect(resolveProviderAuthEnvVarCandidatesCore()["openai"]).toEqual([ "CODEX_API_KEY", "OPENAI_API_KEY", ]); @@ -263,8 +284,10 @@ describe("provider env vars dynamic manifest metadata", () => { envVars: ["MODEL_STUDIO_API_KEY", "MODEL_STUDIO_API_KEY"], }); - expect(getProviderEnvVars("model-studio", { config: {} })).toEqual(["MODEL_STUDIO_API_KEY"]); - expect(listKnownProviderAuthEnvVarNames()).toContain("MODEL_STUDIO_API_KEY"); + expect(getProviderEnvVarsCore("model-studio", { config: {} })).toEqual([ + "MODEL_STUDIO_API_KEY", + ]); + expect(listKnownProviderAuthEnvVarNamesCore()).toContain("MODEL_STUDIO_API_KEY"); expect(listKnownSecretEnvVarNames()).toContain("MODEL_STUDIO_API_KEY"); }); @@ -516,7 +539,7 @@ describe("provider env vars dynamic manifest metadata", () => { ); expect( - resolveProviderAuthEnvVarCandidates({ config: {} })["load-path-provider"], + resolveProviderAuthEnvVarCandidatesCore({ config: {} })["load-path-provider"], ).toBeUndefined(); expect(pluginRegistryMocks.getCurrentPluginMetadataSnapshot).toHaveBeenCalledWith({ env: process.env, @@ -578,7 +601,7 @@ describe("provider env vars dynamic manifest metadata", () => { envVars: ["FIREWORKS_API_KEY", "FIREWORKS_SETUP_KEY", "FIREWORKS_API_KEY"], }); - expect(getProviderEnvVars("fireworks", { config: {} })).toEqual([ + expect(getProviderEnvVarsCore("fireworks", { config: {} })).toEqual([ "FIREWORKS_API_KEY", "FIREWORKS_SETUP_KEY", ]); @@ -594,11 +617,11 @@ describe("provider env vars dynamic manifest metadata", () => { const mod = await import("./provider-env-vars.js"); expect(pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled(); - expect(mod.getProviderEnvVars("fireworks")).toEqual(["FIREWORKS_ALT_API_KEY"]); + expect(mod.getProviderEnvVarsCore("fireworks")).toEqual(["FIREWORKS_ALT_API_KEY"]); const initialLoads = pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mock.calls.length; expect(initialLoads).toBeGreaterThan(0); - expect(mod.getProviderEnvVars("fireworks")).toEqual(["FIREWORKS_ALT_API_KEY"]); + expect(mod.getProviderEnvVarsCore("fireworks")).toEqual(["FIREWORKS_ALT_API_KEY"]); expect(pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes( initialLoads, ); @@ -613,8 +636,8 @@ describe("provider env vars dynamic manifest metadata", () => { vi.resetModules(); const mod = await import("./provider-env-vars.js"); - expect(mod.getProviderEnvVars("whisperx")).toEqual(["WHISPERX_API_KEY"]); - expect(mod.listKnownProviderAuthEnvVarNames()).toContain("WHISPERX_API_KEY"); + expect(mod.getProviderEnvVarsCore("whisperx")).toEqual(["WHISPERX_API_KEY"]); + expect(mod.listKnownProviderAuthEnvVarNamesCore()).toContain("WHISPERX_API_KEY"); }); it("excludes untrusted workspace plugin env vars when requested", async () => { @@ -638,25 +661,25 @@ describe("provider env vars dynamic manifest metadata", () => { const mod = await import("./provider-env-vars.js"); expect( - mod.getProviderEnvVars("whisperx", { + mod.getProviderEnvVarsCore("whisperx", { config: { plugins: {} }, includeUntrustedWorkspacePlugins: false, }), ).toStrictEqual([]); expect( - mod.getProviderEnvVars("workspace-setup", { + mod.getProviderEnvVarsCore("workspace-setup", { config: { plugins: {} }, includeUntrustedWorkspacePlugins: false, }), ).toStrictEqual([]); expect( - mod.listKnownProviderAuthEnvVarNames({ + mod.listKnownProviderAuthEnvVarNamesCore({ config: { plugins: {} }, includeUntrustedWorkspacePlugins: false, }), ).not.toContain("AWS_SECRET_ACCESS_KEY"); expect( - mod.listKnownProviderAuthEnvVarNames({ + mod.listKnownProviderAuthEnvVarNamesCore({ config: { plugins: {} }, includeUntrustedWorkspacePlugins: false, }), @@ -672,7 +695,7 @@ describe("provider env vars dynamic manifest metadata", () => { const mod = await import("./provider-env-vars.js"); expect( - mod.getProviderEnvVars("whisperx", { + mod.getProviderEnvVarsCore("whisperx", { config: { plugins: { allow: ["workspace-audio"], @@ -692,7 +715,7 @@ describe("provider env vars dynamic manifest metadata", () => { const mod = await import("./provider-env-vars.js"); expect( - mod.getProviderEnvVars("whisperx", { + mod.getProviderEnvVarsCore("whisperx", { config: { plugins: { slots: { @@ -716,7 +739,7 @@ describe("provider env vars dynamic manifest metadata", () => { const mod = await import("./provider-env-vars.js"); expect( - mod.getProviderEnvVars("whisperx", { + mod.getProviderEnvVarsCore("whisperx", { config: { plugins: { slots: { @@ -732,13 +755,13 @@ describe("provider env vars dynamic manifest metadata", () => { it.each([ { name: "auth candidates", - resolve: () => resolveProviderAuthEnvVarCandidates({ config: {} }).fireworks, + resolve: () => resolveProviderAuthEnvVarCandidatesCore({ config: {} }).fireworks, metadataLoads: 1, }, { name: "auth scrub keys", resolve: () => - listKnownProviderAuthEnvVarNames({ config: {} }).filter((key) => + listKnownProviderAuthEnvVarNamesCore({ config: {} }).filter((key) => key.startsWith("FIREWORKS_"), ), metadataLoads: 2, @@ -918,7 +941,7 @@ describe("provider env vars dynamic manifest metadata", () => { }, ); - expect(resolveProviderAuthEnvVarCandidates()["load-path-provider"]).toBeUndefined(); + expect(resolveProviderAuthEnvVarCandidatesCore()["load-path-provider"]).toBeUndefined(); expect(pluginRegistryMocks.getCurrentPluginMetadataSnapshot).toHaveBeenCalledWith({ env: process.env, allowWorkspaceScopedSnapshot: true, diff --git a/src/secrets/provider-env-vars.test.ts b/src/secrets/provider-env-vars.test.ts index c22ec4b45b50..68eda829cb59 100644 --- a/src/secrets/provider-env-vars.test.ts +++ b/src/secrets/provider-env-vars.test.ts @@ -1,8 +1,8 @@ /** Tests provider env-var candidate and auth evidence lookup. */ import { describe, expect, it } from "vitest"; import { - getProviderEnvVars, - listKnownProviderAuthEnvVarNames, + getProviderEnvVarsCore, + listKnownProviderAuthEnvVarNamesCore, listKnownSecretEnvVarNames, omitEnvKeysCaseInsensitive, } from "./provider-env-vars.js"; @@ -19,7 +19,7 @@ describe("provider env vars", () => { "OPENROUTER_API_KEY", "TAVILY_API_KEY", ]; - const providerAuthNames = listKnownProviderAuthEnvVarNames(); + const providerAuthNames = listKnownProviderAuthEnvVarNamesCore(); const secretNames = listKnownSecretEnvVarNames(); for (const name of sharedSecretNames) { expect(providerAuthNames).toContain(name); @@ -38,8 +38,8 @@ describe("provider env vars", () => { it.each(["GH_TOKEN", "GITHUB_TOKEN"])("audits %s without activating a provider", (name) => { expect(listKnownSecretEnvVarNames()).toContain(name); - expect(listKnownProviderAuthEnvVarNames()).not.toContain(name); - expect(getProviderEnvVars("github-copilot")).not.toContain(name); + expect(listKnownProviderAuthEnvVarNamesCore()).not.toContain(name); + expect(getProviderEnvVarsCore("github-copilot")).not.toContain(name); }); it("omits env keys case-insensitively", () => { @@ -58,10 +58,13 @@ describe("provider env vars", () => { }); it("ignores prototype-chain keys when resolving provider env vars", () => { - expect(getProviderEnvVars("__proto__")).toStrictEqual([]); - expect(getProviderEnvVars("constructor")).toStrictEqual([]); - expect(getProviderEnvVars("openai")).toEqual(["CODEX_API_KEY", "OPENAI_API_KEY"]); - expect(getProviderEnvVars("anthropic")).toEqual(["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]); - expect(getProviderEnvVars("fal")).toEqual(["FAL_KEY", "FAL_API_KEY"]); + expect(getProviderEnvVarsCore("__proto__")).toStrictEqual([]); + expect(getProviderEnvVarsCore("constructor")).toStrictEqual([]); + expect(getProviderEnvVarsCore("openai")).toEqual(["CODEX_API_KEY", "OPENAI_API_KEY"]); + expect(getProviderEnvVarsCore("anthropic")).toEqual([ + "ANTHROPIC_OAUTH_TOKEN", + "ANTHROPIC_API_KEY", + ]); + expect(getProviderEnvVarsCore("fal")).toEqual(["FAL_KEY", "FAL_API_KEY"]); }); }); diff --git a/src/secrets/provider-env-vars.ts b/src/secrets/provider-env-vars.ts index e17d0efebb7e..9affe49c19b2 100644 --- a/src/secrets/provider-env-vars.ts +++ b/src/secrets/provider-env-vars.ts @@ -18,7 +18,6 @@ import { loadPluginMetadataSnapshot, type PluginMetadataSnapshot, } from "../plugins/plugin-metadata-snapshot.js"; -import { listSetupProviderIds } from "../plugins/setup-descriptors.js"; import { hasKind } from "../plugins/slots.js"; import { appendUniqueEnvVarCandidates } from "../shared/env-var-candidates.js"; @@ -209,11 +208,11 @@ function resolveManifestProviderAuthEnvVarCandidates( sortedAliases: readonly (readonly [string, string])[], ): Record { const candidates: Record = {}; - for (const plugin of snapshot.plugins) { - if (!shouldUsePluginProviderEnvVars(plugin, params)) { + for (const { plugin, envProviders } of snapshot.owners.providerAuthContributions) { + if (envProviders.length === 0 || !shouldUsePluginProviderEnvVars(plugin, params)) { continue; } - for (const provider of plugin.setup?.providers ?? []) { + for (const provider of envProviders) { appendUniqueEnvVarCandidates(candidates, provider.id, provider.envVars ?? []); } } @@ -235,15 +234,9 @@ function resolveManifestRuntimeAuthFacts( const evidenceByProvider: Record = {}; const refs = new Set(); const isEnabled = createInstalledPluginEnabledPredicate(snapshot.index.plugins, params?.config); - for (const plugin of snapshot.plugins) { - const evidenceProviders = (plugin.setup?.providers ?? []).filter( - (provider) => provider.authEvidence?.length, - ); - const fallbackProviders = - plugin.setup?.requiresRuntime !== false && (plugin.setup?.providers || plugin.providers) - ? listSetupProviderIds(plugin) - : []; - if (evidenceProviders.length === 0 && fallbackProviders.length === 0) { + for (const { plugin, evidenceProviders, fallbackProviderRefs } of snapshot.owners + .providerAuthContributions) { + if (evidenceProviders.length === 0 && fallbackProviderRefs.length === 0) { continue; } // Package contributions are fixed, but their eligibility follows current config. @@ -256,8 +249,8 @@ function resolveManifestRuntimeAuthFacts( appendUniqueAuthEvidence(evidenceByProvider, provider.id, provider.authEvidence ?? []); } } - for (const providerId of fallbackProviders) { - appendUniqueProviderRef(refs, providerId); + for (const providerId of fallbackProviderRefs) { + refs.add(providerId); } } for (const [alias, target] of sortedAliases) { @@ -279,7 +272,7 @@ function resolveManifestRuntimeAuthFacts( } /** Resolves provider auth env-var candidates from core fallbacks and plugin metadata. */ -export function resolveProviderAuthEnvVarCandidates( +export function resolveProviderAuthEnvVarCandidatesCore( params?: ProviderEnvVarLookupParams, ): Record { const snapshot = resolveProviderMetadataSnapshot(params); @@ -375,16 +368,16 @@ function createLazyReadonlyRecord( * overrides where generic onboarding wants a different preferred env var. */ const PROVIDER_ENV_VARS = createLazyReadonlyRecord(() => - withSetupEnvOverrides(resolveProviderAuthEnvVarCandidates()), + withSetupEnvOverrides(resolveProviderAuthEnvVarCandidatesCore()), ); /** Returns known env var candidates for a provider id or alias. */ -export function getProviderEnvVars( +export function getProviderEnvVarsCore( providerId: string, params?: ProviderEnvVarLookupParams, ): string[] { const providerEnvVars = params - ? withSetupEnvOverrides(resolveProviderAuthEnvVarCandidates(params)) + ? withSetupEnvOverrides(resolveProviderAuthEnvVarCandidatesCore(params)) : PROVIDER_ENV_VARS; const envVars = Object.hasOwn(providerEnvVars, providerId) ? providerEnvVars[providerId] @@ -395,8 +388,10 @@ export function getProviderEnvVars( // OPENCLAW_API_KEY authenticates the local OpenClaw bridge itself and must // remain available to child bridge/runtime processes. /** Lists known provider auth env vars without bridge-only env vars. */ -export function listKnownProviderAuthEnvVarNames(params?: ProviderEnvVarLookupParams): string[] { - const authCandidates = resolveProviderAuthEnvVarCandidates(params); +export function listKnownProviderAuthEnvVarNamesCore( + params?: ProviderEnvVarLookupParams, +): string[] { + const authCandidates = resolveProviderAuthEnvVarCandidatesCore(params); // Keep auth-only candidates before setup overrides, then append usage-only hints. return uniqueStrings([ ...Object.values(authCandidates).flat(), @@ -410,7 +405,7 @@ export async function listKnownProviderAuthEnvVarNamesAsync( params?: ProviderEnvVarLookupParams, ): Promise { if (params?.metadataSnapshot) { - return listKnownProviderAuthEnvVarNames(params); + return listKnownProviderAuthEnvVarNamesCore(params); } const env = cloneEnvWithPlatformSemantics(params?.env ?? process.env); const lookup = { ...params, env }; @@ -426,7 +421,7 @@ export async function listKnownProviderAuthEnvVarNamesAsync( activate(); metadataSnapshot = resolveProviderMetadataSnapshot(lookup); } - return listKnownProviderAuthEnvVarNames({ ...lookup, metadataSnapshot }); + return listKnownProviderAuthEnvVarNamesCore({ ...lookup, metadataSnapshot }); }); } finally { release(); @@ -438,7 +433,7 @@ export function listKnownSecretEnvVarNames(params?: ProviderEnvVarLookupParams): return uniqueStrings([ "GH_TOKEN", "GITHUB_TOKEN", - ...Object.values(withSetupEnvOverrides(resolveProviderAuthEnvVarCandidates(params))).flat(), + ...Object.values(withSetupEnvOverrides(resolveProviderAuthEnvVarCandidatesCore(params))).flat(), ...resolveManifestProviderUsageAuthEnvVarNames(params), ]); } diff --git a/src/skills/loading/workspace-skill-loader.test.ts b/src/skills/loading/workspace-skill-loader.test.ts index 50992537952a..ecb9f1757058 100644 --- a/src/skills/loading/workspace-skill-loader.test.ts +++ b/src/skills/loading/workspace-skill-loader.test.ts @@ -1,4 +1,3 @@ -// Workspace skill loader tests cover source merging, metadata, filtering, and precedence. import fsSync from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; @@ -13,6 +12,8 @@ import type { PluginManifestRecord, PluginManifestRegistry, } from "../../plugins/manifest-registry.js"; +// Workspace skill loader tests cover source merging, metadata, filtering, and precedence. +import { buildPluginMetadataProviderFacts } from "../../plugins/plugin-metadata-provider-facts.js"; import type { PluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js"; import { buildDeclaredProviderOwnerIndex } from "../../plugins/provider-owner-index.js"; import { setActiveDegradedSecretOwners } from "../../secrets/runtime-degraded-state.js"; @@ -356,6 +357,8 @@ function createWorkspacePluginMetadataSnapshot(params: { setupProviders: new Map(), commandAliases: new Map(), contracts: new Map(), + providerAuthContributions: buildPluginMetadataProviderFacts(params.manifestRegistry.plugins) + .providerAuthContributions, modelIdNormalizationPolicies: new Map(), }; const index: PluginMetadataSnapshot["index"] = { diff --git a/src/test-utils/generation-live-test-helpers.ts b/src/test-utils/generation-live-test-helpers.ts index 7e3aec6b2cab..9a0e6c552773 100644 --- a/src/test-utils/generation-live-test-helpers.ts +++ b/src/test-utils/generation-live-test-helpers.ts @@ -1,11 +1,11 @@ // Live-test helpers for generation provider credentials and config loading. import { loadShellEnvFallback } from "../infra/shell-env.js"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; /** Loads shell env only when a live generation provider declares missing key names. */ export function maybeLoadShellEnvForGenerationProviders(providerIds: string[]): void { const expectedKeys = [ - ...new Set(providerIds.flatMap((providerId) => getProviderEnvVars(providerId))), + ...new Set(providerIds.flatMap((providerId) => getProviderEnvVarsCore(providerId))), ]; if (expectedKeys.length === 0) { return; diff --git a/src/video-generation/runtime.ts b/src/video-generation/runtime.ts index b6ccecc8c047..fbdd1fe3a917 100644 --- a/src/video-generation/runtime.ts +++ b/src/video-generation/runtime.ts @@ -19,7 +19,7 @@ import { buildCapabilityProviderIndex, normalizeCapabilityProviderId, } from "../plugins/provider-registry-shared.js"; -import { getProviderEnvVars } from "../secrets/provider-env-vars.js"; +import { getProviderEnvVarsCore } from "../secrets/provider-env-vars.js"; import { resolveVideoGenerationModeCapabilities } from "./capabilities.js"; import { buildVideoGenerationCapabilityFailure, @@ -38,7 +38,7 @@ const SUPPORTED_DURATIONS_HINT = Symbol.for("openclaw.videoGeneration.supportedD type VideoGenerationRuntimeDeps = { getProvider?: typeof getVideoGenerationProvider; listProviders?: typeof listVideoGenerationProviders; - getProviderEnvVars?: typeof getProviderEnvVars; + getProviderEnvVars?: typeof getProviderEnvVarsCore; log?: Pick; }; diff --git a/test/e2e/qa-lab/media/hosted-media-provider-live.ts b/test/e2e/qa-lab/media/hosted-media-provider-live.ts index a357887f4d47..6cca68d2fc7a 100644 --- a/test/e2e/qa-lab/media/hosted-media-provider-live.ts +++ b/test/e2e/qa-lab/media/hosted-media-provider-live.ts @@ -185,8 +185,8 @@ async function collectProviderApiKeysForLiveMedia(provider: string): Promise { - const { getProviderEnvVars } = await import("../../../../src/secrets/provider-env-vars.js"); - return getProviderEnvVars(provider); + const { getProviderEnvVarsCore } = await import("../../../../src/secrets/provider-env-vars.js"); + return getProviderEnvVarsCore(provider); } async function loadShellEnvFallbackForLiveMedia(params: { diff --git a/test/helpers/gateway-codex-harness.ts b/test/helpers/gateway-codex-harness.ts index f1d383c50dd0..f9639f2b4701 100644 --- a/test/helpers/gateway-codex-harness.ts +++ b/test/helpers/gateway-codex-harness.ts @@ -1,5 +1,5 @@ import type { AgentEventPayload } from "../../src/infra/agent-events.js"; -import { listKnownProviderAuthEnvVarNames } from "../../src/secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../../src/secrets/provider-env-vars.js"; // Native live fixture setup and capture shared with its offline boundary regressions. import { createOpenClawTestInstance } from "./openclaw-test-instance.js"; @@ -13,7 +13,9 @@ export function createCodexHarnessLiveInstance( state: { layout: "state-only" }, gatewayToken: token, env: { - ...Object.fromEntries(listKnownProviderAuthEnvVarNames().map((name) => [name, undefined])), + ...Object.fromEntries( + listKnownProviderAuthEnvVarNamesCore().map((name) => [name, undefined]), + ), OPENCLAW_AGENT_RUNTIME: "codex", OPENCLAW_GATEWAY_TOKEN: token, OPENCLAW_ALLOW_SLOW_REPLY_TESTS: "1", diff --git a/test/helpers/sqlite-sessions-transcripts-flip-proof.ts b/test/helpers/sqlite-sessions-transcripts-flip-proof.ts index 2e6f1bdf52fa..af728696bcc0 100644 --- a/test/helpers/sqlite-sessions-transcripts-flip-proof.ts +++ b/test/helpers/sqlite-sessions-transcripts-flip-proof.ts @@ -31,7 +31,7 @@ import { connectGatewayClient, disconnectGatewayClient, } from "../../src/gateway/test-helpers.e2e.js"; -import { listKnownProviderAuthEnvVarNames } from "../../src/secrets/provider-env-vars.js"; +import { listKnownProviderAuthEnvVarNamesCore } from "../../src/secrets/provider-env-vars.js"; import { closeOpenClawAgentDatabaseByPath, closeOpenClawAgentDatabasesForTest, @@ -109,7 +109,9 @@ export async function runSqliteSessionsTranscriptsFlipProof(options: RunOptions const inst = await createOpenClawTestInstance({ name: `sqlite-sessions-transcripts-flip-${randomUUID()}`, env: { - ...Object.fromEntries(listKnownProviderAuthEnvVarNames().map((name) => [name, undefined])), + ...Object.fromEntries( + listKnownProviderAuthEnvVarNamesCore().map((name) => [name, undefined]), + ), ALL_PROXY: undefined, HTTP_PROXY: undefined, HTTPS_PROXY: undefined,