2025-10-22 23:31:36 +00:00
import { describe , expect , test } from "bun:test"
2026-06-27 16:29:21 +00:00
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
2026-06-03 02:42:13 +00:00
import { SessionV1 } from "@opencode-ai/core/v1/session"
2026-04-25 14:59:17 +00:00
import type { NamedError } from "@opencode-ai/core/util/error"
2026-01-26 18:50:04 +00:00
import { APICallError } from "ai"
2026-03-06 02:54:06 +00:00
import { setTimeout as sleep } from "node:timers/promises"
2026-06-21 01:44:02 +00:00
import { Effect , Schedule , Schema } from "effect"
2026-04-30 16:55:33 +00:00
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
2025-10-22 23:31:36 +00:00
import { SessionRetry } from "../../src/session/retry"
import { MessageV2 } from "../../src/session/message-v2"
2026-05-27 02:22:24 +00:00
import { ProviderError } from "../../src/provider/error"
2026-03-28 16:09:47 +00:00
import { SessionID } from "../../src/session/schema"
import { SessionStatus } from "../../src/session/status"
2026-04-30 16:55:33 +00:00
import { testEffect } from "../lib/effect"
2026-05-31 01:08:38 +00:00
import { ProviderV2 } from "@opencode-ai/core/provider"
2026-03-12 13:27:52 +00:00
2026-05-31 01:08:38 +00:00
const providerID = ProviderV2 . ID . make ( "test" )
2026-05-08 15:48:19 +00:00
const retryProvider = "test"
2026-06-27 16:29:21 +00:00
const it = testEffect ( LayerNode . compile ( LayerNode . group ( [ SessionStatus . node , CrossSpawnSpawner . node ] ) ) )
2025-10-22 23:31:36 +00:00
2026-06-03 02:42:13 +00:00
function apiError ( headers? : Record < string , string > ) : SessionV1 . APIError {
return Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-04-30 16:55:33 +00:00
message : "boom" ,
isRetryable : true ,
responseHeaders : headers ,
} ) . toObject ( ) ,
)
2025-10-22 23:31:36 +00:00
}
2026-01-26 21:12:41 +00:00
function wrap ( message : unknown ) : ReturnType < NamedError [ " toObject " ] > {
2026-04-30 16:55:33 +00:00
return { name : "" , data : { message } }
2026-01-26 21:12:41 +00:00
}
2025-11-17 16:30:55 +00:00
describe ( "session.retry.delay" , ( ) = > {
test ( "caps delay at 30 seconds when headers missing" , ( ) = > {
2025-10-22 23:31:36 +00:00
const error = apiError ( )
2025-11-25 20:44:24 +00:00
const delays = Array . from ( { length : 10 } , ( _ , index ) = > SessionRetry . delay ( index + 1 , error ) )
2025-11-17 16:30:55 +00:00
expect ( delays ) . toStrictEqual ( [ 2000 , 4000 , 8000 , 16000 , 30000 , 30000 , 30000 , 30000 , 30000 , 30000 ] )
2025-10-22 23:31:36 +00:00
} )
test ( "prefers retry-after-ms when shorter than exponential" , ( ) = > {
const error = apiError ( { "retry-after-ms" : "1500" } )
2025-11-25 20:44:24 +00:00
expect ( SessionRetry . delay ( 4 , error ) ) . toBe ( 1500 )
2025-10-22 23:31:36 +00:00
} )
test ( "uses retry-after seconds when reasonable" , ( ) = > {
const error = apiError ( { "retry-after" : "30" } )
2025-11-25 20:44:24 +00:00
expect ( SessionRetry . delay ( 3 , error ) ) . toBe ( 30000 )
2025-10-22 23:31:36 +00:00
} )
test ( "accepts http-date retry-after values" , ( ) = > {
const date = new Date ( Date . now ( ) + 20000 ) . toUTCString ( )
const error = apiError ( { "retry-after" : date } )
2025-11-25 20:44:24 +00:00
const d = SessionRetry . delay ( 1 , error )
2025-11-17 16:30:55 +00:00
expect ( d ) . toBeGreaterThanOrEqual ( 19000 )
expect ( d ) . toBeLessThanOrEqual ( 20000 )
2025-10-22 23:31:36 +00:00
} )
test ( "ignores invalid retry hints" , ( ) = > {
const error = apiError ( { "retry-after" : "not-a-number" } )
2025-11-25 20:44:24 +00:00
expect ( SessionRetry . delay ( 1 , error ) ) . toBe ( 2000 )
2025-10-22 23:31:36 +00:00
} )
2025-11-09 17:46:58 +00:00
test ( "ignores malformed date retry hints" , ( ) = > {
const error = apiError ( { "retry-after" : "Invalid Date String" } )
2025-11-25 20:44:24 +00:00
expect ( SessionRetry . delay ( 1 , error ) ) . toBe ( 2000 )
2025-11-09 17:46:58 +00:00
} )
test ( "ignores past date retry hints" , ( ) = > {
const pastDate = new Date ( Date . now ( ) - 5000 ) . toUTCString ( )
const error = apiError ( { "retry-after" : pastDate } )
2025-11-25 20:44:24 +00:00
expect ( SessionRetry . delay ( 1 , error ) ) . toBe ( 2000 )
2025-11-09 17:46:58 +00:00
} )
2025-11-17 16:33:28 +00:00
test ( "uses retry-after values even when exceeding 10 minutes with headers" , ( ) = > {
2025-11-09 17:46:58 +00:00
const error = apiError ( { "retry-after" : "50" } )
2025-11-25 20:44:24 +00:00
expect ( SessionRetry . delay ( 1 , error ) ) . toBe ( 50000 )
2025-11-09 17:46:58 +00:00
const longError = apiError ( { "retry-after-ms" : "700000" } )
2025-11-25 20:44:24 +00:00
expect ( SessionRetry . delay ( 1 , longError ) ) . toBe ( 700000 )
2025-11-09 17:46:58 +00:00
} )
2025-12-31 22:15:49 +00:00
2026-03-28 16:09:47 +00:00
test ( "caps oversized header delays to the runtime timer limit" , ( ) = > {
const error = apiError ( { "retry-after-ms" : "999999999999" } )
expect ( SessionRetry . delay ( 1 , error ) ) . toBe ( SessionRetry . RETRY_MAX_DELAY )
} )
2025-12-31 22:15:49 +00:00
2026-05-31 01:08:38 +00:00
it . instance ( "policy updates retry status and increments attempts" , ( ) = >
Effect . gen ( function * ( ) {
2026-05-31 01:09:55 +00:00
const sessionID = SessionID . make ( "session-retry-test" )
const error = apiError ( { "retry-after-ms" : "0" } )
const status = yield * SessionStatus . Service
const step = yield * Schedule . toStepWithMetadata (
SessionRetry . policy ( {
provider : "test" ,
2026-06-03 02:42:13 +00:00
parse : Schema.decodeUnknownSync ( SessionV1 . APIError . Schema ) ,
2026-05-31 01:09:55 +00:00
set : ( info ) = >
status . set ( sessionID , {
type : "retry" ,
attempt : info.attempt ,
message : info.message ,
next : info.next ,
} ) ,
} ) ,
)
yield * step ( error )
yield * step ( error )
expect ( yield * status . get ( sessionID ) ) . toMatchObject ( {
type : "retry" ,
attempt : 2 ,
message : "boom" ,
} )
2026-05-31 01:08:38 +00:00
} ) ,
2026-04-30 16:55:33 +00:00
)
2025-10-22 23:31:36 +00:00
} )
2025-12-24 01:28:41 +00:00
2026-01-26 21:12:41 +00:00
describe ( "session.retry.retryable" , ( ) = > {
test ( "maps too_many_requests json messages" , ( ) = > {
const error = wrap ( JSON . stringify ( { type : "error" , error : { type : "too_many_requests" } } ) )
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : "Too Many Requests" } )
2026-01-26 21:12:41 +00:00
} )
test ( "maps overloaded provider codes" , ( ) = > {
const error = wrap ( JSON . stringify ( { code : "resource_exhausted" } ) )
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : "Provider is overloaded" } )
2026-01-26 21:12:41 +00:00
} )
2026-03-28 16:09:47 +00:00
test ( "does not retry unknown json messages" , ( ) = > {
2026-01-26 21:12:41 +00:00
const error = wrap ( JSON . stringify ( { error : { message : "no_kv_space" } } ) )
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toBeUndefined ( )
2026-01-26 21:12:41 +00:00
} )
2026-01-27 16:22:03 +00:00
test ( "does not throw on numeric error codes" , ( ) = > {
const error = wrap ( JSON . stringify ( { type : "error" , error : { code : 123 } } ) )
2026-05-08 15:48:19 +00:00
const result = SessionRetry . retryable ( error , retryProvider )
2026-01-27 16:22:03 +00:00
expect ( result ) . toBeUndefined ( )
} )
2026-01-26 21:12:41 +00:00
test ( "returns undefined for non-json message" , ( ) = > {
const error = wrap ( "not-json" )
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toBeUndefined ( )
2026-01-26 21:12:41 +00:00
} )
2026-02-11 01:55:22 +00:00
2026-04-07 17:46:01 +00:00
test ( "retries plain text rate limit errors from Alibaba" , ( ) = > {
const msg =
"Upstream error from Alibaba: Request rate increased too quickly. To ensure system stability, please adjust your client logic to scale requests more smoothly over time."
const error = wrap ( msg )
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : msg } )
2026-04-07 17:46:01 +00:00
} )
test ( "retries plain text rate limit errors" , ( ) = > {
const msg = "Rate limit exceeded, please try again later"
const error = wrap ( msg )
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : msg } )
2026-04-07 17:46:01 +00:00
} )
test ( "retries too many requests in plain text" , ( ) = > {
const msg = "Too many requests, please slow down"
const error = wrap ( msg )
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : msg } )
2026-04-07 17:46:01 +00:00
} )
2026-05-27 02:22:24 +00:00
test ( "retries transport timeout errors" , ( ) = > {
const request = MessageV2 . fromError ( new ProviderError . HeaderTimeoutError ( 10000 ) , { providerID } )
2026-06-03 02:42:13 +00:00
expect ( SessionV1 . APIError . isInstance ( request ) ) . toBe ( true )
2026-05-27 02:22:24 +00:00
expect ( SessionRetry . retryable ( request , retryProvider ) ) . toEqual ( {
message : "Provider response headers timed out after 10000ms" ,
} )
} )
2026-05-28 06:16:18 +00:00
test ( "retries websocket stream transport errors" , ( ) = > {
const request = MessageV2 . fromError (
new ProviderError . ResponseStreamError ( "WebSocket closed before response.completed (code 1006: Connection ended)" ) ,
{ providerID } ,
)
2026-06-03 02:42:13 +00:00
expect ( SessionV1 . APIError . isInstance ( request ) ) . toBe ( true )
2026-05-28 06:16:18 +00:00
expect ( SessionRetry . retryable ( request , retryProvider ) ) . toEqual ( {
message : "WebSocket closed before response.completed (code 1006: Connection ended)" ,
} )
} )
2026-02-11 01:55:22 +00:00
test ( "does not retry context overflow errors" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = new SessionV1 . ContextOverflowError ( {
2026-02-11 01:55:22 +00:00
message : "Input exceeds context window of this model" ,
responseBody : '{"error":{"code":"context_length_exceeded"}}' ,
2026-04-30 16:55:33 +00:00
} ) . toObject ( )
2026-02-11 01:55:22 +00:00
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toBeUndefined ( )
2026-02-11 01:55:22 +00:00
} )
2026-03-25 18:08:40 +00:00
2026-04-16 00:58:48 +00:00
test ( "retries 500 errors even when isRetryable is false" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-04-30 16:55:33 +00:00
message : "Internal server error" ,
isRetryable : false ,
statusCode : 500 ,
responseBody : '{"type":"api_error","message":"Internal server error"}' ,
} ) . toObject ( ) ,
)
2026-04-16 00:58:48 +00:00
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : "Internal server error" } )
2026-04-16 00:58:48 +00:00
} )
test ( "retries 502 bad gateway errors" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-04-30 16:55:33 +00:00
message : "Bad gateway" ,
isRetryable : false ,
statusCode : 502 ,
} ) . toObject ( ) ,
)
2026-04-16 00:58:48 +00:00
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : "Bad gateway" } )
2026-04-16 00:58:48 +00:00
} )
test ( "retries 503 service unavailable errors" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-04-30 16:55:33 +00:00
message : "Service unavailable" ,
isRetryable : false ,
statusCode : 503 ,
} ) . toObject ( ) ,
)
2026-04-16 00:58:48 +00:00
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toEqual ( { message : "Service unavailable" } )
2026-04-16 00:58:48 +00:00
} )
test ( "does not retry 4xx errors when isRetryable is false" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-04-30 16:55:33 +00:00
message : "Bad request" ,
isRetryable : false ,
statusCode : 400 ,
} ) . toObject ( ) ,
)
2026-04-16 00:58:48 +00:00
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , retryProvider ) ) . toBeUndefined ( )
2026-04-16 00:58:48 +00:00
} )
2026-03-25 18:08:40 +00:00
test ( "retries ZlibError decompression failures" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-04-30 16:55:33 +00:00
message : "Response decompression failed" ,
isRetryable : true ,
metadata : { code : "ZlibError" } ,
} ) . toObject ( ) ,
)
2026-03-25 18:08:40 +00:00
2026-05-08 15:48:19 +00:00
const retryable = SessionRetry . retryable ( error , retryProvider )
2026-03-25 18:08:40 +00:00
expect ( retryable ) . toBeDefined ( )
2026-05-07 21:53:24 +00:00
expect ( retryable ) . toEqual ( { message : "Response decompression failed" } )
} )
test ( "maps free limits to Go upsell action" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-05-07 21:53:24 +00:00
message : "Free usage exceeded" ,
isRetryable : true ,
statusCode : 429 ,
responseBody : JSON.stringify ( {
type : "error" ,
error : { type : "FreeUsageLimitError" , message : "Free usage exceeded" } ,
} ) ,
} ) . toObject ( ) ,
)
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , "opencode" ) ) . toEqual ( {
2026-05-07 21:53:24 +00:00
message : SessionRetry.GO_UPSELL_MESSAGE ,
action : {
2026-05-08 15:48:19 +00:00
reason : "free_tier_limit" ,
provider : "opencode" ,
2026-05-07 21:53:24 +00:00
title : "Free limit reached" ,
2026-05-07 21:54:31 +00:00
message : "Subscribe to OpenCode Go for reliable access to the best open-source models, starting at $5/month." ,
2026-05-07 21:53:24 +00:00
label : "subscribe" ,
link : SessionRetry.GO_UPSELL_URL ,
} ,
} )
} )
test ( "maps Go subscription limits to workspace PAYG upsell" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-05-07 21:53:24 +00:00
message : "Subscription quota exceeded. You can continue using free models." ,
isRetryable : true ,
statusCode : 429 ,
2026-05-08 08:40:18 +00:00
responseHeaders : {
"retry-after" : "19380" ,
} ,
2026-05-07 21:53:24 +00:00
responseBody : JSON.stringify ( {
type : "error" ,
error : {
type : "GoUsageLimitError" ,
message : "Subscription quota exceeded. You can continue using free models." ,
} ,
metadata : {
workspace : "wrk_01K6XGM22R6FM8JVABE9XDQXGH" ,
2026-05-08 08:40:18 +00:00
limitName : "5 hour" ,
2026-05-07 21:53:24 +00:00
} ,
} ) ,
} ) . toObject ( ) ,
)
2026-05-08 15:48:19 +00:00
expect ( SessionRetry . retryable ( error , "opencode-go" ) ) . toEqual ( {
2026-05-08 05:51:31 +00:00
message :
"5 hour usage limit reached. It will reset in 5 hours 23 minutes. To continue using this model now, enable usage from your available balance - https://opencode.ai/workspace/wrk_01K6XGM22R6FM8JVABE9XDQXGH/go" ,
2026-05-07 21:53:24 +00:00
action : {
2026-05-08 15:48:19 +00:00
reason : "account_rate_limit" ,
provider : "opencode-go" ,
2026-05-07 21:53:24 +00:00
title : "Go limit reached" ,
2026-05-08 05:51:31 +00:00
message :
"5 hour usage limit reached. It will reset in 5 hours 23 minutes. To continue using this model now, enable usage from your available balance" ,
label : "open settings" ,
2026-05-07 21:53:24 +00:00
link : "https://opencode.ai/workspace/wrk_01K6XGM22R6FM8JVABE9XDQXGH/go" ,
} ,
} )
2026-03-25 18:08:40 +00:00
} )
2026-05-08 15:48:19 +00:00
test ( "maps Go subscription limits without limit metadata" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-05-08 15:48:19 +00:00
message : "Subscription quota exceeded. You can continue using free models." ,
isRetryable : true ,
statusCode : 429 ,
responseHeaders : {
"retry-after" : "900" ,
} ,
responseBody : JSON.stringify ( {
type : "error" ,
error : {
type : "GoUsageLimitError" ,
message : "Subscription quota exceeded. You can continue using free models." ,
} ,
metadata : {
workspace : "wrk_01K6XGM22R6FM8JVABE9XDQXGH" ,
} ,
} ) ,
} ) . toObject ( ) ,
)
expect ( SessionRetry . retryable ( error , "opencode-go" ) ? . action ? . message ) . toBe (
"Usage limit reached. It will reset in 15 minutes. To continue using this model now, enable usage from your available balance" ,
)
} )
2026-01-26 21:12:41 +00:00
} )
2025-12-24 01:28:41 +00:00
describe ( "session.message-v2.fromError" , ( ) = > {
test . concurrent (
"converts ECONNRESET socket errors to retryable APIError" ,
async ( ) = > {
using server = Bun . serve ( {
port : 0 ,
idleTimeout : 8 ,
2026-04-16 01:56:23 +00:00
async fetch ( _req ) {
2025-12-24 01:28:41 +00:00
return new Response (
new ReadableStream ( {
async pull ( controller ) {
controller . enqueue ( "Hello," )
2026-03-06 02:54:06 +00:00
await sleep ( 10000 )
2025-12-24 01:28:41 +00:00
controller . enqueue ( " World!" )
controller . close ( )
} ,
} ) ,
{ headers : { "Content-Type" : "text/plain" } } ,
)
} ,
} )
const error = await fetch ( new URL ( "/" , server . url . origin ) )
. then ( ( res ) = > res . text ( ) )
. catch ( ( e ) = > e )
2026-03-12 13:27:52 +00:00
const result = MessageV2 . fromError ( error , { providerID } )
2025-12-24 01:28:41 +00:00
2026-06-03 02:42:13 +00:00
expect ( SessionV1 . APIError . isInstance ( result ) ) . toBe ( true )
if ( ! SessionV1 . APIError . isInstance ( result ) ) throw new Error ( "expected APIError" )
2026-04-30 16:55:33 +00:00
expect ( result . data . isRetryable ) . toBe ( true )
expect ( result . data . message ) . toBe ( "Connection reset by server" )
expect ( result . data . metadata ? . code ) . toBe ( "ECONNRESET" )
expect ( result . data . metadata ? . message ) . toInclude ( "socket connection" )
2025-12-24 01:28:41 +00:00
} ,
15 _000 ,
)
test ( "ECONNRESET socket error is retryable" , ( ) = > {
2026-06-03 02:42:13 +00:00
const error = Schema . decodeUnknownSync ( SessionV1 . APIError . Schema ) (
new SessionV1 . APIError ( {
2026-04-30 16:55:33 +00:00
message : "Connection reset by server" ,
isRetryable : true ,
metadata : { code : "ECONNRESET" , message : "The socket connection was closed unexpectedly" } ,
} ) . toObject ( ) ,
)
2025-12-24 01:28:41 +00:00
2026-05-08 15:48:19 +00:00
const retryable = SessionRetry . retryable ( error , retryProvider )
2025-12-24 01:28:41 +00:00
expect ( retryable ) . toBeDefined ( )
2026-05-07 21:53:24 +00:00
expect ( retryable ) . toEqual ( { message : "Connection reset by server" } )
2025-12-24 01:28:41 +00:00
} )
2026-01-26 18:50:04 +00:00
test ( "marks OpenAI 404 status codes as retryable" , ( ) = > {
const error = new APICallError ( {
message : "boom" ,
url : "https://api.openai.com/v1/chat/completions" ,
requestBodyValues : { } ,
statusCode : 404 ,
responseHeaders : { "content-type" : "application/json" } ,
2026-01-26 18:50:58 +00:00
responseBody : '{"error":"boom"}' ,
2026-01-26 18:50:04 +00:00
isRetryable : false ,
} )
2026-05-31 01:08:38 +00:00
const result = MessageV2 . fromError ( error , { providerID : ProviderV2.ID.make ( "openai" ) } )
2026-06-03 02:42:13 +00:00
if ( ! SessionV1 . APIError . isInstance ( result ) ) throw new Error ( "expected APIError" )
2026-01-26 18:50:04 +00:00
expect ( result . data . isRetryable ) . toBe ( true )
} )
2026-04-23 21:31:43 +00:00
test ( "converts OpenAI server_error stream chunks to retryable APIError" , ( ) = > {
const result = MessageV2 . fromError (
{
message : JSON.stringify ( {
type : "error" ,
sequence_number : 2 ,
error : {
type : "server_error" ,
code : "server_error" ,
message : "An error occurred while processing your request." ,
param : null ,
} ,
} ) ,
} ,
2026-05-31 01:08:38 +00:00
{ providerID : ProviderV2.ID.make ( "openai" ) } ,
2026-04-23 21:31:43 +00:00
)
2026-06-03 02:42:13 +00:00
expect ( SessionV1 . APIError . isInstance ( result ) ) . toBe ( true )
if ( ! SessionV1 . APIError . isInstance ( result ) ) throw new Error ( "expected APIError" )
2026-04-30 16:55:33 +00:00
expect ( result . data . isRetryable ) . toBe ( true )
2026-05-08 15:49:37 +00:00
expect ( SessionRetry . retryable ( result , retryProvider ) ) . toEqual ( {
message : "An error occurred while processing your request." ,
} )
2026-04-23 21:31:43 +00:00
} )
2025-12-24 01:28:41 +00:00
} )