7586 lines · 278.2 KB · TypeScript
RawDownload
import * as NodeAssert from "node:assert/strict";import * as NodeServices from "@effect/platform-node/NodeServices";import { it } from "@effect/vitest";import * as Cause from "effect/Cause";import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Fiber from "effect/Fiber";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Stream from "effect/Stream";import * as TestClock from "effect/testing/TestClock";import { beforeEach, vi } from "vite-plus/test";import type {  Event as OpenCodeEvent,  PermissionRequest,  QuestionRequest,  ToolPart,} from "@opencode-ai/sdk/v2"; import {  ApprovalRequestId,  OpenCodeSettings,  ProviderDriverKind,  ProviderInstanceId,  ThreadId,} from "@t3tools/contracts";import { createModelSelection } from "@t3tools/shared/model";import { ServerConfig } from "../../config.ts";import { ServerSettingsService } from "../../serverSettings.ts";import { buildRuntimeInstructions } from "../RuntimeInstructions.ts";import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts";import type { OpenCodeAdapterShape } from "../Services/OpenCodeAdapter.ts";import {  OpenCodeRuntime,  OpenCodeRuntimeError,  type OpenCodeRuntimeShape,} from "../opencodeRuntime.ts";import {  isOpenCodeNotFound,  isSameOpenCodeDirectory,  makeOpenCodeAdapter,  mergeOpenCodeAssistantText,} from "./OpenCodeAdapter.ts";import { symlinksSupported } from "@t3tools/shared/testing/symlinks"; // Test-local service tag so the rest of the file can keep using `yield* OpenCodeAdapter`.class OpenCodeAdapter extends Context.Service<OpenCodeAdapter, OpenCodeAdapterShape>()(  "t3/provider/Layers/OpenCodeAdapter.test/OpenCodeAdapter",) {} const asThreadId = (value: string): ThreadId => ThreadId.make(value); type MessageEntry = {  info: {    id: string;    role: "user" | "assistant";  };  parts: Array<unknown>;}; const runtimeMock = {  state: {    startCalls: [] as string[],    sessionCreateUrls: [] as string[],    sessionCreateInputs: [] as Array<Record<string, unknown>>,    createdSessionIds: [] as string[],    authHeaders: [] as Array<string | null>,    abortCalls: [] as string[],    abortSignals: [] as AbortSignal[],    abortImplementation: null as      | ((sessionID: string, signal?: AbortSignal) => Promise<void>)      | null,    sessionChildrenCalls: [] as string[],    sessionChildrenById: new Map<string, Array<{ id: string }>>(),    sessionChildrenImplementation: null as      | ((sessionID: string) => Promise<Array<{ id: string }>>)      | null,    closeCalls: [] as string[],    revertMessageID: undefined as string | undefined,    revertCalls: [] as Array<{ sessionID: string; messageID?: string }>,    messageCalls: [] as Array<{ sessionID: string; messageID: string }>,    messageFailures: 0,    promptCalls: [] as Array<unknown>,    summarizeCalls: [] as Array<unknown>,    promptAsyncError: null as Error | null,    promptAsyncImplementation: null as (() => Promise<void>) | null,    autoPromptEcho: true,    autoConnect: true,    endEventStream: false,    promptEchoEvents: [] as Array<unknown>,    closeError: null as Error | null,    messages: [] as MessageEntry[],    subscribedEvents: [] as Array<unknown | Promise<unknown>>,    eventSubscribeObserved: null as (() => void) | null,    eventStreamError: null as ((cause: unknown) => void) | null,    permissionReplyCalls: [] as Array<{ requestID: string; reply: string }>,    permissionReplyImplementation: null as ((signal?: AbortSignal) => Promise<void>) | null,    permissionReplySignals: [] as AbortSignal[],    questionReplyCalls: [] as Array<{      requestID: string;      answers: ReadonlyArray<ReadonlyArray<string>>;    }>,    questionReplyImplementation: null as ((signal?: AbortSignal) => Promise<void>) | null,    sessionStatus: "idle" as "idle" | "busy",    sessionStatusFailures: 0,    sessionStatusCalls: 0,    sessionStatusImplementation: null as (() => Promise<unknown>) | null,    sessionGetIds: [] as string[],    sessionGetObserved: null as ((sessionID: string) => void) | null,    sessionGetImplementation: null as      | ((sessionID: string, signal?: AbortSignal) => Promise<void>)      | null,    missingSessionIds: new Set<string>(),    transientErrorSessionIds: new Set<string>(),    sessionDirectoryById: new Map<string, string>(),    sessionParentById: new Map<string, string>(),    pendingPermissions: [] as Array<PermissionRequest>,    pendingQuestions: [] as Array<QuestionRequest>,    permissionListCalls: 0,    questionListCalls: 0,    permissionListImplementation: null as (() => Promise<Array<PermissionRequest>>) | null,    questionListImplementation: null as (() => Promise<Array<QuestionRequest>>) | null,    sessionUpdateCalls: [] as Array<{ sessionID: string; permission: unknown }>,    forkCalls: [] as Array<{ sessionID: string; directory?: string }>,  },  reset() {    this.state.startCalls.length = 0;    this.state.sessionCreateUrls.length = 0;    this.state.sessionCreateInputs.length = 0;    this.state.createdSessionIds.length = 0;    this.state.authHeaders.length = 0;    this.state.abortCalls.length = 0;    this.state.abortSignals.length = 0;    this.state.abortImplementation = null;    this.state.sessionChildrenCalls.length = 0;    this.state.sessionChildrenById.clear();    this.state.sessionChildrenImplementation = null;    this.state.closeCalls.length = 0;    this.state.revertMessageID = undefined;    this.state.revertCalls.length = 0;    this.state.messageCalls.length = 0;    this.state.messageFailures = 0;    this.state.promptCalls.length = 0;    this.state.summarizeCalls.length = 0;    this.state.promptAsyncError = null;    this.state.promptAsyncImplementation = null;    this.state.autoPromptEcho = true;    this.state.autoConnect = true;    this.state.endEventStream = false;    this.state.promptEchoEvents.length = 0;    this.state.closeError = null;    this.state.messages = [];    this.state.subscribedEvents = [];    this.state.eventSubscribeObserved = null;    this.state.eventStreamError = null;    this.state.permissionReplyCalls.length = 0;    this.state.permissionReplyImplementation = null;    this.state.permissionReplySignals.length = 0;    this.state.questionReplyCalls.length = 0;    this.state.questionReplyImplementation = null;    this.state.sessionStatus = "idle";    this.state.sessionStatusFailures = 0;    this.state.sessionStatusCalls = 0;    this.state.sessionStatusImplementation = null;    this.state.sessionGetIds.length = 0;    this.state.sessionGetObserved = null;    this.state.sessionGetImplementation = null;    this.state.missingSessionIds.clear();    this.state.transientErrorSessionIds.clear();    this.state.sessionDirectoryById.clear();    this.state.sessionParentById.clear();    this.state.pendingPermissions = [];    this.state.pendingQuestions = [];    this.state.permissionListCalls = 0;    this.state.questionListCalls = 0;    this.state.permissionListImplementation = null;    this.state.questionListImplementation = null;    this.state.sessionUpdateCalls.length = 0;    this.state.forkCalls.length = 0;  },}; const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {  startOpenCodeServerProcess: ({ binaryPath, serverPassword }) =>    Effect.gen(function* () {      runtimeMock.state.startCalls.push(binaryPath);      const url = "http://127.0.0.1:4301";      yield* Effect.addFinalizer(() =>        Effect.sync(() => {          runtimeMock.state.closeCalls.push(url);          if (runtimeMock.state.closeError) {            throw runtimeMock.state.closeError;          }        }),      );      return {        url,        version: "1.15.13",        ...(serverPassword ? { serverPassword } : {}),        exitCode: Effect.never,        isRunning: Effect.succeed(true),      };    }),  connectToOpenCodeServer: ({ serverUrl, serverPassword }) =>    Effect.gen(function* () {      const url = serverUrl ?? "http://127.0.0.1:4301";      // Always register a finalizer so the closeCalls/closeError probes fire;      // production attaches none for external servers.      yield* Effect.addFinalizer(() =>        Effect.sync(() => {          runtimeMock.state.closeCalls.push(url);          if (runtimeMock.state.closeError) {            throw runtimeMock.state.closeError;          }        }),      );      return {        url,        version: "1.15.13",        ...(serverPassword ? { serverPassword } : {}),        exitCode: null,        external: Boolean(serverUrl),      };    }),  runOpenCodeCommand: () => Effect.succeed({ stdout: "", stderr: "", code: 0 }),  createOpenCodeSdkClient: ({ baseUrl, serverPassword }) =>    ({      session: {        create: async (input: Record<string, unknown>) => {          runtimeMock.state.sessionCreateUrls.push(baseUrl);          runtimeMock.state.sessionCreateInputs.push(input);          runtimeMock.state.authHeaders.push(            serverPassword ? `Basic ${btoa(`opencode:${serverPassword}`)}` : null,          );          return {            data: { id: runtimeMock.state.createdSessionIds.shift() ?? `${baseUrl}/session` },          };        },        get: async ({ sessionID }: { sessionID: string }, options?: { signal?: AbortSignal }) => {          runtimeMock.state.sessionGetIds.push(sessionID);          runtimeMock.state.sessionGetObserved?.(sessionID);          if (runtimeMock.state.sessionGetImplementation) {            await runtimeMock.state.sessionGetImplementation(sessionID, options?.signal);          }          // The real client is `throwOnError: true`: non-2xx rejects rather          // than resolving, so missing → 404 throw, transient → 500 throw.          if (runtimeMock.state.transientErrorSessionIds.has(sessionID)) {            throw new Error("opencode server error", { cause: { status: 500 } });          }          if (runtimeMock.state.missingSessionIds.has(sessionID)) {            throw new Error(`Session not found: ${sessionID}`, {              cause: { status: 404, body: { name: "NotFoundError" } },            });          }          const directory = runtimeMock.state.sessionDirectoryById.get(sessionID);          const parentID = runtimeMock.state.sessionParentById.get(sessionID);          return {            data: {              id: sessionID,              ...(runtimeMock.state.revertMessageID                ? { revert: { messageID: runtimeMock.state.revertMessageID } }                : {}),              ...(directory ? { directory } : {}),              ...(parentID ? { parentID } : {}),            },          };        },        update: async ({ sessionID, permission }: { sessionID: string; permission: unknown }) => {          runtimeMock.state.sessionUpdateCalls.push({ sessionID, permission });          return { data: { id: sessionID } };        },        fork: async ({ sessionID, directory }: { sessionID: string; directory?: string }) => {          // Fork clones history into a new session bound to the directory.          const forkedId = `${sessionID}_fork`;          runtimeMock.state.forkCalls.push({ sessionID, ...(directory ? { directory } : {}) });          if (directory) {            runtimeMock.state.sessionDirectoryById.set(forkedId, directory);          }          return { data: { id: forkedId, ...(directory ? { directory } : {}) } };        },        abort: async ({ sessionID }: { sessionID: string }, options?: { signal?: AbortSignal }) => {          runtimeMock.state.abortCalls.push(sessionID);          if (options?.signal) {            runtimeMock.state.abortSignals.push(options.signal);          }          await runtimeMock.state.abortImplementation?.(sessionID, options?.signal);          runtimeMock.state.pendingPermissions = runtimeMock.state.pendingPermissions.filter(            (request) => request.sessionID !== sessionID,          );          runtimeMock.state.pendingQuestions = runtimeMock.state.pendingQuestions.filter(            (request) => request.sessionID !== sessionID,          );        },        children: async ({ sessionID }: { sessionID: string }) => {          runtimeMock.state.sessionChildrenCalls.push(sessionID);          return {            data: runtimeMock.state.sessionChildrenImplementation              ? await runtimeMock.state.sessionChildrenImplementation(sessionID)              : (runtimeMock.state.sessionChildrenById.get(sessionID) ?? []),          };        },        status: async () => {          runtimeMock.state.sessionStatusCalls += 1;          if (runtimeMock.state.sessionStatusImplementation) {            return await runtimeMock.state.sessionStatusImplementation();          }          if (runtimeMock.state.sessionStatusFailures > 0) {            runtimeMock.state.sessionStatusFailures -= 1;            throw new Error("status failed");          }          return {            data:              runtimeMock.state.sessionStatus === "idle"                ? {}                : { "http://127.0.0.1:9999/session": { type: "busy" as const } },          };        },        promptAsync: async (input: unknown) => {          runtimeMock.state.promptCalls.push(input);          await runtimeMock.state.promptAsyncImplementation?.();          if (runtimeMock.state.promptAsyncError) {            throw runtimeMock.state.promptAsyncError;          }          if (            runtimeMock.state.autoPromptEcho &&            typeof input === "object" &&            input !== null &&            "sessionID" in input &&            "messageID" in input &&            typeof input.sessionID === "string" &&            typeof input.messageID === "string"          ) {            runtimeMock.state.messages.push({              info: { id: input.messageID, role: "user" },              parts: [],            });            runtimeMock.state.promptEchoEvents.push({              id: `evt-auto-user-${input.messageID}`,              type: "message.updated",              properties: {                sessionID: input.sessionID,                info: { id: input.messageID, role: "user" },              },            });          }        },        summarize: async (input: unknown) => {          runtimeMock.state.summarizeCalls.push(input);          return { data: true };        },        messages: async () => ({ data: runtimeMock.state.messages }),        message: async ({ sessionID, messageID }: { sessionID: string; messageID: string }) => {          runtimeMock.state.messageCalls.push({ sessionID, messageID });          if (runtimeMock.state.messageFailures > 0) {            runtimeMock.state.messageFailures -= 1;            throw new Error("message lookup failed", { cause: { status: 500 } });          }          const message = runtimeMock.state.messages.find((entry) => entry.info.id === messageID);          if (!message) {            throw new Error(`Message not found: ${messageID}`, {              cause: { status: 404, body: { name: "NotFoundError" } },            });          }          return { data: message };        },        revert: async ({ sessionID, messageID }: { sessionID: string; messageID?: string }) => {          runtimeMock.state.revertCalls.push({            sessionID,            ...(messageID ? { messageID } : {}),          });          if (!messageID) {            throw new Error("Expected messageID");          }          let lastUserID: string | undefined;          for (const entry of runtimeMock.state.messages) {            if (entry.info.role === "user") lastUserID = entry.info.id;            if (entry.info.id === messageID && entry.parts.length > 0) {              runtimeMock.state.revertMessageID = lastUserID ?? messageID;              break;            }          }        },      },      event: {        subscribe: async (          _input: unknown,          options?: { signal?: AbortSignal; onSseError?: (cause: unknown) => void },        ) => {          runtimeMock.state.eventSubscribeObserved?.();          runtimeMock.state.eventStreamError = options?.onSseError ?? null;          return {            stream: (async function* () {              const aborted = promiseWithResolvers<void>();              const onAbort = () => aborted.resolve(undefined);              options?.signal?.addEventListener("abort", onAbort, { once: true });              try {                if (runtimeMock.state.autoConnect) {                  yield { id: "evt-auto-connected", type: "server.connected", properties: {} };                }                for (const event of runtimeMock.state.subscribedEvents) {                  if (options?.signal?.aborted) return;                  const resolved = await Promise.race([event, aborted.promise]);                  if (options?.signal?.aborted) return;                  while (runtimeMock.state.promptEchoEvents.length > 0) {                    yield runtimeMock.state.promptEchoEvents.shift();                  }                  const nativeEvent = resolved as OpenCodeEvent;                  if (nativeEvent.type === "permission.asked") {                    runtimeMock.state.pendingPermissions =                      runtimeMock.state.pendingPermissions.filter(                        (request) => request.id !== nativeEvent.properties.id,                      );                    runtimeMock.state.pendingPermissions.push(nativeEvent.properties);                  } else if (nativeEvent.type === "permission.replied") {                    runtimeMock.state.pendingPermissions =                      runtimeMock.state.pendingPermissions.filter(                        (request) => request.id !== nativeEvent.properties.requestID,                      );                  } else if (nativeEvent.type === "question.asked") {                    runtimeMock.state.pendingQuestions = runtimeMock.state.pendingQuestions.filter(                      (request) => request.id !== nativeEvent.properties.id,                    );                    runtimeMock.state.pendingQuestions.push(nativeEvent.properties);                  } else if (                    nativeEvent.type === "question.replied" ||                    nativeEvent.type === "question.rejected"                  ) {                    runtimeMock.state.pendingQuestions = runtimeMock.state.pendingQuestions.filter(                      (request) => request.id !== nativeEvent.properties.requestID,                    );                  }                  yield resolved;                }                if (!runtimeMock.state.endEventStream && !options?.signal?.aborted) {                  await aborted.promise;                }              } finally {                options?.signal?.removeEventListener("abort", onAbort);              }            })(),          };        },      },      permission: {        list: async () => {          runtimeMock.state.permissionListCalls += 1;          return {            data: runtimeMock.state.permissionListImplementation              ? await runtimeMock.state.permissionListImplementation()              : runtimeMock.state.pendingPermissions,          };        },        reply: async (          { requestID, reply }: { requestID: string; reply: string },          options?: { signal?: AbortSignal },        ) => {          runtimeMock.state.permissionReplyCalls.push({ requestID, reply });          if (options?.signal) runtimeMock.state.permissionReplySignals.push(options.signal);          if (runtimeMock.state.permissionReplyImplementation) {            await runtimeMock.state.permissionReplyImplementation(options?.signal);          }          runtimeMock.state.pendingPermissions = runtimeMock.state.pendingPermissions.filter(            (request) => request.id !== requestID,          );        },      },      question: {        list: async () => {          runtimeMock.state.questionListCalls += 1;          return {            data: runtimeMock.state.questionListImplementation              ? await runtimeMock.state.questionListImplementation()              : runtimeMock.state.pendingQuestions,          };        },        reply: async (          {            requestID,            answers,          }: {            requestID: string;            answers: ReadonlyArray<ReadonlyArray<string>>;          },          options?: { signal?: AbortSignal },        ) => {          runtimeMock.state.questionReplyCalls.push({ requestID, answers });          await runtimeMock.state.questionReplyImplementation?.(options?.signal);          runtimeMock.state.pendingQuestions = runtimeMock.state.pendingQuestions.filter(            (request) => request.id !== requestID,          );        },      },    }) as unknown as ReturnType<OpenCodeRuntimeShape["createOpenCodeSdkClient"]>,

Lines 1–500 of 7586.