From 291411a45e29f49c5e47dc15cb6b199f08634b32 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 27 Feb 2026 09:25:30 -0800 Subject: [PATCH] Inline Claude adapter behavior into live layer Co-authored-by: codex --- .../provider/Layers/ClaudeCodeAdapter.test.ts | 354 ++++++------------ .../src/provider/Layers/ClaudeCodeAdapter.ts | 346 ++++------------- 2 files changed, 202 insertions(+), 498 deletions(-) diff --git a/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts index 061bc0ca5b9a1e1821be35e7248e73b3e22d0e09..3996b39796b307cd8ca560350bdf4d6193ea1fc5 100644 --- a/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts @@ -1,186 +1,79 @@ -import type { - ProviderApprovalDecision, - ProviderRuntimeEvent, - ProviderSendTurnInput, - ProviderSession, - ProviderSessionStartInput, - ProviderTurnStartResult, -} from "@t3tools/contracts"; -import { - ApprovalRequestId, - EventId, - ProviderSessionId, - ProviderThreadId, - ProviderTurnId, -} from "@t3tools/contracts"; -import { afterAll, assert, it, vi } from "@effect/vitest"; -import { assertFailure } from "@effect/vitest/utils"; - -import { Effect, Fiber, Queue, Stream } from "effect"; +import { EventId, ProviderSessionId, ProviderThreadId, ProviderTurnId } from "@t3tools/contracts"; +import { assert, describe, it } from "@effect/vitest"; +import { Effect, Layer, Stream } from "effect"; -import { ProviderAdapterValidationError } from "../Errors.ts"; -import { ClaudeCodeAdapter } from "../Services/ClaudeCodeAdapter.ts"; -import { type ClaudeCodeRuntime, makeClaudeCodeAdapterLive } from "./ClaudeCodeAdapter.ts"; +import { + ProviderAdapterProcessError, + ProviderAdapterRequestError, + ProviderAdapterValidationError, +} from "../Errors.ts"; +import { + ClaudeCodeAdapter, + type ClaudeCodeAdapterShape, +} from "../Services/ClaudeCodeAdapter.ts"; +import { makeClaudeCodeAdapterLive } from "./ClaudeCodeAdapter.ts"; const asSessionId = (value: string): ProviderSessionId => ProviderSessionId.makeUnsafe(value); const asTurnId = (value: string): ProviderTurnId => ProviderTurnId.makeUnsafe(value); const asEventId = (value: string): EventId => EventId.makeUnsafe(value); -const asRequestId = (value: string): ApprovalRequestId => ApprovalRequestId.makeUnsafe(value); -class FakeClaudeRuntime implements ClaudeCodeRuntime { - private readonly runtimeEventQueue = Effect.runSync(Queue.unbounded()); - readonly streamEvents = Stream.fromQueue(this.runtimeEventQueue); +describe("ClaudeCodeAdapterLive", () => { + const layer = makeClaudeCodeAdapterLive(); - public startSessionImpl = vi.fn( - (input: ProviderSessionStartInput): Effect.Effect => { - const now = new Date().toISOString(); - return Effect.succeed({ - sessionId: asSessionId("claude-sess-1"), - provider: "claudeCode", - status: "ready", - threadId: ProviderThreadId.makeUnsafe("claude-thread-1"), - resumeCursor: input.resumeCursor ?? { opaque: "claude-cursor-1" }, - cwd: input.cwd, - model: input.model, - createdAt: now, - updatedAt: now, - }); - }, - ); - - public sendTurnImpl = vi.fn( - (_input: ProviderSendTurnInput): Effect.Effect => - Effect.succeed({ - threadId: ProviderThreadId.makeUnsafe("claude-thread-1"), - turnId: asTurnId("claude-turn-1"), - }), - ); - - public interruptTurnImpl = vi.fn( - (_sessionId: ProviderSessionId, _turnId?: ProviderTurnId): Effect.Effect => Effect.void, - ); - - public readThreadImpl = vi.fn((_sessionId: ProviderSessionId): Effect.Effect<{ - threadId: ProviderThreadId; - turns: never[]; - }> => - Effect.succeed({ - threadId: ProviderThreadId.makeUnsafe("claude-thread-1"), - turns: [], - })); - - public rollbackThreadImpl = vi.fn( - (_sessionId: ProviderSessionId, _numTurns: number): Effect.Effect<{ - threadId: ProviderThreadId; - turns: never[]; - }> => - Effect.succeed({ - threadId: ProviderThreadId.makeUnsafe("claude-thread-1"), - turns: [], - }), - ); - - public respondToRequestImpl = vi.fn( - ( - _sessionId: ProviderSessionId, - _requestId: ApprovalRequestId, - _decision: ProviderApprovalDecision, - ): Effect.Effect => Effect.void, - ); - - public stopSessionImpl = vi.fn((_sessionId: ProviderSessionId): Effect.Effect => Effect.void); - public listSessionsImpl = vi.fn((): Effect.Effect> => Effect.succeed([])); - public hasSessionImpl = vi.fn((_sessionId: ProviderSessionId): Effect.Effect => Effect.succeed(false)); - public stopAllImpl = vi.fn((): Effect.Effect => Effect.void); - - startSession(input: ProviderSessionStartInput): Effect.Effect { - return this.startSessionImpl(input); - } - - sendTurn(input: ProviderSendTurnInput): Effect.Effect { - return this.sendTurnImpl(input); - } - - interruptTurn(sessionId: ProviderSessionId, turnId?: ProviderTurnId): Effect.Effect { - return this.interruptTurnImpl(sessionId, turnId); - } - - readThread(sessionId: ProviderSessionId) { - return this.readThreadImpl(sessionId); - } - - rollbackThread(sessionId: ProviderSessionId, numTurns: number) { - return this.rollbackThreadImpl(sessionId, numTurns); - } - - respondToRequest( - sessionId: ProviderSessionId, - requestId: ApprovalRequestId, - decision: ProviderApprovalDecision, - ): Effect.Effect { - return this.respondToRequestImpl(sessionId, requestId, decision); - } - - stopSession(sessionId: ProviderSessionId): Effect.Effect { - return this.stopSessionImpl(sessionId); - } - - listSessions(): Effect.Effect> { - return this.listSessionsImpl(); - } - - hasSession(sessionId: ProviderSessionId): Effect.Effect { - return this.hasSessionImpl(sessionId); - } - - stopAll(): Effect.Effect { - return this.stopAllImpl(); - } - - emitRuntimeEvent(event: ProviderRuntimeEvent): void { - Queue.offerAllUnsafe(this.runtimeEventQueue, [event]); - } -} - -const validationRuntime = new FakeClaudeRuntime(); -const validationLayer = it.layer(makeClaudeCodeAdapterLive({ runtime: validationRuntime })); - -validationLayer("ClaudeCodeAdapterLive validation", (it) => { it.effect("returns validation error for non-claudeCode provider on startSession", () => Effect.gen(function* () { const adapter = yield* ClaudeCodeAdapter; - const result = yield* adapter - .startSession({ - provider: "codex", - }) - .pipe(Effect.result); + const result = yield* adapter.startSession({ provider: "codex" }).pipe(Effect.result); - assertFailure( - result, + assert.equal(result._tag, "Failure"); + if (result._tag !== "Failure") { + return; + } + assert.equal(result.failure._tag, "ProviderAdapterValidationError"); + if (result.failure._tag !== "ProviderAdapterValidationError") { + return; + } + assert.deepEqual( + result.failure, new ProviderAdapterValidationError({ provider: "claudeCode", operation: "startSession", issue: "Expected provider 'claudeCode' but received 'codex'.", }), ); - assert.equal(validationRuntime.startSessionImpl.mock.calls.length, 0); - }), + }).pipe(Effect.provide(layer)), ); -}); -const sessionErrorRuntime = new FakeClaudeRuntime(); -sessionErrorRuntime.sendTurnImpl.mockImplementation(() => { - return Effect.fail(new Error("Unknown session: claude-sess-missing")); -}); -const sessionErrorLayer = it.layer(makeClaudeCodeAdapterLive({ runtime: sessionErrorRuntime })); + it.effect("returns typed process error when runtime is not configured", () => + Effect.gen(function* () { + const adapter = yield* ClaudeCodeAdapter; + const result = yield* adapter.startSession({ provider: "claudeCode" }).pipe(Effect.result); -sessionErrorLayer("ClaudeCodeAdapterLive session errors", (it) => { - it.effect("maps unknown-session sendTurn errors to ProviderAdapterSessionNotFoundError", () => + assert.equal(result._tag, "Failure"); + if (result._tag !== "Failure") { + return; + } + assert.equal(result.failure._tag, "ProviderAdapterProcessError"); + if (result.failure._tag !== "ProviderAdapterProcessError") { + return; + } + assert.deepEqual( + result.failure, + new ProviderAdapterProcessError({ + provider: "claudeCode", + sessionId: "pending", + detail: "Claude Code runtime is not configured.", + }), + ); + }).pipe(Effect.provide(layer)), + ); + + it.effect("returns typed request errors for turn operations when runtime is not configured", () => Effect.gen(function* () { const adapter = yield* ClaudeCodeAdapter; const result = yield* adapter .sendTurn({ - sessionId: asSessionId("claude-sess-missing"), + sessionId: asSessionId("claude-sess-1"), input: "hello", attachments: [], }) @@ -190,89 +83,84 @@ sessionErrorLayer("ClaudeCodeAdapterLive session errors", (it) => { if (result._tag !== "Failure") { return; } - - assert.equal(result.failure._tag, "ProviderAdapterSessionNotFoundError"); - if (result.failure._tag !== "ProviderAdapterSessionNotFoundError") { + assert.equal(result.failure._tag, "ProviderAdapterRequestError"); + if (result.failure._tag !== "ProviderAdapterRequestError") { return; } - assert.equal(result.failure.provider, "claudeCode"); - assert.equal(result.failure.sessionId, "claude-sess-missing"); - assert.instanceOf(result.failure.cause, Error); - }), + assert.deepEqual( + result.failure, + new ProviderAdapterRequestError({ + provider: "claudeCode", + method: "turn/start", + detail: "Claude Code runtime is not configured. (session claude-sess-1)", + }), + ); + }).pipe(Effect.provide(layer)), ); -}); - -const lifecycleRuntime = new FakeClaudeRuntime(); -const lifecycleLayer = it.layer(makeClaudeCodeAdapterLive({ runtime: lifecycleRuntime })); -lifecycleLayer("ClaudeCodeAdapterLive lifecycle", (it) => { - it.effect("forwards providerOptions and opaque resumeCursor to runtime startSession", () => + it.effect("emits no runtime events by default", () => Effect.gen(function* () { const adapter = yield* ClaudeCodeAdapter; - const resumeCursor = { - threadId: "provider-thread-opaque", - sessionAt: "message:42", - }; - const started = yield* adapter.startSession({ - provider: "claudeCode", - cwd: "/tmp/claude-workspace", - model: "claude-sonnet-4", - resumeCursor, - providerOptions: { - claudeCode: { - binaryPath: "/usr/local/bin/claude", - permissionMode: "acceptEdits", - maxThinkingTokens: 4_096, - }, - }, - }); - - assert.equal(started.provider, "claudeCode"); - assert.deepEqual(started.resumeCursor, resumeCursor); - assert.deepEqual(lifecycleRuntime.startSessionImpl.mock.calls[0]?.[0], { - provider: "claudeCode", - cwd: "/tmp/claude-workspace", - model: "claude-sonnet-4", - resumeCursor, - providerOptions: { - claudeCode: { - binaryPath: "/usr/local/bin/claude", - permissionMode: "acceptEdits", - maxThinkingTokens: 4_096, - }, - }, - }); - }), + const firstEvent = yield* Stream.runHead(adapter.streamEvents); + assert.equal(firstEvent._tag, "None"); + }).pipe(Effect.provide(layer)), ); +}); - it.effect("passes through canonical runtime events without remapping", () => +describe("ClaudeCodeAdapter mocked layer", () => { + it.effect("supports replacing the adapter layer directly in tests", () => Effect.gen(function* () { const adapter = yield* ClaudeCodeAdapter; - const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); - const runtimeEvent: ProviderRuntimeEvent = { - type: "approval.requested", - eventId: asEventId("evt-claude-approval"), - provider: "claudeCode", - sessionId: asSessionId("claude-sess-1"), - createdAt: new Date().toISOString(), - requestId: asRequestId("approval-claude-1"), - requestKind: "command", - }; - - lifecycleRuntime.emitRuntimeEvent(runtimeEvent); - const firstEvent = yield* Fiber.join(firstEventFiber); - - assert.equal(firstEvent._tag, "Some"); - if (firstEvent._tag !== "Some") { - return; - } - assert.deepEqual(firstEvent.value, runtimeEvent); - }), + const started = yield* adapter.startSession({ provider: "claudeCode" }); + assert.equal(started.sessionId, "mock-sess-1"); + const event = yield* Stream.runHead(adapter.streamEvents); + assert.equal(event._tag, "Some"); + }).pipe( + Effect.provide( + Layer.succeed(ClaudeCodeAdapter, { + provider: "claudeCode", + startSession: () => + Effect.succeed({ + sessionId: asSessionId("mock-sess-1"), + provider: "claudeCode", + status: "ready", + threadId: ProviderThreadId.makeUnsafe("mock-thread-1"), + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }), + sendTurn: () => + Effect.succeed({ + threadId: ProviderThreadId.makeUnsafe("mock-thread-1"), + turnId: asTurnId("mock-turn-1"), + }), + interruptTurn: () => Effect.void, + readThread: () => + Effect.succeed({ + threadId: ProviderThreadId.makeUnsafe("mock-thread-1"), + turns: [], + }), + rollbackThread: () => + Effect.succeed({ + threadId: ProviderThreadId.makeUnsafe("mock-thread-1"), + turns: [], + }), + respondToRequest: () => Effect.void, + stopSession: () => Effect.void, + listSessions: () => Effect.succeed([]), + hasSession: () => Effect.succeed(false), + stopAll: () => Effect.void, + streamEvents: Stream.make({ + type: "turn.completed", + eventId: asEventId("evt-mock-1"), + provider: "claudeCode", + sessionId: asSessionId("mock-sess-1"), + createdAt: new Date().toISOString(), + threadId: ProviderThreadId.makeUnsafe("mock-thread-1"), + turnId: asTurnId("mock-turn-1"), + status: "completed", + }), + } satisfies ClaudeCodeAdapterShape), + ), + ), ); }); - -afterAll(() => { - assert.equal(validationRuntime.stopAllImpl.mock.calls.length, 1); - assert.equal(sessionErrorRuntime.stopAllImpl.mock.calls.length, 1); - assert.equal(lifecycleRuntime.stopAllImpl.mock.calls.length, 1); -}); diff --git a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts index 1e4b0b9f4153e46098c8de464df135ab637ed2ca..1b73e639f87f0608afa0bd1676bd39c73ee2526f 100644 --- a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts @@ -1,289 +1,105 @@ /** * ClaudeCodeAdapterLive - Scoped live implementation for the Claude Code provider adapter. * - * Wraps a Claude runtime bridge behind the `ClaudeCodeAdapter` service contract - * and maps runtime failures into the shared `ProviderAdapterError` algebra. + * Until the Claude runtime bridge is implemented, this adapter is wired + * directly and returns typed "not configured" errors for runtime operations. * * @module ClaudeCodeAdapterLive */ -import type { - ApprovalRequestId, - ProviderApprovalDecision, - ProviderRuntimeEvent, - ProviderSendTurnInput, - ProviderSession, - ProviderSessionId, - ProviderSessionStartInput, - ProviderTurnId, - ProviderTurnStartResult, -} from "@t3tools/contracts"; +import type { ProviderRuntimeEvent } from "@t3tools/contracts"; import { Effect, Layer, Stream } from "effect"; import { ProviderAdapterProcessError, ProviderAdapterRequestError, - ProviderAdapterSessionClosedError, - ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, - type ProviderAdapterError, } from "../Errors.ts"; -import { type ProviderThreadSnapshot } from "../Services/ProviderAdapter.ts"; import { ClaudeCodeAdapter, type ClaudeCodeAdapterShape } from "../Services/ClaudeCodeAdapter.ts"; const PROVIDER = "claudeCode" as const; - -export interface ClaudeCodeRuntime { - readonly startSession: ( - input: ProviderSessionStartInput, - ) => Effect.Effect; - readonly sendTurn: ( - input: ProviderSendTurnInput, - ) => Effect.Effect; - readonly interruptTurn: ( - sessionId: ProviderSessionId, - turnId?: ProviderTurnId, - ) => Effect.Effect; - readonly readThread: ( - sessionId: ProviderSessionId, - ) => Effect.Effect; - readonly rollbackThread: ( - sessionId: ProviderSessionId, - numTurns: number, - ) => Effect.Effect; - readonly respondToRequest: ( - sessionId: ProviderSessionId, - requestId: ApprovalRequestId, - decision: ProviderApprovalDecision, - ) => Effect.Effect; - readonly stopSession: (sessionId: ProviderSessionId) => Effect.Effect; - readonly listSessions: () => Effect.Effect>; - readonly hasSession: (sessionId: ProviderSessionId) => Effect.Effect; - readonly stopAll: () => Effect.Effect; - readonly streamEvents: Stream.Stream; -} - -export interface ClaudeCodeAdapterLiveOptions { - readonly runtime?: ClaudeCodeRuntime; - readonly makeRuntime?: () => ClaudeCodeRuntime; -} - -function toMessage(cause: unknown, fallback: string): string { - if (cause instanceof Error && cause.message.length > 0) { - return cause.message; - } - return fallback; -} - -function toSessionError( - sessionId: ProviderSessionId, - cause: unknown, -): ProviderAdapterSessionNotFoundError | ProviderAdapterSessionClosedError | undefined { - const normalized = toMessage(cause, "").toLowerCase(); - if (normalized.includes("unknown session") || normalized.includes("unknown provider session")) { - return new ProviderAdapterSessionNotFoundError({ - provider: PROVIDER, - sessionId, - cause, - }); - } - if (normalized.includes("session is closed")) { - return new ProviderAdapterSessionClosedError({ - provider: PROVIDER, - sessionId, - cause, - }); - } - return undefined; -} - -function toRequestError( - sessionId: ProviderSessionId, - method: string, - cause: unknown, -): ProviderAdapterError { - const sessionError = toSessionError(sessionId, cause); - if (sessionError) { - return sessionError; - } - return new ProviderAdapterRequestError({ - provider: PROVIDER, - method, - detail: toMessage(cause, `${method} failed`), - cause, - }); -} - -function makeUnavailableRuntime(): ClaudeCodeRuntime { - const unavailableDetail = "Claude Code runtime is not configured."; - - return { - startSession: () => - Effect.fail( - new ProviderAdapterProcessError({ +const CLAUDE_RUNTIME_NOT_CONFIGURED = "Claude Code runtime is not configured."; + +const makeClaudeCodeAdapter = Effect.succeed({ + provider: PROVIDER, + startSession: (input) => { + if (input.provider !== undefined && input.provider !== PROVIDER) { + return Effect.fail( + new ProviderAdapterValidationError({ provider: PROVIDER, - sessionId: "pending", - detail: unavailableDetail, + operation: "startSession", + issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`, }), - ), - sendTurn: (_input) => - Effect.fail( - new ProviderAdapterRequestError({ - provider: PROVIDER, - method: "turn/start", - detail: unavailableDetail, - }), - ), - interruptTurn: (_sessionId) => - Effect.fail( - new ProviderAdapterRequestError({ - provider: PROVIDER, - method: "turn/interrupt", - detail: unavailableDetail, - }), - ), - readThread: (_sessionId) => - Effect.fail( - new ProviderAdapterRequestError({ - provider: PROVIDER, - method: "thread/read", - detail: unavailableDetail, - }), - ), - rollbackThread: (_sessionId) => - Effect.fail( - new ProviderAdapterRequestError({ - provider: PROVIDER, - method: "thread/rollback", - detail: unavailableDetail, - }), - ), - respondToRequest: (_sessionId) => - Effect.fail( - new ProviderAdapterRequestError({ - provider: PROVIDER, - method: "item/requestApproval/decision", - detail: unavailableDetail, - }), - ), - stopSession: () => Effect.void, - listSessions: () => Effect.succeed([]), - hasSession: () => Effect.succeed(false), - stopAll: () => Effect.void, - streamEvents: Stream.empty, - }; -} + ); + } -const makeClaudeCodeAdapter = (options?: ClaudeCodeAdapterLiveOptions) => - Effect.gen(function* () { - const runtime = yield* Effect.acquireRelease( - Effect.sync(() => { - if (options?.runtime) { - return options.runtime; - } - if (options?.makeRuntime) { - return options.makeRuntime(); - } - return makeUnavailableRuntime(); + return Effect.fail( + new ProviderAdapterProcessError({ + provider: PROVIDER, + sessionId: "pending", + detail: CLAUDE_RUNTIME_NOT_CONFIGURED, }), - (runtime) => runtime.stopAll(), ); - - const startSession: ClaudeCodeAdapterShape["startSession"] = (input) => { - if (input.provider !== undefined && input.provider !== PROVIDER) { - return Effect.fail( - new ProviderAdapterValidationError({ - provider: PROVIDER, - operation: "startSession", - issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`, - }), - ); - } - - return runtime.startSession(input).pipe( - Effect.mapError( - (cause) => - new ProviderAdapterProcessError({ - provider: PROVIDER, - sessionId: "pending", - detail: toMessage(cause, "Failed to start Claude Code adapter session."), - cause, - }), - ), + }, + sendTurn: (input) => + Effect.fail( + new ProviderAdapterRequestError({ + provider: PROVIDER, + method: "turn/start", + detail: `${CLAUDE_RUNTIME_NOT_CONFIGURED} (session ${input.sessionId})`, + }), + ), + interruptTurn: (sessionId, _turnId) => + Effect.fail( + new ProviderAdapterRequestError({ + provider: PROVIDER, + method: "turn/interrupt", + detail: `${CLAUDE_RUNTIME_NOT_CONFIGURED} (session ${sessionId})`, + }), + ), + readThread: (sessionId) => + Effect.fail( + new ProviderAdapterRequestError({ + provider: PROVIDER, + method: "thread/read", + detail: `${CLAUDE_RUNTIME_NOT_CONFIGURED} (session ${sessionId})`, + }), + ), + rollbackThread: (sessionId, numTurns) => { + if (!Number.isInteger(numTurns) || numTurns < 1) { + return Effect.fail( + new ProviderAdapterValidationError({ + provider: PROVIDER, + operation: "rollbackThread", + issue: "numTurns must be an integer >= 1.", + }), ); - }; - - const sendTurn: ClaudeCodeAdapterShape["sendTurn"] = (input) => - runtime - .sendTurn(input) - .pipe(Effect.mapError((cause) => toRequestError(input.sessionId, "turn/start", cause))); - - const interruptTurn: ClaudeCodeAdapterShape["interruptTurn"] = (sessionId, turnId) => - runtime - .interruptTurn(sessionId, turnId) - .pipe(Effect.mapError((cause) => toRequestError(sessionId, "turn/interrupt", cause))); - - const readThread: ClaudeCodeAdapterShape["readThread"] = (sessionId) => - runtime - .readThread(sessionId) - .pipe(Effect.mapError((cause) => toRequestError(sessionId, "thread/read", cause))); - - const rollbackThread: ClaudeCodeAdapterShape["rollbackThread"] = (sessionId, numTurns) => { - if (!Number.isInteger(numTurns) || numTurns < 1) { - return Effect.fail( - new ProviderAdapterValidationError({ - provider: PROVIDER, - operation: "rollbackThread", - issue: "numTurns must be an integer >= 1.", - }), - ); - } + } - return runtime - .rollbackThread(sessionId, numTurns) - .pipe(Effect.mapError((cause) => toRequestError(sessionId, "thread/rollback", cause))); - }; - - const respondToRequest: ClaudeCodeAdapterShape["respondToRequest"] = ( - sessionId, - requestId, - decision, - ) => - runtime - .respondToRequest(sessionId, requestId, decision) - .pipe( - Effect.mapError((cause) => - toRequestError(sessionId, "item/requestApproval/decision", cause), - ), - ); - - const stopSession: ClaudeCodeAdapterShape["stopSession"] = (sessionId) => - runtime.stopSession(sessionId); - - const listSessions: ClaudeCodeAdapterShape["listSessions"] = () => runtime.listSessions(); - - const hasSession: ClaudeCodeAdapterShape["hasSession"] = (sessionId) => - runtime.hasSession(sessionId); - - const stopAll: ClaudeCodeAdapterShape["stopAll"] = () => runtime.stopAll(); - - return { - provider: PROVIDER, - startSession, - sendTurn, - interruptTurn, - readThread, - rollbackThread, - respondToRequest, - stopSession, - listSessions, - hasSession, - stopAll, - streamEvents: runtime.streamEvents, - } satisfies ClaudeCodeAdapterShape; - }); - -export const ClaudeCodeAdapterLive = Layer.effect(ClaudeCodeAdapter, makeClaudeCodeAdapter()); - -export function makeClaudeCodeAdapterLive(options?: ClaudeCodeAdapterLiveOptions) { - return Layer.effect(ClaudeCodeAdapter, makeClaudeCodeAdapter(options)); + return Effect.fail( + new ProviderAdapterRequestError({ + provider: PROVIDER, + method: "thread/rollback", + detail: `${CLAUDE_RUNTIME_NOT_CONFIGURED} (session ${sessionId})`, + }), + ); + }, + respondToRequest: (sessionId, _requestId, _decision) => + Effect.fail( + new ProviderAdapterRequestError({ + provider: PROVIDER, + method: "item/requestApproval/decision", + detail: `${CLAUDE_RUNTIME_NOT_CONFIGURED} (session ${sessionId})`, + }), + ), + stopSession: (_sessionId) => Effect.void, + listSessions: () => Effect.succeed([]), + hasSession: (_sessionId) => Effect.succeed(false), + stopAll: () => Effect.void, + streamEvents: Stream.empty as Stream.Stream, +} satisfies ClaudeCodeAdapterShape); + +export const ClaudeCodeAdapterLive = Layer.effect(ClaudeCodeAdapter, makeClaudeCodeAdapter); + +export function makeClaudeCodeAdapterLive() { + return Layer.effect(ClaudeCodeAdapter, makeClaudeCodeAdapter); } -- 2.54.0