summaryrefslogtreecommitdiffhomepage
path: root/packages/tui
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tui')
-rw-r--r--packages/tui/internal/app/app.go42
-rw-r--r--packages/tui/internal/components/chat/editor.go13
-rw-r--r--packages/tui/internal/components/chat/message.go126
-rw-r--r--packages/tui/internal/components/chat/messages.go53
-rw-r--r--packages/tui/internal/components/dialog/session.go2
-rw-r--r--packages/tui/internal/tui/tui.go66
-rw-r--r--packages/tui/sdk/.stats.yml8
-rw-r--r--packages/tui/sdk/api.md12
-rw-r--r--packages/tui/sdk/event.go59
-rw-r--r--packages/tui/sdk/session.go43
-rw-r--r--packages/tui/sdk/session_test.go26
-rw-r--r--packages/tui/sdk/sessionpermission.go126
-rw-r--r--packages/tui/sdk/sessionpermission_test.go43
13 files changed, 490 insertions, 129 deletions
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index df4e209cf..672bc0ee6 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -26,26 +26,28 @@ type Message struct {
}
type App struct {
- Info opencode.App
- Modes []opencode.Mode
- Providers []opencode.Provider
- Version string
- StatePath string
- Config *opencode.Config
- Client *opencode.Client
- State *State
- ModeIndex int
- Mode *opencode.Mode
- Provider *opencode.Provider
- Model *opencode.Model
- Session *opencode.Session
- Messages []Message
- Commands commands.CommandRegistry
- InitialModel *string
- InitialPrompt *string
- IntitialMode *string
- compactCancel context.CancelFunc
- IsLeaderSequence bool
+ Info opencode.App
+ Modes []opencode.Mode
+ Providers []opencode.Provider
+ Version string
+ StatePath string
+ Config *opencode.Config
+ Client *opencode.Client
+ State *State
+ ModeIndex int
+ Mode *opencode.Mode
+ Provider *opencode.Provider
+ Model *opencode.Model
+ Session *opencode.Session
+ Messages []Message
+ Permissions []opencode.Permission
+ CurrentPermission opencode.Permission
+ Commands commands.CommandRegistry
+ InitialModel *string
+ InitialPrompt *string
+ IntitialMode *string
+ compactCancel context.CancelFunc
+ IsLeaderSequence bool
}
type SessionCreatedMsg = struct {
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 009a7ab6c..677d903fd 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -344,9 +344,13 @@ func (m *editorComponent) Content() string {
hint = base(keyText+" again") + muted(" to exit")
} else if m.app.IsBusy() {
keyText := m.getInterruptKeyText()
- if m.interruptKeyInDebounce {
+ status := "working"
+ if m.app.CurrentPermission.ID != "" {
+ status = "waiting for permission"
+ }
+ if m.interruptKeyInDebounce && m.app.CurrentPermission.ID == "" {
hint = muted(
- "working",
+ status,
) + m.spinner.View() + muted(
" ",
) + base(
@@ -355,7 +359,10 @@ func (m *editorComponent) Content() string {
" interrupt",
)
} else {
- hint = muted("working") + m.spinner.View() + muted(" ") + base(keyText) + muted(" interrupt")
+ hint = muted(status) + m.spinner.View()
+ if m.app.CurrentPermission.ID == "" {
+ hint += muted(" ") + base(keyText) + muted(" interrupt")
+ }
}
}
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index 5c92dee5c..10e1b0692 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -3,6 +3,7 @@ package chat
import (
"encoding/json"
"fmt"
+ "maps"
"slices"
"strings"
"time"
@@ -22,16 +23,17 @@ import (
)
type blockRenderer struct {
- textColor compat.AdaptiveColor
- border bool
- borderColor *compat.AdaptiveColor
- borderColorRight bool
- paddingTop int
- paddingBottom int
- paddingLeft int
- paddingRight int
- marginTop int
- marginBottom int
+ textColor compat.AdaptiveColor
+ border bool
+ borderColor *compat.AdaptiveColor
+ borderLeft bool
+ borderRight bool
+ paddingTop int
+ paddingBottom int
+ paddingLeft int
+ paddingRight int
+ marginTop int
+ marginBottom int
}
type renderingOption func(*blockRenderer)
@@ -54,10 +56,26 @@ func WithBorderColor(color compat.AdaptiveColor) renderingOption {
}
}
-func WithBorderColorRight(color compat.AdaptiveColor) renderingOption {
+func WithBorderLeft() renderingOption {
return func(c *blockRenderer) {
- c.borderColorRight = true
- c.borderColor = &color
+ c.borderLeft = true
+ c.borderRight = false
+ }
+}
+
+func WithBorderRight() renderingOption {
+ return func(c *blockRenderer) {
+ c.borderLeft = false
+ c.borderRight = true
+ }
+}
+
+func WithBorderBoth(value bool) renderingOption {
+ return func(c *blockRenderer) {
+ if value {
+ c.borderLeft = true
+ c.borderRight = true
+ }
}
}
@@ -116,6 +134,8 @@ func renderContentBlock(
renderer := &blockRenderer{
textColor: t.TextMuted(),
border: true,
+ borderLeft: true,
+ borderRight: false,
paddingTop: 1,
paddingBottom: 1,
paddingLeft: 2,
@@ -144,19 +164,17 @@ func renderContentBlock(
BorderStyle(lipgloss.ThickBorder()).
BorderLeft(true).
BorderRight(true).
- BorderLeftForeground(borderColor).
+ BorderLeftForeground(t.BackgroundPanel()).
BorderLeftBackground(t.Background()).
BorderRightForeground(t.BackgroundPanel()).
BorderRightBackground(t.Background())
- if renderer.borderColorRight {
- style = style.
- BorderLeftBackground(t.Background()).
- BorderLeftForeground(t.BackgroundPanel()).
- BorderRightForeground(borderColor).
- BorderRightBackground(t.Background())
+ if renderer.borderLeft {
+ style = style.BorderLeftForeground(borderColor)
+ }
+ if renderer.borderRight {
+ style = style.BorderRightForeground(borderColor)
}
-
}
content = style.Render(content)
@@ -223,7 +241,7 @@ func renderText(
if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 {
content = content + "\n\n"
for _, toolCall := range toolCalls {
- title := renderToolTitle(toolCall, width)
+ title := renderToolTitle(toolCall, width-2)
style := styles.NewStyle()
if toolCall.State.Status == opencode.ToolPartStateStatusError {
style = style.Foreground(t.Error())
@@ -247,7 +265,8 @@ func renderText(
content,
width,
WithTextColor(t.Text()),
- WithBorderColorRight(t.Secondary()),
+ WithBorderColor(t.Secondary()),
+ WithBorderRight(),
)
case opencode.AssistantMessage:
return renderContentBlock(
@@ -263,6 +282,7 @@ func renderText(
func renderToolDetails(
app *app.App,
toolCall opencode.ToolPart,
+ permission opencode.Permission,
width int,
) string {
measure := util.Measure("chat.renderToolDetails")
@@ -301,6 +321,39 @@ func renderToolDetails(
borderColor := t.BackgroundPanel()
defaultStyle := styles.NewStyle().Background(backgroundColor).Width(width - 6).Render
+ permissionContent := ""
+ if permission.ID != "" {
+ borderColor = t.Warning()
+
+ base := styles.NewStyle().Background(backgroundColor)
+ text := base.Foreground(t.Text()).Bold(true).Render
+ muted := base.Foreground(t.TextMuted()).Render
+ permissionContent = "Permission required to run this tool:\n\n"
+ permissionContent += text(
+ "enter ",
+ ) + muted(
+ "accept ",
+ ) + text(
+ "a",
+ ) + muted(
+ " accept always ",
+ ) + text(
+ "esc",
+ ) + muted(
+ " reject",
+ )
+
+ }
+
+ if permission.Metadata != nil {
+ metadata := toolCall.State.Metadata.(map[string]any)
+ if metadata == nil {
+ metadata = map[string]any{}
+ }
+ maps.Copy(metadata, permission.Metadata)
+ toolCall.State.Metadata = metadata
+ }
+
if toolCall.State.Metadata != nil {
metadata := toolCall.State.Metadata.(map[string]any)
switch toolCall.Tool {
@@ -351,12 +404,20 @@ func renderToolDetails(
title := renderToolTitle(toolCall, width)
title = style.Render(title)
content := title + "\n" + body
+ if permissionContent != "" {
+ permissionContent = styles.NewStyle().
+ Background(backgroundColor).
+ Padding(1, 2).
+ Render(permissionContent)
+ content += "\n" + permissionContent
+ }
content = renderContentBlock(
app,
content,
width,
WithPadding(0),
WithBorderColor(borderColor),
+ WithBorderBoth(permission.ID != ""),
)
return content
}
@@ -417,7 +478,7 @@ func renderToolDetails(
data, _ := json.Marshal(item)
var toolCall opencode.ToolPart
_ = json.Unmarshal(data, &toolCall)
- step := renderToolTitle(toolCall, width)
+ step := renderToolTitle(toolCall, width-2)
step = "∟ " + step
steps = append(steps, step)
}
@@ -460,7 +521,18 @@ func renderToolDetails(
title := renderToolTitle(toolCall, width)
content := title + "\n\n" + body
- return renderContentBlock(app, content, width, WithBorderColor(borderColor))
+
+ if permissionContent != "" {
+ content += "\n\n\n" + permissionContent
+ }
+
+ return renderContentBlock(
+ app,
+ content,
+ width,
+ WithBorderColor(borderColor),
+ WithBorderBoth(permission.ID != ""),
+ )
}
func renderToolName(name string) string {
@@ -575,6 +647,10 @@ func renderToolTitle(
}
title = truncate.StringWithTail(title, uint(width-6), "...")
+ if toolCall.State.Error != "" {
+ t := theme.CurrentTheme()
+ title = styles.NewStyle().Foreground(t.Error()).Render(title)
+ }
return title
}
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index e675a35d2..96ea8241d 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -100,8 +100,6 @@ func (m *messagesComponent) Init() tea.Cmd {
}
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- measure := util.Measure("messages.Update")
- defer measure("from", fmt.Sprintf("%T", msg))
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.MouseClickMsg:
@@ -199,6 +197,9 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.cache.Clear()
cmds = append(cmds, m.renderView())
}
+ case opencode.EventListResponseEventPermissionUpdated:
+ m.tail = true
+ return m, m.renderView()
case renderCompleteMsg:
m.partCount = msg.partCount
m.lineCount = msg.lineCount
@@ -214,6 +215,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.tail = m.viewport.AtBottom()
+
viewport, cmd := m.viewport.Update(msg)
m.viewport = viewport
cmds = append(cmds, cmd)
@@ -465,7 +467,13 @@ func (m *messagesComponent) renderView() tea.Cmd {
revertedToolCount++
continue
}
- if !m.showToolDetails {
+
+ permission := opencode.Permission{}
+ if m.app.CurrentPermission.ToolCallID == part.CallID {
+ permission = m.app.CurrentPermission
+ }
+
+ if !m.showToolDetails && permission.ID == "" {
if !hasTextPart {
orphanedToolCalls = append(orphanedToolCalls, part)
}
@@ -477,12 +485,14 @@ func (m *messagesComponent) renderView() tea.Cmd {
part.ID,
m.showToolDetails,
width,
+ permission.ID,
)
content, cached = m.cache.Get(key)
if !cached {
content = renderToolDetails(
m.app,
part,
+ permission,
width,
)
content = lipgloss.PlaceHorizontal(
@@ -498,6 +508,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
content = renderToolDetails(
m.app,
part,
+ permission,
width,
)
content = lipgloss.PlaceHorizontal(
@@ -618,6 +629,40 @@ func (m *messagesComponent) renderView() tea.Cmd {
blocks = append(blocks, content)
}
+ if m.app.CurrentPermission.ID != "" &&
+ m.app.CurrentPermission.SessionID != m.app.Session.ID {
+ response, err := m.app.Client.Session.Message(
+ context.Background(),
+ m.app.CurrentPermission.SessionID,
+ m.app.CurrentPermission.MessageID,
+ )
+ if err != nil || response == nil {
+ slog.Error("Failed to get message from child session", "error", err)
+ } else {
+ for _, part := range response.Parts {
+ if part.CallID == m.app.CurrentPermission.ToolCallID {
+ content := renderToolDetails(
+ m.app,
+ part.AsUnion().(opencode.ToolPart),
+ m.app.CurrentPermission,
+ width,
+ )
+ content = lipgloss.PlaceHorizontal(
+ m.width,
+ lipgloss.Center,
+ content,
+ styles.WhitespaceStyle(t.Background()),
+ )
+ if content != "" {
+ partCount++
+ lineCount += lipgloss.Height(content) + 1
+ blocks = append(blocks, content)
+ }
+ }
+ }
+ }
+ }
+
final := []string{}
clipboard := []string{}
var selection *selection
@@ -846,9 +891,7 @@ func (m *messagesComponent) View() string {
)
}
- measure := util.Measure("messages.View")
viewport := m.viewport.View()
- measure()
return styles.NewStyle().
Background(t.Background()).
Render(m.header + "\n" + viewport)
diff --git a/packages/tui/internal/components/dialog/session.go b/packages/tui/internal/components/dialog/session.go
index 307897bc5..daf7a142b 100644
--- a/packages/tui/internal/components/dialog/session.go
+++ b/packages/tui/internal/components/dialog/session.go
@@ -138,8 +138,6 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
)
}
case "n":
- s.app.Session = &opencode.Session{}
- s.app.Messages = []app.Message{}
return s, tea.Sequence(
util.CmdHandler(modal.CloseModalMsg{}),
util.CmdHandler(app.SessionClearedMsg{}),
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 76b96a8e4..9b6ec7ea6 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -103,9 +103,6 @@ func (a Model) Init() tea.Cmd {
}
func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- measure := util.Measure("app.Update")
- defer measure("from", fmt.Sprintf("%T", msg))
-
var cmd tea.Cmd
var cmds []tea.Cmd
@@ -113,6 +110,45 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyPressMsg:
keyString := msg.String()
+ if a.app.CurrentPermission.ID != "" {
+ if keyString == "enter" || keyString == "esc" || keyString == "a" {
+ sessionID := a.app.CurrentPermission.SessionID
+ permissionID := a.app.CurrentPermission.ID
+ a.editor.Focus()
+ a.app.Permissions = a.app.Permissions[1:]
+ if len(a.app.Permissions) > 0 {
+ a.app.CurrentPermission = a.app.Permissions[0]
+ } else {
+ a.app.CurrentPermission = opencode.Permission{}
+ }
+
+ response := opencode.SessionPermissionRespondParamsResponseOnce
+ switch keyString {
+ case "enter":
+ response = opencode.SessionPermissionRespondParamsResponseOnce
+ case "a":
+ response = opencode.SessionPermissionRespondParamsResponseAlways
+ case "esc":
+ response = opencode.SessionPermissionRespondParamsResponseReject
+ }
+
+ return a, func() tea.Msg {
+ resp, err := a.app.Client.Session.Permissions.Respond(
+ context.Background(),
+ sessionID,
+ permissionID,
+ opencode.SessionPermissionRespondParams{Response: opencode.F(response)},
+ )
+ if err != nil {
+ slog.Error("Failed to respond to permission request", "error", err)
+ return toast.NewErrorToast("Failed to respond to permission request")
+ }
+ slog.Debug("Responded to permission request", "response", resp)
+ return nil
+ }
+ }
+ }
+
// 1. Handle active modal
if a.modal != nil {
switch keyString {
@@ -341,6 +377,9 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
updated, cmd := a.editor.Focus()
a.editor = updated.(chat.EditorComponent)
cmds = append(cmds, cmd)
+ case app.SessionClearedMsg:
+ a.app.Session = &opencode.Session{}
+ a.app.Messages = []app.Message{}
case dialog.CompletionDialogCloseMsg:
a.showCompletionDialog = false
case opencode.EventListResponseEventInstallationUpdated:
@@ -364,7 +403,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.app.Session = &msg.Properties.Info
}
case opencode.EventListResponseEventMessagePartUpdated:
- slog.Info("message part updated", "message", msg.Properties.Part.MessageID, "part", msg.Properties.Part.ID)
+ slog.Debug("message part updated", "message", msg.Properties.Part.MessageID, "part", msg.Properties.Part.ID)
if msg.Properties.Part.SessionID == a.app.Session.ID {
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
switch casted := m.Info.(type) {
@@ -402,7 +441,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
case opencode.EventListResponseEventMessagePartRemoved:
- slog.Info("message part removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID, "part", msg.Properties.PartID)
+ slog.Debug("message part removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID, "part", msg.Properties.PartID)
if msg.Properties.SessionID == a.app.Session.ID {
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
switch casted := m.Info.(type) {
@@ -438,7 +477,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
case opencode.EventListResponseEventMessageRemoved:
- slog.Info("message removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID)
+ slog.Debug("message removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID)
if msg.Properties.SessionID == a.app.Session.ID {
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
switch casted := m.Info.(type) {
@@ -480,6 +519,12 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
})
}
}
+ case opencode.EventListResponseEventPermissionUpdated:
+ slog.Debug("permission updated", "session", msg.Properties.SessionID, "permission", msg.Properties.ID)
+ a.app.Permissions = append(a.app.Permissions, msg.Properties)
+ a.app.CurrentPermission = a.app.Permissions[0]
+ cmds = append(cmds, toast.NewInfoToast(msg.Properties.Title, toast.WithTitle("Permission requested")))
+ a.editor.Blur()
case opencode.EventListResponseEventSessionError:
switch err := msg.Properties.Error.AsUnion().(type) {
case nil:
@@ -613,8 +658,6 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (a Model) View() string {
- measure := util.Measure("app.View")
- defer measure()
t := theme.CurrentTheme()
var mainLayout string
@@ -674,8 +717,6 @@ func (a Model) openFile(filepath string) (tea.Model, tea.Cmd) {
}
func (a Model) home() string {
- measure := util.Measure("home.View")
- defer measure()
t := theme.CurrentTheme()
effectiveWidth := a.width - 4
baseStyle := styles.NewStyle().Background(t.Background())
@@ -796,8 +837,6 @@ func (a Model) home() string {
}
func (a Model) chat() string {
- measure := util.Measure("chat.View")
- defer measure()
effectiveWidth := a.width - 4
t := theme.CurrentTheme()
editorView := a.editor.View()
@@ -911,9 +950,8 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
if a.app.Session.ID == "" {
return a, nil
}
- a.app.Session = &opencode.Session{}
- a.app.Messages = []app.Message{}
cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{}))
+
case commands.SessionListCommand:
sessionDialog := dialog.NewSessionDialog(a.app)
a.modal = sessionDialog
diff --git a/packages/tui/sdk/.stats.yml b/packages/tui/sdk/.stats.yml
index 3f719fabf..d69918800 100644
--- a/packages/tui/sdk/.stats.yml
+++ b/packages/tui/sdk/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 26
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-62d8fccba4eb8dc3a80434e0849eab3352e49fb96a718bb7b6d17ed8e582b716.yml
-openapi_spec_hash: 4ff9376cf9634e91731e63fe482ea532
-config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3
+configured_endpoints: 28
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-90f0ff2a2f214a34b74f49a5909e95c31f617bd9bb881da24ab3fe664424c79d.yml
+openapi_spec_hash: 5ef69219c1869f78455b0c5374f638f8
+config_hash: 7707d73ebbd7ad7042ab70466b39348d
diff --git a/packages/tui/sdk/api.md b/packages/tui/sdk/api.md
index fb3db9c53..0291c776e 100644
--- a/packages/tui/sdk/api.md
+++ b/packages/tui/sdk/api.md
@@ -103,6 +103,7 @@ Response Types:
- <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#ToolStatePending">ToolStatePending</a>
- <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#ToolStateRunning">ToolStateRunning</a>
- <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#UserMessage">UserMessage</a>
+- <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#SessionMessageResponse">SessionMessageResponse</a>
- <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#SessionMessagesResponse">SessionMessagesResponse</a>
Methods:
@@ -113,6 +114,7 @@ Methods:
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</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 /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</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#SessionChatParams">SessionChatParams</a>) (<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#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</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#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>) (<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#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<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#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</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#SessionRevertParams">SessionRevertParams</a>) (<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#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<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#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
@@ -120,6 +122,16 @@ Methods:
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<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#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<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#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+## Permissions
+
+Response Types:
+
+- <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#Permission">Permission</a>
+
+Methods:
+
+- <code title="post /session/{id}/permissions/{permissionID}">client.Session.Permissions.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionService.Respond">Respond</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, permissionID <a href="https://pkg.go.dev/builtin#string">string</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#SessionPermissionRespondParams">SessionPermissionRespondParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+
# Tui
Methods:
diff --git a/packages/tui/sdk/event.go b/packages/tui/sdk/event.go
index 5203ab23a..3c08b327e 100644
--- a/packages/tui/sdk/event.go
+++ b/packages/tui/sdk/event.go
@@ -54,8 +54,7 @@ type EventListResponse struct {
// [EventListResponseEventMessageRemovedProperties],
// [EventListResponseEventMessagePartUpdatedProperties],
// [EventListResponseEventMessagePartRemovedProperties],
- // [EventListResponseEventStorageWriteProperties],
- // [EventListResponseEventPermissionUpdatedProperties],
+ // [EventListResponseEventStorageWriteProperties], [Permission],
// [EventListResponseEventFileEditedProperties],
// [EventListResponseEventSessionUpdatedProperties],
// [EventListResponseEventSessionDeletedProperties],
@@ -643,9 +642,9 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
}
type EventListResponseEventPermissionUpdated struct {
- Properties EventListResponseEventPermissionUpdatedProperties `json:"properties,required"`
- Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
- JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
+ Properties Permission `json:"properties,required"`
+ Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
+ JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
}
// eventListResponseEventPermissionUpdatedJSON contains the JSON metadata for the
@@ -667,56 +666,6 @@ func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string {
func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {}
-type EventListResponseEventPermissionUpdatedProperties struct {
- ID string `json:"id,required"`
- Metadata map[string]interface{} `json:"metadata,required"`
- SessionID string `json:"sessionID,required"`
- Time EventListResponseEventPermissionUpdatedPropertiesTime `json:"time,required"`
- Title string `json:"title,required"`
- JSON eventListResponseEventPermissionUpdatedPropertiesJSON `json:"-"`
-}
-
-// eventListResponseEventPermissionUpdatedPropertiesJSON contains the JSON metadata
-// for the struct [EventListResponseEventPermissionUpdatedProperties]
-type eventListResponseEventPermissionUpdatedPropertiesJSON struct {
- ID apijson.Field
- Metadata apijson.Field
- SessionID apijson.Field
- Time apijson.Field
- Title apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventPermissionUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventPermissionUpdatedPropertiesJSON) RawJSON() string {
- return r.raw
-}
-
-type EventListResponseEventPermissionUpdatedPropertiesTime struct {
- Created float64 `json:"created,required"`
- JSON eventListResponseEventPermissionUpdatedPropertiesTimeJSON `json:"-"`
-}
-
-// eventListResponseEventPermissionUpdatedPropertiesTimeJSON contains the JSON
-// metadata for the struct [EventListResponseEventPermissionUpdatedPropertiesTime]
-type eventListResponseEventPermissionUpdatedPropertiesTimeJSON struct {
- Created apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventPermissionUpdatedPropertiesTime) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventPermissionUpdatedPropertiesTimeJSON) RawJSON() string {
- return r.raw
-}
-
type EventListResponseEventPermissionUpdatedType string
const (
diff --git a/packages/tui/sdk/session.go b/packages/tui/sdk/session.go
index 2598d51c6..d38c37e0e 100644
--- a/packages/tui/sdk/session.go
+++ b/packages/tui/sdk/session.go
@@ -24,7 +24,8 @@ import (
// automatically. You should not instantiate this service directly, and instead use
// the [NewSessionService] method instead.
type SessionService struct {
- Options []option.RequestOption
+ Options []option.RequestOption
+ Permissions *SessionPermissionService
}
// NewSessionService generates a new service that applies the given options to each
@@ -33,6 +34,7 @@ type SessionService struct {
func NewSessionService(opts ...option.RequestOption) (r *SessionService) {
r = &SessionService{}
r.Options = opts
+ r.Permissions = NewSessionPermissionService(opts...)
return
}
@@ -100,6 +102,22 @@ func (r *SessionService) Init(ctx context.Context, id string, body SessionInitPa
return
}
+// Get a message from a session
+func (r *SessionService) Message(ctx context.Context, id string, messageID string, opts ...option.RequestOption) (res *SessionMessageResponse, err error) {
+ opts = append(r.Options[:], opts...)
+ if id == "" {
+ err = errors.New("missing required id parameter")
+ return
+ }
+ if messageID == "" {
+ err = errors.New("missing required messageID parameter")
+ return
+ }
+ path := fmt.Sprintf("session/%s/message/%s", id, messageID)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ return
+}
+
// List messages for a session
func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
opts = append(r.Options[:], opts...)
@@ -2012,6 +2030,29 @@ func (r userMessageTimeJSON) RawJSON() string {
return r.raw
}
+type SessionMessageResponse struct {
+ Info Message `json:"info,required"`
+ Parts []Part `json:"parts,required"`
+ JSON sessionMessageResponseJSON `json:"-"`
+}
+
+// sessionMessageResponseJSON contains the JSON metadata for the struct
+// [SessionMessageResponse]
+type sessionMessageResponseJSON struct {
+ Info apijson.Field
+ Parts apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *SessionMessageResponse) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionMessageResponseJSON) RawJSON() string {
+ return r.raw
+}
+
type SessionMessagesResponse struct {
Info Message `json:"info,required"`
Parts []Part `json:"parts,required"`
diff --git a/packages/tui/sdk/session_test.go b/packages/tui/sdk/session_test.go
index 295e9e7ce..ab9fbcf7b 100644
--- a/packages/tui/sdk/session_test.go
+++ b/packages/tui/sdk/session_test.go
@@ -176,6 +176,32 @@ func TestSessionInit(t *testing.T) {
}
}
+func TestSessionMessage(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.Session.Message(
+ context.TODO(),
+ "id",
+ "messageID",
+ )
+ 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 TestSessionMessages(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"
diff --git a/packages/tui/sdk/sessionpermission.go b/packages/tui/sdk/sessionpermission.go
new file mode 100644
index 000000000..90a2134d8
--- /dev/null
+++ b/packages/tui/sdk/sessionpermission.go
@@ -0,0 +1,126 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+
+ "github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/param"
+ "github.com/sst/opencode-sdk-go/internal/requestconfig"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+// SessionPermissionService contains methods and other services that help with
+// interacting with the opencode API.
+//
+// Note, unlike clients, this service does not read variables from the environment
+// automatically. You should not instantiate this service directly, and instead use
+// the [NewSessionPermissionService] method instead.
+type SessionPermissionService struct {
+ Options []option.RequestOption
+}
+
+// NewSessionPermissionService generates a new service that applies the given
+// options to each request. These options are applied after the parent client's
+// options (if there is one), and before any request-specific options.
+func NewSessionPermissionService(opts ...option.RequestOption) (r *SessionPermissionService) {
+ r = &SessionPermissionService{}
+ r.Options = opts
+ return
+}
+
+// Respond to a permission request
+func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, body SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
+ opts = append(r.Options[:], opts...)
+ if id == "" {
+ err = errors.New("missing required id parameter")
+ return
+ }
+ if permissionID == "" {
+ err = errors.New("missing required permissionID parameter")
+ return
+ }
+ path := fmt.Sprintf("session/%s/permissions/%s", id, permissionID)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ return
+}
+
+type Permission struct {
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ Metadata map[string]interface{} `json:"metadata,required"`
+ SessionID string `json:"sessionID,required"`
+ Time PermissionTime `json:"time,required"`
+ Title string `json:"title,required"`
+ ToolCallID string `json:"toolCallID"`
+ JSON permissionJSON `json:"-"`
+}
+
+// permissionJSON contains the JSON metadata for the struct [Permission]
+type permissionJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ Metadata apijson.Field
+ SessionID apijson.Field
+ Time apijson.Field
+ Title apijson.Field
+ ToolCallID apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *Permission) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r permissionJSON) RawJSON() string {
+ return r.raw
+}
+
+type PermissionTime struct {
+ Created float64 `json:"created,required"`
+ JSON permissionTimeJSON `json:"-"`
+}
+
+// permissionTimeJSON contains the JSON metadata for the struct [PermissionTime]
+type permissionTimeJSON struct {
+ Created apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *PermissionTime) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r permissionTimeJSON) RawJSON() string {
+ return r.raw
+}
+
+type SessionPermissionRespondParams struct {
+ Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
+}
+
+func (r SessionPermissionRespondParams) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
+
+type SessionPermissionRespondParamsResponse string
+
+const (
+ SessionPermissionRespondParamsResponseOnce SessionPermissionRespondParamsResponse = "once"
+ SessionPermissionRespondParamsResponseAlways SessionPermissionRespondParamsResponse = "always"
+ SessionPermissionRespondParamsResponseReject SessionPermissionRespondParamsResponse = "reject"
+)
+
+func (r SessionPermissionRespondParamsResponse) IsKnown() bool {
+ switch r {
+ case SessionPermissionRespondParamsResponseOnce, SessionPermissionRespondParamsResponseAlways, SessionPermissionRespondParamsResponseReject:
+ return true
+ }
+ return false
+}
diff --git a/packages/tui/sdk/sessionpermission_test.go b/packages/tui/sdk/sessionpermission_test.go
new file mode 100644
index 000000000..728976be4
--- /dev/null
+++ b/packages/tui/sdk/sessionpermission_test.go
@@ -0,0 +1,43 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/sst/opencode-sdk-go"
+ "github.com/sst/opencode-sdk-go/internal/testutil"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+func TestSessionPermissionRespond(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.Session.Permissions.Respond(
+ context.TODO(),
+ "id",
+ "permissionID",
+ opencode.SessionPermissionRespondParams{
+ Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
+ },
+ )
+ 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())
+ }
+}