Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
494
extensions/github-copilot/index.ts
Normal file
494
extensions/github-copilot/index.ts
Normal file
@@ -0,0 +1,494 @@
|
||||
// Github Copilot plugin entrypoint registers its OpenClaw integration.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import {
|
||||
definePluginEntry,
|
||||
type ProviderCatalogContext,
|
||||
type ProviderCatalogResult,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthResult,
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
type UnifiedModelCatalogEntry,
|
||||
type UnifiedModelCatalogProviderContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
coerceSecretRef,
|
||||
ensureAuthProfileStore,
|
||||
listProfilesForProvider,
|
||||
normalizeOptionalSecretInput,
|
||||
resolveDefaultSecretProviderAlias,
|
||||
upsertAuthProfileWithLock,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { getCachedLiveCatalogValue } from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import { resolveFirstGithubToken } from "./auth.js";
|
||||
import { githubCopilotMemoryEmbeddingProviderAdapter } from "./embeddings.js";
|
||||
import { resolveCopilotExtendedThinkingLevels } from "./model-metadata.js";
|
||||
import {
|
||||
PROVIDER_ID,
|
||||
fetchCopilotModelCatalog,
|
||||
resolveCopilotForwardCompatModel,
|
||||
} from "./models.js";
|
||||
import {
|
||||
buildGithubCopilotReplayPolicy,
|
||||
sanitizeGithubCopilotReplayHistory,
|
||||
} from "./replay-policy.js";
|
||||
import { wrapCopilotProviderStream } from "./stream.js";
|
||||
|
||||
const COPILOT_ENV_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
|
||||
const DEFAULT_COPILOT_MODEL = "github-copilot/claude-opus-4.7";
|
||||
const DEFAULT_COPILOT_PROFILE_ID = "github-copilot:github";
|
||||
|
||||
type GithubCopilotPluginConfig = {
|
||||
discovery?: {
|
||||
enabled?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
async function loadGithubCopilotRuntime() {
|
||||
return await import("./register.runtime.js");
|
||||
}
|
||||
|
||||
function applyCopilotDefaultModel(cfg: OpenClawConfig): OpenClawConfig {
|
||||
const defaults = cfg.agents?.defaults;
|
||||
const existingModel = defaults?.model;
|
||||
const existingPrimary =
|
||||
typeof existingModel === "string"
|
||||
? existingModel.trim()
|
||||
: typeof existingModel === "object" && typeof existingModel?.primary === "string"
|
||||
? existingModel.primary.trim()
|
||||
: "";
|
||||
if (existingPrimary) {
|
||||
return cfg;
|
||||
}
|
||||
const fallbacks =
|
||||
typeof existingModel === "object" && existingModel !== null && "fallbacks" in existingModel
|
||||
? (existingModel as { fallbacks?: string[] }).fallbacks
|
||||
: undefined;
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...defaults,
|
||||
model: {
|
||||
...(fallbacks ? { fallbacks } : undefined),
|
||||
primary: DEFAULT_COPILOT_MODEL,
|
||||
},
|
||||
models: {
|
||||
...defaults?.models,
|
||||
[DEFAULT_COPILOT_MODEL]: defaults?.models?.[DEFAULT_COPILOT_MODEL] ?? {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function resolveExistingCopilotTokenProfileId(agentDir?: string): string | undefined {
|
||||
const authStore = ensureAuthProfileStore(agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
return listProfilesForProvider(authStore, PROVIDER_ID).find((profileId) => {
|
||||
const profile = authStore.profiles[profileId];
|
||||
if (profile?.type !== "token") {
|
||||
return false;
|
||||
}
|
||||
return Boolean(
|
||||
normalizeOptionalSecretInput(profile.token) || coerceSecretRef(profile.tokenRef)?.id.trim(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function resolveExistingCopilotAuthResult(agentDir?: string): ProviderAuthResult | null {
|
||||
const profileId = resolveExistingCopilotTokenProfileId(agentDir);
|
||||
if (!profileId) {
|
||||
return null;
|
||||
}
|
||||
const authStore = ensureAuthProfileStore(agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const credential = authStore.profiles[profileId];
|
||||
if (!credential || credential.type !== "token") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
profiles: [
|
||||
{
|
||||
profileId,
|
||||
credential,
|
||||
},
|
||||
],
|
||||
defaultModel: DEFAULT_COPILOT_MODEL,
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveCopilotNonInteractiveToken(
|
||||
ctx: ProviderAuthMethodNonInteractiveContext,
|
||||
flagValue: string | undefined,
|
||||
) {
|
||||
const resolveFromEnvChain = async () => {
|
||||
for (const envVar of COPILOT_ENV_VARS) {
|
||||
const resolved = await ctx.resolveApiKey({
|
||||
provider: PROVIDER_ID,
|
||||
flagName: "--github-copilot-token",
|
||||
envVar,
|
||||
envVarName: envVar,
|
||||
allowProfile: false,
|
||||
required: false,
|
||||
});
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (ctx.opts.secretInputMode === "ref") {
|
||||
const resolved = await resolveFromEnvChain();
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
if (flagValue) {
|
||||
ctx.runtime.error(
|
||||
[
|
||||
"--github-copilot-token cannot be used with --secret-input-mode ref unless COPILOT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN is set in env.",
|
||||
"Set one of those env vars and omit --github-copilot-token, or use --secret-input-mode plaintext.",
|
||||
].join("\n"),
|
||||
);
|
||||
ctx.runtime.exit(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const primary = await ctx.resolveApiKey({
|
||||
provider: PROVIDER_ID,
|
||||
flagValue,
|
||||
flagName: "--github-copilot-token",
|
||||
envVar: COPILOT_ENV_VARS[0],
|
||||
envVarName: COPILOT_ENV_VARS[0],
|
||||
allowProfile: false,
|
||||
required: false,
|
||||
});
|
||||
if (primary || flagValue) {
|
||||
return primary;
|
||||
}
|
||||
|
||||
for (const envVar of COPILOT_ENV_VARS.slice(1)) {
|
||||
const resolved = await ctx.resolveApiKey({
|
||||
provider: PROVIDER_ID,
|
||||
flagName: "--github-copilot-token",
|
||||
envVar,
|
||||
envVarName: envVar,
|
||||
allowProfile: false,
|
||||
required: false,
|
||||
});
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function runGitHubCopilotNonInteractiveAuth(
|
||||
ctx: ProviderAuthMethodNonInteractiveContext,
|
||||
): Promise<OpenClawConfig | null> {
|
||||
const opts = ctx.opts as Record<string, unknown> | undefined;
|
||||
const flagValue = normalizeOptionalSecretInput(opts?.githubCopilotToken);
|
||||
const resolved = await resolveCopilotNonInteractiveToken(ctx, flagValue);
|
||||
|
||||
let profileId = DEFAULT_COPILOT_PROFILE_ID;
|
||||
if (resolved) {
|
||||
const useTokenRef = ctx.opts.secretInputMode === "ref" && resolved.source === "env";
|
||||
if (useTokenRef && !resolved.envVarName) {
|
||||
ctx.runtime.error(
|
||||
[
|
||||
'--secret-input-mode ref requires an explicit environment variable for provider "github-copilot".',
|
||||
"Set COPILOT_GITHUB_TOKEN in env and retry, or use --secret-input-mode plaintext.",
|
||||
].join("\n"),
|
||||
);
|
||||
ctx.runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
await upsertAuthProfileWithLock({
|
||||
profileId,
|
||||
credential: {
|
||||
type: "token",
|
||||
provider: PROVIDER_ID,
|
||||
...(useTokenRef
|
||||
? {
|
||||
tokenRef: {
|
||||
source: "env",
|
||||
provider: resolveDefaultSecretProviderAlias(ctx.baseConfig, "env", {
|
||||
preferFirstProviderForSource: true,
|
||||
}),
|
||||
id: resolved.envVarName!,
|
||||
},
|
||||
}
|
||||
: { token: resolved.key }),
|
||||
},
|
||||
agentDir: ctx.agentDir,
|
||||
});
|
||||
} else {
|
||||
if (flagValue && ctx.opts.secretInputMode === "ref") {
|
||||
return null;
|
||||
}
|
||||
const existingProfileId = resolveExistingCopilotTokenProfileId(ctx.agentDir);
|
||||
if (!existingProfileId) {
|
||||
ctx.runtime.error(
|
||||
"Missing --github-copilot-token (or COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN env var) for --auth-choice github-copilot.",
|
||||
);
|
||||
ctx.runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
profileId = existingProfileId;
|
||||
}
|
||||
|
||||
return applyCopilotDefaultModel(
|
||||
applyAuthProfileConfig(ctx.config, {
|
||||
profileId,
|
||||
provider: PROVIDER_ID,
|
||||
mode: "token",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "github-copilot",
|
||||
name: "GitHub Copilot Provider",
|
||||
description: "Bundled GitHub Copilot provider plugin",
|
||||
register(api) {
|
||||
const startupPluginConfig = (api.pluginConfig ?? {}) as GithubCopilotPluginConfig;
|
||||
|
||||
function resolveCurrentPluginConfig(config?: OpenClawConfig): GithubCopilotPluginConfig {
|
||||
const runtimePluginConfig = resolvePluginConfigObject(config, "github-copilot");
|
||||
if (runtimePluginConfig) {
|
||||
return runtimePluginConfig as GithubCopilotPluginConfig;
|
||||
}
|
||||
return config ? {} : startupPluginConfig;
|
||||
}
|
||||
|
||||
async function runGithubCopilotCatalog(
|
||||
ctx: ProviderCatalogContext,
|
||||
): Promise<ProviderCatalogResult> {
|
||||
const pluginConfig = resolveCurrentPluginConfig(ctx.config);
|
||||
const discoveryEnabled = pluginConfig.discovery?.enabled;
|
||||
if (discoveryEnabled === false) {
|
||||
return null;
|
||||
}
|
||||
const { DEFAULT_COPILOT_API_BASE_URL, resolveCopilotApiToken } =
|
||||
await loadGithubCopilotRuntime();
|
||||
const { githubToken, hasProfile } = await resolveFirstGithubToken({
|
||||
agentDir: ctx.agentDir,
|
||||
config: ctx.config,
|
||||
env: ctx.env,
|
||||
});
|
||||
if (!hasProfile && !githubToken) {
|
||||
return null;
|
||||
}
|
||||
let baseUrl = DEFAULT_COPILOT_API_BASE_URL;
|
||||
let copilotApiToken: string | undefined;
|
||||
if (githubToken) {
|
||||
try {
|
||||
const token = await resolveCopilotApiToken({
|
||||
githubToken,
|
||||
env: ctx.env,
|
||||
});
|
||||
baseUrl = token.baseUrl;
|
||||
copilotApiToken = token.token;
|
||||
} catch {
|
||||
baseUrl = DEFAULT_COPILOT_API_BASE_URL;
|
||||
}
|
||||
}
|
||||
// Try to fetch the live model catalog from Copilot's /models endpoint so
|
||||
// the runtime tracks per-account entitlements and accurate context
|
||||
// windows (max_context_window_tokens) without manifest churn. On any
|
||||
// failure we return an empty model list, which lets the static manifest
|
||||
// catalog continue to be the visible fallback for users.
|
||||
let discoveredModels: Awaited<ReturnType<typeof fetchCopilotModelCatalog>> = [];
|
||||
if (copilotApiToken) {
|
||||
try {
|
||||
discoveredModels = await getCachedLiveCatalogValue({
|
||||
keyParts: [PROVIDER_ID, "models", baseUrl, copilotApiToken],
|
||||
load: async () =>
|
||||
await fetchCopilotModelCatalog({
|
||||
copilotApiToken,
|
||||
baseUrl,
|
||||
}),
|
||||
});
|
||||
} catch {
|
||||
discoveredModels = [];
|
||||
}
|
||||
}
|
||||
return {
|
||||
provider: {
|
||||
baseUrl,
|
||||
models: discoveredModels,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function runGithubCopilotUnifiedLiveCatalog(
|
||||
ctx: UnifiedModelCatalogProviderContext,
|
||||
): Promise<UnifiedModelCatalogEntry[] | null> {
|
||||
const result = await runGithubCopilotCatalog(ctx);
|
||||
if (!result || !("provider" in result)) {
|
||||
return null;
|
||||
}
|
||||
return (result.provider.models ?? []).map((model) => {
|
||||
const entry: UnifiedModelCatalogEntry = {
|
||||
kind: "text",
|
||||
provider: PROVIDER_ID,
|
||||
model: model.id,
|
||||
source: "live",
|
||||
};
|
||||
if (model.name) {
|
||||
entry.label = model.name;
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
|
||||
async function runGitHubCopilotAuth(ctx: ProviderAuthContext) {
|
||||
const existing = resolveExistingCopilotAuthResult(ctx.agentDir);
|
||||
if (existing) {
|
||||
const runLogin = await ctx.prompter.confirm({
|
||||
message: "GitHub Copilot auth already exists. Re-run login?",
|
||||
initialValue: false,
|
||||
});
|
||||
if (!runLogin) {
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.prompter.note(
|
||||
[
|
||||
"This will open a GitHub device login to authorize Copilot.",
|
||||
"Requires an active GitHub Copilot subscription.",
|
||||
].join("\n"),
|
||||
"GitHub Copilot",
|
||||
);
|
||||
|
||||
const { runGitHubCopilotDeviceFlow } = await import("./login.js");
|
||||
|
||||
const result = await runGitHubCopilotDeviceFlow({
|
||||
showCode: async ({ verificationUrl, userCode, expiresInMs }) => {
|
||||
const expiresInMinutes = Math.max(1, Math.round(expiresInMs / 60_000));
|
||||
await ctx.prompter.note(
|
||||
[
|
||||
"Open this URL in your browser and enter the code below.",
|
||||
`URL: ${verificationUrl}`,
|
||||
`Code: ${userCode}`,
|
||||
`Code expires in ${expiresInMinutes} minutes. Never share it.`,
|
||||
"",
|
||||
"If a browser does not open automatically after you continue, copy the URL manually.",
|
||||
].join("\n"),
|
||||
"Authorize GitHub Copilot",
|
||||
);
|
||||
},
|
||||
openUrl: async (url) => {
|
||||
await ctx.openUrl(url);
|
||||
},
|
||||
});
|
||||
|
||||
if (result.status === "access_denied") {
|
||||
await ctx.prompter.note("GitHub Copilot login was cancelled.", "GitHub Copilot");
|
||||
return { profiles: [] };
|
||||
}
|
||||
|
||||
if (result.status === "expired") {
|
||||
await ctx.prompter.note(
|
||||
"The GitHub device code expired. Retry login to get a new code.",
|
||||
"GitHub Copilot",
|
||||
);
|
||||
return { profiles: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
profiles: [
|
||||
{
|
||||
profileId: DEFAULT_COPILOT_PROFILE_ID,
|
||||
credential: {
|
||||
type: "token" as const,
|
||||
provider: PROVIDER_ID,
|
||||
token: result.accessToken,
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultModel: DEFAULT_COPILOT_MODEL,
|
||||
};
|
||||
}
|
||||
|
||||
api.registerMemoryEmbeddingProvider(githubCopilotMemoryEmbeddingProviderAdapter);
|
||||
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "GitHub Copilot",
|
||||
docsPath: "/providers/models",
|
||||
envVars: COPILOT_ENV_VARS,
|
||||
auth: [
|
||||
{
|
||||
id: "device",
|
||||
label: "GitHub device login",
|
||||
hint: "Browser device-code flow",
|
||||
kind: "device_code",
|
||||
run: async (ctx) => await runGitHubCopilotAuth(ctx),
|
||||
runNonInteractive: async (ctx) => await runGitHubCopilotNonInteractiveAuth(ctx),
|
||||
},
|
||||
],
|
||||
wizard: {
|
||||
setup: {
|
||||
choiceId: "github-copilot",
|
||||
choiceLabel: "GitHub Copilot",
|
||||
choiceHint: "Device login with your GitHub account",
|
||||
methodId: "device",
|
||||
modelSelection: {
|
||||
promptWhenAuthChoiceProvided: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
catalog: {
|
||||
order: "late",
|
||||
run: runGithubCopilotCatalog,
|
||||
},
|
||||
resolveDynamicModel: (ctx) => resolveCopilotForwardCompatModel(ctx),
|
||||
wrapStreamFn: wrapCopilotProviderStream,
|
||||
buildReplayPolicy: ({ modelId }) => buildGithubCopilotReplayPolicy(modelId),
|
||||
sanitizeReplayHistory: sanitizeGithubCopilotReplayHistory,
|
||||
resolveThinkingProfile: ({ modelId, compat }) => {
|
||||
const extendedLevels = resolveCopilotExtendedThinkingLevels(modelId, compat);
|
||||
return {
|
||||
levels: [
|
||||
{ id: "off" },
|
||||
{ id: "minimal" },
|
||||
{ id: "low" },
|
||||
{ id: "medium" },
|
||||
{ id: "high" },
|
||||
...extendedLevels.map((id) => ({ id })),
|
||||
],
|
||||
};
|
||||
},
|
||||
prepareRuntimeAuth: async (ctx) => {
|
||||
const { resolveCopilotApiToken } = await loadGithubCopilotRuntime();
|
||||
const token = await resolveCopilotApiToken({
|
||||
githubToken: ctx.apiKey,
|
||||
env: ctx.env,
|
||||
});
|
||||
return {
|
||||
apiKey: token.token,
|
||||
baseUrl: token.baseUrl,
|
||||
expiresAt: token.expiresAt,
|
||||
};
|
||||
},
|
||||
resolveUsageAuth: async (ctx) => await ctx.resolveOAuthToken(),
|
||||
fetchUsageSnapshot: async (ctx) => {
|
||||
const { fetchCopilotUsage } = await loadGithubCopilotRuntime();
|
||||
return await fetchCopilotUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
|
||||
},
|
||||
});
|
||||
api.registerModelCatalogProvider({
|
||||
provider: PROVIDER_ID,
|
||||
kinds: ["text"],
|
||||
liveCatalog: runGithubCopilotUnifiedLiveCatalog,
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user