|
4 | 4 | DEFAULT_COPILOT_API_BASE_URL, |
5 | 5 | resolveCopilotApiToken, |
6 | 6 | } from "../providers/github-copilot-token.js"; |
| 7 | +import { isRecord } from "../utils.js"; |
7 | 8 | import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; |
8 | 9 | import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles.js"; |
9 | 10 | import { discoverBedrockModels } from "./bedrock-discovery.js"; |
@@ -70,6 +71,11 @@ export { resolveOllamaApiBase } from "./models-config.providers.discovery.js"; |
70 | 71 |
|
71 | 72 | type ModelsConfig = NonNullable<OpenClawConfig["models"]>; |
72 | 73 | export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string]; |
| 74 | +type SecretDefaults = { |
| 75 | + env?: string; |
| 76 | + file?: string; |
| 77 | + exec?: string; |
| 78 | +}; |
73 | 79 |
|
74 | 80 | const ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/; |
75 | 81 |
|
@@ -97,13 +103,7 @@ function resolveAwsSdkApiKeyVarName(env: NodeJS.ProcessEnv = process.env): strin |
97 | 103 |
|
98 | 104 | function normalizeHeaderValues(params: { |
99 | 105 | headers: ProviderConfig["headers"] | undefined; |
100 | | - secretDefaults: |
101 | | - | { |
102 | | - env?: string; |
103 | | - file?: string; |
104 | | - exec?: string; |
105 | | - } |
106 | | - | undefined; |
| 106 | + secretDefaults: SecretDefaults | undefined; |
107 | 107 | }): { headers: ProviderConfig["headers"] | undefined; mutated: boolean } { |
108 | 108 | const { headers } = params; |
109 | 109 | if (!headers) { |
@@ -276,15 +276,155 @@ function normalizeAntigravityProvider(provider: ProviderConfig): ProviderConfig |
276 | 276 | return normalizeProviderModels(provider, normalizeAntigravityModelId); |
277 | 277 | } |
278 | 278 |
|
| 279 | +function normalizeSourceProviderLookup( |
| 280 | + providers: ModelsConfig["providers"] | undefined, |
| 281 | +): Record<string, ProviderConfig> { |
| 282 | + if (!providers) { |
| 283 | + return {}; |
| 284 | + } |
| 285 | + const out: Record<string, ProviderConfig> = {}; |
| 286 | + for (const [key, provider] of Object.entries(providers)) { |
| 287 | + const normalizedKey = key.trim(); |
| 288 | + if (!normalizedKey || !isRecord(provider)) { |
| 289 | + continue; |
| 290 | + } |
| 291 | + out[normalizedKey] = provider; |
| 292 | + } |
| 293 | + return out; |
| 294 | +} |
| 295 | + |
| 296 | +function resolveSourceManagedApiKeyMarker(params: { |
| 297 | + sourceProvider: ProviderConfig | undefined; |
| 298 | + sourceSecretDefaults: SecretDefaults | undefined; |
| 299 | +}): string | undefined { |
| 300 | + const sourceApiKeyRef = resolveSecretInputRef({ |
| 301 | + value: params.sourceProvider?.apiKey, |
| 302 | + defaults: params.sourceSecretDefaults, |
| 303 | + }).ref; |
| 304 | + if (!sourceApiKeyRef || !sourceApiKeyRef.id.trim()) { |
| 305 | + return undefined; |
| 306 | + } |
| 307 | + return sourceApiKeyRef.source === "env" |
| 308 | + ? sourceApiKeyRef.id.trim() |
| 309 | + : resolveNonEnvSecretRefApiKeyMarker(sourceApiKeyRef.source); |
| 310 | +} |
| 311 | + |
| 312 | +function resolveSourceManagedHeaderMarkers(params: { |
| 313 | + sourceProvider: ProviderConfig | undefined; |
| 314 | + sourceSecretDefaults: SecretDefaults | undefined; |
| 315 | +}): Record<string, string> { |
| 316 | + const sourceHeaders = isRecord(params.sourceProvider?.headers) |
| 317 | + ? (params.sourceProvider.headers as Record<string, unknown>) |
| 318 | + : undefined; |
| 319 | + if (!sourceHeaders) { |
| 320 | + return {}; |
| 321 | + } |
| 322 | + const markers: Record<string, string> = {}; |
| 323 | + for (const [headerName, headerValue] of Object.entries(sourceHeaders)) { |
| 324 | + const sourceHeaderRef = resolveSecretInputRef({ |
| 325 | + value: headerValue, |
| 326 | + defaults: params.sourceSecretDefaults, |
| 327 | + }).ref; |
| 328 | + if (!sourceHeaderRef || !sourceHeaderRef.id.trim()) { |
| 329 | + continue; |
| 330 | + } |
| 331 | + markers[headerName] = |
| 332 | + sourceHeaderRef.source === "env" |
| 333 | + ? resolveEnvSecretRefHeaderValueMarker(sourceHeaderRef.id) |
| 334 | + : resolveNonEnvSecretRefHeaderValueMarker(sourceHeaderRef.source); |
| 335 | + } |
| 336 | + return markers; |
| 337 | +} |
| 338 | + |
| 339 | +export function enforceSourceManagedProviderSecrets(params: { |
| 340 | + providers: ModelsConfig["providers"]; |
| 341 | + sourceProviders: ModelsConfig["providers"] | undefined; |
| 342 | + sourceSecretDefaults?: SecretDefaults; |
| 343 | + secretRefManagedProviders?: Set<string>; |
| 344 | +}): ModelsConfig["providers"] { |
| 345 | + const { providers } = params; |
| 346 | + if (!providers) { |
| 347 | + return providers; |
| 348 | + } |
| 349 | + const sourceProvidersByKey = normalizeSourceProviderLookup(params.sourceProviders); |
| 350 | + if (Object.keys(sourceProvidersByKey).length === 0) { |
| 351 | + return providers; |
| 352 | + } |
| 353 | + |
| 354 | + let nextProviders: Record<string, ProviderConfig> | null = null; |
| 355 | + for (const [providerKey, provider] of Object.entries(providers)) { |
| 356 | + if (!isRecord(provider)) { |
| 357 | + continue; |
| 358 | + } |
| 359 | + const sourceProvider = sourceProvidersByKey[providerKey.trim()]; |
| 360 | + if (!sourceProvider) { |
| 361 | + continue; |
| 362 | + } |
| 363 | + let nextProvider = provider; |
| 364 | + let providerMutated = false; |
| 365 | + |
| 366 | + const sourceApiKeyMarker = resolveSourceManagedApiKeyMarker({ |
| 367 | + sourceProvider, |
| 368 | + sourceSecretDefaults: params.sourceSecretDefaults, |
| 369 | + }); |
| 370 | + if (sourceApiKeyMarker) { |
| 371 | + params.secretRefManagedProviders?.add(providerKey.trim()); |
| 372 | + if (nextProvider.apiKey !== sourceApiKeyMarker) { |
| 373 | + providerMutated = true; |
| 374 | + nextProvider = { |
| 375 | + ...nextProvider, |
| 376 | + apiKey: sourceApiKeyMarker, |
| 377 | + }; |
| 378 | + } |
| 379 | + } |
| 380 | + |
| 381 | + const sourceHeaderMarkers = resolveSourceManagedHeaderMarkers({ |
| 382 | + sourceProvider, |
| 383 | + sourceSecretDefaults: params.sourceSecretDefaults, |
| 384 | + }); |
| 385 | + if (Object.keys(sourceHeaderMarkers).length > 0) { |
| 386 | + const currentHeaders = isRecord(nextProvider.headers) |
| 387 | + ? (nextProvider.headers as Record<string, unknown>) |
| 388 | + : undefined; |
| 389 | + const nextHeaders = { |
| 390 | + ...(currentHeaders as Record<string, NonNullable<ProviderConfig["headers"]>[string]>), |
| 391 | + }; |
| 392 | + let headersMutated = !currentHeaders; |
| 393 | + for (const [headerName, marker] of Object.entries(sourceHeaderMarkers)) { |
| 394 | + if (nextHeaders[headerName] === marker) { |
| 395 | + continue; |
| 396 | + } |
| 397 | + headersMutated = true; |
| 398 | + nextHeaders[headerName] = marker; |
| 399 | + } |
| 400 | + if (headersMutated) { |
| 401 | + providerMutated = true; |
| 402 | + nextProvider = { |
| 403 | + ...nextProvider, |
| 404 | + headers: nextHeaders, |
| 405 | + }; |
| 406 | + } |
| 407 | + } |
| 408 | + |
| 409 | + if (!providerMutated) { |
| 410 | + continue; |
| 411 | + } |
| 412 | + if (!nextProviders) { |
| 413 | + nextProviders = { ...providers }; |
| 414 | + } |
| 415 | + nextProviders[providerKey] = nextProvider; |
| 416 | + } |
| 417 | + |
| 418 | + return nextProviders ?? providers; |
| 419 | +} |
| 420 | + |
279 | 421 | export function normalizeProviders(params: { |
280 | 422 | providers: ModelsConfig["providers"]; |
281 | 423 | agentDir: string; |
282 | 424 | env?: NodeJS.ProcessEnv; |
283 | | - secretDefaults?: { |
284 | | - env?: string; |
285 | | - file?: string; |
286 | | - exec?: string; |
287 | | - }; |
| 425 | + secretDefaults?: SecretDefaults; |
| 426 | + sourceProviders?: ModelsConfig["providers"]; |
| 427 | + sourceSecretDefaults?: SecretDefaults; |
288 | 428 | secretRefManagedProviders?: Set<string>; |
289 | 429 | }): ModelsConfig["providers"] { |
290 | 430 | const { providers } = params; |
@@ -434,7 +574,13 @@ export function normalizeProviders(params: { |
434 | 574 | next[normalizedKey] = normalizedProvider; |
435 | 575 | } |
436 | 576 |
|
437 | | - return mutated ? next : providers; |
| 577 | + const normalizedProviders = mutated ? next : providers; |
| 578 | + return enforceSourceManagedProviderSecrets({ |
| 579 | + providers: normalizedProviders, |
| 580 | + sourceProviders: params.sourceProviders, |
| 581 | + sourceSecretDefaults: params.sourceSecretDefaults, |
| 582 | + secretRefManagedProviders: params.secretRefManagedProviders, |
| 583 | + }); |
438 | 584 | } |
439 | 585 |
|
440 | 586 | type ImplicitProviderParams = { |
|
0 commit comments