authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 01:55:11-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
loge9ced44ac6729116a1ee8332077736ca648ee419
treedbecd418a43a8193c83df0905d13507e896cfdc2
parent802cd127d17d77ba9d598675574b9d01480a94b0

Wire Claude adapter into registry and server provider layer

Add provider-service routing coverage for explicit claudeCode sessions. Co-authored-by: codex <codex@users.noreply.github.com>

4 files changed, 68 insertions(+), 12 deletions(-)

apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts+28-4
......@@ -4,6 +4,7 @@ import { assertFailure } from "@effect/vitest/utils";
44
55import { Effect, Layer, Stream } from "effect";
66
7import { ClaudeCodeAdapter, ClaudeCodeAdapterShape } from "../Services/ClaudeCodeAdapter.ts";
78import { CodexAdapter, CodexAdapterShape } from "../Services/CodexAdapter.ts";
89import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts";
910import { ProviderAdapterRegistryLive } from "./ProviderAdapterRegistry.ts";
......@@ -25,9 +26,30 @@ const fakeCodexAdapter: CodexAdapterShape = {
2526 streamEvents: Stream.empty,
2627};
2728
29const fakeClaudeAdapter: ClaudeCodeAdapterShape = {
30 provider: "claudeCode",
31 startSession: vi.fn(),
32 sendTurn: vi.fn(),
33 interruptTurn: vi.fn(),
34 respondToRequest: vi.fn(),
35 stopSession: vi.fn(),
36 listSessions: vi.fn(),
37 hasSession: vi.fn(),
38 readThread: vi.fn(),
39 rollbackThread: vi.fn(),
40 stopAll: vi.fn(),
41 streamEvents: Stream.empty,
42};
43
2844const layer = it.layer(
2945 Layer.mergeAll(
30 Layer.provide(ProviderAdapterRegistryLive, Layer.succeed(CodexAdapter, fakeCodexAdapter)),
46 Layer.provide(
47 ProviderAdapterRegistryLive,
48 Layer.mergeAll(
49 Layer.succeed(CodexAdapter, fakeCodexAdapter),
50 Layer.succeed(ClaudeCodeAdapter, fakeClaudeAdapter),
51 ),
52 ),
3153 NodeServices.layer,
3254 ),
3355);
......@@ -36,11 +58,13 @@ layer("ProviderAdapterRegistryLive", (it) => {
3658 it.effect("resolves a registered provider adapter", () =>
3759 Effect.gen(function* () {
3860 const registry = yield* ProviderAdapterRegistry;
39 const adapter = yield* registry.getByProvider("codex");
40 assert.equal(adapter, fakeCodexAdapter);
61 const codex = yield* registry.getByProvider("codex");
62 const claude = yield* registry.getByProvider("claudeCode");
63 assert.equal(codex, fakeCodexAdapter);
64 assert.equal(claude, fakeClaudeAdapter);
4165
4266 const providers = yield* registry.listProviders();
43 assert.deepEqual(providers, ["codex"]);
67 assert.deepEqual(providers, ["codex", "claudeCode"]);
4468 }),
4569 );
4670
apps/server/src/provider/Layers/ProviderAdapterRegistry.ts+5-1
......@@ -15,6 +15,7 @@ import {
1515 ProviderAdapterRegistry,
1616 type ProviderAdapterRegistryShape,
1717} from "../Services/ProviderAdapterRegistry.ts";
18import { ClaudeCodeAdapter } from "../Services/ClaudeCodeAdapter.ts";
1819import { CodexAdapter } from "../Services/CodexAdapter.ts";
1920
2021export interface ProviderAdapterRegistryLiveOptions {
......@@ -23,7 +24,10 @@ export interface ProviderAdapterRegistryLiveOptions {
2324
2425const makeProviderAdapterRegistry = (options?: ProviderAdapterRegistryLiveOptions) =>
2526 Effect.gen(function* () {
26 const adapters = options?.adapters !== undefined ? options.adapters : [yield* CodexAdapter];
27 const adapters =
28 options?.adapters !== undefined
29 ? options.adapters
30 : [yield* CodexAdapter, yield* ClaudeCodeAdapter];
2731 const byProvider = new Map(adapters.map((adapter) => [adapter.provider, adapter]));
2832
2933 const getByProvider: ProviderAdapterRegistryShape["getByProvider"] = (provider) => {
apps/server/src/provider/Layers/ProviderService.test.ts+32-6
......@@ -12,6 +12,7 @@ import type {
1212import {
1313 ApprovalRequestId,
1414 EventId,
15 type ProviderKind,
1516 ProviderSessionStartInput,
1617 ProviderSessionId,
1718 ProviderThreadId,
......@@ -52,7 +53,7 @@ const asRequestId = (value: string): ApprovalRequestId => ApprovalRequestId.make
5253const asEventId = (value: string): EventId => EventId.makeUnsafe(value);
5354const asThreadId = (value: string): ThreadId => ThreadId.makeUnsafe(value);
5455
55function makeFakeCodexAdapter() {
56function makeFakeCodexAdapter(provider: ProviderKind = "codex") {
5657 const sessions = new Map<ProviderSessionId, ProviderSession>();
5758 const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>());
5859 let nextSession = 1;
......@@ -64,7 +65,7 @@ function makeFakeCodexAdapter() {
6465 const threadId = ProviderThreadId.makeUnsafe(`thread-${next}`);
6566 const session: ProviderSession = {
6667 sessionId: ProviderSessionId.makeUnsafe(`sess-${next}`),
67 provider: "codex",
68 provider,
6869 status: "ready",
6970 threadId,
7071 resumeCursor: input.resumeCursor ?? { opaque: `cursor-${next}` },
......@@ -85,7 +86,7 @@ function makeFakeCodexAdapter() {
8586 if (!sessions.has(input.sessionId)) {
8687 return Effect.fail(
8788 new ProviderAdapterSessionNotFoundError({
88 provider: "codex",
89 provider,
8990 sessionId: input.sessionId,
9091 }),
9192 );
......@@ -160,7 +161,7 @@ function makeFakeCodexAdapter() {
160161 );
161162
162163 const adapter: ProviderAdapterShape<ProviderAdapterError> = {
163 provider: "codex",
164 provider,
164165 startSession,
165166 sendTurn,
166167 interruptTurn,
......@@ -199,12 +200,15 @@ const sleep = (ms: number) =>
199200
200201function makeProviderServiceLayer() {
201202 const codex = makeFakeCodexAdapter();
203 const claude = makeFakeCodexAdapter("claudeCode");
202204 const registry: typeof ProviderAdapterRegistry.Service = {
203205 getByProvider: (provider) =>
204206 provider === "codex"
205207 ? Effect.succeed(codex.adapter)
206 : Effect.fail(new ProviderUnsupportedError({ provider })),
207 listProviders: () => Effect.succeed(["codex"]),
208 : provider === "claudeCode"
209 ? Effect.succeed(claude.adapter)
210 : Effect.fail(new ProviderUnsupportedError({ provider })),
211 listProviders: () => Effect.succeed(["codex", "claudeCode"]),
208212 };
209213
210214 const providerAdapterLayer = Layer.succeed(ProviderAdapterRegistry, registry);
......@@ -227,6 +231,7 @@ function makeProviderServiceLayer() {
227231
228232 return {
229233 codex,
234 claude,
230235 layer,
231236 };
232237}
......@@ -448,6 +453,27 @@ routing.layer("ProviderServiceLive routing", (it) => {
448453 }),
449454 );
450455
456 it.effect("routes explicit claudeCode provider session starts to the claude adapter", () =>
457 Effect.gen(function* () {
458 const provider = yield* ProviderService;
459
460 const session = yield* provider.startSession(asThreadId("thread-claude"), {
461 provider: "claudeCode",
462 cwd: "/tmp/project-claude",
463 });
464
465 assert.equal(session.provider, "claudeCode");
466 assert.equal(routing.claude.startSession.mock.calls.length, 1);
467 const startInput = routing.claude.startSession.mock.calls[0]?.[0];
468 assert.equal(typeof startInput === "object" && startInput !== null, true);
469 if (startInput && typeof startInput === "object") {
470 const startPayload = startInput as { provider?: string; cwd?: string };
471 assert.equal(startPayload.provider, "claudeCode");
472 assert.equal(startPayload.cwd, "/tmp/project-claude");
473 }
474 }),
475 );
476
451477 it.effect("recovers stale persisted sessions for rollback by resuming thread identity", () =>
452478 Effect.gen(function* () {
453479 const provider = yield* ProviderService;
apps/server/src/serverLayers.ts+3-1
......@@ -18,6 +18,7 @@ import { OrchestrationProjectionPipelineLive } from "./orchestration/Layers/Proj
1818import { OrchestrationProjectionSnapshotQueryLive } from "./orchestration/Layers/ProjectionSnapshotQuery";
1919import { ProviderRuntimeIngestionLive } from "./orchestration/Layers/ProviderRuntimeIngestion";
2020import { ProviderUnsupportedError } from "./provider/Errors";
21import { makeClaudeCodeAdapterLive } from "./provider/Layers/ClaudeCodeAdapter";
2122import { makeCodexAdapterLive } from "./provider/Layers/CodexAdapter";
2223import { ProviderAdapterRegistryLive } from "./provider/Layers/ProviderAdapterRegistry";
2324import { makeProviderServiceLive } from "./provider/Layers/ProviderService";
......@@ -56,8 +57,9 @@ export function makeServerProviderLayer(): Layer.Layer<
5657 const codexAdapterLayer = makeCodexAdapterLive(
5758 nativeEventLogger ? { nativeEventLogger } : undefined,
5859 );
60 const claudeAdapterLayer = makeClaudeCodeAdapterLive();
5961 const adapterRegistryLayer = ProviderAdapterRegistryLive.pipe(
60 Layer.provide(codexAdapterLayer),
62 Layer.provide(Layer.mergeAll(codexAdapterLayer, claudeAdapterLayer)),
6163 Layer.provideMerge(providerSessionDirectoryLayer),
6264 );
6365 return makeProviderServiceLive(