| ... | ... | @@ -2,6 +2,7 @@ import * as NodeAssert from "node:assert/strict"; |
| 2 | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | 3 | import { it } from "@effect/vitest"; |
| 4 | 4 | import * as Cause from "effect/Cause"; |
| 5 | import * as Clock from "effect/Clock"; |
| 5 | 6 | import * as Context from "effect/Context"; |
| 6 | 7 | import * as Crypto from "effect/Crypto"; |
| 7 | 8 | import * as Deferred from "effect/Deferred"; |
| ... | ... | @@ -781,6 +782,56 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { |
| 781 | 782 | }), |
| 782 | 783 | ); |
| 783 | 784 | |
| 785 | it.effect( |
| 786 | "stopSession completes after sendTurn is interrupted before the prompt is submitted", |
| 787 | () => |
| 788 | Effect.gen(function* () { |
| 789 | const adapter = yield* OpenCodeAdapter; |
| 790 | const threadId = asThreadId("thread-opencode-interrupted-before-submit"); |
| 791 | runtimeMock.state.createdSessionIds.push("ses_interrupted_before_submit"); |
| 792 | yield* adapter.startSession({ |
| 793 | provider: ProviderDriverKind.make("opencode"), |
| 794 | threadId, |
| 795 | runtimeMode: "full-access", |
| 796 | }); |
| 797 | |
| 798 | const baseClock = yield* Clock.clockWith(Effect.succeed); |
| 799 | const reached = yield* Deferred.make<void>(); |
| 800 | const release = yield* Deferred.make<void>(); |
| 801 | let calls = 0; |
| 802 | const gatedClock: Clock.Clock = { |
| 803 | ...baseClock, |
| 804 | currentTimeMillisUnsafe: () => baseClock.currentTimeMillisUnsafe(), |
| 805 | currentTimeMillis: Effect.suspend(() => { |
| 806 | calls += 1; |
| 807 | return calls === 2 |
| 808 | ? Deferred.succeed(reached, undefined).pipe( |
| 809 | Effect.andThen(Deferred.await(release)), |
| 810 | Effect.andThen(baseClock.currentTimeMillis), |
| 811 | ) |
| 812 | : baseClock.currentTimeMillis; |
| 813 | }), |
| 814 | }; |
| 815 | |
| 816 | const sendFiber = yield* adapter |
| 817 | .sendTurn({ |
| 818 | threadId, |
| 819 | input: "This prompt must not be submitted", |
| 820 | modelSelection: createModelSelection( |
| 821 | ProviderInstanceId.make("opencode"), |
| 822 | "opencode/kimi-k3", |
| 823 | ), |
| 824 | }) |
| 825 | .pipe(Effect.provideService(Clock.Clock, gatedClock), Effect.exit, Effect.forkChild); |
| 826 | yield* Deferred.await(reached); |
| 827 | yield* Fiber.interrupt(sendFiber); |
| 828 | |
| 829 | const stopExit = yield* adapter.stopSession(threadId).pipe(Effect.exit); |
| 830 | NodeAssert.equal(Exit.isSuccess(stopExit), true); |
| 831 | NodeAssert.equal(yield* adapter.hasSession(threadId), false); |
| 832 | }), |
| 833 | ); |
| 834 | |
| 784 | 835 | it.effect("aborts a held teardown request before closing the session scope", () => |
| 785 | 836 | Effect.gen(function* () { |
| 786 | 837 | const adapter = yield* OpenCodeAdapter; |