|
|
@@ -72,6 +72,7 @@ export class ExaSearchProvider implements WebSearchProvider {
|
|
|
|
|
|
status(): WebProviderStatus {
|
|
|
if (this.options.apiKey.length === 0) return { available: false, reason: 'missing-credential' }
|
|
|
+ if (!isValidBaseUrl(this.options.baseURL)) return { available: false, reason: 'misconfigured' }
|
|
|
return { available: true }
|
|
|
}
|
|
|
|
|
|
@@ -105,11 +106,14 @@ export class ExaSearchProvider implements WebSearchProvider {
|
|
|
const parsed = await response.json() as ExaError
|
|
|
const detail = parsed.error ?? parsed.message
|
|
|
if (detail !== undefined && detail.length > 0) message = detail
|
|
|
- } catch {
|
|
|
- // The HTTP status is already captured in `message` above; a malformed or
|
|
|
- // non-JSON error body (normal for gateway 5xx/429s) can only cost a
|
|
|
- // richer provider message, never the real error. `response.json()` is
|
|
|
- // the sole statement and nothing else of consequence reaches here.
|
|
|
+ } catch (error: unknown) {
|
|
|
+ // An abort fired mid-body must surface as WEB_ABORTED, not be swallowed
|
|
|
+ // into a generic HTTP-error message — cancellation is not a provider
|
|
|
+ // error (the seam's cancellation contract).
|
|
|
+ if (isAbortError(error)) throw new WebError('Exa search aborted', 'WEB_ABORTED', { cause: error })
|
|
|
+ // Otherwise: the HTTP status is already captured in `message` above; a
|
|
|
+ // malformed/non-JSON error body (normal for gateway 5xx/429s) can only
|
|
|
+ // cost a richer provider message, never the real error.
|
|
|
}
|
|
|
throw new WebError(message, 'WEB_PROVIDER_ERROR')
|
|
|
}
|
|
|
@@ -118,12 +122,18 @@ export class ExaSearchProvider implements WebSearchProvider {
|
|
|
try {
|
|
|
payload = await response.json() as ExaSearchResponse
|
|
|
} catch (error: unknown) {
|
|
|
+ if (isAbortError(error)) throw new WebError('Exa search aborted', 'WEB_ABORTED', { cause: error })
|
|
|
throw new WebError(`Exa returned an unparseable response body: ${String(error)}`, 'WEB_PROVIDER_ERROR', { cause: error })
|
|
|
}
|
|
|
return mapExaResponse(request.query, payload)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/** True when `baseURL` parses as an absolute URL (a cheap local config check). */
|
|
|
+function isValidBaseUrl(baseURL: string): boolean {
|
|
|
+ return URL.canParse(baseURL)
|
|
|
+}
|
|
|
+
|
|
|
/** True for a fetch/`AbortSignal` abort, surfaced as `WEB_ABORTED`. */
|
|
|
function isAbortError(error: unknown): boolean {
|
|
|
return error instanceof DOMException && error.name === 'AbortError'
|