3426 lines · 132.2 KB · TypeScript
RawDownload
import type {  Event as OpenCodeEvent,  Message as OpenCodeMessage,  Part as OpenCodePart,  PermissionRequest,  PermissionRuleset,  QuestionRequest,  Session as OpenCodeSession,  Todo as OpenCodeTodo,  ToolPart,} from "@opencode-ai/sdk/v2";import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";import { getModelSelectionStringOptionValue } from "@t3tools/shared/model";import { causeErrorTag } from "@t3tools/shared/observability";import {  defaultInstanceIdForDriver,  type ModelSelection,  type OpenCodeSettings,  type OrchestrationV2AppThread,  type OrchestrationV2ConversationMessage,  type OrchestrationV2ExecutionNode,  type OrchestrationV2PlanStep,  type OrchestrationV2ProviderCapabilities,  type OrchestrationV2ProviderFailure,  type OrchestrationV2ProviderRef,  type OrchestrationV2ProviderSession,  type OrchestrationV2ProviderThread,  type OrchestrationV2ProviderTurn,  type OrchestrationV2RuntimeRequest,  type OrchestrationV2Subagent,  type OrchestrationV2TurnItem,  OpenCodeSettings as OpenCodeSettingsSchema,  type PlanId,  ProviderDriverKind,  type ProviderInstanceId,  type ProviderRequestKind,  type ProviderSessionId,  type RuntimeRequestId,  type ThreadId,} from "@t3tools/contracts";import * as Cause from "effect/Cause";import * as DateTime from "effect/DateTime";import * as Deferred from "effect/Deferred";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Queue from "effect/Queue";import * as Random from "effect/Random";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Stream from "effect/Stream"; import { resolveAttachmentPath } from "../../attachmentStore.ts";import { ServerConfig } from "../../config.ts";import * as McpProviderSession from "../../mcp/McpProviderSession.ts";import type { EventNdjsonLogger } from "../../provider/Layers/EventNdjsonLogger.ts";import { ProviderEventLoggers } from "../../provider/Layers/ProviderEventLoggers.ts";import {  structuralProtocolMethod,  summarizeNativeProtocolPayload,} from "../../provider/NativeProtocolLogging.ts";import { mergeProviderInstanceEnvironment } from "../../provider/ProviderInstanceEnvironment.ts";import { t3OrchestrationSystemPrompt } from "../../provider/T3OrchestrationInstructions.ts";import { buildRuntimeInstructions } from "../../provider/RuntimeInstructions.ts";import {  OpenCodeRuntime,  OpenCodeRuntimeError,  openCodeQuestionId,  openCodeRuntimeErrorDetail,  parseOpenCodeModelSlug,  runOpenCodeSdk,  toOpenCodeFileParts,  toOpenCodePermissionReply,  toOpenCodeQuestionAnswers,  type OpenCodeRuntimeShape,} from "../../provider/opencodeRuntime.ts";import { IdAllocatorV2, type IdAllocatorV2Shape } from "../IdAllocator.ts";import { makeProviderFailure } from "../ProviderFailure.ts";import { turnScopedSelectionTransition } from "../ProviderSelectionTransition.ts";import { providerMessageTextWithAttachmentPaths } from "../AttachmentPrompt.ts";import {  ProviderAdapterEnsureThreadError,  ProviderAdapterForkThreadError,  ProviderAdapterInterruptError,  ProviderAdapterOpenSessionError,  ProviderAdapterProtocolError,  ProviderAdapterReadThreadSnapshotError,  ProviderAdapterResumeThreadError,  ProviderAdapterRollbackThreadError,  ProviderAdapterRuntimeRequestResponseError,  ProviderAdapterSteerRunError,  ProviderAdapterTurnStartError,  ProviderAdapterV2,  type ProviderAdapterV2Event,  type ProviderAdapterV2OpenSessionInput,  type ProviderAdapterV2SessionRuntime,  type ProviderAdapterV2Shape,  type ProviderAdapterV2RuntimePolicy,  type ProviderAdapterV2TurnInput,} from "../ProviderAdapter.ts";import {  ProviderAdapterDriverCreateError,  type ProviderAdapterDriver,  type ProviderAdapterDriverCreateInput,} from "../ProviderAdapterDriver.ts";import { makeSubagentChildThread, subagentThreadTitle } from "../SubagentProjection.ts"; export const OPENCODE_PROVIDER = ProviderDriverKind.make("opencode");export const OPENCODE_DRIVER_KIND = OPENCODE_PROVIDER;export const OPENCODE_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(OPENCODE_DRIVER_KIND);export const OPENCODE_SDK_PROTOCOL = "opencode-sdk.sse" as const;const DEFAULT_OPENCODE_SETTINGS = Schema.decodeSync(OpenCodeSettingsSchema)({}); let openCodeMessageIdEpochMillis = -1;let openCodeMessageIdCounter = 0; const makeOpenCodeMessageId = Effect.fnUntraced(function* () {  const epochMillis = DateTime.toEpochMillis(yield* DateTime.now);  if (epochMillis !== openCodeMessageIdEpochMillis) {    openCodeMessageIdEpochMillis = epochMillis;    openCodeMessageIdCounter = 0;  }  openCodeMessageIdCounter += 1;  const encodedTime = BigInt.asUintN(    48,    BigInt(epochMillis) * 0x1000n + BigInt(openCodeMessageIdCounter),  )    .toString(16)    .padStart(12, "0");  const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";  const random = (yield* Effect.forEach(Array.from({ length: 28 }), () =>    Random.nextIntBetween(0, alphabet.length - 1),  ))    .map((index) => alphabet[index])    .join("");  return `msg_${encodedTime}${random}`;}); /** * OpenCode's session, message, part, and interaction-request identifiers are * durable. It does not expose a first-class turn object: the initiating user * message is the best native turn correlation point, and session idle is the * authoritative terminal signal. */export const OpenCodeProviderCapabilitiesV2 = {  sessions: {    // The current adapter owns one directory-bound client/server per session.    // Keep it isolated until its runtime is made safe for cross-thread pooling.    supportsMultipleProviderThreadsPerSession: false,    supportsModelSwitchInSession: true,    supportsProviderSwitchingViaHandoff: true,    supportsRuntimeModeSwitchInSession: false,    pendingRequestsSurviveRestart: false,  },  threads: {    canCreateEmptyThread: true,    canReadThreadSnapshot: true,    canRollbackThread: true,    canForkThread: true,    canForkFromTurn: true,    canForkFromSubagentThread: true,    exposesNativeThreadId: true,  },  turns: {    exposesNativeTurnId: false,    emitsTurnStarted: true,    emitsTurnCompleted: true,    supportsInterrupt: true,    supportsActiveSteering: true,    supportsSteeringByInterruptRestart: true,    supportsQueuedMessages: true,    terminalStatusQuality: "strong",  },  streaming: {    streamsAssistantText: true,    streamsReasoning: true,    streamsToolOutput: true,    streamsPlanText: false,    emitsMessageCompleted: true,  },  tools: {    exposesToolItemIds: true,    emitsToolStarted: true,    emitsToolCompleted: true,    emitsToolOutput: true,    supportsMcpTools: true,    supportsDynamicToolCallbacks: false,  },  approvals: {    supportsCommandApproval: true,    supportsFileReadApproval: true,    supportsFileChangeApproval: true,    supportsApplyPatchApproval: true,    approvalsHaveNativeRequestIds: true,    approvalCallbacksAreLiveOnly: true,    approvalsCanOriginateFromSubagents: true,  },  planning: {    emitsPlanUpdated: true,    emitsTodoList: true,    emitsProposedPlan: false,    supportsStructuredQuestions: true,    planDeltasHaveItemIds: false,  },  subagents: {    supportsSubagents: true,    exposesSubagentThreadIds: true,    emitsSubagentLifecycle: true,    canWaitForSubagents: true,    canCloseSubagents: false,    canForkSubagentThread: true,  },  context: {    acceptsSystemContext: false,    acceptsDeveloperContext: false,    acceptsSyntheticUserContext: true,    canGenerateSummaries: true,    canConsumeHandoffSummaries: true,    supportsDeltaHandoff: true,    supportsFullThreadHandoff: true,    maxRecommendedHandoffChars: null,  },  checkpointing: {    appCanCheckpointFilesystem: true,    supportsNestedCheckpointScopes: true,    providerCanRollbackConversation: true,    providerRollbackReturnsSnapshot: true,    providerCanReadConversationSnapshot: true,  },  identity: {    nativeThreadIds: "strong",    nativeTurnIds: "weak",    nativeItemIds: "strong",    nativeRequestIds: "strong",  },} satisfies OrchestrationV2ProviderCapabilities; type TerminalTurnStatus = Extract<  OrchestrationV2ProviderTurn["status"],  "completed" | "interrupted" | "failed" | "cancelled">; interface ActiveOpenCodeTurn {  readonly isRoot: boolean;  readonly threadId: ThreadId;  readonly runId: ProviderAdapterV2TurnInput["runId"] | null;  readonly rootNodeId: ProviderAdapterV2TurnInput["rootNodeId"];  readonly appThread: OrchestrationV2AppThread;  readonly modelSelection: ModelSelection;  readonly runtimePolicy: ProviderAdapterV2RuntimePolicy;  readonly providerTurnId: OrchestrationV2ProviderTurn["id"];  readonly providerTurnOrdinal: number;  readonly runOrdinal: number;  readonly runAttemptId: OrchestrationV2ProviderTurn["runAttemptId"];  readonly startedAt: DateTime.Utc;  readonly itemOrdinals: Map<string, number>;  readonly parts: Map<string, Exclude<OpenCodePart, ToolPart>>;  readonly partIdsByMessage: Map<string, Set<string>>;  readonly toolNamesByCallId: Map<string, string>;  readonly providerTurn: OrchestrationV2ProviderTurn;  nextItemOrdinal: number;  nativeUserMessageId: string | null;  admissionMessageId: string | null;  interrupted: boolean;  finalized: boolean;  planId: PlanId | null;  admissionGeneration: number;  admissionReconciliationGeneration: number | null;  admissionPending: boolean;  admissionAccepted: boolean;  admissionMessageObserved: boolean;  idleDuringAdmission: boolean;  admissionSettled: Deferred.Deferred<void>;  admissionAbortController: AbortController | null;} type OpenCodeAdmissionSignal = "accepted" | "busy" | "idle" | "user-message";type OpenCodeAdmissionAction = "hold" | "reconcile-idle" | "release"; export function advanceOpenCodePromptAdmission(  admission: Pick<    ActiveOpenCodeTurn,    "admissionAccepted" | "admissionMessageObserved" | "admissionPending" | "idleDuringAdmission"  >,  signal: OpenCodeAdmissionSignal,): OpenCodeAdmissionAction {  if (!admission.admissionPending) return "release";  if (signal === "idle") {    admission.idleDuringAdmission = true;    return "hold";  }  if (signal === "accepted") admission.admissionAccepted = true;  if (signal === "busy" || signal === "user-message") admission.admissionMessageObserved = true;  if (!admission.admissionAccepted || !admission.admissionMessageObserved) return "hold";  if (admission.idleDuringAdmission) return "reconcile-idle";  admission.admissionPending = false;  return "release";} export function cancelOpenCodePromptAdmission(  admission: Pick<ActiveOpenCodeTurn, "admissionGeneration" | "admissionPending">,  nextGeneration: number,): void {  admission.admissionGeneration = nextGeneration;  admission.admissionPending = false;} export const reconcileOpenCodePromptAdmissionStatus = Effect.fn(  "reconcileOpenCodePromptAdmissionStatus",)(function* (  admission: Pick<ActiveOpenCodeTurn, "admissionGeneration" | "admissionPending">,  generation: number,  readStatus: Effect.Effect<"busy" | "idle" | "unknown">,) {  if (admission.admissionGeneration !== generation || !admission.admissionPending) {    return "stale" as const;  }  const status = yield* readStatus;  if (admission.admissionGeneration !== generation || !admission.admissionPending) {    return "stale" as const;  }  if (status !== "unknown") admission.admissionPending = false;  return status;}); interface OpenCodeSubagentContext {  readonly nativeItemId: string;  readonly nodeId: OrchestrationV2Subagent["id"];  readonly parentTurn: ActiveOpenCodeTurn;  readonly prompt: string;  readonly title: string | null;  readonly startedAt: DateTime.Utc;  childSessionId: string | null;  childThreadId: ThreadId | null;  childProviderThreadId: OrchestrationV2ProviderThread["id"] | null;  model: string | null;  result: string | null;} interface OpenCodeThreadState {  readonly nativeSessionId: string;  providerThread: OrchestrationV2ProviderThread;  appThread: OrchestrationV2AppThread | null;  activeTurn: ActiveOpenCodeTurn | null;  readonly providerTurns: Map<string, OrchestrationV2ProviderTurn>;  readonly messages: Map<string, OrchestrationV2ConversationMessage>;  readonly runtimeRequests: Map<string, OrchestrationV2RuntimeRequest>;  readonly messageRoles: Map<string, "user" | "assistant">;  readonly userMessageIds: Array<string>;  parentSubagent: OpenCodeSubagentContext | null;  nextChildTurnOrdinal: number;  nextAdmissionGeneration: number;} interface PendingOpenCodeRequest {  readonly requestId: RuntimeRequestId;  readonly nativeRequestId: string;  readonly turn: ActiveOpenCodeTurn;  readonly state: OpenCodeThreadState;  readonly nodeId: OrchestrationV2ExecutionNode["id"];  readonly turnItemId: OrchestrationV2TurnItem["id"];  readonly requestKind: OpenCodePermissionRequestKind | "user_input";  readonly createdAt: DateTime.Utc;  readonly permission?: PermissionRequest;  readonly question?: QuestionRequest;} export interface OpenCodeAdapterV2Options {  readonly instanceId: ProviderInstanceId;  readonly settings: OpenCodeSettings;  readonly environment: NodeJS.ProcessEnv;  readonly runtime: OpenCodeRuntimeShape;  readonly idAllocator: IdAllocatorV2Shape;  readonly serverConfig: ServerConfig["Service"];  readonly nativeEventLogger?: EventNdjsonLogger;} export interface OpenCodeProtocolLogEvent {  readonly direction: "incoming" | "outgoing";  readonly messageKind: "request" | "response" | "notification" | "error";  readonly method: string;  readonly payload: unknown;} export function formatOpenCodeProtocolLogPayload(event: OpenCodeProtocolLogEvent) {  return {    direction: event.direction,    messageKind: event.messageKind,    method: structuralProtocolMethod(event.method),    payload: summarizeNativeProtocolPayload(event.payload),  };} export function makeOpenCodeProtocolLogger(input: {  readonly nativeEventLogger: EventNdjsonLogger | undefined;  readonly idAllocator: IdAllocatorV2Shape;  readonly providerInstanceId: ProviderInstanceId;  readonly providerSessionId: ProviderSessionId;  readonly threadId: ThreadId;}): (event: OpenCodeProtocolLogEvent) => Effect.Effect<void, never> {  return (event) =>    Effect.gen(function* () {      if (!input.nativeEventLogger) return;      const observedAt = DateTime.formatIso(yield* DateTime.now);      const method = structuralProtocolMethod(event.method);      yield* input.nativeEventLogger.write(        {          observedAt,          event: {            id: yield* input.idAllocator.allocate.rawEvent({              providerSessionId: input.providerSessionId,              method,            }),            kind: "protocol",            protocol: OPENCODE_SDK_PROTOCOL,            provider: OPENCODE_PROVIDER,            providerInstanceId: input.providerInstanceId,            providerSessionId: input.providerSessionId,            createdAt: observedAt,            threadId: input.threadId,            payload: formatOpenCodeProtocolLogPayload(event),          },        },        input.threadId,      );    }).pipe(      Effect.catchCause((cause) =>        Cause.hasInterrupts(cause)          ? Effect.interrupt          : Effect.logWarning("Failed to write native OpenCode event log.", {              errorTag: causeErrorTag(cause),              reasonCount: cause.reasons.length,              provider: OPENCODE_PROVIDER,              threadId: input.threadId,            }),      ),    );} function protocolError(detail: string, payload?: unknown): ProviderAdapterProtocolError {  return new ProviderAdapterProtocolError({    driver: OPENCODE_PROVIDER,    detail,    ...(payload === undefined ? {} : { payload }),  });} function nativeThreadId(providerThread: OrchestrationV2ProviderThread): string {  const nativeId = providerThread.nativeThreadRef?.nativeId;  if (nativeId === null || nativeId === undefined) {    throw protocolError(`Provider thread ${providerThread.id} has no OpenCode session id`);  }  return nativeId;} function dateTimeFromEpoch(value: number | undefined, fallback: DateTime.Utc): DateTime.Utc {  if (value === undefined) return fallback;  return Option.getOrElse(DateTime.make(value), () => fallback);} function nonEmptyString(value: unknown): string | undefined {  return typeof value === "string" && value.trim().length > 0 ? value : undefined;} function recordValue(input: unknown, key: string): unknown {  return typeof input === "object" && input !== null && key in input    ? (input as Record<string, unknown>)[key]    : undefined;} function recordString(input: unknown, ...keys: ReadonlyArray<string>): string | undefined {  for (const key of keys) {    const value = nonEmptyString(recordValue(input, key));    if (value !== undefined) return value;  }  return undefined;} function recordNumber(input: unknown, ...keys: ReadonlyArray<string>): number | undefined {  for (const key of keys) {    const value = recordValue(input, key);    if (typeof value === "number" && Number.isFinite(value)) return value;  }  return undefined;} function stableJson(value: unknown): string {  if (typeof value === "string") return value;  try {    return JSON.stringify(value, null, 2);  } catch {    return String(value);  }} function sdkResponseForRawLog(value: unknown): unknown {  if (typeof value !== "object" || value === null) return value;  if ("data" in value) return { data: (value as { readonly data?: unknown }).data ?? null };

Lines 1–500 of 3426.