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

+ 14 - 0
packages/opencode/src/permission/index.ts

@@ -29,6 +29,10 @@ export namespace Permission {
 
 
   export const Event = {
   export const Event = {
     Updated: Bus.event("permission.updated", Info),
     Updated: Bus.event("permission.updated", Info),
+    Replied: Bus.event(
+      "permission.replied",
+      z.object({ sessionID: z.string(), permissionID: z.string(), response: z.string() }),
+    ),
   }
   }
 
 
   const state = App.state(
   const state = App.state(
@@ -120,9 +124,19 @@ export namespace Permission {
       return
       return
     }
     }
     match.resolve()
     match.resolve()
+    Bus.publish(Event.Replied, {
+      sessionID: input.sessionID,
+      permissionID: input.permissionID,
+      response: input.response,
+    })
     if (input.response === "always") {
     if (input.response === "always") {
       approved[input.sessionID] = approved[input.sessionID] || {}
       approved[input.sessionID] = approved[input.sessionID] || {}
       approved[input.sessionID][input.permissionID] = match.info
       approved[input.sessionID][input.permissionID] = match.info
+      for (const item of Object.values(pending[input.sessionID])) {
+        if ((item.info.pattern ?? item.info.type) === (match.info.pattern ?? match.info.type)) {
+          respond({ sessionID: item.info.sessionID, permissionID: item.info.id, response: input.response })
+        }
+      }
     }
     }
   }
   }
 
 

+ 4 - 4
packages/sdk/go/.stats.yml

@@ -1,4 +1,4 @@
-configured_endpoints: 28
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-3fa00e84a92784c0e12cf47a49cf5ac4eb5556b5b3ad8769ad7b4e7e1bf1b01a.yml
-openapi_spec_hash: 5f98ce812d7feb00e6c2eb7a15dd8887
-config_hash: 7707d73ebbd7ad7042ab70466b39348d
+configured_endpoints: 34
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-2ebd9d5478864042a2e01b4995f42acbc39069fa7fcccd1c2e567366ee6c243d.yml
+openapi_spec_hash: 2a34451b288ea30af1cb61332c417c2a
+config_hash: 11a6f0803eb407367c3f677d3e524c37

+ 6 - 0
packages/sdk/go/api.md

@@ -137,4 +137,10 @@ Methods:
 Methods:
 Methods:
 
 
 - <code title="post /tui/append-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.AppendPrompt">AppendPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
 - <code title="post /tui/append-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.AppendPrompt">AppendPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/clear-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ClearPrompt">ClearPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/execute-command">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ExecuteCommand">ExecuteCommand</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiExecuteCommandParams">TuiExecuteCommandParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
 - <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
 - <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-models">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenModels">OpenModels</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-sessions">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenSessions">OpenSessions</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-themes">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenThemes">OpenThemes</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/submit-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.SubmitPrompt">SubmitPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>

+ 80 - 7
packages/sdk/go/event.go

