summaryrefslogtreecommitdiffhomepage
path: root/packages/tui
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tui')
-rw-r--r--packages/tui/internal/app/app.go109
-rw-r--r--packages/tui/internal/components/chat/messages.go43
-rw-r--r--packages/tui/internal/id/id.go96
-rw-r--r--packages/tui/internal/tui/tui.go103
-rw-r--r--packages/tui/sdk/.stats.yml6
-rw-r--r--packages/tui/sdk/README.md23
-rw-r--r--packages/tui/sdk/api.md8
-rw-r--r--packages/tui/sdk/client_test.go44
-rw-r--r--packages/tui/sdk/event.go8
-rwxr-xr-xpackages/tui/sdk/scripts/lint4
-rw-r--r--packages/tui/sdk/session.go683
-rw-r--r--packages/tui/sdk/session_test.go18
-rw-r--r--packages/tui/sdk/usage_test.go7
13 files changed, 690 insertions, 462 deletions
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 03eb361b7..fb8358d8c 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -16,11 +16,17 @@ import (
"github.com/sst/opencode/internal/commands"
"github.com/sst/opencode/internal/components/toast"
"github.com/sst/opencode/internal/config"
+ "github.com/sst/opencode/internal/id"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
)
+type Message struct {
+ Info opencode.MessageUnion
+ Parts []opencode.PartUnion
+}
+
type App struct {
Info opencode.App
Modes []opencode.Mode
@@ -35,7 +41,7 @@ type App struct {
Provider *opencode.Provider
Model *opencode.Model
Session *opencode.Session
- Messages []opencode.MessageUnion
+ Messages []Message
Commands commands.CommandRegistry
InitialModel *string
InitialPrompt *string
@@ -158,7 +164,7 @@ func New(
ModeIndex: modeIndex,
Mode: mode,
Session: &opencode.Session{},
- Messages: []opencode.MessageUnion{},
+ Messages: []Message{},
Commands: commands.LoadFromConfig(configInfo),
InitialModel: initialModel,
InitialPrompt: initialPrompt,
@@ -351,7 +357,7 @@ func (a *App) IsBusy() bool {
}
lastMessage := a.Messages[len(a.Messages)-1]
- if casted, ok := lastMessage.(opencode.AssistantMessage); ok {
+ if casted, ok := lastMessage.Info.(opencode.AssistantMessage); ok {
return casted.Time.Completed == 0
}
return false
@@ -452,54 +458,67 @@ func (a *App) SendChatMessage(
cmds = append(cmds, util.CmdHandler(SessionSelectedMsg(session)))
}
- optimisticParts := []opencode.UserMessagePart{{
- Type: opencode.UserMessagePartTypeText,
- Text: text,
+ message := opencode.UserMessage{
+ ID: id.Ascending(id.Message),
+ SessionID: a.Session.ID,
+ Role: opencode.UserMessageRoleUser,
+ Time: opencode.UserMessageTime{
+ Created: float64(time.Now().UnixMilli()),
+ },
+ }
+
+ parts := []opencode.PartUnion{opencode.TextPart{
+ ID: id.Ascending(id.Part),
+ MessageID: message.ID,
+ SessionID: a.Session.ID,
+ Type: opencode.TextPartTypeText,
+ Text: text,
}}
if len(attachments) > 0 {
for _, attachment := range attachments {
- optimisticParts = append(optimisticParts, opencode.UserMessagePart{
- Type: opencode.UserMessagePartTypeFile,
- Filename: attachment.Filename.Value,
- Mime: attachment.Mime.Value,
- URL: attachment.URL.Value,
+ parts = append(parts, opencode.FilePart{
+ ID: id.Ascending(id.Part),
+ MessageID: message.ID,
+ SessionID: a.Session.ID,
+ Type: opencode.FilePartTypeFile,
+ Filename: attachment.Filename.Value,
+ Mime: attachment.Mime.Value,
+ URL: attachment.URL.Value,
})
}
}
- optimisticMessage := opencode.UserMessage{
- ID: fmt.Sprintf("optimistic-%d", time.Now().UnixNano()),
- Role: opencode.UserMessageRoleUser,
- Parts: optimisticParts,
- SessionID: a.Session.ID,
- Time: opencode.UserMessageTime{
- Created: float64(time.Now().Unix()),
- },
- }
-
- a.Messages = append(a.Messages, optimisticMessage)
- cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: optimisticMessage}))
+ a.Messages = append(a.Messages, Message{Info: message, Parts: parts})
+ cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: message}))
cmds = append(cmds, func() tea.Msg {
- parts := []opencode.UserMessagePartUnionParam{
- opencode.TextPartParam{
- Type: opencode.F(opencode.TextPartTypeText),
- Text: opencode.F(text),
- },
- }
- if len(attachments) > 0 {
- for _, attachment := range attachments {
- parts = append(parts, opencode.FilePartParam{
- Mime: attachment.Mime,
- Type: attachment.Type,
- URL: attachment.URL,
- Filename: attachment.Filename,
+ partsParam := []opencode.SessionChatParamsPartUnion{}
+ for _, part := range parts {
+ switch casted := part.(type) {
+ case opencode.TextPart:
+ partsParam = append(partsParam, opencode.TextPartParam{
+ ID: opencode.F(casted.ID),
+ MessageID: opencode.F(casted.MessageID),
+ SessionID: opencode.F(casted.SessionID),
+ Type: opencode.F(casted.Type),
+ Text: opencode.F(casted.Text),
+ })
+ case opencode.FilePart:
+ partsParam = append(partsParam, opencode.FilePartParam{
+ ID: opencode.F(casted.ID),
+ Mime: opencode.F(casted.Mime),
+ MessageID: opencode.F(casted.MessageID),
+ SessionID: opencode.F(casted.SessionID),
+ Type: opencode.F(casted.Type),
+ URL: opencode.F(casted.URL),
+ Filename: opencode.F(casted.Filename),
})
}
}
_, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{
- Parts: opencode.F(parts),
+ Parts: opencode.F(partsParam),
+ MessageID: opencode.F(message.ID),
ProviderID: opencode.F(a.Provider.ID),
ModelID: opencode.F(a.Model.ID),
Mode: opencode.F(a.Mode.Name),
@@ -557,15 +576,25 @@ func (a *App) DeleteSession(ctx context.Context, sessionID string) error {
return nil
}
-func (a *App) ListMessages(ctx context.Context, sessionId string) ([]opencode.Message, error) {
+func (a *App) ListMessages(ctx context.Context, sessionId string) ([]Message, error) {
response, err := a.Client.Session.Messages(ctx, sessionId)
if err != nil {
return nil, err
}
if response == nil {
- return []opencode.Message{}, nil
+ return []Message{}, nil
+ }
+ messages := []Message{}
+ for _, message := range *response {
+ msg := Message{
+ Info: message.Info.AsUnion(),
+ Parts: []opencode.PartUnion{},
+ }
+ for _, part := range message.Parts {
+ msg.Parts = append(msg.Parts, part.AsUnion())
+ }
+ messages = append(messages, msg)
}
- messages := *response
return messages, nil
}
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 7ecd9b21f..191432cc3 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -106,6 +106,13 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.viewport.GotoBottom()
}
}
+ case opencode.EventListResponseEventMessagePartUpdated:
+ if msg.Properties.Part.SessionID == m.app.Session.ID {
+ m.renderView(m.width)
+ if m.tail {
+ m.viewport.GotoBottom()
+ }
+ }
}
viewport, cmd := m.viewport.Update(msg)
@@ -131,16 +138,16 @@ func (m *messagesComponent) renderView(width int) {
var content string
var cached bool
- switch casted := message.(type) {
+ switch casted := message.Info.(type) {
case opencode.UserMessage:
userLoop:
- for partIndex, part := range casted.Parts {
- switch part := part.AsUnion().(type) {
+ for partIndex, part := range message.Parts {
+ switch part := part.(type) {
case opencode.TextPart:
- remainingParts := casted.Parts[partIndex+1:]
+ remainingParts := message.Parts[partIndex+1:]
fileParts := make([]opencode.FilePart, 0)
for _, part := range remainingParts {
- switch part := part.AsUnion().(type) {
+ switch part := part.(type) {
case opencode.FilePart:
fileParts = append(fileParts, part)
}
@@ -181,7 +188,7 @@ func (m *messagesComponent) renderView(width int) {
if !cached {
content = renderText(
m.app,
- message,
+ message.Info,
part.Text,
m.app.Info.User,
m.showToolDetails,
@@ -202,12 +209,12 @@ func (m *messagesComponent) renderView(width int) {
case opencode.AssistantMessage:
hasTextPart := false
- for partIndex, p := range casted.Parts {
- switch part := p.AsUnion().(type) {
+ for partIndex, p := range message.Parts {
+ switch part := p.(type) {
case opencode.TextPart:
hasTextPart = true
finished := casted.Time.Completed > 0
- remainingParts := casted.Parts[partIndex+1:]
+ remainingParts := message.Parts[partIndex+1:]
toolCallParts := make([]opencode.ToolPart, 0)
// sometimes tool calls happen without an assistant message
@@ -222,7 +229,7 @@ func (m *messagesComponent) renderView(width int) {
if !remaining {
break
}
- switch part := part.AsUnion().(type) {
+ switch part := part.(type) {
case opencode.TextPart:
// we only want tool calls associated with the current text part.
// if we hit another text part, we're done.
@@ -238,13 +245,13 @@ func (m *messagesComponent) renderView(width int) {
}
if finished {
- key := m.cache.GenerateKey(casted.ID, p.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
+ key := m.cache.GenerateKey(casted.ID, part.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
content, cached = m.cache.Get(key)
if !cached {
content = renderText(
m.app,
- message,
- p.Text,
+ message.Info,
+ part.Text,
casted.ModelID,
m.showToolDetails,
m.partCount == m.selectedPart,
@@ -257,8 +264,8 @@ func (m *messagesComponent) renderView(width int) {
} else {
content = renderText(
m.app,
- message,
- p.Text,
+ message.Info,
+ part.Text,
casted.ModelID,
m.showToolDetails,
m.partCount == m.selectedPart,
@@ -268,7 +275,7 @@ func (m *messagesComponent) renderView(width int) {
)
}
if content != "" {
- m = m.updateSelected(content, p.Text)
+ m = m.updateSelected(content, part.Text)
blocks = append(blocks, content)
}
case opencode.ToolPart:
@@ -314,7 +321,7 @@ func (m *messagesComponent) renderView(width int) {
}
error := ""
- if assistant, ok := message.(opencode.AssistantMessage); ok {
+ if assistant, ok := message.Info.(opencode.AssistantMessage); ok {
switch err := assistant.Error.AsUnion().(type) {
case nil:
case opencode.AssistantMessageErrorMessageOutputLengthError:
@@ -386,7 +393,7 @@ func (m *messagesComponent) header(width int) string {
contextWindow := m.app.Model.Limit.Context
for _, message := range m.app.Messages {
- if assistant, ok := message.(opencode.AssistantMessage); ok {
+ if assistant, ok := message.Info.(opencode.AssistantMessage); ok {
cost += assistant.Cost
usage := assistant.Tokens
if usage.Output > 0 {
diff --git a/packages/tui/internal/id/id.go b/packages/tui/internal/id/id.go
new file mode 100644
index 000000000..0490b8f20
--- /dev/null
+++ b/packages/tui/internal/id/id.go
@@ -0,0 +1,96 @@
+package id
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "sync"
+ "time"
+)
+
+const (
+ PrefixSession = "ses"
+ PrefixMessage = "msg"
+ PrefixUser = "usr"
+ PrefixPart = "prt"
+)
+
+const length = 26
+
+var (
+ lastTimestamp int64
+ counter int64
+ mu sync.Mutex
+)
+
+type Prefix string
+
+const (
+ Session Prefix = PrefixSession
+ Message Prefix = PrefixMessage
+ User Prefix = PrefixUser
+ Part Prefix = PrefixPart
+)
+
+func ValidatePrefix(id string, prefix Prefix) bool {
+ return strings.HasPrefix(id, string(prefix))
+}
+
+func Ascending(prefix Prefix, given ...string) string {
+ return generateID(prefix, false, given...)
+}
+
+func Descending(prefix Prefix, given ...string) string {
+ return generateID(prefix, true, given...)
+}
+
+func generateID(prefix Prefix, descending bool, given ...string) string {
+ if len(given) > 0 && given[0] != "" {
+ if !strings.HasPrefix(given[0], string(prefix)) {
+ panic(fmt.Sprintf("ID %s does not start with %s", given[0], string(prefix)))
+ }
+ return given[0]
+ }
+
+ return generateNewID(prefix, descending)
+}
+
+func randomBase62(length int) string {
+ const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ result := make([]byte, length)
+ bytes := make([]byte, length)
+ rand.Read(bytes)
+
+ for i := 0; i < length; i++ {
+ result[i] = chars[bytes[i]%62]
+ }
+
+ return string(result)
+}
+
+func generateNewID(prefix Prefix, descending bool) string {
+ mu.Lock()
+ defer mu.Unlock()
+
+ currentTimestamp := time.Now().UnixMilli()
+
+ if currentTimestamp != lastTimestamp {
+ lastTimestamp = currentTimestamp
+ counter = 0
+ }
+ counter++
+
+ now := uint64(currentTimestamp)*0x1000 + uint64(counter)
+
+ if descending {
+ now = ^now
+ }
+
+ timeBytes := make([]byte, 6)
+ for i := 0; i < 6; i++ {
+ timeBytes[i] = byte((now >> (40 - 8*i)) & 0xff)
+ }
+
+ return string(prefix) + "_" + hex.EncodeToString(timeBytes) + randomBase62(length-12)
+} \ No newline at end of file
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 389dd64f1..0ebdd35ab 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -5,6 +5,7 @@ import (
"log/slog"
"os"
"os/exec"
+ "slices"
"strings"
"time"
@@ -364,55 +365,76 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case opencode.EventListResponseEventSessionDeleted:
if a.app.Session != nil && msg.Properties.Info.ID == a.app.Session.ID {
a.app.Session = &opencode.Session{}
- a.app.Messages = []opencode.MessageUnion{}
+ a.app.Messages = []app.Message{}
}
return a, toast.NewSuccessToast("Session deleted successfully")
case opencode.EventListResponseEventSessionUpdated:
if msg.Properties.Info.ID == a.app.Session.ID {
a.app.Session = &msg.Properties.Info
}
- case opencode.EventListResponseEventMessageUpdated:
- if msg.Properties.Info.SessionID == a.app.Session.ID {
- exists := false
- optimisticReplaced := false
-
- // First check if this is replacing an optimistic message
- if msg.Properties.Info.Role == opencode.MessageRoleUser {
- // Look for optimistic messages to replace
- for i, m := range a.app.Messages {
- switch m := m.(type) {
- case opencode.UserMessage:
- if strings.HasPrefix(m.ID, "optimistic-") && m.Role == opencode.UserMessageRoleUser {
- // Replace the optimistic message with the real one
- a.app.Messages[i] = msg.Properties.Info.AsUnion()
- exists = true
- optimisticReplaced = true
- break
- }
+ case opencode.EventListResponseEventMessagePartUpdated:
+ slog.Info("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) {
+ case opencode.UserMessage:
+ return casted.ID == msg.Properties.Part.MessageID
+ case opencode.AssistantMessage:
+ return casted.ID == msg.Properties.Part.MessageID
+ }
+ return false
+ })
+ if messageIndex > -1 {
+ message := a.app.Messages[messageIndex]
+ partIndex := slices.IndexFunc(message.Parts, func(p opencode.PartUnion) bool {
+ switch casted := p.(type) {
+ case opencode.TextPart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.FilePart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.ToolPart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.StepStartPart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.StepFinishPart:
+ return casted.ID == msg.Properties.Part.ID
}
+ return false
+ })
+ if partIndex > -1 {
+ message.Parts[partIndex] = msg.Properties.Part.AsUnion()
}
+ if partIndex == -1 {
+ message.Parts = append(message.Parts, msg.Properties.Part.AsUnion())
+ }
+ a.app.Messages[messageIndex] = message
}
-
- // If not replacing optimistic, check for existing message with same ID
- if !optimisticReplaced {
- for i, m := range a.app.Messages {
- var id string
- switch m := m.(type) {
- case opencode.UserMessage:
- id = m.ID
- case opencode.AssistantMessage:
- id = m.ID
- }
- if id == msg.Properties.Info.ID {
- a.app.Messages[i] = msg.Properties.Info.AsUnion()
- exists = true
- break
- }
+ }
+ case opencode.EventListResponseEventMessageUpdated:
+ if msg.Properties.Info.SessionID == a.app.Session.ID {
+ matchIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
+ switch casted := m.Info.(type) {
+ case opencode.UserMessage:
+ return casted.ID == msg.Properties.Info.ID
+ case opencode.AssistantMessage:
+ return casted.ID == msg.Properties.Info.ID
+ }
+ return false
+ })
+
+ if matchIndex > -1 {
+ match := a.app.Messages[matchIndex]
+ a.app.Messages[matchIndex] = app.Message{
+ Info: msg.Properties.Info.AsUnion(),
+ Parts: match.Parts,
}
}
- if !exists {
- a.app.Messages = append(a.app.Messages, msg.Properties.Info.AsUnion())
+ if matchIndex == -1 {
+ a.app.Messages = append(a.app.Messages, app.Message{
+ Info: msg.Properties.Info.AsUnion(),
+ Parts: []opencode.PartUnion{},
+ })
}
}
case opencode.EventListResponseEventSessionError:
@@ -473,10 +495,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, toast.NewErrorToast("Failed to open session")
}
a.app.Session = msg
- a.app.Messages = make([]opencode.MessageUnion, 0)
- for _, message := range messages {
- a.app.Messages = append(a.app.Messages, message.AsUnion())
- }
+ a.app.Messages = messages
return a, util.CmdHandler(app.SessionLoadedMsg{})
case app.ModelSelectedMsg:
a.app.Provider = &msg.Provider
@@ -837,7 +856,7 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
return a, nil
}
a.app.Session = &opencode.Session{}
- a.app.Messages = []opencode.MessageUnion{}
+ a.app.Messages = []app.Message{}
cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{}))
case commands.SessionListCommand:
sessionDialog := dialog.NewSessionDialog(a.app)
diff --git a/packages/tui/sdk/.stats.yml b/packages/tui/sdk/.stats.yml
index 4449c4d95..61d2fa3b7 100644
--- a/packages/tui/sdk/.stats.yml
+++ b/packages/tui/sdk/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-eb25bb3673f94d0e98a7036e2a2b0ed7ad63d1598665f2d5e091ec0835273798.yml
-openapi_spec_hash: 62f6a8a06aaa4f4ae13e85d56652724f
-config_hash: 589ec6a935a43a3c49a325ece86cbda2
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-352994eb17f76d9472b0f0176efacf77a200a6fab2db28d1cfcd29451b211d7a.yml
+openapi_spec_hash: f01cd3de8c7cf0c9fd513896e81986de
+config_hash: 3695cfc829cfaae14490850b4a1ed282
diff --git a/packages/tui/sdk/README.md b/packages/tui/sdk/README.md
index 2b5782347..38840b28a 100644
--- a/packages/tui/sdk/README.md
+++ b/packages/tui/sdk/README.md
@@ -49,11 +49,14 @@ import (
func main() {
client := opencode.NewClient()
- events, err := client.Event.List(context.TODO())
+ stream := client.Event.ListStreaming(context.TODO())
+ for stream.Next() {
+ fmt.Printf("%+v\n", stream.Current())
+ }
+ err := stream.Err()
if err != nil {
panic(err.Error())
}
- fmt.Printf("%+v\n", events)
}
```
@@ -171,14 +174,14 @@ When the API returns a non-success status code, we return an error with type
To handle errors, we recommend that you use the `errors.As` pattern:
```go
-_, err := client.Event.List(context.TODO())
-if err != nil {
+stream := client.Event.ListStreaming(context.TODO())
+if stream.Err() != nil {
var apierr *opencode.Error
- if errors.As(err, &apierr) {
+ if errors.As(stream.Err(), &apierr) {
println(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request
println(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response
}
- panic(err.Error()) // GET "/event": 400 Bad Request { ... }
+ panic(stream.Err().Error()) // GET "/event": 400 Bad Request { ... }
}
```
@@ -196,7 +199,7 @@ To set a per-retry timeout, use `option.WithRequestTimeout()`.
// This sets the timeout for the request, including all the retries.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
-client.Event.List(
+client.Event.ListStreaming(
ctx,
// This sets the per-retry timeout
option.WithRequestTimeout(20*time.Second),
@@ -231,7 +234,7 @@ client := opencode.NewClient(
)
// Override per-request:
-client.Event.List(context.TODO(), option.WithMaxRetries(5))
+client.Event.ListStreaming(context.TODO(), option.WithMaxRetries(5))
```
### Accessing raw response data (e.g. response headers)
@@ -242,8 +245,8 @@ you need to examine response headers, status codes, or other details.
```go
// Create a variable to store the HTTP response
var response *http.Response
-events, err := client.Event.List(context.TODO(), option.WithResponseInto(&response))
-if err != nil {
+stream := client.Event.ListStreaming(context.TODO(), option.WithResponseInto(&response))
+if stream.Err() != nil {
// handle error
}
fmt.Printf("%+v\n", events)
diff --git a/packages/tui/sdk/api.md b/packages/tui/sdk/api.md
index 15bdcfa93..a48e6d7f1 100644
--- a/packages/tui/sdk/api.md
+++ b/packages/tui/sdk/api.md
@@ -77,15 +77,15 @@ Params 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#FilePartParam">FilePartParam</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#TextPartParam">TextPartParam</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#UserMessagePartUnionParam">UserMessagePartUnionParam</a>
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#AssistantMessage">AssistantMessage</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#AssistantMessagePart">AssistantMessagePart</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#FilePart">FilePart</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#Message">Message</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#Part">Part</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/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepFinishPart">StepFinishPart</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#StepStartPart">StepStartPart</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#TextPart">TextPart</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#ToolPart">ToolPart</a>
@@ -94,7 +94,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#UserMessagePart">UserMessagePart</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:
@@ -104,7 +104,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">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#Message">Message</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}/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>
- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</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#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</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>
diff --git a/packages/tui/sdk/client_test.go b/packages/tui/sdk/client_test.go
index e75d64925..62222a465 100644
--- a/packages/tui/sdk/client_test.go
+++ b/packages/tui/sdk/client_test.go
@@ -38,7 +38,7 @@ func TestUserAgentHeader(t *testing.T) {
},
}),
)
- client.Event.List(context.Background())
+ client.Event.ListStreaming(context.Background())
if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) {
t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent)
}
@@ -61,7 +61,11 @@ func TestRetryAfter(t *testing.T) {
},
}),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -95,7 +99,11 @@ func TestDeleteRetryCountHeader(t *testing.T) {
}),
option.WithHeaderDel("X-Stainless-Retry-Count"),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -124,7 +132,11 @@ func TestOverwriteRetryCountHeader(t *testing.T) {
}),
option.WithHeader("X-Stainless-Retry-Count", "42"),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -152,7 +164,11 @@ func TestRetryAfterMs(t *testing.T) {
},
}),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -174,7 +190,11 @@ func TestContextCancel(t *testing.T) {
)
cancelCtx, cancel := context.WithCancel(context.Background())
cancel()
- _, err := client.Event.List(cancelCtx)
+ stream := client.Event.ListStreaming(cancelCtx)
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -193,7 +213,11 @@ func TestContextCancelDelay(t *testing.T) {
)
cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
defer cancel()
- _, err := client.Event.List(cancelCtx)
+ stream := client.Event.ListStreaming(cancelCtx)
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("expected there to be a cancel error")
}
@@ -218,7 +242,11 @@ func TestContextDeadline(t *testing.T) {
},
}),
)
- _, err := client.Event.List(deadlineCtx)
+ stream := client.Event.ListStreaming(deadlineCtx)
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("expected there to be a deadline error")
}
diff --git a/packages/tui/sdk/event.go b/packages/tui/sdk/event.go
index 8bbf636c3..9002d2aac 100644
--- a/packages/tui/sdk/event.go
+++ b/packages/tui/sdk/event.go
@@ -610,18 +610,14 @@ func (r eventListResponseEventMessagePartUpdatedJSON) RawJSON() string {
func (r EventListResponseEventMessagePartUpdated) implementsEventListResponse() {}
type EventListResponseEventMessagePartUpdatedProperties struct {
- MessageID string `json:"messageID,required"`
- Part AssistantMessagePart `json:"part,required"`
- SessionID string `json:"sessionID,required"`
- JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
+ Part Part `json:"part,required"`
+ JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
}
// eventListResponseEventMessagePartUpdatedPropertiesJSON contains the JSON
// metadata for the struct [EventListResponseEventMessagePartUpdatedProperties]
type eventListResponseEventMessagePartUpdatedPropertiesJSON struct {
- MessageID apijson.Field
Part apijson.Field
- SessionID apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
diff --git a/packages/tui/sdk/scripts/lint b/packages/tui/sdk/scripts/lint
index 9f37abf28..7e03a7beb 100755
--- a/packages/tui/sdk/scripts/lint
+++ b/packages/tui/sdk/scripts/lint
@@ -5,7 +5,7 @@ set -e
cd "$(dirname "$0")/.."
echo "==> Running Go build"
-go build .
+go build ./...
echo "==> Checking tests compile"
-go test -run=^$ .
+go test -run=^$ ./...
diff --git a/packages/tui/sdk/session.go b/packages/tui/sdk/session.go
index 6321d1ff6..e76ab7f90 100644
--- a/packages/tui/sdk/session.go
+++ b/packages/tui/sdk/session.go
@@ -101,7 +101,7 @@ func (r *SessionService) Init(ctx context.Context, id string, body SessionInitPa
}
// List messages for a session
-func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]Message, err error) {
+func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
@@ -152,7 +152,6 @@ type AssistantMessage struct {
ID string `json:"id,required"`
Cost float64 `json:"cost,required"`
ModelID string `json:"modelID,required"`
- Parts []AssistantMessagePart `json:"parts,required"`
Path AssistantMessagePath `json:"path,required"`
ProviderID string `json:"providerID,required"`
Role AssistantMessageRole `json:"role,required"`
@@ -171,7 +170,6 @@ type assistantMessageJSON struct {
ID apijson.Field
Cost apijson.Field
ModelID apijson.Field
- Parts apijson.Field
Path apijson.Field
ProviderID apijson.Field
Role apijson.Field
@@ -435,211 +433,23 @@ func (r AssistantMessageErrorName) IsKnown() bool {
return false
}
-type AssistantMessagePart struct {
- Type AssistantMessagePartType `json:"type,required"`
- ID string `json:"id"`
- Cost float64 `json:"cost"`
- // This field can have the runtime type of [ToolPartState].
- State interface{} `json:"state"`
- Synthetic bool `json:"synthetic"`
- Text string `json:"text"`
- // This field can have the runtime type of
- // [AssistantMessagePartStepFinishPartTokens].
- Tokens interface{} `json:"tokens"`
- Tool string `json:"tool"`
- JSON assistantMessagePartJSON `json:"-"`
- union AssistantMessagePartUnion
-}
-
-// assistantMessagePartJSON contains the JSON metadata for the struct
-// [AssistantMessagePart]
-type assistantMessagePartJSON struct {
- Type apijson.Field
- ID apijson.Field
- Cost apijson.Field
- State apijson.Field
- Synthetic apijson.Field
- Text apijson.Field
- Tokens apijson.Field
- Tool apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r assistantMessagePartJSON) RawJSON() string {
- return r.raw
-}
-
-func (r *AssistantMessagePart) UnmarshalJSON(data []byte) (err error) {
- *r = AssistantMessagePart{}
- err = apijson.UnmarshalRoot(data, &r.union)
- if err != nil {
- return err
- }
- return apijson.Port(r.union, &r)
-}
-
-// AsUnion returns a [AssistantMessagePartUnion] interface which you can cast to
-// the specific types for more type safety.
-//
-// Possible runtime types of the union are [TextPart], [ToolPart], [StepStartPart],
-// [AssistantMessagePartStepFinishPart].
-func (r AssistantMessagePart) AsUnion() AssistantMessagePartUnion {
- return r.union
-}
-
-// Union satisfied by [TextPart], [ToolPart], [StepStartPart] or
-// [AssistantMessagePartStepFinishPart].
-type AssistantMessagePartUnion interface {
- implementsAssistantMessagePart()
-}
-
-func init() {
- apijson.RegisterUnion(
- reflect.TypeOf((*AssistantMessagePartUnion)(nil)).Elem(),
- "type",
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(TextPart{}),
- DiscriminatorValue: "text",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(ToolPart{}),
- DiscriminatorValue: "tool",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(StepStartPart{}),
- DiscriminatorValue: "step-start",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(AssistantMessagePartStepFinishPart{}),
- DiscriminatorValue: "step-finish",
- },
- )
-}
-
-type AssistantMessagePartStepFinishPart struct {
- Cost float64 `json:"cost,required"`
- Tokens AssistantMessagePartStepFinishPartTokens `json:"tokens,required"`
- Type AssistantMessagePartStepFinishPartType `json:"type,required"`
- JSON assistantMessagePartStepFinishPartJSON `json:"-"`
-}
-
-// assistantMessagePartStepFinishPartJSON contains the JSON metadata for the struct
-// [AssistantMessagePartStepFinishPart]
-type assistantMessagePartStepFinishPartJSON struct {
- Cost apijson.Field
- Tokens apijson.Field
- Type apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AssistantMessagePartStepFinishPart) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r assistantMessagePartStepFinishPartJSON) RawJSON() string {
- return r.raw
-}
-
-func (r AssistantMessagePartStepFinishPart) implementsAssistantMessagePart() {}
-
-type AssistantMessagePartStepFinishPartTokens struct {
- Cache AssistantMessagePartStepFinishPartTokensCache `json:"cache,required"`
- Input float64 `json:"input,required"`
- Output float64 `json:"output,required"`
- Reasoning float64 `json:"reasoning,required"`
- JSON assistantMessagePartStepFinishPartTokensJSON `json:"-"`
-}
-
-// assistantMessagePartStepFinishPartTokensJSON contains the JSON metadata for the
-// struct [AssistantMessagePartStepFinishPartTokens]
-type assistantMessagePartStepFinishPartTokensJSON struct {
- Cache apijson.Field
- Input apijson.Field
- Output apijson.Field
- Reasoning apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AssistantMessagePartStepFinishPartTokens) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r assistantMessagePartStepFinishPartTokensJSON) RawJSON() string {
- return r.raw
-}
-
-type AssistantMessagePartStepFinishPartTokensCache struct {
- Read float64 `json:"read,required"`
- Write float64 `json:"write,required"`
- JSON assistantMessagePartStepFinishPartTokensCacheJSON `json:"-"`
-}
-
-// assistantMessagePartStepFinishPartTokensCacheJSON contains the JSON metadata for
-// the struct [AssistantMessagePartStepFinishPartTokensCache]
-type assistantMessagePartStepFinishPartTokensCacheJSON struct {
- Read apijson.Field
- Write apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AssistantMessagePartStepFinishPartTokensCache) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r assistantMessagePartStepFinishPartTokensCacheJSON) RawJSON() string {
- return r.raw
-}
-
-type AssistantMessagePartStepFinishPartType string
-
-const (
- AssistantMessagePartStepFinishPartTypeStepFinish AssistantMessagePartStepFinishPartType = "step-finish"
-)
-
-func (r AssistantMessagePartStepFinishPartType) IsKnown() bool {
- switch r {
- case AssistantMessagePartStepFinishPartTypeStepFinish:
- return true
- }
- return false
-}
-
-type AssistantMessagePartType string
-
-const (
- AssistantMessagePartTypeText AssistantMessagePartType = "text"
- AssistantMessagePartTypeTool AssistantMessagePartType = "tool"
- AssistantMessagePartTypeStepStart AssistantMessagePartType = "step-start"
- AssistantMessagePartTypeStepFinish AssistantMessagePartType = "step-finish"
-)
-
-func (r AssistantMessagePartType) IsKnown() bool {
- switch r {
- case AssistantMessagePartTypeText, AssistantMessagePartTypeTool, AssistantMessagePartTypeStepStart, AssistantMessagePartTypeStepFinish:
- return true
- }
- return false
-}
-
type FilePart struct {
- Mime string `json:"mime,required"`
- Type FilePartType `json:"type,required"`
- URL string `json:"url,required"`
- Filename string `json:"filename"`
- JSON filePartJSON `json:"-"`
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ Mime string `json:"mime,required"`
+ SessionID string `json:"sessionID,required"`
+ Type FilePartType `json:"type,required"`
+ URL string `json:"url,required"`
+ Filename string `json:"filename"`
+ JSON filePartJSON `json:"-"`
}
// filePartJSON contains the JSON metadata for the struct [FilePart]
type filePartJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
Mime apijson.Field
+ SessionID apijson.Field
Type apijson.Field
URL apijson.Field
Filename apijson.Field
@@ -655,7 +465,7 @@ func (r filePartJSON) RawJSON() string {
return r.raw
}
-func (r FilePart) implementsUserMessagePart() {}
+func (r FilePart) implementsPart() {}
type FilePartType string
@@ -672,23 +482,23 @@ func (r FilePartType) IsKnown() bool {
}
type FilePartParam struct {
- Mime param.Field[string] `json:"mime,required"`
- Type param.Field[FilePartType] `json:"type,required"`
- URL param.Field[string] `json:"url,required"`
- Filename param.Field[string] `json:"filename"`
+ ID param.Field[string] `json:"id,required"`
+ MessageID param.Field[string] `json:"messageID,required"`
+ Mime param.Field[string] `json:"mime,required"`
+ SessionID param.Field[string] `json:"sessionID,required"`
+ Type param.Field[FilePartType] `json:"type,required"`
+ URL param.Field[string] `json:"url,required"`
+ Filename param.Field[string] `json:"filename"`
}
func (r FilePartParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r FilePartParam) implementsUserMessagePartUnionParam() {}
+func (r FilePartParam) implementsSessionChatParamsPartUnion() {}
type Message struct {
- ID string `json:"id,required"`
- // This field can have the runtime type of [[]UserMessagePart],
- // [[]AssistantMessagePart].
- Parts interface{} `json:"parts,required"`
+ ID string `json:"id,required"`
Role MessageRole `json:"role,required"`
SessionID string `json:"sessionID,required"`
// This field can have the runtime type of [UserMessageTime],
@@ -713,7 +523,6 @@ type Message struct {
// messageJSON contains the JSON metadata for the struct [Message]
type messageJSON struct {
ID apijson.Field
- Parts apijson.Field
Role apijson.Field
SessionID apijson.Field
Time apijson.Field
@@ -787,6 +596,128 @@ func (r MessageRole) IsKnown() bool {
return false
}
+type Part struct {
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ Type PartType `json:"type,required"`
+ CallID string `json:"callID"`
+ Cost float64 `json:"cost"`
+ Filename string `json:"filename"`
+ Mime string `json:"mime"`
+ // This field can have the runtime type of [ToolPartState].
+ State interface{} `json:"state"`
+ Synthetic bool `json:"synthetic"`
+ Text string `json:"text"`
+ // This field can have the runtime type of [TextPartTime].
+ Time interface{} `json:"time"`
+ // This field can have the runtime type of [StepFinishPartTokens].
+ Tokens interface{} `json:"tokens"`
+ Tool string `json:"tool"`
+ URL string `json:"url"`
+ JSON partJSON `json:"-"`
+ union PartUnion
+}
+
+// partJSON contains the JSON metadata for the struct [Part]
+type partJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
+ Type apijson.Field
+ CallID apijson.Field
+ Cost apijson.Field
+ Filename apijson.Field
+ Mime apijson.Field
+ State apijson.Field
+ Synthetic apijson.Field
+ Text apijson.Field
+ Time apijson.Field
+ Tokens apijson.Field
+ Tool apijson.Field
+ URL apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r partJSON) RawJSON() string {
+ return r.raw
+}
+
+func (r *Part) UnmarshalJSON(data []byte) (err error) {
+ *r = Part{}
+ err = apijson.UnmarshalRoot(data, &r.union)
+ if err != nil {
+ return err
+ }
+ return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [PartUnion] interface which you can cast to the specific types
+// for more type safety.
+//
+// Possible runtime types of the union are [TextPart], [FilePart], [ToolPart],
+// [StepStartPart], [StepFinishPart].
+func (r Part) AsUnion() PartUnion {
+ return r.union
+}
+
+// Union satisfied by [TextPart], [FilePart], [ToolPart], [StepStartPart] or
+// [StepFinishPart].
+type PartUnion interface {
+ implementsPart()
+}
+
+func init() {
+ apijson.RegisterUnion(
+ reflect.TypeOf((*PartUnion)(nil)).Elem(),
+ "type",
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(TextPart{}),
+ DiscriminatorValue: "text",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(FilePart{}),
+ DiscriminatorValue: "file",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(ToolPart{}),
+ DiscriminatorValue: "tool",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(StepStartPart{}),
+ DiscriminatorValue: "step-start",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(StepFinishPart{}),
+ DiscriminatorValue: "step-finish",
+ },
+ )
+}
+
+type PartType string
+
+const (
+ PartTypeText PartType = "text"
+ PartTypeFile PartType = "file"
+ PartTypeTool PartType = "tool"
+ PartTypeStepStart PartType = "step-start"
+ PartTypeStepFinish PartType = "step-finish"
+)
+
+func (r PartType) IsKnown() bool {
+ switch r {
+ case PartTypeText, PartTypeFile, PartTypeTool, PartTypeStepStart, PartTypeStepFinish:
+ return true
+ }
+ return false
+}
+
type Session struct {
ID string `json:"id,required"`
Time SessionTime `json:"time,required"`
@@ -885,13 +816,115 @@ func (r sessionShareJSON) RawJSON() string {
return r.raw
}
+type StepFinishPart struct {
+ ID string `json:"id,required"`
+ Cost float64 `json:"cost,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ Tokens StepFinishPartTokens `json:"tokens,required"`
+ Type StepFinishPartType `json:"type,required"`
+ JSON stepFinishPartJSON `json:"-"`
+}
+
+// stepFinishPartJSON contains the JSON metadata for the struct [StepFinishPart]
+type stepFinishPartJSON struct {
+ ID apijson.Field
+ Cost apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
+ Tokens apijson.Field
+ Type apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPart) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartJSON) RawJSON() string {
+ return r.raw
+}
+
+func (r StepFinishPart) implementsPart() {}
+
+type StepFinishPartTokens struct {
+ Cache StepFinishPartTokensCache `json:"cache,required"`
+ Input float64 `json:"input,required"`
+ Output float64 `json:"output,required"`
+ Reasoning float64 `json:"reasoning,required"`
+ JSON stepFinishPartTokensJSON `json:"-"`
+}
+
+// stepFinishPartTokensJSON contains the JSON metadata for the struct
+// [StepFinishPartTokens]
+type stepFinishPartTokensJSON struct {
+ Cache apijson.Field
+ Input apijson.Field
+ Output apijson.Field
+ Reasoning apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPartTokens) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartTokensJSON) RawJSON() string {
+ return r.raw
+}
+
+type StepFinishPartTokensCache struct {
+ Read float64 `json:"read,required"`
+ Write float64 `json:"write,required"`
+ JSON stepFinishPartTokensCacheJSON `json:"-"`
+}
+
+// stepFinishPartTokensCacheJSON contains the JSON metadata for the struct
+// [StepFinishPartTokensCache]
+type stepFinishPartTokensCacheJSON struct {
+ Read apijson.Field
+ Write apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPartTokensCache) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartTokensCacheJSON) RawJSON() string {
+ return r.raw
+}
+
+type StepFinishPartType string
+
+const (
+ StepFinishPartTypeStepFinish StepFinishPartType = "step-finish"
+)
+
+func (r StepFinishPartType) IsKnown() bool {
+ switch r {
+ case StepFinishPartTypeStepFinish:
+ return true
+ }
+ return false
+}
+
type StepStartPart struct {
- Type StepStartPartType `json:"type,required"`
- JSON stepStartPartJSON `json:"-"`
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ Type StepStartPartType `json:"type,required"`
+ JSON stepStartPartJSON `json:"-"`
}
// stepStartPartJSON contains the JSON metadata for the struct [StepStartPart]
type stepStartPartJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
@@ -905,7 +938,7 @@ func (r stepStartPartJSON) RawJSON() string {
return r.raw
}
-func (r StepStartPart) implementsAssistantMessagePart() {}
+func (r StepStartPart) implementsPart() {}
type StepStartPartType string
@@ -922,17 +955,25 @@ func (r StepStartPartType) IsKnown() bool {
}
type TextPart struct {
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
Text string `json:"text,required"`
Type TextPartType `json:"type,required"`
Synthetic bool `json:"synthetic"`
+ Time TextPartTime `json:"time"`
JSON textPartJSON `json:"-"`
}
// textPartJSON contains the JSON metadata for the struct [TextPart]
type textPartJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
Text apijson.Field
Type apijson.Field
Synthetic apijson.Field
+ Time apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -945,9 +986,7 @@ func (r textPartJSON) RawJSON() string {
return r.raw
}
-func (r TextPart) implementsAssistantMessagePart() {}
-
-func (r TextPart) implementsUserMessagePart() {}
+func (r TextPart) implementsPart() {}
type TextPartType string
@@ -963,29 +1002,70 @@ func (r TextPartType) IsKnown() bool {
return false
}
+type TextPartTime struct {
+ Start float64 `json:"start,required"`
+ End float64 `json:"end"`
+ JSON textPartTimeJSON `json:"-"`
+}
+
+// textPartTimeJSON contains the JSON metadata for the struct [TextPartTime]
+type textPartTimeJSON struct {
+ Start apijson.Field
+ End apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *TextPartTime) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r textPartTimeJSON) RawJSON() string {
+ return r.raw
+}
+
type TextPartParam struct {
- Text param.Field[string] `json:"text,required"`
- Type param.Field[TextPartType] `json:"type,required"`
- Synthetic param.Field[bool] `json:"synthetic"`
+ ID param.Field[string] `json:"id,required"`
+ MessageID param.Field[string] `json:"messageID,required"`
+ SessionID param.Field[string] `json:"sessionID,required"`
+ Text param.Field[string] `json:"text,required"`
+ Type param.Field[TextPartType] `json:"type,required"`
+ Synthetic param.Field[bool] `json:"synthetic"`
+ Time param.Field[TextPartTimeParam] `json:"time"`
}
func (r TextPartParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r TextPartParam) implementsUserMessagePartUnionParam() {}
+func (r TextPartParam) implementsSessionChatParamsPartUnion() {}
+
+type TextPartTimeParam struct {
+ Start param.Field[float64] `json:"start,required"`
+ End param.Field[float64] `json:"end"`
+}
+
+func (r TextPartTimeParam) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
type ToolPart struct {
- ID string `json:"id,required"`
- State ToolPartState `json:"state,required"`
- Tool string `json:"tool,required"`
- Type ToolPartType `json:"type,required"`
- JSON toolPartJSON `json:"-"`
+ ID string `json:"id,required"`
+ CallID string `json:"callID,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ State ToolPartState `json:"state,required"`
+ Tool string `json:"tool,required"`
+ Type ToolPartType `json:"type,required"`
+ JSON toolPartJSON `json:"-"`
}
// toolPartJSON contains the JSON metadata for the struct [ToolPart]
type toolPartJSON struct {
ID apijson.Field
+ CallID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
State apijson.Field
Tool apijson.Field
Type apijson.Field
@@ -1001,7 +1081,7 @@ func (r toolPartJSON) RawJSON() string {
return r.raw
}
-func (r ToolPart) implementsAssistantMessagePart() {}
+func (r ToolPart) implementsPart() {}
type ToolPartState struct {
Status ToolPartStateStatus `json:"status,required"`
@@ -1357,18 +1437,16 @@ func (r toolStateRunningTimeJSON) RawJSON() string {
}
type UserMessage struct {
- ID string `json:"id,required"`
- Parts []UserMessagePart `json:"parts,required"`
- Role UserMessageRole `json:"role,required"`
- SessionID string `json:"sessionID,required"`
- Time UserMessageTime `json:"time,required"`
- JSON userMessageJSON `json:"-"`
+ ID string `json:"id,required"`
+ Role UserMessageRole `json:"role,required"`
+ SessionID string `json:"sessionID,required"`
+ Time UserMessageTime `json:"time,required"`
+ JSON userMessageJSON `json:"-"`
}
// userMessageJSON contains the JSON metadata for the struct [UserMessage]
type userMessageJSON struct {
ID apijson.Field
- Parts apijson.Field
Role apijson.Field
SessionID apijson.Field
Time apijson.Field
@@ -1420,119 +1498,82 @@ func (r userMessageTimeJSON) RawJSON() string {
return r.raw
}
-type UserMessagePart struct {
- Type UserMessagePartType `json:"type,required"`
- Filename string `json:"filename"`
- Mime string `json:"mime"`
- Synthetic bool `json:"synthetic"`
- Text string `json:"text"`
- URL string `json:"url"`
- JSON userMessagePartJSON `json:"-"`
- union UserMessagePartUnion
+type SessionMessagesResponse struct {
+ Info Message `json:"info,required"`
+ Parts []Part `json:"parts,required"`
+ JSON sessionMessagesResponseJSON `json:"-"`
}
-// userMessagePartJSON contains the JSON metadata for the struct [UserMessagePart]
-type userMessagePartJSON struct {
- Type apijson.Field
- Filename apijson.Field
- Mime apijson.Field
- Synthetic apijson.Field
- Text apijson.Field
- URL apijson.Field
+// sessionMessagesResponseJSON contains the JSON metadata for the struct
+// [SessionMessagesResponse]
+type sessionMessagesResponseJSON struct {
+ Info apijson.Field
+ Parts apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
-func (r userMessagePartJSON) RawJSON() string {
+func (r *SessionMessagesResponse) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionMessagesResponseJSON) RawJSON() string {
return r.raw
}
-func (r *UserMessagePart) UnmarshalJSON(data []byte) (err error) {
- *r = UserMessagePart{}
- err = apijson.UnmarshalRoot(data, &r.union)
- if err != nil {
- return err
- }
- return apijson.Port(r.union, &r)
+type SessionChatParams struct {
+ MessageID param.Field[string] `json:"messageID,required"`
+ Mode param.Field[string] `json:"mode,required"`
+ ModelID param.Field[string] `json:"modelID,required"`
+ Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
+ ProviderID param.Field[string] `json:"providerID,required"`
}
-// AsUnion returns a [UserMessagePartUnion] interface which you can cast to the
-// specific types for more type safety.
-//
-// Possible runtime types of the union are [TextPart], [FilePart].
-func (r UserMessagePart) AsUnion() UserMessagePartUnion {
- return r.union
+func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
}
-// Union satisfied by [TextPart] or [FilePart].
-type UserMessagePartUnion interface {
- implementsUserMessagePart()
+type SessionChatParamsPart struct {
+ ID param.Field[string] `json:"id,required"`
+ MessageID param.Field[string] `json:"messageID,required"`
+ SessionID param.Field[string] `json:"sessionID,required"`
+ Type param.Field[SessionChatParamsPartsType] `json:"type,required"`
+ Filename param.Field[string] `json:"filename"`
+ Mime param.Field[string] `json:"mime"`
+ Synthetic param.Field[bool] `json:"synthetic"`
+ Text param.Field[string] `json:"text"`
+ Time param.Field[interface{}] `json:"time"`
+ URL param.Field[string] `json:"url"`
}
-func init() {
- apijson.RegisterUnion(
- reflect.TypeOf((*UserMessagePartUnion)(nil)).Elem(),
- "type",
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(TextPart{}),
- DiscriminatorValue: "text",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(FilePart{}),
- DiscriminatorValue: "file",
- },
- )
+func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
}
-type UserMessagePartType string
+func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {}
+
+// Satisfied by [FilePartParam], [TextPartParam], [SessionChatParamsPart].
+type SessionChatParamsPartUnion interface {
+ implementsSessionChatParamsPartUnion()
+}
+
+type SessionChatParamsPartsType string
const (
- UserMessagePartTypeText UserMessagePartType = "text"
- UserMessagePartTypeFile UserMessagePartType = "file"
+ SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
+ SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
)
-func (r UserMessagePartType) IsKnown() bool {
+func (r SessionChatParamsPartsType) IsKnown() bool {
switch r {
- case UserMessagePartTypeText, UserMessagePartTypeFile:
+ case SessionChatParamsPartsTypeFile, SessionChatParamsPartsTypeText:
return true
}
return false
}
-type UserMessagePartParam struct {
- Type param.Field[UserMessagePartType] `json:"type,required"`
- Filename param.Field[string] `json:"filename"`
- Mime param.Field[string] `json:"mime"`
- Synthetic param.Field[bool] `json:"synthetic"`
- Text param.Field[string] `json:"text"`
- URL param.Field[string] `json:"url"`
-}
-
-func (r UserMessagePartParam) MarshalJSON() (data []byte, err error) {
- return apijson.MarshalRoot(r)
-}
-
-func (r UserMessagePartParam) implementsUserMessagePartUnionParam() {}
-
-// Satisfied by [TextPartParam], [FilePartParam], [UserMessagePartParam].
-type UserMessagePartUnionParam interface {
- implementsUserMessagePartUnionParam()
-}
-
-type SessionChatParams struct {
- Mode param.Field[string] `json:"mode,required"`
- ModelID param.Field[string] `json:"modelID,required"`
- Parts param.Field[[]UserMessagePartUnionParam] `json:"parts,required"`
- ProviderID param.Field[string] `json:"providerID,required"`
-}
-
-func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
- return apijson.MarshalRoot(r)
-}
-
type SessionInitParams struct {
+ MessageID param.Field[string] `json:"messageID,required"`
ModelID param.Field[string] `json:"modelID,required"`
ProviderID param.Field[string] `json:"providerID,required"`
}
diff --git a/packages/tui/sdk/session_test.go b/packages/tui/sdk/session_test.go
index 4ff2818c5..c74a4a385 100644
--- a/packages/tui/sdk/session_test.go
+++ b/packages/tui/sdk/session_test.go
@@ -117,12 +117,17 @@ func TestSessionChat(t *testing.T) {
context.TODO(),
"id",
opencode.SessionChatParams{
- Mode: opencode.F("mode"),
- ModelID: opencode.F("modelID"),
- Parts: opencode.F([]opencode.UserMessagePartUnionParam{opencode.TextPartParam{
- Text: opencode.F("text"),
- Type: opencode.F(opencode.TextPartTypeText),
- Synthetic: opencode.F(true),
+ MessageID: opencode.F("messageID"),
+ Mode: opencode.F("mode"),
+ ModelID: opencode.F("modelID"),
+ Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.FilePartParam{
+ ID: opencode.F("id"),
+ MessageID: opencode.F("messageID"),
+ Mime: opencode.F("mime"),
+ SessionID: opencode.F("sessionID"),
+ Type: opencode.F(opencode.FilePartTypeFile),
+ URL: opencode.F("url"),
+ Filename: opencode.F("filename"),
}}),
ProviderID: opencode.F("providerID"),
},
@@ -152,6 +157,7 @@ func TestSessionInit(t *testing.T) {
context.TODO(),
"id",
opencode.SessionInitParams{
+ MessageID: opencode.F("messageID"),
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
},
diff --git a/packages/tui/sdk/usage_test.go b/packages/tui/sdk/usage_test.go
index 0e261a7aa..5e8f44c7c 100644
--- a/packages/tui/sdk/usage_test.go
+++ b/packages/tui/sdk/usage_test.go
@@ -23,10 +23,13 @@ func TestUsage(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- events, err := client.Event.List(context.TODO())
+ stream := client.Event.ListStreaming(context.TODO())
+ for stream.Next() {
+ t.Logf("%+v\n", stream.Current())
+ }
+ err := stream.Err()
if err != nil {
t.Error(err)
return
}
- t.Logf("%+v\n", events)
}