Просмотр исходного кода

better handling of aborting sessions

Dax Raad 7 месяцев назад
Родитель
Сommit
c7a59ee2b1

+ 168 - 139
packages/opencode/src/session/index.ts

@@ -552,167 +552,196 @@ export namespace Session {
         ],
       }),
     })
-    for await (const value of result.fullStream) {
-      l.info("part", {
-        type: value.type,
-      })
-      switch (value.type) {
-        case "start":
-          break
-
-        case "tool-input-start":
-          next.parts.push({
-            type: "tool",
-            tool: value.toolName,
-            id: value.id,
-            state: {
-              status: "pending",
-            },
-          })
-          Bus.publish(MessageV2.Event.PartUpdated, {
-            part: next.parts[next.parts.length - 1],
-            sessionID: next.sessionID,
-            messageID: next.id,
-          })
-          break
-
-        case "tool-input-delta":
-          break
-
-        case "tool-call": {
-          const match = next.parts.find((p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId)
-          if (match) {
-            match.state = {
-              status: "running",
-              input: value.input,
-              time: {
-                start: Date.now(),
+    try {
+      for await (const value of result.fullStream) {
+        l.info("part", {
+          type: value.type,
+        })
+        switch (value.type) {
+          case "start":
+            break
+
+          case "tool-input-start":
+            next.parts.push({
+              type: "tool",
+              tool: value.toolName,
+              id: value.id,
+              state: {
+                status: "pending",
               },
-            }
+            })
             Bus.publish(MessageV2.Event.PartUpdated, {
-              part: match,
+              part: next.parts[next.parts.length - 1],
               sessionID: next.sessionID,
               messageID: next.id,
             })
+            break
+
+          case "tool-input-delta":
+            break
+
+          case "tool-call": {
+            const match = next.parts.find(
+              (p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId,
+            )
+            if (match) {
+              match.state = {
+                status: "running",
+                input: value.input,
+                time: {
+                  start: Date.now(),
+                },
+              }
+              Bus.publish(MessageV2.Event.PartUpdated, {
+                part: match,
+                sessionID: next.sessionID,
+                messageID: next.id,
+              })
+            }
+            break
           }
-          break
-        }
-        case "tool-result": {
-          const match = next.parts.find((p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId)
-          if (match && match.state.status === "running") {
-            match.state = {
-              status: "completed",
-              input: value.input,
-              output: value.output.output,
-              metadata: value.output.metadata,
-              title: value.output.title,
-              time: {
-                start: match.state.time.start,
-                end: Date.now(),
-              },
+          case "tool-result": {
+            const match = next.parts.find(
+              (p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId,
+            )
+            if (match && match.state.status === "running") {
+              match.state = {
+                status: "completed",
+                input: value.input,
+                output: value.output.output,
+                metadata: value.output.metadata,
+                title: value.output.title,
+                time: {
+                  start: match.state.time.start,
+                  end: Date.now(),
+                },
+              }
+              Bus.publish(MessageV2.Event.PartUpdated, {
+                part: match,
+                sessionID: next.sessionID,
+                messageID: next.id,
+              })
             }
-            Bus.publish(MessageV2.Event.PartUpdated, {
-              part: match,
-              sessionID: next.sessionID,
-              messageID: next.id,
-            })
+            break
           }
-          break
-        }
 
-        case "tool-error": {
-          const match = next.parts.find((p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId)
-          if (match && match.state.status === "running") {
-            match.state = {
-              status: "error",
-              input: value.input,
-              error: (value.error as any).toString(),
-              time: {
-                start: match.state.time.start,
-                end: Date.now(),
-              },
+          case "tool-error": {
+            const match = next.parts.find(
+              (p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId,
+            )
+            if (match && match.state.status === "running") {
+              match.state = {
+                status: "error",
+                input: value.input,
+                error: (value.error as any).toString(),
+                time: {
+                  start: match.state.time.start,
+                  end: Date.now(),
+                },
+              }
+              Bus.publish(MessageV2.Event.PartUpdated, {
+                part: match,
+                sessionID: next.sessionID,
+                messageID: next.id,
+              })
             }
+            break
+          }
+
+          case "error":
+            throw value.error
+
+          case "start-step":
+            next.parts.push({
+              type: "step-start",
+            })
+            break
+
+          case "finish-step":
+            const usage = getUsage(model.info, value.usage, value.providerMetadata)
+            next.cost += usage.cost
+            next.tokens = usage.tokens
+            break
+
+          case "text-start":
+            text = {
+              type: "text",
+              text: "",
+            }
+            break
+
+          case "text":
+            if (text.text === "") next.parts.push(text)
+            text.text += value.text
+            break
+
+          case "text-end":
             Bus.publish(MessageV2.Event.PartUpdated, {
-              part: match,
+              part: text,
               sessionID: next.sessionID,
               messageID: next.id,
             })
-          }
-          break
-        }
+            break
 
-        case "error":
-          const e = value.error
-          log.error("", {
-            error: e,
-          })
-          switch (true) {
-            case MessageV2.OutputLengthError.isInstance(e):
-              next.error = e
-              break
-            case LoadAPIKeyError.isInstance(e):
-              next.error = new Provider.AuthError(
-                {
-                  providerID: input.providerID,
-                  message: e.message,
-                },
-                { cause: e },
-              ).toObject()
-              break
-            case e instanceof Error:
-              next.error = new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
-              break
-            default:
-              next.error = new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e })
-          }
-          Bus.publish(Event.Error, {
-            error: next.error,
-          })
-          break
+          case "finish":
+            next.time.completed = Date.now()
+            break
 
-        case "start-step":
-          next.parts.push({
-            type: "step-start",
-          })
-          break
-
-        case "finish-step":
-          const usage = getUsage(model.info, value.usage, value.providerMetadata)
-          next.cost += usage.cost
-          next.tokens = usage.tokens
-          break
-
-        case "text-start":
-          text = {
-            type: "text",
-            text: "",
-          }
+          default:
+            l.info("unhandled", {
+              ...value,
+            })
+            continue
+        }
+        await updateMessage(next)
+      }
+    } catch (e) {
+      log.error("", {
+        error: e,
+      })
+      switch (true) {
+        case e instanceof DOMException && e.name === "AbortError":
+          next.error = new MessageV2.AbortedError(
+            { message: e.message },
+            {
+              cause: e,
+            },
+          ).toObject()
           break
-
-        case "text":
-          if (text.text === "") next.parts.push(text)
-          text.text += value.text
+        case MessageV2.OutputLengthError.isInstance(e):
+          next.error = e
           break
-
-        case "text-end":
-          Bus.publish(MessageV2.Event.PartUpdated, {
-            part: text,
-            sessionID: next.sessionID,
-            messageID: next.id,
-          })
+        case LoadAPIKeyError.isInstance(e):
+          next.error = new Provider.AuthError(
+            {
+              providerID: input.providerID,
+              message: e.message,
+            },
+            { cause: e },
+          ).toObject()
           break
-
-        case "finish":
-          next.time.completed = Date.now()
+        case e instanceof Error:
+          next.error = new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
           break
-
         default:
-          l.info("unhandled", {
-            ...value,
-          })
-          continue
+          next.error = new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e })
+      }
+      Bus.publish(Event.Error, {
+        error: next.error,
+      })
+    }
+    for (const part of next.parts) {
+      if (part.type === "tool" && part.state.status !== "completed") {
+        part.state = {
+          status: "error",
+          error: "Tool execution aborted",
+          time: {
+            start: Date.now(),
+            end: Date.now(),
+          },
+          input: {},
+        }
       }
-      await updateMessage(next)
     }
     next.time.completed = Date.now()
     await updateMessage(next)

+ 7 - 1
packages/opencode/src/session/message-v2.ts

@@ -7,6 +7,7 @@ import { convertToModelMessages, type ModelMessage, type UIMessage } from "ai"
 
 export namespace MessageV2 {
   export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
+  export const AbortedError = NamedError.create("MessageAbortedError", z.object({}))
 
   export const ToolStatePending = z
     .object({
@@ -148,7 +149,12 @@ export namespace MessageV2 {
       completed: z.number().optional(),
     }),
     error: z
-      .discriminatedUnion("name", [Provider.AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema])
+      .discriminatedUnion("name", [
+        Provider.AuthError.Schema,
+        NamedError.Unknown.Schema,
+        OutputLengthError.Schema,
+        AbortedError.Schema,
+      ])
       .optional(),
     system: z.string().array(),
     modelID: z.string(),

+ 2 - 0
packages/tui/internal/components/chat/messages.go

@@ -320,6 +320,8 @@ func (m *messagesComponent) renderView(width int) {
 				error = "Message output length exceeded"
 			case opencode.ProviderAuthError:
 				error = err.Data.Message
+			case opencode.MessageAbortedError:
+				error = "Request was aborted"
 			case opencode.UnknownError:
 				error = err.Data.Message
 			}

+ 7 - 7
packages/tui/sdk/.github/workflows/ci.yml

@@ -2,15 +2,15 @@ name: CI
 on:
   push:
     branches-ignore:
-      - "generated"
-      - "codegen/**"
-      - "integrated/**"
-      - "stl-preview-head/**"
-      - "stl-preview-base/**"
+      - 'generated'
+      - 'codegen/**'
+      - 'integrated/**'
+      - 'stl-preview-head/**'
+      - 'stl-preview-base/**'
   pull_request:
     branches-ignore:
-      - "stl-preview-head/**"
-      - "stl-preview-base/**"
+      - 'stl-preview-head/**'
+      - 'stl-preview-base/**'
 
 jobs:
   lint:

+ 1 - 1
packages/tui/sdk/.release-please-manifest.json

@@ -1,3 +1,3 @@
 {
   ".": "0.1.0-alpha.8"
-}
+}

+ 3 - 3
packages/tui/sdk/.stats.yml

@@ -1,4 +1,4 @@
 configured_endpoints: 20
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-15eeb028f79b9a065b4e54a6ea6a58631e9bd5004f97820f0c79d18e3f8bac84.yml
-openapi_spec_hash: 38c8bacb6c8e4c46852a3e81e3fb9fda
-config_hash: e03e9d1aad76081fa1163086e89f201b
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-4955370de3d0a21bb41c4e51257210b3284deb5bc3dbace6e7572de0d1635c9e.yml
+openapi_spec_hash: b7591d636977423cd7455aa02caa718f
+config_hash: de53ecf98e1038f2cc2fd273b582f082

+ 13 - 12
packages/tui/sdk/CHANGELOG.md

@@ -6,7 +6,7 @@ Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencod
 
 ### Features
 
-- **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519))
+* **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519))
 
 ## 0.1.0-alpha.7 (2025-06-30)
 
@@ -14,12 +14,13 @@ Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencod
 
 ### Features
 
-- **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
-- **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
+* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
+* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
+
 
 ### Chores
 
-- **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
+* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
 
 ## 0.1.0-alpha.6 (2025-06-28)
 
@@ -27,7 +28,7 @@ Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencod
 
 ### Bug Fixes
 
-- don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d))
+* don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d))
 
 ## 0.1.0-alpha.5 (2025-06-27)
 
@@ -35,7 +36,7 @@ Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencod
 
 ### Features
 
-- **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
+* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
 
 ## 0.1.0-alpha.4 (2025-06-27)
 
@@ -43,7 +44,7 @@ Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencod
 
 ### Features
 
-- **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
+* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
 
 ## 0.1.0-alpha.3 (2025-06-27)
 
@@ -51,7 +52,7 @@ Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencod
 
 ### Features
 
-- **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61))
+* **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61))
 
 ## 0.1.0-alpha.2 (2025-06-27)
 
