1import { assert, it, vi } from "@effect/vitest";
2import {
3 NodeId,
4 ProviderSessionId,
5 RuntimeRequestId,
6 ThreadId,
7 type OrchestrationV2RuntimeRequest,
8 type OrchestrationV2ThreadProjection,
9} from "@t3tools/contracts";
10import * as DateTime from "effect/DateTime";
11import * as Effect from "effect/Effect";
12import * as Layer from "effect/Layer";
13import * as Option from "effect/Option";
14
15import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
16import * as ProjectionStore from "./ProjectionStore.ts";
17import * as ProviderSessionManager from "./ProviderSessionManager.ts";
18import * as RuntimeRequestService from "./RuntimeRequestService.ts";
19
20function projectionWithRuntimeRequest(
21 runtimeRequest?: OrchestrationV2RuntimeRequest,
22): OrchestrationV2ThreadProjection {
23 return {
24 runtimeRequests: runtimeRequest === undefined ? [] : [runtimeRequest],
25 } as unknown as OrchestrationV2ThreadProjection;
26}
27
28function resolvedRuntimeRequest(
29 requestId: RuntimeRequestId,
30 providerSessionId: ProviderSessionId,
31): OrchestrationV2RuntimeRequest {
32 return {
33 id: requestId,
34 nodeId: NodeId.make(`node-${requestId}`),
35 providerTurnId: null,
36 nativeRequestRef: null,
37 kind: "command",
38 status: "resolved",
39 responseCapability: {
40 type: "live",
41 providerSessionId,
42 },
43 createdAt: DateTime.makeUnsafe("2026-07-29T00:00:00.000Z"),
44 resolvedAt: DateTime.makeUnsafe("2026-07-29T00:00:01.000Z"),
45 };
46}
47
48function layerRuntimeRequestTest(
49 projection: OrchestrationV2ThreadProjection,
50 getSession: ProviderSessionManager.ProviderSessionManagerV2Shape["get"],
51) {
52 return RuntimeRequestService.layer.pipe(
53 Layer.provide(
54 Layer.mergeAll(
55 Layer.mock(ProjectionStore.ProjectionStoreV2)({
56 getRuntimeRequest: (_threadId, requestId) =>
57 Effect.succeed(projection.runtimeRequests.find((request) => request.id === requestId)),
58 }),
59 Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({
60 get: getSession,
61 }),
62 ),
63 ),
64 );
65}
66
67it.effect("forwards orchestrator-resolved runtime requests to the live adapter", () => {
68 const threadId = ThreadId.make("thread-runtime-request-resolved");
69 const providerSessionId = ProviderSessionId.make("provider-session-runtime-request-resolved");
70 const requestId = RuntimeRequestId.make("request-resolved");
71 const respondToRuntimeRequest = vi.fn(
72 (_input: ProviderAdapter.ProviderAdapterV2RuntimeRequestResponseInput) => Effect.void,
73 );
74 const getSession = vi.fn(() =>
75 Effect.succeed(
76 Option.some({
77 respondToRuntimeRequest,
78 } as never),
79 ),
80 );
81 const projection = {
82 runtimeRequests: [
83 {
84 id: requestId,
85 nodeId: NodeId.make("node-runtime-request-resolved"),
86 providerTurnId: null,
87 nativeRequestRef: null,
88 kind: "command",
89 status: "resolved",
90 responseCapability: {
91 type: "live",
92 providerSessionId,
93 },
94 createdAt: DateTime.makeUnsafe("2026-07-29T00:00:00.000Z"),
95 resolvedAt: DateTime.makeUnsafe("2026-07-29T00:00:01.000Z"),
96 },
97 ],
98 } as unknown as OrchestrationV2ThreadProjection;
99 const layerTest = RuntimeRequestService.layer.pipe(
100 Layer.provide(
101 Layer.mergeAll(
102 Layer.mock(ProjectionStore.ProjectionStoreV2)({
103 getRuntimeRequest: (_threadId, requestId) =>
104 Effect.succeed(projection.runtimeRequests.find((request) => request.id === requestId)),
105 }),
106 Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({
107 get: getSession,
108 }),
109 ),
110 ),
111 );
112
113 return Effect.gen(function* () {
114 const service = yield* RuntimeRequestService.RuntimeRequestServiceV2;
115 yield* service.respond({
116 threadId,
117 providerSessionId,
118 requestId,
119 decision: "accept",
120 });
121
122 assert.equal(getSession.mock.calls.length, 1);
123 assert.equal(respondToRuntimeRequest.mock.calls.length, 1);
124 assert.deepEqual(respondToRuntimeRequest.mock.calls[0]?.[0], {
125 requestId,
126 decision: "accept",
127 });
128 }).pipe(Effect.provide(layerTest));
129});
130
131it.effect("rejects expired runtime requests before invoking the live adapter", () => {
132 const threadId = ThreadId.make("thread-runtime-request-expired");
133 const providerSessionId = ProviderSessionId.make("provider-session-runtime-request-expired");
134 const requestId = RuntimeRequestId.make("request-expired");
135 const respondToRuntimeRequest = vi.fn(
136 (_input: ProviderAdapter.ProviderAdapterV2RuntimeRequestResponseInput) => Effect.void,
137 );
138 const getSession = vi.fn(() =>
139 Effect.succeed(
140 Option.some({
141 respondToRuntimeRequest,
142 } as never),
143 ),
144 );
145 const projection = {
146 runtimeRequests: [
147 {
148 id: requestId,
149 nodeId: NodeId.make("node-runtime-request-expired"),
150 providerTurnId: null,
151 nativeRequestRef: null,
152 kind: "command",
153 status: "expired",
154 responseCapability: {
155 type: "live",
156 providerSessionId,
157 },
158 createdAt: DateTime.makeUnsafe("2026-07-29T00:00:00.000Z"),
159 resolvedAt: DateTime.makeUnsafe("2026-07-29T00:00:01.000Z"),
160 },
161 ],
162 } as unknown as OrchestrationV2ThreadProjection;
163 const layerTest = RuntimeRequestService.layer.pipe(
164 Layer.provide(
165 Layer.mergeAll(
166 Layer.mock(ProjectionStore.ProjectionStoreV2)({
167 getRuntimeRequest: (_threadId, requestId) =>
168 Effect.succeed(projection.runtimeRequests.find((request) => request.id === requestId)),
169 }),
170 Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({
171 get: getSession,
172 }),
173 ),
174 ),
175 );
176
177 return Effect.gen(function* () {
178 const service = yield* RuntimeRequestService.RuntimeRequestServiceV2;
179 const error = yield* service
180 .respond({
181 threadId,
182 providerSessionId,
183 requestId,
184 decision: "accept",
185 })
186 .pipe(Effect.flip);
187
188 assert.equal(error.reason, "request-not-ready");
189 assert.equal(
190 error.message,
191 `Runtime request ${requestId} on thread ${threadId} is not ready for response execution.`,
192 );
193 assert.isUndefined(error.cause);
194 assert.equal(getSession.mock.calls.length, 0);
195 assert.equal(respondToRuntimeRequest.mock.calls.length, 0);
196 }).pipe(Effect.provide(layerTest));
197});
198
199it.effect("classifies runtime request response validation failures", () => {
200 const threadId = ThreadId.make("thread-runtime-request-validation");
201 const providerSessionId = ProviderSessionId.make("provider-session-runtime-request-validation");
202 const otherProviderSessionId = ProviderSessionId.make(
203 "provider-session-runtime-request-validation-other",
204 );
205 const requestId = RuntimeRequestId.make("request-validation");
206 const unexpectedGetSession = vi.fn(() => Effect.succeed(Option.none()));
207 const layerMissing = layerRuntimeRequestTest(
208 projectionWithRuntimeRequest(),
209 unexpectedGetSession,
210 );
211 const layerNotResumable = layerRuntimeRequestTest(
212 projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, otherProviderSessionId)),
213 unexpectedGetSession,
214 );
215 const layerInactive = layerRuntimeRequestTest(
216 projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, providerSessionId)),
217 () => Effect.succeed(Option.none()),
218 );
219 const respond = Effect.gen(function* () {
220 const service = yield* RuntimeRequestService.RuntimeRequestServiceV2;
221 return yield* service
222 .respond({
223 threadId,
224 providerSessionId,
225 requestId,
226 decision: "accept",
227 })
228 .pipe(Effect.flip);
229 });
230
231 return Effect.gen(function* () {
232 const missing = yield* respond.pipe(Effect.provide(layerMissing));
233 assert.equal(missing.reason, "request-missing");
234 assert.equal(
235 missing.message,
236 `Runtime request ${requestId} no longer exists on thread ${threadId}.`,
237 );
238 assert.isUndefined(missing.cause);
239
240 const notResumable = yield* respond.pipe(Effect.provide(layerNotResumable));
241 assert.equal(notResumable.reason, "request-not-resumable");
242 assert.equal(
243 notResumable.message,
244 `Runtime request ${requestId} on thread ${threadId} is not resumable on provider session ${providerSessionId}.`,
245 );
246 assert.isUndefined(notResumable.cause);
247
248 const inactive = yield* respond.pipe(Effect.provide(layerInactive));
249 assert.equal(inactive.reason, "provider-session-not-active");
250 assert.equal(
251 inactive.message,
252 `Provider session ${providerSessionId} is not active for runtime request ${requestId} on thread ${threadId}.`,
253 );
254 assert.isUndefined(inactive.cause);
255 assert.equal(unexpectedGetSession.mock.calls.length, 0);
256 });
257});
258
259it.effect("preserves genuine provider session lookup failures as the cause", () => {
260 const threadId = ThreadId.make("thread-runtime-request-lookup-failure");
261 const providerSessionId = ProviderSessionId.make(
262 "provider-session-runtime-request-lookup-failure",
263 );
264 const requestId = RuntimeRequestId.make("request-lookup-failure");
265 const lookupFailure = new ProviderSessionManager.ProviderSessionLookupError({
266 providerSessionId,
267 cause: "lookup failed",
268 });
269 const layerTest = layerRuntimeRequestTest(
270 projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, providerSessionId)),
271 () => Effect.fail(lookupFailure),
272 );
273
274 return Effect.gen(function* () {
275 const service = yield* RuntimeRequestService.RuntimeRequestServiceV2;
276 const error = yield* service
277 .respond({
278 threadId,
279 providerSessionId,
280 requestId,
281 decision: "accept",
282 })
283 .pipe(Effect.flip);
284
285 assert.instanceOf(error, RuntimeRequestService.RuntimeRequestResponseExecutionError);
286 assert.equal(error.reason, "unexpected-failure");
287 assert.equal(
288 error.message,
289 `Failed to respond to runtime request ${requestId} on thread ${threadId} via provider session ${providerSessionId}.`,
290 );
291 assert.strictEqual(error.cause, lookupFailure);
292 }).pipe(Effect.provide(layerTest));
293});