1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, it } from "@effect/vitest";
3import {
4 ProviderDriverKind,
5 ProviderInstanceId,
6 ProviderSessionId,
7 ThreadId,
8 ProviderSetupError,
9 type ProviderInstanceConfigMap,
10} from "@t3tools/contracts";
11import * as Deferred from "effect/Deferred";
12import * as Effect from "effect/Effect";
13import * as Exit from "effect/Exit";
14import * as Fiber from "effect/Fiber";
15import * as Layer from "effect/Layer";
16import * as Ref from "effect/Ref";
17import * as Schema from "effect/Schema";
18import type * as Scope from "effect/Scope";
19import * as Stream from "effect/Stream";
20
21import * as ProviderAuthFlow from "@t3tools/provider-core/server/providerAuthFlow";
22import type { ProviderAuthController } from "../provider/ProviderAuthService.ts";
23import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
24import * as ProviderInstanceRegistry from "../provider/ProviderInstanceRegistry.ts";
25import {
26 ProviderAdapterDriverCreateError,
27 type ProviderAdapterDriver,
28} from "@t3tools/provider-core/server/adapterDriver";
29import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
30import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
31
32const driver = ProviderDriverKind.make("codex");
33const personalId = ProviderInstanceId.make("codex_personal");
34const workId = ProviderInstanceId.make("codex_work");
35
36const makeAdapter = (
37 instanceId: ProviderInstanceId,
38): ProviderAdapter.ProviderAdapterV2["Service"] =>
39 ({
40 instanceId,
41 driver,
42 getCapabilities: () => Effect.die("capabilities are not used by this registry test"),
43 planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" }),
44 openSession: () => Effect.die("sessions are not used by this registry test"),
45 }) as ProviderAdapter.ProviderAdapterV2["Service"];
46
47const makeInstance = (
48 instanceId: ProviderInstanceId,
49 orchestrationAdapter: ProviderAdapter.ProviderAdapterV2["Service"],
50): ProviderInstance => ({
51 instanceId,
52 driverKind: driver,
53 continuationIdentity: {
54 driverKind: driver,
55 continuationKey: `codex:test:${instanceId}`,
56 },
57 displayName: String(instanceId),
58 enabled: true,
59 snapshot: {} as ProviderInstance["snapshot"],
60 orchestrationAdapter,
61 textGeneration: {} as ProviderInstance["textGeneration"],
62});
63
64const personalAdapter = makeAdapter(personalId);
65const workAdapter = makeAdapter(workId);
66const instances = [
67 makeInstance(personalId, personalAdapter),
68 makeInstance(workId, workAdapter),
69] as const;
70const layerInstanceRegistry = Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
71 getInstance: (instanceId) =>
72 Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)),
73 listInstances: Effect.succeed(instances),
74 listUnavailable: Effect.succeed([]),
75 streamChanges: Stream.empty,
76 subscribeChanges: Effect.never,
77});
78const layerTest = ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
79 Layer.provide(layerInstanceRegistry),
80);
81
82it.effect("routes two configured instances of the same driver independently", () =>
83 Effect.gen(function* () {
84 const registry = yield* ProviderAdapterRegistry.ProviderAdapterRegistryV2;
85
86 assert.strictEqual(yield* registry.get(personalId), personalAdapter);
87 assert.strictEqual(yield* registry.get(workId), workAdapter);
88 assert.deepEqual(yield* registry.list(), [personalId, workId]);
89 }).pipe(Effect.provide(layerTest)),
90);
91
92const lifecycleDriver = ProviderDriverKind.make("lifecycle-test");
93const lifecycleInstanceId = ProviderInstanceId.make("lifecycle-test");
94const lifecycleConfigMap: ProviderInstanceConfigMap = {
95 [lifecycleInstanceId]: {
96 driver: lifecycleDriver,
97 config: {},
98 },
99};
100const lifecycleAdapter = makeAdapter(lifecycleInstanceId);
101
102const makeLifecycleDriver = (
103 create: Effect.Effect<
104 ProviderAdapter.ProviderAdapterV2["Service"],
105 ProviderAdapterDriverCreateError,
106 Scope.Scope
107 >,
108): ProviderAdapterDriver<Record<string, never>> => ({
109 driverKind: lifecycleDriver,
110 configSchema: Schema.Struct({}),
111 defaultConfig: () => ({}),
112 create: () => create,
113});
114
115const trackedCreate = <A, E, R>(
116 releases: Ref.Ref<number>,
117 effect: Effect.Effect<A, E, R>,
118): Effect.Effect<A, E, R | Scope.Scope> =>
119 Effect.gen(function* () {
120 yield* Effect.addFinalizer(() => Ref.update(releases, (count) => count + 1));
121 return yield* effect;
122 });
123
124it.effect("closes a partially-created adapter scope immediately on typed failure", () =>
125 Effect.gen(function* () {
126 const releases = yield* Ref.make(0);
127 const createError = new ProviderAdapterDriverCreateError({
128 driver: lifecycleDriver,
129 instanceId: lifecycleInstanceId,
130 detail: "expected test failure",
131 });
132
133 yield* Effect.scoped(
134 Effect.gen(function* () {
135 const exit = yield* ProviderAdapterRegistry.makeRegistryFromConfigMap({
136 drivers: [makeLifecycleDriver(trackedCreate(releases, Effect.fail(createError)))],
137 configMap: lifecycleConfigMap,
138 }).pipe(Effect.exit);
139
140 assert.isTrue(Exit.isFailure(exit));
141 assert.strictEqual(yield* Ref.get(releases), 1);
142 }),
143 );
144
145 assert.strictEqual(yield* Ref.get(releases), 1);
146 }),
147);
148
149it.effect("closes a partially-created adapter scope immediately on defect", () =>
150 Effect.gen(function* () {
151 const releases = yield* Ref.make(0);
152
153 yield* Effect.scoped(
154 Effect.gen(function* () {
155 const exit = yield* ProviderAdapterRegistry.makeRegistryFromConfigMap({
156 drivers: [
157 makeLifecycleDriver(trackedCreate(releases, Effect.die("expected test defect"))),
158 ],
159 configMap: lifecycleConfigMap,
160 }).pipe(Effect.exit);
161
162 assert.isTrue(Exit.hasDies(exit));
163 assert.strictEqual(yield* Ref.get(releases), 1);
164 }),
165 );
166
167 assert.strictEqual(yield* Ref.get(releases), 1);
168 }),
169);
170
171it.effect("closes a partially-created adapter scope immediately on interruption", () =>
172 Effect.gen(function* () {
173 const releases = yield* Ref.make(0);
174 const createStarted = yield* Deferred.make<void>();
175
176 yield* Effect.scoped(
177 Effect.gen(function* () {
178 const fiber = yield* ProviderAdapterRegistry.makeRegistryFromConfigMap({
179 drivers: [
180 makeLifecycleDriver(
181 trackedCreate(
182 releases,
183 Deferred.succeed(createStarted, undefined).pipe(Effect.andThen(Effect.never)),
184 ),
185 ),
186 ],
187 configMap: lifecycleConfigMap,
188 }).pipe(Effect.forkChild({ startImmediately: true }));
189
190 yield* Deferred.await(createStarted);
191 yield* Fiber.interrupt(fiber);
192 const exit = yield* Fiber.await(fiber);
193
194 assert.isTrue(Exit.hasInterrupts(exit));
195 assert.strictEqual(yield* Ref.get(releases), 1);
196 }),
197 );
198
199 assert.strictEqual(yield* Ref.get(releases), 1);
200 }),
201);
202
203it.effect("keeps a successfully-created adapter scope open until normal release", () =>
204 Effect.gen(function* () {
205 const releases = yield* Ref.make(0);
206
207 yield* Effect.scoped(
208 Effect.gen(function* () {
209 const registry = yield* ProviderAdapterRegistry.makeRegistryFromConfigMap({
210 drivers: [makeLifecycleDriver(trackedCreate(releases, Effect.succeed(lifecycleAdapter)))],
211 configMap: lifecycleConfigMap,
212 });
213
214 assert.strictEqual(yield* registry.get(lifecycleInstanceId), lifecycleAdapter);
215 assert.strictEqual(yield* Ref.get(releases), 0);
216 }),
217 );
218
219 assert.strictEqual(yield* Ref.get(releases), 1);
220 }),
221);
222
223it.effect(
224 "blocks a new session while another instance changes their shared provider credentials",
225 () =>
226 Effect.gen(function* () {
227 const unused = () => Effect.die("unused auth operation");
228 const auth: ProviderAuthController = {
229 credentialBinding: { owner: "provider", key: "shared-cli" },
230 isChangingCredentials: Effect.succeed(false),
231 start: unused,
232 complete: unused,
233 cancel: unused,
234 logout: unused,
235 subscribe: () => Stream.empty,
236 };
237 const related = [
238 { ...instances[0], auth },
239 { ...instances[1], auth: { ...auth, isChangingCredentials: Effect.succeed(true) } },
240 ];
241 const registry = yield* Effect.service(
242 ProviderAdapterRegistry.ProviderAdapterRegistryV2,
243 ).pipe(
244 Effect.provide(
245 ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
246 Layer.provide(
247 Layer.mock(ProviderInstanceRegistry.ProviderInstanceRegistry)({
248 getInstance: (id) =>
249 Effect.succeed(related.find((instance) => instance.instanceId === id)),
250 listInstances: Effect.succeed(related),
251 }),
252 ),
253 ),
254 ),
255 );
256 const adapter = yield* registry.get(personalId);
257 const error = yield* adapter
258 .openSession({
259 threadId: ThreadId.make("new-thread"),
260 providerSessionId: ProviderSessionId.make("new-session"),
261 modelSelection: { instanceId: personalId, model: "test-model" },
262 runtimePolicy: {
263 runtimeMode: "full-access",
264 interactionMode: "default",
265 cwd: "/workspace",
266 },
267 })
268 .pipe(Effect.flip);
269 assert.instanceOf(error, ProviderAdapter.ProviderAdapterOpenSessionError);
270 assert.instanceOf(error.cause, ProviderSetupError);
271 }),
272);
273
274it.effect("interrupts admitted session startup when a shared peer signs out", () =>
275 Effect.gen(function* () {
276 const entered = yield* Deferred.make<void>();
277 const stopped = yield* Deferred.make<void>();
278 const binding = { owner: "provider" as const, key: "shared-cli" };
279 const auth = yield* ProviderAuthFlow.make({
280 instanceId: personalId,
281 credentialBinding: binding,
282 methods: Effect.succeed([]),
283 authenticate: () => Effect.void,
284 logout: Effect.void,
285 });
286 const peerAuth = yield* ProviderAuthFlow.make({
287 instanceId: workId,
288 credentialBinding: binding,
289 methods: Effect.succeed([]),
290 authenticate: () => Effect.void,
291 logout: Effect.void,
292 });
293 const adapter: ProviderAdapter.ProviderAdapterV2["Service"] = {
294 ...workAdapter,
295 openSession: () =>
296 Effect.gen(function* () {
297 yield* Deferred.succeed(entered, undefined);
298 return yield* Effect.never;
299 }).pipe(Effect.ensuring(Deferred.succeed(stopped, undefined))),
300 };
301 const related = [
302 { ...instances[0], auth },
303 { ...instances[1], auth: peerAuth, orchestrationAdapter: adapter },
304 ];
305 const registry = yield* Effect.service(ProviderAdapterRegistry.ProviderAdapterRegistryV2).pipe(
306 Effect.provide(
307 ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
308 Layer.provide(
309 Layer.mock(ProviderInstanceRegistry.ProviderInstanceRegistry)({
310 getInstance: (id) =>
311 Effect.succeed(related.find((instance) => instance.instanceId === id)),
312 listInstances: Effect.succeed(related),
313 }),
314 ),
315 ),
316 ),
317 );
318 const guarded = yield* registry.get(workId);
319 const startup = yield* guarded
320 .openSession({
321 threadId: ThreadId.make("shared-startup"),
322 providerSessionId: ProviderSessionId.make("shared-session"),
323 modelSelection: { instanceId: workId, model: "test-model" },
324 runtimePolicy: {
325 runtimeMode: "full-access",
326 interactionMode: "default",
327 cwd: "/workspace",
328 },
329 })
330 .pipe(Effect.forkChild);
331 yield* Deferred.await(entered);
332 yield* auth.logout(Effect.void);
333 yield* Deferred.await(stopped);
334 assert.isTrue(Exit.isFailure(yield* Fiber.await(startup)));
335 }).pipe(Effect.provide(NodeServices.layer)),
336);