





























@@ -309,6 +309,258 @@ describe("compactEmbeddedPiSessionDirect hooks", () => {
309309);
310310});
311311312+it("uses the session model fallback chain when implicit compaction fails", async () => {
313+resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
314+model: { provider, api: "responses", id: modelId, input: [] },
315+error: null,
316+authStorage: { setRuntimeApiKey: vi.fn() },
317+modelRegistry: {},
318+}));
319+sessionCompactImpl
320+.mockRejectedValueOnce(
321+Object.assign(
322+new Error(
323+"400 The response was filtered due to the prompt triggering Azure OpenAI's content management policy.",
324+),
325+{ status: 400 },
326+),
327+)
328+.mockResolvedValueOnce({
329+summary: "fallback summary",
330+firstKeptEntryId: "entry-fallback",
331+tokensBefore: 120,
332+details: { ok: true },
333+});
334+335+const result = await compactEmbeddedPiSessionDirect({
336+sessionId: "session-1",
337+sessionKey: TEST_SESSION_KEY,
338+sessionFile: "/tmp/session.jsonl",
339+workspaceDir: "/tmp/workspace",
340+provider: "openai",
341+model: "gpt-primary",
342+config: {
343+agents: {
344+defaults: {
345+model: {
346+primary: "openai/gpt-primary",
347+fallbacks: ["anthropic/claude-fallback"],
348+},
349+},
350+},
351+} as never,
352+});
353+354+expect(result.ok).toBe(true);
355+expect(result.result?.summary).toBe("fallback summary");
356+expect(resolveModelMock).toHaveBeenCalledWith(
357+"openai",
358+"gpt-primary",
359+expect.any(String),
360+expect.anything(),
361+);
362+expect(resolveModelMock).toHaveBeenCalledWith(
363+"anthropic",
364+"claude-fallback",
365+expect.any(String),
366+expect.anything(),
367+);
368+});
369+370+it("uses the session model fallback chain when overflow compaction fails", async () => {
371+resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
372+model: { provider, api: "responses", id: modelId, input: [] },
373+error: null,
374+authStorage: { setRuntimeApiKey: vi.fn() },
375+modelRegistry: {},
376+}));
377+sessionCompactImpl
378+.mockRejectedValueOnce(
379+Object.assign(new Error("primary compaction rate limited"), {
380+status: 429,
381+code: "rate_limit_exceeded",
382+}),
383+)
384+.mockResolvedValueOnce({
385+summary: "overflow fallback summary",
386+firstKeptEntryId: "entry-fallback",
387+tokensBefore: 120,
388+details: { ok: true },
389+});
390+391+const result = await compactEmbeddedPiSessionDirect({
392+sessionId: "session-1",
393+sessionKey: TEST_SESSION_KEY,
394+sessionFile: "/tmp/session.jsonl",
395+workspaceDir: "/tmp/workspace",
396+provider: "openai",
397+model: "gpt-primary",
398+trigger: "overflow",
399+modelFallbacksOverride: ["anthropic/claude-fallback"],
400+config: {
401+agents: {
402+defaults: {
403+model: {
404+primary: "openai/gpt-primary",
405+fallbacks: [],
406+},
407+},
408+},
409+} as never,
410+});
411+412+expect(result.ok).toBe(true);
413+expect(result.result?.summary).toBe("overflow fallback summary");
414+expect(resolveModelMock).toHaveBeenCalledWith(
415+"openai",
416+"gpt-primary",
417+expect.any(String),
418+expect.anything(),
419+);
420+expect(resolveModelMock).toHaveBeenCalledWith(
421+"anthropic",
422+"claude-fallback",
423+expect.any(String),
424+expect.anything(),
425+);
426+});
427+428+it("keeps compaction fallback selection ephemeral", async () => {
429+resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
430+model: { provider, api: "responses", id: modelId, input: [] },
431+error: null,
432+authStorage: { setRuntimeApiKey: vi.fn() },
433+modelRegistry: {},
434+}));
435+sessionCompactImpl
436+.mockRejectedValueOnce(Object.assign(new Error("400 invalid request body"), { status: 400 }))
437+.mockResolvedValueOnce({
438+summary: "fallback summary",
439+firstKeptEntryId: "entry-fallback",
440+tokensBefore: 120,
441+details: { ok: true },
442+});
443+const config = {
444+agents: {
445+defaults: {
446+model: {
447+primary: "openai/gpt-primary",
448+fallbacks: ["anthropic/claude-fallback"],
449+},
450+},
451+},
452+sessions: {
453+entries: {
454+[TEST_SESSION_KEY]: {
455+modelProvider: "openai",
456+model: "gpt-primary",
457+},
458+},
459+},
460+};
461+const configBefore = structuredClone(config);
462+463+const result = await compactEmbeddedPiSessionDirect({
464+sessionId: "session-1",
465+sessionKey: TEST_SESSION_KEY,
466+sessionFile: "/tmp/session.jsonl",
467+workspaceDir: "/tmp/workspace",
468+provider: "openai",
469+model: "gpt-primary",
470+config: config as never,
471+});
472+473+expect(result.ok).toBe(true);
474+expect(config).toEqual(configBefore);
475+});
476+477+it("preserves explicit compaction.model behavior without session fallback", async () => {
478+resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
479+model: { provider, api: "responses", id: modelId, input: [] },
480+error: null,
481+authStorage: { setRuntimeApiKey: vi.fn() },
482+modelRegistry: {},
483+}));
484+sessionCompactImpl.mockRejectedValueOnce(
485+Object.assign(new Error("400 invalid request body"), { status: 400 }),
486+);
487+488+const result = await compactEmbeddedPiSessionDirect({
489+sessionId: "session-1",
490+sessionKey: TEST_SESSION_KEY,
491+sessionFile: "/tmp/session.jsonl",
492+workspaceDir: "/tmp/workspace",
493+provider: "openai",
494+model: "gpt-primary",
495+config: {
496+agents: {
497+defaults: {
498+model: {
499+primary: "openai/gpt-primary",
500+fallbacks: ["anthropic/claude-fallback"],
501+},
502+compaction: {
503+model: "azure/compact-primary",
504+},
505+},
506+},
507+} as never,
508+});
509+510+expect(result.ok).toBe(false);
511+expect(resolveModelMock).toHaveBeenCalledTimes(1);
512+expect(resolveModelMock).toHaveBeenCalledWith(
513+"azure",
514+"compact-primary",
515+expect.any(String),
516+expect.anything(),
517+);
518+});
519+520+it("preserves compaction failure status and code metadata", async () => {
521+resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
522+model: { provider, api: "responses", id: modelId, input: [] },
523+error: null,
524+authStorage: { setRuntimeApiKey: vi.fn() },
525+modelRegistry: {},
526+}));
527+sessionCompactImpl.mockRejectedValueOnce(
528+Object.assign(new Error("primary compaction rate limited"), {
529+status: 429,
530+code: "rate_limit_exceeded",
531+}),
532+);
533+534+const result = await compactEmbeddedPiSessionDirect({
535+sessionId: "session-1",
536+sessionKey: TEST_SESSION_KEY,
537+sessionFile: "/tmp/session.jsonl",
538+workspaceDir: "/tmp/workspace",
539+provider: "openai",
540+model: "gpt-primary",
541+config: {
542+agents: {
543+defaults: {
544+compaction: {
545+model: "openai/gpt-primary",
546+},
547+},
548+},
549+} as never,
550+});
551+552+expect(result).toMatchObject({
553+ok: false,
554+compacted: false,
555+failure: {
556+reason: "rate_limit",
557+status: 429,
558+code: "rate_limit_exceeded",
559+rawError: "primary compaction rate limited",
560+},
561+});
562+});
563+312564it("emits internal + plugin compaction hooks with counts", async () => {
313565hookRunner.hasHooks.mockReturnValue(true);
314566await runCompactionHooks({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。