Skip to content

Commit 49f6f6a

Browse files
Add retry logic and update fetchResponse to handle network issues (#13)
Co-authored-by: Santiago Morelle <santiago.morelle@abstracta.us>
1 parent a1eaa3f commit 49f6f6a

File tree

5 files changed

+68
-11
lines changed

5 files changed

+68
-11
lines changed

browser-extension/src/background.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AgentSession } from "./scripts/agent-session";
55
import { findAgentSession, saveAgentSession, removeAgentSession, } from "./scripts/agent-session-repository";
66
import { saveAgentPrompts } from "./scripts/prompt-repository";
77
import { BrowserMessage, ToggleSidebar, ActiveTabListener, ActivateAgent, AgentActivation, InteractionSummary, } from "./scripts/browser-message";
8-
import { HttpServiceError } from "./scripts/http";
8+
import { BaseError } from "./scripts/http";
99
import { isActiveTabListener, setTabListenerActive, removeTabListenerStatus, } from "./scripts/tab-listener-status-repository";
1010
import { removeTabState } from "./scripts/tab-state-repository";
1111

@@ -174,8 +174,9 @@ async function asyncProcessRequest(req: RequestEvent) {
174174
sendToTab(
175175
tabId,
176176
new InteractionSummary(
177-
false,
178-
e instanceof HttpServiceError ? e.detail : undefined
177+
false,
178+
e instanceof BaseError ? e.detail : undefined,
179+
e instanceof BaseError ? e.getType() : undefined
179180
)
180181
);
181182
}

