Julius Marmingefmt858116d8 months agoHistory
947 lines · 28.7 KB · TypeScript
RawDownload
import type {  Options as ClaudeQueryOptions,  PermissionMode,  PermissionResult,  SDKMessage,  SDKUserMessage,} from "@anthropic-ai/claude-agent-sdk";import { ApprovalRequestId, ThreadId } from "@t3tools/contracts";import { assert, describe, it } from "@effect/vitest";import { Effect, Fiber, Random, Stream } from "effect"; import { ProviderAdapterValidationError } from "../Errors.ts";import { ClaudeCodeAdapter } from "../Services/ClaudeCodeAdapter.ts";import {  makeClaudeCodeAdapterLive,  type ClaudeCodeAdapterLiveOptions,} from "./ClaudeCodeAdapter.ts"; class FakeClaudeQuery implements AsyncIterable<SDKMessage> {  private readonly queue: Array<SDKMessage> = [];  private readonly resolvers: Array<(value: IteratorResult<SDKMessage>) => void> = [];  private done = false;   public readonly interruptCalls: Array<void> = [];  public readonly setModelCalls: Array<string | undefined> = [];  public readonly setPermissionModeCalls: Array<string> = [];  public readonly setMaxThinkingTokensCalls: Array<number | null> = [];  public closeCalls = 0;   emit(message: SDKMessage): void {    if (this.done) {      return;    }    const resolver = this.resolvers.shift();    if (resolver) {      resolver({ done: false, value: message });      return;    }    this.queue.push(message);  }   finish(): void {    if (this.done) {      return;    }    this.done = true;    for (const resolver of this.resolvers.splice(0)) {      resolver({ done: true, value: undefined });    }  }   readonly interrupt = async (): Promise<void> => {    this.interruptCalls.push(undefined);  };   readonly setModel = async (model?: string): Promise<void> => {    this.setModelCalls.push(model);  };   readonly setPermissionMode = async (mode: PermissionMode): Promise<void> => {    this.setPermissionModeCalls.push(mode);  };   readonly setMaxThinkingTokens = async (maxThinkingTokens: number | null): Promise<void> => {    this.setMaxThinkingTokensCalls.push(maxThinkingTokens);  };   readonly close = (): void => {    this.closeCalls += 1;    this.finish();  };   [Symbol.asyncIterator](): AsyncIterator<SDKMessage> {    return {      next: () => {        if (this.queue.length > 0) {          const value = this.queue.shift();          if (value) {            return Promise.resolve({              done: false,              value,            });          }        }        if (this.done) {          return Promise.resolve({            done: true,            value: undefined,          });        }        return new Promise((resolve) => {          this.resolvers.push(resolve);        });      },    };  }} interface Harness {  readonly layer: ReturnType<typeof makeClaudeCodeAdapterLive>;  readonly query: FakeClaudeQuery;  readonly getLastCreateQueryInput: () =>    | {        readonly prompt: AsyncIterable<SDKUserMessage>;        readonly options: ClaudeQueryOptions;      }    | undefined;} function makeHarness(config?: {  readonly nativeEventLogPath?: string;  readonly nativeEventLogger?: ClaudeCodeAdapterLiveOptions["nativeEventLogger"];}): Harness {  const query = new FakeClaudeQuery();  let createInput:    | {        readonly prompt: AsyncIterable<SDKUserMessage>;        readonly options: ClaudeQueryOptions;      }    | undefined;   const adapterOptions: ClaudeCodeAdapterLiveOptions = {    createQuery: (input) => {      createInput = input;      return query;    },    ...(config?.nativeEventLogger      ? {          nativeEventLogger: config.nativeEventLogger,        }      : {}),    ...(config?.nativeEventLogPath      ? {          nativeEventLogPath: config.nativeEventLogPath,        }      : {}),  };   return {    layer: makeClaudeCodeAdapterLive(adapterOptions),    query,    getLastCreateQueryInput: () => createInput,  };} function makeDeterministicRandomService(seed = 0x1234_5678): {  nextIntUnsafe: () => number;  nextDoubleUnsafe: () => number;} {  let state = seed >>> 0;  const nextIntUnsafe = (): number => {    state = (Math.imul(1_664_525, state) + 1_013_904_223) >>> 0;    return state;  };   return {    nextIntUnsafe,    nextDoubleUnsafe: () => nextIntUnsafe() / 0x1_0000_0000,  };} const THREAD_ID = ThreadId.makeUnsafe("thread-claude-1");const RESUME_THREAD_ID = ThreadId.makeUnsafe("thread-claude-resume"); describe("ClaudeCodeAdapterLive", () => {  it.effect("returns validation error for non-claudeCode provider on startSession", () => {    const harness = makeHarness();    return Effect.gen(function* () {      const adapter = yield* ClaudeCodeAdapter;      const result = yield* adapter        .startSession({ threadId: THREAD_ID, provider: "codex", runtimeMode: "full-access" })        .pipe(Effect.result);       assert.equal(result._tag, "Failure");      if (result._tag !== "Failure") {        return;      }      assert.deepEqual(        result.failure,        new ProviderAdapterValidationError({          provider: "claudeCode",          operation: "startSession",          issue: "Expected provider 'claudeCode' but received 'codex'.",        }),      );    }).pipe(      Effect.provideService(Random.Random, makeDeterministicRandomService()),      Effect.provide(harness.layer),    );  });   it.effect("derives bypass permission mode from full-access runtime policy", () => {    const harness = makeHarness();    return Effect.gen(function* () {      const adapter = yield* ClaudeCodeAdapter;      yield* adapter.startSession({        threadId: THREAD_ID,        provider: "claudeCode",        runtimeMode: "full-access",      });       const createInput = harness.getLastCreateQueryInput();      assert.equal(createInput?.options.permissionMode, "bypassPermissions");      assert.equal(createInput?.options.allowDangerouslySkipPermissions, true);    }).pipe(      Effect.provideService(Random.Random, makeDeterministicRandomService()),      Effect.provide(harness.layer),    );  });   it.effect("keeps explicit claude permission mode over runtime-derived defaults", () => {    const harness = makeHarness();    return Effect.gen(function* () {      const adapter = yield* ClaudeCodeAdapter;      yield* adapter.startSession({        threadId: THREAD_ID,        provider: "claudeCode",        runtimeMode: "full-access",        providerOptions: {          claudeCode: {            permissionMode: "plan",          },        },      });       const createInput = harness.getLastCreateQueryInput();      assert.equal(createInput?.options.permissionMode, "plan");      assert.equal(createInput?.options.allowDangerouslySkipPermissions, undefined);    }).pipe(      Effect.provideService(Random.Random, makeDeterministicRandomService()),      Effect.provide(harness.layer),    );  });   it.effect("maps Claude stream/runtime messages to canonical provider runtime events", () => {    const harness = makeHarness();    return Effect.gen(function* () {      const adapter = yield* ClaudeCodeAdapter;       const runtimeEventsFiber = yield* Stream.take(adapter.streamEvents, 11).pipe(        Stream.runCollect,        Effect.forkChild,      );       const session = yield* adapter.startSession({        threadId: THREAD_ID,        provider: "claudeCode",        model: "claude-sonnet-4-5",        runtimeMode: "full-access",      });       const turn = yield* adapter.sendTurn({        threadId: session.threadId,        input: "hello",        attachments: [],      });       harness.query.emit({        type: "stream_event",        session_id: "sdk-session-1",        uuid: "stream-1",        parent_tool_use_id: null,        event: {          type: "content_block_delta",          index: 0,          delta: {            type: "text_delta",            text: "Hi",          },        },      } as unknown as SDKMessage);       harness.query.emit({        type: "stream_event",        session_id: "sdk-session-1",        uuid: "stream-2",        parent_tool_use_id: null,        event: {          type: "content_block_start",          index: 1,          content_block: {            type: "tool_use",            id: "tool-1",            name: "Bash",            input: {              command: "ls",            },          },        },      } as unknown as SDKMessage);       harness.query.emit({        type: "stream_event",        session_id: "sdk-session-1",        uuid: "stream-3",        parent_tool_use_id: null,        event: {          type: "content_block_stop",          index: 1,        },      } as unknown as SDKMessage);       harness.query.emit({        type: "assistant",        session_id: "sdk-session-1",        uuid: "assistant-1",        parent_tool_use_id: null,        message: {          id: "assistant-message-1",          content: [{ type: "text", text: "Hi" }],        },      } as unknown as SDKMessage);       harness.query.emit({        type: "result",        subtype: "success",        is_error: false,        errors: [],        session_id: "sdk-session-1",        uuid: "result-1",      } as unknown as SDKMessage);       const runtimeEvents = Array.from(yield* Fiber.join(runtimeEventsFiber));      assert.deepEqual(        runtimeEvents.map((event) => event.type),        [          "session.started",          "session.configured",          "session.state.changed",          "turn.started",          "thread.started",          "content.delta",          "item.started",          "item.completed",          "item.updated",          "item.completed",          "turn.completed",        ],      );       const turnStarted = runtimeEvents[3];      assert.equal(turnStarted?.type, "turn.started");      if (turnStarted?.type === "turn.started") {        assert.equal(String(turnStarted.turnId), String(turn.turnId));      }       const deltaEvent = runtimeEvents.find((event) => event.type === "content.delta");      assert.equal(deltaEvent?.type, "content.delta");      if (deltaEvent?.type === "content.delta") {        assert.equal(deltaEvent.payload.delta, "Hi");        assert.equal(String(deltaEvent.turnId), String(turn.turnId));      }       const toolStarted = runtimeEvents.find((event) => event.type === "item.started");      assert.equal(toolStarted?.type, "item.started");      if (toolStarted?.type === "item.started") {        assert.equal(toolStarted.payload.itemType, "command_execution");      }       const turnCompleted = runtimeEvents[runtimeEvents.length - 1];      assert.equal(turnCompleted?.type, "turn.completed");      if (turnCompleted?.type === "turn.completed") {        assert.equal(String(turnCompleted.turnId), String(turn.turnId));        assert.equal(turnCompleted.payload.state, "completed");      }    }).pipe(      Effect.provideService(Random.Random, makeDeterministicRandomService()),      Effect.provide(harness.layer),    );  });   it.effect(    "emits completion only after turn result when assistant frames arrive before deltas",    () => {      const harness = makeHarness();      return Effect.gen(function* () {        const adapter = yield* ClaudeCodeAdapter;         const runtimeEventsFiber = yield* Stream.take(adapter.streamEvents, 9).pipe(          Stream.runCollect,          Effect.forkChild,        );         const session = yield* adapter.startSession({          threadId: THREAD_ID,          provider: "claudeCode",          runtimeMode: "full-access",        });         const turn = yield* adapter.sendTurn({          threadId: session.threadId,          input: "hello",          attachments: [],        });         harness.query.emit({          type: "assistant",          session_id: "sdk-session-early-assistant",          uuid: "assistant-early",          parent_tool_use_id: null,          message: {            id: "assistant-message-early",            content: [              { type: "tool_use", id: "tool-early", name: "Read", input: { path: "a.ts" } },            ],          },        } as unknown as SDKMessage);         harness.query.emit({          type: "stream_event",          session_id: "sdk-session-early-assistant",          uuid: "stream-early",          parent_tool_use_id: null,          event: {            type: "content_block_delta",            index: 0,            delta: {              type: "text_delta",              text: "Late text",            },          },        } as unknown as SDKMessage);         harness.query.emit({          type: "result",          subtype: "success",          is_error: false,          errors: [],          session_id: "sdk-session-early-assistant",          uuid: "result-early",        } as unknown as SDKMessage);         const runtimeEvents = Array.from(yield* Fiber.join(runtimeEventsFiber));        assert.deepEqual(          runtimeEvents.map((event) => event.type),          [            "session.started",            "session.configured",            "session.state.changed",            "turn.started",            "thread.started",            "item.updated",            "content.delta",            "item.completed",            "turn.completed",          ],        );         const deltaIndex = runtimeEvents.findIndex((event) => event.type === "content.delta");        const completedIndex = runtimeEvents.findIndex((event) => event.type === "item.completed");        assert.equal(deltaIndex >= 0 && completedIndex >= 0 && deltaIndex < completedIndex, true);         const deltaEvent = runtimeEvents[deltaIndex];        assert.equal(deltaEvent?.type, "content.delta");        if (deltaEvent?.type === "content.delta") {          assert.equal(deltaEvent.payload.delta, "Late text");          assert.equal(String(deltaEvent.turnId), String(turn.turnId));        }      }).pipe(        Effect.provideService(Random.Random, makeDeterministicRandomService()),        Effect.provide(harness.layer),      );    },  );   it.effect("falls back to assistant payload text when stream deltas are absent", () => {    const harness = makeHarness();    return Effect.gen(function* () {      const adapter = yield* ClaudeCodeAdapter;       const runtimeEventsFiber = yield* Stream.take(adapter.streamEvents, 9).pipe(        Stream.runCollect,        Effect.forkChild,      );       const session = yield* adapter.startSession({        threadId: THREAD_ID,        provider: "claudeCode",        runtimeMode: "full-access",      });       const turn = yield* adapter.sendTurn({        threadId: session.threadId,        input: "hello",        attachments: [],      });       harness.query.emit({        type: "assistant",        session_id: "sdk-session-fallback-text",        uuid: "assistant-fallback",        parent_tool_use_id: null,        message: {          id: "assistant-message-fallback",          content: [{ type: "text", text: "Fallback hello" }],        },      } as unknown as SDKMessage);       harness.query.emit({        type: "result",

Lines 1–500 of 947.