1import * as Effect from "effect/Effect";
2import * as Layer from "effect/Layer";
3
4import * as NodeServices from "@effect/platform-node/NodeServices";
5import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
6
7import * as AcpAgent from "../../src/agent.ts";
8
9if (process.env.ACP_MOCK_STDOUT_PREFIX !== undefined) {
10 process.stdout.write(process.env.ACP_MOCK_STDOUT_PREFIX);
11}
12
13if (process.env.ACP_MOCK_MALFORMED_OUTPUT === "1") {
14 process.stdout.write("{not-json}\n");
15 process.exit(Number(process.env.ACP_MOCK_MALFORMED_OUTPUT_EXIT_CODE ?? "0"));
16}
17
18if (process.env.ACP_MOCK_EXIT_IMMEDIATELY_CODE !== undefined) {
19 process.exit(Number(process.env.ACP_MOCK_EXIT_IMMEDIATELY_CODE));
20}
21
22const sessionId = "mock-session-1";
23const v2Management = process.env.ACP_MOCK_V2_MANAGEMENT === "1";
24const unknownVariants = process.env.ACP_MOCK_UNKNOWN_VARIANTS === "1";
25const mcpOverAcp = process.env.ACP_MOCK_MCP_OVER_ACP === "1";
26
27const program = Effect.gen(function* () {
28 const agent = yield* AcpAgent.AcpAgent;
29
30 yield* agent.handleInitialize(() =>
31 Effect.succeed({
32 protocolVersion: 2,
33 capabilities: {
34 session: {
35 ...(v2Management ? { delete: {} } : {}),
36 ...(mcpOverAcp ? { mcp: { acp: {} } } : {}),
37 },
38 ...(v2Management ? { providers: {} } : {}),
39 },
40 info: {
41 name: "mock-agent",
42 version: "0.0.0",
43 },
44 ...(process.env.ACP_MOCK_CUSTOM_AUTH === "1"
45 ? { authMethods: [{ type: "custom_future", methodId: "custom", name: "Future auth" }] }
46 : {}),
47 ...(process.env.ACP_MOCK_ENV_VAR_AUTH === "1"
48 ? {
49 authMethods: [
50 {
51 type: "env_var",
52 methodId: "api_key",
53 name: "API key",
54 vars: [{ name: "MOCK_API_KEY", label: "Mock API key" }],
55 link: "https://example.test/keys",
56 },
57 ],
58 }
59 : {}),
60 }),
61 );
62
63 yield* agent.handleAuthenticate(() => Effect.succeed({}));
64 yield* agent.handleLogout(() => Effect.succeed({}));
65 yield* agent.handleCreateSession(() =>
66 Effect.succeed({
67 sessionId,
68 }),
69 );
70 yield* agent.handleLoadSession(() => Effect.succeed({}));
71 yield* agent.handleListSessions(() =>
72 Effect.succeed({
73 sessions: [
74 {
75 sessionId,
76 cwd: process.cwd(),
77 },
78 ],
79 }),
80 );
81 yield* agent.handleDeleteSession(() => Effect.succeed({}));
82 yield* agent.handleListProviders(() =>
83 Effect.succeed({
84 providers: [
85 {
86 providerId: "mock-provider",
87 supported: ["openai"],
88 required: false,
89 current: null,
90 },
91 ],
92 }),
93 );
94 yield* agent.handleSetProvider(() => Effect.succeed({}));
95 yield* agent.handleDisableProvider(() => Effect.succeed({}));
96
97 yield* agent.handlePrompt(() =>
98 Effect.gen(function* () {
99 if (mcpOverAcp) {
100 const connected = yield* agent.client.connectMcp({ serverId: "t3-code" });
101 yield* agent.client.messageMcp({
102 connectionId: connected.connectionId,
103 method: "tools/list",
104 });
105 yield* agent.client.notifyMcp({
106 connectionId: connected.connectionId,
107 method: "notifications/initialized",
108 });
109 yield* agent.client.disconnectMcp({ connectionId: connected.connectionId });
110 }
111
112 if (unknownVariants) {
113 yield* agent.client.sessionUpdate({
114 sessionId,
115 update: {
116 sessionUpdate: "agent_message_chunk",
117 messageId: "future-content",
118 content: { type: "chart", points: [] },
119 },
120 });
121 yield* agent.client.sessionUpdate({
122 sessionId,
123 update: { sessionUpdate: "timeline_update", entries: [] },
124 });
125 }
126
127 yield* agent.client.requestPermission({
128 sessionId,
129 title: "Read project files",
130 options: [
131 {
132 optionId: "allow",
133 name: "Allow",
134 kind: "allow_once",
135 },
136 ],
137 subject: {
138 type: "tool_call",
139 toolCall: {
140 toolCallId: "tool-1",
141 title: "Read project files",
142 },
143 },
144 });
145
146 yield* agent.client.elicit({
147 sessionId,
148 message: "Need confirmation before continuing.",
149 mode: "form",
150 requestedSchema: {
151 type: "object",
152 title: "Need confirmation",
153 properties: {
154 approved: {
155 type: "boolean",
156 title: "Approved",
157 },
158 },
159 required: ["approved"],
160 },
161 });
162
163 yield* agent.client.sessionUpdate({
164 sessionId,
165 update: {
166 sessionUpdate: "plan_update",
167 plan: {
168 type: "items",
169 planId: "mock-plan",
170 entries: [
171 {
172 content: "Inspect the repository",
173 priority: "high",
174 status: "in_progress",
175 },
176 ],
177 },
178 },
179 });
180
181 if (process.env.ACP_MOCK_V2_DIFF === "1") {
182 yield* agent.client.sessionUpdate({
183 sessionId,
184 update: {
185 sessionUpdate: "tool_call_update",
186 toolCallId: "tool-diff",
187 content: [
188 {
189 type: "diff",
190 changes: [
191 {
192 operation: "move",
193 oldPath: "/workspace/old.ts",
194 path: "/workspace/new.ts",
195 fileType: "text",
196 mimeType: "text/typescript",
197 },
198 ],
199 patch: {
200 format: "git_patch",
201 text: "diff --git a/old.ts b/new.ts\nsimilarity index 100%\nrename from old.ts\nrename to new.ts\n",
202 },
203 },
204 ],
205 },
206 });
207 }
208
209 yield* agent.client.elicitationComplete({
210 elicitationId: "elicitation-1",
211 });
212
213 yield* agent.client.extRequest("x/typed_request", {
214 message: process.env.ACP_MOCK_BAD_TYPED_REQUEST === "1" ? 123 : "hello from typed request",
215 });
216
217 yield* agent.client.extNotification("x/typed_notification", {
218 count: 2,
219 });
220
221 yield* agent.client.sessionUpdate({
222 sessionId,
223 update: { sessionUpdate: "state_update", state: "idle", stopReason: "end_turn" },
224 });
225 return {};
226 }),
227 );
228
229 yield* agent.handleUnknownExtRequest((method, params) =>
230 Effect.succeed({
231 echoedMethod: method,
232 echoedParams: params ?? null,
233 }),
234 );
235
236 return yield* Effect.never;
237});
238
239program.pipe(
240 Effect.provide(Layer.provide(AcpAgent.layerStdio(), NodeServices.layer)),
241 NodeRuntime.runMain,
242);