diff --git a/package.json b/package.json
index bfd1976f..0bd1560c 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"scripts": {
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
"typecheck": "bun run --filter='*' typecheck",
- "stainless": "./scripts/stainless",
+ "generate": "(cd packages/sdk && ./js/script/generate.ts) && (cd packages/sdk/stainless && ./generate.ts)",
"postinstall": "./script/hooks"
},
"workspaces": {
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index d4a26689..1a3d42b5 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -1120,6 +1120,32 @@ export namespace Server {
),
async (c) => c.json(await callTui(c)),
)
+ .post(
+ "/tui/show-toast",
+ describeRoute({
+ description: "Show a toast notification in the TUI",
+ operationId: "tui.showToast",
+ responses: {
+ 200: {
+ description: "Toast notification shown successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
+ },
+ },
+ },
+ },
+ }),
+ zValidator(
+ "json",
+ z.object({
+ title: z.string().optional(),
+ message: z.string(),
+ variant: z.enum(["info", "success", "warning", "error"]),
+ }),
+ ),
+ async (c) => c.json(await callTui(c)),
+ )
.route("/tui/control", TuiRoute)
.put(
"/auth/:id",
diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml
index e3713a1c..6a6c7c1f 100644
--- a/packages/sdk/go/.stats.yml
+++ b/packages/sdk/go/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 36
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-d0eaf92deaa53a25bbbc3181540ad73ed5a4aec6381ac08d8122e24318e5e455.yml
-openapi_spec_hash: 22196d859c0711e564b9538d988abda6
-config_hash: 8d85a768523cff92b85ef06c443d49fa
+configured_endpoints: 37
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-e438f89b86b573c957866c9b057efacc912e3946be03390e4027e8dfab5b83ba.yml
+openapi_spec_hash: 4e9d257b86172e266dc9d72fb04548bc
+config_hash: a78225c7474eb9ab8745e72a0cfe6f96
diff --git a/packages/sdk/go/api.md b/packages/sdk/go/api.md
index 79a67e42..f48d0687 100644
--- a/packages/sdk/go/api.md
+++ b/packages/sdk/go/api.md
@@ -147,4 +147,5 @@ Methods:
- client.Tui.OpenModels(ctx context.Context) (bool, error)
- client.Tui.OpenSessions(ctx context.Context) (bool, error)
- client.Tui.OpenThemes(ctx context.Context) (bool, error)
+- client.Tui.ShowToast(ctx context.Context, body opencode.TuiShowToastParams) (bool, error)
- client.Tui.SubmitPrompt(ctx context.Context) (bool, error)
diff --git a/packages/sdk/go/tui.go b/packages/sdk/go/tui.go
index 6f36ce54..ab5ed640 100644
--- a/packages/sdk/go/tui.go
+++ b/packages/sdk/go/tui.go
@@ -87,6 +87,14 @@ func (r *TuiService) OpenThemes(ctx context.Context, opts ...option.RequestOptio
return
}
+// Show a toast notification in the TUI
+func (r *TuiService) ShowToast(ctx context.Context, body TuiShowToastParams, opts ...option.RequestOption) (res *bool, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "tui/show-toast"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &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...)
@@ -110,3 +118,30 @@ type TuiExecuteCommandParams struct {
func (r TuiExecuteCommandParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+
+type TuiShowToastParams struct {
+ Message param.Field[string] `json:"message,required"`
+ Variant param.Field[TuiShowToastParamsVariant] `json:"variant,required"`
+ Title param.Field[string] `json:"title"`
+}
+
+func (r TuiShowToastParams) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
+
+type TuiShowToastParamsVariant string
+
+const (
+ TuiShowToastParamsVariantInfo TuiShowToastParamsVariant = "info"
+ TuiShowToastParamsVariantSuccess TuiShowToastParamsVariant = "success"
+ TuiShowToastParamsVariantWarning TuiShowToastParamsVariant = "warning"
+ TuiShowToastParamsVariantError TuiShowToastParamsVariant = "error"
+)
+
+func (r TuiShowToastParamsVariant) IsKnown() bool {
+ switch r {
+ case TuiShowToastParamsVariantInfo, TuiShowToastParamsVariantSuccess, TuiShowToastParamsVariantWarning, TuiShowToastParamsVariantError:
+ return true
+ }
+ return false
+}
diff --git a/packages/sdk/go/tui_test.go b/packages/sdk/go/tui_test.go
index f3260aaf..cb482226 100644
--- a/packages/sdk/go/tui_test.go
+++ b/packages/sdk/go/tui_test.go
@@ -171,6 +171,32 @@ func TestTuiOpenThemes(t *testing.T) {
}
}
+func TestTuiShowToastWithOptionalParams(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.ShowToast(context.TODO(), opencode.TuiShowToastParams{
+ Message: opencode.F("message"),
+ Variant: opencode.F(opencode.TuiShowToastParamsVariantInfo),
+ Title: opencode.F("title"),
+ })
+ 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"
diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts
index d2986238..1e0e1ae5 100644
--- a/packages/sdk/js/src/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/gen/sdk.gen.ts
@@ -77,6 +77,8 @@ import type {
TuiClearPromptResponses,
TuiExecuteCommandData,
TuiExecuteCommandResponses,
+ TuiShowToastData,
+ TuiShowToastResponses,
AuthSetData,
AuthSetResponses,
AuthSetErrors,
@@ -518,6 +520,20 @@ class Tui extends _HeyApiClient {
},
})
}
+
+ /**
+ * Show a toast notification in the TUI
+ */
+ public showToast(options?: Options) {
+ return (options?.client ?? this._client).post({
+ url: "/tui/show-toast",
+ ...options,
+ headers: {
+ "Content-Type": "application/json",
+ ...options?.headers,
+ },
+ })
+ }
}
class Auth extends _HeyApiClient {
diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts
index 54087908..81faf7bf 100644
--- a/packages/sdk/js/src/gen/types.gen.ts
+++ b/packages/sdk/js/src/gen/types.gen.ts
@@ -1911,6 +1911,26 @@ export type TuiExecuteCommandResponses = {
export type TuiExecuteCommandResponse = TuiExecuteCommandResponses[keyof TuiExecuteCommandResponses]
+export type TuiShowToastData = {
+ body?: {
+ title?: string
+ message: string
+ variant: "info" | "success" | "warning" | "error"
+ }
+ path?: never
+ query?: never
+ url: "/tui/show-toast"
+}
+
+export type TuiShowToastResponses = {
+ /**
+ * Toast notification shown successfully
+ */
+ 200: boolean
+}
+
+export type TuiShowToastResponse = TuiShowToastResponses[keyof TuiShowToastResponses]
+
export type AuthSetData = {
body?: Auth
path: {
diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml
index db4afd8d..4ce3cd2a 100644
--- a/packages/sdk/stainless/stainless.yml
+++ b/packages/sdk/stainless/stainless.yml
@@ -146,6 +146,7 @@ resources:
openThemes: post /tui/open-themes
openModels: post /tui/open-models
executeCommand: post /tui/execute-command
+ showToast: post /tui/show-toast
settings:
disable_mock_tests: true
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 7b5fc72c..97e5527b 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -705,6 +705,45 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
updated, cmd := a.executeCommand(commands.Command(command))
a = updated.(Model)
cmds = append(cmds, cmd)
+ case "/tui/show-toast":
+ var body struct {
+ Title string `json:"title,omitempty"`
+ Message string `json:"message"`
+ Variant string `json:"variant"`
+ }
+ json.Unmarshal((msg.Body), &body)
+
+ var toastCmd tea.Cmd
+ switch body.Variant {
+ case "info":
+ if body.Title != "" {
+ toastCmd = toast.NewInfoToast(body.Message, toast.WithTitle(body.Title))
+ } else {
+ toastCmd = toast.NewInfoToast(body.Message)
+ }
+ case "success":
+ if body.Title != "" {
+ toastCmd = toast.NewSuccessToast(body.Message, toast.WithTitle(body.Title))
+ } else {
+ toastCmd = toast.NewSuccessToast(body.Message)
+ }
+ case "warning":
+ if body.Title != "" {
+ toastCmd = toast.NewErrorToast(body.Message, toast.WithTitle(body.Title))
+ } else {
+ toastCmd = toast.NewErrorToast(body.Message)
+ }
+ case "error":
+ if body.Title != "" {
+ toastCmd = toast.NewErrorToast(body.Message, toast.WithTitle(body.Title))
+ } else {
+ toastCmd = toast.NewErrorToast(body.Message)
+ }
+ default:
+ slog.Error("Invalid toast variant", "variant", body.Variant)
+ return a, nil
+ }
+ cmds = append(cmds, toastCmd)
default:
break