test/default-ai-client.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("node:fs");
var vm = require("node:vm");
function load() {
var source = fs.readFileSync(require("node:path").join(__dirname, "../lib/public/modules/default-ai.js"), "utf8");
source = source.replace(/^import .*;\n/gm, "").replace(/export function /g, "function ");
source += "\nthis.api={beginDefaultAiConnection:beginDefaultAiConnection,handleDefaultAiMessage:handleDefaultAiMessage,requestDefaultAi:requestDefaultAi};";
var state = { defaultAiState: { catalogs: {}, installedVendors: [] }, defaultAiDraft: { vendor: "", model: "", effort: "" }, defaultAiDraftDirty: false };
var listeners = [];
var context = { store: { get: function (key) { return state[key]; }, set: function (value) { var previous = state; state = Object.assign({}, state, value); listeners.forEach(function (listener) { listener(state, previous); }); }, subscribe: function (listener) { listeners.push(listener); } }, getWs: function () { return null; }, VENDOR_AVATARS: { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png" }, VENDOR_NAMES: {}, refreshIcons: function () {}, document: { querySelector: function () { return null; } }, window: {}, console: console, Date: Date, Math: Math, setTimeout: setTimeout, clearTimeout: clearTimeout };
vm.runInNewContext(source, context);
return { api: context.api, getState: function () { return state; }, setState: function (value) { state = Object.assign({}, state, value); }, storeSet: context.store.set };
}
test("client handler caches first-load catalog and seeds effective draft", function () {
var h = load();
h.getState().defaultAiState.refreshRequestId = "r";
h.api.handleDefaultAiMessage({ type: "default_ai_state", requestId: "r", installedVendors: ["codex"], preference: null, selection: { vendor: "codex", model: "gpt-6-astra", effort: "medium" }, catalog: { status: "ready", models: [{ value: "gpt-6-astra" }] }, ready: true });
assert.equal(h.getState().defaultAiDraft.model, "gpt-6-astra");
assert.equal(h.getState().defaultAiState.catalogs.codex.models[0].value, "gpt-6-astra");
});
test("client ignores stale catalogs and preserves dirty draft on canonical broadcasts/errors", function () {
var h = load();
h.getState().defaultAiState.refreshRequestId = "r";
h.api.handleDefaultAiMessage({ type: "default_ai_state", requestId: "r", installedVendors: ["codex"], preference: { vendor: "codex", model: "old", effort: "" }, selection: { vendor: "codex", model: "old", effort: "" }, ready: true });
h.getState().defaultAiDraftDirty = true;
h.getState().defaultAiDraft.model = "draft";
h.api.handleDefaultAiMessage({ type: "default_ai_catalog", requestId: "wrong", vendor: "codex", catalog: { models: [{ value: "bad" }] } });
assert.equal(h.getState().defaultAiState.catalogs.codex, undefined);
h.api.handleDefaultAiMessage({ type: "default_ai_state", preference: { vendor: "codex", model: "new", effort: "" }, selection: { vendor: "codex", model: "new", effort: "" }, ready: true });
assert.equal(h.getState().defaultAiDraft.model, "draft");
h.getState().defaultAiState.saveRequestId = "save";
h.getState().defaultAiState.saving = true;
h.api.handleDefaultAiMessage({ type: "default_ai_state", requestId: "save", ready: false, error: "save failed" });
assert.equal(h.getState().defaultAiState.error, "save failed");
assert.equal(h.getState().defaultAiState.selection.model, "new");
assert.equal(h.getState().defaultAiDraft.model, "draft");
});
test("client applies catalog recommendation to an empty vendor draft and preserves cleared effort", function () {
var h = load();
h.setState({
defaultAiState: { catalogs: {}, catalogRequestIds: { codex: "catalog" }, installedVendors: ["codex"], preference: { vendor: "codex", model: "old", effort: "high" }, selection: { vendor: "codex", model: "old", effort: "high" } },
defaultAiDraft: { vendor: "codex", model: "", effort: "" },
defaultAiDraftDirty: true,
});
h.api.handleDefaultAiMessage({ type: "default_ai_catalog", requestId: "catalog", vendor: "codex", installedVendors: ["codex"], selection: { vendor: "codex", model: "gpt-6-astra", effort: "" }, catalog: { status: "ready", models: [{ value: "gpt-6-astra", supportedEffortLevels: ["medium", "high"] }] }, ready: true });
assert.deepEqual(JSON.parse(JSON.stringify(h.getState().defaultAiDraft)), { vendor: "codex", model: "gpt-6-astra", effort: "" });
assert.equal(h.getState().defaultAiDraftDirty, true);
});
test("broadcasts preserve unsaved draft and outstanding request correlation", function () {
var h = load();
h.setState({
defaultAiState: { catalogs: {}, installedVendors: ["codex"], saveRequestId: "save", saving: true, refreshRequestId: "refresh", loading: true },
defaultAiDraft: { vendor: "codex", model: "draft", effort: "" },
defaultAiDraftDirty: true,
});
h.api.handleDefaultAiMessage({ type: "default_ai_state", requestId: null, ready: true, preference: { vendor: "codex", model: "canonical", effort: "medium" }, selection: { vendor: "codex", model: "canonical", effort: "medium" } });
assert.equal(h.getState().defaultAiDraft.model, "draft");
assert.equal(h.getState().defaultAiState.saveRequestId, "save");
assert.equal(h.getState().defaultAiState.refreshRequestId, "refresh");
});
test("account loss disables the picker without replacing its last canonical badge", function () {
var h = load();
h.setState({
defaultAiState: { catalogs: {}, installedVendors: ["codex"], refreshRequestId: "account", loading: true, selection: { vendor: "codex", model: "gpt-6-astra", effort: "" } },
defaultAiDraft: { vendor: "codex", model: "gpt-6-astra", effort: "" },
defaultAiDraftDirty: false,
});
h.api.handleDefaultAiMessage({ type: "default_ai_state", requestId: "account", ready: false, accountAvailable: false, error: "Your account is no longer available." });
assert.equal(h.getState().defaultAiState.accountAvailable, false);
assert.equal(h.getState().defaultAiState.selection.model, "gpt-6-astra");
assert.equal(h.getState().defaultAiState.refreshRequestId, null);
});
test("an unavailable saved choice remains visible as the canonical draft", function () {
var h = load();
h.getState().defaultAiState.refreshRequestId = "unavailable";
h.api.handleDefaultAiMessage({ type: "default_ai_state", requestId: "unavailable", ready: false, installedVendors: ["codex", "claude"], preference: { vendor: "kiro", model: "saved-unavailable", effort: "high" }, error: "Default AI vendor is unavailable." });
assert.deepEqual(JSON.parse(JSON.stringify(h.getState().defaultAiDraft)), { vendor: "kiro", model: "saved-unavailable", effort: "high" });
assert.equal(h.getState().defaultAiState.preference.model, "saved-unavailable");
});
test("an unavailable saved model retains its failed catalog for explicit rendering", function () {
var h = load();
h.getState().defaultAiState.refreshRequestId = "catalog-error";
h.api.handleDefaultAiMessage({ type: "default_ai_state", requestId: "catalog-error", ready: false, installedVendors: ["codex"], preference: { vendor: "codex", model: "retired-model", effort: "high" }, catalog: { status: "ready", models: [{ value: "gpt-6-astra" }] }, error: "The saved Default AI model is unavailable." });
assert.equal(h.getState().defaultAiState.catalogs.codex.models[0].value, "gpt-6-astra");
assert.match(h.getState().defaultAiState.catalogs.codex.error, /saved Default AI model/);
assert.equal(h.getState().defaultAiState.preference.model, "retired-model");
});
test("account changes clear canonical and draft state and reject old-account replies", function () {
var h = load();
h.setState({
isMultiUserMode: true,
myUserId: "u1",
connected: false,
defaultAiState: { accountId: "u1", canonicalRevision: 4, catalogs: { codex: {} }, catalogRequestIds: { codex: "catalog-old" }, refreshRequestId: "refresh-old", saveRequestId: "save-old", installedVendors: ["codex"], selection: { vendor: "codex", model: "gpt-6-astra", effort: "" } },
defaultAiDraft: { vendor: "codex", model: "draft", effort: "" },
defaultAiDraftDirty: true,
});
h.storeSet({ myUserId: "u2" });
assert.deepEqual(JSON.parse(JSON.stringify(h.getState().defaultAiDraft)), { vendor: "", model: "", effort: "" });
assert.equal(h.getState().defaultAiState.selection, null);
assert.deepEqual(JSON.parse(JSON.stringify(h.getState().defaultAiState.catalogRequestIds)), {});
h.api.handleDefaultAiMessage({ type: "default_ai_state", accountId: "u1", canonicalRevision: 5, ready: true, preference: { vendor: "claude", model: "opus", effort: "" }, selection: { vendor: "claude", model: "opus", effort: "" } });
assert.equal(h.getState().defaultAiState.selection, null);
});
test("an older canonical reply cannot replace a newer saved selection", function () {
var h = load();
h.setState({
defaultAiState: { accountId: "default", canonicalRevision: 2, catalogs: {}, installedVendors: ["codex", "claude"], refreshRequestId: "slow-get", selection: { vendor: "codex", model: "gpt-6-astra", effort: "high" } },
defaultAiDraft: { vendor: "codex", model: "gpt-6-astra", effort: "high" },
defaultAiDraftDirty: false,
});
h.api.handleDefaultAiMessage({ type: "default_ai_state", accountId: "default", canonicalRevision: 1, requestId: "slow-get", ready: true, preference: { vendor: "claude", model: "opus", effort: "" }, selection: { vendor: "claude", model: "opus", effort: "" } });
assert.equal(h.getState().defaultAiState.selection.vendor, "codex");
assert.equal(h.getState().defaultAiDraft.vendor, "codex");
});
test("a same-user reconnect accepts a new server epoch at revision zero and rejects stale epochs", function () {
var h = load();
h.setState({
isMultiUserMode: true,
myUserId: "u1",
defaultAiState: { accountId: "u1", serverEpoch: "old-server", canonicalRevision: 5, catalogs: {}, catalogRequestIds: { codex: "old-catalog" }, refreshRequestId: "old-refresh", selection: { vendor: "codex", model: "old", effort: "" } },
defaultAiDraft: { vendor: "codex", model: "unsaved", effort: "high" },
defaultAiDraftDirty: true,
});
h.api.beginDefaultAiConnection();
assert.equal(h.getState().defaultAiState.serverEpoch, null);
assert.equal(h.getState().defaultAiState.canonicalRevision, 0);
assert.equal(h.getState().defaultAiState.refreshRequestId, null);
assert.equal(h.getState().defaultAiDraft.model, "unsaved");
h.getState().defaultAiState.refreshRequestId = "new-refresh";
h.api.handleDefaultAiMessage({ type: "default_ai_state", accountId: "u1", serverEpoch: "old-server", canonicalRevision: 5, requestId: "old-refresh", ready: true, preference: { vendor: "codex", model: "stale", effort: "" }, selection: { vendor: "codex", model: "stale", effort: "" } });
assert.equal(h.getState().defaultAiState.selection, null);
h.api.handleDefaultAiMessage({ type: "default_ai_state", accountId: "u1", serverEpoch: "new-server", canonicalRevision: 0, requestId: "new-refresh", ready: true, preference: { vendor: "claude", model: "fresh", effort: "" }, selection: { vendor: "claude", model: "fresh", effort: "" } });
assert.equal(h.getState().defaultAiState.serverEpoch, "new-server");
assert.equal(h.getState().defaultAiState.canonicalRevision, 0);
assert.equal(h.getState().defaultAiState.selection.model, "fresh");
h.api.handleDefaultAiMessage({ type: "default_ai_state", accountId: "u1", serverEpoch: "new-server", canonicalRevision: 2, ready: true, preference: { vendor: "claude", model: "newest", effort: "" }, selection: { vendor: "claude", model: "newest", effort: "" } });
h.api.handleDefaultAiMessage({ type: "default_ai_state", accountId: "u1", serverEpoch: "new-server", canonicalRevision: 1, ready: true, preference: { vendor: "codex", model: "same-epoch-stale", effort: "" }, selection: { vendor: "codex", model: "same-epoch-stale", effort: "" } });
h.api.handleDefaultAiMessage({ type: "default_ai_state", accountId: "u1", serverEpoch: "old-server", canonicalRevision: 6, ready: true, preference: { vendor: "codex", model: "old-epoch", effort: "" }, selection: { vendor: "codex", model: "old-epoch", effort: "" } });
assert.equal(h.getState().defaultAiState.selection.model, "newest");
assert.equal(h.getState().defaultAiState.canonicalRevision, 2);
});
test("the WebSocket open path resets Default AI correlation before refreshing", function () {
var source = fs.readFileSync(require("node:path").join(__dirname, "../lib/public/modules/app-connection.js"), "utf8");
assert.match(source, /beginDefaultAiConnection\(\);\s*requestDefaultAi\(\);/);
});
test("the Default AI opener delegates to the inline Account surface", function () {
var source = fs.readFileSync(require("node:path").join(__dirname, "../lib/public/modules/default-ai.js"), "utf8");
assert.match(source, /clay:open-account-default-ai/);
assert.match(source, /export function mountDefaultAi/);
assert.match(source, /export function unmountDefaultAi/);
assert.doesNotMatch(source, /default-ai-btn/);
assert.doesNotMatch(source, /avatar\.innerHTML = source/);
});
test("picker source exposes a visible reasoning label and no runtime eyebrow", function () {
var source = fs.readFileSync(require("node:path").join(__dirname, "../lib/public/modules/default-ai.js"), "utf8");
assert.match(source, /addFieldLabel\("Reasoning effort", effort\)/);
assert.doesNotMatch(source, /Clay runtime/);
});