| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; |
| 3 | |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 6 | |
| 7 | import * as AcpAgent from "../../src/agent.ts"; |
| 8 | |
| 9 | if (process.env.ACP_MOCK_STDOUT_PREFIX !== undefined) { |
| 10 | process.stdout.write(process.env.ACP_MOCK_STDOUT_PREFIX); |
| 11 | } |
| 12 | |
| 13 | if (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 | |
| 18 | if (process.env.ACP_MOCK_EXIT_IMMEDIATELY_CODE !== undefined) { |
| 19 | process.exit(Number(process.env.ACP_MOCK_EXIT_IMMEDIATELY_CODE)); |
| 20 | } |
| 21 | |
| 22 | const sessionId = "mock-session-1"; |
| 23 | const v2Management = process.env.ACP_MOCK_V2_MANAGEMENT === "1"; |
| 24 | const unknownVariants = process.env.ACP_MOCK_UNKNOWN_VARIANTS === "1"; |
| 25 | const mcpOverAcp = process.env.ACP_MOCK_MCP_OVER_ACP === "1"; |
| 26 | |
| 27 | const 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 | |
| 239 | program.pipe( |
| 240 | Effect.provide(Layer.provide(AcpAgent.layerStdio(), NodeServices.layer)), |
| 241 | NodeRuntime.runMain, |
| 242 | ); |