

























@@ -0,0 +1,210 @@
1+import { splitTrailingAuthProfile } from "../agents/model-ref-profile.js";
2+import { collectConfiguredModelRefs } from "../config/model-refs.js";
3+import type { AuthProfileConfig } from "../config/types.auth.js";
4+import type { OpenClawConfig } from "../config/types.openclaw.js";
5+import {
6+normalizeLowercaseStringOrEmpty,
7+normalizeOptionalString,
8+} from "../shared/string-coerce.js";
9+import { isRecord } from "../utils.js";
10+11+const AUTH_PROFILE_MODES = new Set<AuthProfileConfig["mode"]>(["api_key", "oauth", "token"]);
12+13+export type AuthProfileConfigProtectionResult = {
14+config: OpenClawConfig;
15+repairs: string[];
16+warnings: string[];
17+};
18+19+function normalizeProviderId(value: unknown): string {
20+return normalizeLowercaseStringOrEmpty(value);
21+}
22+23+function normalizeProfileId(value: unknown): string | null {
24+return normalizeOptionalString(value) ?? null;
25+}
26+27+function normalizeMode(value: unknown): AuthProfileConfig["mode"] | null {
28+return typeof value === "string" && AUTH_PROFILE_MODES.has(value as AuthProfileConfig["mode"])
29+ ? (value as AuthProfileConfig["mode"])
30+ : null;
31+}
32+33+function extractProviderFromModelRef(value: string): string | null {
34+const { model } = splitTrailingAuthProfile(value);
35+const slash = model.indexOf("/");
36+if (slash <= 0) {
37+return null;
38+}
39+return normalizeProviderId(model.slice(0, slash)) || null;
40+}
41+42+function extractProviderFromProfileId(profileId: string): string | null {
43+const colon = profileId.indexOf(":");
44+if (colon <= 0) {
45+return null;
46+}
47+return normalizeProviderId(profileId.slice(0, colon)) || null;
48+}
49+50+function collectActiveAuthHints(config: OpenClawConfig): {
51+activeProviders: Set<string>;
52+explicitProfileIds: Set<string>;
53+explicitProfileProviders: Map<string, Set<string>>;
54+} {
55+const activeProviders = new Set<string>();
56+const explicitProfileIds = new Set<string>();
57+const explicitProfileProviders = new Map<string, Set<string>>();
58+59+const models = isRecord(config.models) ? config.models : {};
60+const providers = isRecord(models.providers) ? models.providers : {};
61+for (const providerId of Object.keys(providers)) {
62+const normalized = normalizeProviderId(providerId);
63+if (normalized) {
64+activeProviders.add(normalized);
65+}
66+}
67+68+for (const { value } of collectConfiguredModelRefs(config)) {
69+const { profile } = splitTrailingAuthProfile(value);
70+const provider = extractProviderFromModelRef(value);
71+if (profile) {
72+explicitProfileIds.add(profile);
73+if (provider) {
74+const providers = explicitProfileProviders.get(profile) ?? new Set<string>();
75+providers.add(provider);
76+explicitProfileProviders.set(profile, providers);
77+}
78+}
79+if (provider) {
80+activeProviders.add(provider);
81+}
82+}
83+84+const auth = isRecord(config.auth) ? config.auth : {};
85+const order = isRecord(auth.order) ? auth.order : {};
86+for (const [providerId, profileIds] of Object.entries(order)) {
87+const provider = normalizeProviderId(providerId);
88+if (!provider || !activeProviders.has(provider) || !Array.isArray(profileIds)) {
89+continue;
90+}
91+for (const profileId of profileIds) {
92+const normalized = normalizeProfileId(profileId);
93+if (normalized) {
94+explicitProfileIds.add(normalized);
95+}
96+}
97+}
98+99+return { activeProviders, explicitProfileIds, explicitProfileProviders };
100+}
101+102+function isValidProfileMetadata(value: unknown): value is AuthProfileConfig {
103+if (!isRecord(value)) {
104+return false;
105+}
106+return normalizeProviderId(value.provider) !== "" && normalizeMode(value.mode) !== null;
107+}
108+109+function buildProfileMetadata(params: {
110+profileId: string;
111+before: unknown;
112+after: unknown;
113+providerHint?: string;
114+}): AuthProfileConfig | null {
115+const before = isRecord(params.before) ? params.before : {};
116+const after = isRecord(params.after) ? params.after : {};
117+const provider =
118+normalizeProviderId(after.provider) ||
119+normalizeProviderId(before.provider) ||
120+extractProviderFromProfileId(params.profileId) ||
121+normalizeProviderId(params.providerHint);
122+if (!provider) {
123+return null;
124+}
125+const mode = normalizeMode(after.mode) ?? normalizeMode(before.mode) ?? "api_key";
126+const repaired: AuthProfileConfig = { provider, mode };
127+const email = normalizeOptionalString(after.email) ?? normalizeOptionalString(before.email);
128+const displayName =
129+normalizeOptionalString(after.displayName) ?? normalizeOptionalString(before.displayName);
130+if (email) {
131+repaired.email = email;
132+}
133+if (displayName) {
134+repaired.displayName = displayName;
135+}
136+return repaired;
137+}
138+139+function ensureAuthProfiles(config: OpenClawConfig): Record<string, AuthProfileConfig> {
140+const root = config as Record<string, unknown>;
141+const auth: Record<string, unknown> = isRecord(root.auth) ? root.auth : {};
142+if (root.auth !== auth) {
143+root.auth = auth;
144+}
145+if (!isRecord(auth.profiles)) {
146+auth.profiles = {};
147+}
148+return auth.profiles as Record<string, AuthProfileConfig>;
149+}
150+151+export function protectActiveAuthProfileConfig(params: {
152+before: OpenClawConfig;
153+after: OpenClawConfig;
154+}): AuthProfileConfigProtectionResult {
155+const { activeProviders, explicitProfileIds, explicitProfileProviders } = collectActiveAuthHints(
156+params.before,
157+);
158+const beforeAuth = isRecord(params.before.auth) ? params.before.auth : {};
159+const beforeProfiles = isRecord(beforeAuth.profiles) ? beforeAuth.profiles : {};
160+if (Object.keys(beforeProfiles).length === 0) {
161+return { config: params.after, repairs: [], warnings: [] };
162+}
163+164+const config = structuredClone(params.after);
165+const afterAuth = isRecord(config.auth) ? config.auth : {};
166+const afterProfiles = isRecord(afterAuth.profiles) ? afterAuth.profiles : {};
167+const repairs: string[] = [];
168+const warnings: string[] = [];
169+170+for (const [profileId, beforeProfile] of Object.entries(beforeProfiles)) {
171+const afterProfile = afterProfiles[profileId];
172+const afterProfileRecord = isRecord(afterProfile) ? afterProfile : null;
173+const beforeProfileRecord = isRecord(beforeProfile) ? beforeProfile : null;
174+if (isValidProfileMetadata(afterProfile)) {
175+continue;
176+}
177+const provider =
178+normalizeProviderId(afterProfileRecord?.provider) ||
179+normalizeProviderId(beforeProfileRecord?.provider) ||
180+extractProviderFromProfileId(profileId);
181+const protectsActiveProvider = !!provider && activeProviders.has(provider);
182+const protectsExplicitProfile = explicitProfileIds.has(profileId);
183+if (!protectsActiveProvider && !protectsExplicitProfile) {
184+continue;
185+}
186+187+const repaired = buildProfileMetadata({
188+ profileId,
189+before: beforeProfile,
190+after: afterProfile,
191+providerHint:
192+explicitProfileProviders.get(profileId)?.size === 1
193+ ? [...(explicitProfileProviders.get(profileId) ?? [])][0]
194+ : undefined,
195+});
196+if (!repaired) {
197+warnings.push(
198+`auth.profiles.${profileId}: active auth profile metadata could not be inferred; repair manually before running doctor --fix.`,
199+);
200+continue;
201+}
202+const profiles = ensureAuthProfiles(config);
203+profiles[profileId] = repaired;
204+repairs.push(
205+`Repaired auth.profiles.${profileId} metadata for active ${repaired.provider} auth.`,
206+);
207+}
208+209+return { config, repairs, warnings };
210+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。