import { ModelConfigError } from "../protocol/errors.js";
export type CredentialEnv = Record<string, string | undefined>;
const ENV_REFERENCE_PATTERN = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
* Resolve a provider apiKey from raw config.
*
* Whitespace handling: `value` and any `${VAR}`-resolved env value are both
* trimmed before use. A stray space inside a YAML literal (e.g.
* `apiKey: " sk-..."`) or an env variable that ships a trailing newline
* would otherwise be pasted verbatim into `Authorization: Bearer sk-...`,
* which most providers reject as `invalid_token` / `无效的令牌`. Trimming at
* the source guarantees every downstream caller (streamModel, AlwaysOn,
* Cron, plugins) sees the cleaned value.
*/
export function resolveApiKey(value: unknown, env: CredentialEnv = process.env): string {
if (typeof value !== "string") {
throw new ModelConfigError("missing_api_key", "Provider apiKey must be a non-empty string.");
}
const trimmed = value.trim();
if (trimmed.length === 0) {
throw new ModelConfigError("missing_api_key", "Provider apiKey must be a non-empty string.");
}
const match = ENV_REFERENCE_PATTERN.exec(trimmed);
if (!match) {
return trimmed;
}
const envName = match[1];
const rawResolved = env[envName];
const resolved = typeof rawResolved === "string" ? rawResolved.trim() : "";
if (!resolved) {
throw new ModelConfigError("missing_api_key", `Environment variable ${envName} is not set.`, {
envName,
});
}
return resolved;
}