| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773 |
- package workspace
- import (
- "context"
- "fmt"
- "log/slog"
- "strings"
- "sync"
- "time"
- tea "charm.land/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/agent/notify"
- "github.com/charmbracelet/crush/internal/agent/tools/mcp"
- "github.com/charmbracelet/crush/internal/client"
- "github.com/charmbracelet/crush/internal/config"
- "github.com/charmbracelet/crush/internal/history"
- "github.com/charmbracelet/crush/internal/log"
- "github.com/charmbracelet/crush/internal/lsp"
- "github.com/charmbracelet/crush/internal/message"
- "github.com/charmbracelet/crush/internal/oauth"
- "github.com/charmbracelet/crush/internal/permission"
- "github.com/charmbracelet/crush/internal/proto"
- "github.com/charmbracelet/crush/internal/pubsub"
- "github.com/charmbracelet/crush/internal/session"
- "github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
- )
- // ClientWorkspace implements the Workspace interface by delegating all
- // operations to a remote server via the client SDK. It caches the
- // proto.Workspace returned at creation time and refreshes it after
- // config-mutating operations.
- type ClientWorkspace struct {
- client *client.Client
- mu sync.RWMutex
- ws proto.Workspace
- }
- // NewClientWorkspace creates a new ClientWorkspace that proxies all
- // operations through the given client SDK. The ws parameter is the
- // proto.Workspace snapshot returned by the server at creation time.
- func NewClientWorkspace(c *client.Client, ws proto.Workspace) *ClientWorkspace {
- if ws.Config != nil {
- ws.Config.SetupAgents()
- }
- return &ClientWorkspace{
- client: c,
- ws: ws,
- }
- }
- // refreshWorkspace re-fetches the workspace from the server, updating
- // the cached snapshot. Called after config-mutating operations.
- func (w *ClientWorkspace) refreshWorkspace() {
- updated, err := w.client.GetWorkspace(context.Background(), w.ws.ID)
- if err != nil {
- slog.Error("Failed to refresh workspace", "error", err)
- return
- }
- if updated.Config != nil {
- updated.Config.SetupAgents()
- }
- w.mu.Lock()
- w.ws = *updated
- w.mu.Unlock()
- }
- // cached returns a snapshot of the cached workspace.
- func (w *ClientWorkspace) cached() proto.Workspace {
- w.mu.RLock()
- defer w.mu.RUnlock()
- return w.ws
- }
- // workspaceID returns the cached workspace ID.
- func (w *ClientWorkspace) workspaceID() string {
- return w.cached().ID
- }
- // -- Sessions --
- func (w *ClientWorkspace) CreateSession(ctx context.Context, title string) (session.Session, error) {
- sess, err := w.client.CreateSession(ctx, w.workspaceID(), title)
- if err != nil {
- return session.Session{}, err
- }
- return protoToSession(*sess), nil
- }
- func (w *ClientWorkspace) GetSession(ctx context.Context, sessionID string) (session.Session, error) {
- sess, err := w.client.GetSession(ctx, w.workspaceID(), sessionID)
- if err != nil {
- return session.Session{}, err
- }
- return protoToSession(*sess), nil
- }
- func (w *ClientWorkspace) ListSessions(ctx context.Context) ([]session.Session, error) {
- protoSessions, err := w.client.ListSessions(ctx, w.workspaceID())
- if err != nil {
- return nil, err
- }
- sessions := make([]session.Session, len(protoSessions))
- for i, s := range protoSessions {
- sessions[i] = protoToSession(s)
- }
- return sessions, nil
- }
- func (w *ClientWorkspace) SaveSession(ctx context.Context, sess session.Session) (session.Session, error) {
- saved, err := w.client.SaveSession(ctx, w.workspaceID(), sessionToProto(sess))
- if err != nil {
- return session.Session{}, err
- }
- return protoToSession(*saved), nil
- }
- func (w *ClientWorkspace) DeleteSession(ctx context.Context, sessionID string) error {
- return w.client.DeleteSession(ctx, w.workspaceID(), sessionID)
- }
- func (w *ClientWorkspace) CreateAgentToolSessionID(messageID, toolCallID string) string {
- return fmt.Sprintf("%s$$%s", messageID, toolCallID)
- }
- func (w *ClientWorkspace) ParseAgentToolSessionID(sessionID string) (string, string, bool) {
- parts := strings.Split(sessionID, "$$")
- if len(parts) != 2 {
- return "", "", false
- }
- return parts[0], parts[1], true
- }
- // -- Messages --
- func (w *ClientWorkspace) ListMessages(ctx context.Context, sessionID string) ([]message.Message, error) {
- msgs, err := w.client.ListMessages(ctx, w.workspaceID(), sessionID)
- if err != nil {
- return nil, err
- }
- return protoToMessages(msgs), nil
- }
- func (w *ClientWorkspace) ListUserMessages(ctx context.Context, sessionID string) ([]message.Message, error) {
- msgs, err := w.client.ListUserMessages(ctx, w.workspaceID(), sessionID)
- if err != nil {
- return nil, err
- }
- return protoToMessages(msgs), nil
- }
- func (w *ClientWorkspace) ListAllUserMessages(ctx context.Context) ([]message.Message, error) {
- msgs, err := w.client.ListAllUserMessages(ctx, w.workspaceID())
- if err != nil {
- return nil, err
- }
- return protoToMessages(msgs), nil
- }
- // -- Agent --
- func (w *ClientWorkspace) AgentRun(ctx context.Context, sessionID, prompt string, attachments ...message.Attachment) error {
- return w.client.SendMessage(ctx, w.workspaceID(), sessionID, prompt, attachments...)
- }
- func (w *ClientWorkspace) AgentCancel(sessionID string) {
- _ = w.client.CancelAgentSession(context.Background(), w.workspaceID(), sessionID)
- }
- func (w *ClientWorkspace) AgentIsBusy() bool {
- info, err := w.client.GetAgentInfo(context.Background(), w.workspaceID())
- if err != nil {
- return false
- }
- return info.IsBusy
- }
- func (w *ClientWorkspace) AgentIsSessionBusy(sessionID string) bool {
- info, err := w.client.GetAgentSessionInfo(context.Background(), w.workspaceID(), sessionID)
- if err != nil {
- return false
- }
- return info.IsBusy
- }
- func (w *ClientWorkspace) AgentModel() AgentModel {
- info, err := w.client.GetAgentInfo(context.Background(), w.workspaceID())
- if err != nil {
- return AgentModel{}
- }
- return AgentModel{
- CatwalkCfg: info.Model,
- ModelCfg: info.ModelCfg,
- }
- }
- func (w *ClientWorkspace) AgentIsReady() bool {
- info, err := w.client.GetAgentInfo(context.Background(), w.workspaceID())
- if err != nil {
- return false
- }
- return info.IsReady
- }
- func (w *ClientWorkspace) AgentQueuedPrompts(sessionID string) int {
- count, err := w.client.GetAgentSessionQueuedPrompts(context.Background(), w.workspaceID(), sessionID)
- if err != nil {
- return 0
- }
- return count
- }
- func (w *ClientWorkspace) AgentQueuedPromptsList(sessionID string) []string {
- prompts, err := w.client.GetAgentSessionQueuedPromptsList(context.Background(), w.workspaceID(), sessionID)
- if err != nil {
- return nil
- }
- return prompts
- }
- func (w *ClientWorkspace) AgentClearQueue(sessionID string) {
- _ = w.client.ClearAgentSessionQueuedPrompts(context.Background(), w.workspaceID(), sessionID)
- }
- func (w *ClientWorkspace) AgentSummarize(ctx context.Context, sessionID string) error {
- return w.client.AgentSummarizeSession(ctx, w.workspaceID(), sessionID)
- }
- func (w *ClientWorkspace) UpdateAgentModel(ctx context.Context) error {
- return w.client.UpdateAgent(ctx, w.workspaceID())
- }
- func (w *ClientWorkspace) InitCoderAgent(ctx context.Context) error {
- return w.client.InitiateAgentProcessing(ctx, w.workspaceID())
- }
- func (w *ClientWorkspace) GetDefaultSmallModel(providerID string) config.SelectedModel {
- model, err := w.client.GetDefaultSmallModel(context.Background(), w.workspaceID(), providerID)
- if err != nil {
- return config.SelectedModel{}
- }
- return *model
- }
- // -- Permissions --
- func (w *ClientWorkspace) PermissionGrant(perm permission.PermissionRequest) {
- _ = w.client.GrantPermission(context.Background(), w.workspaceID(), proto.PermissionGrant{
- Permission: proto.PermissionRequest{
- ID: perm.ID,
- SessionID: perm.SessionID,
- ToolCallID: perm.ToolCallID,
- ToolName: perm.ToolName,
- Description: perm.Description,
- Action: perm.Action,
- Path: perm.Path,
- Params: perm.Params,
- },
- Action: proto.PermissionAllowForSession,
- })
- }
- func (w *ClientWorkspace) PermissionGrantPersistent(perm permission.PermissionRequest) {
- _ = w.client.GrantPermission(context.Background(), w.workspaceID(), proto.PermissionGrant{
- Permission: proto.PermissionRequest{
- ID: perm.ID,
- SessionID: perm.SessionID,
- ToolCallID: perm.ToolCallID,
- ToolName: perm.ToolName,
- Description: perm.Description,
- Action: perm.Action,
- Path: perm.Path,
- Params: perm.Params,
- },
- Action: proto.PermissionAllow,
- })
- }
- func (w *ClientWorkspace) PermissionDeny(perm permission.PermissionRequest) {
- _ = w.client.GrantPermission(context.Background(), w.workspaceID(), proto.PermissionGrant{
- Permission: proto.PermissionRequest{
- ID: perm.ID,
- SessionID: perm.SessionID,
- ToolCallID: perm.ToolCallID,
- ToolName: perm.ToolName,
- Description: perm.Description,
- Action: perm.Action,
- Path: perm.Path,
- Params: perm.Params,
- },
- Action: proto.PermissionDeny,
- })
- }
- func (w *ClientWorkspace) PermissionSkipRequests() bool {
- skip, err := w.client.GetPermissionsSkipRequests(context.Background(), w.workspaceID())
- if err != nil {
- return false
- }
- return skip
- }
- func (w *ClientWorkspace) PermissionSetSkipRequests(skip bool) {
- _ = w.client.SetPermissionsSkipRequests(context.Background(), w.workspaceID(), skip)
- }
- // -- FileTracker --
- func (w *ClientWorkspace) FileTrackerRecordRead(ctx context.Context, sessionID, path string) {
- _ = w.client.FileTrackerRecordRead(ctx, w.workspaceID(), sessionID, path)
- }
- func (w *ClientWorkspace) FileTrackerLastReadTime(ctx context.Context, sessionID, path string) time.Time {
- t, err := w.client.FileTrackerLastReadTime(ctx, w.workspaceID(), sessionID, path)
- if err != nil {
- return time.Time{}
- }
- return t
- }
- func (w *ClientWorkspace) FileTrackerListReadFiles(ctx context.Context, sessionID string) ([]string, error) {
- return w.client.FileTrackerListReadFiles(ctx, w.workspaceID(), sessionID)
- }
- // -- History --
- func (w *ClientWorkspace) ListSessionHistory(ctx context.Context, sessionID string) ([]history.File, error) {
- files, err := w.client.ListSessionHistoryFiles(ctx, w.workspaceID(), sessionID)
- if err != nil {
- return nil, err
- }
- return protoToFiles(files), nil
- }
- // -- LSP --
- func (w *ClientWorkspace) LSPStart(ctx context.Context, path string) {
- _ = w.client.LSPStart(ctx, w.workspaceID(), path)
- }
- func (w *ClientWorkspace) LSPStopAll(ctx context.Context) {
- _ = w.client.LSPStopAll(ctx, w.workspaceID())
- }
- func (w *ClientWorkspace) LSPGetStates() map[string]LSPClientInfo {
- states, err := w.client.GetLSPs(context.Background(), w.workspaceID())
- if err != nil {
- return nil
- }
- result := make(map[string]LSPClientInfo, len(states))
- for k, v := range states {
- result[k] = LSPClientInfo{
- Name: v.Name,
- State: v.State,
- Error: v.Error,
- DiagnosticCount: v.DiagnosticCount,
- ConnectedAt: v.ConnectedAt,
- }
- }
- return result
- }
- func (w *ClientWorkspace) LSPGetDiagnosticCounts(name string) lsp.DiagnosticCounts {
- diags, err := w.client.GetLSPDiagnostics(context.Background(), w.workspaceID(), name)
- if err != nil {
- return lsp.DiagnosticCounts{}
- }
- var counts lsp.DiagnosticCounts
- for _, fileDiags := range diags {
- for _, d := range fileDiags {
- switch d.Severity {
- case protocol.SeverityError:
- counts.Error++
- case protocol.SeverityWarning:
- counts.Warning++
- case protocol.SeverityInformation:
- counts.Information++
- case protocol.SeverityHint:
- counts.Hint++
- }
- }
- }
- return counts
- }
- // -- Config (read-only) --
- func (w *ClientWorkspace) Config() *config.Config {
- return w.cached().Config
- }
- func (w *ClientWorkspace) WorkingDir() string {
- return w.cached().Path
- }
- func (w *ClientWorkspace) Resolver() config.VariableResolver {
- return config.IdentityResolver()
- }
- // -- Config mutations --
- func (w *ClientWorkspace) UpdatePreferredModel(scope config.Scope, modelType config.SelectedModelType, model config.SelectedModel) error {
- err := w.client.UpdatePreferredModel(context.Background(), w.workspaceID(), scope, modelType, model)
- if err == nil {
- w.refreshWorkspace()
- }
- return err
- }
- func (w *ClientWorkspace) SetCompactMode(scope config.Scope, enabled bool) error {
- err := w.client.SetCompactMode(context.Background(), w.workspaceID(), scope, enabled)
- if err == nil {
- w.refreshWorkspace()
- }
- return err
- }
- func (w *ClientWorkspace) SetProviderAPIKey(scope config.Scope, providerID string, apiKey any) error {
- err := w.client.SetProviderAPIKey(context.Background(), w.workspaceID(), scope, providerID, apiKey)
- if err == nil {
- w.refreshWorkspace()
- }
- return err
- }
- func (w *ClientWorkspace) SetConfigField(scope config.Scope, key string, value any) error {
- err := w.client.SetConfigField(context.Background(), w.workspaceID(), scope, key, value)
- if err == nil {
- w.refreshWorkspace()
- }
- return err
- }
- func (w *ClientWorkspace) RemoveConfigField(scope config.Scope, key string) error {
- err := w.client.RemoveConfigField(context.Background(), w.workspaceID(), scope, key)
- if err == nil {
- w.refreshWorkspace()
- }
- return err
- }
- func (w *ClientWorkspace) ImportCopilot() (*oauth.Token, bool) {
- token, ok, err := w.client.ImportCopilot(context.Background(), w.workspaceID())
- if err != nil {
- return nil, false
- }
- if ok {
- w.refreshWorkspace()
- }
- return token, ok
- }
- func (w *ClientWorkspace) RefreshOAuthToken(ctx context.Context, scope config.Scope, providerID string) error {
- err := w.client.RefreshOAuthToken(ctx, w.workspaceID(), scope, providerID)
- if err == nil {
- w.refreshWorkspace()
- }
- return err
- }
- // -- Project lifecycle --
- func (w *ClientWorkspace) ProjectNeedsInitialization() (bool, error) {
- return w.client.ProjectNeedsInitialization(context.Background(), w.workspaceID())
- }
- func (w *ClientWorkspace) MarkProjectInitialized() error {
- return w.client.MarkProjectInitialized(context.Background(), w.workspaceID())
- }
- func (w *ClientWorkspace) InitializePrompt() (string, error) {
- return w.client.GetInitializePrompt(context.Background(), w.workspaceID())
- }
- // -- MCP operations --
- func (w *ClientWorkspace) MCPGetStates() map[string]mcp.ClientInfo {
- states, err := w.client.MCPGetStates(context.Background(), w.workspaceID())
- if err != nil {
- return nil
- }
- result := make(map[string]mcp.ClientInfo, len(states))
- for k, v := range states {
- result[k] = mcp.ClientInfo{
- Name: v.Name,
- State: mcp.State(v.State),
- Error: v.Error,
- Counts: mcp.Counts{
- Tools: v.ToolCount,
- Prompts: v.PromptCount,
- Resources: v.ResourceCount,
- },
- ConnectedAt: v.ConnectedAt,
- }
- }
- return result
- }
- func (w *ClientWorkspace) MCPRefreshPrompts(ctx context.Context, name string) {
- _ = w.client.MCPRefreshPrompts(ctx, w.workspaceID(), name)
- }
- func (w *ClientWorkspace) MCPRefreshResources(ctx context.Context, name string) {
- _ = w.client.MCPRefreshResources(ctx, w.workspaceID(), name)
- }
- func (w *ClientWorkspace) RefreshMCPTools(ctx context.Context, name string) {
- _ = w.client.RefreshMCPTools(ctx, w.workspaceID(), name)
- }
- func (w *ClientWorkspace) ReadMCPResource(ctx context.Context, name, uri string) ([]MCPResourceContents, error) {
- contents, err := w.client.ReadMCPResource(ctx, w.workspaceID(), name, uri)
- if err != nil {
- return nil, err
- }
- result := make([]MCPResourceContents, len(contents))
- for i, c := range contents {
- result[i] = MCPResourceContents{
- URI: c.URI,
- MIMEType: c.MIMEType,
- Text: c.Text,
- Blob: c.Blob,
- }
- }
- return result, nil
- }
- func (w *ClientWorkspace) GetMCPPrompt(clientID, promptID string, args map[string]string) (string, error) {
- return w.client.GetMCPPrompt(context.Background(), w.workspaceID(), clientID, promptID, args)
- }
- func (w *ClientWorkspace) EnableDockerMCP(ctx context.Context) error {
- return w.client.EnableDockerMCP(ctx, w.workspaceID())
- }
- func (w *ClientWorkspace) DisableDockerMCP() error {
- return w.client.DisableDockerMCP(context.Background(), w.workspaceID())
- }
- // -- Lifecycle --
- func (w *ClientWorkspace) Subscribe(program *tea.Program) {
- defer log.RecoverPanic("ClientWorkspace.Subscribe", func() {
- slog.Info("TUI subscription panic: attempting graceful shutdown")
- program.Quit()
- })
- evc, err := w.client.SubscribeEvents(context.Background(), w.workspaceID())
- if err != nil {
- slog.Error("Failed to subscribe to events", "error", err)
- return
- }
- for ev := range evc {
- translated := translateEvent(ev)
- if translated != nil {
- program.Send(translated)
- }
- }
- }
- func (w *ClientWorkspace) Shutdown() {
- _ = w.client.DeleteWorkspace(context.Background(), w.workspaceID())
- }
- // translateEvent converts proto-typed SSE events into the domain types
- // that the TUI's Update() method expects.
- func translateEvent(ev any) tea.Msg {
- switch e := ev.(type) {
- case pubsub.Event[proto.LSPEvent]:
- return pubsub.Event[LSPEvent]{
- Type: e.Type,
- Payload: LSPEvent{
- Type: LSPEventType(e.Payload.Type),
- Name: e.Payload.Name,
- State: e.Payload.State,
- Error: e.Payload.Error,
- DiagnosticCount: e.Payload.DiagnosticCount,
- },
- }
- case pubsub.Event[proto.MCPEvent]:
- return pubsub.Event[mcp.Event]{
- Type: e.Type,
- Payload: mcp.Event{
- Type: protoToMCPEventType(e.Payload.Type),
- Name: e.Payload.Name,
- State: mcp.State(e.Payload.State),
- Error: e.Payload.Error,
- Counts: mcp.Counts{
- Tools: e.Payload.ToolCount,
- Prompts: e.Payload.PromptCount,
- Resources: e.Payload.ResourceCount,
- },
- },
- }
- case pubsub.Event[proto.PermissionRequest]:
- return pubsub.Event[permission.PermissionRequest]{
- Type: e.Type,
- Payload: permission.PermissionRequest{
- ID: e.Payload.ID,
- SessionID: e.Payload.SessionID,
- ToolCallID: e.Payload.ToolCallID,
- ToolName: e.Payload.ToolName,
- Description: e.Payload.Description,
- Action: e.Payload.Action,
- Path: e.Payload.Path,
- Params: e.Payload.Params,
- },
- }
- case pubsub.Event[proto.PermissionNotification]:
- return pubsub.Event[permission.PermissionNotification]{
- Type: e.Type,
- Payload: permission.PermissionNotification{
- ToolCallID: e.Payload.ToolCallID,
- Granted: e.Payload.Granted,
- Denied: e.Payload.Denied,
- },
- }
- case pubsub.Event[proto.Message]:
- return pubsub.Event[message.Message]{
- Type: e.Type,
- Payload: protoToMessage(e.Payload),
- }
- case pubsub.Event[proto.Session]:
- return pubsub.Event[session.Session]{
- Type: e.Type,
- Payload: protoToSession(e.Payload),
- }
- case pubsub.Event[proto.File]:
- return pubsub.Event[history.File]{
- Type: e.Type,
- Payload: protoToFile(e.Payload),
- }
- case pubsub.Event[proto.AgentEvent]:
- return pubsub.Event[notify.Notification]{
- Type: e.Type,
- Payload: notify.Notification{
- SessionID: e.Payload.SessionID,
- SessionTitle: e.Payload.SessionTitle,
- Type: notify.Type(e.Payload.Type),
- },
- }
- default:
- slog.Warn("Unknown event type in translateEvent", "type", fmt.Sprintf("%T", ev))
- return nil
- }
- }
- func protoToMCPEventType(t proto.MCPEventType) mcp.EventType {
- switch t {
- case proto.MCPEventStateChanged:
- return mcp.EventStateChanged
- case proto.MCPEventToolsListChanged:
- return mcp.EventToolsListChanged
- case proto.MCPEventPromptsListChanged:
- return mcp.EventPromptsListChanged
- case proto.MCPEventResourcesListChanged:
- return mcp.EventResourcesListChanged
- default:
- return mcp.EventStateChanged
- }
- }
- func protoToSession(s proto.Session) session.Session {
- return session.Session{
- ID: s.ID,
- ParentSessionID: s.ParentSessionID,
- Title: s.Title,
- SummaryMessageID: s.SummaryMessageID,
- MessageCount: s.MessageCount,
- PromptTokens: s.PromptTokens,
- CompletionTokens: s.CompletionTokens,
- Cost: s.Cost,
- CreatedAt: s.CreatedAt,
- UpdatedAt: s.UpdatedAt,
- }
- }
- func protoToFile(f proto.File) history.File {
- return history.File{
- ID: f.ID,
- SessionID: f.SessionID,
- Path: f.Path,
- Content: f.Content,
- Version: f.Version,
- CreatedAt: f.CreatedAt,
- UpdatedAt: f.UpdatedAt,
- }
- }
- func protoToMessage(m proto.Message) message.Message {
- msg := message.Message{
- ID: m.ID,
- SessionID: m.SessionID,
- Role: message.MessageRole(m.Role),
- Model: m.Model,
- Provider: m.Provider,
- CreatedAt: m.CreatedAt,
- UpdatedAt: m.UpdatedAt,
- }
- for _, p := range m.Parts {
- switch v := p.(type) {
- case proto.TextContent:
- msg.Parts = append(msg.Parts, message.TextContent{Text: v.Text})
- case proto.ReasoningContent:
- msg.Parts = append(msg.Parts, message.ReasoningContent{
- Thinking: v.Thinking,
- Signature: v.Signature,
- StartedAt: v.StartedAt,
- FinishedAt: v.FinishedAt,
- })
- case proto.ToolCall:
- msg.Parts = append(msg.Parts, message.ToolCall{
- ID: v.ID,
- Name: v.Name,
- Input: v.Input,
- Finished: v.Finished,
- })
- case proto.ToolResult:
- msg.Parts = append(msg.Parts, message.ToolResult{
- ToolCallID: v.ToolCallID,
- Name: v.Name,
- Content: v.Content,
- IsError: v.IsError,
- })
- case proto.Finish:
- msg.Parts = append(msg.Parts, message.Finish{
- Reason: message.FinishReason(v.Reason),
- Time: v.Time,
- Message: v.Message,
- Details: v.Details,
- })
- case proto.ImageURLContent:
- msg.Parts = append(msg.Parts, message.ImageURLContent{URL: v.URL, Detail: v.Detail})
- case proto.BinaryContent:
- msg.Parts = append(msg.Parts, message.BinaryContent{Path: v.Path, MIMEType: v.MIMEType, Data: v.Data})
- }
- }
- return msg
- }
- func protoToMessages(msgs []proto.Message) []message.Message {
- out := make([]message.Message, len(msgs))
- for i, m := range msgs {
- out[i] = protoToMessage(m)
- }
- return out
- }
- func protoToFiles(files []proto.File) []history.File {
- out := make([]history.File, len(files))
- for i, f := range files {
- out[i] = protoToFile(f)
- }
- return out
- }
- func sessionToProto(s session.Session) proto.Session {
- return proto.Session{
- ID: s.ID,
- ParentSessionID: s.ParentSessionID,
- Title: s.Title,
- SummaryMessageID: s.SummaryMessageID,
- MessageCount: s.MessageCount,
- PromptTokens: s.PromptTokens,
- CompletionTokens: s.CompletionTokens,
- Cost: s.Cost,
- CreatedAt: s.CreatedAt,
- UpdatedAt: s.UpdatedAt,
- }
- }
|