1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { AcpRegistrySettings } from "@t3tools/provider-acp-registry/settings";
3import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Layer from "effect/Layer";
7import * as Path from "effect/Path";
8import * as Schema from "effect/Schema";
9import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
10
11import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
12import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
13import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
14import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";
15import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";
16import {
17 type AcpReplayTranscript,
18 AcpReplayTranscriptDecodeError,
19 decodeAcpReplayTranscript,
20 makeAcpReplayCompletenessAssertion,
21 makeAcpReplayRuntime,
22} from "./AcpAdapterV2.testkit.ts";
23import {
24 ACP_REGISTRY_DEFAULT_INSTANCE_ID,
25 ACP_REGISTRY_PROVIDER,
26 makeAcpRegistryAdapterV2,
27} from "@t3tools/provider-acp-registry/testing";
28import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport";
29
30const REPLAY_SETTINGS = Schema.decodeUnknownSync(AcpRegistrySettings)({
31 agentId: "replay-agent",
32 authMethodId: "replay",
33});
34
35function layerAcpRegistryProviderAdapterRegistryReplay(
36 transcript: AcpReplayTranscript,
37 options: { readonly replayGate?: ProviderReplayGate } = {},
38) {
39 const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
40
41 return ProviderAdapterRegistry.layerFromAdaptersEffect(
42 Effect.gen(function* () {
43 const fileSystem = yield* FileSystem.FileSystem;
44 const path = yield* Path.Path;
45 const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
46 const replayGate = options.replayGate;
47 const replayDir = yield* fileSystem
48 .makeTempDirectory({
49 prefix: `t3-orchestration-v2-acp-registry-replay-${transcript.scenario}-`,
50 })
51 .pipe(Effect.orDie);
52 const statusPath = path.join(replayDir, "status.json");
53 const scriptPath = yield* path
54 .fromFileUrl(new URL("../../../scripts/acp-replay-agent.ts", import.meta.url))
55 .pipe(Effect.orDie);
56 const adapter = yield* makeAcpRegistryAdapterV2({
57 instanceId: ACP_REGISTRY_DEFAULT_INSTANCE_ID,
58 settings: REPLAY_SETTINGS,
59 environment: {},
60 selfInvocation: yield* resolveSelfInvocation(),
61 makeRuntime: makeAcpReplayRuntime({
62 transcript,
63 statusPath,
64 scriptPath,
65 childProcessSpawner,
66 fileSystem,
67 ...(replayGate === undefined ? {} : { replayGate }),
68 }),
69 assertComplete: makeAcpReplayCompletenessAssertion(fileSystem, statusPath, transcript),
70 });
71 return [adapter];
72 }),
73 ).pipe(
74 Layer.provide(
75 Layer.mergeAll(
76 layerHost,
77 NodeServices.layer,
78 IdAllocator.layer,
79 Layer.mock(AcpRegistrySupport.AcpRegistryCatalog)({
80 resolve: () => Effect.die("ACP registry resolver must not run during replay"),
81 }),
82 ),
83 ),
84 // Held inbound lines must not outlive the scenario and wedge teardown.
85 Layer.merge(
86 Layer.effectDiscard(
87 Effect.addFinalizer(() => Effect.sync(() => options.replayGate?.releaseAll())),
88 ),
89 ),
90 );
91}
92
93export const AcpRegistryOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness<
94 AcpReplayTranscript,
95 AcpReplayTranscriptDecodeError
96> = {
97 driver: ACP_REGISTRY_PROVIDER,
98 decodeTranscript: (transcript) =>
99 decodeAcpReplayTranscript(transcript, ACP_REGISTRY_PROVIDER, {
100 retargetProvider: true,
101 }),
102 makeProviderAdapterRegistryLayer: layerAcpRegistryProviderAdapterRegistryReplay,
103};