message-v2.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. import { BusEvent } from "@/bus/bus-event"
  2. import z from "zod"
  3. import { NamedError } from "@opencode-ai/util/error"
  4. import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
  5. import { Identifier } from "../id/id"
  6. import { LSP } from "../lsp"
  7. import { Snapshot } from "@/snapshot"
  8. import { fn } from "@/util/fn"
  9. import { Storage } from "@/storage/storage"
  10. import { ProviderTransform } from "@/provider/transform"
  11. import { STATUS_CODES } from "http"
  12. import { iife } from "@/util/iife"
  13. import { type SystemError } from "bun"
  14. import type { Provider } from "@/provider/provider"
  15. export namespace MessageV2 {
  16. export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
  17. export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))
  18. export const AuthError = NamedError.create(
  19. "ProviderAuthError",
  20. z.object({
  21. providerID: z.string(),
  22. message: z.string(),
  23. }),
  24. )
  25. export const APIError = NamedError.create(
  26. "APIError",
  27. z.object({
  28. message: z.string(),
  29. statusCode: z.number().optional(),
  30. isRetryable: z.boolean(),
  31. responseHeaders: z.record(z.string(), z.string()).optional(),
  32. responseBody: z.string().optional(),
  33. metadata: z.record(z.string(), z.string()).optional(),
  34. }),
  35. )
  36. export type APIError = z.infer<typeof APIError.Schema>
  37. const PartBase = z.object({
  38. id: z.string(),
  39. sessionID: z.string(),
  40. messageID: z.string(),
  41. })
  42. export const SnapshotPart = PartBase.extend({
  43. type: z.literal("snapshot"),
  44. snapshot: z.string(),
  45. }).meta({
  46. ref: "SnapshotPart",
  47. })
  48. export type SnapshotPart = z.infer<typeof SnapshotPart>
  49. export const PatchPart = PartBase.extend({
  50. type: z.literal("patch"),
  51. hash: z.string(),
  52. files: z.string().array(),
  53. }).meta({
  54. ref: "PatchPart",
  55. })
  56. export type PatchPart = z.infer<typeof PatchPart>
  57. export const TextPart = PartBase.extend({
  58. type: z.literal("text"),
  59. text: z.string(),
  60. synthetic: z.boolean().optional(),
  61. ignored: z.boolean().optional(),
  62. time: z
  63. .object({
  64. start: z.number(),
  65. end: z.number().optional(),
  66. })
  67. .optional(),
  68. metadata: z.record(z.string(), z.any()).optional(),
  69. }).meta({
  70. ref: "TextPart",
  71. })
  72. export type TextPart = z.infer<typeof TextPart>
  73. export const ReasoningPart = PartBase.extend({
  74. type: z.literal("reasoning"),
  75. text: z.string(),
  76. metadata: z.record(z.string(), z.any()).optional(),
  77. time: z.object({
  78. start: z.number(),
  79. end: z.number().optional(),
  80. }),
  81. }).meta({
  82. ref: "ReasoningPart",
  83. })
  84. export type ReasoningPart = z.infer<typeof ReasoningPart>
  85. const FilePartSourceBase = z.object({
  86. text: z
  87. .object({
  88. value: z.string(),
  89. start: z.number().int(),
  90. end: z.number().int(),
  91. })
  92. .meta({
  93. ref: "FilePartSourceText",
  94. }),
  95. })
  96. export const FileSource = FilePartSourceBase.extend({
  97. type: z.literal("file"),
  98. path: z.string(),
  99. }).meta({
  100. ref: "FileSource",
  101. })
  102. export const SymbolSource = FilePartSourceBase.extend({
  103. type: z.literal("symbol"),
  104. path: z.string(),
  105. range: LSP.Range,
  106. name: z.string(),
  107. kind: z.number().int(),
  108. }).meta({
  109. ref: "SymbolSource",
  110. })
  111. export const ResourceSource = FilePartSourceBase.extend({
  112. type: z.literal("resource"),
  113. clientName: z.string(),
  114. uri: z.string(),
  115. }).meta({
  116. ref: "ResourceSource",
  117. })
  118. export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource, ResourceSource]).meta({
  119. ref: "FilePartSource",
  120. })
  121. export const FilePart = PartBase.extend({
  122. type: z.literal("file"),
  123. mime: z.string(),
  124. filename: z.string().optional(),
  125. url: z.string(),
  126. source: FilePartSource.optional(),
  127. }).meta({
  128. ref: "FilePart",
  129. })
  130. export type FilePart = z.infer<typeof FilePart>
  131. export const AgentPart = PartBase.extend({
  132. type: z.literal("agent"),
  133. name: z.string(),
  134. source: z
  135. .object({
  136. value: z.string(),
  137. start: z.number().int(),
  138. end: z.number().int(),
  139. })
  140. .optional(),
  141. }).meta({
  142. ref: "AgentPart",
  143. })
  144. export type AgentPart = z.infer<typeof AgentPart>
  145. export const CompactionPart = PartBase.extend({
  146. type: z.literal("compaction"),
  147. auto: z.boolean(),
  148. }).meta({
  149. ref: "CompactionPart",
  150. })
  151. export type CompactionPart = z.infer<typeof CompactionPart>
  152. export const SubtaskPart = PartBase.extend({
  153. type: z.literal("subtask"),
  154. prompt: z.string(),
  155. description: z.string(),
  156. agent: z.string(),
  157. model: z
  158. .object({
  159. providerID: z.string(),
  160. modelID: z.string(),
  161. })
  162. .optional(),
  163. command: z.string().optional(),
  164. }).meta({
  165. ref: "SubtaskPart",
  166. })
  167. export type SubtaskPart = z.infer<typeof SubtaskPart>
  168. export const RetryPart = PartBase.extend({
  169. type: z.literal("retry"),
  170. attempt: z.number(),
  171. error: APIError.Schema,
  172. time: z.object({
  173. created: z.number(),
  174. }),
  175. }).meta({
  176. ref: "RetryPart",
  177. })
  178. export type RetryPart = z.infer<typeof RetryPart>
  179. export const StepStartPart = PartBase.extend({
  180. type: z.literal("step-start"),
  181. snapshot: z.string().optional(),
  182. }).meta({
  183. ref: "StepStartPart",
  184. })
  185. export type StepStartPart = z.infer<typeof StepStartPart>
  186. export const StepFinishPart = PartBase.extend({
  187. type: z.literal("step-finish"),
  188. reason: z.string(),
  189. snapshot: z.string().optional(),
  190. cost: z.number(),
  191. tokens: z.object({
  192. input: z.number(),
  193. output: z.number(),
  194. reasoning: z.number(),
  195. cache: z.object({
  196. read: z.number(),
  197. write: z.number(),
  198. }),
  199. }),
  200. }).meta({
  201. ref: "StepFinishPart",
  202. })
  203. export type StepFinishPart = z.infer<typeof StepFinishPart>
  204. export const ToolStatePending = z
  205. .object({
  206. status: z.literal("pending"),
  207. input: z.record(z.string(), z.any()),
  208. raw: z.string(),
  209. })
  210. .meta({
  211. ref: "ToolStatePending",
  212. })
  213. export type ToolStatePending = z.infer<typeof ToolStatePending>
  214. export const ToolStateRunning = z
  215. .object({
  216. status: z.literal("running"),
  217. input: z.record(z.string(), z.any()),
  218. title: z.string().optional(),
  219. metadata: z.record(z.string(), z.any()).optional(),
  220. time: z.object({
  221. start: z.number(),
  222. }),
  223. })
  224. .meta({
  225. ref: "ToolStateRunning",
  226. })
  227. export type ToolStateRunning = z.infer<typeof ToolStateRunning>
  228. export const ToolStateCompleted = z
  229. .object({
  230. status: z.literal("completed"),
  231. input: z.record(z.string(), z.any()),
  232. output: z.string(),
  233. title: z.string(),
  234. metadata: z.record(z.string(), z.any()),
  235. time: z.object({
  236. start: z.number(),
  237. end: z.number(),
  238. compacted: z.number().optional(),
  239. }),
  240. attachments: FilePart.array().optional(),
  241. })
  242. .meta({
  243. ref: "ToolStateCompleted",
  244. })
  245. export type ToolStateCompleted = z.infer<typeof ToolStateCompleted>
  246. export const ToolStateError = z
  247. .object({
  248. status: z.literal("error"),
  249. input: z.record(z.string(), z.any()),
  250. error: z.string(),
  251. metadata: z.record(z.string(), z.any()).optional(),
  252. time: z.object({
  253. start: z.number(),
  254. end: z.number(),
  255. }),
  256. })
  257. .meta({
  258. ref: "ToolStateError",
  259. })
  260. export type ToolStateError = z.infer<typeof ToolStateError>
  261. export const ToolState = z
  262. .discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
  263. .meta({
  264. ref: "ToolState",
  265. })
  266. export const ToolPart = PartBase.extend({
  267. type: z.literal("tool"),
  268. callID: z.string(),
  269. tool: z.string(),
  270. state: ToolState,
  271. metadata: z.record(z.string(), z.any()).optional(),
  272. }).meta({
  273. ref: "ToolPart",
  274. })
  275. export type ToolPart = z.infer<typeof ToolPart>
  276. const Base = z.object({
  277. id: z.string(),
  278. sessionID: z.string(),
  279. })
  280. export const User = Base.extend({
  281. role: z.literal("user"),
  282. time: z.object({
  283. created: z.number(),
  284. }),
  285. summary: z
  286. .object({
  287. title: z.string().optional(),
  288. body: z.string().optional(),
  289. diffs: Snapshot.FileDiff.array(),
  290. })
  291. .optional(),
  292. agent: z.string(),
  293. model: z.object({
  294. providerID: z.string(),
  295. modelID: z.string(),
  296. }),
  297. system: z.string().optional(),
  298. tools: z.record(z.string(), z.boolean()).optional(),
  299. variant: z.string().optional(),
  300. }).meta({
  301. ref: "UserMessage",
  302. })
  303. export type User = z.infer<typeof User>
  304. export const Part = z
  305. .discriminatedUnion("type", [
  306. TextPart,
  307. SubtaskPart,
  308. ReasoningPart,
  309. FilePart,
  310. ToolPart,
  311. StepStartPart,
  312. StepFinishPart,
  313. SnapshotPart,
  314. PatchPart,
  315. AgentPart,
  316. RetryPart,
  317. CompactionPart,
  318. ])
  319. .meta({
  320. ref: "Part",
  321. })
  322. export type Part = z.infer<typeof Part>
  323. export const Assistant = Base.extend({
  324. role: z.literal("assistant"),
  325. time: z.object({
  326. created: z.number(),
  327. completed: z.number().optional(),
  328. }),
  329. error: z
  330. .discriminatedUnion("name", [
  331. AuthError.Schema,
  332. NamedError.Unknown.Schema,
  333. OutputLengthError.Schema,
  334. AbortedError.Schema,
  335. APIError.Schema,
  336. ])
  337. .optional(),
  338. parentID: z.string(),
  339. modelID: z.string(),
  340. providerID: z.string(),
  341. /**
  342. * @deprecated
  343. */
  344. mode: z.string(),
  345. agent: z.string(),
  346. path: z.object({
  347. cwd: z.string(),
  348. root: z.string(),
  349. }),
  350. summary: z.boolean().optional(),
  351. cost: z.number(),
  352. tokens: z.object({
  353. input: z.number(),
  354. output: z.number(),
  355. reasoning: z.number(),
  356. cache: z.object({
  357. read: z.number(),
  358. write: z.number(),
  359. }),
  360. }),
  361. finish: z.string().optional(),
  362. }).meta({
  363. ref: "AssistantMessage",
  364. })
  365. export type Assistant = z.infer<typeof Assistant>
  366. export const Info = z.discriminatedUnion("role", [User, Assistant]).meta({
  367. ref: "Message",
  368. })
  369. export type Info = z.infer<typeof Info>
  370. export const Event = {
  371. Updated: BusEvent.define(
  372. "message.updated",
  373. z.object({
  374. info: Info,
  375. }),
  376. ),
  377. Removed: BusEvent.define(
  378. "message.removed",
  379. z.object({
  380. sessionID: z.string(),
  381. messageID: z.string(),
  382. }),
  383. ),
  384. PartUpdated: BusEvent.define(
  385. "message.part.updated",
  386. z.object({
  387. part: Part,
  388. delta: z.string().optional(),
  389. }),
  390. ),
  391. PartRemoved: BusEvent.define(
  392. "message.part.removed",
  393. z.object({
  394. sessionID: z.string(),
  395. messageID: z.string(),
  396. partID: z.string(),
  397. }),
  398. ),
  399. }
  400. export const WithParts = z.object({
  401. info: Info,
  402. parts: z.array(Part),
  403. })
  404. export type WithParts = z.infer<typeof WithParts>
  405. export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] {
  406. const result: UIMessage[] = []
  407. const toolNames = new Set<string>()
  408. const toModelOutput = (output: unknown) => {
  409. if (typeof output === "string") {
  410. return { type: "text", value: output }
  411. }
  412. if (typeof output === "object") {
  413. const outputObject = output as {
  414. text: string
  415. attachments?: Array<{ mime: string; url: string }>
  416. }
  417. const attachments = (outputObject.attachments ?? []).filter((attachment) => {
  418. return attachment.url.startsWith("data:") && attachment.url.includes(",")
  419. })
  420. return {
  421. type: "content",
  422. value: [
  423. { type: "text", text: outputObject.text },
  424. ...attachments.map((attachment) => ({
  425. type: "media",
  426. mediaType: attachment.mime,
  427. data: iife(() => {
  428. const commaIndex = attachment.url.indexOf(",")
  429. return commaIndex === -1 ? attachment.url : attachment.url.slice(commaIndex + 1)
  430. }),
  431. })),
  432. ],
  433. }
  434. }
  435. return { type: "json", value: output as never }
  436. }
  437. for (const msg of input) {
  438. if (msg.parts.length === 0) continue
  439. if (msg.info.role === "user") {
  440. const userMessage: UIMessage = {
  441. id: msg.info.id,
  442. role: "user",
  443. parts: [],
  444. }
  445. result.push(userMessage)
  446. for (const part of msg.parts) {
  447. if (part.type === "text" && !part.ignored)
  448. userMessage.parts.push({
  449. type: "text",
  450. text: part.text,
  451. })
  452. // text/plain and directory files are converted into text parts, ignore them
  453. if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory")
  454. userMessage.parts.push({
  455. type: "file",
  456. url: part.url,
  457. mediaType: part.mime,
  458. filename: part.filename,
  459. })
  460. if (part.type === "compaction") {
  461. userMessage.parts.push({
  462. type: "text",
  463. text: "What did we do so far?",
  464. })
  465. }
  466. if (part.type === "subtask") {
  467. userMessage.parts.push({
  468. type: "text",
  469. text: "The following tool was executed by the user",
  470. })
  471. }
  472. }
  473. }
  474. if (msg.info.role === "assistant") {
  475. const differentModel = `${model.providerID}/${model.id}` !== `${msg.info.providerID}/${msg.info.modelID}`
  476. if (
  477. msg.info.error &&
  478. !(
  479. MessageV2.AbortedError.isInstance(msg.info.error) &&
  480. msg.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
  481. )
  482. ) {
  483. continue
  484. }
  485. const assistantMessage: UIMessage = {
  486. id: msg.info.id,
  487. role: "assistant",
  488. parts: [],
  489. }
  490. for (const part of msg.parts) {
  491. if (part.type === "text")
  492. assistantMessage.parts.push({
  493. type: "text",
  494. text: part.text,
  495. ...(differentModel ? {} : { providerMetadata: part.metadata }),
  496. })
  497. if (part.type === "step-start")
  498. assistantMessage.parts.push({
  499. type: "step-start",
  500. })
  501. if (part.type === "tool") {
  502. toolNames.add(part.tool)
  503. if (part.state.status === "completed") {
  504. const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output
  505. const attachments = part.state.time.compacted ? [] : (part.state.attachments ?? [])
  506. const output =
  507. attachments.length > 0
  508. ? {
  509. text: outputText,
  510. attachments,
  511. }
  512. : outputText
  513. assistantMessage.parts.push({
  514. type: ("tool-" + part.tool) as `tool-${string}`,
  515. state: "output-available",
  516. toolCallId: part.callID,
  517. input: part.state.input,
  518. output,
  519. ...(differentModel ? {} : { callProviderMetadata: part.metadata }),
  520. })
  521. }
  522. if (part.state.status === "error")
  523. assistantMessage.parts.push({
  524. type: ("tool-" + part.tool) as `tool-${string}`,
  525. state: "output-error",
  526. toolCallId: part.callID,
  527. input: part.state.input,
  528. errorText: part.state.error,
  529. ...(differentModel ? {} : { callProviderMetadata: part.metadata }),
  530. })
  531. // Handle pending/running tool calls to prevent dangling tool_use blocks
  532. // Anthropic/Claude APIs require every tool_use to have a corresponding tool_result
  533. if (part.state.status === "pending" || part.state.status === "running")
  534. assistantMessage.parts.push({
  535. type: ("tool-" + part.tool) as `tool-${string}`,
  536. state: "output-error",
  537. toolCallId: part.callID,
  538. input: part.state.input,
  539. errorText: "[Tool execution was interrupted]",
  540. ...(differentModel ? {} : { callProviderMetadata: part.metadata }),
  541. })
  542. }
  543. if (part.type === "reasoning") {
  544. assistantMessage.parts.push({
  545. type: "reasoning",
  546. text: part.text,
  547. ...(differentModel ? {} : { providerMetadata: part.metadata }),
  548. })
  549. }
  550. }
  551. if (assistantMessage.parts.length > 0) {
  552. result.push(assistantMessage)
  553. }
  554. }
  555. }
  556. const tools = Object.fromEntries(Array.from(toolNames).map((toolName) => [toolName, { toModelOutput }]))
  557. return convertToModelMessages(
  558. result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")),
  559. {
  560. //@ts-expect-error (convertToModelMessages expects a ToolSet but only actually needs tools[name]?.toModelOutput)
  561. tools,
  562. },
  563. )
  564. }
  565. export const stream = fn(Identifier.schema("session"), async function* (sessionID) {
  566. const list = await Array.fromAsync(await Storage.list(["message", sessionID]))
  567. for (let i = list.length - 1; i >= 0; i--) {
  568. yield await get({
  569. sessionID,
  570. messageID: list[i][2],
  571. })
  572. }
  573. })
  574. export const parts = fn(Identifier.schema("message"), async (messageID) => {
  575. const result = [] as MessageV2.Part[]
  576. for (const item of await Storage.list(["part", messageID])) {
  577. const read = await Storage.read<MessageV2.Part>(item)
  578. result.push(read)
  579. }
  580. result.sort((a, b) => (a.id > b.id ? 1 : -1))
  581. return result
  582. })
  583. export const get = fn(
  584. z.object({
  585. sessionID: Identifier.schema("session"),
  586. messageID: Identifier.schema("message"),
  587. }),
  588. async (input): Promise<WithParts> => {
  589. return {
  590. info: await Storage.read<MessageV2.Info>(["message", input.sessionID, input.messageID]),
  591. parts: await parts(input.messageID),
  592. }
  593. },
  594. )
  595. export async function filterCompacted(stream: AsyncIterable<MessageV2.WithParts>) {
  596. const result = [] as MessageV2.WithParts[]
  597. const completed = new Set<string>()
  598. for await (const msg of stream) {
  599. result.push(msg)
  600. if (
  601. msg.info.role === "user" &&
  602. completed.has(msg.info.id) &&
  603. msg.parts.some((part) => part.type === "compaction")
  604. )
  605. break
  606. if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish) completed.add(msg.info.parentID)
  607. }
  608. result.reverse()
  609. return result
  610. }
  611. const isOpenAiErrorRetryable = (e: APICallError) => {
  612. const status = e.statusCode
  613. if (!status) return e.isRetryable
  614. // openai sometimes returns 404 for models that are actually available
  615. return status === 404 || e.isRetryable
  616. }
  617. export function fromError(e: unknown, ctx: { providerID: string }) {
  618. switch (true) {
  619. case e instanceof DOMException && e.name === "AbortError":
  620. return new MessageV2.AbortedError(
  621. { message: e.message },
  622. {
  623. cause: e,
  624. },
  625. ).toObject()
  626. case MessageV2.OutputLengthError.isInstance(e):
  627. return e
  628. case LoadAPIKeyError.isInstance(e):
  629. return new MessageV2.AuthError(
  630. {
  631. providerID: ctx.providerID,
  632. message: e.message,
  633. },
  634. { cause: e },
  635. ).toObject()
  636. case (e as SystemError)?.code === "ECONNRESET":
  637. return new MessageV2.APIError(
  638. {
  639. message: "Connection reset by server",
  640. isRetryable: true,
  641. metadata: {
  642. code: (e as SystemError).code ?? "",
  643. syscall: (e as SystemError).syscall ?? "",
  644. message: (e as SystemError).message ?? "",
  645. },
  646. },
  647. { cause: e },
  648. ).toObject()
  649. case APICallError.isInstance(e):
  650. const message = iife(() => {
  651. let msg = e.message
  652. if (msg === "") {
  653. if (e.responseBody) return e.responseBody
  654. if (e.statusCode) {
  655. const err = STATUS_CODES[e.statusCode]
  656. if (err) return err
  657. }
  658. return "Unknown error"
  659. }
  660. const transformed = ProviderTransform.error(ctx.providerID, e)
  661. if (transformed !== msg) {
  662. return transformed
  663. }
  664. if (!e.responseBody || (e.statusCode && msg !== STATUS_CODES[e.statusCode])) {
  665. return msg
  666. }
  667. try {
  668. const body = JSON.parse(e.responseBody)
  669. // try to extract common error message fields
  670. const errMsg = body.message || body.error || body.error?.message
  671. if (errMsg && typeof errMsg === "string") {
  672. return `${msg}: ${errMsg}`
  673. }
  674. } catch {}
  675. return `${msg}: ${e.responseBody}`
  676. }).trim()
  677. const metadata = e.url ? { url: e.url } : undefined
  678. return new MessageV2.APIError(
  679. {
  680. message,
  681. statusCode: e.statusCode,
  682. isRetryable: ctx.providerID.startsWith("openai") ? isOpenAiErrorRetryable(e) : e.isRetryable,
  683. responseHeaders: e.responseHeaders,
  684. responseBody: e.responseBody,
  685. metadata,
  686. },
  687. { cause: e },
  688. ).toObject()
  689. case e instanceof Error:
  690. return new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
  691. default:
  692. return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e })
  693. }
  694. }
  695. }