openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
239 lines (238 loc) • 10.7 kB
JavaScript
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { D as tryResolveLegacyCompatibilityAgentId, c as resolveAgentConfig } from "./agent-scope-config-DcbEhP0R.js";
import "./legacy.default-agent-owner-BGwEdQRe.js";
import { a as resolveAgentModelPrimaryValue } from "./model-input-BuGMCNOz.js";
import { t as resolveDefaultModelForAgent } from "./model-selection-config-BrdmmqKD.js";
import { i as tryLoadActivatedBundledPluginPublicSurfaceModule } from "./facade-runtime-BjmVS_9G.js";
import { n as readCodexCliCredentialsCached, r as readGeminiCliCredentialsCached } from "./cli-credentials-CHuqM5pH.js";
import "./model-selection-di2kjKCB.js";
import { n as probeLocalCommand } from "./probes-CspEEOIq.js";
import { i as GEMINI_CLI_DEFAULT_MODEL_REF, n as CLAUDE_CLI_DEFAULT_MODEL_REF, o as detectAmbientInferenceBackends, r as CODEX_APP_SERVER_DEFAULT_MODEL_REF } from "./onboard-inference-ambient-BvxNJ30C.js";
import path from "node:path";
import os from "node:os";
import { randomInt } from "node:crypto";
//#region src/commands/onboard-inference.ts
function detectCliCredentialState(params) {
if (!params.probe.found) return;
if (params.hasStoredCredentials) return true;
return params.platform === "darwin" ? void 0 : false;
}
const CLI_AUTH_KIND_LABEL = {
"api-key": "API key (usage-billed)",
"chatgpt-subscription": "ChatGPT account",
"claude-subscription": "Claude account",
token: "OAuth token"
};
function describeCliDetail(state, loginHint) {
if (state.authKind) {
const identity = state.authKind === "chatgpt-subscription" || state.authKind === "claude-subscription" ? ` · ${state.email || "email unavailable"}` : "";
return `logged in · ${CLI_AUTH_KIND_LABEL[state.authKind]}${identity}`;
}
if (state.credentials === true) return "logged in · authentication method unavailable";
if (state.credentials === false) return `installed, not logged in — ${loginHint}, then check again`;
return "installed";
}
function describeGeminiCliDetail(credentials) {
return credentials === true ? "installed; credentials found" : "installed; login status unavailable";
}
async function classifyCodexLoginStatus(probe, command) {
const status = await probe(command, ["login", "status"], { timeoutMs: 3e3 });
if (status.error) return { credentials: void 0 };
if (status.version === "Logged in using ChatGPT") return {
credentials: true,
authKind: "chatgpt-subscription"
};
if (/^Logged in using an API key - .+$/u.test(status.version ?? "")) return {
credentials: true,
authKind: "api-key"
};
return { credentials: true };
}
async function detectClaudeLoginState(_probe, command, env = process.env) {
try {
const status = await (await tryLoadActivatedBundledPluginPublicSurfaceModule({
dirName: "anthropic",
artifactBasename: "cli-auth-api.js",
env
}))?.probeClaudeCliAuthStatus({
command,
env
});
if (status?.status !== "available") return { credentials: status?.status === "missing" ? false : void 0 };
switch (status.authMethod) {
case "claude.ai": return {
credentials: true,
authKind: "claude-subscription",
email: status.email
};
case "api_key":
case "api_key_helper": return {
credentials: true,
authKind: "api-key"
};
case "oauth_token": return {
credentials: true,
authKind: "token"
};
default: return { credentials: true };
}
} catch {
return { credentials: void 0 };
}
}
async function readCodexNativeLoginState(command, env) {
try {
const account = await (await tryLoadActivatedBundledPluginPublicSurfaceModule({
dirName: "openai",
artifactBasename: "cli-auth-api.js",
env
}))?.readCodexCliAccount({
command,
env
});
switch (account?.type) {
case "apiKey": return {
credentials: true,
authKind: "api-key"
};
case "chatgpt": return {
credentials: true,
authKind: "chatgpt-subscription",
email: account.email
};
case "none": return { credentials: account.requiresOpenaiAuth ? false : void 0 };
case "unknown": return { credentials: void 0 };
case void 0: return;
}
} catch {}
}
function randomizeClaudeCodexTie(candidates, pickRandomInt) {
const claudeIndex = candidates.findIndex((candidate) => candidate.kind === "claude-cli" && candidate.credentials !== false);
const codexIndex = candidates.findIndex((candidate) => candidate.kind === "codex-cli" && candidate.credentials !== false);
if (claudeIndex === -1 || codexIndex === -1 || pickRandomInt(2) === 0) return;
const claudeCandidate = candidates[claudeIndex];
const codexCandidate = candidates[codexIndex];
candidates[claudeIndex] = expectDefined(codexCandidate, "Codex onboarding candidate");
candidates[codexIndex] = expectDefined(claudeCandidate, "Claude onboarding candidate");
}
const CODEX_MACOS_APP_NAMES = [
"ChatGPT.app",
"Codex.app",
"Codex Beta.app"
];
const CODEX_MACOS_APP_PROBE_TIMEOUT_MS = 3e3;
async function probeCodexCommand(params) {
const pathProbe = await params.probe("codex");
if (pathProbe.found || params.platform !== "darwin") return pathProbe;
const home = params.env.HOME?.trim() || os.homedir();
const appExecutables = new Set(CODEX_MACOS_APP_NAMES.flatMap((appName) => [path.join("/Applications", appName, "Contents", "Resources", "codex"), path.join(home, "Applications", appName, "Contents", "Resources", "codex")]));
for (const executable of appExecutables) {
const appProbe = await params.probe(executable, ["--version"], { timeoutMs: CODEX_MACOS_APP_PROBE_TIMEOUT_MS });
if (appProbe.found) return appProbe;
}
return pathProbe;
}
/** Detects a native Codex App Server without coupling it to inference selection. */
async function detectNativeCodexAppServer(options = {}) {
return await probeCodexCommand({
probe: options.probeLocalCommand ?? probeLocalCommand,
env: options.env ?? process.env,
platform: options.platform ?? process.platform
});
}
/**
* Detect usable inference backends in ladder order. Returns candidates only
* for backends that exist on this machine; the first entry is the bootstrap
* default. Backends that are definitively logged out sink below logged-in and
* unknown ones so a stale install never outranks a working login.
*/
async function detectInferenceBackends(options = {}) {
const env = options.env ?? process.env;
const platform = options.platform ?? process.platform;
const probe = options.deps?.probeLocalCommand ?? probeLocalCommand;
const readCodex = options.deps?.readCodexCliCredentials ?? (() => readCodexCliCredentialsCached({
allowKeychainPrompt: false,
ttlMs: 6e4
}));
const readGemini = options.deps?.readGeminiCliCredentials ?? (() => readGeminiCliCredentialsCached({ ttlMs: 6e4 }));
const candidates = [];
const defaultAgentId = options.config ? options.agentId?.trim() || tryResolveLegacyCompatibilityAgentId(options.config) : void 0;
const defaultAgentModel = options.config && defaultAgentId ? resolveAgentConfig(options.config, defaultAgentId)?.model : void 0;
if (resolveAgentModelPrimaryValue(defaultAgentModel) ?? resolveAgentModelPrimaryValue(options.config?.agents?.defaults?.model)) {
const resolved = resolveDefaultModelForAgent({
cfg: options.config ?? {},
...defaultAgentId ? { agentId: defaultAgentId } : {}
});
const modelRef = `${resolved.provider}/${resolved.model}`;
candidates.push({
kind: "existing-model",
modelRef,
label: "Current model",
detail: `${modelRef} — already configured`,
credentials: true
});
}
const envCandidates = detectAmbientInferenceBackends(env).filter((candidate) => candidate.kind === "openai-api-key" || candidate.kind === "anthropic-api-key");
const [claudeProbe, codexProbe, geminiProbe] = await Promise.all([
probe("claude"),
detectNativeCodexAppServer({
probeLocalCommand: probe,
env,
platform
}),
probe("gemini")
]);
const cliCandidates = [];
const subscriptionPromotionEligibleCliKinds = /* @__PURE__ */ new Set();
if (claudeProbe.found && !claudeProbe.timedOut) {
const loginState = options.deps?.detectClaudeLoginState ? await options.deps.detectClaudeLoginState(probe, claudeProbe.command) : await detectClaudeLoginState(probe, claudeProbe.command, env);
const credentials = loginState.credentials;
if (credentials === true && loginState.authKind === "claude-subscription") subscriptionPromotionEligibleCliKinds.add("claude-cli");
const detail = describeCliDetail(loginState, "run `claude auth login`");
cliCandidates.push({
kind: "claude-cli",
modelRef: CLAUDE_CLI_DEFAULT_MODEL_REF,
label: "Claude Code",
detail,
...credentials === void 0 ? {} : { credentials }
});
}
if (codexProbe.found && !codexProbe.timedOut) {
const codexCredential = readCodex();
const loginState = options.deps?.detectCodexLoginState ? { credentials: await options.deps.detectCodexLoginState(probe, codexProbe.command) } : await readCodexNativeLoginState(codexProbe.command, env) ?? (options.deps?.readCodexCliCredentials ? {
credentials: detectCliCredentialState({
probe: codexProbe,
hasStoredCredentials: codexCredential !== null,
platform
}),
...codexCredential?.type === "oauth" ? { authKind: "chatgpt-subscription" } : {}
} : await classifyCodexLoginStatus(probe, codexProbe.command));
const credentials = loginState.credentials;
if (credentials === true && loginState.authKind === "chatgpt-subscription" && codexCredential?.type === "oauth") subscriptionPromotionEligibleCliKinds.add("codex-cli");
cliCandidates.push({
kind: "codex-cli",
modelRef: CODEX_APP_SERVER_DEFAULT_MODEL_REF,
label: "Codex",
detail: describeCliDetail(loginState, "run `codex login`"),
...credentials === void 0 ? {} : { credentials }
});
}
if (geminiProbe.found && !geminiProbe.timedOut) {
const credentials = readGemini() !== null ? true : void 0;
cliCandidates.push({
kind: "gemini-cli",
modelRef: GEMINI_CLI_DEFAULT_MODEL_REF,
label: "Gemini CLI",
detail: describeGeminiCliDetail(credentials),
...credentials === void 0 ? {} : { credentials }
});
}
randomizeClaudeCodexTie(cliCandidates, options.deps?.randomInt ?? randomInt);
const loggedInSubscriptionCliCandidates = cliCandidates.filter((candidate) => candidate.credentials === true && subscriptionPromotionEligibleCliKinds.has(candidate.kind));
const remainingCliCandidates = cliCandidates.filter((candidate) => !loggedInSubscriptionCliCandidates.includes(candidate));
candidates.push(...loggedInSubscriptionCliCandidates, ...envCandidates, ...remainingCliCandidates.filter((candidate) => candidate.credentials !== false), ...remainingCliCandidates.filter((candidate) => candidate.credentials === false));
return candidates;
}
//#endregion
export { detectInferenceBackends as t };