diff --git a/packages/provider-cursor/src/server/CursorAgentSdk.ts b/packages/provider-cursor/src/server/CursorAgentSdk.ts index 92fb764b432d5..8a19ddfaa64fb 100644 --- a/packages/provider-cursor/src/server/CursorAgentSdk.ts +++ b/packages/provider-cursor/src/server/CursorAgentSdk.ts @@ -58,6 +58,17 @@ export function isCursorUsageLimitError(cause: unknown): boolean { ); } +export function cursorSdkFailureMetadata(cause: unknown) { + const error = isCursorAgentSdkRunnerError(cause) ? cause.cause : cause; + if (typeof error !== "object" || error === null) return {}; + const code = Reflect.get(error, "code"); + const retryable = Reflect.get(error, "isRetryable"); + return { + ...(typeof code === "string" ? { code } : {}), + ...(typeof retryable === "boolean" ? { retryable } : {}), + }; +} + const GENERIC_SDK_ERROR_CODES = new Set(["", "error", "unknown"]); function sdkErrorReason(cause: unknown): string | undefined { diff --git a/packages/provider-cursor/src/server/adapter.test.ts b/packages/provider-cursor/src/server/adapter.test.ts index fdf81a68d8411..c96171921e20b 100644 --- a/packages/provider-cursor/src/server/adapter.test.ts +++ b/packages/provider-cursor/src/server/adapter.test.ts @@ -53,7 +53,7 @@ const cursorBackendUrl = await vi.hoisted(async () => { return url; }); -const { Agent, ConfigurationError } = loadCursorSdk(); +const { Agent, ConfigurationError, NetworkError } = loadCursorSdk(); describe("CursorAdapterV2", () => { it.effect.each([ @@ -296,6 +296,42 @@ describe("CursorAdapterV2", () => { ); it.effect.each([ + ...([true, false] as const).map((isRetryable) => ({ + reason: `a returned SDK error with retryable=${isRetryable}`, + updates: [], + answers: undefined, + wait: Effect.succeed({ + id: "native-cursor-run", + status: "error" as const, + error: { message: "Connection stalled", code: "connection_stalled", isRetryable }, + }), + failure: { + class: "provider_error", + message: "Connection stalled", + code: "connection_stalled", + retryable: isRetryable, + }, + })), + ...([true, false] as const).map((isRetryable) => ({ + reason: `the SDK network failure with retryable=${isRetryable}`, + updates: [], + answers: undefined, + wait: Effect.fail( + new CursorAgentSdk.CursorAgentSdkRunnerError({ + method: "run.wait", + cause: new NetworkError("Connection stalled", { + code: "connection_stalled", + isRetryable, + }), + }), + ), + failure: { + class: "transport_error", + message: "Cursor Agent SDK run.wait failed: Connection stalled (connection_stalled)", + code: "connection_stalled", + retryable: isRetryable, + }, + })), { reason: "the SDK's detailed plan usage limit code", wait: Effect.succeed({ @@ -510,6 +546,10 @@ describe("CursorAdapterV2", () => { assert.equal(terminal?.status, failure === null ? "completed" : "failed"); assert.equal(terminal?.failure?.class, failure?.class); assert.equal(terminal?.failure?.message, failure?.message); + if (failure !== null && "retryable" in failure) { + assert.equal(terminal?.failure?.retryable, failure.retryable); + assert.equal(terminal?.failure?.code, failure.code); + } if (answers !== undefined) { assert.deepEqual( events.flatMap((event) => diff --git a/packages/provider-cursor/src/server/adapter.ts b/packages/provider-cursor/src/server/adapter.ts index f5fbd0630d478..cd0c6637c043f 100644 --- a/packages/provider-cursor/src/server/adapter.ts +++ b/packages/provider-cursor/src/server/adapter.ts @@ -2542,6 +2542,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* ( transportFailure === undefined ? makeProviderFailure({ cause: result.error, + ...CursorAgentSdk.cursorSdkFailureMetadata(result.error), // makeProviderFailure never reads a cause's own text. message: result.error?.message, class: CursorAgentSdk.isCursorUsageLimitError(result.error) @@ -2569,6 +2570,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* ( : { failure: makeProviderFailure({ cause, + ...CursorAgentSdk.cursorSdkFailureMetadata(cause), message: CursorAgentSdk.isCursorAgentSdkRunnerError(cause) ? CursorAgentSdk.cursorSdkFailureMessage(cause) : undefined,