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

fix: make compact command interruptible (#691)

Co-authored-by: GitHub Action <[email protected]>
Timo Clasen 7 месяцев назад
Родитель
Сommit
1e07384364
2 измененных файлов с 71 добавлено и 13 удалено
  1. 49 11
      packages/opencode/src/session/index.ts
  2. 22 2
      packages/tui/internal/app/app.go

+ 49 - 11
packages/opencode/src/session/index.ts

@@ -873,20 +873,58 @@ export namespace Session {
       },
     })
 
-    for await (const value of result.fullStream) {
-      switch (value.type) {
-        case "text":
-          if (!text) {
-            text = {
-              type: "text",
-              text: value.text,
-            }
-            next.parts.push(text)
-          } else text.text += value.text
-          await updateMessage(next)
+    try {
+      for await (const value of result.fullStream) {
+        switch (value.type) {
+          case "text":
+            if (!text) {
+              text = {
+                type: "text",
+                text: value.text,
+              }
+              next.parts.push(text)
+            } else text.text += value.text
+            await updateMessage(next)
+            break
+        }
+      }
+    } catch (e: any) {
+      log.error("summarize stream 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 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 }).toObject()
       }
+      Bus.publish(Event.Error, {
+        error: next.error,
+      })
     }
+    next.time.completed = Date.now()
+    await updateMessage(next)
   }
 
   function lock(sessionID: string) {

+ 22 - 2
packages/tui/internal/app/app.go

@@ -35,6 +35,7 @@ type App struct {
 	Commands      commands.CommandRegistry
 	InitialModel  *string
 	InitialPrompt *string
+	compactCancel context.CancelFunc
 }
 
 type SessionSelectedMsg = *opencode.Session
@@ -306,13 +307,26 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
 }
 
 func (a *App) CompactSession(ctx context.Context) tea.Cmd {
+	if a.compactCancel != nil {
+		a.compactCancel()
+	}
+
+	compactCtx, cancel := context.WithCancel(ctx)
+	a.compactCancel = cancel
+
 	go func() {
-		_, err := a.Client.Session.Summarize(ctx, a.Session.ID, opencode.SessionSummarizeParams{
+		defer func() {
+			a.compactCancel = nil
+		}()
+
+		_, err := a.Client.Session.Summarize(compactCtx, a.Session.ID, opencode.SessionSummarizeParams{
 			ProviderID: opencode.F(a.Provider.ID),
 			ModelID:    opencode.F(a.Model.ID),
 		})
 		if err != nil {
-			slog.Error("Failed to compact session", "error", err)
+			if compactCtx.Err() != context.Canceled {
+				slog.Error("Failed to compact session", "error", err)
+			}
 		}
 	}()
 	return nil
@@ -415,6 +429,12 @@ func (a *App) SendChatMessage(
 }
 
 func (a *App) Cancel(ctx context.Context, sessionID string) error {
+	// Cancel any running compact operation
+	if a.compactCancel != nil {
+		a.compactCancel()
+		a.compactCancel = nil
+	}
+
 	_, err := a.Client.Session.Abort(ctx, sessionID)
 	if err != nil {
 		slog.Error("Failed to cancel session", "error", err)