Skip to content

Commit b6b193f

Browse files
committed
Improve SDK error handling and CLI network error display
- Sanitize SDK error messages to remove unhelpful system messages - Increase default stream timeout from 15s to 3 minutes - Move offline banner to top of chat interface for better visibility - Add comprehensive error handling tests for SDK
1 parent df88d62 commit b6b193f

File tree

6 files changed

+108
-30
lines changed

6 files changed

+108
-30
lines changed

cli/src/chat.tsx

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -579,9 +579,8 @@ export const Chat = ({
579579
})
580580

581581
const offlineBannerMessage = useMemo(() => {
582-
const baseMessage = `⚠️ Network Error: ${
583-
networkStatus.error?.message ?? 'Connection lost'
584-
}`
582+
const errorMessage = networkStatus.error?.message ?? 'Connection lost'
583+
const baseMessage = `⚠️ ${errorMessage}`
585584
const retryMessage =
586585
pendingRetryCount > 0
587586
? ` • ${pendingRetryCount} message${
@@ -921,6 +920,31 @@ export const Chat = ({
921920
flexGrow: 1,
922921
}}
923922
>
923+
{!networkStatus.isOnline && (
924+
<box
925+
style={{
926+
flexDirection: 'column',
927+
flexShrink: 0,
928+
width: '100%',
929+
paddingTop: 1,
930+
paddingBottom: 1,
931+
paddingLeft: 1,
932+
paddingRight: 1,
933+
backgroundColor: theme.surface ?? theme.background ?? '#333333',
934+
}}
935+
>
936+
<text
937+
style={{
938+
fg: theme.warning ?? theme.foreground,
939+
wrapMode: 'word',
940+
width: '100%',
941+
}}
942+
>
943+
{offlineBannerMessage}
944+
</text>
945+
</box>
946+
)}
947+
924948
<scrollbox
925949
ref={scrollRef}
926950
stickyScroll
@@ -1096,28 +1120,6 @@ export const Chat = ({
10961120
</box>
10971121

10981122
{validationBanner}
1099-
1100-
{!networkStatus.isOnline && (
1101-
<box
1102-
style={{
1103-
width: '100%',
1104-
paddingTop: 1,
1105-
paddingBottom: 1,
1106-
paddingLeft: 1,
1107-
paddingRight: 1,
1108-
backgroundColor: theme.surface ?? theme.background ?? '#333333',
1109-
}}
1110-
>
1111-
<text
1112-
style={{
1113-
fg: theme.warning ?? theme.foreground,
1114-
wrapMode: 'word',
1115-
}}
1116-
>
1117-
{offlineBannerMessage}
1118-
</text>
1119-
</box>
1120-
)}
11211123
</box>
11221124
)
11231125
}

sdk/src/__tests__/abort-controller.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ describe('Abort Controller - Single Controller Architecture', () => {
6666
expect(client.options.streamTimeoutMs).toBe(30000)
6767
})
6868

69-
test('streamTimeoutMs defaults to 15000ms', () => {
69+
test('streamTimeoutMs defaults to 180000ms (3 minutes)', () => {
7070
const client = new CodebuffClient({
7171
apiKey: 'test-key',
7272
disableConsoleErrors: true,
7373
})
7474

75-
expect(client.options.streamTimeoutMs).toBe(15000)
75+
expect(client.options.streamTimeoutMs).toBe(180000)
7676
})
7777
})
7878

sdk/src/__tests__/errors.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { describe, expect, test } from 'bun:test'
2+
import { NetworkError, AuthenticationError, isNetworkError, isAuthenticationError } from '../errors'
3+
4+
describe('NetworkError', () => {
5+
test('removes unhelpful browser error messages', () => {
6+
const error = new NetworkError('Is the computer able to access the url?')
7+
expect(error.message).toBe('')
8+
})
9+
10+
test('removes unhelpful text but preserves the rest', () => {
11+
const error = new NetworkError('Network error: Is the computer able to access the url?')
12+
expect(error.message).toBe('Network error:')
13+
})
14+
15+
test('removes unhelpful text from middle of message', () => {
16+
const error = new NetworkError('Unable to connect. Is the computer able to access the url? Verify connection.')
17+
expect(error.message).toBe('Unable to connect. Verify connection.')
18+
})
19+
20+
test('preserves helpful error messages', () => {
21+
const error = new NetworkError('Connection refused')
22+
expect(error.message).toBe('Connection refused')
23+
})
24+
25+
test('includes error code', () => {
26+
const error = new NetworkError('Test error')
27+
expect(error.code).toBe('NETWORK_ERROR')
28+
})
29+
30+
test('includes status when provided', () => {
31+
const error = new NetworkError('Server error', { status: 500 })
32+
expect(error.status).toBe(500)
33+
})
34+
35+
test('includes originalError when provided', () => {
36+
const originalError = new Error('Original')
37+
const error = new NetworkError('Wrapped error', { originalError })
38+
expect(error.originalError).toBe(originalError)
39+
})
40+
41+
test('includes streamTimedOut flag when provided', () => {
42+
const error = new NetworkError('Timeout', { streamTimedOut: true })
43+
expect(error.streamTimedOut).toBe(true)
44+
})
45+
})
46+
47+
describe('AuthenticationError', () => {
48+
test('creates error with status', () => {
49+
const error = new AuthenticationError('Auth failed', 401)
50+
expect(error.message).toBe('Auth failed')
51+
expect(error.status).toBe(401)
52+
expect(error.code).toBe('AUTH_FAILED')
53+
})
54+
})
55+
56+
describe('Type guards', () => {
57+
test('isNetworkError identifies NetworkError instances', () => {
58+
const error = new NetworkError('Test')
59+
expect(isNetworkError(error)).toBe(true)
60+
expect(isNetworkError(new Error('Test'))).toBe(false)
61+
})
62+
63+
test('isAuthenticationError identifies AuthenticationError instances', () => {
64+
const error = new AuthenticationError('Test', 401)
65+
expect(isAuthenticationError(error)).toBe(true)
66+
expect(isAuthenticationError(new Error('Test'))).toBe(false)
67+
})
68+
})

sdk/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class CodebuffClient {
3535
const fingerprintId =
3636
options.fingerprintId ??
3737
`codebuff-sdk-${Math.random().toString(36).substring(2, 15)}`
38-
const streamTimeoutMs = options.streamTimeoutMs ?? 15_000
38+
const streamTimeoutMs = options.streamTimeoutMs ?? 180_000 // 3 minutes
3939

4040
this.options = {
4141
apiKey: foundApiKey,

sdk/src/errors.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
* Custom error classes for the SDK
33
*/
44

5+
/**
6+
* Sanitizes error messages by removing unhelpful system messages
7+
*/
8+
function sanitizeErrorMessage(message: string): string {
9+
// Remove unhelpful browser/system error message
10+
return message.replace(/Is the computer able to access the url\?\s*/g, '').trim()
11+
}
12+
513
export interface ErrorWithCode extends Error {
614
code: string
715
}
@@ -44,7 +52,7 @@ export class NetworkError extends Error implements NetworkErrorDetails {
4452
message: string,
4553
options?: { status?: number; originalError?: any; streamTimedOut?: boolean },
4654
) {
47-
super(message)
55+
super(sanitizeErrorMessage(message))
4856
this.name = 'NetworkError'
4957
this.status = options?.status
5058
this.originalError = options?.originalError

sdk/src/run.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export async function run({
118118
agentDefinitions,
119119
maxAgentSteps = MAX_AGENT_STEPS_DEFAULT,
120120
env,
121-
streamTimeoutMs = 15_000,
121+
streamTimeoutMs = 180_000, // 3 minutes
122122

123123
handleEvent,
124124
handleStreamChunk,

0 commit comments

Comments
 (0)