@@ -59,7 +60,7 @@ Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencod
 
 ### Features
 
-- **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6))
+* **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6))
 
 ## 0.1.0-alpha.1 (2025-06-27)
 
@@ -67,6 +68,6 @@ Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencod
 
 ### Features
 
-- **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a))
-- **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
-- **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
+* **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a))
+* **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
+* **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))

+ 9 - 0
packages/tui/sdk/aliases.go

@@ -9,6 +9,15 @@ import (
 
 type Error = apierror.Error
 
+// This is an alias to an internal type.
+type MessageAbortedError = shared.MessageAbortedError
+
+// This is an alias to an internal type.
+type MessageAbortedErrorName = shared.MessageAbortedErrorName
+
+// This is an alias to an internal value.
+const MessageAbortedErrorNameMessageAbortedError = shared.MessageAbortedErrorNameMessageAbortedError
+
 // This is an alias to an internal type.
 type ProviderAuthError = shared.ProviderAuthError
 

+ 1 - 0
packages/tui/sdk/api.md

@@ -1,5 +1,6 @@
 # Shared Response Types
 
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#MessageAbortedError">MessageAbortedError</a>
 - <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#ProviderAuthError">ProviderAuthError</a>
 - <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#UnknownError">UnknownError</a>
 

+ 90 - 82
packages/tui/sdk/event.go

@@ -52,10 +52,10 @@ type EventListResponse struct {
 	// [EventListResponseEventPermissionUpdatedProperties],
 	// [EventListResponseEventFileEditedProperties],
 	// [EventListResponseEventInstallationUpdatedProperties],
-	// [EventListResponseEventStorageWriteProperties],
 	// [EventListResponseEventMessageUpdatedProperties],
 	// [EventListResponseEventMessageRemovedProperties],
 	// [EventListResponseEventMessagePartUpdatedProperties],
+	// [EventListResponseEventStorageWriteProperties],
 	// [EventListResponseEventSessionUpdatedProperties],
 	// [EventListResponseEventSessionDeletedProperties],
 	// [EventListResponseEventSessionIdleProperties],
@@ -96,11 +96,11 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
 // [EventListResponseEventLspClientDiagnostics],
 // [EventListResponseEventPermissionUpdated], [EventListResponseEventFileEdited],
 // [EventListResponseEventInstallationUpdated],
-// [EventListResponseEventStorageWrite], [EventListResponseEventMessageUpdated],
-// [EventListResponseEventMessageRemoved],
+// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
 // [EventListResponseEventMessagePartUpdated],
-// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
-// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
+// [EventListResponseEventStorageWrite], [EventListResponseEventSessionUpdated],
+// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
+// [EventListResponseEventSessionError],
 // [EventListResponseEventFileWatcherUpdated].
 func (r EventListResponse) AsUnion() EventListResponseUnion {
 	return r.union
@@ -109,11 +109,11 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
 // Union satisfied by [EventListResponseEventLspClientDiagnostics],
 // [EventListResponseEventPermissionUpdated], [EventListResponseEventFileEdited],
 // [EventListResponseEventInstallationUpdated],
-// [EventListResponseEventStorageWrite], [EventListResponseEventMessageUpdated],
-// [EventListResponseEventMessageRemoved],
+// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
 // [EventListResponseEventMessagePartUpdated],
-// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
-// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError] or
+// [EventListResponseEventStorageWrite], [EventListResponseEventSessionUpdated],
+// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
+// [EventListResponseEventSessionError] or
 // [EventListResponseEventFileWatcherUpdated].
 type EventListResponseUnion interface {
 	implementsEventListResponse()
@@ -143,11 +143,6 @@ func init() {
 			Type:               reflect.TypeOf(EventListResponseEventInstallationUpdated{}),
 			DiscriminatorValue: "installation.updated",
 		},
-		apijson.UnionVariant{
-			TypeFilter:         gjson.JSON,
-			Type:               reflect.TypeOf(EventListResponseEventStorageWrite{}),
-			DiscriminatorValue: "storage.write",
-		},
 		apijson.UnionVariant{
 			TypeFilter:         gjson.JSON,
 			Type:               reflect.TypeOf(EventListResponseEventMessageUpdated{}),
@@ -163,6 +158,11 @@ func init() {
 			Type:               reflect.TypeOf(EventListResponseEventMessagePartUpdated{}),
 			DiscriminatorValue: "message.part.updated",
 		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventStorageWrite{}),
+			DiscriminatorValue: "storage.write",
+		},
 		apijson.UnionVariant{
 			TypeFilter:         gjson.JSON,
 			Type:               reflect.TypeOf(EventListResponseEventSessionUpdated{}),
@@ -462,68 +462,6 @@ func (r EventListResponseEventInstallationUpdatedType) IsKnown() bool {
 	return false
 }
 
-type EventListResponseEventStorageWrite struct {
-	Properties EventListResponseEventStorageWriteProperties `json:"properties,required"`
-	Type       EventListResponseEventStorageWriteType       `json:"type,required"`
-	JSON       eventListResponseEventStorageWriteJSON       `json:"-"`
-}
-
-// eventListResponseEventStorageWriteJSON contains the JSON metadata for the struct
-// [EventListResponseEventStorageWrite]
-type eventListResponseEventStorageWriteJSON struct {
-	Properties  apijson.Field
-	Type        apijson.Field
-	raw         string
-	ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventStorageWrite) UnmarshalJSON(data []byte) (err error) {
-	return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventStorageWriteJSON) RawJSON() string {
-	return r.raw
-}
-
-func (r EventListResponseEventStorageWrite) implementsEventListResponse() {}
-
-type EventListResponseEventStorageWriteProperties struct {
-	Key     string                                           `json:"key,required"`
-	Content interface{}                                      `json:"content"`
-	JSON    eventListResponseEventStorageWritePropertiesJSON `json:"-"`
-}
-
-// eventListResponseEventStorageWritePropertiesJSON contains the JSON metadata for
-// the struct [EventListResponseEventStorageWriteProperties]
-type eventListResponseEventStorageWritePropertiesJSON struct {
-	Key         apijson.Field
-	Content     apijson.Field
-	raw         string
-	ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventStorageWriteProperties) UnmarshalJSON(data []byte) (err error) {
-	return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventStorageWritePropertiesJSON) RawJSON() string {
-	return r.raw
-}
-
-type EventListResponseEventStorageWriteType string
-
-const (
-	EventListResponseEventStorageWriteTypeStorageWrite EventListResponseEventStorageWriteType = "storage.write"
-)
-
-func (r EventListResponseEventStorageWriteType) IsKnown() bool {
-	switch r {
-	case EventListResponseEventStorageWriteTypeStorageWrite:
-		return true
-	}
-	return false
-}
-
 type EventListResponseEventMessageUpdated struct {
 	Properties EventListResponseEventMessageUpdatedProperties `json:"properties,required"`
 	Type       EventListResponseEventMessageUpdatedType       `json:"type,required"`
@@ -710,6 +648,68 @@ func (r EventListResponseEventMessagePartUpdatedType) IsKnown() bool {
 	return false
 }
 
+type EventListResponseEventStorageWrite struct {
+	Properties EventListResponseEventStorageWriteProperties `json:"properties,required"`
+	Type       EventListResponseEventStorageWriteType       `json:"type,required"`
+	JSON       eventListResponseEventStorageWriteJSON       `json:"-"`
+}
+
+// eventListResponseEventStorageWriteJSON contains the JSON metadata for the struct
+// [EventListResponseEventStorageWrite]
+type eventListResponseEventStorageWriteJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventStorageWrite) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventStorageWriteJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventStorageWrite) implementsEventListResponse() {}
+
+type EventListResponseEventStorageWriteProperties struct {
+	Key     string                                           `json:"key,required"`
+	Content interface{}                                      `json:"content"`
+	JSON    eventListResponseEventStorageWritePropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventStorageWritePropertiesJSON contains the JSON metadata for
+// the struct [EventListResponseEventStorageWriteProperties]
+type eventListResponseEventStorageWritePropertiesJSON struct {
+	Key         apijson.Field
+	Content     apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventStorageWriteProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventStorageWritePropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventStorageWriteType string
+
+const (
+	EventListResponseEventStorageWriteTypeStorageWrite EventListResponseEventStorageWriteType = "storage.write"
+)
+
+func (r EventListResponseEventStorageWriteType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventStorageWriteTypeStorageWrite:
+		return true
+	}
+	return false
+}
+
 type EventListResponseEventSessionUpdated struct {
 	Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
 	Type       EventListResponseEventSessionUpdatedType       `json:"type,required"`
@@ -972,13 +972,15 @@ func (r *EventListResponseEventSessionErrorPropertiesError) UnmarshalJSON(data [
 //
 // Possible runtime types of the union are [shared.ProviderAuthError],
 // [shared.UnknownError],
-// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError].
+// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError],
+// [shared.MessageAbortedError].
 func (r EventListResponseEventSessionErrorPropertiesError) AsUnion() EventListResponseEventSessionErrorPropertiesErrorUnion {
 	return r.union
 }
 
-// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError] or
-// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError].
+// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError],
+// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError] or
+// [shared.MessageAbortedError].
 type EventListResponseEventSessionErrorPropertiesErrorUnion interface {
 	ImplementsEventListResponseEventSessionErrorPropertiesError()
 }
@@ -1002,6 +1004,11 @@ func init() {
 			Type:               reflect.TypeOf(EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError{}),
 			DiscriminatorValue: "MessageOutputLengthError",
 		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.MessageAbortedError{}),
+			DiscriminatorValue: "MessageAbortedError",
+		},
 	)
 }
 
@@ -1052,11 +1059,12 @@ const (
 	EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError        EventListResponseEventSessionErrorPropertiesErrorName = "ProviderAuthError"
 	EventListResponseEventSessionErrorPropertiesErrorNameUnknownError             EventListResponseEventSessionErrorPropertiesErrorName = "UnknownError"
 	EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError EventListResponseEventSessionErrorPropertiesErrorName = "MessageOutputLengthError"
+	EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError      EventListResponseEventSessionErrorPropertiesErrorName = "MessageAbortedError"
 )
 
 func (r EventListResponseEventSessionErrorPropertiesErrorName) IsKnown() bool {
 	switch r {
-	case EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError, EventListResponseEventSessionErrorPropertiesErrorNameUnknownError, EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError:
+	case EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError, EventListResponseEventSessionErrorPropertiesErrorNameUnknownError, EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError, EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError:
 		return true
 	}
 	return false
@@ -1160,10 +1168,10 @@ const (
 	EventListResponseTypePermissionUpdated    EventListResponseType = "permission.updated"
 	EventListResponseTypeFileEdited           EventListResponseType = "file.edited"
 	EventListResponseTypeInstallationUpdated  EventListResponseType = "installation.updated"
-	EventListResponseTypeStorageWrite         EventListResponseType = "storage.write"
 	EventListResponseTypeMessageUpdated       EventListResponseType = "message.updated"
 	EventListResponseTypeMessageRemoved       EventListResponseType = "message.removed"
 	EventListResponseTypeMessagePartUpdated   EventListResponseType = "message.part.updated"
+	EventListResponseTypeStorageWrite         EventListResponseType = "storage.write"
 	EventListResponseTypeSessionUpdated       EventListResponseType = "session.updated"
 	EventListResponseTypeSessionDeleted       EventListResponseType = "session.deleted"
 	EventListResponseTypeSessionIdle          EventListResponseType = "session.idle"
@@ -1173,7 +1181,7 @@ const (
 
 func (r EventListResponseType) IsKnown() bool {
 	switch r {
-	case EventListResponseTypeLspClientDiagnostics, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeInstallationUpdated, EventListResponseTypeStorageWrite, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeFileWatcherUpdated:
+	case EventListResponseTypeLspClientDiagnostics, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeInstallationUpdated, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeStorageWrite, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeFileWatcherUpdated:
 		return true
 	}
 	return false

+ 5 - 2
packages/tui/sdk/release-please-config.json

@@ -60,5 +60,8 @@
     }
   ],
   "release-type": "go",
-  "extra-files": ["internal/version.go", "README.md"]
-}
+  "extra-files": [
+    "internal/version.go",
+    "README.md"
+  ]
+}

+ 20 - 13
packages/tui/sdk/session.go

@@ -340,13 +340,14 @@ func (r *AssistantMessageError) UnmarshalJSON(data []byte) (err error) {
 // the specific types for more type safety.
 //
 // Possible runtime types of the union are [shared.ProviderAuthError],
-// [shared.UnknownError], [AssistantMessageErrorMessageOutputLengthError].
+// [shared.UnknownError], [AssistantMessageErrorMessageOutputLengthError],
+// [shared.MessageAbortedError].
 func (r AssistantMessageError) AsUnion() AssistantMessageErrorUnion {
 	return r.union
 }
 
-// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError] or
-// [AssistantMessageErrorMessageOutputLengthError].
+// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError],
+// [AssistantMessageErrorMessageOutputLengthError] or [shared.MessageAbortedError].
 type AssistantMessageErrorUnion interface {
 	ImplementsAssistantMessageError()
 }
@@ -370,6 +371,11 @@ func init() {
 			Type:               reflect.TypeOf(AssistantMessageErrorMessageOutputLengthError{}),
 			DiscriminatorValue: "MessageOutputLengthError",
 		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(shared.MessageAbortedError{}),
+			DiscriminatorValue: "MessageAbortedError",
+		},
 	)
 }
 
@@ -418,11 +424,12 @@ const (
 	AssistantMessageErrorNameProviderAuthError        AssistantMessageErrorName = "ProviderAuthError"
 	AssistantMessageErrorNameUnknownError             AssistantMessageErrorName = "UnknownError"
 	AssistantMessageErrorNameMessageOutputLengthError AssistantMessageErrorName = "MessageOutputLengthError"
+	AssistantMessageErrorNameMessageAbortedError      AssistantMessageErrorName = "MessageAbortedError"
 )
 
 func (r AssistantMessageErrorName) IsKnown() bool {
 	switch r {
-	case AssistantMessageErrorNameProviderAuthError, AssistantMessageErrorNameUnknownError, AssistantMessageErrorNameMessageOutputLengthError:
+	case AssistantMessageErrorNameProviderAuthError, AssistantMessageErrorNameUnknownError, AssistantMessageErrorNameMessageOutputLengthError, AssistantMessageErrorNameMessageAbortedError:
 		return true
 	}
 	return false
@@ -889,7 +896,7 @@ func (r ToolPart) implementsAssistantMessagePart() {}
 type ToolPartState struct {
 	Status ToolPartStateStatus `json:"status,required"`
 	Error  string              `json:"error"`
-	// This field can have the runtime type of [interface{}].
+	// This field can have the runtime type of [interface{}], [map[string]interface{}].
 	Input interface{} `json:"input"`
 	// This field can have the runtime type of [map[string]interface{}].
 	Metadata interface{} `json:"metadata"`
@@ -1002,24 +1009,24 @@ func (r ToolPartType) IsKnown() bool {
 }
 
 type ToolStateCompleted struct {
+	Input    map[string]interface{}   `json:"input,required"`
 	Metadata map[string]interface{}   `json:"metadata,required"`
 	Output   string                   `json:"output,required"`
 	Status   ToolStateCompletedStatus `json:"status,required"`
 	Time     ToolStateCompletedTime   `json:"time,required"`
 	Title    string                   `json:"title,required"`
-	Input    interface{}              `json:"input"`
 	JSON     toolStateCompletedJSON   `json:"-"`
 }
 
 // toolStateCompletedJSON contains the JSON metadata for the struct
 // [ToolStateCompleted]
 type toolStateCompletedJSON struct {
+	Input       apijson.Field
 	Metadata    apijson.Field
 	Output      apijson.Field
 	Status      apijson.Field
 	Time        apijson.Field
 	Title       apijson.Field
-	Input       apijson.Field
 	raw         string
 	ExtraFields map[string]apijson.Field
 }
@@ -1072,19 +1079,19 @@ func (r toolStateCompletedTimeJSON) RawJSON() string {
 }
 
 type ToolStateError struct {
-	Error  string               `json:"error,required"`
-	Status ToolStateErrorStatus `json:"status,required"`
-	Time   ToolStateErrorTime   `json:"time,required"`
-	Input  interface{}          `json:"input"`
-	JSON   toolStateErrorJSON   `json:"-"`
+	Error  string                 `json:"error,required"`
+	Input  map[string]interface{} `json:"input,required"`
+	Status ToolStateErrorStatus   `json:"status,required"`
+	Time   ToolStateErrorTime     `json:"time,required"`
+	JSON   toolStateErrorJSON     `json:"-"`
 }
 
 // toolStateErrorJSON contains the JSON metadata for the struct [ToolStateError]
 type toolStateErrorJSON struct {
 	Error       apijson.Field
+	Input       apijson.Field
 	Status      apijson.Field
 	Time        apijson.Field
-	Input       apijson.Field
 	raw         string
 	ExtraFields map[string]apijson.Field
 }

+ 41 - 0
packages/tui/sdk/shared/shared.go

@@ -6,6 +6,47 @@ import (
 	"github.com/sst/opencode-sdk-go/internal/apijson"
 )
 
+type MessageAbortedError struct {
+	Data interface{}             `json:"data,required"`
+	Name MessageAbortedErrorName `json:"name,required"`
+	JSON messageAbortedErrorJSON `json:"-"`
+}
+
+// messageAbortedErrorJSON contains the JSON metadata for the struct
+// [MessageAbortedError]
+type messageAbortedErrorJSON struct {
+	Data        apijson.Field
+	Name        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *MessageAbortedError) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r messageAbortedErrorJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r MessageAbortedError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
+
+func (r MessageAbortedError) ImplementsAssistantMessageError() {}
+
+type MessageAbortedErrorName string
+
+const (
+	MessageAbortedErrorNameMessageAbortedError MessageAbortedErrorName = "MessageAbortedError"
+)
+
+func (r MessageAbortedErrorName) IsKnown() bool {
+	switch r {
+	case MessageAbortedErrorNameMessageAbortedError:
+		return true
+	}
+	return false
+}
+
 type ProviderAuthError struct {
 	Data ProviderAuthErrorData `json:"data,required"`
 	Name ProviderAuthErrorName `json:"name,required"`

+ 1 - 0
stainless.yml

@@ -34,6 +34,7 @@ resources:
     models:
       unknownError: UnknownError
       providerAuthError: ProviderAuthError
+      messageAbortedError: MessageAbortedError
 
   event:
     methods: