mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-29 16:54:30 +00:00
feat(plugins): move provider runtimes into bundled plugins
This commit is contained in:
49
extensions/github-copilot/index.test.ts
Normal file
49
extensions/github-copilot/index.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ProviderPlugin } from "../../src/plugins/types.js";
|
||||
import githubCopilotPlugin from "./index.js";
|
||||
|
||||
function registerProvider(): ProviderPlugin {
|
||||
let provider: ProviderPlugin | undefined;
|
||||
githubCopilotPlugin.register({
|
||||
registerProvider(nextProvider: ProviderPlugin) {
|
||||
provider = nextProvider;
|
||||
},
|
||||
} as never);
|
||||
if (!provider) {
|
||||
throw new Error("provider registration missing");
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
describe("github-copilot plugin", () => {
|
||||
it("owns Copilot-specific forward-compat fallbacks", () => {
|
||||
const provider = registerProvider();
|
||||
const model = provider.resolveDynamicModel?.({
|
||||
provider: "github-copilot",
|
||||
modelId: "gpt-5.3-codex",
|
||||
modelRegistry: {
|
||||
find: (_provider: string, id: string) =>
|
||||
id === "gpt-5.2-codex"
|
||||
? {
|
||||
id,
|
||||
name: id,
|
||||
api: "openai-codex-responses",
|
||||
provider: "github-copilot",
|
||||
baseUrl: "https://api.copilot.example",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 8_192,
|
||||
}
|
||||
: null,
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(model).toMatchObject({
|
||||
id: "gpt-5.3-codex",
|
||||
provider: "github-copilot",
|
||||
api: "openai-codex-responses",
|
||||
});
|
||||
});
|
||||
});
|
||||
137
extensions/github-copilot/index.ts
Normal file
137
extensions/github-copilot/index.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderResolveDynamicModelContext,
|
||||
type ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { listProfilesForProvider } from "../../src/agents/auth-profiles/profiles.js";
|
||||
import { ensureAuthProfileStore } from "../../src/agents/auth-profiles/store.js";
|
||||
import { normalizeModelCompat } from "../../src/agents/model-compat.js";
|
||||
import { coerceSecretRef } from "../../src/config/types.secrets.js";
|
||||
import {
|
||||
DEFAULT_COPILOT_API_BASE_URL,
|
||||
resolveCopilotApiToken,
|
||||
} from "../../src/providers/github-copilot-token.js";
|
||||
|
||||
const PROVIDER_ID = "github-copilot";
|
||||
const COPILOT_ENV_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
|
||||
const CODEX_GPT_53_MODEL_ID = "gpt-5.3-codex";
|
||||
const CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const;
|
||||
|
||||
function resolveFirstGithubToken(params: { agentDir?: string; env: NodeJS.ProcessEnv }): {
|
||||
githubToken: string;
|
||||
hasProfile: boolean;
|
||||
} {
|
||||
const authStore = ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const hasProfile = listProfilesForProvider(authStore, PROVIDER_ID).length > 0;
|
||||
const envToken =
|
||||
params.env.COPILOT_GITHUB_TOKEN ?? params.env.GH_TOKEN ?? params.env.GITHUB_TOKEN ?? "";
|
||||
const githubToken = envToken.trim();
|
||||
if (githubToken || !hasProfile) {
|
||||
return { githubToken, hasProfile };
|
||||
}
|
||||
|
||||
const profileId = listProfilesForProvider(authStore, PROVIDER_ID)[0];
|
||||
const profile = profileId ? authStore.profiles[profileId] : undefined;
|
||||
if (profile?.type !== "token") {
|
||||
return { githubToken: "", hasProfile };
|
||||
}
|
||||
const directToken = profile.token?.trim() ?? "";
|
||||
if (directToken) {
|
||||
return { githubToken: directToken, hasProfile };
|
||||
}
|
||||
const tokenRef = coerceSecretRef(profile.tokenRef);
|
||||
if (tokenRef?.source === "env" && tokenRef.id.trim()) {
|
||||
return {
|
||||
githubToken: (params.env[tokenRef.id] ?? process.env[tokenRef.id] ?? "").trim(),
|
||||
hasProfile,
|
||||
};
|
||||
}
|
||||
return { githubToken: "", hasProfile };
|
||||
}
|
||||
|
||||
function resolveCopilotForwardCompatModel(
|
||||
ctx: ProviderResolveDynamicModelContext,
|
||||
): ProviderRuntimeModel | undefined {
|
||||
const trimmedModelId = ctx.modelId.trim();
|
||||
if (trimmedModelId.toLowerCase() !== CODEX_GPT_53_MODEL_ID) {
|
||||
return undefined;
|
||||
}
|
||||
for (const templateId of CODEX_TEMPLATE_MODEL_IDS) {
|
||||
const template = ctx.modelRegistry.find(PROVIDER_ID, templateId) as ProviderRuntimeModel | null;
|
||||
if (!template) {
|
||||
continue;
|
||||
}
|
||||
return normalizeModelCompat({
|
||||
...template,
|
||||
id: trimmedModelId,
|
||||
name: trimmedModelId,
|
||||
} as ProviderRuntimeModel);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const githubCopilotPlugin = {
|
||||
id: "github-copilot",
|
||||
name: "GitHub Copilot Provider",
|
||||
description: "Bundled GitHub Copilot provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "GitHub Copilot",
|
||||
docsPath: "/providers/models",
|
||||
envVars: COPILOT_ENV_VARS,
|
||||
auth: [],
|
||||
catalog: {
|
||||
order: "late",
|
||||
run: async (ctx) => {
|
||||
const { githubToken, hasProfile } = resolveFirstGithubToken({
|
||||
agentDir: ctx.agentDir,
|
||||
env: ctx.env,
|
||||
});
|
||||
if (!hasProfile && !githubToken) {
|
||||
return null;
|
||||
}
|
||||
let baseUrl = DEFAULT_COPILOT_API_BASE_URL;
|
||||
if (githubToken) {
|
||||
try {
|
||||
const token = await resolveCopilotApiToken({
|
||||
githubToken,
|
||||
env: ctx.env,
|
||||
});
|
||||
baseUrl = token.baseUrl;
|
||||
} catch {
|
||||
baseUrl = DEFAULT_COPILOT_API_BASE_URL;
|
||||
}
|
||||
}
|
||||
return {
|
||||
provider: {
|
||||
baseUrl,
|
||||
models: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
resolveDynamicModel: (ctx) => resolveCopilotForwardCompatModel(ctx),
|
||||
capabilities: {
|
||||
dropThinkingBlockModelHints: ["claude"],
|
||||
},
|
||||
prepareRuntimeAuth: async (ctx) => {
|
||||
const token = await resolveCopilotApiToken({
|
||||
githubToken: ctx.apiKey,
|
||||
env: ctx.env,
|
||||
});
|
||||
return {
|
||||
apiKey: token.token,
|
||||
baseUrl: token.baseUrl,
|
||||
expiresAt: token.expiresAt,
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default githubCopilotPlugin;
|
||||
Reference in New Issue
Block a user