Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
// @vitest-environment node
import { beforeAll, describe, expect, it, vi } from "vitest";
import { handleAgentEvent, type FallbackStatus, type ToolStreamEntry } from "./app-tool-stream.ts";
type ToolStreamHost = Parameters<typeof handleAgentEvent>[0];
type AgentEvent = NonNullable<Parameters<typeof handleAgentEvent>[1]>;
type MutableHost = ToolStreamHost & {
compactionStatus?: unknown;
compactionClearTimer?: number | null;
fallbackStatus?: FallbackStatus | null;
fallbackClearTimer?: number | null;
};
function createHost(overrides?: Partial<MutableHost>): MutableHost {
return {
sessionKey: "main",
chatRunId: null,
chatStream: null,
chatStreamStartedAt: null,
chatStreamSegments: [],
toolStreamById: new Map<string, ToolStreamEntry>(),
toolStreamOrder: [],
chatToolMessages: [],
toolStreamSyncTimer: null,
compactionStatus: null,
compactionClearTimer: null,
fallbackStatus: null,
fallbackClearTimer: null,
...overrides,
};
}
function agentEvent(
runId: string,
seq: number,
stream: AgentEvent["stream"],
data: AgentEvent["data"],
sessionKey = "main",
): AgentEvent {
return {
runId,
seq,
stream,
ts: Date.now(),
sessionKey,
data,
};
}
function expectCompactionCompleteAndAutoClears(host: MutableHost) {
expect(host.compactionStatus).toEqual({
phase: "complete",
runId: "run-1",
startedAt: expect.any(Number),
completedAt: expect.any(Number),
});
expect(host.compactionClearTimer).not.toBeNull();
vi.advanceTimersByTime(5_000);
expect(host.compactionStatus).toBeNull();
expect(host.compactionClearTimer).toBeNull();
}
describe("app-tool-stream fallback lifecycle handling", () => {
beforeAll(() => {
const globalWithWindow = globalThis as typeof globalThis & {
window?: Window & typeof globalThis;
};
if (!globalWithWindow.window) {
globalWithWindow.window = globalThis as unknown as Window & typeof globalThis;
}
});
it("accepts session-scoped fallback lifecycle events when no run is active", () => {
vi.useFakeTimers();
const host = createHost();
handleAgentEvent(host, {
runId: "run-1",
seq: 1,
stream: "lifecycle",
ts: Date.now(),
sessionKey: "main",
data: {
phase: "fallback",
selectedProvider: "fireworks",
selectedModel: "fireworks/accounts/fireworks/routers/kimi-k2p5-turbo",
activeProvider: "deepinfra",
activeModel: "moonshotai/Kimi-K2.5",
reasonSummary: "rate limit",
},
});
expect(host.fallbackStatus?.selected).toBe(
"fireworks/accounts/fireworks/routers/kimi-k2p5-turbo",
);
expect(host.fallbackStatus?.active).toBe("deepinfra/moonshotai/Kimi-K2.5");
expect(host.fallbackStatus?.reason).toBe("rate limit");
vi.useRealTimers();
});
it("rejects idle fallback lifecycle events for other sessions", () => {
vi.useFakeTimers();
const host = createHost();
handleAgentEvent(host, {
runId: "run-1",
seq: 1,
stream: "lifecycle",
ts: Date.now(),
sessionKey: "agent:other:main",
data: {
phase: "fallback",
selectedProvider: "fireworks",
selectedModel: "fireworks/accounts/fireworks/routers/kimi-k2p5-turbo",
activeProvider: "deepinfra",
activeModel: "moonshotai/Kimi-K2.5",
},
});
expect(host.fallbackStatus).toBeNull();
vi.useRealTimers();
});
it("auto-clears fallback status after toast duration", () => {
vi.useFakeTimers();
const host = createHost();
handleAgentEvent(host, {
runId: "run-1",
seq: 1,
stream: "lifecycle",
ts: Date.now(),
sessionKey: "main",
data: {
phase: "fallback",
selectedProvider: "fireworks",
selectedModel: "fireworks/accounts/fireworks/routers/kimi-k2p5-turbo",
activeProvider: "deepinfra",
activeModel: "moonshotai/Kimi-K2.5",
},
});
expect(host.fallbackStatus).not.toBeNull();
vi.advanceTimersByTime(7_999);
expect(host.fallbackStatus).not.toBeNull();
vi.advanceTimersByTime(1);
expect(host.fallbackStatus).toBeNull();
vi.useRealTimers();
});
it("builds previous fallback label from provider + model on fallback_cleared", () => {
vi.useFakeTimers();
const host = createHost();
handleAgentEvent(host, {
runId: "run-1",
seq: 1,
stream: "lifecycle",
ts: Date.now(),
sessionKey: "main",
data: {
phase: "fallback_cleared",
selectedProvider: "fireworks",
selectedModel: "fireworks/accounts/fireworks/routers/kimi-k2p5-turbo",
activeProvider: "fireworks",
activeModel: "fireworks/accounts/fireworks/routers/kimi-k2p5-turbo",
previousActiveProvider: "deepinfra",
previousActiveModel: "moonshotai/Kimi-K2.5",
},
});
expect(host.fallbackStatus?.phase).toBe("cleared");
expect(host.fallbackStatus?.previous).toBe("deepinfra/moonshotai/Kimi-K2.5");
vi.useRealTimers();
});
it("keeps compaction in retry-pending state until the matching lifecycle end", () => {
vi.useFakeTimers();
const host = createHost();
handleAgentEvent(host, agentEvent("run-1", 1, "compaction", { phase: "start" }));
expect(host.compactionStatus).toEqual({
phase: "active",
runId: "run-1",
startedAt: expect.any(Number),
completedAt: null,
});
handleAgentEvent(
host,
agentEvent("run-1", 2, "compaction", {
phase: "end",
willRetry: true,
completed: true,
}),
);
expect(host.compactionStatus).toEqual({
phase: "retrying",
runId: "run-1",
startedAt: expect.any(Number),
completedAt: null,
});
expect(host.compactionClearTimer).toBeNull();
handleAgentEvent(host, agentEvent("run-2", 3, "lifecycle", { phase: "end" }));
expect(host.compactionStatus).toEqual({
phase: "retrying",
runId: "run-1",
startedAt: expect.any(Number),
completedAt: null,
});
handleAgentEvent(host, agentEvent("run-1", 4, "lifecycle", { phase: "end" }));
expectCompactionCompleteAndAutoClears(host);
vi.useRealTimers();
});
it("treats lifecycle error as terminal for retry-pending compaction", () => {
vi.useFakeTimers();
const host = createHost();
handleAgentEvent(host, agentEvent("run-1", 1, "compaction", { phase: "start" }));
handleAgentEvent(
host,
agentEvent("run-1", 2, "compaction", {
phase: "end",
willRetry: true,
completed: true,
}),
);
expect(host.compactionStatus).toEqual({
phase: "retrying",
runId: "run-1",
startedAt: expect.any(Number),
completedAt: null,
});
handleAgentEvent(host, agentEvent("run-1", 3, "lifecycle", { phase: "error", error: "boom" }));
expectCompactionCompleteAndAutoClears(host);
vi.useRealTimers();
});
it("does not surface retrying or complete when retry compaction failed", () => {
vi.useFakeTimers();
const host = createHost();
handleAgentEvent(host, agentEvent("run-1", 1, "compaction", { phase: "start" }));
handleAgentEvent(
host,
agentEvent("run-1", 2, "compaction", {
phase: "end",
willRetry: true,
completed: false,
}),
);
expect(host.compactionStatus).toBeNull();
expect(host.compactionClearTimer).toBeNull();
handleAgentEvent(host, agentEvent("run-1", 3, "lifecycle", { phase: "error", error: "boom" }));
expect(host.compactionStatus).toBeNull();
expect(host.compactionClearTimer).toBeNull();
vi.useRealTimers();
});
});
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|