feat: unify InvokeError; add rawResponse to historical ErrorEvent

This commit is contained in:
Simon
2026-01-20 20:16:34 +08:00
parent 1f8953c450
commit 22516dec74
5 changed files with 34 additions and 13 deletions

View File

@@ -2,7 +2,7 @@
* Copyright (C) 2025 Alibaba Group Holding Limited * Copyright (C) 2025 Alibaba Group Holding Limited
* All rights reserved. * All rights reserved.
*/ */
import { LLM, type Tool } from '@page-agent/llms' import { InvokeError, LLM, type Tool } from '@page-agent/llms'
import type { PageController } from '@page-agent/page-controller' import type { PageController } from '@page-agent/page-controller'
import chalk from 'chalk' import chalk from 'chalk'
import zod from 'zod' import zod from 'zod'
@@ -107,7 +107,7 @@ export class PageAgentCore extends EventTarget {
this.#emitHistoryChange() this.#emitHistoryChange()
}) })
this.#llm.addEventListener('error', (e) => { this.#llm.addEventListener('error', (e) => {
const { error } = (e as CustomEvent).detail const error = (e as CustomEvent).detail.error as Error | InvokeError
const message = String(error) const message = String(error)
this.emitActivity({ type: 'error', message }) this.emitActivity({ type: 'error', message })
// Also push to history for panel rendering // Also push to history for panel rendering
@@ -115,6 +115,7 @@ export class PageAgentCore extends EventTarget {
type: 'error', type: 'error',
errorType: 'error', errorType: 'error',
message, message,
rawResponse: (error as InvokeError).rawResponse,
}) })
this.#emitHistoryChange() this.#emitHistoryChange()
}) })

View File

@@ -76,6 +76,7 @@ export interface ErrorEvent {
message: string message: string
attempt?: number attempt?: number
maxAttempts?: number maxAttempts?: number
rawResponse?: unknown
} }
/** /**

View File

@@ -107,14 +107,23 @@ export class OpenAIClient implements LLMClient {
case 'length': case 'length':
throw new InvokeError( throw new InvokeError(
InvokeErrorType.CONTEXT_LENGTH, InvokeErrorType.CONTEXT_LENGTH,
'Response truncated: max tokens reached' 'Response truncated: max tokens reached',
undefined,
data
) )
case 'content_filter': case 'content_filter':
throw new InvokeError(InvokeErrorType.CONTENT_FILTER, 'Content filtered by safety system') throw new InvokeError(
InvokeErrorType.CONTENT_FILTER,
'Content filtered by safety system',
undefined,
data
)
default: default:
throw new InvokeError( throw new InvokeError(
InvokeErrorType.UNKNOWN, InvokeErrorType.UNKNOWN,
`Unexpected finish_reason: ${choice.finish_reason}` `Unexpected finish_reason: ${choice.finish_reason}`,
undefined,
data
) )
} }
@@ -128,7 +137,8 @@ export class OpenAIClient implements LLMClient {
throw new InvokeError( throw new InvokeError(
InvokeErrorType.NO_TOOL_CALL, InvokeErrorType.NO_TOOL_CALL,
'No tool call found in response', 'No tool call found in response',
normalizedData undefined,
data
) )
} }
@@ -137,7 +147,8 @@ export class OpenAIClient implements LLMClient {
throw new InvokeError( throw new InvokeError(
InvokeErrorType.UNKNOWN, InvokeErrorType.UNKNOWN,
`Tool "${toolCallName}" not found in tools`, `Tool "${toolCallName}" not found in tools`,
normalizedData undefined,
data
) )
} }
@@ -147,7 +158,8 @@ export class OpenAIClient implements LLMClient {
throw new InvokeError( throw new InvokeError(
InvokeErrorType.INVALID_TOOL_ARGS, InvokeErrorType.INVALID_TOOL_ARGS,
'No tool call arguments found', 'No tool call arguments found',
normalizedData undefined,
data
) )
} }
@@ -158,7 +170,8 @@ export class OpenAIClient implements LLMClient {
throw new InvokeError( throw new InvokeError(
InvokeErrorType.INVALID_TOOL_ARGS, InvokeErrorType.INVALID_TOOL_ARGS,
'Failed to parse tool arguments as JSON', 'Failed to parse tool arguments as JSON',
error error,
data
) )
} }
@@ -169,7 +182,8 @@ export class OpenAIClient implements LLMClient {
throw new InvokeError( throw new InvokeError(
InvokeErrorType.INVALID_TOOL_ARGS, InvokeErrorType.INVALID_TOOL_ARGS,
'Tool arguments validation failed', 'Tool arguments validation failed',
validation.error validation.error,
data
) )
} }
const toolInput = validation.data const toolInput = validation.data
@@ -182,7 +196,8 @@ export class OpenAIClient implements LLMClient {
throw new InvokeError( throw new InvokeError(
InvokeErrorType.TOOL_EXECUTION_ERROR, InvokeErrorType.TOOL_EXECUTION_ERROR,
`Tool execution failed: ${(e as Error).message}`, `Tool execution failed: ${(e as Error).message}`,
e e,
data
) )
} }

View File

@@ -25,14 +25,18 @@ export class InvokeError extends Error {
type: InvokeErrorType type: InvokeErrorType
retryable: boolean retryable: boolean
statusCode?: number statusCode?: number
/* raw error (provided if this error is caused by another error) */
rawError?: unknown rawError?: unknown
/* raw response from the API (provided if this error is caused by an API calling) */
rawResponse?: unknown
constructor(type: InvokeErrorType, message: string, rawError?: unknown) { constructor(type: InvokeErrorType, message: string, rawError?: unknown, rawResponse?: unknown) {
super(message) super(message)
this.name = 'InvokeError' this.name = 'InvokeError'
this.type = type this.type = type
this.retryable = this.isRetryable(type) this.retryable = this.isRetryable(type)
this.rawError = rawError this.rawError = rawError
this.rawResponse = rawResponse
} }
private isRetryable(type: InvokeErrorType): boolean { private isRetryable(type: InvokeErrorType): boolean {

View File

@@ -3,7 +3,7 @@ import { DEFAULT_TEMPERATURE, LLM_MAX_RETRIES } from './constants'
import { InvokeError } from './errors' import { InvokeError } from './errors'
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types' import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } export type { InvokeError, InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> { export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
// Runtime validation as defensive programming (types already guarantee these) // Runtime validation as defensive programming (types already guarantee these)