1import type {
2 AgentMessage,
3 AgentOptions,
4 InteractionUpdate,
5 Run,
6 RunResult,
7 SDKUserMessage,
8 SendOptions,
9 SteerAckOutcome,
10} from "@cursor/sdk";
11import {
12 type OrchestrationV2ProviderSession,
13 ProviderDriverKind,
14 type ThreadId,
15} from "@t3tools/contracts";
16import * as Context from "effect/Context";
17import * as Deferred from "effect/Deferred";
18import * as Effect from "effect/Effect";
19import * as Layer from "effect/Layer";
20import * as Ref from "effect/Ref";
21import * as Schema from "effect/Schema";
22
23import * as CursorSdk from "./CursorSdk.ts";
24import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
25
26export const CURSOR_AGENT_SDK_PROTOCOL = "cursor-agent-sdk.local" as const;
27export const CURSOR_PROVIDER = ProviderDriverKind.make("cursor");
28
29export class CursorAgentSdkRunnerError extends Schema.TaggedError<CursorAgentSdkRunnerError>()(
30 "CursorAgentSdkRunnerError",
31 {
32 method: Schema.String,
33 cause: Schema.Defect(),
34 },
35) {
36 override get message(): string {
37 return `Cursor Agent SDK ${this.method} failed.`;
38 }
39}
40
41export function cursorSdkFailureMessage(error: CursorAgentSdkRunnerError): string {
42 const reason = sdkErrorReason(error.cause);
43 return reason === undefined
44 ? error.message
45 : `Cursor Agent SDK ${error.method} failed: ${reason}`;
46}
47
48// The SDK maps backend details to codes like PRO_USER_USAGE_LIMIT or RATE_LIMITED, without a status.
49const CURSOR_USAGE_LIMIT_CODE =
50 /^(?:resource_exhausted|rate_limited(?:_changeable)?|\w*_usage_limit|\w*_rate_limit(?:_exceeded)?)$/i;
51
52export function isCursorUsageLimitError(cause: unknown): boolean {
53 const error = isCursorAgentSdkRunnerError(cause) ? cause.cause : cause;
54 if (typeof error !== "object" || error === null) return false;
55 const code = Reflect.get(error, "code");
56 return (
57 Reflect.get(error, "name") === "RateLimitError" ||
58 Reflect.get(error, "status") === 429 ||
59 (typeof code === "string" && CURSOR_USAGE_LIMIT_CODE.test(code))
60 );
61}
62
63const GENERIC_SDK_ERROR_CODES = new Set(["", "error", "unknown"]);
64
65function sdkErrorReason(cause: unknown): string | undefined {
66 if (!(cause instanceof Error)) return undefined;
67 const message = cause.message.trim();
68 if (message.length === 0) return undefined;
69 const code = Reflect.get(cause, "code");
70 const status = Reflect.get(cause, "status");
71 const details = [
72 ...(typeof code === "string" && !GENERIC_SDK_ERROR_CODES.has(code) && !message.includes(code)
73 ? [code]
74 : []),
75 ...(typeof status === "number" ? [`HTTP ${status}`] : []),
76 ];
77 return details.length === 0 ? message : `${message} (${details.join(", ")})`;
78}
79
80export const isCursorAgentSdkRunnerError = Schema.is(CursorAgentSdkRunnerError);
81
82export interface CursorAgentSdkOpenInput {
83 readonly operation: "create" | "resume";
84 readonly agentId?: string;
85 readonly storeCwd?: string;
86 readonly options: AgentOptions;
87 readonly threadId: ThreadId;
88 readonly providerSessionId: OrchestrationV2ProviderSession["id"];
89}
90
91export interface CursorAgentSdkSendInput<Error> {
92 readonly message: string | SDKUserMessage;
93 readonly options?: Omit<SendOptions, "onDelta">;
94 readonly onDelta?: (update: InteractionUpdate) => Effect.Effect<void, Error>;
95 /** Runs `agent.summarize()` instead, which takes no message or options. */
96 readonly summarize?: boolean;
97}
98
99export interface CursorAgentSdkRun {
100 readonly runId: string;
101 readonly agentId: string;
102 readonly wait: Effect.Effect<RunResult, CursorAgentSdkRunnerError>;
103 readonly cancel: Effect.Effect<void, CursorAgentSdkRunnerError>;
104 /** Absent on test doubles that never steer. The live runner always sets it. */
105 readonly steer?: (text: string) => Effect.Effect<SteerAckOutcome, CursorAgentSdkRunnerError>;
106}
107
108export interface CursorAgentSdkSession {
109 readonly agentId: string;
110 readonly send: <Error>(
111 input: CursorAgentSdkSendInput<Error>,
112 ) => Effect.Effect<CursorAgentSdkRun, CursorAgentSdkRunnerError>;
113 readonly listMessages: Effect.Effect<ReadonlyArray<AgentMessage>, CursorAgentSdkRunnerError>;
114 readonly close: Effect.Effect<void, CursorAgentSdkRunnerError>;
115}
116
117export class CursorAgentSdkRunner extends Context.Service<
118 CursorAgentSdkRunner,
119 {
120 readonly open: (
121 input: CursorAgentSdkOpenInput,
122 ) => Effect.Effect<CursorAgentSdkSession, CursorAgentSdkRunnerError>;
123 readonly assertComplete: Effect.Effect<void, CursorAgentSdkRunnerError>;
124 }
125>()("@t3tools/provider-cursor/server/CursorAgentSdk/CursorAgentSdkRunner") {}
126
127export interface CursorAgentSdkLoggedAgentOptions {
128 readonly model?: AgentOptions["model"];
129 readonly hasName?: boolean;
130 readonly mode?: AgentOptions["mode"];
131 readonly local?: {
132 readonly hasCwd?: boolean;
133 readonly autoReview?: boolean;
134 readonly settingSources?: AgentOptions["local"] extends infer Local
135 ? Local extends { readonly settingSources?: infer Sources }
136 ? Sources
137 : never
138 : never;
139 readonly sandboxEnabled?: boolean;
140 readonly enableAgentRetries?: boolean;
141 readonly hasCustomTools?: boolean;
142 };
143 readonly agents?: ReadonlyArray<string>;
144}
145
146export interface CursorAgentSdkLoggedSendOptions {
147 readonly model?: SendOptions["model"];
148 readonly mode?: SendOptions["mode"];
149 readonly hasMcpServers?: boolean;
150 readonly mcpServerNames?: ReadonlyArray<string>;
151 readonly local?: {
152 readonly force?: boolean;
153 readonly hasCustomTools?: boolean;
154 };
155 readonly idempotencyKey?: string;
156}
157
158export type CursorAgentSdkProtocolLogEvent =
159 | {
160 readonly direction: "outgoing";
161 readonly stage: "decoded";
162 readonly payload: {
163 readonly type: "agent.open";
164 readonly operation: CursorAgentSdkOpenInput["operation"];
165 readonly agentId?: string;
166 readonly options: CursorAgentSdkLoggedAgentOptions;
167 };
168 }
169 | {
170 readonly direction: "incoming";
171 readonly stage: "decoded";
172 readonly payload: {
173 readonly type: "agent.opened";
174 readonly agentId: string;
175 };
176 }
177 | {
178 readonly direction: "outgoing";
179 readonly stage: "decoded";
180 readonly payload: {
181 readonly type: "run.start";
182 readonly message: string | SDKUserMessage;
183 readonly options: CursorAgentSdkLoggedSendOptions;
184 };
185 }
186 | {
187 readonly direction: "incoming";
188 readonly stage: "decoded";
189 readonly payload: {
190 readonly type: "run.started";
191 readonly runId: string;
192 readonly agentId: string;
193 };
194 }
195 | {
196 readonly direction: "incoming";
197 readonly stage: "decoded";
198 readonly payload: {
199 readonly type: "interaction.update";
200 readonly runId: string;
201 readonly update: InteractionUpdate;
202 };
203 }
204 | {
205 readonly direction: "incoming";
206 readonly stage: "decoded";
207 readonly payload: {
208 readonly type: "run.completed";
209 readonly result: RunResult;
210 };
211 }
212 | {
213 readonly direction: "outgoing";
214 readonly stage: "decoded";
215 readonly payload: {
216 readonly type: "run.steer";
217 readonly runId: string;
218 readonly text: string;
219 };
220 }
221 | {
222 readonly direction: "incoming";
223 readonly stage: "decoded";
224 readonly payload: {
225 readonly type: "run.steered";
226 readonly runId: string;
227 readonly outcome: SteerAckOutcome;
228 };
229 }
230 | {
231 readonly direction: "outgoing";
232 readonly stage: "decoded";
233 readonly payload: {
234 readonly type: "run.cancel";
235 readonly runId: string;
236 };
237 }
238 | {
239 readonly direction: "outgoing";
240 readonly stage: "decoded";
241 readonly payload: {
242 readonly type: "agent.messages.list";
243 readonly agentId: string;
244 };
245 }
246 | {
247 readonly direction: "incoming";
248 readonly stage: "decoded";
249 readonly payload: {
250 readonly type: "agent.messages";
251 readonly agentId: string;
252 readonly messages: ReadonlyArray<AgentMessage>;
253 };
254 }
255 | {
256 readonly direction: "outgoing";
257 readonly stage: "decoded";
258 readonly payload: {
259 readonly type: "agent.close";
260 readonly agentId: string;
261 };
262 };
263
264export type CursorAgentSdkProtocolLogger = (
265 event: CursorAgentSdkProtocolLogEvent,
266) => Effect.Effect<void>;
267
268function runnerError(cause: unknown, method: string): CursorAgentSdkRunnerError {
269 return isCursorAgentSdkRunnerError(cause)
270 ? cause
271 : new CursorAgentSdkRunnerError({ method, cause });
272}
273
274function isActiveRunConflict(cause: unknown): boolean {
275 return cause instanceof Error && /already has active run/i.test(cause.message);
276}
277
278export function isCursorCancellationError(cause: unknown): boolean {
279 let current = cause;
280 const seen = new Set<object>();
281
282 while (typeof current === "object" && current !== null && !seen.has(current)) {
283 if (Reflect.get(current, "name") === "AbortError") {
284 return true;
285 }
286 seen.add(current);
287 current = Reflect.get(current, "cause");
288 }
289
290 return false;
291}
292
293export function loggedCursorAgentOptions(options: AgentOptions): CursorAgentSdkLoggedAgentOptions {
294 return {
295 ...(options.model === undefined ? {} : { model: options.model }),
296 ...(options.name === undefined ? {} : { hasName: true }),
297 ...(options.mode === undefined ? {} : { mode: options.mode }),
298 ...(options.local === undefined
299 ? {}
300 : {
301 local: {
302 ...(options.local.cwd === undefined ? {} : { hasCwd: true }),
303 ...(options.local.autoReview === undefined
304 ? {}
305 : { autoReview: options.local.autoReview }),
306 ...(options.local.settingSources === undefined
307 ? {}
308 : { settingSources: options.local.settingSources }),
309 ...(options.local.sandboxOptions === undefined
310 ? {}
311 : { sandboxEnabled: options.local.sandboxOptions.enabled }),
312 ...(options.local.enableAgentRetries === undefined
313 ? {}
314 : { enableAgentRetries: options.local.enableAgentRetries }),
315 ...(options.local.customTools === undefined ? {} : { hasCustomTools: true }),
316 },
317 }),
318 ...(options.agents === undefined ? {} : { agents: Object.keys(options.agents).toSorted() }),
319 };
320}
321
322export function loggedCursorSendOptions(
323 options: Omit<SendOptions, "onDelta"> | undefined,
324): CursorAgentSdkLoggedSendOptions {
325 if (options === undefined) {
326 return {};
327 }
328 const mcpServerNames =
329 options.mcpServers === undefined ? undefined : Object.keys(options.mcpServers).toSorted();
330 return {
331 ...(options.model === undefined ? {} : { model: options.model }),
332 ...(options.mode === undefined ? {} : { mode: options.mode }),
333 ...(mcpServerNames === undefined ? {} : { hasMcpServers: true, mcpServerNames }),
334 ...(options.local === undefined
335 ? {}
336 : {
337 local: {
338 ...(options.local.force === undefined ? {} : { force: options.local.force }),
339 ...(options.local.customTools === undefined ? {} : { hasCustomTools: true }),
340 },
341 }),
342 ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),
343 };
344}
345
346function makeCursorAgentSdkProtocolLogger(input: {
347 readonly nativeEventLogger: ProviderEventLoggers.EventNdjsonLogger | undefined;
348 readonly threadId: ThreadId;
349 readonly providerSessionId: OrchestrationV2ProviderSession["id"];
350}): CursorAgentSdkProtocolLogger | undefined {
351 if (input.nativeEventLogger === undefined) {
352 return undefined;
353 }
354 const nativeEventLogger = input.nativeEventLogger;
355 return (event) =>
356 nativeEventLogger
357 .write(
358 {
359 provider: CURSOR_PROVIDER,
360 protocol: CURSOR_AGENT_SDK_PROTOCOL,
361 kind: "protocol",
362 providerSessionId: input.providerSessionId,
363 event,
364 },
365 input.threadId,
366 )
367 .pipe(Effect.ignore);
368}
369
370interface CursorShellStreamOutput {
371 readonly conversationId?: unknown;
372 readonly toolCallId?: unknown;
373 readonly event: {
374 readonly case: "stdout" | "stderr";
375 readonly value: { readonly data: unknown };
376 };
377}
378
379// The SDK reports shell-output-delta only for user-started shell commands, so
380// patches/@cursor__sdk@1.2.0.patch publishes agent shell output on this hook.
381const SHELL_STREAM_OUTPUT_HOOK = Symbol.for("@cursor/sdk.shellStreamOutput");
382const shellStreamListeners = new Set<(output: CursorShellStreamOutput) => void>();
383
384function subscribeShellStreamOutput(listener: (output: CursorShellStreamOutput) => void) {
385 const hooks = globalThis as Record<symbol, unknown>;
386 hooks[SHELL_STREAM_OUTPUT_HOOK] ??= (output: CursorShellStreamOutput) => {
387 for (const notify of shellStreamListeners) notify(output);
388 };
389 shellStreamListeners.add(listener);
390 return () => {
391 shellStreamListeners.delete(listener);
392 };
393}
394
395export const makeCursorAgentSdkRunner = Effect.fn("makeCursorAgentSdkRunner")(function* (
396 protocolLoggerFor: (input: CursorAgentSdkOpenInput) => CursorAgentSdkProtocolLogger | undefined,
397) {
398 const sdk = yield* CursorSdk.CursorSdk;
399
400 // Prime with cursorsandbox before any Full access run; the SDK caches sandbox support per process.
401 const sandboxSupportPrimeStarted = yield* Ref.make(false);
402 const sandboxSupportPrimed = yield* Deferred.make<void>();
403 const primeCursorSandboxSupport = Effect.fn("CursorAgentSdkRunner.primeSandboxSupport")(
404 function* (options: AgentOptions) {
405 if (yield* Ref.getAndSet(sandboxSupportPrimeStarted, true)) {
406 return yield* Deferred.await(sandboxSupportPrimed);
407 }
408 yield* Effect.tryPromise({
409 try: async () => {
410 const cwd = typeof options.local?.cwd === "string" ? options.local.cwd : undefined;
411 const platform = await sdk
412 .load()
413 .createAgentPlatform(cwd === undefined ? {} : { workspaceRef: cwd });
414 const release = await platform.prewarmLocalWorkspace({
415 ...(options.apiKey === undefined ? {} : { apiKey: options.apiKey }),
416 local: {
417 ...(cwd === undefined ? {} : { cwd }),
418 settingSources: [],
419 sandboxOptions: { enabled: true },
420 },
421 });
422 await release();
423 },
424 catch: (cause) => runnerError(cause, "prewarmLocalWorkspace"),
425 }).pipe(Effect.ignore, Effect.ensuring(Deferred.succeed(sandboxSupportPrimed, undefined)));
426 },
427 );
428
429 return CursorAgentSdkRunner.of({
430 open: Effect.fn("CursorAgentSdkRunner.open")(function* (input) {
431 const { Agent, createAgentPlatform } = yield* Effect.try({
432 try: sdk.load,
433 catch: (cause) => runnerError(cause, `agent.${input.operation}`),
434 });
435 if (input.options.local?.sandboxOptions?.enabled === false) {
436 yield* primeCursorSandboxSupport(input.options);
437 }
438 const protocolLogger = protocolLoggerFor(input);
439 const log = (event: CursorAgentSdkProtocolLogEvent) =>
440 protocolLogger === undefined ? Effect.void : protocolLogger(event);
441
442 yield* log({
443 direction: "outgoing",
444 stage: "decoded",
445 payload: {
446 type: "agent.open",
447 operation: input.operation,
448 ...(input.agentId === undefined ? {} : { agentId: input.agentId }),
449 options: loggedCursorAgentOptions(input.options),
450 },
451 });
452
453 // Cursor uses cwd for both execution and store lookup; a moved thread keeps its original store.
454 const storeCwd = input.storeCwd;
455 const storePlatform =
456 input.operation === "resume" &&
457 storeCwd !== undefined &&
458 storeCwd !== input.options.local?.cwd
459 ? yield* Effect.tryPromise({
460 try: () =>
461 createAgentPlatform({
462 workspaceRef: storeCwd,
463 ...(input.options.local?.store === undefined
464 ? {}
465 : { localStore: input.options.local.store }),
466 }),
467 catch: (cause) => runnerError(cause, "agent.resume"),
468 })
469 : undefined;
470 const agent = yield* Effect.tryPromise({
471 try: () =>
472 input.operation === "create"
473 ? Agent.create(input.options)
474 : storePlatform === undefined
475 ? Agent.resume(input.agentId!, input.options)
476 : storePlatform.resumeAgent(input.agentId!, input.options),
477 catch: (cause) => runnerError(cause, `agent.${input.operation}`),
478 });
479
480 yield* log({
481 direction: "incoming",
482 stage: "decoded",
483 payload: {
484 type: "agent.opened",
485 agentId: agent.agentId,
486 },
487 });
488
489 const cwd =
490 typeof input.options.local?.cwd === "string"
491 ? input.options.local.cwd
492 : input.options.local?.cwd?.[0];
493 const runOptions = {
494 runtime: "local" as const,
495 ...(cwd === undefined ? {} : { cwd }),
496 ...(input.options.local?.store === undefined ? {} : { store: input.options.local.store }),
497 };
498 let abandonedRunRecoveryAvailable = input.operation === "resume";
499
500 return {
501 agentId: agent.agentId,
502 send: Effect.fn("CursorAgentSdkSession.send")(function* (sendInput) {
503 const context = yield* Effect.context();
504 yield* log({
505 direction: "outgoing",
506 stage: "decoded",
507 payload: {
508 type: "run.start",
509 message: sendInput.message,
510 options: loggedCursorSendOptions(sendInput.options),
511 },
512 });
513
514 let callbacksReady = false;
515 const pendingUpdates: Array<InteractionUpdate> = [];
516 let callbackFailure: { readonly cause: unknown } | undefined;
517 let callbackChain = Promise.resolve();
518 let runId = "";
519 const dispatchUpdate = (update: InteractionUpdate): Promise<void> => {
520 callbackChain = callbackChain
521 .then(() => {
522 if (callbackFailure !== undefined) {
523 return;
524 }
525 return Effect.runPromiseWith(context)(
526 log({
527 direction: "incoming",
528 stage: "decoded",
529 payload: {
530 type: "interaction.update",
531 runId,
532 update,
533 },
534 }).pipe(Effect.andThen(sendInput.onDelta?.(update) ?? Effect.void)),
535 );
536 })
537 .catch((cause) => {
538 callbackFailure ??= { cause };
539 });
540 return callbackChain;
541 };
542
543 const startRun = () =>
544 Effect.tryPromise({
545 try: () =>
546 sendInput.summarize === true
547 ? agent.summarize()
548 : agent.send(sendInput.message, {
549 ...sendInput.options,
550 onDelta: async ({ update }) => {
551 if (!callbacksReady) {
552 pendingUpdates.push(update);
553 return;
554 }
555 await dispatchUpdate(update);
556 },
557 }),
558 catch: (cause) => runnerError(cause, "run.start"),
559 });
560 const run = yield* startRun().pipe(
561 Effect.catchIf(
562 (error) => abandonedRunRecoveryAvailable && isActiveRunConflict(error.cause),
563 (error) =>
564 Effect.gen(function* () {
565 abandonedRunRecoveryAvailable = false;
566 // Local stores list runs oldest first, so the active run is on the
567 // last page and every page has to be read to reach it.
568 let activeRun: Run | undefined;
569 let cursor: string | undefined;
570 do {
571 const page = yield* Effect.tryPromise({
572 try: () =>
573 storePlatform === undefined
574 ? Agent.listRuns(agent.agentId, {
575 ...runOptions,
576 ...(cursor === undefined ? {} : { cursor }),
577 })
578 : storePlatform.listRuns(
579 agent.agentId,
580 cursor === undefined ? {} : { cursor },
581 ),
582 catch: (cause) => runnerError(cause, "agent.listRuns"),
583 });
584 activeRun =
585 page.items.findLast((candidate) => candidate.status === "running") ??
586 activeRun;
587 cursor = page.nextCursor;
588 } while (cursor !== undefined);
589 if (activeRun === undefined) {
590 return yield* error;
591 }
592 yield* log({
593 direction: "outgoing",
594 stage: "decoded",
595 payload: { type: "run.cancel", runId: activeRun.id },
596 });
597 yield* Effect.tryPromise({
598 try: () =>
599 storePlatform === undefined
600 ? Agent.cancelRun(activeRun.id, runOptions)
601 : storePlatform.cancelRun(activeRun.id),
602 catch: (cause) => runnerError(cause, "agent.cancelRun"),
603 });
604 return yield* startRun();
605 }),
606 ),
607 );
608 abandonedRunRecoveryAvailable = false;
609 runId = run.id;
610 yield* log({
611 direction: "incoming",
612 stage: "decoded",
613 payload: {
614 type: "run.started",
615 runId: run.id,
616 agentId: run.agentId,
617 },
618 });
619 callbacksReady = true;
620 const unsubscribeShellOutput = subscribeShellStreamOutput((output) => {
621 if (output.conversationId !== agent.agentId || typeof output.toolCallId !== "string") {
622 return;
623 }
624 void dispatchUpdate({
625 type: "shell-output-delta",
626 event: { ...output.event, toolCallId: output.toolCallId },
627 });
628 });
629 for (const update of pendingUpdates) {
630 yield* Effect.tryPromise({
631 try: () => dispatchUpdate(update),
632 catch: (cause) => runnerError(cause, "run.onDelta"),
633 });
634 }
635
636 return {
637 runId: run.id,
638 agentId: run.agentId,
639 wait: Effect.tryPromise({
640 try: async () => {
641 const result = await run.wait();
642 await callbackChain;
643 if (callbackFailure !== undefined) {
644 throw callbackFailure.cause;
645 }
646 return result;
647 },
648 catch: (cause) => runnerError(cause, "run.wait"),
649 }).pipe(
650 Effect.ensuring(Effect.sync(unsubscribeShellOutput)),
651 Effect.tap((result) =>
652 log({
653 direction: "incoming",
654 stage: "decoded",
655 payload: {
656 type: "run.completed",
657 result,
658 },
659 }),
660 ),
661 ),
662 steer: Effect.fn("CursorAgentSdkRun.steer")((text: string) =>
663 log({
664 direction: "outgoing",
665 stage: "decoded",
666 payload: {
667 type: "run.steer",
668 runId: run.id,
669 text,
670 },
671 }).pipe(
672 Effect.andThen(
673 Effect.tryPromise({
674 try: () => run.steer?.(text) ?? Promise.resolve("revert_to_followup" as const),
675 catch: (cause) => runnerError(cause, "run.steer"),
676 }),
677 ),
678 Effect.tap((outcome) =>
679 log({
680 direction: "incoming",
681 stage: "decoded",
682 payload: {
683 type: "run.steered",
684 runId: run.id,
685 outcome,
686 },
687 }),
688 ),
689 ),
690 ),
691 cancel: log({
692 direction: "outgoing",
693 stage: "decoded",
694 payload: {
695 type: "run.cancel",
696 runId: run.id,
697 },
698 }).pipe(
699 Effect.andThen(
700 Effect.tryPromise({
701 try: async () => {
702 try {
703 await run.cancel();
704 } catch (cause) {
705 if (!isCursorCancellationError(cause)) {
706 throw cause;
707 }
708 }
709 },
710 catch: (cause) => runnerError(cause, "run.cancel"),
711 }),
712 ),
713 ),
714 } satisfies CursorAgentSdkRun;
715 }),
716 listMessages: log({
717 direction: "outgoing",
718 stage: "decoded",
719 payload: {
720 type: "agent.messages.list",
721 agentId: agent.agentId,
722 },
723 }).pipe(
724 Effect.andThen(
725 Effect.tryPromise({
726 try: () =>
727 storePlatform === undefined
728 ? Agent.messages.list(agent.agentId, {
729 runtime: "local",
730 ...(cwd === undefined ? {} : { cwd }),
731 ...(input.options.local?.store === undefined
732 ? {}
733 : { store: input.options.local.store }),
734 })
735 : storePlatform.getAgentMessages(agent.agentId),
736 catch: (cause) => runnerError(cause, "agent.messages.list"),
737 }),
738 ),
739 Effect.tap((messages) =>
740 log({
741 direction: "incoming",
742 stage: "decoded",
743 payload: {
744 type: "agent.messages",
745 agentId: agent.agentId,
746 messages,
747 },
748 }),
749 ),
750 ),
751 close: log({
752 direction: "outgoing",
753 stage: "decoded",
754 payload: {
755 type: "agent.close",
756 agentId: agent.agentId,
757 },
758 }).pipe(
759 Effect.andThen(
760 Effect.try({
761 try: () => agent.close(),
762 catch: (cause) => runnerError(cause, "agent.close"),
763 }),
764 ),
765 ),
766 } satisfies CursorAgentSdkSession;
767 }),
768 assertComplete: Effect.void,
769 });
770});
771
772export const layer = Layer.effect(
773 CursorAgentSdkRunner,
774 Effect.gen(function* () {
775 const { native: nativeEventLogger } = yield* ProviderEventLoggers.ProviderEventLoggers;
776 return yield* makeCursorAgentSdkRunner((input) =>
777 makeCursorAgentSdkProtocolLogger({
778 nativeEventLogger,
779 threadId: input.threadId,
780 providerSessionId: input.providerSessionId,
781 }),
782 );
783 }),
784);