| 1 | import * as OtelEnvironment from "@t3tools/shared/otelEnvironment"; |
| 2 | import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability"; |
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { type ProviderReplayTranscript } from "@t3tools/contracts"; |
| 5 | import * as CodexClient from "effect-codex-app-server/client"; |
| 6 | import type * as CodexError from "effect-codex-app-server/errors"; |
| 7 | import * as CodexReplay from "effect-codex-app-server/replay"; |
| 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as FileSystem from "effect/FileSystem"; |
| 10 | import * as Layer from "effect/Layer"; |
| 11 | import * as Path from "effect/Path"; |
| 12 | import * as PlatformError from "effect/PlatformError"; |
| 13 | import * as Predicate from "effect/Predicate"; |
| 14 | import * as Schema from "effect/Schema"; |
| 15 | |
| 16 | import * as ServerConfig from "../../config.ts"; |
| 17 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; |
| 18 | import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; |
| 19 | import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver"; |
| 20 | import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts"; |
| 21 | import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts"; |
| 22 | import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate"; |
| 23 | import * as CodexAdapterV2 from "./CodexAdapterV2.ts"; |
| 24 | |
| 25 | export class CodexReplayTranscriptDecodeError extends Schema.TaggedError<CodexReplayTranscriptDecodeError>()( |
| 26 | "CodexReplayTranscriptDecodeError", |
| 27 | { |
| 28 | driver: Schema.optional(Schema.String), |
| 29 | protocol: Schema.optional(Schema.String), |
| 30 | scenario: Schema.optional(Schema.String), |
| 31 | cause: Schema.Defect(), |
| 32 | }, |
| 33 | ) { |
| 34 | override get message(): string { |
| 35 | return `Failed to decode Codex app-server replay transcript for scenario ${this.scenario ?? "<unknown>"}.`; |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | export const CodexOrchestratorReplayHarnessError = Schema.Union([ |
| 40 | CodexReplayTranscriptDecodeError, |
| 41 | CodexReplay.CodexAppServerReplayError, |
| 42 | ProviderAdapterDriverCreateError, |
| 43 | ]); |
| 44 | export type CodexOrchestratorReplayHarnessError = typeof CodexOrchestratorReplayHarnessError.Type; |
| 45 | |
| 46 | export function withCodexReplayChildMetadata( |
| 47 | client: CodexClient.CodexAppServerClient["Service"], |
| 48 | transcript: CodexReplay.CodexAppServerReplayTranscript, |
| 49 | readMetadata: ( |
| 50 | threadId: string, |
| 51 | method: "thread/read" | "thread/resume", |
| 52 | ) => Effect.Effect<unknown, CodexError.CodexAppServerError> = (threadId) => |
| 53 | Effect.succeed({ thread: { id: threadId }, model: null }), |
| 54 | ): CodexClient.CodexAppServerClient["Service"] { |
| 55 | const childThreadIds = new Set( |
| 56 | transcript.entries.flatMap((entry) => { |
| 57 | if (entry.type !== "emit_inbound" || !Predicate.isObject(entry.frame)) return []; |
| 58 | const params = entry.frame.params; |
| 59 | if (!Predicate.isObject(params) || !Predicate.isObject(params.item)) return []; |
| 60 | const item = params.item; |
| 61 | if (item.type === "subAgentActivity" && typeof item.agentThreadId === "string") { |
| 62 | return [item.agentThreadId]; |
| 63 | } |
| 64 | return item.type === "collabAgentToolCall" && Array.isArray(item.receiverThreadIds) |
| 65 | ? item.receiverThreadIds.filter(Predicate.isString) |
| 66 | : []; |
| 67 | }), |
| 68 | ); |
| 69 | return { |
| 70 | ...client, |
| 71 | raw: { |
| 72 | ...client.raw, |
| 73 | request: (method, params) => |
| 74 | (method === "thread/read" || method === "thread/resume") && |
| 75 | Predicate.isObject(params) && |
| 76 | (method === "thread/read" ? params.includeTurns === false : params.excludeTurns === true) && |
| 77 | typeof params.threadId === "string" && |
| 78 | childThreadIds.has(params.threadId) |
| 79 | ? readMetadata(params.threadId, method) |
| 80 | : client.raw.request(method, params), |
| 81 | }, |
| 82 | }; |
| 83 | } |
| 84 | |
| 85 | function metadataFromTranscript(transcript: ProviderReplayTranscript): { |
| 86 | readonly provider?: string; |
| 87 | readonly protocol?: string; |
| 88 | readonly scenario?: string; |
| 89 | } { |
| 90 | return { |
| 91 | provider: transcript.provider, |
| 92 | protocol: transcript.protocol, |
| 93 | scenario: transcript.scenario, |
| 94 | }; |
| 95 | } |
| 96 | |
| 97 | export function makeReplayServerConfig( |
| 98 | scenario: string, |
| 99 | ): Effect.Effect< |
| 100 | ServerConfig.ServerConfig["Service"], |
| 101 | PlatformError.PlatformError, |
| 102 | FileSystem.FileSystem | Path.Path |
| 103 | > { |
| 104 | return Effect.gen(function* () { |
| 105 | const fs = yield* FileSystem.FileSystem; |
| 106 | const path = yield* Path.Path; |
| 107 | const baseDir = yield* fs.makeTempDirectory({ |
| 108 | prefix: `t3-orchestration-v2-codex-${scenario}-`, |
| 109 | }); |
| 110 | const stateDir = path.join(baseDir, "userdata"); |
| 111 | const logsDir = path.join(stateDir, "logs"); |
| 112 | const providerLogsDir = path.join(logsDir, "provider"); |
| 113 | const terminalLogsDir = path.join(logsDir, "terminals"); |
| 114 | const attachmentsDir = path.join(stateDir, "attachments"); |
| 115 | const environmentThemesDir = path.join(stateDir, "themes"); |
| 116 | const worktreesDir = path.join(baseDir, "worktrees"); |
| 117 | const providerStatusCacheDir = path.join(baseDir, "caches"); |
| 118 | |
| 119 | for (const directory of [ |
| 120 | stateDir, |
| 121 | logsDir, |
| 122 | providerLogsDir, |
| 123 | terminalLogsDir, |
| 124 | attachmentsDir, |
| 125 | environmentThemesDir, |
| 126 | worktreesDir, |
| 127 | providerStatusCacheDir, |
| 128 | ]) { |
| 129 | yield* fs.makeDirectory(directory, { recursive: true }); |
| 130 | } |
| 131 | |
| 132 | return { |
| 133 | logLevel: "Error", |
| 134 | traceMinLevel: "Info", |
| 135 | traceTimingEnabled: true, |
| 136 | traceBatchWindowMs: 200, |
| 137 | traceMaxBytes: 10 * 1024 * 1024, |
| 138 | traceMaxFiles: 10, |
| 139 | otelEnvironment: OtelEnvironment.none, |
| 140 | otlpTracesUrl: undefined, |
| 141 | otlpMetricsUrl: undefined, |
| 142 | otlpLogsUrl: undefined, |
| 143 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 144 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, |
| 145 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, |
| 146 | mode: "web", |
| 147 | port: 0, |
| 148 | host: undefined, |
| 149 | cwd: process.cwd(), |
| 150 | baseDir, |
| 151 | staticDir: undefined, |
| 152 | devUrl: undefined, |
| 153 | devAllowedOrigins: [], |
| 154 | noBrowser: false, |
| 155 | startupPresentation: "browser", |
| 156 | tailscaleServeEnabled: false, |
| 157 | tailscaleServePort: 443, |
| 158 | desktopBootstrapToken: undefined, |
| 159 | autoBootstrapProjectFromCwd: false, |
| 160 | logWebSocketEvents: false, |
| 161 | stateDir, |
| 162 | dbPath: path.join(stateDir, "state.sqlite"), |
| 163 | keybindingsConfigPath: path.join(stateDir, "keybindings.json"), |
| 164 | settingsPath: path.join(stateDir, "settings.json"), |
| 165 | providerStatusCacheDir, |
| 166 | worktreesDir, |
| 167 | attachmentsDir, |
| 168 | browserArtifactsDir: path.join(stateDir, "browser-artifacts"), |
| 169 | environmentThemesDir, |
| 170 | logsDir, |
| 171 | serverLogPath: path.join(logsDir, "server.log"), |
| 172 | serverTracePath: path.join(logsDir, "server.trace.ndjson"), |
| 173 | providerLogsDir, |
| 174 | providerEventLogPath: path.join(providerLogsDir, "events.log"), |
| 175 | terminalLogsDir, |
| 176 | environmentIdPath: path.join(stateDir, "environment-id"), |
| 177 | serverRuntimeStatePath: path.join(stateDir, "server-runtime.json"), |
| 178 | secretsDir: path.join(stateDir, "secrets"), |
| 179 | }; |
| 180 | }); |
| 181 | } |
| 182 | |
| 183 | export function layer(input: { |
| 184 | readonly transcript: CodexReplay.CodexAppServerReplayTranscript; |
| 185 | readonly driver?: CodexReplay.CodexAppServerReplayDriver; |
| 186 | }) { |
| 187 | const layerReplay = |
| 188 | input.driver === undefined |
| 189 | ? CodexReplay.layerReplay(input.transcript) |
| 190 | : CodexReplay.layerReplayWithDriver(input.driver); |
| 191 | const layerReplayClientFactory = Layer.succeed(CodexAdapterV2.CodexAppServerClientFactory, { |
| 192 | open: (openInput) => |
| 193 | Effect.gen(function* () { |
| 194 | const context = yield* Layer.build(layerReplay).pipe( |
| 195 | Effect.mapError( |
| 196 | (cause) => |
| 197 | new ProviderAdapter.ProviderAdapterOpenSessionError({ |
| 198 | driver: CodexAdapterV2.CODEX_DRIVER_KIND, |
| 199 | providerSessionId: openInput.providerSessionId, |
| 200 | cause, |
| 201 | }), |
| 202 | ), |
| 203 | ); |
| 204 | return yield* Effect.service(CodexClient.CodexAppServerClient).pipe( |
| 205 | Effect.map((client) => withCodexReplayChildMetadata(client, input.transcript)), |
| 206 | Effect.provide(context), |
| 207 | ); |
| 208 | }), |
| 209 | }); |
| 210 | const layerServerConfig = Layer.effect( |
| 211 | ServerConfig.ServerConfig, |
| 212 | makeReplayServerConfig(input.transcript.scenario).pipe(Effect.orDie), |
| 213 | ).pipe(Layer.provide(NodeServices.layer)); |
| 214 | const layerRegistry = ProviderAdapterRegistry.layerFromDrivers({ |
| 215 | drivers: [CodexAdapterV2.CodexAdapterV2Driver], |
| 216 | configMap: { |
| 217 | [CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID]: { |
| 218 | driver: CodexAdapterV2.CODEX_DRIVER_KIND, |
| 219 | }, |
| 220 | }, |
| 221 | }).pipe( |
| 222 | Layer.provide( |
| 223 | Layer.mergeAll( |
| 224 | layerReplayClientFactory, |
| 225 | layerServerConfig, |
| 226 | NodeServices.layer, |
| 227 | IdAllocator.layer, |
| 228 | ), |
| 229 | ), |
| 230 | ); |
| 231 | |
| 232 | return layerRegistry; |
| 233 | } |
| 234 | |
| 235 | const decodeCodexAppServerReplayTranscript = Schema.decodeUnknownEffect( |
| 236 | CodexReplay.CodexAppServerReplayTranscript, |
| 237 | ); |
| 238 | |
| 239 | export const CodexOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness< |
| 240 | CodexReplay.CodexAppServerReplayTranscript, |
| 241 | CodexOrchestratorReplayHarnessError |
| 242 | > = { |
| 243 | driver: CodexAdapterV2.CODEX_DRIVER_KIND, |
| 244 | decodeTranscript: (transcript) => |
| 245 | decodeCodexAppServerReplayTranscript(transcript).pipe( |
| 246 | Effect.mapError( |
| 247 | (cause) => |
| 248 | new CodexReplayTranscriptDecodeError({ |
| 249 | ...metadataFromTranscript(transcript), |
| 250 | cause, |
| 251 | }), |
| 252 | ), |
| 253 | ), |
| 254 | makeProviderAdapterRegistryLayer: ( |
| 255 | transcript, |
| 256 | options: { readonly replayGate?: ProviderReplayGate } = {}, |
| 257 | ) => { |
| 258 | return Layer.effectContext( |
| 259 | Effect.gen(function* () { |
| 260 | const replayGate = options.replayGate; |
| 261 | if (replayGate !== undefined) { |
| 262 | yield* Effect.addFinalizer(() => Effect.sync(() => replayGate.releaseAll())); |
| 263 | } |
| 264 | const driver = yield* CodexReplay.makeReplayDriver( |
| 265 | transcript, |
| 266 | replayGate === undefined |
| 267 | ? {} |
| 268 | : { |
| 269 | beforeEmitInbound: (entry) => |
| 270 | Effect.promise((signal) => replayGate.beforeEmit(entry.label, signal)), |
| 271 | }, |
| 272 | ); |
| 273 | return yield* Layer.build(layer({ transcript, driver })); |
| 274 | }), |
| 275 | ); |
| 276 | }, |
| 277 | }; |