openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
253 lines (252 loc) • 11.8 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { r as resolveOsHomeRelativePath } from "./home-dir-Dc6nN6HB.mjs";
import { c as isRecord, t as asNonArrayRecord } from "./record-coerce-TIsquoVR.mjs";
import "./types.secrets-DsPRn9Dz.mjs";
import "./provider-env-vars-DJIIV_DA.mjs";
import "./provider-openai-chatgpt-auth-fIvYFuQh.mjs";
import "./persisted-BVyWmvfF.mjs";
import "./credential-state-WXO5HScp.mjs";
import { t as loadJsonFileThroughSymlink } from "./json-file-5C1aYJVp.mjs";
import "./cli-credentials-iMPJTQjZ.mjs";
import "./profiles-zTWlOxGK.mjs";
import "./order-BJ9-s7yV.mjs";
import "./model-auth-markers-BxCB9MeR.mjs";
import "./model-auth-env-RKKDA1-c.mjs";
import "./store-runtime-DZk7O5wt.mjs";
import "./repair-D-SnIpjs.mjs";
import "./models-config.providers.secret-helpers-BA_B-Pu1.mjs";
import "./provider-auth-availability-GiSm5QUa.mjs";
import "./copilot-dynamic-headers-C8eJEO7a.mjs";
import "./provider-auth-helpers-CMstK74G.mjs";
import "./provider-env-vars-BE6j6HoE.mjs";
import "./provider-auth-write-compat-BaNX2G-6.mjs";
import "./provider-auth-input-CpNkpIyx.mjs";
import "./provider-api-key-auth-CoMirT4h.mjs";
import "./provider-auth-result-BIBiHM9E.mjs";
import fs from "node:fs";
import path from "node:path";
import { userInfo } from "node:os";
import { execSync } from "node:child_process";
import { createHash } from "node:crypto";
//#region src/plugin-sdk/github-copilot-domain.ts
const DEFAULT_GITHUB_COPILOT_DOMAIN = "github.com";
const GHE_DATA_RESIDENCY_HOST = /^[a-z0-9-]+\.ghe\.com$/;
/**
* Whether a host may be templated into a Copilot endpoint: the public host or a
* data-residency GHE tenant (`*.ghe.com`). An absent value counts as supported
* because callers fall back to the public default. Anything else (a scheme,
* path, credentials, or an off-allowlist host) is not, so a persisted or
* injected origin can be rejected before any token is sent to it.
*/
function isSupportedGithubCopilotDomain(raw) {
const trimmed = (raw ?? "").trim().toLowerCase();
if (!trimmed) return true;
return trimmed === "github.com" || GHE_DATA_RESIDENCY_HOST.test(trimmed);
}
/**
* Coerce a user/config-supplied GitHub host to a safe bare lowercase hostname.
*
* Fails closed to public `github.com`: only the public host and data-residency
* GHE tenants (`*.ghe.com`) are trusted. Any other value falls back to the
* default rather than being used verbatim, because the resolved host becomes the
* `api.<host>` endpoint that receives the GitHub OAuth token during exchange — a
* typo or injected value like `evil.com` must never redirect that token.
* (Classic self-hosted GHE Server uses arbitrary hostnames but does not host
* Copilot, so it is deliberately out of scope.) Config-supplied hosts coerce
* rather than throw; persisted credential origins are rejected upstream with
* `isSupportedGithubCopilotDomain` before reaching a token request.
*/
function normalizeGithubCopilotDomain(raw) {
const trimmed = (raw ?? "").trim().toLowerCase();
if (trimmed && isSupportedGithubCopilotDomain(trimmed)) return trimmed;
return DEFAULT_GITHUB_COPILOT_DOMAIN;
}
//#endregion
//#region src/plugin-sdk/provider-auth-claude-compat.ts
const CLAUDE_CLI_CREDENTIALS_FILE = ".credentials.json";
const CLAUDE_CLI_USER_SETTINGS_FILE = "settings.json";
const CLAUDE_CLI_KEYCHAIN_SERVICE = "Claude Code-credentials";
const CLAUDE_CLI_KEYCHAIN_TIMEOUT_MS = 2e3;
const CLAUDE_CLI_KEYCHAIN_ACCOUNT_FALLBACK = "claude-code-user";
const MACOS_SECURITY_PATH = "/usr/bin/security";
const SAFE_KEYCHAIN_ACCOUNT_PATTERN = /^[a-zA-Z0-9._-]+$/u;
let claudeCliCache = null;
function resolveClaudeCliConfigDir(homeDir) {
if (homeDir !== void 0) return path.join(resolveOsHomeRelativePath(homeDir), ".claude");
const configuredDir = process.env.CLAUDE_CONFIG_DIR;
return configuredDir ? path.resolve(configuredDir) : path.join(resolveOsHomeRelativePath("~"), ".claude");
}
function resolveClaudeCliPath(homeDir, fileName) {
return path.join(resolveClaudeCliConfigDir(homeDir), fileName);
}
function resolveClaudeCliCredentialsPath(homeDir) {
if (homeDir !== void 0) return path.join(resolveClaudeCliConfigDir(homeDir), CLAUDE_CLI_CREDENTIALS_FILE);
const secureStorageDir = process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
if (secureStorageDir === void 0) return resolveClaudeCliPath(void 0, CLAUDE_CLI_CREDENTIALS_FILE);
const credentialDir = secureStorageDir ? path.resolve(secureStorageDir) : path.join(resolveOsHomeRelativePath("~"), ".claude");
return path.join(credentialDir, CLAUDE_CLI_CREDENTIALS_FILE);
}
function resolveClaudeCliAccountPath(homeDir) {
if (homeDir !== void 0) return path.join(resolveOsHomeRelativePath(homeDir), ".claude.json");
const configuredDir = process.env.CLAUDE_CONFIG_DIR;
return configuredDir ? path.join(path.resolve(configuredDir), ".claude.json") : path.join(resolveOsHomeRelativePath("~"), ".claude.json");
}
function resolveClaudeCliKeychainService(homeDir) {
if (homeDir !== void 0) return CLAUDE_CLI_KEYCHAIN_SERVICE;
const secureStorageDir = process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
const configDir = process.env.CLAUDE_CONFIG_DIR;
const selectedDir = secureStorageDir !== void 0 ? secureStorageDir : configDir;
if (!selectedDir) return CLAUDE_CLI_KEYCHAIN_SERVICE;
const suffix = createHash("sha256").update(selectedDir.normalize("NFC")).digest("hex").slice(0, 8);
return `${CLAUDE_CLI_KEYCHAIN_SERVICE}-${suffix}`;
}
function readFileMtimeMs(filePath) {
try {
return fs.statSync(filePath).mtimeMs;
} catch {
return null;
}
}
function parseClaudeCliOauthCredential(value) {
if (!value || typeof value !== "object") return null;
const data = asNonArrayRecord(value);
const accessToken = data.accessToken;
const refreshToken = data.refreshToken;
const expiresAt = data.expiresAt;
if (typeof accessToken !== "string" || !accessToken || typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) return null;
const subscriptionType = normalizeOptionalString(data.subscriptionType);
const rateLimitTier = normalizeOptionalString(data.rateLimitTier);
const plan = {
...subscriptionType ? { subscriptionType } : {},
...rateLimitTier ? { rateLimitTier } : {}
};
return typeof refreshToken === "string" && refreshToken ? {
type: "oauth",
provider: "anthropic",
access: accessToken,
refresh: refreshToken,
expires: expiresAt,
...plan
} : {
type: "token",
provider: "anthropic",
token: accessToken,
expires: expiresAt,
...plan
};
}
function readClaudeAccountEmail(homeDir) {
const raw = loadJsonFileThroughSymlink(resolveClaudeCliAccountPath(homeDir));
const account = asNonArrayRecord(raw).oauthAccount;
const email = asNonArrayRecord(account).emailAddress;
return normalizeOptionalString(email);
}
function withClaudeAccountEmail(credential, homeDir) {
if (!credential || credential.type === "api_key_helper") return credential;
if (path.dirname(resolveClaudeCliCredentialsPath(homeDir)) !== resolveClaudeCliConfigDir(homeDir)) return credential;
const email = readClaudeAccountEmail(homeDir);
return email ? {
...credential,
email
} : credential;
}
function readClaudeApiKeyHelper(homeDir) {
const raw = loadJsonFileThroughSymlink(resolveClaudeCliPath(homeDir, CLAUDE_CLI_USER_SETTINGS_FILE));
const helper = asNonArrayRecord(raw).apiKeyHelper;
return typeof helper === "string" && helper.trim() ? {
type: "api_key_helper",
provider: "anthropic",
helperHash: createHash("sha256").update(helper.trim()).digest("hex")
} : null;
}
function readClaudeKeychain(execSyncImpl, timeout, service) {
try {
const account = resolveClaudeCliKeychainAccount();
const result = execSyncImpl(`${MACOS_SECURITY_PATH} find-generic-password -a "${account}" -w -s "${service}"`, {
encoding: "utf8",
stdio: [
"pipe",
"pipe",
"pipe"
],
...timeout === void 0 ? {} : { timeout }
});
const parsed = JSON.parse(result.trim());
return isRecord(parsed) ? parsed : null;
} catch {
return null;
}
}
function hasClaudeKeychainItem(execSyncImpl, service) {
try {
const account = resolveClaudeCliKeychainAccount();
execSyncImpl(`${MACOS_SECURITY_PATH} find-generic-password -a "${account}" -s "${service}"`, {
encoding: "utf8",
timeout: CLAUDE_CLI_KEYCHAIN_TIMEOUT_MS,
stdio: [
"pipe",
"pipe",
"pipe"
]
});
return true;
} catch {
return false;
}
}
function resolveClaudeCliKeychainAccount() {
let account;
try {
account = process.env.USER || userInfo().username;
} catch {
account = void 0;
}
return account && SAFE_KEYCHAIN_ACCOUNT_PATTERN.test(account) ? account : CLAUDE_CLI_KEYCHAIN_ACCOUNT_FALLBACK;
}
function readClaudeCliCredentials(options) {
const helper = readClaudeApiKeyHelper(options.homeDir);
if (helper) return helper;
const platform = options.platform ?? process.platform;
const execSyncImpl = options.execSync ?? execSync;
const keychainService = resolveClaudeCliKeychainService(options.homeDir);
if (platform === "darwin" && options.allowKeychainPrompt !== false) {
const credential = parseClaudeCliOauthCredential(readClaudeKeychain(execSyncImpl, options.tryKeychainWithoutPrompt ? CLAUDE_CLI_KEYCHAIN_TIMEOUT_MS : void 0, keychainService)?.claudeAiOauth);
if (credential) return withClaudeAccountEmail(credential, options.homeDir);
}
const credentialsPath = resolveClaudeCliCredentialsPath(options.homeDir);
const raw = loadJsonFileThroughSymlink(credentialsPath);
const credential = withClaudeAccountEmail(parseClaudeCliOauthCredential(asNonArrayRecord(raw).claudeAiOauth), options.homeDir);
if (credential) return credential;
if (options.onStoredCredentialUnreadable && options.tryKeychainWithoutPrompt && (fs.existsSync(credentialsPath) || platform === "darwin" && hasClaudeKeychainItem(execSyncImpl, keychainService))) options.onStoredCredentialUnreadable();
return null;
}
/**
* @deprecated Claude CLI owns native login. Kept functional for shipped Plugin SDK callers only.
* Scheduled for removal after v2026.10.
*/
function readClaudeCliCredentialsCached(options = {}) {
const platform = options.platform ?? process.platform;
const ttlMs = options.ttlMs ?? 0;
const credentialsPath = resolveClaudeCliCredentialsPath(options.homeDir);
const settingsPath = resolveClaudeCliPath(options.homeDir, CLAUDE_CLI_USER_SETTINGS_FILE);
const accountPath = resolveClaudeCliAccountPath(options.homeDir);
const keychainService = resolveClaudeCliKeychainService(options.homeDir);
const cacheKey = `${credentialsPath}:${settingsPath}:${accountPath}:${platform !== "darwin" ? "file" : options.allowKeychainPrompt === false ? options.tryKeychainWithoutPrompt ? "keychain-presence" : "file" : options.tryKeychainWithoutPrompt ? "keychain-bounded" : "keychain"}:${keychainService}:${options.onStoredCredentialUnreadable && options.tryKeychainWithoutPrompt ? "notify" : "silent"}`;
const sourceFingerprint = `${readFileMtimeMs(credentialsPath) ?? "missing"}:${readFileMtimeMs(settingsPath) ?? "missing"}:${readFileMtimeMs(accountPath) ?? "missing"}`;
const now = Date.now();
if (ttlMs > 0 && claudeCliCache?.cacheKey === cacheKey && claudeCliCache.sourceFingerprint === sourceFingerprint && now - claudeCliCache.readAt < ttlMs) return claudeCliCache.value;
const value = readClaudeCliCredentials({
...options,
platform
});
const nextFingerprint = `${readFileMtimeMs(credentialsPath) ?? "missing"}:${readFileMtimeMs(settingsPath) ?? "missing"}:${readFileMtimeMs(accountPath) ?? "missing"}`;
claudeCliCache = ttlMs > 0 && nextFingerprint === sourceFingerprint ? {
value,
readAt: now,
cacheKey,
sourceFingerprint: nextFingerprint
} : null;
return value;
}
//#endregion
export { normalizeGithubCopilotDomain as n, readClaudeCliCredentialsCached as t };