diff --git a/src/agents/auth-profiles/store.ts b/src/agents/auth-profiles/store.ts index d6470303003..5dfe20d66b1 100644 --- a/src/agents/auth-profiles/store.ts +++ b/src/agents/auth-profiles/store.ts @@ -1,5 +1,4 @@ import fs from "node:fs"; -import type { OAuthCredentials } from "@mariozechner/pi-ai"; import { resolveOAuthPath } from "../../config/paths.js"; import { withFileLock } from "../../infra/file-lock.js"; import { loadJsonFile, saveJsonFile } from "../../infra/json-file.js"; @@ -11,7 +10,12 @@ import { } from "./constants.js"; import { syncExternalCliCredentials } from "./external-cli-sync.js"; import { ensureAuthStoreFile, resolveAuthStorePath, resolveLegacyAuthStorePath } from "./paths.js"; -import type { AuthProfileCredential, AuthProfileStore, ProfileUsageStats } from "./types.js"; +import type { + AuthProfileCredential, + AuthProfileStore, + OAuthCredentials, + ProfileUsageStats, +} from "./types.js"; type LegacyAuthStore = Record; type CredentialRejectReason = "non_object" | "invalid_type" | "missing_provider"; diff --git a/src/agents/auth-profiles/types.ts b/src/agents/auth-profiles/types.ts index e4b83675e06..6b3fe2bdd43 100644 --- a/src/agents/auth-profiles/types.ts +++ b/src/agents/auth-profiles/types.ts @@ -1,7 +1,19 @@ -import type { OAuthCredentials } from "@mariozechner/pi-ai"; import type { OpenClawConfig } from "../../config/config.js"; import type { SecretRef } from "../../config/types.secrets.js"; +export type OAuthProvider = string; + +export type OAuthCredentials = { + access: string; + refresh: string; + expires: number; + provider?: OAuthProvider; + email?: string; + enterpriseUrl?: string; + projectId?: string; + accountId?: string; +}; + export type ApiKeyCredential = { type: "api_key"; provider: string; diff --git a/src/agents/cli-credentials.ts b/src/agents/cli-credentials.ts index 76cf465508e..436a8829e9e 100644 --- a/src/agents/cli-credentials.ts +++ b/src/agents/cli-credentials.ts @@ -2,10 +2,10 @@ import { execFileSync, execSync } from "node:child_process"; import { createHash } from "node:crypto"; import fs from "node:fs"; import path from "node:path"; -import type { OAuthCredentials, OAuthProvider } from "@mariozechner/pi-ai"; import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveUserPath } from "../utils.js"; +import type { OAuthCredentials, OAuthProvider } from "./auth-profiles/types.js"; const log = createSubsystemLogger("agents/auth-profiles"); diff --git a/src/agents/models-config.providers.normalize.ts b/src/agents/models-config.providers.normalize.ts index 462eb28227a..c3fa2b97987 100644 --- a/src/agents/models-config.providers.normalize.ts +++ b/src/agents/models-config.providers.normalize.ts @@ -1,5 +1,5 @@ import type { OpenClawConfig } from "../config/config.js"; -import { ensureAuthProfileStore } from "./auth-profiles.js"; +import { ensureAuthProfileStore } from "./auth-profiles/store.js"; import { normalizeProviderSpecificConfig, resolveProviderConfigApiKeyResolver, @@ -30,9 +30,17 @@ export function normalizeProviders(params: { return providers; } const env = params.env ?? process.env; - const authStore = ensureAuthProfileStore(params.agentDir, { - allowKeychainPrompt: false, - }); + let authStore: ReturnType | undefined; + const resolveProfileApiKey = (providerKey: string) => { + authStore ??= ensureAuthProfileStore(params.agentDir, { + allowKeychainPrompt: false, + }); + return resolveApiKeyFromProfiles({ + provider: providerKey, + store: authStore, + env, + }); + }; let mutated = false; const next: Record = {}; @@ -54,16 +62,11 @@ export function normalizeProviders(params: { mutated = true; normalizedProvider = { ...normalizedProvider, headers: normalizedHeaders.headers }; } - const profileApiKey = resolveApiKeyFromProfiles({ - provider: normalizedKey, - store: authStore, - env, - }); const providerWithConfiguredApiKey = normalizeConfiguredProviderApiKey({ providerKey: normalizedKey, provider: normalizedProvider, secretDefaults: params.secretDefaults, - profileApiKey, + profileApiKey: undefined, secretRefManagedProviders: params.secretRefManagedProviders, }); if (providerWithConfiguredApiKey !== normalizedProvider) { @@ -86,13 +89,24 @@ export function normalizeProviders(params: { normalizedProvider = providerWithResolvedEnvApiKey; } + const needsProfileApiKey = + Array.isArray(normalizedProvider.models) && + normalizedProvider.models.length > 0 && + !( + (typeof normalizedProvider.apiKey === "string" && normalizedProvider.apiKey.trim()) || + normalizedProvider.apiKey + ); + const profileApiKey = needsProfileApiKey ? resolveProfileApiKey(normalizedKey) : undefined; + const providerApiKeyResolver = needsProfileApiKey + ? resolveProviderConfigApiKeyResolver(normalizedKey) + : undefined; const providerWithApiKey = resolveMissingProviderApiKey({ providerKey: normalizedKey, provider: normalizedProvider, env, profileApiKey, secretRefManagedProviders: params.secretRefManagedProviders, - providerApiKeyResolver: resolveProviderConfigApiKeyResolver(normalizedKey), + providerApiKeyResolver, }); if (providerWithApiKey !== normalizedProvider) { mutated = true; diff --git a/src/agents/models-config.providers.policy.test.ts b/src/agents/models-config.providers.policy.test.ts index d0d626e3fda..a0c71953f54 100644 --- a/src/agents/models-config.providers.policy.test.ts +++ b/src/agents/models-config.providers.policy.test.ts @@ -1,23 +1,7 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it } from "vitest"; describe("models-config.providers.policy", () => { - beforeEach(() => { - vi.resetModules(); - vi.restoreAllMocks(); - }); - - it("resolves config apiKey markers through provider runtime hooks", async () => { - const providerRuntime = await import("../plugins/provider-runtime.js"); - vi.spyOn(providerRuntime, "resolveProviderRuntimePlugin").mockReturnValue({ - id: "amazon-bedrock", - label: "Amazon Bedrock", - auth: [], - resolveConfigApiKey: () => "AWS_PROFILE", - }); - const resolveProviderConfigApiKeyWithPluginSpy = vi - .spyOn(providerRuntime, "resolveProviderConfigApiKeyWithPlugin") - .mockReturnValue("AWS_PROFILE"); - + it("resolves config apiKey markers through the local bedrock helper", async () => { const { resolveProviderConfigApiKeyResolver } = await import("./models-config.providers.policy.js"); const env = { @@ -27,13 +11,5 @@ describe("models-config.providers.policy", () => { expect(resolver).toBeTypeOf("function"); expect(resolver?.(env)).toBe("AWS_PROFILE"); - expect(resolveProviderConfigApiKeyWithPluginSpy).toHaveBeenCalledWith({ - provider: "amazon-bedrock", - env, - context: { - provider: "amazon-bedrock", - env, - }, - }); }); }); diff --git a/src/agents/models-config.providers.policy.ts b/src/agents/models-config.providers.policy.ts index d4dc601c272..90445f75375 100644 --- a/src/agents/models-config.providers.policy.ts +++ b/src/agents/models-config.providers.policy.ts @@ -1,18 +1,25 @@ +import { resolveBedrockConfigApiKey } from "../plugin-sdk/amazon-bedrock.js"; import { resolveAnthropicVertexConfigApiKey } from "../plugin-sdk/anthropic-vertex.js"; -import { - applyProviderNativeStreamingUsageCompatWithPlugin, - normalizeProviderConfigWithPlugin, - resolveProviderConfigApiKeyWithPlugin, - resolveProviderRuntimePlugin, -} from "../plugins/provider-runtime.js"; +import { normalizeGoogleProviderConfig } from "../plugin-sdk/google.js"; +import { applyModelStudioNativeStreamingUsageCompat } from "../plugin-sdk/modelstudio.js"; +import { applyMoonshotNativeStreamingUsageCompat } from "../plugin-sdk/moonshot.js"; import type { ProviderConfig } from "./models-config.providers.secrets.js"; const PROVIDER_CONFIG_API_KEY_RESOLVERS: Partial< Record string | undefined> > = { + "amazon-bedrock": resolveBedrockConfigApiKey, "anthropic-vertex": resolveAnthropicVertexConfigApiKey, }; +function shouldNormalizeGoogleProviderConfigLocally(providerKey: string): boolean { + return ( + providerKey === "google" || + providerKey === "google-antigravity" || + providerKey === "google-vertex" + ); +} + export function applyNativeStreamingUsageCompat( providers: Record, ): Record { @@ -21,13 +28,11 @@ export function applyNativeStreamingUsageCompat( for (const [providerKey, provider] of Object.entries(providers)) { const nextProvider = - applyProviderNativeStreamingUsageCompatWithPlugin({ - provider: providerKey, - context: { - provider: providerKey, - providerConfig: provider, - }, - }) ?? provider; + providerKey === "modelstudio" + ? applyModelStudioNativeStreamingUsageCompat(provider) + : providerKey === "moonshot" + ? applyMoonshotNativeStreamingUsageCompat(provider) + : provider; nextProviders[providerKey] = nextProvider; changed ||= nextProvider !== provider; } @@ -39,32 +44,15 @@ export function normalizeProviderSpecificConfig( providerKey: string, provider: ProviderConfig, ): ProviderConfig { - return ( - normalizeProviderConfigWithPlugin({ - provider: providerKey, - context: { - provider: providerKey, - providerConfig: provider, - }, - }) ?? provider - ); + if (shouldNormalizeGoogleProviderConfigLocally(providerKey)) { + return normalizeGoogleProviderConfig(providerKey, provider); + } + return provider; } export function resolveProviderConfigApiKeyResolver( providerKey: string, ): ((env: NodeJS.ProcessEnv) => string | undefined) | undefined { const fallback = PROVIDER_CONFIG_API_KEY_RESOLVERS[providerKey]; - const plugin = resolveProviderRuntimePlugin({ provider: providerKey }); - if (!plugin?.resolveConfigApiKey && !fallback) { - return undefined; - } - return (env: NodeJS.ProcessEnv) => - resolveProviderConfigApiKeyWithPlugin({ - provider: providerKey, - env, - context: { - provider: providerKey, - env, - }, - }) ?? fallback?.(env); + return fallback; } diff --git a/src/agents/models-config.providers.secrets.ts b/src/agents/models-config.providers.secrets.ts index 3d09484aa34..cbda53ff88e 100644 --- a/src/agents/models-config.providers.secrets.ts +++ b/src/agents/models-config.providers.secrets.ts @@ -1,14 +1,16 @@ import type { OpenClawConfig } from "../config/config.js"; import { coerceSecretRef, resolveSecretInputRef } from "../config/types.secrets.js"; import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; -import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles.js"; +import { listProfilesForProvider } from "./auth-profiles/profiles.js"; +import { ensureAuthProfileStore } from "./auth-profiles/store.js"; +import { resolveEnvApiKey } from "./model-auth-env.js"; import { isNonSecretApiKeyMarker, resolveEnvSecretRefHeaderValueMarker, resolveNonEnvSecretRefApiKeyMarker, resolveNonEnvSecretRefHeaderValueMarker, } from "./model-auth-markers.js"; -import { resolveAwsSdkEnvVarName, resolveEnvApiKey } from "./model-auth.js"; +import { resolveAwsSdkEnvVarName } from "./model-auth-runtime-shared.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string];