import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { discoverCodeBuddyModels, listCodeBuddyModels, resetCodeBuddyModelsCacheForTests, } from "@penclipai/adapter-codebuddy-local/server"; async function writeFakeCodeBuddyCommand(root: string, scriptBody: string): Promise { if (process.platform === "win32") { const scriptPath = path.join(root, "codebuddy.js"); const commandPath = path.join(root, "codebuddy.cmd"); await fs.writeFile(scriptPath, scriptBody, "utf8"); await fs.writeFile(commandPath, `@echo off\r\n"${process.execPath}" "${scriptPath}" %*\r\n`, "utf8"); return commandPath; } const commandPath = path.join(root, "codebuddy"); await fs.writeFile(commandPath, `#!/usr/bin/env node\n${scriptBody}`, "utf8"); await fs.chmod(commandPath, 0o755); return commandPath; } describe("codebuddy model discovery", () => { const cleanupDirs = new Set(); afterEach(async () => { delete process.env.PAPERCLIP_CODEBUDDY_COMMAND; resetCodeBuddyModelsCacheForTests(); await Promise.all(Array.from(cleanupDirs).map((dir) => fs.rm(dir, { recursive: true, force: true }))); cleanupDirs.clear(); }); it("parses the currently supported model list from codebuddy --help", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codebuddy-models-")); cleanupDirs.add(root); const command = await writeFakeCodeBuddyCommand( root, ` if (process.argv.includes("--help")) { console.log([ "Usage: codebuddy [options]", " --model Select model. Currently supported: (glm-5.0, glm-4.7, minimax-m2.7, glm-5.0, deepseek-v3-2-volc)", ].join("\\n")); process.exit(0); } process.exit(0); `, ); const models = await discoverCodeBuddyModels({ command, cwd: root }); expect(models.map((model) => model.id)).toEqual([ "deepseek-v3-2-volc", "glm-4.7", "glm-5.0", "minimax-m2.7", ]); }); it("caches model discovery for repeated listCodeBuddyModels calls", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codebuddy-models-cache-")); cleanupDirs.add(root); const hitsPath = path.join(root, "hits.txt"); process.env.PAPERCLIP_CODEBUDDY_COMMAND = await writeFakeCodeBuddyCommand( root, ` const fs = require("node:fs"); const hitsPath = ${JSON.stringify(hitsPath)}; if (process.argv.includes("--help")) { const hits = fs.existsSync(hitsPath) ? Number(fs.readFileSync(hitsPath, "utf8")) : 0; fs.writeFileSync(hitsPath, String(hits + 1), "utf8"); console.log("Usage: codebuddy [options]\\n --model Select model. Currently supported: (glm-5.0, kimi-k2.5)"); process.exit(0); } process.exit(0); `, ); const first = await listCodeBuddyModels(); const second = await listCodeBuddyModels(); expect(first).toEqual(second); expect(Number(await fs.readFile(hitsPath, "utf8"))).toBe(1); }); it("returns an empty list when help output omits the supported-model section", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codebuddy-models-empty-")); cleanupDirs.add(root); process.env.PAPERCLIP_CODEBUDDY_COMMAND = await writeFakeCodeBuddyCommand( root, ` if (process.argv.includes("--help")) { console.log("Usage: codebuddy [options]\\n --model Select model."); process.exit(0); } process.exit(0); `, ); await expect(listCodeBuddyModels()).resolves.toEqual([]); }); it("returns an empty list when the codebuddy help command fails", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codebuddy-models-fail-")); cleanupDirs.add(root); process.env.PAPERCLIP_CODEBUDDY_COMMAND = await writeFakeCodeBuddyCommand( root, ` if (process.argv.includes("--help")) { console.error("boom"); process.exit(1); } process.exit(0); `, ); await expect(listCodeBuddyModels()).resolves.toEqual([]); }); });