server-errors.ts 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. export type ConfigInvalidError = {
  2. name: "ConfigInvalidError"
  3. data: {
  4. path?: string
  5. message?: string
  6. issues?: Array<{ message: string; path: string[] }>
  7. }
  8. }
  9. export type ProviderModelNotFoundError = {
  10. name: "ProviderModelNotFoundError"
  11. data: {
  12. providerID: string
  13. modelID: string
  14. suggestions?: string[]
  15. }
  16. }
  17. type Translator = (key: string, vars?: Record<string, string | number>) => string
  18. function tr(translator: Translator | undefined, key: string, text: string, vars?: Record<string, string | number>) {
  19. if (!translator) return text
  20. const out = translator(key, vars)
  21. if (!out || out === key) return text
  22. return out
  23. }
  24. export function formatServerError(error: unknown, translate?: Translator, fallback?: string) {
  25. if (isConfigInvalidErrorLike(error)) return parseReadableConfigInvalidError(error, translate)
  26. if (isProviderModelNotFoundErrorLike(error)) return parseReadableProviderModelNotFoundError(error, translate)
  27. if (error instanceof Error && error.message) return error.message
  28. if (typeof error === "string" && error) return error
  29. if (fallback) return fallback
  30. return tr(translate, "error.chain.unknown", "Unknown error")
  31. }
  32. function isConfigInvalidErrorLike(error: unknown): error is ConfigInvalidError {
  33. if (typeof error !== "object" || error === null) return false
  34. const o = error as Record<string, unknown>
  35. return o.name === "ConfigInvalidError" && typeof o.data === "object" && o.data !== null
  36. }
  37. function isProviderModelNotFoundErrorLike(error: unknown): error is ProviderModelNotFoundError {
  38. if (typeof error !== "object" || error === null) return false
  39. const o = error as Record<string, unknown>
  40. return o.name === "ProviderModelNotFoundError" && typeof o.data === "object" && o.data !== null
  41. }
  42. export function parseReadableConfigInvalidError(errorInput: ConfigInvalidError, translator?: Translator) {
  43. const file = errorInput.data.path && errorInput.data.path !== "config" ? errorInput.data.path : "config"
  44. const detail = errorInput.data.message?.trim() ?? ""
  45. const issues = (errorInput.data.issues ?? [])
  46. .map((issue) => {
  47. const msg = issue.message.trim()
  48. if (!issue.path.length) return msg
  49. return `${issue.path.join(".")}: ${msg}`
  50. })
  51. .filter(Boolean)
  52. const msg = issues.length ? issues.join("\n") : detail
  53. if (!msg) return tr(translator, "error.chain.configInvalid", `Config file at ${file} is invalid`, { path: file })
  54. return tr(translator, "error.chain.configInvalidWithMessage", `Config file at ${file} is invalid: ${msg}`, {
  55. path: file,
  56. message: msg,
  57. })
  58. }
  59. function parseReadableProviderModelNotFoundError(errorInput: ProviderModelNotFoundError, translator?: Translator) {
  60. const p = errorInput.data.providerID.trim()
  61. const m = errorInput.data.modelID.trim()
  62. const list = (errorInput.data.suggestions ?? []).map((v) => v.trim()).filter(Boolean)
  63. const body = tr(translator, "error.chain.modelNotFound", `Model not found: ${p}/${m}`, { provider: p, model: m })
  64. const tail = tr(translator, "error.chain.checkConfig", "Check your config (opencode.json) provider/model names")
  65. if (list.length) {
  66. const suggestions = list.slice(0, 5).join(", ")
  67. return [body, tr(translator, "error.chain.didYouMean", `Did you mean: ${suggestions}`, { suggestions }), tail].join(
  68. "\n",
  69. )
  70. }
  71. return [body, tail].join("\n")
  72. }