Skip to content
Source
Sign inSign up

ProviderAdapterRegistry.test.ts

198 lines7.4 KB

1import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, it } from "@effect/vitest";
4import {
5 ProviderDriverKind,
6 ProviderInstanceId,
7 ProviderSessionId,
8 ThreadId,
9 ProviderSetupError,
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 Stream from "effect/Stream";
17​
18import * as ProviderAuthFlow from "@t3tools/provider-core/server/providerAuthFlow";
19import type { ProviderAuthController } from "../provider/ProviderAuthService.ts";
20import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
21import * as ProviderInstanceRegistry from "../provider/ProviderInstanceRegistry.ts";
22import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
23import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
24​
25const driver = ProviderDriverKind.make("codex");
26const personalId = ProviderInstanceId.make("codex_personal");
27const workId = ProviderInstanceId.make("codex_work");
28​
29const makeAdapter = (
30 instanceId: ProviderInstanceId,
31): ProviderAdapter.ProviderAdapterV2["Service"] =>
32 ({
33 instanceId,
34 driver,
35 getCapabilities: () => Effect.die("capabilities are not used by this registry test"),
36 planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" }),
37 openSession: () => Effect.die("sessions are not used by this registry test"),
38 }) as ProviderAdapter.ProviderAdapterV2["Service"];
39​
40const makeInstance = (
41 instanceId: ProviderInstanceId,
42 orchestrationAdapter: ProviderAdapter.ProviderAdapterV2["Service"],
43): ProviderInstance => ({
44 instanceId,
45 driverKind: driver,
46 continuationIdentity: {
47 driverKind: driver,
48 continuationKey: `codex:test:${instanceId}`,
49 },
50 displayName: String(instanceId),
51 enabled: true,
52 snapshot: {} as ProviderInstance["snapshot"],
53 orchestrationAdapter,
54 textGeneration: {} as ProviderInstance["textGeneration"],
55});
56​
57const personalAdapter = makeAdapter(personalId);
58const workAdapter = makeAdapter(workId);
59const instances = [
60 makeInstance(personalId, personalAdapter),
61 makeInstance(workId, workAdapter),
62] as const;
63const layerInstanceRegistry = Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
64 getInstance: (instanceId) =>
65 Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)),
66 listInstances: Effect.succeed(instances),
67 listUnavailable: Effect.succeed([]),
68 streamChanges: Stream.empty,
69 subscribeChanges: Effect.never,
70});
71const layerTest = ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
72 Layer.provide(layerInstanceRegistry),
73);
74​
75it.effect("routes two configured instances of the same driver independently", () =>
76 Effect.gen(function* () {
77 const registry = yield* ProviderAdapterRegistry.ProviderAdapterRegistryV2;
78​
79 assert.strictEqual(yield* registry.get(personalId), personalAdapter);
80 assert.strictEqual(yield* registry.get(workId), workAdapter);
81 assert.deepEqual(yield* registry.list(), [personalId, workId]);
82 }).pipe(Effect.provide(layerTest)),
83);
84​
85it.effect(
86 "blocks a new session while another instance changes their shared provider credentials",
87 () =>
88 Effect.gen(function* () {
89 const unused = () => Effect.die("unused auth operation");
90 const auth: ProviderAuthController = {
91 credentialBinding: { owner: "provider", key: "shared-cli" },
92 isChangingCredentials: Effect.succeed(false),
93 start: unused,
94 complete: unused,
95 cancel: unused,
96 logout: unused,
97 subscribe: () => Stream.empty,
98 };
99 const related = [
100 { ...instances[0], auth },
101 { ...instances[1], auth: { ...auth, isChangingCredentials: Effect.succeed(true) } },
102 ];
103 const registry = yield* Effect.service(
104 ProviderAdapterRegistry.ProviderAdapterRegistryV2,
105 ).pipe(
106 Effect.provide(
107 ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
108 Layer.provide(
109 Layer.mock(ProviderInstanceRegistry.ProviderInstanceRegistry)({
110 getInstance: (id) =>
111 Effect.succeed(related.find((instance) => instance.instanceId === id)),
112 listInstances: Effect.succeed(related),
113 }),
114 ),
115 ),
116 ),
117 );
118 const adapter = yield* registry.get(personalId);
119 const error = yield* adapter
120 .openSession({
121 threadId: ThreadId.make("new-thread"),
122 providerSessionId: ProviderSessionId.make("new-session"),
123 modelSelection: { instanceId: personalId, model: "test-model" },
124 runtimePolicy: {
125 runtimeMode: "full-access",
126 interactionMode: "default",
127 cwd: "/workspace",
128 },
129 })
130 .pipe(Effect.flip);
131 assert.instanceOf(error, ProviderAdapter.ProviderAdapterOpenSessionError);
132 assert.instanceOf(error.cause, ProviderSetupError);
133 }),
134);
135​
136it.effect("interrupts admitted session startup when a shared peer signs out", () =>
137 Effect.gen(function* () {
138 const entered = yield* Deferred.make<void>();
139 const stopped = yield* Deferred.make<void>();
140 const binding = { owner: "provider" as const, key: "shared-cli" };
141 const auth = yield* ProviderAuthFlow.make({
142 instanceId: personalId,
143 credentialBinding: binding,
144 methods: Effect.succeed([]),
145 authenticate: () => Effect.void,
146 logout: Effect.void,
147 });
148 const peerAuth = yield* ProviderAuthFlow.make({
149 instanceId: workId,
150 credentialBinding: binding,
151 methods: Effect.succeed([]),
152 authenticate: () => Effect.void,
153 logout: Effect.void,
154 });
155 const adapter: ProviderAdapter.ProviderAdapterV2["Service"] = {
156 ...workAdapter,
157 openSession: () =>
158 Effect.gen(function* () {
159 yield* Deferred.succeed(entered, undefined);
160 return yield* Effect.never;
161 }).pipe(Effect.ensuring(Deferred.succeed(stopped, undefined))),
162 };
163 const related = [
164 { ...instances[0], auth },
165 { ...instances[1], auth: peerAuth, orchestrationAdapter: adapter },
166 ];
167 const registry = yield* Effect.service(ProviderAdapterRegistry.ProviderAdapterRegistryV2).pipe(
168 Effect.provide(
169 ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
170 Layer.provide(
171 Layer.mock(ProviderInstanceRegistry.ProviderInstanceRegistry)({
172 getInstance: (id) =>
173 Effect.succeed(related.find((instance) => instance.instanceId === id)),
174 listInstances: Effect.succeed(related),
175 }),
176 ),
177 ),
178 ),
179 );
180 const guarded = yield* registry.get(workId);
181 const startup = yield* guarded
182 .openSession({
183 threadId: ThreadId.make("shared-startup"),
184 providerSessionId: ProviderSessionId.make("shared-session"),
185 modelSelection: { instanceId: workId, model: "test-model" },
186 runtimePolicy: {
187 runtimeMode: "full-access",
188 interactionMode: "default",
189 cwd: "/workspace",
190 },
191 })
192 .pipe(Effect.forkChild);
193 yield* Deferred.await(entered);
194 yield* auth.logout(Effect.void);
195 yield* Deferred.await(stopped);
196 assert.isTrue(Exit.isFailure(yield* Fiber.await(startup)));
197 }).pipe(Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))),
198);