import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk"; const DEFAULT_BASE_URL = "http://localhost:3000/v1"; const DEFAULT_API_KEY = "n/a"; const DEFAULT_CONTEXT_WINDOW = 128_000; const DEFAULT_MAX_TOKENS = 8192; const DEFAULT_MODEL_IDS = [ "gpt-5.2", "gpt-5.2-codex", "gpt-5.1", "gpt-5.1-codex", "gpt-5.1-codex-max", "gpt-5-mini", "claude-opus-4.5", "claude-sonnet-4.5", "claude-haiku-4.5", "gemini-3-pro", "gemini-3-flash", "grok-code-fast-1", ] as const; function normalizeBaseUrl(value: string): string { const trimmed = value.trim(); if (!trimmed) return DEFAULT_BASE_URL; let normalized = trimmed; while (normalized.endsWith("/")) normalized = normalized.slice(0, -1); if (!normalized.endsWith("/v1")) normalized = `${normalized}/v1`; return normalized; } function validateBaseUrl(value: string): string | undefined { const normalized = normalizeBaseUrl(value); try { new URL(normalized); } catch { return "Enter a valid URL"; } return undefined; } function parseModelIds(input: string): string[] { const parsed = input .split(/[\n,]/) .map((model) => model.trim()) .filter(Boolean); return Array.from(new Set(parsed)); } function buildModelDefinition(modelId: string) { return { id: modelId, name: modelId, api: "openai-completions", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: DEFAULT_CONTEXT_WINDOW, maxTokens: DEFAULT_MAX_TOKENS, }; } const copilotProxyPlugin = { id: "copilot-proxy", name: "Copilot Proxy", description: "Local Copilot Proxy (VS Code LM) provider plugin", configSchema: emptyPluginConfigSchema(), register(api) { api.registerProvider({ id: "copilot-proxy", label: "Copilot Proxy", docsPath: "/providers/models", auth: [ { id: "local", label: "Local proxy", hint: "Configure base URL + models for the Copilot Proxy server", kind: "custom", run: async (ctx) => { const baseUrlInput = await ctx.prompter.text({ message: "Copilot Proxy base URL", initialValue: DEFAULT_BASE_URL, validate: validateBaseUrl, }); const modelInput = await ctx.prompter.text({ message: "Model IDs (comma-separated)", initialValue: DEFAULT_MODEL_IDS.join(", "), validate: (value) => parseModelIds(value).length > 0 ? undefined : "Enter at least one model id", }); const baseUrl = normalizeBaseUrl(baseUrlInput); const modelIds = parseModelIds(modelInput); const defaultModelId = modelIds[0] ?? DEFAULT_MODEL_IDS[0]; const defaultModelRef = `copilot-proxy/${defaultModelId}`; return { profiles: [ { profileId: "copilot-proxy:local", credential: { type: "token", provider: "copilot-proxy", token: DEFAULT_API_KEY, }, }, ], configPatch: { models: { providers: { "copilot-proxy": { baseUrl, apiKey: DEFAULT_API_KEY, api: "openai-completions", authHeader: false, models: modelIds.map((modelId) => buildModelDefinition(modelId)), }, }, }, agents: { defaults: { models: Object.fromEntries( modelIds.map((modelId) => [`copilot-proxy/${modelId}`, {}]), ), }, }, }, defaultModel: defaultModelRef, notes: [ "Start the Copilot Proxy VS Code extension before using these models.", "Copilot Proxy serves /v1/chat/completions; base URL must include /v1.", "Model availability depends on your Copilot plan; edit models.providers.copilot-proxy if needed.", ], }; }, }, ], }); }, }; export default copilotProxyPlugin;