66 lines
5.3 KiB
TypeScript
66 lines
5.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|||
|
|
import { it } from "node:test";
|
||
|
|
import { sanitizeUserSettings, type ModelsSettings, type UserSettings } from "../src/core/settings.ts";
|
||
|
|
import { resolveConversationSpec, resolveModelSpec, resolveRoleSpec, resolveSettings } from "../src/core/settings-resolution.ts";
|
||
|
|
import { parseModelsSettingsBody } from "../src/server/settings-body.ts";
|
||
|
|
import { clampModelsLevels } from "../src/server/model-catalog.ts";
|
||
|
|
import { composeSlotFast, composeSlotModel } from "../components/settings/agent-section.tsx";
|
||
|
|
import { chatGPTPayload, chatGPTStreamError } from "../src/agent/chatgpt-stream.ts";
|
||
|
|
import { translate } from "../src/core/i18n.ts";
|
||
|
|
|
||
|
|
const chatgpt = { provider: "chatgpt", id: "gpt-5" };
|
||
|
|
const api = { provider: "openai", id: "gpt-5" };
|
||
|
|
it("validates and saves explicit per-tier Fast choices, dropping invalid and unsupported saved flags", () => {
|
||
|
|
const models = { strong: { model: chatgpt, thinkingLevel: "high", fastMode: true }, cheap: { model: chatgpt, fastMode: false }, vision: { model: api } };
|
||
|
|
assert.deepEqual(parseModelsSettingsBody(models), { ok: true, settings: models });
|
||
|
|
assert.deepEqual(sanitizeUserSettings({ version: 1, models })?.models, models);
|
||
|
|
for (const fastMode of ["true", 1, null]) assert.equal(parseModelsSettingsBody({ strong: { model: chatgpt, fastMode } }).ok, false);
|
||
|
|
assert.equal(parseModelsSettingsBody({ strong: { model: api, fastMode: true } }).ok, false);
|
||
|
|
assert.deepEqual(sanitizeUserSettings({ version: 1, models: { strong: { model: api, fastMode: true }, cheap: { model: chatgpt, fastMode: "true" } } })?.models, { strong: { model: api }, cheap: { model: chatgpt } });
|
||
|
|
});
|
||
|
|
it("toggles one tier without altering its model, reasoning, or siblings; a provider swap clears Fast", () => {
|
||
|
|
const models: ModelsSettings = { strong: { model: chatgpt, thinkingLevel: "high" }, cheap: { model: api, thinkingLevel: "low" } };
|
||
|
|
const enabled = composeSlotFast(models, "strong", true);
|
||
|
|
assert.deepEqual(enabled, { ...models, strong: { ...models.strong, fastMode: true } });
|
||
|
|
assert.equal(models.strong?.fastMode, undefined);
|
||
|
|
assert.deepEqual(composeSlotFast(enabled, "cheap", true), enabled);
|
||
|
|
assert.equal(composeSlotModel(enabled, "strong", "chatgpt", "another-model", "low").strong?.fastMode, true);
|
||
|
|
assert.equal(composeSlotModel(enabled, "strong", "openai", "gpt-5", "high").strong?.fastMode, undefined);
|
||
|
|
assert.equal(composeSlotFast(enabled, "strong", false).strong?.fastMode, false);
|
||
|
|
});
|
||
|
|
it("updates existing conversations on the next resolution without replacing their saved model or reasoning", () => {
|
||
|
|
const chat = { model: { provider: "chatgpt", id: "saved-chat-model" }, thinkingLevel: "low" as const };
|
||
|
|
const before = structuredClone(chat);
|
||
|
|
const strong = { provider: "chatgpt", id: "new-session-default", thinkingLevel: "high" as const, fastMode: true };
|
||
|
|
assert.deepEqual(resolveConversationSpec(chat, strong), { provider: "chatgpt", id: "saved-chat-model", thinkingLevel: "low", fastMode: true });
|
||
|
|
assert.equal(resolveConversationSpec(chat, { ...strong, fastMode: false })?.fastMode, undefined);
|
||
|
|
assert.equal(resolveConversationSpec({ ...chat, model: api }, strong)?.fastMode, undefined);
|
||
|
|
assert.equal(resolveConversationSpec(chat, { ...strong, provider: "openai" })?.fastMode, undefined);
|
||
|
|
assert.deepEqual(chat, before);
|
||
|
|
});
|
||
|
|
it("background work uses only its mapped tier's Fast preference and never opts another provider in", () => {
|
||
|
|
const settings: UserSettings = { version: 1, models: { strong: { model: chatgpt, fastMode: true }, cheap: { model: chatgpt, fastMode: false }, vision: { model: chatgpt, fastMode: true } } };
|
||
|
|
assert.equal(resolveRoleSpec("researcher", settings)?.fastMode, undefined);
|
||
|
|
assert.equal(resolveRoleSpec("visualMaker", settings)?.fastMode, true);
|
||
|
|
assert.equal(resolveRoleSpec("pdfReader", settings)?.fastMode, true);
|
||
|
|
settings.models!.cheap!.fastMode = true;
|
||
|
|
assert.equal(resolveRoleSpec("researcher", settings)?.fastMode, true);
|
||
|
|
assert.equal(resolveModelSpec("researcher", { model: api }, settings.models)?.fastMode, undefined);
|
||
|
|
assert.equal(resolveSettings({ version: 1, models: { strong: { model: api, fastMode: true } } }).models.strong?.fastMode, undefined);
|
||
|
|
});
|
||
|
|
it("the server's reasoning clamp preserves Fast rather than silently dropping the opt-in on write", () => {
|
||
|
|
const models: ModelsSettings = { strong: { model: chatgpt, thinkingLevel: "high", fastMode: true }, cheap: { model: api, thinkingLevel: "low" } };
|
||
|
|
assert.equal(clampModelsLevels(models).strong?.fastMode, true);
|
||
|
|
assert.equal(clampModelsLevels(models).strong?.thinkingLevel, "high");
|
||
|
|
});
|
||
|
|
it("Fast is off for old records, request sanitization pins the chosen tier, and unsupported-mode errors are localized", () => {
|
||
|
|
const settings = resolveSettings({ version: 1, models: { strong: { model: chatgpt } } });
|
||
|
|
assert.equal(settings.models.strong?.fastMode, undefined);
|
||
|
|
assert.equal(chatGPTPayload({ input: [], service_tier: "priority" }, "gpt-5").service_tier, undefined);
|
||
|
|
assert.equal(chatGPTPayload({ input: [], service_tier: "flex" }, "gpt-5", true).service_tier, "priority");
|
||
|
|
const error = chatGPTStreamError("400 invalid service_tier");
|
||
|
|
assert(error.includes("Turn off Fast mode"));
|
||
|
|
assert(translate("ru", error).includes("Отключите быстрый режим"));
|
||
|
|
assert(chatGPTStreamError("429 service_tier quota exceeded").includes("https://chatgpt.com/settings/usage"));
|
||
|
|
});
|