feat: robust error handling and type safety
Add type-safe error helpers and remove 'as any' casts. Changes: - Add isAbortError() helper for checking abort signals - Add getEventDetail() helper for CustomEvent extraction - Fix variable naming in isRetryable() (isAborted vs isAbortError) - Use proper instanceof checks instead of casting - Add proper interface for normalized response data - Type-safe tool execution error messages
This commit is contained in:
@@ -56,9 +56,9 @@ export class OpenAIClient implements LLMClient {
|
||||
signal: abortSignal,
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
const isAbortError = (error as any)?.name === 'AbortError'
|
||||
const errorMessage = isAbortError ? 'Network request aborted' : 'Network request failed'
|
||||
if (!isAbortError) console.error(error)
|
||||
const isAborted = error instanceof Error && error.name === 'AbortError'
|
||||
const errorMessage = isAborted ? 'Network request aborted' : 'Network request failed'
|
||||
if (!isAborted) console.error(error)
|
||||
throw new InvokeError(InvokeErrorType.NETWORK_ERROR, errorMessage, error)
|
||||
}
|
||||
|
||||
@@ -135,7 +135,15 @@ export class OpenAIClient implements LLMClient {
|
||||
|
||||
// Apply normalizeResponse if provided (for fixing format issues automatically)
|
||||
const normalizedData = options?.normalizeResponse ? options.normalizeResponse(data) : data
|
||||
const normalizedChoice = (normalizedData as any).choices?.[0]
|
||||
const normalizedChoice = (
|
||||
normalizedData as {
|
||||
choices?: {
|
||||
message?: {
|
||||
tool_calls?: { function?: { name?: string; arguments?: string } }[]
|
||||
}
|
||||
}[]
|
||||
}
|
||||
)?.choices?.[0]
|
||||
|
||||
// Get tool name from response
|
||||
const toolCallName = normalizedChoice?.message?.tool_calls?.[0]?.function?.name
|
||||
@@ -201,7 +209,7 @@ export class OpenAIClient implements LLMClient {
|
||||
} catch (e) {
|
||||
throw new InvokeError(
|
||||
InvokeErrorType.TOOL_EXECUTION_ERROR,
|
||||
`Tool execution failed: ${(e as Error).message}`,
|
||||
`Tool execution failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||
e,
|
||||
data
|
||||
)
|
||||
|
||||
@@ -40,8 +40,8 @@ export class InvokeError extends Error {
|
||||
}
|
||||
|
||||
private isRetryable(type: InvokeErrorType, rawError?: unknown): boolean {
|
||||
const isAbortError = (rawError as any)?.name === 'AbortError'
|
||||
if (isAbortError) return false
|
||||
const isAborted = rawError instanceof Error && rawError.name === 'AbortError'
|
||||
if (isAborted) return false
|
||||
|
||||
const retryableTypes: InvokeErrorType[] = [
|
||||
InvokeErrorType.NETWORK_ERROR,
|
||||
|
||||
@@ -93,15 +93,21 @@ async function withRetry<T>(
|
||||
return await fn()
|
||||
} catch (error: unknown) {
|
||||
// do not retry if aborted by user
|
||||
if ((error as any)?.rawError?.name === 'AbortError') throw error
|
||||
if (
|
||||
error instanceof InvokeError &&
|
||||
error.rawError instanceof Error &&
|
||||
error.rawError.name === 'AbortError'
|
||||
) {
|
||||
throw error
|
||||
}
|
||||
|
||||
console.error(error)
|
||||
settings.onError(error as Error)
|
||||
settings.onError(error instanceof Error ? error : new Error(String(error)))
|
||||
|
||||
// do not retry if error is not retryable (InvokeError)
|
||||
if (error instanceof InvokeError && !error.retryable) throw error
|
||||
|
||||
lastError = error as Error
|
||||
lastError = error instanceof Error ? error : new Error(String(error))
|
||||
attempt++
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
Reference in New Issue
Block a user