


























11import { hashRuntimeConfigValue } from "../config/runtime-snapshot.js";
22import type { OpenClawConfig } from "../config/types.openclaw.js";
3+import {
4+listAgentIds,
5+resolveAgentDir,
6+resolveAgentWorkspaceDir,
7+resolveDefaultAgentId,
8+} from "./agent-scope-config.js";
39import {
410externalCliDiscoveryForProviderAuth,
511externalCliDiscoveryForProviders,
@@ -20,24 +26,43 @@ import { resolveDefaultAgentWorkspaceDir } from "./workspace.js";
2026// discovery and external-CLI probing on the hot path.
21272228type PreparedProviderAuthState = {
29+agentId: string;
2330configFingerprint: string;
24-workspaceDir: string;
25-preparedAtMs: number;
2631providers: ReadonlyMap<string, boolean>;
2732};
283329-const PREPARED_PROVIDER_AUTH_STATE_TTL_MS = 10_000;
30-let currentProviderAuthState: PreparedProviderAuthState | null = null;
34+// One entry per configured agent, keyed by agentId. Populated by
35+// warmCurrentProviderAuthState at gateway startup / on reload; consulted by
36+// hasAuthForModelProvider on every model-listing call.
37+let currentProviderAuthStates: ReadonlyMap<string, PreparedProviderAuthState> | null = null;
3138const configFingerprintCache = new WeakMap<OpenClawConfig, string>();
3239// Generation counter guards against an in-flight warm publishing stale
3340// state after a subsequent warm or clear has invalidated it.
3441let currentProviderAuthStateGeneration = 0;
35423643export function clearCurrentProviderAuthState(): void {
37-currentProviderAuthState = null;
44+currentProviderAuthStates = null;
3845currentProviderAuthStateGeneration += 1;
3946}
404748+function resolvePreparedStateForCaller(params: {
49+states: ReadonlyMap<string, PreparedProviderAuthState> | null;
50+cfg: OpenClawConfig | undefined;
51+callerAgentId: string | undefined;
52+}): PreparedProviderAuthState | null {
53+if (!params.states) {
54+return null;
55+}
56+if (params.callerAgentId !== undefined) {
57+return params.states.get(params.callerAgentId) ?? null;
58+}
59+// Caller didn't pass agentId: treat as a query against the default agent.
60+if (!params.cfg) {
61+return null;
62+}
63+return params.states.get(resolveDefaultAgentId(params.cfg)) ?? null;
64+}
65+4166function resolveProviderAuthConfigFingerprint(cfg: OpenClawConfig | undefined): string | null {
4267if (!cfg) {
4368return null;
@@ -55,33 +80,41 @@ export function hasAuthForModelProvider(params: {
5580provider: string;
5681cfg?: OpenClawConfig;
5782workspaceDir?: string;
58-agentDir?: string;
83+agentId?: string;
5984env?: NodeJS.ProcessEnv;
6085store?: AuthProfileStore;
6186allowPluginSyntheticAuth?: boolean;
6287discoverExternalCliAuth?: boolean;
6388}): boolean {
6489const provider = normalizeProviderId(params.provider);
65-// The prepared map is built by warmCurrentProviderAuthState with broad
66-// auth discovery (external CLI + plugin synthetic auth enabled) and the
67-// default-agent workspace dir. Only consult it when the caller's full
68-// auth context matches; otherwise fall through to compute so callers
69-// that narrow the scope — e.g. gateway `models.list` with
70-// `runtimeAuthDiscovery: false`, or per-agent picker calls that pass a
71-// non-default workspaceDir — get the answer they asked for.
72-const preparedState = currentProviderAuthState;
90+// The prepared map is built by warmCurrentProviderAuthState — one entry per
91+// configured agent, keyed by agentId. Only consult it when the caller's
92+// full auth context matches the warmed scope; otherwise fall through to
93+// compute so callers that narrow the scope — e.g. gateway `models.list`
94+// with `runtimeAuthDiscovery: false`, or callers with a non-warmed
95+// workspaceDir — get the answer they asked for.
96+const preparedStates = currentProviderAuthStates;
7397const workspaceDir = params.workspaceDir ?? resolveDefaultAgentWorkspaceDir();
7498const configFingerprint = resolveProviderAuthConfigFingerprint(params.cfg);
75-const preparedStateFresh =
76-preparedState !== null &&
77-Date.now() - preparedState.preparedAtMs <= PREPARED_PROVIDER_AUTH_STATE_TTL_MS;
99+const preparedState = resolvePreparedStateForCaller({
100+states: preparedStates,
101+cfg: params.cfg,
102+callerAgentId: params.agentId,
103+});
104+// workspaceDir is a pure function of (cfg, agentId), so we recompute the
105+// warmer's expected value at read time rather than storing it. Caller can
106+// still override workspaceDir explicitly — that forces a mismatch and
107+// falls through to the compute path.
108+const expectedWorkspaceDir =
109+preparedState !== null && params.cfg
110+ ? resolveAgentWorkspaceDir(params.cfg, preparedState.agentId)
111+ : null;
78112const matchesWarmedScope =
79-preparedStateFresh &&
113+preparedState !== null &&
80114configFingerprint === preparedState.configFingerprint &&
81-workspaceDir === preparedState.workspaceDir &&
115+workspaceDir === expectedWorkspaceDir &&
82116params.discoverExternalCliAuth !== false &&
83117params.allowPluginSyntheticAuth !== false &&
84-params.agentDir === undefined &&
85118params.env === undefined &&
86119params.store === undefined;
87120if (matchesWarmedScope) {
@@ -101,13 +134,15 @@ export function hasAuthForModelProvider(params: {
101134) {
102135return true;
103136}
137+const slowPathAgentDir =
138+params.agentId && params.cfg ? resolveAgentDir(params.cfg, params.agentId) : undefined;
104139const store =
105140params.store ??
106141(params.discoverExternalCliAuth === false
107- ? ensureAuthProfileStoreWithoutExternalProfiles(params.agentDir, {
142+ ? ensureAuthProfileStoreWithoutExternalProfiles(slowPathAgentDir, {
108143allowKeychainPrompt: false,
109144})
110- : ensureAuthProfileStore(params.agentDir, {
145+ : ensureAuthProfileStore(slowPathAgentDir, {
111146externalCli: externalCliDiscoveryForProviderAuth({ cfg: params.cfg, provider }),
112147}));
113148if (listProfilesForProvider(store, provider).length > 0) {
@@ -119,7 +154,7 @@ export function hasAuthForModelProvider(params: {
119154export function createProviderAuthChecker(params: {
120155cfg?: OpenClawConfig;
121156workspaceDir?: string;
122-agentDir?: string;
157+agentId?: string;
123158env?: NodeJS.ProcessEnv;
124159allowPluginSyntheticAuth?: boolean;
125160discoverExternalCliAuth?: boolean;
@@ -135,7 +170,7 @@ export function createProviderAuthChecker(params: {
135170provider: key,
136171cfg: params.cfg,
137172workspaceDir: params.workspaceDir,
138-agentDir: params.agentDir,
173+agentId: params.agentId,
139174env: params.env,
140175allowPluginSyntheticAuth: params.allowPluginSyntheticAuth,
141176discoverExternalCliAuth: params.discoverExternalCliAuth,
@@ -155,35 +190,45 @@ export async function warmCurrentProviderAuthState(cfg: OpenClawConfig): Promise
155190for (const entry of catalog) {
156191providers.add(normalizeProviderId(entry.provider));
157192}
158-const workspaceDir = resolveDefaultAgentWorkspaceDir();
159-// One AuthProfileStore scoped to every candidate provider; without this the
160-// per-provider externalCli discovery rebuilds the store ~N times.
161-const store = ensureAuthProfileStore(undefined, {
162-config: cfg,
163-externalCli: externalCliDiscoveryForProviders({
164- cfg,
165-providers: [...providers],
166-}),
167-});
168-const state = new Map<string, boolean>();
169-for (const provider of providers) {
170-const value = hasAuthForModelProvider({
171- provider,
172- cfg,
173- workspaceDir,
174- store,
193+const providerList = [...providers];
194+const configFingerprint = resolveProviderAuthConfigFingerprint(cfg) ?? "";
195+const states = new Map<string, PreparedProviderAuthState>();
196+// Warm one entry per configured agent so callers hit the prepared map for
197+// any agentId. The catalog above is shared across agents; the per-agent
198+// work is the auth-discovery sweep against that agent's store.
199+for (const agentId of listAgentIds(cfg)) {
200+const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
201+const agentDir = resolveAgentDir(cfg, agentId);
202+// One AuthProfileStore scoped to every candidate provider; without this
203+// the per-provider externalCli discovery rebuilds the store ~N times.
204+const store = ensureAuthProfileStore(agentDir, {
205+config: cfg,
206+externalCli: externalCliDiscoveryForProviders({
207+ cfg,
208+providers: providerList,
209+}),
210+});
211+const state = new Map<string, boolean>();
212+for (const provider of providers) {
213+const value = hasAuthForModelProvider({
214+ provider,
215+ cfg,
216+ workspaceDir,
217+ agentId,
218+ store,
219+});
220+state.set(provider, value);
221+}
222+states.set(agentId, {
223+ agentId,
224+ configFingerprint,
225+providers: state,
175226});
176-state.set(provider, value);
177227}
178228if (ownGeneration !== currentProviderAuthStateGeneration) {
179229// A newer warm or clear ran while we were building; skip publication so
180230// the newer answer wins.
181231return;
182232}
183-currentProviderAuthState = {
184-configFingerprint: resolveProviderAuthConfigFingerprint(cfg) ?? "",
185- workspaceDir,
186-preparedAtMs: Date.now(),
187-providers: state,
188-};
233+currentProviderAuthStates = states;
189234}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。