History

Wire Claude adapter into registry and server provider layer

Add provider-service routing coverage for explicit claudeCode sessions.

Julius Marminge committed 8 months ago

Commit e9ced441 parent 802cd124 files · +68 −12Browse files

Unified · Split

4 files
  1. apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts +28 −4
  2. apps/server/src/provider/Layers/ProviderAdapterRegistry.ts +5 −1
  3. apps/server/src/provider/Layers/ProviderService.test.ts +32 −6
  4. apps/server/src/serverLayers.ts +3 −1
apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts+28−4
@@ -4,6 +4,7 @@ import { assertFailure } from "@effect/vitest/utils"; · 3 unmodified lines
44
55 import { Effect, Layer, Stream } from "effect";
66
7+import { ClaudeCodeAdapter, ClaudeCodeAdapterShape } from "../Services/ClaudeCodeAdapter.ts";
78 import { CodexAdapter, CodexAdapterShape } from "../Services/CodexAdapter.ts";
89 import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts";
910 import { ProviderAdapterRegistryLive } from "./ProviderAdapterRegistry.ts";
@@ -25,9 +26,30 @@ const fakeCodexAdapter: CodexAdapterShape = { · 15 unmodified lines
2526 streamEvents: Stream.empty,
2627 };
2728
29+const 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+
2844 const 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) => { · 2 unmodified lines
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 { · 14 unmodified lines
1515 ProviderAdapterRegistry,
1616 type ProviderAdapterRegistryShape,
1717 } from "../Services/ProviderAdapterRegistry.ts";
18+import { ClaudeCodeAdapter } from "../Services/ClaudeCodeAdapter.ts";
1819 import { CodexAdapter } from "../Services/CodexAdapter.ts";
1920
2021 export interface ProviderAdapterRegistryLiveOptions {
@@ -23,7 +24,10 @@ export interface ProviderAdapterRegistryLiveOptions { · 2 unmodified lines
2324
2425 const 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 { · 11 unmodified lines
1212 import {
1313 ApprovalRequestId,
1414 EventId,
15+ type ProviderKind,
1516 ProviderSessionStartInput,
1617 ProviderSessionId,
1718 ProviderThreadId,
@@ -52,7 +53,7 @@ const asRequestId = (value: string): ApprovalRequestId => ApprovalRequestId.make · 34 unmodified lines
5253 const asEventId = (value: string): EventId => EventId.makeUnsafe(value);
5354 const asThreadId = (value: string): ThreadId => ThreadId.makeUnsafe(value);
5455
55−function makeFakeCodexAdapter() {
56+function 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() { · 5 unmodified lines
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() { · 14 unmodified lines
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() { · 68 unmodified lines
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) => · 32 unmodified lines
199200
200201 function 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() { · 16 unmodified lines
227231
228232 return {
229233 codex,
234+ claude,
230235 layer,
231236 };
232237 }
@@ -448,6 +453,27 @@ routing.layer("ProviderServiceLive routing", (it) => { · 215 unmodified lines
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 · 17 unmodified lines
1818 import { OrchestrationProjectionSnapshotQueryLive } from "./orchestration/Layers/ProjectionSnapshotQuery";
1919 import { ProviderRuntimeIngestionLive } from "./orchestration/Layers/ProviderRuntimeIngestion";
2020 import { ProviderUnsupportedError } from "./provider/Errors";
21+import { makeClaudeCodeAdapterLive } from "./provider/Layers/ClaudeCodeAdapter";
2122 import { makeCodexAdapterLive } from "./provider/Layers/CodexAdapter";
2223 import { ProviderAdapterRegistryLive } from "./provider/Layers/ProviderAdapterRegistry";
2324 import { makeProviderServiceLive } from "./provider/Layers/ProviderService";
@@ -56,8 +57,9 @@ export function makeServerProviderLayer(): Layer.Layer< · 32 unmodified lines
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(

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t