11import * as analytics from '@codebuff/common/analytics'
22import { TEST_USER_ID } from '@codebuff/common/old-constants'
33import { createTestAgentRuntimeParams } from '@codebuff/common/testing/fixtures/agent-runtime'
4- import {
5- clearMockedModules ,
6- } from '@codebuff/common/testing/mock-modules'
4+ import { clearMockedModules } from '@codebuff/common/testing/mock-modules'
75import { setupDbSpies } from '@codebuff/common/testing/mocks/database'
86import { getInitialSessionState } from '@codebuff/common/types/session-state'
97import { AbortError , promptSuccess } from '@codebuff/common/util/error'
@@ -20,7 +18,7 @@ import {
2018 mock ,
2119 spyOn ,
2220} from 'bun:test'
23- import { APICallError } from 'ai'
21+ import { APICallError , RetryError } from 'ai'
2422import { z } from 'zod/v4'
2523
2624import { loopAgentSteps } from '../run-agent-step'
@@ -661,13 +659,15 @@ describe('loopAgentSteps - runAgentStep vs runProgrammaticStep behavior', () =>
661659 // Mock promptAiSdk to capture the n parameter
662660 loopAgentStepsBaseParams . promptAiSdk = async ( params : any ) => {
663661 agentStepN = params . n
664- return promptSuccess ( JSON . stringify ( [
665- 'Response 1' ,
666- 'Response 2' ,
667- 'Response 3' ,
668- 'Response 4' ,
669- 'Response 5' ,
670- ] ) )
662+ return promptSuccess (
663+ JSON . stringify ( [
664+ 'Response 1' ,
665+ 'Response 2' ,
666+ 'Response 3' ,
667+ 'Response 4' ,
668+ 'Response 5' ,
669+ ] ) ,
670+ )
671671 }
672672
673673 await loopAgentSteps ( {
@@ -972,7 +972,9 @@ describe('loopAgentSteps - runAgentStep vs runProgrammaticStep behavior', () =>
972972 expect ( result . output . type ) . toBe ( 'error' )
973973 if ( result . output . type === 'error' ) {
974974 // Should use the server's message, NOT the generic "Forbidden"
975- expect ( result . output . message ) . toBe ( 'Free mode is not available in your country.' )
975+ expect ( result . output . message ) . toBe (
976+ 'Free mode is not available in your country.' ,
977+ )
976978 // Should NOT have the 'Agent run error: ' prefix since message came from responseBody
977979 expect ( result . output . message ) . not . toContain ( 'Agent run error:' )
978980 // Should propagate the error code so the CLI can match on it
@@ -1022,5 +1024,53 @@ describe('loopAgentSteps - runAgentStep vs runProgrammaticStep behavior', () =>
10221024 expect ( result . output . error ) . toBeUndefined ( )
10231025 }
10241026 } )
1027+
1028+ it ( 'should unwrap retry errors to propagate underlying 409 gate errors' , async ( ) => {
1029+ const llmOnlyTemplate = {
1030+ ...mockTemplate ,
1031+ handleSteps : undefined ,
1032+ }
1033+
1034+ const localAgentTemplates = {
1035+ 'test-agent' : llmOnlyTemplate ,
1036+ }
1037+
1038+ const apiError = new APICallError ( {
1039+ statusCode : 409 ,
1040+ message : 'Conflict' ,
1041+ url : 'https://api.codebuff.com/v1/chat/completions' ,
1042+ requestBodyValues : { } ,
1043+ responseBody : JSON . stringify ( {
1044+ error : 'session_superseded' ,
1045+ message :
1046+ 'Another instance of freebuff has taken over this session. Only one instance per account is allowed.' ,
1047+ } ) ,
1048+ isRetryable : true ,
1049+ } )
1050+
1051+ loopAgentStepsBaseParams . promptAiSdkStream = async function * ( ) {
1052+ throw new RetryError ( {
1053+ message : 'Failed after 4 attempts. Last error: Conflict' ,
1054+ reason : 'maxRetriesExceeded' ,
1055+ errors : [ apiError ] ,
1056+ } )
1057+ }
1058+
1059+ const result = await loopAgentSteps ( {
1060+ ...loopAgentStepsBaseParams ,
1061+ agentType : 'test-agent' ,
1062+ localAgentTemplates,
1063+ } )
1064+
1065+ expect ( result . output . type ) . toBe ( 'error' )
1066+ if ( result . output . type === 'error' ) {
1067+ expect ( result . output . message ) . toBe (
1068+ 'Another instance of freebuff has taken over this session. Only one instance per account is allowed.' ,
1069+ )
1070+ expect ( result . output . message ) . not . toContain ( 'Agent run error:' )
1071+ expect ( result . output . error ) . toBe ( 'session_superseded' )
1072+ expect ( result . output . statusCode ) . toBe ( 409 )
1073+ }
1074+ } )
10251075 } )
10261076} )
0 commit comments