mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
Require explicit Copilot configuration, a saved profile, or COPILOT_GITHUB_TOKEN within the existing agent scope. Generic GitHub credentials continue to serve other tools and remain covered by secret audit and cleanup. Retire the discovery opt-out through the shared Doctor migration and add one-time upgrade guidance. Update provider documentation and generated configuration inventories. Validated with focused plugin and secret tests, real Gateway starts across all 16 sanitized configs, and independent CLI checks for activation, Doctor notices, secret detection, and matching-value cleanup. Related: #144726 Co-authored-by: Ayaan Zaidi <hi@obviy.us>
116 lines
4.5 KiB
TypeScript
116 lines
4.5 KiB
TypeScript
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
import type { ProviderPrepareDynamicModelContext } from "openclaw/plugin-sdk/plugin-entry";
|
|
import {
|
|
coerceSecretRef,
|
|
ensureAuthProfileStore,
|
|
findNormalizedProviderValue,
|
|
listProfilesForProvider,
|
|
normalizeOptionalSecretInput,
|
|
resolveAuthProfileOrder,
|
|
} from "openclaw/plugin-sdk/provider-auth";
|
|
import {
|
|
resolveConfiguredSecretInputWithFallback,
|
|
resolveRequiredConfiguredSecretRefInputString,
|
|
} from "openclaw/plugin-sdk/secret-input-runtime";
|
|
import { PUBLIC_GITHUB_COPILOT_DOMAIN } from "./domain.js";
|
|
import { PROVIDER_ID } from "./models.js";
|
|
import { formatGithubCopilotApiKey, parseGithubCopilotApiKey } from "./oauth.js";
|
|
|
|
export async function resolveFirstGithubToken(params: {
|
|
agentDir?: string;
|
|
config?: OpenClawConfig;
|
|
env: NodeJS.ProcessEnv;
|
|
profileId?: string;
|
|
authProfileMode?: ProviderPrepareDynamicModelContext["authProfileMode"];
|
|
}): Promise<{
|
|
githubToken: string;
|
|
githubDomain?: string;
|
|
hasProfile: boolean;
|
|
profileId?: string;
|
|
}> {
|
|
const authStore = ensureAuthProfileStore(params.agentDir, {
|
|
allowKeychainPrompt: false,
|
|
});
|
|
const profileIds = listProfilesForProvider(authStore, PROVIDER_ID);
|
|
const hasProfile = profileIds.length > 0;
|
|
const requestedProfileId = params.profileId?.trim();
|
|
const githubToken = normalizeOptionalSecretInput(params.env.COPILOT_GITHUB_TOKEN) ?? "";
|
|
const providerConfig = params.config?.models?.providers?.[PROVIDER_ID];
|
|
const configuredRefCanOwnAuth =
|
|
providerConfig?.auth === undefined ||
|
|
providerConfig.auth === "api-key" ||
|
|
providerConfig.auth === "token";
|
|
const preferConfiguredToken =
|
|
(configuredRefCanOwnAuth &&
|
|
Boolean(coerceSecretRef(providerConfig?.apiKey, params.config?.secrets?.defaults))) ||
|
|
(providerConfig?.auth === "api-key" &&
|
|
Boolean(normalizeOptionalSecretInput(providerConfig.apiKey)));
|
|
if (
|
|
!requestedProfileId &&
|
|
(params.authProfileMode || preferConfiguredToken || githubToken || !hasProfile)
|
|
) {
|
|
// Prepared direct-auth attempts must not borrow a stored profile: model
|
|
// limits and the later runtime exchange must use the same source token.
|
|
if (githubToken && !preferConfiguredToken) {
|
|
return { githubToken, hasProfile: false };
|
|
}
|
|
if (!params.config) {
|
|
return { githubToken: "", hasProfile: false };
|
|
}
|
|
const resolved = await resolveConfiguredSecretInputWithFallback({
|
|
config: params.config,
|
|
env: params.env,
|
|
value: configuredRefCanOwnAuth
|
|
? providerConfig?.apiKey
|
|
: normalizeOptionalSecretInput(providerConfig?.apiKey),
|
|
path: `models.providers.${PROVIDER_ID}.apiKey`,
|
|
readFallback: () => "",
|
|
});
|
|
if (resolved.secretRefConfigured && !resolved.value) {
|
|
throw new Error(
|
|
resolved.unresolvedRefReason ??
|
|
`models.providers.${PROVIDER_ID}.apiKey SecretRef is unresolved.`,
|
|
);
|
|
}
|
|
return { githubToken: resolved.value?.trim() || githubToken, hasProfile: false };
|
|
}
|
|
|
|
const explicitProfileOrder =
|
|
findNormalizedProviderValue(authStore.order, PROVIDER_ID) ??
|
|
findNormalizedProviderValue(params.config?.auth?.order, PROVIDER_ID);
|
|
// Preserve discovery's existing first-profile default; authored order alone
|
|
// delegates eligibility and cooldown handling to the canonical auth owner.
|
|
const profileId = requestedProfileId
|
|
? profileIds.find((candidate) => candidate === requestedProfileId)
|
|
: explicitProfileOrder === undefined
|
|
? profileIds[0]
|
|
: resolveAuthProfileOrder({
|
|
cfg: params.config,
|
|
store: authStore,
|
|
provider: PROVIDER_ID,
|
|
})[0];
|
|
const profile = profileId ? authStore.profiles[profileId] : undefined;
|
|
if (profile?.type === "oauth") {
|
|
const formatted = formatGithubCopilotApiKey(profile);
|
|
if (!normalizeOptionalSecretInput(profile.refresh)) {
|
|
return { githubToken: "", hasProfile };
|
|
}
|
|
const parsed = parseGithubCopilotApiKey(formatted);
|
|
return {
|
|
...parsed,
|
|
githubDomain: parsed.githubDomain ?? PUBLIC_GITHUB_COPILOT_DOMAIN,
|
|
hasProfile,
|
|
profileId,
|
|
};
|
|
}
|
|
if (profile?.type !== "token") {
|
|
return { githubToken: "", hasProfile };
|
|
}
|
|
const resolved = await resolveRequiredConfiguredSecretRefInputString({
|
|
config: params.config ?? {},
|
|
env: params.env,
|
|
value: profile.tokenRef,
|
|
path: `providers.github-copilot.authProfiles.${profileId ?? "default"}.tokenRef`,
|
|
});
|
|
return { githubToken: (resolved ?? profile.token ?? "").trim(), hasProfile, profileId };
|
|
}
|