Files
93bad8de2a fix: restore GitHub Copilot turns and account context limits (#106198)
* fix: restore GitHub Copilot runtime routing

Refs #106170

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6053811e-f081-436b-8741-54c5cab6c016

* fix: bind Copilot metadata to resolved credentials

Refs #106170

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6053811e-f081-436b-8741-54c5cab6c016

* refactor: split Copilot runtime helpers

* fix: satisfy Copilot runtime static gates

* fix: satisfy Copilot runtime lint

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Galin Iliev <galin.iliev@microsoft.com>
2026-07-13 16:04:34 -07:00

145 lines
4.8 KiB
TypeScript

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type {
ProviderCatalogContext,
ProviderCatalogResult,
ProviderPrepareDynamicModelContext,
ProviderResolveDynamicModelContext,
ProviderRuntimeModel,
} from "openclaw/plugin-sdk/plugin-entry";
import { getCachedLiveCatalogValue } from "openclaw/plugin-sdk/provider-catalog-shared";
import { resolveFirstGithubToken } from "./auth.js";
import { resolveGithubCopilotDomain } from "./domain.js";
import {
PROVIDER_ID,
fetchCopilotModelCatalog,
resolveCopilotForwardCompatModel,
} from "./models.js";
type GithubCopilotCatalogContext = {
agentDir?: string;
config?: OpenClawConfig;
env: NodeJS.ProcessEnv;
profileId?: string;
authProfileMode?: ProviderPrepareDynamicModelContext["authProfileMode"];
};
function dynamicModelScope(
profileId?: string,
authProfileMode?: ProviderPrepareDynamicModelContext["authProfileMode"],
): string {
const normalizedProfileId = profileId?.trim();
return normalizedProfileId
? `profile:${normalizedProfileId}`
: authProfileMode
? `direct:${authProfileMode}`
: "unscoped";
}
async function loadGithubCopilotRuntime() {
return await import("./register.runtime.js");
}
export function createGithubCopilotDynamicModelHooks(params: {
discoveryEnabled(config?: OpenClawConfig): boolean;
}) {
const preparedDynamicModels = new WeakMap<
object,
Map<string, ReadonlyMap<string, ProviderRuntimeModel>>
>();
async function resolveCatalog(ctx: GithubCopilotCatalogContext) {
if (!params.discoveryEnabled(ctx.config)) {
return null;
}
const { DEFAULT_COPILOT_API_BASE_URL, resolveCopilotApiToken } =
await loadGithubCopilotRuntime();
const { githubToken, hasProfile } = await resolveFirstGithubToken({
agentDir: ctx.agentDir,
env: ctx.env,
...(ctx.config ? { config: ctx.config } : {}),
...(ctx.profileId ? { profileId: ctx.profileId } : {}),
...(ctx.authProfileMode ? { authProfileMode: ctx.authProfileMode } : {}),
});
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,
githubDomain: resolveGithubCopilotDomain({ env: ctx.env, config: ctx.config }),
});
baseUrl = token.baseUrl;
copilotApiToken = token.token;
} catch {
baseUrl = DEFAULT_COPILOT_API_BASE_URL;
}
}
// Live metadata follows account entitlements and context limits. Static
// manifest models remain the visible fallback when exchange or discovery fails.
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 { baseUrl, models: discoveredModels };
}
async function runCatalog(ctx: ProviderCatalogContext): Promise<ProviderCatalogResult> {
const catalog = await resolveCatalog(ctx);
return catalog ? { provider: catalog } : null;
}
async function prepareDynamicModel(ctx: ProviderPrepareDynamicModelContext): Promise<void> {
const catalog = await resolveCatalog({
agentDir: ctx.agentDir,
env: process.env,
...(ctx.config ? { config: ctx.config } : {}),
...(ctx.authProfileId ? { profileId: ctx.authProfileId } : {}),
...(ctx.authProfileMode ? { authProfileMode: ctx.authProfileMode } : {}),
});
const models = new Map<string, ProviderRuntimeModel>();
if (catalog) {
for (const model of catalog.models) {
models.set(model.id, {
...model,
provider: PROVIDER_ID,
baseUrl: catalog.baseUrl,
});
}
}
let scopedModels = preparedDynamicModels.get(ctx.modelRegistry);
if (!scopedModels) {
scopedModels = new Map();
preparedDynamicModels.set(ctx.modelRegistry, scopedModels);
}
scopedModels.set(dynamicModelScope(ctx.authProfileId, ctx.authProfileMode), models);
}
function resolveDynamicModel(ctx: ProviderResolveDynamicModelContext) {
return (
preparedDynamicModels
.get(ctx.modelRegistry)
?.get(dynamicModelScope(ctx.authProfileId, ctx.authProfileMode))
?.get(ctx.modelId) ?? resolveCopilotForwardCompatModel(ctx)
);
}
return {
prepareDynamicModel,
resolveDynamicModel,
runCatalog,
preferRuntimeResolvedModel: ({ config }: { config?: OpenClawConfig }) =>
params.discoveryEnabled(config),
};
}