461 lines · 15.4 KB · TypeScript
RawDownload
import type { Event as OpenCodeEvent, OpencodeClient } from "@opencode-ai/sdk/v2";import * as NodeServices from "@effect/platform-node/NodeServices";import { ProviderReplayEntry, type ProviderReplayTranscript } from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as Duration from "effect/Duration";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema"; import { ServerConfig } from "../../config.ts";import {  OpenCodeRuntime,  OpenCodeRuntimeError,  type OpenCodeRuntimeShape,} from "../../provider/opencodeRuntime.ts";import {  NoOpProviderEventLoggers,  ProviderEventLoggers,} from "../../provider/Layers/ProviderEventLoggers.ts";import { layer as idAllocatorLayer } from "../IdAllocator.ts";import { ProviderAdapterDriverCreateError } from "../ProviderAdapterDriver.ts";import { makeDriverLayer as makeProviderAdapterRegistryDriverLayer } from "../ProviderAdapterRegistry.ts";import {  makeReplayServerConfig,  type OrchestratorV2ProviderReplayHarness,} from "../testkit/ProviderReplayHarness.ts";import {  OPENCODE_DEFAULT_INSTANCE_ID,  OPENCODE_DRIVER_KIND,  OPENCODE_PROVIDER,  OPENCODE_SDK_PROTOCOL,  OpenCodeAdapterV2Driver,} from "./OpenCodeAdapterV2.ts"; export const OPENCODE_SDK_REPLAY_PROTOCOL = OPENCODE_SDK_PROTOCOL; const OpenCodeSdkReplayTranscript = Schema.Struct({  provider: Schema.Literal(OPENCODE_PROVIDER),  protocol: Schema.Literal(OPENCODE_SDK_REPLAY_PROTOCOL),  version: Schema.String,  scenario: Schema.String,  metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),  entries: Schema.Array(ProviderReplayEntry),});export type OpenCodeSdkReplayTranscript = typeof OpenCodeSdkReplayTranscript.Type;const decodeOpenCodeSdkReplayTranscript = Schema.decodeUnknownEffect(OpenCodeSdkReplayTranscript); export class OpenCodeReplayTranscriptDecodeError extends Schema.TaggedErrorClass<OpenCodeReplayTranscriptDecodeError>()(  "OpenCodeReplayTranscriptDecodeError",  {    driver: Schema.optional(Schema.String),    protocol: Schema.optional(Schema.String),    scenario: Schema.optional(Schema.String),    cause: Schema.Defect(),  },) {  override get message(): string {    return `Failed to decode OpenCode replay transcript for scenario ${this.scenario ?? "<unknown>"}.`;  }} export class OpenCodeReplayMismatchError extends Schema.TaggedErrorClass<OpenCodeReplayMismatchError>()(  "OpenCodeReplayMismatchError",  {    scenario: Schema.String,    cursor: Schema.Number,    expected: Schema.Unknown,    actual: Schema.Unknown,  },) {  override get message(): string {    return `OpenCode replay frame mismatch at cursor ${this.cursor} in scenario ${this.scenario}.`;  }} export class OpenCodeReplayIncompleteError extends Schema.TaggedErrorClass<OpenCodeReplayIncompleteError>()(  "OpenCodeReplayIncompleteError",  {    scenario: Schema.String,    cursor: Schema.Number,    remaining: Schema.Number,  },) {  override get message(): string {    return `OpenCode replay ended with ${this.remaining} unconsumed entries in scenario ${this.scenario}.`;  }} export const OpenCodeReplayError = Schema.Union([  OpenCodeReplayTranscriptDecodeError,  OpenCodeReplayMismatchError,  OpenCodeReplayIncompleteError,]);export type OpenCodeReplayError = typeof OpenCodeReplayError.Type;export const OpenCodeOrchestratorReplayHarnessError = Schema.Union([  OpenCodeReplayError,  ProviderAdapterDriverCreateError,]);export type OpenCodeOrchestratorReplayHarnessError =  typeof OpenCodeOrchestratorReplayHarnessError.Type; function replayValueMatches(expected: unknown, actual: unknown): boolean {  if (expected === "<any>" || expected === "<workspace>") return true;  if (Array.isArray(expected)) {    return (      Array.isArray(actual) &&      expected.length === actual.length &&      expected.every((entry, index) => replayValueMatches(entry, actual[index]))    );  }  if (typeof expected === "object" && expected !== null) {    if (typeof actual !== "object" || actual === null) return false;    return Object.entries(expected).every(([key, value]) =>      replayValueMatches(value, (actual as Record<string, unknown>)[key]),    );  }  return Object.is(expected, actual);} function frameRecord(frame: unknown): Record<string, unknown> | null {  return typeof frame === "object" && frame !== null ? (frame as Record<string, unknown>) : null;} function materializeMessageIds(value: unknown, messageIds: ReadonlyMap<string, string>): unknown {  if (Array.isArray(value)) return value.map((entry) => materializeMessageIds(entry, messageIds));  const record = frameRecord(value);  if (record === null) return value;  return Object.fromEntries(    Object.entries(record).map(([key, entry]) => [      key,      typeof entry === "string" && (key === "id" || key === "messageID" || key === "parentID")        ? (messageIds.get(entry) ?? entry)        : materializeMessageIds(entry, messageIds),    ]),  );} export class OpenCodeReplayController {  private cursor = 0;  private readonly waiters = new Set<() => void>();  private failure: unknown = null;  private readonly transcript: OpenCodeSdkReplayTranscript;  private messageIds = new Map<string, string>();   constructor(transcript: OpenCodeSdkReplayTranscript) {    this.transcript = transcript;  }   async expectOutbound(actual: unknown): Promise<void> {    try {      const entry = this.transcript.entries[this.cursor];      const expectedFrame = entry?.type === "expect_outbound" ? frameRecord(entry.frame) : null;      const actualFrame = frameRecord(actual);      const messageIds = new Map(this.messageIds);      let conflictingMessageId = false;      if (        expectedFrame?.type === "session.promptAsync" &&        actualFrame?.type === expectedFrame.type      ) {        const recordedId = frameRecord(expectedFrame.input)?.messageID;        const actualId = frameRecord(actualFrame.input)?.messageID;        if (          typeof recordedId === "string" &&          typeof actualId === "string" &&          recordedId !== "<any>" &&          recordedId !== "<workspace>" &&          !messageIds.has(recordedId)        ) {          conflictingMessageId = [...messageIds.values()].includes(actualId);          if (!conflictingMessageId) messageIds.set(recordedId, actualId);        }      }      const expected =        entry?.type === "expect_outbound"          ? materializeMessageIds(entry.frame, messageIds)          : (entry ?? null);      if (        conflictingMessageId ||        entry?.type !== "expect_outbound" ||        !replayValueMatches(expected, actual)      ) {        throw new OpenCodeReplayMismatchError({          scenario: this.transcript.scenario,          cursor: this.cursor,          expected,          actual,        });      }      this.messageIds = messageIds;      this.advance();    } catch (cause) {      this.fail(cause);      throw cause;    }  }   async response(operation: string): Promise<unknown> {    while (true) {      this.throwFailure();      const entry = this.transcript.entries[this.cursor];      if (entry?.type === "emit_inbound") {        const frame = frameRecord(entry.frame);        if (frame?.type === "sdk.response" && frame.operation === operation) {          if (entry.afterMs !== undefined && entry.afterMs > 0) {            await Effect.runPromise(Effect.sleep(Duration.millis(entry.afterMs)));          }          const data = materializeMessageIds(frame.data, this.messageIds);          this.advance();          return data;        }      }      if (entry?.type === "runtime_exit") {        const mismatch = new OpenCodeReplayMismatchError({          scenario: this.transcript.scenario,          cursor: this.cursor,          expected: { type: "sdk.response", operation },          actual: entry,        });        this.fail(mismatch);        throw mismatch;      }      await this.changed();    }  }   async *events(signal?: AbortSignal): AsyncIterable<OpenCodeEvent> {    while (true) {      if (signal?.aborted === true) return;      this.throwFailure();      const entry = this.transcript.entries[this.cursor];      if (entry?.type === "emit_inbound") {        const frame = frameRecord(entry.frame);        if (frame?.type === "sdk.event") {          if (entry.afterMs !== undefined && entry.afterMs > 0) {            await Effect.runPromise(Effect.sleep(Duration.millis(entry.afterMs)));          }          const event = materializeMessageIds(frame.event, this.messageIds) as OpenCodeEvent;          this.advance();          yield event;          continue;        }      }      if (entry?.type === "runtime_exit") {        this.advance();        if (entry.status === "success") return;        const mismatch = new OpenCodeReplayMismatchError({          scenario: this.transcript.scenario,          cursor: this.cursor - 1,          expected: { status: "success" },          actual: entry,        });        this.fail(mismatch);        throw mismatch;      }      await this.changed(signal);    }  }   assertComplete(): void {    while (this.transcript.entries[this.cursor]?.type === "runtime_exit") {      const exit = this.transcript.entries[this.cursor];      if (exit?.type !== "runtime_exit" || exit.status !== "success") break;      this.cursor += 1;    }    this.throwFailure();    if (this.cursor !== this.transcript.entries.length) {      throw new OpenCodeReplayIncompleteError({        scenario: this.transcript.scenario,        cursor: this.cursor,        remaining: this.transcript.entries.length - this.cursor,      });    }  }   private advance(): void {    this.cursor += 1;    for (const waiter of this.waiters) waiter();    this.waiters.clear();  }   private fail(cause: unknown): void {    this.failure = cause;    for (const waiter of this.waiters) waiter();    this.waiters.clear();  }   private throwFailure(): void {    if (this.failure !== null) throw this.failure;  }   private changed(signal?: AbortSignal): Promise<void> {    return new Promise((resolve) => {      let settled = false;      const done = () => {        if (settled) return;        settled = true;        signal?.removeEventListener("abort", done);        this.waiters.delete(done);        resolve();      };      this.waiters.add(done);      signal?.addEventListener("abort", done, { once: true });      if (signal?.aborted === true) done();    });  }} function makeReplayClient(controller: OpenCodeReplayController): OpencodeClient {  const request = async (operation: string, input: unknown) => {    await controller.expectOutbound({ type: operation, input });    return { data: await controller.response(operation) };  };  return {    event: {      subscribe: async (_input?: unknown, options?: { readonly signal?: AbortSignal }) => {        await controller.expectOutbound({ type: "event.subscribe" });        return { stream: controller.events(options?.signal) };      },    },    session: {      create: (input: unknown) => request("session.create", input),      get: (input: unknown) => request("session.get", input),      update: (input: unknown) => request("session.update", input),      messages: (input: unknown) => request("session.messages", input),      promptAsync: (input: unknown) => request("session.promptAsync", input),      abort: (input: unknown) => request("session.abort", input),      revert: (input: unknown) => request("session.revert", input),      unrevert: (input: unknown) => request("session.unrevert", input),      fork: (input: unknown) => request("session.fork", input),    },    permission: {      reply: (input: unknown) => request("permission.reply", input),    },    question: {      reply: (input: unknown) => request("question.reply", input),    },    mcp: {      add: (input: unknown) => request("mcp.add", input),    },  } as unknown as OpencodeClient;} function makeOpenCodeReplayRuntimeLayer(transcript: OpenCodeSdkReplayTranscript) {  return Layer.effect(    OpenCodeRuntime,    Effect.gen(function* () {      const controller = new OpenCodeReplayController(transcript);      yield* Effect.addFinalizer(() =>        Effect.sync(() => {          controller.assertComplete();        }),      );      const client = makeReplayClient(controller);      return OpenCodeRuntime.of({        startOpenCodeServerProcess: () =>          Effect.fail(            new OpenCodeRuntimeError({              operation: "startOpenCodeServerProcess",              detail: "OpenCode replay uses an external in-memory SDK boundary.",            }),          ),        connectToOpenCodeServer: () =>          Effect.succeed({            url: "replay://opencode",            version: "0.0.0-replay",            exitCode: null,            external: true,          }),        runOpenCodeCommand: () =>          Effect.fail(            new OpenCodeRuntimeError({              operation: "runOpenCodeCommand",              detail: "OpenCode replay does not execute commands.",            }),          ),        createOpenCodeSdkClient: () => client,        loadOpenCodeInventory: () =>          Effect.fail(            new OpenCodeRuntimeError({              operation: "loadOpenCodeInventory",              detail: "OpenCode replay does not load inventory.",            }),          ),        loadInventoryFromCli: () =>          Effect.fail(            new OpenCodeRuntimeError({              operation: "loadInventoryFromCli",              detail: "OpenCode replay does not load inventory.",            }),          ),        loadOpenCodeSkills: () =>          Effect.fail(            new OpenCodeRuntimeError({              operation: "loadOpenCodeSkills",              detail: "OpenCode replay does not load skills.",            }),          ),        loadSkillsFromCli: () =>          Effect.fail(            new OpenCodeRuntimeError({              operation: "loadSkillsFromCli",              detail: "OpenCode replay does not load skills.",            }),          ),      } satisfies OpenCodeRuntimeShape);    }),  );} export function makeOpenCodeProviderAdapterRegistryReplayLayer(  transcript: OpenCodeSdkReplayTranscript,) {  const serverConfigLayer = Layer.effect(    ServerConfig,    makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie),  ).pipe(Layer.provide(NodeServices.layer));  return makeProviderAdapterRegistryDriverLayer({    drivers: [OpenCodeAdapterV2Driver],    configMap: {      [OPENCODE_DEFAULT_INSTANCE_ID]: {        driver: OPENCODE_DRIVER_KIND,        config: { serverUrl: "replay://opencode" },      },    },  }).pipe(    Layer.provide(      Layer.mergeAll(        makeOpenCodeReplayRuntimeLayer(transcript),        serverConfigLayer,        NodeServices.layer,        idAllocatorLayer,        Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers),      ),    ),  );} function transcriptMetadata(transcript: ProviderReplayTranscript) {  return {    provider: transcript.provider,    protocol: transcript.protocol,    scenario: transcript.scenario,  };} export const OpenCodeOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness<  OpenCodeSdkReplayTranscript,  OpenCodeOrchestratorReplayHarnessError> = {  driver: OPENCODE_PROVIDER,  decodeTranscript: (transcript) =>    decodeOpenCodeSdkReplayTranscript(transcript).pipe(      Effect.mapError(        (cause) =>          new OpenCodeReplayTranscriptDecodeError({            ...transcriptMetadata(transcript),            cause,          }),      ),    ),  makeProviderAdapterRegistryLayer: makeOpenCodeProviderAdapterRegistryReplayLayer,};