mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
wip(tui): undo/redo
This commit is contained in:
parent
9e7bd9ca9a
commit
d2f9e24f26
17 changed files with 573 additions and 67 deletions
|
|
@ -26,6 +26,9 @@ export namespace Config {
|
|||
if (result.autoshare === true && !result.share) {
|
||||
result.share = "auto"
|
||||
}
|
||||
if (result.keybinds?.messages_revert && !result.keybinds.messages_undo) {
|
||||
result.keybinds.messages_undo = result.keybinds.messages_revert
|
||||
}
|
||||
|
||||
if (!result.username) {
|
||||
const os = await import("os")
|
||||
|
|
@ -89,7 +92,7 @@ export namespace Config {
|
|||
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
||||
session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
|
||||
session_share: z.string().optional().default("<leader>s").describe("Share current session"),
|
||||
session_unshare: z.string().optional().default("<leader>u").describe("Unshare current session"),
|
||||
session_unshare: z.string().optional().default("none").describe("Unshare current session"),
|
||||
session_interrupt: z.string().optional().default("esc").describe("Interrupt current session"),
|
||||
session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
|
||||
tool_details: z.string().optional().default("<leader>d").describe("Toggle tool details"),
|
||||
|
|
@ -118,7 +121,9 @@ export namespace Config {
|
|||
messages_last: z.string().optional().default("ctrl+alt+g").describe("Navigate to last message"),
|
||||
messages_layout_toggle: z.string().optional().default("<leader>p").describe("Toggle layout"),
|
||||
messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
|
||||
messages_revert: z.string().optional().default("<leader>r").describe("Revert message"),
|
||||
messages_revert: z.string().optional().default("none").describe("@deprecated use messages_undo. Revert message"),
|
||||
messages_undo: z.string().optional().default("<leader>u").describe("Undo message"),
|
||||
messages_redo: z.string().optional().default("<leader>r").describe("Redo message"),
|
||||
app_exit: z.string().optional().default("ctrl+c,<leader>q").describe("Exit the application"),
|
||||
})
|
||||
.strict()
|
||||
|
|
|
|||
|
|
@ -57,15 +57,20 @@ export namespace Server {
|
|||
})
|
||||
})
|
||||
.use(async (c, next) => {
|
||||
log.info("request", {
|
||||
method: c.req.method,
|
||||
path: c.req.path,
|
||||
})
|
||||
const skipLogging = c.req.path === "/log"
|
||||
if (!skipLogging) {
|
||||
log.info("request", {
|
||||
method: c.req.method,
|
||||
path: c.req.path,
|
||||
})
|
||||
}
|
||||
const start = Date.now()
|
||||
await next()
|
||||
log.info("response", {
|
||||
duration: Date.now() - start,
|
||||
})
|
||||
if (!skipLogging) {
|
||||
log.info("response", {
|
||||
duration: Date.now() - start,
|
||||
})
|
||||
}
|
||||
})
|
||||
.get(
|
||||
"/doc",
|
||||
|
|
@ -459,6 +464,61 @@ export namespace Server {
|
|||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/revert",
|
||||
describeRoute({
|
||||
description: "Revert a message",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
zValidator("json", Session.RevertInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const session = await Session.revert({ sessionID: id, ...c.req.valid("json") })
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/unrevert",
|
||||
describeRoute({
|
||||
description: "Restore all reverted messages",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const session = await Session.unrevert({ sessionID: id })
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/config/providers",
|
||||
describeRoute({
|
||||
|
|
|
|||
|
|
@ -972,7 +972,14 @@ export namespace Session {
|
|||
}
|
||||
}
|
||||
|
||||
export async function revert(input: { sessionID: string; messageID: string; partID?: string }) {
|
||||
export const RevertInput = z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
messageID: Identifier.schema("message"),
|
||||
partID: Identifier.schema("part").optional(),
|
||||
})
|
||||
export type RevertInput = z.infer<typeof RevertInput>
|
||||
|
||||
export async function revert(input: RevertInput) {
|
||||
const all = await messages(input.sessionID)
|
||||
let lastUser: MessageV2.User | undefined
|
||||
let lastSnapshot: MessageV2.SnapshotPart | undefined
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ type SessionCreatedMsg = struct {
|
|||
Session *opencode.Session
|
||||
}
|
||||
type SessionSelectedMsg = *opencode.Session
|
||||
type MessageRevertedMsg struct {
|
||||
Session opencode.Session
|
||||
Message Message
|
||||
}
|
||||
type SessionUnrevertedMsg struct {
|
||||
Session opencode.Session
|
||||
}
|
||||
type SessionLoadedMsg struct{}
|
||||
type ModelSelectedMsg struct {
|
||||
Provider opencode.Provider
|
||||
|
|
@ -175,6 +182,16 @@ func New(
|
|||
return app, nil
|
||||
}
|
||||
|
||||
func (a *App) Keybind(commandName commands.CommandName) string {
|
||||
command := a.Commands[commandName]
|
||||
kb := command.Keybindings[0]
|
||||
key := kb.Key
|
||||
if kb.RequiresLeader {
|
||||
key = a.Config.Keybinds.Leader + " " + kb.Key
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func (a *App) Key(commandName commands.CommandName) string {
|
||||
t := theme.CurrentTheme()
|
||||
base := styles.NewStyle().Background(t.Background()).Foreground(t.Text()).Bold(true).Render
|
||||
|
|
@ -184,11 +201,7 @@ func (a *App) Key(commandName commands.CommandName) string {
|
|||
Faint(true).
|
||||
Render
|
||||
command := a.Commands[commandName]
|
||||
kb := command.Keybindings[0]
|
||||
key := kb.Key
|
||||
if kb.RequiresLeader {
|
||||
key = a.Config.Keybinds.Leader + " " + kb.Key
|
||||
}
|
||||
key := a.Keybind(commandName)
|
||||
return base(key) + muted(" "+command.Description)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/sst/opencode-sdk-go"
|
||||
|
|
@ -106,6 +107,73 @@ func (p Prompt) ToMessage(
|
|||
}
|
||||
}
|
||||
|
||||
func (m Message) ToPrompt() (*Prompt, error) {
|
||||
switch m.Info.(type) {
|
||||
case opencode.UserMessage:
|
||||
text := ""
|
||||
attachments := []*attachment.Attachment{}
|
||||
for _, part := range m.Parts {
|
||||
switch p := part.(type) {
|
||||
case opencode.TextPart:
|
||||
if p.Synthetic {
|
||||
continue
|
||||
}
|
||||
text += p.Text + " "
|
||||
case opencode.FilePart:
|
||||
switch p.Source.Type {
|
||||
case "file":
|
||||
attachments = append(attachments, &attachment.Attachment{
|
||||
ID: p.ID,
|
||||
Type: "file",
|
||||
Display: p.Source.Text.Value,
|
||||
URL: p.URL,
|
||||
Filename: p.Filename,
|
||||
MediaType: p.Mime,
|
||||
StartIndex: int(p.Source.Text.Start),
|
||||
EndIndex: int(p.Source.Text.End),
|
||||
Source: &attachment.FileSource{
|
||||
Path: p.Source.Path,
|
||||
Mime: p.Mime,
|
||||
},
|
||||
})
|
||||
case "symbol":
|
||||
r := p.Source.Range.(opencode.SymbolSourceRange)
|
||||
attachments = append(attachments, &attachment.Attachment{
|
||||
ID: p.ID,
|
||||
Type: "symbol",
|
||||
Display: p.Source.Text.Value,
|
||||
URL: p.URL,
|
||||
Filename: p.Filename,
|
||||
MediaType: p.Mime,
|
||||
StartIndex: int(p.Source.Text.Start),
|
||||
EndIndex: int(p.Source.Text.End),
|
||||
Source: &attachment.SymbolSource{
|
||||
Path: p.Source.Path,
|
||||
Name: p.Source.Name,
|
||||
Kind: int(p.Source.Kind),
|
||||
Range: attachment.SymbolRange{
|
||||
Start: attachment.Position{
|
||||
Line: int(r.Start.Line),
|
||||
Char: int(r.Start.Character),
|
||||
},
|
||||
End: attachment.Position{
|
||||
Line: int(r.End.Line),
|
||||
Char: int(r.End.Character),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return &Prompt{
|
||||
Text: text,
|
||||
Attachments: attachments,
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("unknown message type")
|
||||
}
|
||||
|
||||
func (m Message) ToSessionChatParams() []opencode.SessionChatParamsPartUnion {
|
||||
parts := []opencode.SessionChatParamsPartUnion{}
|
||||
for _, part := range m.Parts {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ const (
|
|||
MessagesLastCommand CommandName = "messages_last"
|
||||
MessagesLayoutToggleCommand CommandName = "messages_layout_toggle"
|
||||
MessagesCopyCommand CommandName = "messages_copy"
|
||||
MessagesRevertCommand CommandName = "messages_revert"
|
||||
MessagesUndoCommand CommandName = "messages_undo"
|
||||
MessagesRedoCommand CommandName = "messages_redo"
|
||||
AppExitCommand CommandName = "app_exit"
|
||||
)
|
||||
|
||||
|
|
@ -328,9 +329,16 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
|||
Keybindings: parseBindings("<leader>y"),
|
||||
},
|
||||
{
|
||||
Name: MessagesRevertCommand,
|
||||
Description: "revert message",
|
||||
Name: MessagesUndoCommand,
|
||||
Description: "undo last message",
|
||||
Keybindings: parseBindings("<leader>u"),
|
||||
Trigger: []string{"undo"},
|
||||
},
|
||||
{
|
||||
Name: MessagesRedoCommand,
|
||||
Description: "redo message",
|
||||
Keybindings: parseBindings("<leader>r"),
|
||||
Trigger: []string{"redo"},
|
||||
},
|
||||
{
|
||||
Name: AppExitCommand,
|
||||
|
|
@ -345,7 +353,8 @@ func LoadFromConfig(config *opencode.Config) CommandRegistry {
|
|||
json.Unmarshal(marshalled, &keybinds)
|
||||
for _, command := range defaults {
|
||||
// Remove share/unshare commands if sharing is disabled
|
||||
if config.Share == opencode.ConfigShareDisabled && (command.Name == SessionShareCommand || command.Name == SessionUnshareCommand) {
|
||||
if config.Share == opencode.ConfigShareDisabled &&
|
||||
(command.Name == SessionShareCommand || command.Name == SessionUnshareCommand) {
|
||||
continue
|
||||
}
|
||||
if keybind, ok := keybinds[string(command.Name)]; ok && keybind != "" {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/sst/opencode/internal/commands"
|
||||
"github.com/sst/opencode/internal/components/dialog"
|
||||
"github.com/sst/opencode/internal/components/textarea"
|
||||
"github.com/sst/opencode/internal/components/toast"
|
||||
"github.com/sst/opencode/internal/styles"
|
||||
"github.com/sst/opencode/internal/theme"
|
||||
"github.com/sst/opencode/internal/util"
|
||||
|
|
@ -57,6 +58,7 @@ type editorComponent struct {
|
|||
historyIndex int // -1 means current (not in history)
|
||||
currentText string // Store current text when navigating history
|
||||
pasteCounter int
|
||||
reverted bool
|
||||
}
|
||||
|
||||
func (m *editorComponent) Init() tea.Cmd {
|
||||
|
|
@ -120,10 +122,34 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
// Maximize editor responsiveness for printable characters
|
||||
if msg.Text != "" {
|
||||
m.reverted = false
|
||||
m.textarea, cmd = m.textarea.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
case app.MessageRevertedMsg:
|
||||
if msg.Session.ID == m.app.Session.ID {
|
||||
switch msg.Message.Info.(type) {
|
||||
case opencode.UserMessage:
|
||||
prompt, err := msg.Message.ToPrompt()
|
||||
if err != nil {
|
||||
return m, toast.NewErrorToast("Failed to revert message")
|
||||
}
|
||||
m.RestoreFromPrompt(*prompt)
|
||||
m.textarea.MoveToEnd()
|
||||
m.reverted = true
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
case app.SessionUnrevertedMsg:
|
||||
if msg.Session.ID == m.app.Session.ID {
|
||||
if m.reverted {
|
||||
updated, cmd := m.Clear()
|
||||
m = updated.(*editorComponent)
|
||||
return m, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
case tea.PasteMsg:
|
||||
text := string(msg)
|
||||
text = strings.ReplaceAll(text, "\\", "")
|
||||
|
|
@ -626,21 +652,14 @@ func NewEditorComponent(app *app.App) EditorComponent {
|
|||
return m
|
||||
}
|
||||
|
||||
// RestoreFromHistory restores a message from history at the given index
|
||||
func (m *editorComponent) RestoreFromHistory(index int) {
|
||||
if index < 0 || index >= len(m.app.State.MessageHistory) {
|
||||
return
|
||||
}
|
||||
|
||||
entry := m.app.State.MessageHistory[index]
|
||||
|
||||
func (m *editorComponent) RestoreFromPrompt(prompt app.Prompt) {
|
||||
m.textarea.Reset()
|
||||
m.textarea.SetValue(entry.Text)
|
||||
m.textarea.SetValue(prompt.Text)
|
||||
|
||||
// Sort attachments by start index in reverse order (process from end to beginning)
|
||||
// This prevents index shifting issues
|
||||
attachmentsCopy := make([]*attachment.Attachment, len(entry.Attachments))
|
||||
copy(attachmentsCopy, entry.Attachments)
|
||||
attachmentsCopy := make([]*attachment.Attachment, len(prompt.Attachments))
|
||||
copy(attachmentsCopy, prompt.Attachments)
|
||||
|
||||
for i := 0; i < len(attachmentsCopy)-1; i++ {
|
||||
for j := i + 1; j < len(attachmentsCopy); j++ {
|
||||
|
|
@ -657,6 +676,15 @@ func (m *editorComponent) RestoreFromHistory(index int) {
|
|||
}
|
||||
}
|
||||
|
||||
// RestoreFromHistory restores a message from history at the given index
|
||||
func (m *editorComponent) RestoreFromHistory(index int) {
|
||||
if index < 0 || index >= len(m.app.State.MessageHistory) {
|
||||
return
|
||||
}
|
||||
entry := m.app.State.MessageHistory[index]
|
||||
m.RestoreFromPrompt(entry)
|
||||
}
|
||||
|
||||
func getMediaTypeFromExtension(ext string) string {
|
||||
switch strings.ToLower(ext) {
|
||||
case ".jpg":
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/charmbracelet/x/ansi"
|
||||
"github.com/sst/opencode-sdk-go"
|
||||
"github.com/sst/opencode/internal/app"
|
||||
"github.com/sst/opencode/internal/commands"
|
||||
"github.com/sst/opencode/internal/components/dialog"
|
||||
"github.com/sst/opencode/internal/components/toast"
|
||||
"github.com/sst/opencode/internal/layout"
|
||||
|
|
@ -30,6 +32,8 @@ type MessagesComponent interface {
|
|||
GotoTop() (tea.Model, tea.Cmd)
|
||||
GotoBottom() (tea.Model, tea.Cmd)
|
||||
CopyLastMessage() (tea.Model, tea.Cmd)
|
||||
UndoLastMessage() (tea.Model, tea.Cmd)
|
||||
RedoLastMessage() (tea.Model, tea.Cmd)
|
||||
}
|
||||
|
||||
type messagesComponent struct {
|
||||
|
|
@ -160,6 +164,18 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.tail = true
|
||||
m.loading = true
|
||||
return m, m.renderView()
|
||||
case app.SessionUnrevertedMsg:
|
||||
if msg.Session.ID == m.app.Session.ID {
|
||||
m.cache.Clear()
|
||||
m.tail = true
|
||||
return m, m.renderView()
|
||||
}
|
||||
case app.MessageRevertedMsg:
|
||||
if msg.Session.ID == m.app.Session.ID {
|
||||
m.cache.Clear()
|
||||
m.tail = true
|
||||
return m, m.renderView()
|
||||
}
|
||||
|
||||
case opencode.EventListResponseEventSessionUpdated:
|
||||
if msg.Properties.Info.ID == m.app.Session.ID {
|
||||
|
|
@ -204,7 +220,6 @@ type renderCompleteMsg struct {
|
|||
}
|
||||
|
||||
func (m *messagesComponent) renderView() tea.Cmd {
|
||||
|
||||
if m.rendering {
|
||||
slog.Debug("pending render, skipping")
|
||||
m.dirty = true
|
||||
|
|
@ -232,12 +247,26 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||
|
||||
width := m.width // always use full width
|
||||
|
||||
reverted := false
|
||||
revertedMessageCount := 0
|
||||
revertedToolCount := 0
|
||||
for _, message := range m.app.Messages {
|
||||
var content string
|
||||
var cached bool
|
||||
|
||||
switch casted := message.Info.(type) {
|
||||
case opencode.UserMessage:
|
||||
if casted.ID == m.app.Session.Revert.MessageID {
|
||||
reverted = true
|
||||
revertedMessageCount = 1
|
||||
revertedToolCount = 0
|
||||
continue
|
||||
}
|
||||
if reverted {
|
||||
revertedMessageCount++
|
||||
continue
|
||||
}
|
||||
|
||||
for partIndex, part := range message.Parts {
|
||||
switch part := part.(type) {
|
||||
case opencode.TextPart:
|
||||
|
|
@ -312,10 +341,18 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||
}
|
||||
|
||||
case opencode.AssistantMessage:
|
||||
if casted.ID == m.app.Session.Revert.MessageID {
|
||||
reverted = true
|
||||
revertedMessageCount = 1
|
||||
revertedToolCount = 0
|
||||
}
|
||||
hasTextPart := false
|
||||
for partIndex, p := range message.Parts {
|
||||
switch part := p.(type) {
|
||||
case opencode.TextPart:
|
||||
if reverted {
|
||||
continue
|
||||
}
|
||||
hasTextPart = true
|
||||
finished := part.Time.End > 0
|
||||
remainingParts := message.Parts[partIndex+1:]
|
||||
|
|
@ -394,6 +431,10 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||
blocks = append(blocks, content)
|
||||
}
|
||||
case opencode.ToolPart:
|
||||
if reverted {
|
||||
revertedToolCount++
|
||||
continue
|
||||
}
|
||||
if !m.showToolDetails {
|
||||
if !hasTextPart {
|
||||
orphanedToolCalls = append(orphanedToolCalls, part)
|
||||
|
|
@ -460,7 +501,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||
}
|
||||
}
|
||||
|
||||
if error != "" {
|
||||
if error != "" && !reverted {
|
||||
error = styles.NewStyle().Width(width - 6).Render(error)
|
||||
error = renderContentBlock(
|
||||
m.app,
|
||||
|
|
@ -479,6 +520,40 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||
}
|
||||
}
|
||||
|
||||
if revertedMessageCount > 0 || revertedToolCount > 0 {
|
||||
messagePlural := ""
|
||||
toolPlural := ""
|
||||
if revertedMessageCount != 1 {
|
||||
messagePlural = "s"
|
||||
}
|
||||
if revertedToolCount != 1 {
|
||||
toolPlural = "s"
|
||||
}
|
||||
revertedStyle := styles.NewStyle().
|
||||
Background(t.BackgroundPanel()).
|
||||
Foreground(t.TextMuted())
|
||||
|
||||
content := revertedStyle.Render(fmt.Sprintf(
|
||||
"%d message%s reverted, %d tool call%s reverted",
|
||||
revertedMessageCount,
|
||||
messagePlural,
|
||||
revertedToolCount,
|
||||
toolPlural,
|
||||
))
|
||||
hintStyle := styles.NewStyle().Background(t.BackgroundPanel()).Foreground(t.Text())
|
||||
hint := hintStyle.Render(m.app.Keybind(commands.MessagesRedoCommand))
|
||||
hint += revertedStyle.Render(" (or /redo) to restore")
|
||||
|
||||
content += "\n" + hint
|
||||
content = renderContentBlock(
|
||||
m.app,
|
||||
content,
|
||||
width,
|
||||
WithBorderColor(t.BackgroundPanel()),
|
||||
)
|
||||
blocks = append(blocks, content)
|
||||
}
|
||||
|
||||
final := []string{}
|
||||
clipboard := []string{}
|
||||
var selection *selection
|
||||
|
|
@ -510,7 +585,11 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||
middle := strings.TrimRight(ansi.Strip(ansi.Cut(line, left, right)), " ")
|
||||
suffix := ansi.Cut(line, left+len(middle), width)
|
||||
clipboard = append(clipboard, middle)
|
||||
line = prefix + styles.NewStyle().Background(t.Accent()).Foreground(t.BackgroundPanel()).Render(ansi.Strip(middle)) + suffix
|
||||
line = prefix + styles.NewStyle().
|
||||
Background(t.Accent()).
|
||||
Foreground(t.BackgroundPanel()).
|
||||
Render(ansi.Strip(middle)) +
|
||||
suffix
|
||||
}
|
||||
final = append(final, line)
|
||||
}
|
||||
|
|
@ -761,6 +840,155 @@ func (m *messagesComponent) CopyLastMessage() (tea.Model, tea.Cmd) {
|
|||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *messagesComponent) UndoLastMessage() (tea.Model, tea.Cmd) {
|
||||
after := float64(0)
|
||||
var revertedMessage app.Message
|
||||
reversedMessages := []app.Message{}
|
||||
for i := len(m.app.Messages) - 1; i >= 0; i-- {
|
||||
reversedMessages = append(reversedMessages, m.app.Messages[i])
|
||||
switch casted := m.app.Messages[i].Info.(type) {
|
||||
case opencode.UserMessage:
|
||||
if casted.ID == m.app.Session.Revert.MessageID {
|
||||
after = casted.Time.Created
|
||||
}
|
||||
case opencode.AssistantMessage:
|
||||
if casted.ID == m.app.Session.Revert.MessageID {
|
||||
after = casted.Time.Created
|
||||
}
|
||||
}
|
||||
if m.app.Session.Revert.PartID != "" {
|
||||
for _, part := range m.app.Messages[i].Parts {
|
||||
switch casted := part.(type) {
|
||||
case opencode.TextPart:
|
||||
if casted.ID == m.app.Session.Revert.PartID {
|
||||
after = casted.Time.Start
|
||||
}
|
||||
case opencode.ToolPart:
|
||||
// TODO: handle tool parts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageID := ""
|
||||
for _, msg := range reversedMessages {
|
||||
switch casted := msg.Info.(type) {
|
||||
case opencode.UserMessage:
|
||||
if after > 0 && casted.Time.Created >= after {
|
||||
continue
|
||||
}
|
||||
messageID = casted.ID
|
||||
revertedMessage = msg
|
||||
}
|
||||
if messageID != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if messageID == "" {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m, func() tea.Msg {
|
||||
response, err := m.app.Client.Session.Revert(
|
||||
context.Background(),
|
||||
m.app.Session.ID,
|
||||
opencode.SessionRevertParams{
|
||||
MessageID: opencode.F(messageID),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("Failed to undo message", "error", err)
|
||||
return toast.NewErrorToast("Failed to undo message")
|
||||
}
|
||||
if response == nil {
|
||||
return toast.NewErrorToast("Failed to undo message")
|
||||
}
|
||||
return app.MessageRevertedMsg{Session: *response, Message: revertedMessage}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *messagesComponent) RedoLastMessage() (tea.Model, tea.Cmd) {
|
||||
before := float64(0)
|
||||
var revertedMessage app.Message
|
||||
for _, message := range m.app.Messages {
|
||||
switch casted := message.Info.(type) {
|
||||
case opencode.UserMessage:
|
||||
if casted.ID == m.app.Session.Revert.MessageID {
|
||||
before = casted.Time.Created
|
||||
}
|
||||
case opencode.AssistantMessage:
|
||||
if casted.ID == m.app.Session.Revert.MessageID {
|
||||
before = casted.Time.Created
|
||||
}
|
||||
}
|
||||
if m.app.Session.Revert.PartID != "" {
|
||||
for _, part := range message.Parts {
|
||||
switch casted := part.(type) {
|
||||
case opencode.TextPart:
|
||||
if casted.ID == m.app.Session.Revert.PartID {
|
||||
before = casted.Time.Start
|
||||
}
|
||||
case opencode.ToolPart:
|
||||
// TODO: handle tool parts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageID := ""
|
||||
for _, msg := range m.app.Messages {
|
||||
switch casted := msg.Info.(type) {
|
||||
case opencode.UserMessage:
|
||||
if casted.Time.Created <= before {
|
||||
continue
|
||||
}
|
||||
messageID = casted.ID
|
||||
revertedMessage = msg
|
||||
}
|
||||
if messageID != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if messageID == "" {
|
||||
return m, func() tea.Msg {
|
||||
// unrevert back to original state
|
||||
response, err := m.app.Client.Session.Unrevert(
|
||||
context.Background(),
|
||||
m.app.Session.ID,
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("Failed to unrevert session", "error", err)
|
||||
return toast.NewErrorToast("Failed to redo message")
|
||||
}
|
||||
if response == nil {
|
||||
return toast.NewErrorToast("Failed to redo message")
|
||||
}
|
||||
return app.SessionUnrevertedMsg{Session: *response}
|
||||
}
|
||||
}
|
||||
|
||||
return m, func() tea.Msg {
|
||||
// calling revert on a "later" message is like a redo
|
||||
response, err := m.app.Client.Session.Revert(
|
||||
context.Background(),
|
||||
m.app.Session.ID,
|
||||
opencode.SessionRevertParams{
|
||||
MessageID: opencode.F(messageID),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("Failed to redo message", "error", err)
|
||||
return toast.NewErrorToast("Failed to redo message")
|
||||
}
|
||||
if response == nil {
|
||||
return toast.NewErrorToast("Failed to redo message")
|
||||
}
|
||||
return app.MessageRevertedMsg{Session: *response, Message: revertedMessage}
|
||||
}
|
||||
}
|
||||
|
||||
func NewMessagesComponent(app *app.App) MessagesComponent {
|
||||
vp := viewport.New()
|
||||
vp.KeyMap = viewport.KeyMap{}
|
||||
|
|
|
|||
|
|
@ -463,6 +463,10 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case app.SessionCreatedMsg:
|
||||
a.app.Session = msg.Session
|
||||
return a, util.CmdHandler(app.SessionLoadedMsg{})
|
||||
case app.MessageRevertedMsg:
|
||||
if msg.Session.ID == a.app.Session.ID {
|
||||
a.app.Session = &msg.Session
|
||||
}
|
||||
case app.ModelSelectedMsg:
|
||||
a.app.Provider = &msg.Provider
|
||||
a.app.Model = &msg.Model
|
||||
|
|
@ -1005,7 +1009,14 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
|
|||
updated, cmd := a.messages.CopyLastMessage()
|
||||
a.messages = updated.(chat.MessagesComponent)
|
||||
cmds = append(cmds, cmd)
|
||||
case commands.MessagesRevertCommand:
|
||||
case commands.MessagesUndoCommand:
|
||||
updated, cmd := a.messages.UndoLastMessage()
|
||||
a.messages = updated.(chat.MessagesComponent)
|
||||
cmds = append(cmds, cmd)
|
||||
case commands.MessagesRedoCommand:
|
||||
updated, cmd := a.messages.RedoLastMessage()
|
||||
a.messages = updated.(chat.MessagesComponent)
|
||||
cmds = append(cmds, cmd)
|
||||
case commands.AppExitCommand:
|
||||
return a, tea.Quit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
configured_endpoints: 22
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-e7f4ac9b5afd5c6db4741a27b5445167808b0a3b7c36dfd525bfb3446a11a253.yml
|
||||
openapi_spec_hash: 3e7b367a173d6de7924f35a41ac6b5a5
|
||||
config_hash: 6d56a7ca0d6ed899ecdb5c053a8278ae
|
||||
configured_endpoints: 24
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-e4d6c6107c344f07223707185edd88aec4d15be1b57298c697fb83e43e7fd741.yml
|
||||
openapi_spec_hash: eec0031eab68bb9868fc7aac364d701a
|
||||
config_hash: 8bedc9f1bc45691bd29b7e5162f4984d
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ Methods:
|
|||
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#App">App</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#LogLevel">LogLevel</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#Mode">Mode</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#Model">Model</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#Provider">Provider</a>
|
||||
|
|
@ -115,6 +114,8 @@ Methods:
|
|||
- <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#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>
|
||||
- <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="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>
|
||||
|
|
|
|||
|
|
@ -145,24 +145,6 @@ func (r appTimeJSON) RawJSON() string {
|
|||
return r.raw
|
||||
}
|
||||
|
||||
// Log level
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
LogLevelDebug LogLevel = "DEBUG"
|
||||
LogLevelInfo LogLevel = "INFO"
|
||||
LogLevelWarn LogLevel = "WARN"
|
||||
LogLevelError LogLevel = "ERROR"
|
||||
)
|
||||
|
||||
func (r LogLevel) IsKnown() bool {
|
||||
switch r {
|
||||
case LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Mode struct {
|
||||
Name string `json:"name,required"`
|
||||
Tools map[string]bool `json:"tools,required"`
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ type Config struct {
|
|||
Keybinds KeybindsConfig `json:"keybinds"`
|
||||
// @deprecated Always uses stretch layout.
|
||||
Layout ConfigLayout `json:"layout"`
|
||||
// Minimum log level to write to log files
|
||||
LogLevel LogLevel `json:"log_level"`
|
||||
// MCP (Model Context Protocol) server configurations
|
||||
Mcp map[string]ConfigMcp `json:"mcp"`
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
|
|
@ -90,7 +88,6 @@ type configJSON struct {
|
|||
Instructions apijson.Field
|
||||
Keybinds apijson.Field
|
||||
Layout apijson.Field
|
||||
LogLevel apijson.Field
|
||||
Mcp apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
|
|
@ -513,8 +510,12 @@ type KeybindsConfig struct {
|
|||
MessagesPageUp string `json:"messages_page_up,required"`
|
||||
// Navigate to previous message
|
||||
MessagesPrevious string `json:"messages_previous,required"`
|
||||
// Revert message
|
||||
// Redo message
|
||||
MessagesRedo string `json:"messages_redo,required"`
|
||||
// @deprecated use messages_undo. Revert message
|
||||
MessagesRevert string `json:"messages_revert,required"`
|
||||
// Undo message
|
||||
MessagesUndo string `json:"messages_undo,required"`
|
||||
// List available models
|
||||
ModelList string `json:"model_list,required"`
|
||||
// Create/update AGENTS.md
|
||||
|
|
@ -568,7 +569,9 @@ type keybindsConfigJSON struct {
|
|||
MessagesPageDown apijson.Field
|
||||
MessagesPageUp apijson.Field
|
||||
MessagesPrevious apijson.Field
|
||||
MessagesRedo apijson.Field
|
||||
MessagesRevert apijson.Field
|
||||
MessagesUndo apijson.Field
|
||||
ModelList apijson.Field
|
||||
ProjectInit apijson.Field
|
||||
SessionCompact apijson.Field
|
||||
|
|
|
|||
|
|
@ -27,14 +27,15 @@ type RequestOption = requestconfig.RequestOption
|
|||
// For security reasons, ensure that the base URL is trusted.
|
||||
func WithBaseURL(base string) RequestOption {
|
||||
u, err := url.Parse(base)
|
||||
if err == nil && u.Path != "" && !strings.HasSuffix(u.Path, "/") {
|
||||
u.Path += "/"
|
||||
}
|
||||
|
||||
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("requestoption: WithBaseURL failed to parse url %s\n", err)
|
||||
return fmt.Errorf("requestoption: WithBaseURL failed to parse url %s", err)
|
||||
}
|
||||
|
||||
if u.Path != "" && !strings.HasSuffix(u.Path, "/") {
|
||||
u.Path += "/"
|
||||
}
|
||||
r.BaseURL = u
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -112,6 +112,18 @@ func (r *SessionService) Messages(ctx context.Context, id string, opts ...option
|
|||
return
|
||||
}
|
||||
|
||||
// Revert a message
|
||||
func (r *SessionService) Revert(ctx context.Context, id string, body SessionRevertParams, opts ...option.RequestOption) (res *Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/revert", id)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Share a session
|
||||
func (r *SessionService) Share(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
|
|
@ -136,6 +148,18 @@ func (r *SessionService) Summarize(ctx context.Context, id string, body SessionS
|
|||
return
|
||||
}
|
||||
|
||||
// Restore all reverted messages
|
||||
func (r *SessionService) Unrevert(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/unrevert", id)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Unshare the session
|
||||
func (r *SessionService) Unshare(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
|
|
@ -988,7 +1012,7 @@ func (r sessionTimeJSON) RawJSON() string {
|
|||
|
||||
type SessionRevert struct {
|
||||
MessageID string `json:"messageID,required"`
|
||||
Part float64 `json:"part,required"`
|
||||
PartID string `json:"partID"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
JSON sessionRevertJSON `json:"-"`
|
||||
}
|
||||
|
|
@ -996,7 +1020,7 @@ type SessionRevert struct {
|
|||
// sessionRevertJSON contains the JSON metadata for the struct [SessionRevert]
|
||||
type sessionRevertJSON struct {
|
||||
MessageID apijson.Field
|
||||
Part apijson.Field
|
||||
PartID apijson.Field
|
||||
Snapshot apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
|
|
@ -1954,6 +1978,7 @@ type SessionChatParams struct {
|
|||
ProviderID param.Field[string] `json:"providerID,required"`
|
||||
MessageID param.Field[string] `json:"messageID"`
|
||||
Mode param.Field[string] `json:"mode"`
|
||||
Tools param.Field[map[string]bool] `json:"tools"`
|
||||
}
|
||||
|
||||
func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
|
||||
|
|
@ -2009,6 +2034,15 @@ func (r SessionInitParams) MarshalJSON() (data []byte, err error) {
|
|||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type SessionRevertParams struct {
|
||||
MessageID param.Field[string] `json:"messageID,required"`
|
||||
PartID param.Field[string] `json:"partID"`
|
||||
}
|
||||
|
||||
func (r SessionRevertParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type SessionSummarizeParams struct {
|
||||
ModelID param.Field[string] `json:"modelID,required"`
|
||||
ProviderID param.Field[string] `json:"providerID,required"`
|
||||
|
|
|
|||
|
|
@ -131,6 +131,9 @@ func TestSessionChatWithOptionalParams(t *testing.T) {
|
|||
ProviderID: opencode.F("providerID"),
|
||||
MessageID: opencode.F("msg"),
|
||||
Mode: opencode.F("mode"),
|
||||
Tools: opencode.F(map[string]bool{
|
||||
"foo": true,
|
||||
}),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -194,6 +197,35 @@ func TestSessionMessages(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionRevertWithOptionalParams(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.Revert(
|
||||
context.TODO(),
|
||||
"id",
|
||||
opencode.SessionRevertParams{
|
||||
MessageID: opencode.F("msg"),
|
||||
PartID: opencode.F("prt"),
|
||||
},
|
||||
)
|
||||
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 TestSessionShare(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
|
|
@ -245,6 +277,28 @@ func TestSessionSummarize(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSessionUnrevert(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.Unrevert(context.TODO(), "id")
|
||||
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 TestSessionUnshare(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ resources:
|
|||
summarize: post /session/{id}/summarize
|
||||
messages: get /session/{id}/message
|
||||
chat: post /session/{id}/message
|
||||
revert: post /session/{id}/revert
|
||||
unrevert: post /session/{id}/unrevert
|
||||
|
||||
settings:
|
||||
disable_mock_tests: true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue