Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 63 additions & 35 deletions agents/src/tts/tts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,40 +157,54 @@ export abstract class SynthesizeStream
}

private async mainTask() {
let lastError: unknown;

for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {
try {
return await this.run();
} catch (error) {
} catch (error: unknown) {
lastError = error;

if (error instanceof APIError) {
const retryInterval = this._connOptions._intervalForRetry(i);

if (this._connOptions.maxRetry === 0 || !error.retryable) {
this.emitError({ error, recoverable: false });
throw error;
} else if (i === this._connOptions.maxRetry) {
this.emitError({ error, recoverable: false });
throw new APIConnectionError({
message: `failed to generate TTS completion after ${this._connOptions.maxRetry + 1} attempts`,
options: { retryable: false },
});
} else {
// Don't emit error event for recoverable errors during retry loop
// to avoid ERR_UNHANDLED_ERROR or premature session termination
// Non-retryable error or retries disabled - break immediately
break;
} else if (i < this._connOptions.maxRetry) {
// Retryable error with retries remaining - log and wait
this.logger.warn(
{ tts: this.#tts.label, attempt: i + 1, error },
`failed to synthesize speech, retrying in ${retryInterval}s`,
`failed to synthesize speech, retrying in ${retryInterval}s`,
);
}

if (retryInterval > 0) {
await delay(retryInterval);
if (retryInterval > 0) {
await delay(retryInterval);
}
}
// If i === maxRetry, we break and handle below
} else {
this.emitError({ error: toError(error), recoverable: false });
throw error;
// Non-APIError - break immediately
break;
}
}
}

// Only emit error after all retries are exhausted
if (lastError) {
const error = toError(lastError);
const recoverable = error instanceof APIError && error.retryable;
this.emitError({ error, recoverable });

if (error instanceof APIError && recoverable) {
throw new APIConnectionError({
message: `failed to generate TTS completion after ${this._connOptions.maxRetry + 1} attempts`,
options: { retryable: false },
});
} else {
throw error;
}
}
}

private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {
Expand Down Expand Up @@ -385,40 +399,54 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
}

private async mainTask() {
let lastError: unknown;

for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {
try {
return await this.run();
} catch (error) {
} catch (error: unknown) {
lastError = error;

if (error instanceof APIError) {
const retryInterval = this._connOptions._intervalForRetry(i);

if (this._connOptions.maxRetry === 0 || !error.retryable) {
this.emitError({ error, recoverable: false });
throw error;
} else if (i === this._connOptions.maxRetry) {
this.emitError({ error, recoverable: false });
throw new APIConnectionError({
message: `failed to generate TTS completion after ${this._connOptions.maxRetry + 1} attempts`,
options: { retryable: false },
});
} else {
// Don't emit error event for recoverable errors during retry loop
// to avoid ERR_UNHANDLED_ERROR or premature session termination
// Non-retryable error or retries disabled - break immediately
break;
} else if (i < this._connOptions.maxRetry) {
// Retryable error with retries remaining - log and wait
this.logger.warn(
{ tts: this.#tts.label, attempt: i + 1, error },
`failed to generate TTS completion, retrying in ${retryInterval}s`,
);
}

if (retryInterval > 0) {
await delay(retryInterval);
if (retryInterval > 0) {
await delay(retryInterval);
}
}
// If i === maxRetry, we break and handle below
} else {
this.emitError({ error: toError(error), recoverable: false });
throw error;
// Non-APIError - break immediately
break;
}
}
}

// Only emit error after all retries are exhausted
if (lastError) {
const error = toError(lastError);
const recoverable = error instanceof APIError && error.retryable;
this.emitError({ error, recoverable });

if (error instanceof APIError && recoverable) {
throw new APIConnectionError({
message: `failed to generate TTS completion after ${this._connOptions.maxRetry + 1} attempts`,
options: { retryable: false },
});
} else {
throw error;
}
}
}

private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {
Expand Down