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