browser-extension/src/pages/Index.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,14 @@ const onAgentActivation = (msg: AgentActivation) => {
165165
}
166166
167167
const onInteractionSummary = (msg: InteractionSummary) => {
168-
const text = msg.text ? msg.text : t('interactionSummaryError', { contactEmail: agent.value!.manifest.contactEmail })
168+
let text = msg.text ?? ''
169+
170+
if (!msg.success) {
171+
text = msg.errorType === 'NetworkError' ? t('networkError') : t('interactionSummaryError', {
172+
contactEmail: agent.value!.manifest.contactEmail
173+
})
174+
}
175+
169176
const lastMessage = messages.value[messages.value.length - 1]
170177
const messagePosition = lastMessage.isComplete ? messages.value.length : messages.value.length - 1
171178
messages.value.splice(messagePosition, 0, msg.success ? ChatMessage.agentMessage(text) : ChatMessage.agentErrorMessage(text))
@@ -257,12 +264,14 @@ const sidebarClasses = computed(() => [
257264
"interactionSummaryError": "I could not process some information from the current site. This might impact the information and answers I provide. If the issue persists please contact [support](mailto:{contactEmail}?subject=Interaction%20issue)",
258265
"agentAnswerError": "I am currently unable to complete your request. You can try again and if the issue persists contact [support](mailto:{contactEmail}?subject=Question%20issue)",
259266
"flowStepMissingElement": "I could not find the element '{selector}'. This might be due to recent changes in the page which I am not aware of. Please try again and if the issue persists contact [support](mailto:{contactEmail}?subject=Navigation%20element).",
267+
"networkError": "It seems there is a problem with the network connection. Please check your connection and try again."
260268
},
261269
"es": {
262270
"activationError": "No se pudo activar el Copiloto {agentName}. Puedes intentar de nuevo y si el problema persiste contactar al [soporte del Copiloto {agentName}](mailto:{contactEmail}?subject=Activation%20issue)",
263271
"interactionSummaryError": "No pude procesar informacion generada por la página actual. Esto puede impactar en la información y respuestas que te puedo dar. Si el problema persiste por favor contacta a [soporte](mailto:{contactEmail})?subject=Interaction%20issue",
264272
"agentAnswerError": "Ahora no puedo completar tu pedido. Puedes intentar de nuevo y si el problema persiste contactar a [soporte](mailto:{contactEmail}?subject=Question%20issue)",
265-
"flowStepMissingElement": "No pude encontrar el elemento '{selector}'. Esto puede ser debido a cambios recientes en la página de los cuales no tengo conocimiento. Por favor intenta de nuevo y si el problema persiste contacta a [soporte](mailto:{contactEmail}?subject=Navigation%20element).",
273+
"flowStepMissingElement": "No pude encontrar el elemento '{selector}'. Esto puede ser debido a cambios recientes en la página de los cuales no tengo conocimiento. Por favor intenta de nuevo y si el problema persiste contacta a [soporte](mailto:{contactEmail}?subject=Navigation%20element).",
274+
"networkError": "Parece que hay un problema con la conexión a la red. Por favor verifica tu conexión y vuelve a intentarlo."
266275
}
267276
}
268277
</i18n>

browser-extension/src/scripts/agent-session.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export class AgentSession {
105105

106106
private async pollInteraction(msgSender: (msg: BrowserMessage) => void) {
107107
try {
108-
const summary = await this.agent.solveInteractionSummary(undefined, this.id!, this.authService)
108+
const summary = await this.retryInteractionSummary(undefined, this.id!, this.authService)
109109
if (summary) {
110110
await msgSender(new InteractionSummary(true, summary))
111111
}
@@ -120,7 +120,7 @@ export class AgentSession {
120120
for (const a of actions) {
121121
if (a.recordInteraction) {
122122
const interactionDetail = await this.findInteraction(req, a.recordInteraction)
123-
return await this.agent.solveInteractionSummary(interactionDetail, this.id!, this.authService)
123+
return await this.retryInteractionSummary(interactionDetail, this.id!, this.authService)
124124
}
125125
}
126126
}
@@ -189,4 +189,23 @@ export class AgentSession {
189189
}
190190
}
191191

192+
private async retryInteractionSummary(interactionDetail: any | undefined, sessionId: string, authService?: AuthService, maxRetries = 2, delayMs = 5000): Promise<string | undefined> {
193+
let attempts = 0
194+
let lastError: any
195+
196+
while (attempts <= maxRetries) {
197+
try {
198+
return await this.agent.solveInteractionSummary(interactionDetail, sessionId, authService)
199+
} catch (error) {
200+
lastError = error
201+
attempts++
202+
203+
if (attempts <= maxRetries) {
204+
await new Promise(resolve => setTimeout(resolve, delayMs))
205+
}
206+
}
207+
}
208+
209+
throw lastError
210+
}
192211
}

browser-extension/src/scripts/browser-message.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,17 @@ export class AgentActivation extends BrowserMessage {
102102
export class InteractionSummary extends BrowserMessage {
103103
text?: string
104104
success: boolean
105+
errorType?: string
105106

106-
constructor(success: boolean, text?: string) {
107+
constructor(success: boolean, text?: string, errorType?: string) {
107108
super("interactionSummary")
108109
this.text = text
109110
this.success = success
111+
this.errorType = errorType
110112
}
111113

112114
public static fromJsonObject(obj: any): InteractionSummary {
113-
return new InteractionSummary(obj.success, obj.text)
115+
return new InteractionSummary(obj.success, obj.text, obj.errorType)
114116
}
115117
}
116118

browser-extension/src/scripts/http.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@ export const fetchJson = async (url: string, options?: RequestInit) => {
44
}
55

66
const fetchResponse = async (url: string, options?: RequestInit) => {
7-
let ret = await fetch(url, options)
7+
let ret
8+
9+
try {
10+
ret = await fetch(url, options)
11+
} catch (error) {
12+
// This handles the case where the user is temporarily disconnected from the internet
13+
const partialErrorMessage = "Failed to fetch"
14+
15+
if (error instanceof TypeError && error.message.includes(partialErrorMessage)) {
16+
throw new NetworkError(partialErrorMessage)
17+
}
18+
19+
throw error
20+
}
21+
822
if (ret.status < 200 || ret.status >= 300) {
923
let body = await ret.text()
1024
console.warn(`Problem with ${options?.method ? options.method : 'GET'} ${url}`, { status: ret.status, body: body })
@@ -19,14 +33,26 @@ const fetchResponse = async (url: string, options?: RequestInit) => {
1933
return ret
2034
}
2135

22-
export class HttpServiceError extends Error {
36+
export class BaseError extends Error {
2337
detail?: string
38+
readonly type: string = 'BaseError'
2439

2540
constructor(detail?: string) {
2641
super()
2742
this.detail = detail
2843
}
2944

45+
getType(): string {
46+
return this.type
47+
}
48+
}
49+
50+
export class HttpServiceError extends BaseError {
51+
readonly type: string = 'HttpServiceError'
52+
}
53+
54+
export class NetworkError extends BaseError {
55+
readonly type: string = 'NetworkError'
3056
}
3157

3258
export async function* fetchStreamJson(url: string, options?: RequestInit): AsyncIterable<any> {

0 commit comments

Comments
 (0)