English reports hid the analysis fold, timeline titles left out the month span, and deleting a pinned model stopped the next consultation turn. Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
4.4 KiB
TypeScript
106 lines
4.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { reconcileConsultSessionModel } from "../src/lib/consult/session-model-fallback.ts";
|
|
import { loadConsultTurnHead } from "../src/lib/consult/turn-prep.ts";
|
|
import type { ResolvedLanguageModel } from "../src/mastra/model.ts";
|
|
|
|
function model(id: string, version: number): ResolvedLanguageModel {
|
|
return {
|
|
id,
|
|
label: id,
|
|
description: "",
|
|
creditCost: 1,
|
|
isDefault: id === "default-model",
|
|
mode: "openai",
|
|
model: { providerId: "openai", modelId: id, apiKey: "sk-test" },
|
|
configVersion: version,
|
|
};
|
|
}
|
|
|
|
test("a session pinned to a deleted version continues on the default model", async () => {
|
|
const writes: Array<string | null> = [];
|
|
const decision = await reconcileConsultSessionModel({
|
|
session: { id: "session-1", model_id: "retired-model", model_config_version: 4 },
|
|
requestedModelId: "retired-model",
|
|
resolveBound: async (modelId) => modelId === "retired-model" ? null : model(modelId, 1),
|
|
loadCatalog: async () => ({
|
|
models: [model("default-model", 2)],
|
|
publicModels: [],
|
|
defaultModelId: "default-model",
|
|
issues: [],
|
|
}),
|
|
persistModelId: async (_sessionId, modelId) => {
|
|
writes.push(modelId);
|
|
return true;
|
|
},
|
|
});
|
|
assert.equal(decision.kind, "ready");
|
|
if (decision.kind !== "ready") return;
|
|
assert.equal(decision.model.id, "default-model");
|
|
assert.equal(decision.session.model_id, "default-model");
|
|
assert.equal(decision.session.model_config_version, 2);
|
|
assert.deepEqual(writes, ["default-model"]);
|
|
});
|
|
|
|
test("the next turn still uses the default when the client sends the deleted model id", async () => {
|
|
const decision = await reconcileConsultSessionModel({
|
|
session: { id: "session-1", model_id: "default-model", model_config_version: 2 },
|
|
requestedModelId: "retired-model",
|
|
resolveBound: async (modelId, version) => {
|
|
if (modelId === "retired-model") return null;
|
|
if (modelId === "default-model" && version === 2) return model("default-model", 2);
|
|
if (modelId === "default-model" && version == null) return model("default-model", 2);
|
|
return null;
|
|
},
|
|
loadCatalog: async () => ({ models: [], publicModels: [], defaultModelId: null, issues: [] }),
|
|
persistModelId: async () => {
|
|
throw new Error("persist should not run");
|
|
},
|
|
});
|
|
assert.equal(decision.kind, "ready");
|
|
if (decision.kind !== "ready") return;
|
|
assert.equal(decision.model.id, "default-model");
|
|
});
|
|
|
|
test("a live different model is still refused as a model change", async () => {
|
|
const decision = await reconcileConsultSessionModel({
|
|
session: { id: "session-1", model_id: "default-model", model_config_version: 2 },
|
|
requestedModelId: "other-model",
|
|
resolveBound: async (modelId) => model(modelId, modelId === "other-model" ? 1 : 2),
|
|
loadCatalog: async () => ({ models: [], publicModels: [], defaultModelId: null, issues: [] }),
|
|
persistModelId: async () => false,
|
|
});
|
|
assert.equal(decision.kind, "changed");
|
|
});
|
|
|
|
test("a deleted version with no default model still reports the model as unavailable", async () => {
|
|
const head = await loadConsultTurnHead({
|
|
parallel: false,
|
|
getUser: async () => ({ data: { user: { id: "user-1" } }, error: null }),
|
|
readPendingDeletion: async () => null,
|
|
readBody: async () => ({ success: true as const, data: { sessionId: "session-1", modelId: "retired-model" } }),
|
|
readSession: async () => ({
|
|
data: { id: "session-1", model_id: "retired-model", model_config_version: 4, session_type: "consultation" },
|
|
error: null,
|
|
}),
|
|
resolveModel: async (): Promise<ResolvedLanguageModel | null> => null,
|
|
reconcileModel: async (chatSession, requestedModelId) => {
|
|
const decision = await reconcileConsultSessionModel({
|
|
session: chatSession,
|
|
requestedModelId,
|
|
resolveBound: async () => null,
|
|
loadCatalog: async () => ({ models: [], publicModels: [], defaultModelId: null, issues: ["default_model_unavailable"] }),
|
|
persistModelId: async () => false,
|
|
});
|
|
if (decision.kind !== "ready") return decision;
|
|
return { kind: "ready" as const, chatSession: decision.session, sessionModel: decision.model };
|
|
},
|
|
});
|
|
assert.equal(head.ok, false);
|
|
if (head.ok) return;
|
|
assert.equal(head.response.status, 503);
|
|
const body = await head.response.json() as { error?: string };
|
|
assert.equal(body.error, "模型暂不可用");
|
|
});
|