import { assert, describe, it } from "@effect/vitest" import { Effect, Option } from "effect" import { Prompt, ResponseIdTracker } from "effect/unstable/ai" const systemMessage = (text: string) => Prompt.systemMessage({ content: text }) const userMessage = (text: string) => Prompt.userMessage({ content: [Prompt.textPart({ text })] }) const assistantMessage = (text: string) => Prompt.assistantMessage({ content: [Prompt.textPart({ text })] }) const assistantToolCallMessage = (id: string) => Prompt.assistantMessage({ content: [ Prompt.makePart("tool-call", { id, name: "test_tool", params: { query: "query" }, providerExecuted: false }) ] }) const toolResultMessage = (id: string) => Prompt.toolMessage({ content: [ Prompt.toolResultPart({ id, name: "test_tool", result: { ok: true }, isFailure: false }) ] }) const assertPreparedSome = ( prepared: Option.Option, previousResponseId: string, prompt: Prompt.Prompt ) => { assert.isTrue(Option.isSome(prepared)) if (Option.isSome(prepared)) { assert.strictEqual(prepared.value.previousResponseId, previousResponseId) assert.deepStrictEqual(prepared.value.prompt, prompt) } } describe("ResponseIdTracker", () => { it.effect("returns None for a fresh tracker", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const msg1 = userMessage("msg1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1])) assert.isTrue(Option.isNone(prepared)) })) it.effect("prepares only the new user message after a tracked first turn", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const msg1 = userMessage("msg1") const asst = assistantMessage("done") const msg2 = userMessage("msg2") tracker.markParts([msg1], "resp_123") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1, asst, msg2])) assertPreparedSome(prepared, "resp_123", Prompt.fromMessages([msg2])) })) it.effect("shares tracker state through its Context service layer", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const msg1 = userMessage("msg1") const msg1New = userMessage("msg1-new") const asst = assistantMessage("done") const msg2 = userMessage("msg2") tracker.markParts([msg1], "resp_123") tracker.clearUnsafe() tracker.markParts([msg1New], "resp_456") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1New, asst, msg2])) assertPreparedSome(prepared, "resp_456", Prompt.fromMessages([msg2])) })) it.effect("keeps latest response id when markParts is called twice", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const msg1 = userMessage("msg1") const asst = assistantMessage("done") const msg2 = userMessage("msg2") tracker.markParts([msg1], "resp_1") tracker.markParts([msg1], "resp_2") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1, asst, msg2])) assertPreparedSome(prepared, "resp_2", Prompt.fromMessages([msg2])) })) it.effect("returns None after clear", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const msg1 = userMessage("msg1") const asst = assistantMessage("done") const msg2 = userMessage("msg2") tracker.markParts([msg1], "resp_123") tracker.clearUnsafe() const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1, asst, msg2])) assert.isTrue(Option.isNone(prepared)) })) it.effect("recovers after clear with new marks", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const msg1 = userMessage("msg1") const msg1New = userMessage("msg1-new") const asst = assistantMessage("done") const msg2 = userMessage("msg2") tracker.markParts([msg1], "resp_123") tracker.clearUnsafe() tracker.markParts([msg1New], "resp_456") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1New, asst, msg2])) assertPreparedSome(prepared, "resp_456", Prompt.fromMessages([msg2])) })) it.effect("remains usable after concurrent markParts and clear", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const raceMessage = userMessage("race") const markPartsRace = Effect.gen(function*() { for (let i = 0; i < 200; i++) { tracker.markParts([raceMessage], "resp_race") yield* Effect.yieldNow } }) const clearRace = Effect.gen(function*() { for (let i = 0; i < 200; i++) { tracker.clearUnsafe() yield* Effect.yieldNow } }) yield* Effect.all([ markPartsRace, clearRace ], { concurrency: "unbounded", discard: true }) const msg1 = userMessage("msg1") const asst = assistantMessage("done") const msg2 = userMessage("msg2") tracker.markParts([msg1], "resp_final") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1, asst, msg2])) assertPreparedSome(prepared, "resp_final", Prompt.fromMessages([msg2])) })) it.effect("returns None for first turn with system and user messages", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns None when tracked prompt has no assistant message", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns incremental user follow-up after assistant", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1, user2])) assertPreparedSome(prepared, "resp_1", Prompt.fromMessages([user2])) })) it.effect("returns incremental tool results after assistant tool call", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const asst1 = assistantToolCallMessage("call_1") const tool1 = toolResultMessage("call_1") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1, tool1])) assertPreparedSome(prepared, "resp_1", Prompt.fromMessages([tool1])) })) it.effect("returns incremental tool results plus user message in multi-step flow", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const asst1 = assistantToolCallMessage("call_1") const tool1 = toolResultMessage("call_1") const user2 = userMessage("user2") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1, tool1, user2])) assertPreparedSome(prepared, "resp_1", Prompt.fromMessages([tool1, user2])) })) it.effect("returns None when no new messages follow assistant", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const asst1 = assistantMessage("assistant1") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns None in multi-turn prompt with no messages after last assistant", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") const asst2 = assistantMessage("assistant2") tracker.markParts([sys, user1, asst1, user2], "resp_2") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1, user2, asst2])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns None for empty prompt", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const prepared = tracker.prepareUnsafe(Prompt.fromMessages([])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns None when system prompt object changes", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const sysNew = systemMessage("sys-new") const user1 = userMessage("user1") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sysNew, user1, asst1, user2])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns None when user message object changes", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const user1Edited = userMessage("user1-edited") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1Edited, asst1, user2])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns None when multiple prefix messages diverge", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const sysNew = systemMessage("sys-new") const user1Edited = userMessage("user1-edited") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") tracker.markParts([sys, user1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sysNew, user1Edited, asst1, user2])) assert.isTrue(Option.isNone(prepared)) })) it.effect("clears stale state after divergence and recovers when the new prefix is re-marked", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const sysNew = systemMessage("sys-new") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") tracker.markParts([sys, user1], "resp_1") const diverged = tracker.prepareUnsafe(Prompt.fromMessages([sysNew, user1, asst1, user2])) assert.isTrue(Option.isNone(diverged)) tracker.markParts([sysNew, user1], "resp_2") const recovered = tracker.prepareUnsafe(Prompt.fromMessages([sysNew, user1, asst1, user2])) assertPreparedSome(recovered, "resp_2", Prompt.fromMessages([user2])) })) it.effect("supports full lifecycle across two turns", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") const asst2 = assistantMessage("assistant2") const user3 = userMessage("user3") tracker.markParts([sys, user1], "resp_1") const firstPrepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1, user2])) assertPreparedSome(firstPrepared, "resp_1", Prompt.fromMessages([user2])) tracker.markParts([sys, user1, asst1, user2], "resp_2") const secondPrepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1, user2, asst2, user3])) assertPreparedSome(secondPrepared, "resp_2", Prompt.fromMessages([user3])) })) it.effect("returns None for structurally-equal but identity-different prefix message", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const msg1 = userMessage("msg1") const msg1Copy = userMessage("msg1") const asst1 = assistantMessage("assistant1") const msg2 = userMessage("msg2") tracker.markParts([msg1], "resp_1") const prepared = tracker.prepareUnsafe(Prompt.fromMessages([msg1Copy, asst1, msg2])) assert.isTrue(Option.isNone(prepared)) })) it.effect("returns None after explicit clear", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sys = systemMessage("sys") const user1 = userMessage("user1") const asst1 = assistantMessage("assistant1") const user2 = userMessage("user2") tracker.markParts([sys, user1], "resp_1") tracker.clearUnsafe() const prepared = tracker.prepareUnsafe(Prompt.fromMessages([sys, user1, asst1, user2])) assert.isTrue(Option.isNone(prepared)) })) it.effect("isolates two conversations on the same tracker", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sysA = systemMessage("sysA") const userA1 = userMessage("userA1") const asstA1 = assistantMessage("asstA1") const userA2 = userMessage("userA2") const sysB = systemMessage("sysB") const userB1 = userMessage("userB1") const asstB1 = assistantMessage("asstB1") const userB2 = userMessage("userB2") tracker.markParts([sysA, userA1], "resp_A1") tracker.markParts([sysB, userB1], "resp_B1") const preparedA = tracker.prepareUnsafe(Prompt.fromMessages([sysA, userA1, asstA1, userA2])) assertPreparedSome(preparedA, "resp_A1", Prompt.fromMessages([userA2])) const preparedB = tracker.prepareUnsafe(Prompt.fromMessages([sysB, userB1, asstB1, userB2])) assertPreparedSome(preparedB, "resp_B1", Prompt.fromMessages([userB2])) })) it.effect("does not leak tracked state across conversations without assistant turns", () => Effect.gen(function*() { const tracker = yield* ResponseIdTracker.make const sysA = systemMessage("sysA") const userA1 = userMessage("userA1") const asstA1 = assistantMessage("asstA1") const userA2 = userMessage("userA2") const sysB = systemMessage("sysB") const userB1 = userMessage("userB1") tracker.markParts([sysA, userA1], "resp_A1") tracker.markParts([sysB, userB1], "resp_B1") const preparedA = tracker.prepareUnsafe(Prompt.fromMessages([sysA, userA1, asstA1, userA2])) assertPreparedSome(preparedA, "resp_A1", Prompt.fromMessages([userA2])) const preparedB = tracker.prepareUnsafe(Prompt.fromMessages([sysB, userB1])) assert.isTrue(Option.isNone(preparedB)) })) })