refactor(auth): isolate external oauth overlays

This commit is contained in:
Peter Steinberger
2026-04-06 13:29:46 +01:00
parent 49e3ecfe5e
commit 7e0e2f81e5
12 changed files with 588 additions and 59 deletions

View File

@@ -49,6 +49,7 @@ let resolveProviderDefaultThinkingLevel: typeof import("./provider-runtime.js").
let resolveProviderModernModelRef: typeof import("./provider-runtime.js").resolveProviderModernModelRef;
let resolveProviderReasoningOutputModeWithPlugin: typeof import("./provider-runtime.js").resolveProviderReasoningOutputModeWithPlugin;
let resolveProviderReplayPolicyWithPlugin: typeof import("./provider-runtime.js").resolveProviderReplayPolicyWithPlugin;
let resolveExternalOAuthProfilesWithPlugins: typeof import("./provider-runtime.js").resolveExternalOAuthProfilesWithPlugins;
let resolveProviderSyntheticAuthWithPlugin: typeof import("./provider-runtime.js").resolveProviderSyntheticAuthWithPlugin;
let shouldDeferProviderSyntheticProfileAuthWithPlugin: typeof import("./provider-runtime.js").shouldDeferProviderSyntheticProfileAuthWithPlugin;
let sanitizeProviderReplayHistoryWithPlugin: typeof import("./provider-runtime.js").sanitizeProviderReplayHistoryWithPlugin;
@@ -256,6 +257,7 @@ describe("provider-runtime", () => {
resolveProviderModernModelRef,
resolveProviderReasoningOutputModeWithPlugin,
resolveProviderReplayPolicyWithPlugin,
resolveExternalOAuthProfilesWithPlugins,
resolveProviderSyntheticAuthWithPlugin,
shouldDeferProviderSyntheticProfileAuthWithPlugin,
sanitizeProviderReplayHistoryWithPlugin,
@@ -644,6 +646,23 @@ describe("provider-runtime", () => {
},
createEmbeddingProvider,
resolveSyntheticAuth,
resolveExternalOAuthProfiles: ({ store }) =>
store.profiles["demo:managed"]
? []
: [
{
persistence: "runtime-only",
profileId: "demo:managed",
credential: {
type: "oauth",
provider: DEMO_PROVIDER_ID,
access: "external-access",
refresh: "external-refresh",
expires: Date.now() + 60_000,
managedBy: "demo-cli",
},
},
],
shouldDeferSyntheticProfileAuth,
normalizeResolvedModel: ({ model }) => ({
...model,
@@ -1035,6 +1054,30 @@ describe("provider-runtime", () => {
}),
expected: true,
},
{
actual: () =>
resolveExternalOAuthProfilesWithPlugins({
env: process.env,
context: {
env: process.env,
store: { version: 1, profiles: {} },
},
}),
expected: [
{
persistence: "runtime-only",
profileId: "demo:managed",
credential: {
type: "oauth",
provider: DEMO_PROVIDER_ID,
access: "external-access",
refresh: "external-refresh",
expires: expect.any(Number),
managedBy: "demo-cli",
},
},
],
},
{
actual: () =>
resolveProviderSyntheticAuthWithPlugin({

View File

@@ -16,6 +16,7 @@ import type {
ProviderCacheTtlEligibilityContext,
ProviderCreateEmbeddingProviderContext,
ProviderDeferSyntheticProfileAuthContext,
ProviderExternalOAuthProfile,
ProviderResolveSyntheticAuthContext,
ProviderCreateStreamFnContext,
ProviderDefaultThinkingPolicyContext,
@@ -33,6 +34,7 @@ import type {
ProviderModernModelPolicyContext,
ProviderPrepareExtraParamsContext,
ProviderPrepareDynamicModelContext,
ProviderResolveExternalOAuthProfilesContext,
ProviderPrepareRuntimeAuthContext,
ProviderApplyConfigDefaultsContext,
ProviderResolveConfigApiKeyContext,
@@ -789,6 +791,23 @@ export function resolveProviderSyntheticAuthWithPlugin(params: {
return resolveProviderRuntimePlugin(params)?.resolveSyntheticAuth?.(params.context) ?? undefined;
}
export function resolveExternalOAuthProfilesWithPlugins(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
context: ProviderResolveExternalOAuthProfilesContext;
}): ProviderExternalOAuthProfile[] {
const matches: ProviderExternalOAuthProfile[] = [];
for (const plugin of resolveProviderPluginsForHooks(params)) {
const profiles = plugin.resolveExternalOAuthProfiles?.(params.context);
if (!profiles || profiles.length === 0) {
continue;
}
matches.push(...profiles);
}
return matches;
}
export function shouldDeferProviderSyntheticProfileAuthWithPlugin(params: {
provider: string;
config?: OpenClawConfig;

View File

@@ -1040,6 +1040,20 @@ export type ProviderSyntheticAuthResult = {
mode: Exclude<ModelProviderAuthMode, "aws-sdk">;
};
export type ProviderResolveExternalOAuthProfilesContext = {
config?: OpenClawConfig;
agentDir?: string;
workspaceDir?: string;
env: NodeJS.ProcessEnv;
store: AuthProfileStore;
};
export type ProviderExternalOAuthProfile = {
profileId: string;
credential: OAuthCredential;
persistence?: "runtime-only" | "persisted";
};
export type ProviderDeferSyntheticProfileAuthContext = {
config?: OpenClawConfig;
provider: string;
@@ -1517,6 +1531,20 @@ export type ProviderPlugin = {
resolveSyntheticAuth?: (
ctx: ProviderResolveSyntheticAuthContext,
) => ProviderSyntheticAuthResult | null | undefined;
/**
* Provider-owned external OAuth profile discovery.
*
* Use this when credentials are managed by an external tool and should be
* visible to runtime auth resolution without being written back into
* `auth-profiles.json` by core.
*/
resolveExternalOAuthProfiles?: (
ctx: ProviderResolveExternalOAuthProfilesContext,
) =>
| Array<ProviderExternalOAuthProfile>
| ReadonlyArray<ProviderExternalOAuthProfile>
| null
| undefined;
/**
* Provider-owned precedence rule for stored synthetic auth profiles.
*