error-interceptor.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. /**
  2. * Wrap whatever the generated client decoded from a non-2xx error body
  3. * into a real `Error` so downstream formatters (TUI, plugins) get a
  4. * useful `.message` instead of `[object Object]` or blank. The original
  5. * parsed body and status live under `.cause` for callers that need
  6. * structured fields.
  7. *
  8. * Only fires when the caller used `{ throwOnError: true }`. Callers that
  9. * read `result.error` directly (the result-tuple path) get the parsed
  10. * body unchanged so existing field-level reads (`.error.name`,
  11. * `JSON.stringify(error)`, etc.) are byte-for-byte identical to before.
  12. */
  13. export function wrapClientError(
  14. error: unknown,
  15. response: Response | undefined,
  16. request: Request | undefined,
  17. opts: { throwOnError?: boolean } | undefined,
  18. ): unknown {
  19. if (!opts?.throwOnError) return error
  20. if (error instanceof Error) return error
  21. // NamedError-shaped responses (the common case for kirincode 4xx) come
  22. // through as POJOs — extract a useful message first, then wrap.
  23. if (typeof error === "object" && error !== null && Object.keys(error).length > 0) {
  24. const obj = error as { data?: { message?: unknown }; message?: unknown; name?: unknown }
  25. const message =
  26. (typeof obj.data?.message === "string" && obj.data.message) ||
  27. (typeof obj.message === "string" && obj.message) ||
  28. (typeof obj.name === "string" && obj.name) ||
  29. describe(request, response)
  30. return new Error(message, { cause: { body: error, status: response?.status } })
  31. }
  32. if (typeof error === "string" && error.length > 0) {
  33. return new Error(error, { cause: { body: error, status: response?.status } })
  34. }
  35. // Empty body / network failure / undefined / null / empty object.
  36. const reason = response ? "(empty response body)" : "network error (no response)"
  37. return new Error(`opencode server ${describe(request, response)}: ${reason}`, {
  38. cause: { body: error, status: response?.status },
  39. })
  40. }
  41. function describe(request: Request | undefined, response: Response | undefined) {
  42. const method = request?.method ?? "?"
  43. const url = request?.url ?? "?"
  44. const status = response?.status
  45. const statusText = response?.statusText
  46. return `${method} ${url}${status ? " → " + status : ""}${statusText ? " " + statusText : ""}`
  47. }