diff options
| author | adamdottv <[email protected]> | 2025-05-28 12:35:20 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-05-28 15:36:35 -0500 |
| commit | 15d21bf04acd6af75df97918f66df866c239b248 (patch) | |
| tree | 16ac93b0c686faaf3b21700771b082b6bc26f098 /internal/app | |
| parent | 5e738ce7d3b66b88a407a1bcc53a5169d0f4a904 (diff) | |
| download | opencode-15d21bf04acd6af75df97918f66df866c239b248.tar.gz opencode-15d21bf04acd6af75df97918f66df866c239b248.zip | |
wip: refactoring tui
Diffstat (limited to 'internal/app')
| -rw-r--r-- | internal/app/app.go | 35 | ||||
| -rw-r--r-- | internal/app/event_adapter.go | 158 | ||||
| -rw-r--r-- | internal/app/services_bridge.go | 19 |
3 files changed, 33 insertions, 179 deletions
diff --git a/internal/app/app.go b/internal/app/app.go index 7d0bdd4cb..7aed6fb10 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -18,14 +18,17 @@ import ( ) type App struct { + State map[string]any + CurrentSession *session.Session - Logs interface{} // TODO: Define LogService interface when needed + Logs any // TODO: Define LogService interface when needed Sessions SessionService Messages MessageService - History interface{} // TODO: Define HistoryService interface when needed - Permissions interface{} // TODO: Define PermissionService interface when needed + History any // TODO: Define HistoryService interface when needed + Permissions any // TODO: Define PermissionService interface when needed Status status.Service - Client *client.Client + Client *client.ClientWithResponses + Events *client.Client PrimaryAgent AgentService @@ -36,9 +39,9 @@ type App struct { watcherCancelFuncs []context.CancelFunc cancelFuncsMutex sync.Mutex watcherWG sync.WaitGroup - + // UI state - filepickerOpen bool + filepickerOpen bool completionDialogOpen bool } @@ -49,16 +52,22 @@ func New(ctx context.Context) (*App, error) { slog.Error("Failed to initialize status service", "error", err) return nil, err } - + // Initialize file utilities fileutil.Init() // Create HTTP client - httpClient, err := client.NewClient("http://localhost:16713") + url := "http://localhost:16713" + httpClient, err := client.NewClientWithResponses(url) if err != nil { slog.Error("Failed to create client", "error", err) return nil, err } + eventClient, err := client.NewClient(url) + if err != nil { + slog.Error("Failed to create event client", "error", err) + return nil, err + } // Create service bridges sessionBridge := NewSessionServiceBridge(httpClient) @@ -66,18 +75,20 @@ func New(ctx context.Context) (*App, error) { agentBridge := NewAgentServiceBridge(httpClient) app := &App{ + State: make(map[string]any), Client: httpClient, + Events: eventClient, CurrentSession: &session.Session{}, Sessions: sessionBridge, Messages: messageBridge, PrimaryAgent: agentBridge, Status: status.GetService(), LSPClients: make(map[string]*lsp.Client), - + // TODO: These services need API endpoints: - Logs: nil, // logging.GetService(), - History: nil, // history.GetService(), - Permissions: nil, // permission.GetService(), + Logs: nil, // logging.GetService(), + History: nil, // history.GetService(), + Permissions: nil, // permission.GetService(), } // Initialize theme based on configuration diff --git a/internal/app/event_adapter.go b/internal/app/event_adapter.go deleted file mode 100644 index 4772bde49..000000000 --- a/internal/app/event_adapter.go +++ /dev/null @@ -1,158 +0,0 @@ -package app - -import ( - "encoding/json" - "time" - - tea "github.com/charmbracelet/bubbletea" - "github.com/sst/opencode/internal/message" - "github.com/sst/opencode/pkg/client" -) - -// StorageWriteMsg is sent when a storage.write event is received -type StorageWriteMsg struct { - Key string - Content interface{} -} - -// ProcessSSEEvent converts SSE events into TUI messages -func ProcessSSEEvent(event interface{}) tea.Msg { - switch e := event.(type) { - case *client.EventStorageWrite: - return StorageWriteMsg{ - Key: e.Key, - Content: e.Content, - } - } - - // Return the raw event if we don't have a specific handler - return event -} - -// MessageFromStorage converts storage content to internal message format -type MessageData struct { - ID string `json:"id"` - Role string `json:"role"` - Parts []interface{} `json:"parts"` - Metadata map[string]interface{} `json:"metadata"` -} - -// SessionInfoFromStorage converts storage content to session info -type SessionInfoData struct { - ID string `json:"id"` - Title string `json:"title"` - ShareID *string `json:"shareID,omitempty"` - Tokens struct { - Input float32 `json:"input"` - Output float32 `json:"output"` - Reasoning float32 `json:"reasoning"` - } `json:"tokens"` -} - -// ConvertStorageMessage converts a storage message to internal message format -func ConvertStorageMessage(data interface{}, sessionID string) (*message.Message, error) { - // Convert the interface{} to JSON then back to our struct - jsonData, err := json.Marshal(data) - if err != nil { - return nil, err - } - - var msgData MessageData - if err := json.Unmarshal(jsonData, &msgData); err != nil { - return nil, err - } - - // Convert parts - var parts []message.ContentPart - for _, part := range msgData.Parts { - partMap, ok := part.(map[string]interface{}) - if !ok { - continue - } - - partType, ok := partMap["type"].(string) - if !ok { - continue - } - - switch partType { - case "text": - if text, ok := partMap["text"].(string); ok { - parts = append(parts, message.TextContent{Text: text}) - } - case "tool-invocation": - if toolInv, ok := partMap["toolInvocation"].(map[string]interface{}); ok { - // Convert tool invocation to tool call - toolCall := message.ToolCall{ - ID: toolInv["toolCallId"].(string), - Name: toolInv["toolName"].(string), - Type: "function", - } - - if args, ok := toolInv["args"]; ok { - argsJSON, _ := json.Marshal(args) - toolCall.Input = string(argsJSON) - } - - if state, ok := toolInv["state"].(string); ok { - toolCall.Finished = state == "result" - } - - parts = append(parts, toolCall) - - // If there's a result, add it as a tool result - if result, ok := toolInv["result"]; ok && toolCall.Finished { - resultStr := "" - switch r := result.(type) { - case string: - resultStr = r - default: - resultJSON, _ := json.Marshal(r) - resultStr = string(resultJSON) - } - - parts = append(parts, message.ToolResult{ - ToolCallID: toolCall.ID, - Name: toolCall.Name, - Content: resultStr, - }) - } - } - } - } - - // Convert role - var role message.MessageRole - switch msgData.Role { - case "user": - role = message.User - case "assistant": - role = message.Assistant - case "system": - role = message.System - default: - role = message.MessageRole(msgData.Role) - } - - // Create message - msg := &message.Message{ - ID: msgData.ID, - Role: role, - SessionID: sessionID, - Parts: parts, - CreatedAt: time.Now(), // TODO: Get from metadata - UpdatedAt: time.Now(), // TODO: Get from metadata - } - - // Try to get timestamps from metadata - if metadata, ok := msgData.Metadata["time"].(map[string]interface{}); ok { - if created, ok := metadata["created"].(float64); ok { - msg.CreatedAt = time.Unix(int64(created/1000), 0) - } - if completed, ok := metadata["completed"].(float64); ok { - msg.UpdatedAt = time.Unix(int64(completed/1000), 0) - } - } - - return msg, nil -}
\ No newline at end of file diff --git a/internal/app/services_bridge.go b/internal/app/services_bridge.go index e2d2ab5ba..d7e032bfd 100644 --- a/internal/app/services_bridge.go +++ b/internal/app/services_bridge.go @@ -14,11 +14,11 @@ import ( // SessionServiceBridge adapts the HTTP API to the old session.Service interface type SessionServiceBridge struct { - client *client.Client + client *client.ClientWithResponses } // NewSessionServiceBridge creates a new session service bridge -func NewSessionServiceBridge(client *client.Client) *SessionServiceBridge { +func NewSessionServiceBridge(client *client.ClientWithResponses) *SessionServiceBridge { return &SessionServiceBridge{client: client} } @@ -107,11 +107,11 @@ func (s *SessionServiceBridge) Delete(ctx context.Context, id string) error { // AgentServiceBridge provides a minimal agent service that sends messages to the API type AgentServiceBridge struct { - client *client.Client + client *client.ClientWithResponses } // NewAgentServiceBridge creates a new agent service bridge -func NewAgentServiceBridge(client *client.Client) *AgentServiceBridge { +func NewAgentServiceBridge(client *client.ClientWithResponses) *AgentServiceBridge { return &AgentServiceBridge{client: client} } @@ -123,7 +123,7 @@ func (a *AgentServiceBridge) Run(ctx context.Context, sessionID string, text str // return "", fmt.Errorf("attachments not supported yet") } - parts := interface{}([]map[string]interface{}{ + parts := any([]map[string]any{ { "type": "text", "text": text, @@ -170,12 +170,12 @@ func (a *AgentServiceBridge) CompactSession(ctx context.Context, sessionID strin // MessageServiceBridge provides a minimal message service that fetches from the API type MessageServiceBridge struct { - client *client.Client + client *client.ClientWithResponses broker *pubsub.Broker[message.Message] } // NewMessageServiceBridge creates a new message service bridge -func NewMessageServiceBridge(client *client.Client) *MessageServiceBridge { +func NewMessageServiceBridge(client *client.ClientWithResponses) *MessageServiceBridge { return &MessageServiceBridge{ client: client, broker: pubsub.NewBroker[message.Message](), @@ -198,7 +198,7 @@ func (m *MessageServiceBridge) List(ctx context.Context, sessionID string) ([]me defer resp.Body.Close() // The API returns a different format, we'll need to adapt it - var rawMessages interface{} + var rawMessages any if err := json.NewDecoder(resp.Body).Decode(&rawMessages); err != nil { return nil, err } @@ -247,4 +247,5 @@ func (m *MessageServiceBridge) ListAfter(ctx context.Context, sessionID string, // Subscribe subscribes to message events func (m *MessageServiceBridge) Subscribe(ctx context.Context) <-chan pubsub.Event[message.Message] { return m.broker.Subscribe(ctx) -}
\ No newline at end of file +} + |
