| 1 | import type { |
| 2 | AgentMessage, |
| 3 | AgentOptions, |
| 4 | InteractionUpdate, |
| 5 | RunResult, |
| 6 | SDKUserMessage, |
| 7 | SendOptions, |
| 8 | } from "@cursor/sdk"; |
| 9 | import { |
| 10 | type OrchestrationV2ProviderSession, |
| 11 | ProviderDriverKind, |
| 12 | type ThreadId, |
| 13 | } from "@t3tools/contracts"; |
| 14 | import * as Context from "effect/Context"; |
| 15 | import * as Deferred from "effect/Deferred"; |
| 16 | import * as Effect from "effect/Effect"; |
| 17 | import * as Layer from "effect/Layer"; |
| 18 | import * as Ref from "effect/Ref"; |
| 19 | import * as Schema from "effect/Schema"; |
| 20 | |
| 21 | import * as CursorSdk from "./CursorSdk.ts"; |
| 22 | import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; |
| 23 | |
| 24 | export const CURSOR_AGENT_SDK_PROTOCOL = "cursor-agent-sdk.local" as const; |
| 25 | export const CURSOR_PROVIDER = ProviderDriverKind.make("cursor"); |
| 26 | |
| 27 | export class CursorAgentSdkRunnerError extends Schema.TaggedError<CursorAgentSdkRunnerError>()( |
| 28 | "CursorAgentSdkRunnerError", |
| 29 | { |
| 30 | method: Schema.String, |
| 31 | cause: Schema.Defect(), |
| 32 | }, |
| 33 | ) { |
| 34 | override get message(): string { |
| 35 | return `Cursor Agent SDK ${this.method} failed.`; |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | const isCursorAgentSdkRunnerError = Schema.is(CursorAgentSdkRunnerError); |
| 40 | |
| 41 | export interface CursorAgentSdkOpenInput { |
| 42 | readonly operation: "create" | "resume"; |
| 43 | readonly agentId?: string; |
| 44 | readonly options: AgentOptions; |
| 45 | readonly threadId: ThreadId; |
| 46 | readonly providerSessionId: OrchestrationV2ProviderSession["id"]; |
| 47 | } |
| 48 | |
| 49 | export interface CursorAgentSdkSendInput<Error> { |
| 50 | readonly message: string | SDKUserMessage; |
| 51 | readonly options?: Omit<SendOptions, "onDelta">; |
| 52 | readonly onDelta?: (update: InteractionUpdate) => Effect.Effect<void, Error>; |
| 53 | } |
| 54 | |
| 55 | export interface CursorAgentSdkRun { |
| 56 | readonly runId: string; |
| 57 | readonly agentId: string; |
| 58 | readonly wait: Effect.Effect<RunResult, CursorAgentSdkRunnerError>; |
| 59 | readonly cancel: Effect.Effect<void, CursorAgentSdkRunnerError>; |
| 60 | } |
| 61 | |
| 62 | export interface CursorAgentSdkSession { |
| 63 | readonly agentId: string; |
| 64 | readonly send: <Error>( |
| 65 | input: CursorAgentSdkSendInput<Error>, |
| 66 | ) => Effect.Effect<CursorAgentSdkRun, CursorAgentSdkRunnerError>; |
| 67 | readonly listMessages: Effect.Effect<ReadonlyArray<AgentMessage>, CursorAgentSdkRunnerError>; |
| 68 | readonly close: Effect.Effect<void, CursorAgentSdkRunnerError>; |
| 69 | } |
| 70 | |
| 71 | export class CursorAgentSdkRunner extends Context.Service< |
| 72 | CursorAgentSdkRunner, |
| 73 | { |
| 74 | readonly open: ( |
| 75 | input: CursorAgentSdkOpenInput, |
| 76 | ) => Effect.Effect<CursorAgentSdkSession, CursorAgentSdkRunnerError>; |
| 77 | readonly assertComplete: Effect.Effect<void, CursorAgentSdkRunnerError>; |
| 78 | } |
| 79 | >()("@t3tools/provider-cursor/server/CursorAgentSdk/CursorAgentSdkRunner") {} |
| 80 | |
| 81 | export interface CursorAgentSdkLoggedAgentOptions { |
| 82 | readonly model?: AgentOptions["model"]; |
| 83 | readonly hasName?: boolean; |
| 84 | readonly mode?: AgentOptions["mode"]; |
| 85 | readonly local?: { |
| 86 | readonly hasCwd?: boolean; |
| 87 | readonly autoReview?: boolean; |
| 88 | readonly settingSources?: AgentOptions["local"] extends infer Local |
| 89 | ? Local extends { readonly settingSources?: infer Sources } |
| 90 | ? Sources |
| 91 | : never |
| 92 | : never; |
| 93 | readonly sandboxEnabled?: boolean; |
| 94 | readonly enableAgentRetries?: boolean; |
| 95 | readonly hasCustomTools?: boolean; |
| 96 | }; |
| 97 | readonly agents?: ReadonlyArray<string>; |
| 98 | } |
| 99 | |
| 100 | export interface CursorAgentSdkLoggedSendOptions { |
| 101 | readonly model?: SendOptions["model"]; |
| 102 | readonly mode?: SendOptions["mode"]; |
| 103 | readonly local?: { |
| 104 | readonly force?: boolean; |
| 105 | readonly hasCustomTools?: boolean; |
| 106 | }; |
| 107 | readonly idempotencyKey?: string; |
| 108 | } |
| 109 | |
| 110 | export type CursorAgentSdkProtocolLogEvent = |
| 111 | | { |
| 112 | readonly direction: "outgoing"; |
| 113 | readonly stage: "decoded"; |
| 114 | readonly payload: { |
| 115 | readonly type: "agent.open"; |
| 116 | readonly operation: CursorAgentSdkOpenInput["operation"]; |
| 117 | readonly agentId?: string; |
| 118 | readonly options: CursorAgentSdkLoggedAgentOptions; |
| 119 | }; |
| 120 | } |
| 121 | | { |
| 122 | readonly direction: "incoming"; |
| 123 | readonly stage: "decoded"; |
| 124 | readonly payload: { |
| 125 | readonly type: "agent.opened"; |
| 126 | readonly agentId: string; |
| 127 | }; |
| 128 | } |
| 129 | | { |
| 130 | readonly direction: "outgoing"; |
| 131 | readonly stage: "decoded"; |
| 132 | readonly payload: { |
| 133 | readonly type: "run.start"; |
| 134 | readonly message: string | SDKUserMessage; |
| 135 | readonly options: CursorAgentSdkLoggedSendOptions; |
| 136 | }; |
| 137 | } |
| 138 | | { |
| 139 | readonly direction: "incoming"; |
| 140 | readonly stage: "decoded"; |
| 141 | readonly payload: { |
| 142 | readonly type: "run.started"; |
| 143 | readonly runId: string; |
| 144 | readonly agentId: string; |
| 145 | }; |
| 146 | } |
| 147 | | { |
| 148 | readonly direction: "incoming"; |
| 149 | readonly stage: "decoded"; |
| 150 | readonly payload: { |
| 151 | readonly type: "interaction.update"; |
| 152 | readonly runId: string; |
| 153 | readonly update: InteractionUpdate; |
| 154 | }; |
| 155 | } |
| 156 | | { |
| 157 | readonly direction: "incoming"; |
| 158 | readonly stage: "decoded"; |
| 159 | readonly payload: { |
| 160 | readonly type: "run.completed"; |
| 161 | readonly result: RunResult; |
| 162 | }; |
| 163 | } |
| 164 | | { |
| 165 | readonly direction: "outgoing"; |
| 166 | readonly stage: "decoded"; |
| 167 | readonly payload: { |
| 168 | readonly type: "run.cancel"; |
| 169 | readonly runId: string; |
| 170 | }; |
| 171 | } |
| 172 | | { |
| 173 | readonly direction: "outgoing"; |
| 174 | readonly stage: "decoded"; |
| 175 | readonly payload: { |
| 176 | readonly type: "agent.messages.list"; |
| 177 | readonly agentId: string; |
| 178 | }; |
| 179 | } |
| 180 | | { |
| 181 | readonly direction: "incoming"; |
| 182 | readonly stage: "decoded"; |
| 183 | readonly payload: { |
| 184 | readonly type: "agent.messages"; |
| 185 | readonly agentId: string; |
| 186 | readonly messages: ReadonlyArray<AgentMessage>; |
| 187 | }; |
| 188 | } |
| 189 | | { |
| 190 | readonly direction: "outgoing"; |
| 191 | readonly stage: "decoded"; |
| 192 | readonly payload: { |
| 193 | readonly type: "agent.close"; |
| 194 | readonly agentId: string; |
| 195 | }; |
| 196 | }; |
| 197 | |
| 198 | export type CursorAgentSdkProtocolLogger = ( |
| 199 | event: CursorAgentSdkProtocolLogEvent, |
| 200 | ) => Effect.Effect<void>; |
| 201 | |
| 202 | function runnerError(cause: unknown, method: string): CursorAgentSdkRunnerError { |
| 203 | return isCursorAgentSdkRunnerError(cause) |
| 204 | ? cause |
| 205 | : new CursorAgentSdkRunnerError({ method, cause }); |
| 206 | } |
| 207 | |
| 208 | function isActiveRunConflict(cause: unknown): boolean { |
| 209 | return cause instanceof Error && /already has active run/i.test(cause.message); |
| 210 | } |
| 211 | |
| 212 | export function isCursorCancellationError(cause: unknown): boolean { |
| 213 | let current = cause; |
| 214 | const seen = new Set<object>(); |
| 215 | |
| 216 | while (typeof current === "object" && current !== null && !seen.has(current)) { |
| 217 | if (Reflect.get(current, "name") === "AbortError") { |
| 218 | return true; |
| 219 | } |
| 220 | seen.add(current); |
| 221 | current = Reflect.get(current, "cause"); |
| 222 | } |
| 223 | |
| 224 | return false; |
| 225 | } |
| 226 | |
| 227 | export function loggedCursorAgentOptions(options: AgentOptions): CursorAgentSdkLoggedAgentOptions { |
| 228 | return { |
| 229 | ...(options.model === undefined ? {} : { model: options.model }), |
| 230 | ...(options.name === undefined ? {} : { hasName: true }), |
| 231 | ...(options.mode === undefined ? {} : { mode: options.mode }), |
| 232 | ...(options.local === undefined |
| 233 | ? {} |
| 234 | : { |
| 235 | local: { |
| 236 | ...(options.local.cwd === undefined ? {} : { hasCwd: true }), |
| 237 | ...(options.local.autoReview === undefined |
| 238 | ? {} |
| 239 | : { autoReview: options.local.autoReview }), |
| 240 | ...(options.local.settingSources === undefined |
| 241 | ? {} |
| 242 | : { settingSources: options.local.settingSources }), |
| 243 | ...(options.local.sandboxOptions === undefined |
| 244 | ? {} |
| 245 | : { sandboxEnabled: options.local.sandboxOptions.enabled }), |
| 246 | ...(options.local.enableAgentRetries === undefined |
| 247 | ? {} |
| 248 | : { enableAgentRetries: options.local.enableAgentRetries }), |
| 249 | ...(options.local.customTools === undefined ? {} : { hasCustomTools: true }), |
| 250 | }, |
| 251 | }), |
| 252 | ...(options.agents === undefined ? {} : { agents: Object.keys(options.agents).toSorted() }), |
| 253 | }; |
| 254 | } |
| 255 | |
| 256 | export function loggedCursorSendOptions( |
| 257 | options: Omit<SendOptions, "onDelta"> | undefined, |
| 258 | ): CursorAgentSdkLoggedSendOptions { |
| 259 | if (options === undefined) { |
| 260 | return {}; |
| 261 | } |
| 262 | return { |
| 263 | ...(options.model === undefined ? {} : { model: options.model }), |
| 264 | ...(options.mode === undefined ? {} : { mode: options.mode }), |
| 265 | ...(options.local === undefined |
| 266 | ? {} |
| 267 | : { |
| 268 | local: { |
| 269 | ...(options.local.force === undefined ? {} : { force: options.local.force }), |
| 270 | ...(options.local.customTools === undefined ? {} : { hasCustomTools: true }), |
| 271 | }, |
| 272 | }), |
| 273 | ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }), |
| 274 | }; |
| 275 | } |
| 276 | |
| 277 | function makeCursorAgentSdkProtocolLogger(input: { |
| 278 | readonly nativeEventLogger: ProviderEventLoggers.EventNdjsonLogger | undefined; |
| 279 | readonly threadId: ThreadId; |
| 280 | readonly providerSessionId: OrchestrationV2ProviderSession["id"]; |
| 281 | }): CursorAgentSdkProtocolLogger | undefined { |
| 282 | if (input.nativeEventLogger === undefined) { |
| 283 | return undefined; |
| 284 | } |
| 285 | const nativeEventLogger = input.nativeEventLogger; |
| 286 | return (event) => |
| 287 | nativeEventLogger |
| 288 | .write( |
| 289 | { |
| 290 | provider: CURSOR_PROVIDER, |
| 291 | protocol: CURSOR_AGENT_SDK_PROTOCOL, |
| 292 | kind: "protocol", |
| 293 | providerSessionId: input.providerSessionId, |
| 294 | event, |
| 295 | }, |
| 296 | input.threadId, |
| 297 | ) |
| 298 | .pipe(Effect.ignore); |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * Runs agents through the Cursor SDK, logging every frame to the protocol |
| 303 | * logger chosen for each opened agent. The live layer writes the native |
| 304 | * provider event log; the replay recorder turns the same frames into a |
| 305 | * transcript. |
| 306 | */ |
| 307 | export const makeCursorAgentSdkRunner = Effect.fn("makeCursorAgentSdkRunner")(function* ( |
| 308 | protocolLoggerFor: (input: CursorAgentSdkOpenInput) => CursorAgentSdkProtocolLogger | undefined, |
| 309 | ) { |
| 310 | const { Agent, createAgentPlatform } = yield* CursorSdk.CursorSdk; |
| 311 | |
| 312 | /** |
| 313 | * The Cursor SDK decides once per process whether local sandboxing works, and |
| 314 | * caches the answer the first time any run starts. Only sandboxed runs point |
| 315 | * it at its `cursorsandbox` helper first, so after an unsandboxed (Full access) |
| 316 | * run it caches "unsupported" and rejects every later sandboxed run until the |
| 317 | * server restarts. Warming a bare sandboxed executor before the first |
| 318 | * unsandboxed agent opens lets the SDK find the helper and cache the real |
| 319 | * answer. Warming is best effort: on a machine without sandbox support it |
| 320 | * fails, the SDK caches "unsupported", and sandboxed runs report that as |
| 321 | * before. |
| 322 | */ |
| 323 | const sandboxSupportPrimeStarted = yield* Ref.make(false); |
| 324 | const sandboxSupportPrimed = yield* Deferred.make<void>(); |
| 325 | const primeCursorSandboxSupport = Effect.fn("CursorAgentSdkRunner.primeSandboxSupport")( |
| 326 | function* (options: AgentOptions) { |
| 327 | if (yield* Ref.getAndSet(sandboxSupportPrimeStarted, true)) { |
| 328 | return yield* Deferred.await(sandboxSupportPrimed); |
| 329 | } |
| 330 | yield* Effect.tryPromise(async () => { |
| 331 | const cwd = typeof options.local?.cwd === "string" ? options.local.cwd : undefined; |
| 332 | const platform = await createAgentPlatform(cwd === undefined ? {} : { workspaceRef: cwd }); |
| 333 | const release = await platform.prewarmLocalWorkspace({ |
| 334 | ...(options.apiKey === undefined ? {} : { apiKey: options.apiKey }), |
| 335 | local: { |
| 336 | ...(cwd === undefined ? {} : { cwd }), |
| 337 | settingSources: [], |
| 338 | sandboxOptions: { enabled: true }, |
| 339 | }, |
| 340 | }); |
| 341 | await release(); |
| 342 | }).pipe(Effect.ignore, Effect.ensuring(Deferred.succeed(sandboxSupportPrimed, undefined))); |
| 343 | }, |
| 344 | ); |
| 345 | |
| 346 | return CursorAgentSdkRunner.of({ |
| 347 | open: Effect.fn("CursorAgentSdkRunner.open")(function* (input) { |
| 348 | if (input.options.local?.sandboxOptions?.enabled === false) { |
| 349 | yield* primeCursorSandboxSupport(input.options); |
| 350 | } |
| 351 | const protocolLogger = protocolLoggerFor(input); |
| 352 | const log = (event: CursorAgentSdkProtocolLogEvent) => |
| 353 | protocolLogger === undefined ? Effect.void : protocolLogger(event); |
| 354 | |
| 355 | yield* log({ |
| 356 | direction: "outgoing", |
| 357 | stage: "decoded", |
| 358 | payload: { |
| 359 | type: "agent.open", |
| 360 | operation: input.operation, |
| 361 | ...(input.agentId === undefined ? {} : { agentId: input.agentId }), |
| 362 | options: loggedCursorAgentOptions(input.options), |
| 363 | }, |
| 364 | }); |
| 365 | |
| 366 | const agent = yield* Effect.tryPromise({ |
| 367 | try: () => |
| 368 | input.operation === "create" |
| 369 | ? Agent.create(input.options) |
| 370 | : Agent.resume(input.agentId!, input.options), |
| 371 | catch: (cause) => runnerError(cause, `agent.${input.operation}`), |
| 372 | }); |
| 373 | |
| 374 | yield* log({ |
| 375 | direction: "incoming", |
| 376 | stage: "decoded", |
| 377 | payload: { |
| 378 | type: "agent.opened", |
| 379 | agentId: agent.agentId, |
| 380 | }, |
| 381 | }); |
| 382 | |
| 383 | const cwd = |
| 384 | typeof input.options.local?.cwd === "string" |
| 385 | ? input.options.local.cwd |
| 386 | : input.options.local?.cwd?.[0]; |
| 387 | const runOptions = { |
| 388 | runtime: "local" as const, |
| 389 | ...(cwd === undefined ? {} : { cwd }), |
| 390 | ...(input.options.local?.store === undefined ? {} : { store: input.options.local.store }), |
| 391 | }; |
| 392 | let abandonedRunRecoveryAvailable = input.operation === "resume"; |
| 393 | |
| 394 | return { |
| 395 | agentId: agent.agentId, |
| 396 | send: Effect.fn("CursorAgentSdkSession.send")(function* (sendInput) { |
| 397 | const context = yield* Effect.context(); |
| 398 | yield* log({ |
| 399 | direction: "outgoing", |
| 400 | stage: "decoded", |
| 401 | payload: { |
| 402 | type: "run.start", |
| 403 | message: sendInput.message, |
| 404 | options: loggedCursorSendOptions(sendInput.options), |
| 405 | }, |
| 406 | }); |
| 407 | |
| 408 | let callbacksReady = false; |
| 409 | const pendingUpdates: Array<InteractionUpdate> = []; |
| 410 | let callbackFailure: { readonly cause: unknown } | undefined; |
| 411 | let callbackChain = Promise.resolve(); |
| 412 | let runId = ""; |
| 413 | const dispatchUpdate = (update: InteractionUpdate): Promise<void> => { |
| 414 | callbackChain = callbackChain |
| 415 | .then(() => { |
| 416 | if (callbackFailure !== undefined) { |
| 417 | return; |
| 418 | } |
| 419 | return Effect.runPromiseWith(context)( |
| 420 | log({ |
| 421 | direction: "incoming", |
| 422 | stage: "decoded", |
| 423 | payload: { |
| 424 | type: "interaction.update", |
| 425 | runId, |
| 426 | update, |
| 427 | }, |
| 428 | }).pipe(Effect.andThen(sendInput.onDelta?.(update) ?? Effect.void)), |
| 429 | ); |
| 430 | }) |
| 431 | .catch((cause) => { |
| 432 | callbackFailure ??= { cause }; |
| 433 | }); |
| 434 | return callbackChain; |
| 435 | }; |
| 436 | |
| 437 | const startRun = () => |
| 438 | Effect.tryPromise({ |
| 439 | try: () => |
| 440 | agent.send(sendInput.message, { |
| 441 | ...sendInput.options, |
| 442 | onDelta: async ({ update }) => { |
| 443 | if (!callbacksReady) { |
| 444 | pendingUpdates.push(update); |
| 445 | return; |
| 446 | } |
| 447 | await dispatchUpdate(update); |
| 448 | }, |
| 449 | }), |
| 450 | catch: (cause) => runnerError(cause, "run.start"), |
| 451 | }); |
| 452 | const run = yield* startRun().pipe( |
| 453 | Effect.catchIf( |
| 454 | (error) => abandonedRunRecoveryAvailable && isActiveRunConflict(error.cause), |
| 455 | (error) => |
| 456 | Effect.gen(function* () { |
| 457 | abandonedRunRecoveryAvailable = false; |
| 458 | const latestRun = yield* Effect.tryPromise({ |
| 459 | try: () => Agent.listRuns(agent.agentId, { ...runOptions, limit: 1 }), |
| 460 | catch: (cause) => runnerError(cause, "agent.listRuns"), |
| 461 | }); |
| 462 | const activeRun = latestRun.items.find( |
| 463 | (candidate) => candidate.status === "running", |
| 464 | ); |
| 465 | if (activeRun === undefined) { |
| 466 | return yield* error; |
| 467 | } |
| 468 | yield* log({ |
| 469 | direction: "outgoing", |
| 470 | stage: "decoded", |
| 471 | payload: { type: "run.cancel", runId: activeRun.id }, |
| 472 | }); |
| 473 | yield* Effect.tryPromise({ |
| 474 | try: () => Agent.cancelRun(activeRun.id, runOptions), |
| 475 | catch: (cause) => runnerError(cause, "agent.cancelRun"), |
| 476 | }); |
| 477 | return yield* startRun(); |
| 478 | }), |
| 479 | ), |
| 480 | ); |
| 481 | abandonedRunRecoveryAvailable = false; |
| 482 | runId = run.id; |
| 483 | yield* log({ |
| 484 | direction: "incoming", |
| 485 | stage: "decoded", |
| 486 | payload: { |
| 487 | type: "run.started", |
| 488 | runId: run.id, |
| 489 | agentId: run.agentId, |
| 490 | }, |
| 491 | }); |
| 492 | callbacksReady = true; |
| 493 | for (const update of pendingUpdates) { |
| 494 | yield* Effect.tryPromise({ |
| 495 | try: () => dispatchUpdate(update), |
| 496 | catch: (cause) => runnerError(cause, "run.onDelta"), |
| 497 | }); |
| 498 | } |
| 499 | |
| 500 | return { |
| 501 | runId: run.id, |
| 502 | agentId: run.agentId, |
| 503 | wait: Effect.tryPromise({ |
| 504 | try: async () => { |
| 505 | const result = await run.wait(); |
| 506 | await callbackChain; |
| 507 | if (callbackFailure !== undefined) { |
| 508 | throw callbackFailure.cause; |
| 509 | } |
| 510 | return result; |
| 511 | }, |
| 512 | catch: (cause) => runnerError(cause, "run.wait"), |
| 513 | }).pipe( |
| 514 | Effect.tap((result) => |
| 515 | log({ |
| 516 | direction: "incoming", |
| 517 | stage: "decoded", |
| 518 | payload: { |
| 519 | type: "run.completed", |
| 520 | result, |
| 521 | }, |
| 522 | }), |
| 523 | ), |
| 524 | ), |
| 525 | cancel: log({ |
| 526 | direction: "outgoing", |
| 527 | stage: "decoded", |
| 528 | payload: { |
| 529 | type: "run.cancel", |
| 530 | runId: run.id, |
| 531 | }, |
| 532 | }).pipe( |
| 533 | Effect.andThen( |
| 534 | Effect.tryPromise({ |
| 535 | try: async () => { |
| 536 | try { |
| 537 | await run.cancel(); |
| 538 | } catch (cause) { |
| 539 | if (!isCursorCancellationError(cause)) { |
| 540 | throw cause; |
| 541 | } |
| 542 | } |
| 543 | }, |
| 544 | catch: (cause) => runnerError(cause, "run.cancel"), |
| 545 | }), |
| 546 | ), |
| 547 | ), |
| 548 | } satisfies CursorAgentSdkRun; |
| 549 | }), |
| 550 | listMessages: log({ |
| 551 | direction: "outgoing", |
| 552 | stage: "decoded", |
| 553 | payload: { |
| 554 | type: "agent.messages.list", |
| 555 | agentId: agent.agentId, |
| 556 | }, |
| 557 | }).pipe( |
| 558 | Effect.andThen( |
| 559 | Effect.tryPromise({ |
| 560 | try: () => |
| 561 | Agent.messages.list(agent.agentId, { |
| 562 | runtime: "local", |
| 563 | ...(cwd === undefined ? {} : { cwd }), |
| 564 | }), |
| 565 | catch: (cause) => runnerError(cause, "agent.messages.list"), |
| 566 | }), |
| 567 | ), |
| 568 | Effect.tap((messages) => |
| 569 | log({ |
| 570 | direction: "incoming", |
| 571 | stage: "decoded", |
| 572 | payload: { |
| 573 | type: "agent.messages", |
| 574 | agentId: agent.agentId, |
| 575 | messages, |
| 576 | }, |
| 577 | }), |
| 578 | ), |
| 579 | ), |
| 580 | close: log({ |
| 581 | direction: "outgoing", |
| 582 | stage: "decoded", |
| 583 | payload: { |
| 584 | type: "agent.close", |
| 585 | agentId: agent.agentId, |
| 586 | }, |
| 587 | }).pipe( |
| 588 | Effect.andThen( |
| 589 | Effect.try({ |
| 590 | try: () => agent.close(), |
| 591 | catch: (cause) => runnerError(cause, "agent.close"), |
| 592 | }), |
| 593 | ), |
| 594 | ), |
| 595 | } satisfies CursorAgentSdkSession; |
| 596 | }), |
| 597 | assertComplete: Effect.void, |
| 598 | }); |
| 599 | }); |
| 600 | |
| 601 | export const layer = Layer.effect( |
| 602 | CursorAgentSdkRunner, |
| 603 | Effect.gen(function* () { |
| 604 | const { native: nativeEventLogger } = yield* ProviderEventLoggers.ProviderEventLoggers; |
| 605 | return yield* makeCursorAgentSdkRunner((input) => |
| 606 | makeCursorAgentSdkProtocolLogger({ |
| 607 | nativeEventLogger, |
| 608 | threadId: input.threadId, |
| 609 | providerSessionId: input.providerSessionId, |
| 610 | }), |
| 611 | ); |
| 612 | }), |
| 613 | ); |