4968 lines · 185.1 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:offimport * as NodeFS from "node:fs";import * as NodeOS from "node:os";import * as NodePath from "node:path"; import type {  ProviderApprovalDecision,  ProviderRuntimeEvent,  ProviderSendTurnInput,  ProviderSession,  ProviderTurnStartResult,  ProviderUploadFeedbackInput,  ProviderUploadFeedbackResult,} from "@t3tools/contracts";import {  ASSISTANT_CITATION_MAX_TEXT_LENGTH,  AssistantCitation,  ApprovalRequestId,  EnvironmentId,  EventId,  MessageId,  OrchestrationThreadShell,  ProjectId,  PROVIDER_SEND_TURN_MAX_INPUT_CHARS,  ProviderDriverKind,  ProviderInstanceId,  ProviderSessionStartInput,  ThreadId,  TurnId,} from "@t3tools/contracts";import {  expandAssistantCitationsForProvider,  serializeAssistantCitation,} from "@t3tools/shared/assistantCitations";import { createModelSelection } from "@t3tools/shared/model";import { it, assert, describe, vi } from "@effect/vitest";import { afterAll } from "vite-plus/test"; import * as Cause from "effect/Cause";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 Layer from "effect/Layer";import * as Metric from "effect/Metric";import * as Option from "effect/Option";import * as PubSub from "effect/PubSub";import * as Ref from "effect/Ref";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 * as SqlClient from "effect/unstable/sql/SqlClient"; import {  ProviderAdapterRequestError,  ProviderAdapterSessionNotFoundError,  ProviderUnsupportedError,  ProviderValidationError,  ProviderWorkspaceMissingError,  type ProviderAdapterError,} from "../Errors.ts";import type { ProviderAdapterShape } from "../Services/ProviderAdapter.ts";import * as ProviderAdapterRegistry from "../Services/ProviderAdapterRegistry.ts";import * as ProviderService from "../Services/ProviderService.ts";import * as ProviderSessionDirectory from "../Services/ProviderSessionDirectory.ts";import { makeProviderServiceLive } from "./ProviderService.ts";import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";import { ProviderSessionDirectoryLive } from "./ProviderSessionDirectory.ts";import * as NodeServices from "@effect/platform-node/NodeServices";import * as ProviderSessionRuntime from "../../persistence/ProviderSessionRuntime.ts";import {  makeSqlitePersistenceLive,  SqlitePersistenceMemory,} from "../../persistence/Layers/Sqlite.ts";import * as ServerConfig from "../../config.ts";import * as ServerSettings from "../../serverSettings.ts";import * as AnalyticsService from "../../telemetry/AnalyticsService.ts";import { makeAdapterRegistryMock } from "../testUtils/providerAdapterRegistryMock.ts";import * as ProjectionSnapshotQuery from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));const defaultServerSettingsLayer = ServerSettings.ServerSettingsService.layerTest();const serverConfigTestLayer = ServerConfig.layerTest(process.cwd(), process.cwd()).pipe(  Layer.provide(NodeServices.layer),); // startSession verifies the workspace folder exists before dispatching to an// adapter, so session cwd fixtures must be real directories.const fixtureCwdRoot = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "provider-service-test-"));afterAll(() => NodeFS.rmSync(fixtureCwdRoot, { recursive: true, force: true }));function fixtureCwd(name: string): string {  const dir = NodePath.join(fixtureCwdRoot, name);  NodeFS.mkdirSync(dir, { recursive: true });  return dir;} const asRequestId = (value: string): ApprovalRequestId => ApprovalRequestId.make(value);const asEventId = (value: string): EventId => EventId.make(value);const asThreadId = (value: string): ThreadId => ThreadId.make(value);const asTurnId = (value: string): TurnId => TurnId.make(value);const codexInstanceId = ProviderInstanceId.make("codex");const claudeAgentInstanceId = ProviderInstanceId.make("claudeAgent");const CODEX_DRIVER = ProviderDriverKind.make("codex");const CLAUDE_AGENT_DRIVER = ProviderDriverKind.make("claudeAgent");const CURSOR_DRIVER = ProviderDriverKind.make("cursor"); const assistantQuoteText = 'Keep the shared parser for "résumé".\nPreserve line breaks.';const assistantCitation = {  version: 1,  environmentId: EnvironmentId.make("source-environment/remote"),  threadId: asThreadId("source-thread/earlier"),  messageId: MessageId.make("source-message/first"),  text: assistantQuoteText,  start: 17,  end: 17 + assistantQuoteText.length,  prefix: "Previous advice. ",  suffix: " Next steps.",} satisfies AssistantCitation;const decodeAssistantQuoteContext = Schema.decodeUnknownEffect(  Schema.fromJsonString(    Schema.Array(Schema.Struct({ id: Schema.String, citation: AssistantCitation })),  ),); type LegacyProviderRuntimeEvent = {  readonly type: string;  readonly eventId: EventId;  readonly provider: ProviderDriverKind;  readonly createdAt: string;  readonly threadId: ThreadId;  readonly turnId?: string | undefined;  readonly itemId?: string | undefined;  readonly requestId?: string | undefined;  readonly payload?: unknown | undefined;  readonly [key: string]: unknown;}; function makeFakeCodexAdapter(  provider: ProviderDriverKind = CODEX_DRIVER,  supportsConversationRollback?: boolean,) {  const sessions = new Map<ThreadId, ProviderSession>();  const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>());   const startSession = vi.fn((input: ProviderSessionStartInput) =>    Effect.sync(() => {      const now = "2026-01-01T00:00:00.000Z";      const session: ProviderSession = {        provider,        ...(input.providerInstanceId !== undefined          ? { providerInstanceId: input.providerInstanceId }          : {}),        status: "ready",        runtimeMode: input.runtimeMode,        threadId: input.threadId,        resumeCursor: input.resumeCursor ?? {          opaque: `resume-${String(input.threadId)}`,        },        cwd: input.cwd ?? process.cwd(),        createdAt: now,        updatedAt: now,      };      sessions.set(session.threadId, session);      return session;    }),  );   const sendTurn = vi.fn(    (      input: ProviderSendTurnInput,    ): Effect.Effect<ProviderTurnStartResult, ProviderAdapterError> => {      if (!sessions.has(input.threadId)) {        return Effect.fail(          new ProviderAdapterSessionNotFoundError({            provider,            threadId: input.threadId,          }),        );      }       return Effect.succeed({        threadId: input.threadId,        turnId: TurnId.make(`turn-${String(input.threadId)}`),      });    },  );   const interruptTurn = vi.fn(    (_threadId: ThreadId, _turnId?: TurnId): Effect.Effect<void, ProviderAdapterError> =>      Effect.void,  );   const compactThread = vi.fn((threadId: ThreadId): Effect.Effect<void, ProviderAdapterError> =>    Effect.sync(() =>      emit({        type: "thread.state.changed",        eventId: asEventId("evt-native-compact"),        provider,        createdAt: "2026-01-01T00:00:00.000Z",        threadId,        payload: { state: "compacted" },      }),    ),  );  const respondToRequest = vi.fn(    (      _threadId: ThreadId,      _requestId: string,      _decision: ProviderApprovalDecision,    ): Effect.Effect<void, ProviderAdapterError> => Effect.void,  );   const respondToUserInput = vi.fn(    (      _threadId: ThreadId,      _requestId: string,      _answers: Record<string, unknown>,    ): Effect.Effect<void, ProviderAdapterError> => Effect.void,  );   const stopSession = vi.fn((threadId: ThreadId): Effect.Effect<void, ProviderAdapterError> =>    Effect.sync(() => {      sessions.delete(threadId);    }),  );   const listSessions = vi.fn((): Effect.Effect<ReadonlyArray<ProviderSession>> =>    Effect.sync(() => Array.from(sessions.values())),  );   const hasSession = vi.fn((threadId: ThreadId): Effect.Effect<boolean> =>    Effect.succeed(sessions.has(threadId)),  );   const readThread = vi.fn(    (      threadId: ThreadId,    ): Effect.Effect<      {        threadId: ThreadId;        turns: ReadonlyArray<{ id: TurnId; items: readonly [] }>;      },      ProviderAdapterError    > =>      Effect.succeed({        threadId,        turns: [{ id: asTurnId("turn-1"), items: [] }],      }),  );   const rollbackThread = vi.fn(    (      threadId: ThreadId,      _numTurns: number,    ): Effect.Effect<{ threadId: ThreadId; turns: readonly [] }, ProviderAdapterError> =>      Effect.succeed({ threadId, turns: [] }),  );   const uploadFeedback = vi.fn(    (      input: ProviderUploadFeedbackInput,    ): Effect.Effect<ProviderUploadFeedbackResult, ProviderAdapterError> =>      Effect.succeed({ feedbackId: `feedback-${input.threadId}` }),  );   const stopAll = vi.fn((): Effect.Effect<void, ProviderAdapterError> =>    Effect.sync(() => {      sessions.clear();    }),  );   const adapter: ProviderAdapterShape<ProviderAdapterError> = {    provider,    capabilities: {      sessionModelSwitch: "in-session",      ...(supportsConversationRollback !== undefined ? { supportsConversationRollback } : {}),      ...(provider === CODEX_DRIVER ? { promptlessTurnContinuation: true } : {}),    },    startSession,    sendTurn,    ...(provider === CODEX_DRIVER      ? { compaction: { type: "native", start: compactThread } }      : provider === CURSOR_DRIVER        ? { compaction: { type: "slash-command", command: "/compress" } }        : provider === CLAUDE_AGENT_DRIVER          ? { compaction: { type: "slash-command", command: "/compact" } }          : {}),    interruptTurn,    respondToRequest,    respondToUserInput,    stopSession,    listSessions,    hasSession,    readThread,    rollbackThread,    ...(provider === CODEX_DRIVER ? { uploadFeedback } : {}),    stopAll,    get streamEvents() {      return Stream.fromPubSub(runtimeEventPubSub);    },  };   const emit = (event: LegacyProviderRuntimeEvent): void => {    Effect.runSync(PubSub.publish(runtimeEventPubSub, event as unknown as ProviderRuntimeEvent));  };   const updateSession = (    threadId: ThreadId,    update: (session: ProviderSession) => ProviderSession,  ): void => {    const existing = sessions.get(threadId);    if (!existing) {      return;    }    sessions.set(threadId, update(existing));  };   return {    adapter,    emit,    updateSession,    startSession,    sendTurn,    compactThread,    interruptTurn,    respondToRequest,    respondToUserInput,    stopSession,    listSessions,    hasSession,    readThread,    rollbackThread,    uploadFeedback,    stopAll,  };} interface RecordedAnalyticsEvent {  readonly event: string;  readonly properties?: Readonly<Record<string, unknown>>;} function makeRecordingAnalytics() {  const events: Array<RecordedAnalyticsEvent> = [];  const layer = Layer.succeed(    AnalyticsService.AnalyticsService,    AnalyticsService.AnalyticsService.of({      record: (event, properties) =>        Effect.sync(() => {          events.push({ event, ...(properties ? { properties } : {}) });        }),      flush: Effect.void,    }),  );   return {    layer,    reset: () => {      events.length = 0;    },    eventsByName: (event: string) => events.filter((entry) => entry.event === event),  };} function makeStaticInstanceRegistry(  entries: ReadonlyArray<readonly [ProviderInstanceId, ProviderAdapterShape<ProviderAdapterError>]>,): ProviderAdapterRegistry.ProviderAdapterRegistry["Service"] {  const adapters = new Map(entries);  const unsupported = (instanceId: ProviderInstanceId) =>    new ProviderUnsupportedError({      provider: ProviderDriverKind.make(instanceId),    });   return {    getByInstance: (instanceId) => {      const adapter = adapters.get(instanceId);      return adapter ? Effect.succeed(adapter) : Effect.fail(unsupported(instanceId));    },    getInstanceInfo: (instanceId) => {      const adapter = adapters.get(instanceId);      return adapter        ? Effect.succeed({            instanceId,            driverKind: adapter.provider,            displayName: undefined,            enabled: true,            continuationIdentity: {              driverKind: adapter.provider,              continuationKey: `${adapter.provider}:instance:${instanceId}`,            },          })        : Effect.fail(unsupported(instanceId));    },    listInstances: () => Effect.succeed(Array.from(adapters.keys())),    subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>      PubSub.subscribe(pubsub),    ),  };} const advanceTestClock = (ms: number) =>  TestClock.adjust(`${ms} millis`).pipe(Effect.andThen(Effect.yieldNow)); const hasMetricSnapshot = (  snapshots: ReadonlyArray<Metric.Metric.Snapshot>,  id: string,  attributes: Readonly<Record<string, string>>,) =>  snapshots.some(    (snapshot) =>      snapshot.id === id &&      Object.entries(attributes).every(([key, value]) => snapshot.attributes?.[key] === value),  ); function makeProviderServiceLayer(  input: {    readonly directory?: ProviderSessionDirectory.ProviderSessionDirectory["Service"];    readonly supportsConversationRollback?: boolean;    readonly analyticsLayer?: Layer.Layer<AnalyticsService.AnalyticsService>;    readonly registry?: ProviderAdapterRegistry.ProviderAdapterRegistry["Service"];  } = {},) {  const codex = makeFakeCodexAdapter(CODEX_DRIVER, input.supportsConversationRollback);  const claude = makeFakeCodexAdapter(CLAUDE_AGENT_DRIVER);  const cursor = makeFakeCodexAdapter(CURSOR_DRIVER);  const registry =    input.registry ??    makeAdapterRegistryMock({      [ProviderDriverKind.make("codex")]: codex.adapter,      [ProviderDriverKind.make("claudeAgent")]: claude.adapter,      [ProviderDriverKind.make("cursor")]: cursor.adapter,    });   const providerAdapterLayer = Layer.succeed(    ProviderAdapterRegistry.ProviderAdapterRegistry,    registry,  );  const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(    Layer.provide(SqlitePersistenceMemory),  );  const directoryLayer =    input.directory === undefined      ? ProviderSessionDirectoryLive.pipe(Layer.provide(runtimeRepositoryLayer))      : Layer.succeed(ProviderSessionDirectory.ProviderSessionDirectory, input.directory);   const layer = it.layer(    Layer.mergeAll(      makeProviderServiceLive().pipe(        Layer.provide(NodeServices.layer),        Layer.provide(providerAdapterLayer),        Layer.provide(directoryLayer),        Layer.provide(defaultServerSettingsLayer),        Layer.provide(serverConfigTestLayer),        Layer.provideMerge(input.analyticsLayer ?? AnalyticsService.layerTest),        Layer.provide(          Layer.succeed(            ProviderEventLoggers.ProviderEventLoggers,            ProviderEventLoggers.NoOpProviderEventLoggers,          ),        ),      ),      directoryLayer,       runtimeRepositoryLayer,      NodeServices.layer,    ),  );   return {    codex,    claude,    cursor,    layer,  };} for (const [enabled, completed] of [  [false, false],  [true, false],  [true, true],] as const) {  it.effect(    `persists shutdown recovery before stopping providers when enabled=${enabled}, completed=${completed}`,    () =>      Effect.gen(function* () {        const codex = makeFakeCodexAdapter();        const persistence = yield* Layer.build(          ProviderSessionDirectoryLive.pipe(            Layer.provide(              ProviderSessionRuntime.layer.pipe(Layer.provide(SqlitePersistenceMemory)),            ),          ),        );        const directory = yield* ProviderSessionDirectory.ProviderSessionDirectory.pipe(          Effect.provide(persistence),        );        const threadId = asThreadId("shutdown-recovery");        const turnId = asTurnId("shutdown-recovery-turn");        const scope = yield* Scope.make();

Lines 1–500 of 4968.