import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { ValidationError, readModelsResponse, saveModelsRequest } from "../src/dashboard/api/models.js"; function createConfigFile(config: unknown): { configPath: string; cleanup: () => void } { const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-dashboard-models-")); const workspaceDir = join(tempDir, "workspace"); const configPath = join(workspaceDir, "config.json"); mkdirSync(workspaceDir, { recursive: true }); writeFileSync(configPath, JSON.stringify(config, null, 2)); return { configPath, cleanup: () => rmSync(tempDir, { recursive: true, force: true }), }; } test("readModelsResponse exposes default model and selectable options", () => { const { configPath, cleanup } = createConfigFile({ agents: { defaults: { model: { primary: "coze/auto", }, }, }, models: { providers: { coze: { api: "openai-completions", apiKey: "${COZE_API_KEY}", baseUrl: "https://coze.example.com/v1", authHeader: true, models: [ { id: "auto", name: "Auto", input: ["text", "image"], reasoning: true, contextWindow: 200000, maxTokens: 8192, }, ], }, }, }, }); const result = readModelsResponse({ configPath }); cleanup(); assert.equal(result.defaultModel, "coze/auto"); assert.deepEqual(result.options, [{ value: "coze/auto", label: "coze / Auto" }]); }); test("saveModelsRequest updates default model while preserving unrelated config", () => { const { configPath, cleanup } = createConfigFile({ agents: { defaults: { model: { primary: "coze/auto", }, }, }, models: { providers: { coze: { api: "openai-completions", apiKey: "${COZE_API_KEY}", baseUrl: "https://coze.example.com/v1", headers: { "X-Provider": "keep-me", }, models: [ { id: "auto", name: "Auto", input: ["text"], reasoning: false, contextWindow: 200000, maxTokens: 8192, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, }, }, { id: "glm-4.7", name: "GLM-4.7", input: ["text"], reasoning: false, contextWindow: 256000, maxTokens: 8192, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, }, }, ], }, }, }, channels: { feishu: { enabled: true, }, }, }); saveModelsRequest({ configPath, body: { models: { defaultModel: "coze/glm-4.7", }, }, }); const saved = JSON.parse(readFileSync(configPath, "utf-8")) as { agents: { defaults: { model: { primary: string } } }; models: { providers: { coze: { apiKey: string; baseUrl: string; authHeader: boolean; headers: Record; models: Array>; }; }; }; channels: { feishu: { enabled: boolean } }; }; cleanup(); assert.equal(saved.agents.defaults.model.primary, "coze/glm-4.7"); assert.deepEqual(saved.models.providers.coze.headers, { "X-Provider": "keep-me", }); assert.equal(saved.channels.feishu.enabled, true); }); test("saveModelsRequest rejects invalid defaultModel", () => { const { configPath, cleanup } = createConfigFile({ agents: { defaults: { model: { primary: "coze/auto" } } }, models: { providers: { coze: { models: [{ id: "auto", name: "Auto" }] } } }, }); try { assert.throws(() => { saveModelsRequest({ configPath, body: { models: { defaultModel: "coze/does-not-exist", }, }, }); }, ValidationError); } finally { cleanup(); } });