























@@ -907,7 +907,7 @@ describe("runWithModelFallback", () => {
907907});
908908});
909909910-it("falls back directly to configured primary when an override model fails", async () => {
910+it("puts configured primary next when an override model is requested", () => {
911911const cfg = makeCfg({
912912agents: {
913913defaults: {
@@ -919,31 +919,19 @@ describe("runWithModelFallback", () => {
919919},
920920});
921921922-const run = createOverrideFailureRun({
923-overrideProvider: "anthropic",
924-overrideModel: "claude-opus-4-5",
925-fallbackProvider: "openai",
926-fallbackModel: "gpt-4.1-mini",
927-firstError: Object.assign(new Error("unauthorized"), { status: 401 }),
928-});
929-930-const result = await runWithModelFallback({
931- cfg,
932-provider: "anthropic",
933-model: "claude-opus-4-5",
934- run,
935-});
936-937-expect(result.result).toBe("ok");
938-expect(result.provider).toBe("openai");
939-expect(result.model).toBe("gpt-4.1-mini");
940-expect(run.mock.calls).toEqual([
941-["anthropic", "claude-opus-4-5"],
942-["openai", "gpt-4.1-mini"],
922+expect(
923+__testing.resolveFallbackCandidates({
924+ cfg,
925+provider: "anthropic",
926+model: "claude-opus-4-5",
927+}),
928+).toEqual([
929+{ provider: "anthropic", model: "claude-opus-4-5" },
930+{ provider: "openai", model: "gpt-4.1-mini" },
943931]);
944932});
945933946-it("keeps configured fallback chain when current model is a configured fallback", async () => {
934+it("keeps configured fallback chain when current model is a configured fallback", () => {
947935const cfg = makeCfg({
948936agents: {
949937defaults: {
@@ -955,33 +943,20 @@ describe("runWithModelFallback", () => {
955943},
956944});
957945958-const run = vi.fn().mockImplementation(async (provider: string, model: string) => {
959-if (provider === "anthropic" && model === "claude-haiku-3-5") {
960-throw Object.assign(new Error("rate-limited"), { status: 429 });
961-}
962-if (provider === "openrouter" && model === "openrouter/deepseek-chat") {
963-return "ok";
964-}
965-throw new Error(`unexpected fallback candidate: ${provider}/${model}`);
966-});
967-968-const result = await runWithModelFallback({
969- cfg,
970-provider: "anthropic",
971-model: "claude-haiku-3-5",
972- run,
973-});
974-975-expect(result.result).toBe("ok");
976-expect(result.provider).toBe("openrouter");
977-expect(result.model).toBe("openrouter/deepseek-chat");
978-expect(run.mock.calls).toEqual([
979-["anthropic", "claude-haiku-3-5"],
980-["openrouter", "openrouter/deepseek-chat"],
946+expect(
947+__testing.resolveFallbackCandidates({
948+ cfg,
949+provider: "anthropic",
950+model: "claude-haiku-3-5",
951+}),
952+).toEqual([
953+{ provider: "anthropic", model: "claude-haiku-3-5" },
954+{ provider: "openrouter", model: "openrouter/deepseek-chat" },
955+{ provider: "openai", model: "gpt-4.1-mini" },
981956]);
982957});
983958984-it("treats normalized default refs as primary and keeps configured fallback chain", async () => {
959+it("treats normalized default refs as primary and keeps configured fallback chain", () => {
985960const cfg = makeCfg({
986961agents: {
987962defaults: {
@@ -993,22 +968,39 @@ describe("runWithModelFallback", () => {
993968},
994969});
995970996-const run = vi
997-.fn()
998-.mockRejectedValueOnce(Object.assign(new Error("nope"), { status: 401 }))
999-.mockResolvedValueOnce("ok");
971+expect(
972+__testing.resolveFallbackCandidates({
973+ cfg,
974+provider: " OpenAI ",
975+model: "gpt-4.1-mini",
976+}),
977+).toEqual([
978+{ provider: "openai", model: "gpt-4.1-mini" },
979+{ provider: "anthropic", model: "claude-haiku-3-5" },
980+]);
981+});
982+983+it("falls back to configured primary for override credential validation errors", async () => {
984+const cfg = makeCfg();
985+const run = createOverrideFailureRun({
986+overrideProvider: "anthropic",
987+overrideModel: "claude-opus-4",
988+fallbackProvider: "openai",
989+fallbackModel: "gpt-4.1-mini",
990+firstError: new Error('No credentials found for profile "anthropic:default".'),
991+});
10009921001993const result = await runWithModelFallback({
1002994 cfg,
1003-provider: " OpenAI ",
1004-model: "gpt-4.1-mini",
995+provider: "anthropic",
996+model: "claude-opus-4",
1005997 run,
1006998});
100799910081000expect(result.result).toBe("ok");
10091001expect(run.mock.calls).toEqual([
1002+["anthropic", "claude-opus-4"],
10101003["openai", "gpt-4.1-mini"],
1011-["anthropic", "claude-haiku-3-5"],
10121004]);
10131005});
10141006@@ -1031,30 +1023,6 @@ describe("runWithModelFallback", () => {
10311023expect(result.attempts[0]?.reason).toBe("billing");
10321024});
103310251034-it("falls back to configured primary for override credential validation errors", async () => {
1035-const cfg = makeCfg();
1036-const run = createOverrideFailureRun({
1037-overrideProvider: "anthropic",
1038-overrideModel: "claude-opus-4",
1039-fallbackProvider: "openai",
1040-fallbackModel: "gpt-4.1-mini",
1041-firstError: new Error('No credentials found for profile "anthropic:default".'),
1042-});
1043-1044-const result = await runWithModelFallback({
1045- cfg,
1046-provider: "anthropic",
1047-model: "claude-opus-4",
1048- run,
1049-});
1050-1051-expect(result.result).toBe("ok");
1052-expect(run.mock.calls).toEqual([
1053-["anthropic", "claude-opus-4"],
1054-["openai", "gpt-4.1-mini"],
1055-]);
1056-});
1057-10581026it("falls back on model-not-found error shapes", async () => {
10591027const cases: Array<{
10601028name: string;
@@ -1257,7 +1225,7 @@ describe("runWithModelFallback", () => {
12571225expect(result.attempts).toEqual([]);
12581226});
125912271260-it("does not append configured primary when fallbacksOverride is set", async () => {
1228+it("does not append configured primary when fallbacksOverride is set", () => {
12611229const cfg = makeCfg({
12621230agents: {
12631231defaults: {
@@ -1267,23 +1235,17 @@ describe("runWithModelFallback", () => {
12671235},
12681236},
12691237});
1270-const run = vi
1271-.fn()
1272-.mockImplementation(() => Promise.reject(Object.assign(new Error("nope"), { status: 401 })));
127312381274-await expect(
1275-runWithModelFallback({
1239+expect(
1240+__testing.resolveFallbackCandidates({
12761241 cfg,
12771242provider: "anthropic",
12781243model: "claude-opus-4-5",
12791244fallbacksOverride: ["anthropic/claude-haiku-3-5"],
1280- run,
12811245}),
1282-).rejects.toThrow("All models failed");
1283-1284-expect(run.mock.calls).toEqual([
1285-["anthropic", "claude-opus-4-5"],
1286-["anthropic", "claude-haiku-3-5"],
1246+).toEqual([
1247+{ provider: "anthropic", model: "claude-opus-4-5" },
1248+{ provider: "anthropic", model: "claude-haiku-3-5" },
12871249]);
12881250});
12891251此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。