@@ -55,6 +55,7 @@ type EventListResponse struct {
 	// [EventListResponseEventMessagePartUpdatedProperties],
 	// [EventListResponseEventMessagePartUpdatedProperties],
 	// [EventListResponseEventMessagePartRemovedProperties],
 	// [EventListResponseEventMessagePartRemovedProperties],
 	// [EventListResponseEventStorageWriteProperties], [Permission],
 	// [EventListResponseEventStorageWriteProperties], [Permission],
+	// [EventListResponseEventPermissionRepliedProperties],
 	// [EventListResponseEventFileEditedProperties],
 	// [EventListResponseEventFileEditedProperties],
 	// [EventListResponseEventSessionUpdatedProperties],
 	// [EventListResponseEventSessionUpdatedProperties],
 	// [EventListResponseEventSessionDeletedProperties],
 	// [EventListResponseEventSessionDeletedProperties],
@@ -100,9 +101,10 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
 // [EventListResponseEventMessagePartUpdated],
 // [EventListResponseEventMessagePartUpdated],
 // [EventListResponseEventMessagePartRemoved],
 // [EventListResponseEventMessagePartRemoved],
 // [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
 // [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
-// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated],
-// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
-// [EventListResponseEventSessionError], [EventListResponseEventServerConnected],
+// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
+// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
+// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
+// [EventListResponseEventServerConnected],
 // [EventListResponseEventFileWatcherUpdated],
 // [EventListResponseEventFileWatcherUpdated],
 // [EventListResponseEventIdeInstalled].
 // [EventListResponseEventIdeInstalled].
 func (r EventListResponse) AsUnion() EventListResponseUnion {
 func (r EventListResponse) AsUnion() EventListResponseUnion {
@@ -115,9 +117,10 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
 // [EventListResponseEventMessagePartUpdated],
 // [EventListResponseEventMessagePartUpdated],
 // [EventListResponseEventMessagePartRemoved],
 // [EventListResponseEventMessagePartRemoved],
 // [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
 // [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
-// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated],
-// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
-// [EventListResponseEventSessionError], [EventListResponseEventServerConnected],
+// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
+// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
+// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
+// [EventListResponseEventServerConnected],
 // [EventListResponseEventFileWatcherUpdated] or
 // [EventListResponseEventFileWatcherUpdated] or
 // [EventListResponseEventIdeInstalled].
 // [EventListResponseEventIdeInstalled].
 type EventListResponseUnion interface {
 type EventListResponseUnion interface {
@@ -168,6 +171,11 @@ func init() {
 			Type:               reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
 			Type:               reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
 			DiscriminatorValue: "permission.updated",
 			DiscriminatorValue: "permission.updated",
 		},
 		},
+		apijson.UnionVariant{
+			TypeFilter:         gjson.JSON,
+			Type:               reflect.TypeOf(EventListResponseEventPermissionReplied{}),
+			DiscriminatorValue: "permission.replied",
+		},
 		apijson.UnionVariant{
 		apijson.UnionVariant{
 			TypeFilter:         gjson.JSON,
 			TypeFilter:         gjson.JSON,
 			Type:               reflect.TypeOf(EventListResponseEventFileEdited{}),
 			Type:               reflect.TypeOf(EventListResponseEventFileEdited{}),
@@ -680,6 +688,70 @@ func (r EventListResponseEventPermissionUpdatedType) IsKnown() bool {
 	return false
 	return false
 }
 }
 
 
+type EventListResponseEventPermissionReplied struct {
+	Properties EventListResponseEventPermissionRepliedProperties `json:"properties,required"`
+	Type       EventListResponseEventPermissionRepliedType       `json:"type,required"`
+	JSON       eventListResponseEventPermissionRepliedJSON       `json:"-"`
+}
+
+// eventListResponseEventPermissionRepliedJSON contains the JSON metadata for the
+// struct [EventListResponseEventPermissionReplied]
+type eventListResponseEventPermissionRepliedJSON struct {
+	Properties  apijson.Field
+	Type        apijson.Field
+	raw         string
+	ExtraFields map[string]apijson.Field
+}
+
+func (r *EventListResponseEventPermissionReplied) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventPermissionRepliedJSON) RawJSON() string {
+	return r.raw
+}
+
+func (r EventListResponseEventPermissionReplied) implementsEventListResponse() {}
+
+type EventListResponseEventPermissionRepliedProperties struct {
+	PermissionID string                                                `json:"permissionID,required"`
+	Response     string                                                `json:"response,required"`
+	SessionID    string                                                `json:"sessionID,required"`
+	JSON         eventListResponseEventPermissionRepliedPropertiesJSON `json:"-"`
+}
+
+// eventListResponseEventPermissionRepliedPropertiesJSON contains the JSON metadata
+// for the struct [EventListResponseEventPermissionRepliedProperties]
+type eventListResponseEventPermissionRepliedPropertiesJSON struct {
+	PermissionID apijson.Field
+	Response     apijson.Field
+	SessionID    apijson.Field
+	raw          string
+	ExtraFields  map[string]apijson.Field
+}
+
+func (r *EventListResponseEventPermissionRepliedProperties) UnmarshalJSON(data []byte) (err error) {
+	return apijson.UnmarshalRoot(data, r)
+}
+
+func (r eventListResponseEventPermissionRepliedPropertiesJSON) RawJSON() string {
+	return r.raw
+}
+
+type EventListResponseEventPermissionRepliedType string
+
+const (
+	EventListResponseEventPermissionRepliedTypePermissionReplied EventListResponseEventPermissionRepliedType = "permission.replied"
+)
+
+func (r EventListResponseEventPermissionRepliedType) IsKnown() bool {
+	switch r {
+	case EventListResponseEventPermissionRepliedTypePermissionReplied:
+		return true
+	}
+	return false
+}
+
 type EventListResponseEventFileEdited struct {
 type EventListResponseEventFileEdited struct {
 	Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
 	Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
 	Type       EventListResponseEventFileEditedType       `json:"type,required"`
 	Type       EventListResponseEventFileEditedType       `json:"type,required"`
@@ -1303,6 +1375,7 @@ const (
 	EventListResponseTypeMessagePartRemoved   EventListResponseType = "message.part.removed"
 	EventListResponseTypeMessagePartRemoved   EventListResponseType = "message.part.removed"
 	EventListResponseTypeStorageWrite         EventListResponseType = "storage.write"
 	EventListResponseTypeStorageWrite         EventListResponseType = "storage.write"
 	EventListResponseTypePermissionUpdated    EventListResponseType = "permission.updated"
 	EventListResponseTypePermissionUpdated    EventListResponseType = "permission.updated"
+	EventListResponseTypePermissionReplied    EventListResponseType = "permission.replied"
 	EventListResponseTypeFileEdited           EventListResponseType = "file.edited"
 	EventListResponseTypeFileEdited           EventListResponseType = "file.edited"
 	EventListResponseTypeSessionUpdated       EventListResponseType = "session.updated"
 	EventListResponseTypeSessionUpdated       EventListResponseType = "session.updated"
 	EventListResponseTypeSessionDeleted       EventListResponseType = "session.deleted"
 	EventListResponseTypeSessionDeleted       EventListResponseType = "session.deleted"
@@ -1315,7 +1388,7 @@ const (
 
 
 func (r EventListResponseType) IsKnown() bool {
 func (r EventListResponseType) IsKnown() bool {
 	switch r {
 	switch r {
-	case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
+	case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
 		return true
 		return true
 	}
 	}
 	return false
 	return false

+ 56 - 0
packages/sdk/go/tui.go

@@ -39,6 +39,22 @@ func (r *TuiService) AppendPrompt(ctx context.Context, body TuiAppendPromptParam
 	return
 	return
 }
 }
 
 
+// Clear the prompt
+func (r *TuiService) ClearPrompt(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/clear-prompt"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Execute a TUI command (e.g. switch_mode)
+func (r *TuiService) ExecuteCommand(ctx context.Context, body TuiExecuteCommandParams, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/execute-command"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+	return
+}
+
 // Open the help dialog
 // Open the help dialog
 func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
 func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
 	opts = append(r.Options[:], opts...)
 	opts = append(r.Options[:], opts...)
@@ -47,6 +63,38 @@ func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption)
 	return
 	return
 }
 }
 
 
+// Open the model dialog
+func (r *TuiService) OpenModels(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/open-models"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Open the session dialog
+func (r *TuiService) OpenSessions(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/open-sessions"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Open the theme dialog
+func (r *TuiService) OpenThemes(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/open-themes"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
+// Submit the prompt
+func (r *TuiService) SubmitPrompt(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+	opts = append(r.Options[:], opts...)
+	path := "tui/submit-prompt"
+	err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+	return
+}
+
 type TuiAppendPromptParams struct {
 type TuiAppendPromptParams struct {
 	Text param.Field[string] `json:"text,required"`
 	Text param.Field[string] `json:"text,required"`
 }
 }
@@ -54,3 +102,11 @@ type TuiAppendPromptParams struct {
 func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) {
 func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) {
 	return apijson.MarshalRoot(r)
 	return apijson.MarshalRoot(r)
 }
 }
+
+type TuiExecuteCommandParams struct {
+	Command param.Field[string] `json:"command,required"`
+}
+
+func (r TuiExecuteCommandParams) MarshalJSON() (data []byte, err error) {
+	return apijson.MarshalRoot(r)
+}

+ 134 - 0
packages/sdk/go/tui_test.go

@@ -37,6 +37,52 @@ func TestTuiAppendPrompt(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestTuiClearPrompt(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Tui.ClearPrompt(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestTuiExecuteCommand(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Tui.ExecuteCommand(context.TODO(), opencode.TuiExecuteCommandParams{
+		Command: opencode.F("command"),
+	})
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
 func TestTuiOpenHelp(t *testing.T) {
 func TestTuiOpenHelp(t *testing.T) {
 	t.Skip("skipped: tests are disabled for the time being")
 	t.Skip("skipped: tests are disabled for the time being")
 	baseURL := "http://localhost:4010"
 	baseURL := "http://localhost:4010"
@@ -58,3 +104,91 @@ func TestTuiOpenHelp(t *testing.T) {
 		t.Fatalf("err should be nil: %s", err.Error())
 		t.Fatalf("err should be nil: %s", err.Error())
 	}
 	}
 }
 }
+
+func TestTuiOpenModels(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Tui.OpenModels(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestTuiOpenSessions(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Tui.OpenSessions(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestTuiOpenThemes(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Tui.OpenThemes(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}
+
+func TestTuiSubmitPrompt(t *testing.T) {
+	t.Skip("skipped: tests are disabled for the time being")
+	baseURL := "http://localhost:4010"
+	if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+		baseURL = envURL
+	}
+	if !testutil.CheckTestServer(t, baseURL) {
+		return
+	}
+	client := opencode.NewClient(
+		option.WithBaseURL(baseURL),
+	)
+	_, err := client.Tui.SubmitPrompt(context.TODO())
+	if err != nil {
+		var apierr *opencode.Error
+		if errors.As(err, &apierr) {
+			t.Log(string(apierr.DumpRequest(true)))
+		}
+		t.Fatalf("err should be nil: %s", err.Error())
+	}
+}

+ 1 - 1
packages/tui/internal/components/chat/messages.go

@@ -200,7 +200,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case opencode.EventListResponseEventPermissionUpdated:
 	case opencode.EventListResponseEventPermissionUpdated:
 		m.tail = true
 		m.tail = true
 		return m, m.renderView()
 		return m, m.renderView()
-	case app.PermissionRespondedToMsg:
+	case opencode.EventListResponseEventPermissionReplied:
 		m.tail = true
 		m.tail = true
 		return m, m.renderView()
 		return m, m.renderView()
 	case renderCompleteMsg:
 	case renderCompleteMsg:

+ 15 - 2
packages/tui/internal/tui/tui.go

@@ -143,7 +143,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 						return toast.NewErrorToast("Failed to respond to permission request")
 						return toast.NewErrorToast("Failed to respond to permission request")
 					}
 					}
 					slog.Debug("Responded to permission request", "response", resp)
 					slog.Debug("Responded to permission request", "response", resp)
-					return app.PermissionRespondedToMsg{Response: response}
+					return nil
 				}
 				}
 			}
 			}
 		}
 		}
@@ -522,8 +522,21 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		slog.Debug("permission updated", "session", msg.Properties.SessionID, "permission", msg.Properties.ID)
 		slog.Debug("permission updated", "session", msg.Properties.SessionID, "permission", msg.Properties.ID)
 		a.app.Permissions = append(a.app.Permissions, msg.Properties)
 		a.app.Permissions = append(a.app.Permissions, msg.Properties)
 		a.app.CurrentPermission = a.app.Permissions[0]
 		a.app.CurrentPermission = a.app.Permissions[0]
-		cmds = append(cmds, toast.NewInfoToast(msg.Properties.Title, toast.WithTitle("Permission requested")))
 		a.editor.Blur()
 		a.editor.Blur()
+	case opencode.EventListResponseEventPermissionReplied:
+		index := slices.IndexFunc(a.app.Permissions, func(p opencode.Permission) bool {
+			return p.ID == msg.Properties.PermissionID
+		})
+		if index > -1 {
+			a.app.Permissions = append(a.app.Permissions[:index], a.app.Permissions[index+1:]...)
+		}
+		if a.app.CurrentPermission.ID == msg.Properties.PermissionID {
+			if len(a.app.Permissions) > 0 {
+				a.app.CurrentPermission = a.app.Permissions[0]
+			} else {
+				a.app.CurrentPermission = opencode.Permission{}
+			}
+		}
 	case opencode.EventListResponseEventSessionError:
 	case opencode.EventListResponseEventSessionError:
 		switch err := msg.Properties.Error.AsUnion().(type) {
 		switch err := msg.Properties.Error.AsUnion().(type) {
 		case nil:
 		case nil: