client_workspace.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. package workspace
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "strings"
  7. "sync"
  8. "time"
  9. tea "charm.land/bubbletea/v2"
  10. "github.com/charmbracelet/crush/internal/agent/notify"
  11. "github.com/charmbracelet/crush/internal/agent/tools/mcp"
  12. "github.com/charmbracelet/crush/internal/client"
  13. "github.com/charmbracelet/crush/internal/config"
  14. "github.com/charmbracelet/crush/internal/history"
  15. "github.com/charmbracelet/crush/internal/log"
  16. "github.com/charmbracelet/crush/internal/lsp"
  17. "github.com/charmbracelet/crush/internal/message"
  18. "github.com/charmbracelet/crush/internal/oauth"
  19. "github.com/charmbracelet/crush/internal/permission"
  20. "github.com/charmbracelet/crush/internal/proto"
  21. "github.com/charmbracelet/crush/internal/pubsub"
  22. "github.com/charmbracelet/crush/internal/session"
  23. "github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
  24. )
  25. // ClientWorkspace implements the Workspace interface by delegating all
  26. // operations to a remote server via the client SDK. It caches the
  27. // proto.Workspace returned at creation time and refreshes it after
  28. // config-mutating operations.
  29. type ClientWorkspace struct {
  30. client *client.Client
  31. mu sync.RWMutex
  32. ws proto.Workspace
  33. }
  34. // NewClientWorkspace creates a new ClientWorkspace that proxies all
  35. // operations through the given client SDK. The ws parameter is the
  36. // proto.Workspace snapshot returned by the server at creation time.
  37. func NewClientWorkspace(c *client.Client, ws proto.Workspace) *ClientWorkspace {
  38. if ws.Config != nil {
  39. ws.Config.SetupAgents()
  40. }
  41. return &ClientWorkspace{
  42. client: c,
  43. ws: ws,
  44. }
  45. }
  46. // refreshWorkspace re-fetches the workspace from the server, updating
  47. // the cached snapshot. Called after config-mutating operations.
  48. func (w *ClientWorkspace) refreshWorkspace() {
  49. updated, err := w.client.GetWorkspace(context.Background(), w.ws.ID)
  50. if err != nil {
  51. slog.Error("Failed to refresh workspace", "error", err)
  52. return
  53. }
  54. if updated.Config != nil {
  55. updated.Config.SetupAgents()
  56. }
  57. w.mu.Lock()
  58. w.ws = *updated
  59. w.mu.Unlock()
  60. }
  61. // cached returns a snapshot of the cached workspace.
  62. func (w *ClientWorkspace) cached() proto.Workspace {
  63. w.mu.RLock()
  64. defer w.mu.RUnlock()
  65. return w.ws
  66. }
  67. // workspaceID returns the cached workspace ID.
  68. func (w *ClientWorkspace) workspaceID() string {
  69. return w.cached().ID
  70. }
  71. // -- Sessions --
  72. func (w *ClientWorkspace) CreateSession(ctx context.Context, title string) (session.Session, error) {
  73. sess, err := w.client.CreateSession(ctx, w.workspaceID(), title)
  74. if err != nil {
  75. return session.Session{}, err
  76. }
  77. return protoToSession(*sess), nil
  78. }
  79. func (w *ClientWorkspace) GetSession(ctx context.Context, sessionID string) (session.Session, error) {
  80. sess, err := w.client.GetSession(ctx, w.workspaceID(), sessionID)
  81. if err != nil {
  82. return session.Session{}, err
  83. }
  84. return protoToSession(*sess), nil
  85. }
  86. func (w *ClientWorkspace) ListSessions(ctx context.Context) ([]session.Session, error) {
  87. protoSessions, err := w.client.ListSessions(ctx, w.workspaceID())
  88. if err != nil {
  89. return nil, err
  90. }
  91. sessions := make([]session.Session, len(protoSessions))
  92. for i, s := range protoSessions {
  93. sessions[i] = protoToSession(s)
  94. }
  95. return sessions, nil
  96. }
  97. func (w *ClientWorkspace) SaveSession(ctx context.Context, sess session.Session) (session.Session, error) {
  98. saved, err := w.client.SaveSession(ctx, w.workspaceID(), sessionToProto(sess))
  99. if err != nil {
  100. return session.Session{}, err
  101. }
  102. return protoToSession(*saved), nil
  103. }
  104. func (w *ClientWorkspace) DeleteSession(ctx context.Context, sessionID string) error {
  105. return w.client.DeleteSession(ctx, w.workspaceID(), sessionID)
  106. }
  107. func (w *ClientWorkspace) CreateAgentToolSessionID(messageID, toolCallID string) string {
  108. return fmt.Sprintf("%s$$%s", messageID, toolCallID)
  109. }
  110. func (w *ClientWorkspace) ParseAgentToolSessionID(sessionID string) (string, string, bool) {
  111. parts := strings.Split(sessionID, "$$")
  112. if len(parts) != 2 {
  113. return "", "", false
  114. }
  115. return parts[0], parts[1], true
  116. }
  117. // -- Messages --
  118. func (w *ClientWorkspace) ListMessages(ctx context.Context, sessionID string) ([]message.Message, error) {
  119. msgs, err := w.client.ListMessages(ctx, w.workspaceID(), sessionID)
  120. if err != nil {
  121. return nil, err
  122. }
  123. return protoToMessages(msgs), nil
  124. }
  125. func (w *ClientWorkspace) ListUserMessages(ctx context.Context, sessionID string) ([]message.Message, error) {
  126. msgs, err := w.client.ListUserMessages(ctx, w.workspaceID(), sessionID)
  127. if err != nil {
  128. return nil, err
  129. }
  130. return protoToMessages(msgs), nil
  131. }
  132. func (w *ClientWorkspace) ListAllUserMessages(ctx context.Context) ([]message.Message, error) {
  133. msgs, err := w.client.ListAllUserMessages(ctx, w.workspaceID())
  134. if err != nil {
  135. return nil, err
  136. }
  137. return protoToMessages(msgs), nil
  138. }
  139. // -- Agent --
  140. func (w *ClientWorkspace) AgentRun(ctx context.Context, sessionID, prompt string, attachments ...message.Attachment) error {
  141. return w.client.SendMessage(ctx, w.workspaceID(), sessionID, prompt, attachments...)
  142. }
  143. func (w *ClientWorkspace) AgentCancel(sessionID string) {
  144. _ = w.client.CancelAgentSession(context.Background(), w.workspaceID(), sessionID)
  145. }
  146. func (w *ClientWorkspace) AgentIsBusy() bool {
  147. info, err := w.client.GetAgentInfo(context.Background(), w.workspaceID())
  148. if err != nil {
  149. return false
  150. }
  151. return info.IsBusy
  152. }
  153. func (w *ClientWorkspace) AgentIsSessionBusy(sessionID string) bool {
  154. info, err := w.client.GetAgentSessionInfo(context.Background(), w.workspaceID(), sessionID)
  155. if err != nil {
  156. return false
  157. }
  158. return info.IsBusy
  159. }
  160. func (w *ClientWorkspace) AgentModel() AgentModel {
  161. info, err := w.client.GetAgentInfo(context.Background(), w.workspaceID())
  162. if err != nil {
  163. return AgentModel{}
  164. }
  165. return AgentModel{
  166. CatwalkCfg: info.Model,
  167. ModelCfg: info.ModelCfg,
  168. }
  169. }
  170. func (w *ClientWorkspace) AgentIsReady() bool {
  171. info, err := w.client.GetAgentInfo(context.Background(), w.workspaceID())
  172. if err != nil {
  173. return false
  174. }
  175. return info.IsReady
  176. }
  177. func (w *ClientWorkspace) AgentQueuedPrompts(sessionID string) int {
  178. count, err := w.client.GetAgentSessionQueuedPrompts(context.Background(), w.workspaceID(), sessionID)
  179. if err != nil {
  180. return 0
  181. }
  182. return count
  183. }
  184. func (w *ClientWorkspace) AgentQueuedPromptsList(sessionID string) []string {
  185. prompts, err := w.client.GetAgentSessionQueuedPromptsList(context.Background(), w.workspaceID(), sessionID)
  186. if err != nil {
  187. return nil
  188. }
  189. return prompts
  190. }
  191. func (w *ClientWorkspace) AgentClearQueue(sessionID string) {
  192. _ = w.client.ClearAgentSessionQueuedPrompts(context.Background(), w.workspaceID(), sessionID)
  193. }
  194. func (w *ClientWorkspace) AgentSummarize(ctx context.Context, sessionID string) error {
  195. return w.client.AgentSummarizeSession(ctx, w.workspaceID(), sessionID)
  196. }
  197. func (w *ClientWorkspace) UpdateAgentModel(ctx context.Context) error {
  198. return w.client.UpdateAgent(ctx, w.workspaceID())
  199. }
  200. func (w *ClientWorkspace) InitCoderAgent(ctx context.Context) error {
  201. return w.client.InitiateAgentProcessing(ctx, w.workspaceID())
  202. }
  203. func (w *ClientWorkspace) GetDefaultSmallModel(providerID string) config.SelectedModel {
  204. model, err := w.client.GetDefaultSmallModel(context.Background(), w.workspaceID(), providerID)
  205. if err != nil {
  206. return config.SelectedModel{}
  207. }
  208. return *model
  209. }
  210. // -- Permissions --
  211. func (w *ClientWorkspace) PermissionGrant(perm permission.PermissionRequest) {
  212. _ = w.client.GrantPermission(context.Background(), w.workspaceID(), proto.PermissionGrant{
  213. Permission: proto.PermissionRequest{
  214. ID: perm.ID,
  215. SessionID: perm.SessionID,
  216. ToolCallID: perm.ToolCallID,
  217. ToolName: perm.ToolName,
  218. Description: perm.Description,
  219. Action: perm.Action,
  220. Path: perm.Path,
  221. Params: perm.Params,
  222. },
  223. Action: proto.PermissionAllowForSession,
  224. })
  225. }
  226. func (w *ClientWorkspace) PermissionGrantPersistent(perm permission.PermissionRequest) {
  227. _ = w.client.GrantPermission(context.Background(), w.workspaceID(), proto.PermissionGrant{
  228. Permission: proto.PermissionRequest{
  229. ID: perm.ID,
  230. SessionID: perm.SessionID,
  231. ToolCallID: perm.ToolCallID,
  232. ToolName: perm.ToolName,
  233. Description: perm.Description,
  234. Action: perm.Action,
  235. Path: perm.Path,
  236. Params: perm.Params,
  237. },
  238. Action: proto.PermissionAllow,
  239. })
  240. }
  241. func (w *ClientWorkspace) PermissionDeny(perm permission.PermissionRequest) {
  242. _ = w.client.GrantPermission(context.Background(), w.workspaceID(), proto.PermissionGrant{
  243. Permission: proto.PermissionRequest{
  244. ID: perm.ID,
  245. SessionID: perm.SessionID,
  246. ToolCallID: perm.ToolCallID,
  247. ToolName: perm.ToolName,
  248. Description: perm.Description,
  249. Action: perm.Action,
  250. Path: perm.Path,
  251. Params: perm.Params,
  252. },
  253. Action: proto.PermissionDeny,
  254. })
  255. }
  256. func (w *ClientWorkspace) PermissionSkipRequests() bool {
  257. skip, err := w.client.GetPermissionsSkipRequests(context.Background(), w.workspaceID())
  258. if err != nil {
  259. return false
  260. }
  261. return skip
  262. }
  263. func (w *ClientWorkspace) PermissionSetSkipRequests(skip bool) {
  264. _ = w.client.SetPermissionsSkipRequests(context.Background(), w.workspaceID(), skip)
  265. }
  266. // -- FileTracker --
  267. func (w *ClientWorkspace) FileTrackerRecordRead(ctx context.Context, sessionID, path string) {
  268. _ = w.client.FileTrackerRecordRead(ctx, w.workspaceID(), sessionID, path)
  269. }
  270. func (w *ClientWorkspace) FileTrackerLastReadTime(ctx context.Context, sessionID, path string) time.Time {
  271. t, err := w.client.FileTrackerLastReadTime(ctx, w.workspaceID(), sessionID, path)
  272. if err != nil {
  273. return time.Time{}
  274. }
  275. return t
  276. }
  277. func (w *ClientWorkspace) FileTrackerListReadFiles(ctx context.Context, sessionID string) ([]string, error) {
  278. return w.client.FileTrackerListReadFiles(ctx, w.workspaceID(), sessionID)
  279. }
  280. // -- History --
  281. func (w *ClientWorkspace) ListSessionHistory(ctx context.Context, sessionID string) ([]history.File, error) {
  282. files, err := w.client.ListSessionHistoryFiles(ctx, w.workspaceID(), sessionID)
  283. if err != nil {
  284. return nil, err
  285. }
  286. return protoToFiles(files), nil
  287. }
  288. // -- LSP --
  289. func (w *ClientWorkspace) LSPStart(ctx context.Context, path string) {
  290. _ = w.client.LSPStart(ctx, w.workspaceID(), path)
  291. }
  292. func (w *ClientWorkspace) LSPStopAll(ctx context.Context) {
  293. _ = w.client.LSPStopAll(ctx, w.workspaceID())
  294. }
  295. func (w *ClientWorkspace) LSPGetStates() map[string]LSPClientInfo {
  296. states, err := w.client.GetLSPs(context.Background(), w.workspaceID())
  297. if err != nil {
  298. return nil
  299. }
  300. result := make(map[string]LSPClientInfo, len(states))
  301. for k, v := range states {
  302. result[k] = LSPClientInfo{
  303. Name: v.Name,
  304. State: v.State,
  305. Error: v.Error,
  306. DiagnosticCount: v.DiagnosticCount,
  307. ConnectedAt: v.ConnectedAt,
  308. }
  309. }
  310. return result
  311. }
  312. func (w *ClientWorkspace) LSPGetDiagnosticCounts(name string) lsp.DiagnosticCounts {
  313. diags, err := w.client.GetLSPDiagnostics(context.Background(), w.workspaceID(), name)
  314. if err != nil {
  315. return lsp.DiagnosticCounts{}
  316. }
  317. var counts lsp.DiagnosticCounts
  318. for _, fileDiags := range diags {
  319. for _, d := range fileDiags {
  320. switch d.Severity {
  321. case protocol.SeverityError:
  322. counts.Error++
  323. case protocol.SeverityWarning:
  324. counts.Warning++
  325. case protocol.SeverityInformation:
  326. counts.Information++
  327. case protocol.SeverityHint:
  328. counts.Hint++
  329. }
  330. }
  331. }
  332. return counts
  333. }
  334. // -- Config (read-only) --
  335. func (w *ClientWorkspace) Config() *config.Config {
  336. return w.cached().Config
  337. }
  338. func (w *ClientWorkspace) WorkingDir() string {
  339. return w.cached().Path
  340. }
  341. func (w *ClientWorkspace) Resolver() config.VariableResolver {
  342. return config.IdentityResolver()
  343. }
  344. // -- Config mutations --
  345. func (w *ClientWorkspace) UpdatePreferredModel(scope config.Scope, modelType config.SelectedModelType, model config.SelectedModel) error {
  346. err := w.client.UpdatePreferredModel(context.Background(), w.workspaceID(), scope, modelType, model)
  347. if err == nil {
  348. w.refreshWorkspace()
  349. }
  350. return err
  351. }
  352. func (w *ClientWorkspace) SetCompactMode(scope config.Scope, enabled bool) error {
  353. err := w.client.SetCompactMode(context.Background(), w.workspaceID(), scope, enabled)
  354. if err == nil {
  355. w.refreshWorkspace()
  356. }
  357. return err
  358. }
  359. func (w *ClientWorkspace) SetProviderAPIKey(scope config.Scope, providerID string, apiKey any) error {
  360. err := w.client.SetProviderAPIKey(context.Background(), w.workspaceID(), scope, providerID, apiKey)
  361. if err == nil {
  362. w.refreshWorkspace()
  363. }
  364. return err
  365. }
  366. func (w *ClientWorkspace) SetConfigField(scope config.Scope, key string, value any) error {
  367. err := w.client.SetConfigField(context.Background(), w.workspaceID(), scope, key, value)
  368. if err == nil {
  369. w.refreshWorkspace()
  370. }
  371. return err
  372. }
  373. func (w *ClientWorkspace) RemoveConfigField(scope config.Scope, key string) error {
  374. err := w.client.RemoveConfigField(context.Background(), w.workspaceID(), scope, key)
  375. if err == nil {
  376. w.refreshWorkspace()
  377. }
  378. return err
  379. }
  380. func (w *ClientWorkspace) ImportCopilot() (*oauth.Token, bool) {
  381. token, ok, err := w.client.ImportCopilot(context.Background(), w.workspaceID())
  382. if err != nil {
  383. return nil, false
  384. }
  385. if ok {
  386. w.refreshWorkspace()
  387. }
  388. return token, ok
  389. }
  390. func (w *ClientWorkspace) RefreshOAuthToken(ctx context.Context, scope config.Scope, providerID string) error {
  391. err := w.client.RefreshOAuthToken(ctx, w.workspaceID(), scope, providerID)
  392. if err == nil {
  393. w.refreshWorkspace()
  394. }
  395. return err
  396. }
  397. // -- Project lifecycle --
  398. func (w *ClientWorkspace) ProjectNeedsInitialization() (bool, error) {
  399. return w.client.ProjectNeedsInitialization(context.Background(), w.workspaceID())
  400. }
  401. func (w *ClientWorkspace) MarkProjectInitialized() error {
  402. return w.client.MarkProjectInitialized(context.Background(), w.workspaceID())
  403. }
  404. func (w *ClientWorkspace) InitializePrompt() (string, error) {
  405. return w.client.GetInitializePrompt(context.Background(), w.workspaceID())
  406. }
  407. // -- MCP operations --
  408. func (w *ClientWorkspace) MCPGetStates() map[string]mcp.ClientInfo {
  409. states, err := w.client.MCPGetStates(context.Background(), w.workspaceID())
  410. if err != nil {
  411. return nil
  412. }
  413. result := make(map[string]mcp.ClientInfo, len(states))
  414. for k, v := range states {
  415. result[k] = mcp.ClientInfo{
  416. Name: v.Name,
  417. State: mcp.State(v.State),
  418. Error: v.Error,
  419. Counts: mcp.Counts{
  420. Tools: v.ToolCount,
  421. Prompts: v.PromptCount,
  422. Resources: v.ResourceCount,
  423. },
  424. ConnectedAt: v.ConnectedAt,
  425. }
  426. }
  427. return result
  428. }
  429. func (w *ClientWorkspace) MCPRefreshPrompts(ctx context.Context, name string) {
  430. _ = w.client.MCPRefreshPrompts(ctx, w.workspaceID(), name)
  431. }
  432. func (w *ClientWorkspace) MCPRefreshResources(ctx context.Context, name string) {
  433. _ = w.client.MCPRefreshResources(ctx, w.workspaceID(), name)
  434. }
  435. func (w *ClientWorkspace) RefreshMCPTools(ctx context.Context, name string) {
  436. _ = w.client.RefreshMCPTools(ctx, w.workspaceID(), name)
  437. }
  438. func (w *ClientWorkspace) ReadMCPResource(ctx context.Context, name, uri string) ([]MCPResourceContents, error) {
  439. contents, err := w.client.ReadMCPResource(ctx, w.workspaceID(), name, uri)
  440. if err != nil {
  441. return nil, err
  442. }
  443. result := make([]MCPResourceContents, len(contents))
  444. for i, c := range contents {
  445. result[i] = MCPResourceContents{
  446. URI: c.URI,
  447. MIMEType: c.MIMEType,
  448. Text: c.Text,
  449. Blob: c.Blob,
  450. }
  451. }
  452. return result, nil
  453. }
  454. func (w *ClientWorkspace) GetMCPPrompt(clientID, promptID string, args map[string]string) (string, error) {
  455. return w.client.GetMCPPrompt(context.Background(), w.workspaceID(), clientID, promptID, args)
  456. }
  457. func (w *ClientWorkspace) EnableDockerMCP(ctx context.Context) error {
  458. return w.client.EnableDockerMCP(ctx, w.workspaceID())
  459. }
  460. func (w *ClientWorkspace) DisableDockerMCP() error {
  461. return w.client.DisableDockerMCP(context.Background(), w.workspaceID())
  462. }
  463. // -- Lifecycle --
  464. func (w *ClientWorkspace) Subscribe(program *tea.Program) {
  465. defer log.RecoverPanic("ClientWorkspace.Subscribe", func() {
  466. slog.Info("TUI subscription panic: attempting graceful shutdown")
  467. program.Quit()
  468. })
  469. evc, err := w.client.SubscribeEvents(context.Background(), w.workspaceID())
  470. if err != nil {
  471. slog.Error("Failed to subscribe to events", "error", err)
  472. return
  473. }
  474. for ev := range evc {
  475. translated := translateEvent(ev)
  476. if translated != nil {
  477. program.Send(translated)
  478. }
  479. }
  480. }
  481. func (w *ClientWorkspace) Shutdown() {
  482. _ = w.client.DeleteWorkspace(context.Background(), w.workspaceID())
  483. }
  484. // translateEvent converts proto-typed SSE events into the domain types
  485. // that the TUI's Update() method expects.
  486. func translateEvent(ev any) tea.Msg {
  487. switch e := ev.(type) {
  488. case pubsub.Event[proto.LSPEvent]:
  489. return pubsub.Event[LSPEvent]{
  490. Type: e.Type,
  491. Payload: LSPEvent{
  492. Type: LSPEventType(e.Payload.Type),
  493. Name: e.Payload.Name,
  494. State: e.Payload.State,
  495. Error: e.Payload.Error,
  496. DiagnosticCount: e.Payload.DiagnosticCount,
  497. },
  498. }
  499. case pubsub.Event[proto.MCPEvent]:
  500. return pubsub.Event[mcp.Event]{
  501. Type: e.Type,
  502. Payload: mcp.Event{
  503. Type: protoToMCPEventType(e.Payload.Type),
  504. Name: e.Payload.Name,
  505. State: mcp.State(e.Payload.State),
  506. Error: e.Payload.Error,
  507. Counts: mcp.Counts{
  508. Tools: e.Payload.ToolCount,
  509. Prompts: e.Payload.PromptCount,
  510. Resources: e.Payload.ResourceCount,
  511. },
  512. },
  513. }
  514. case pubsub.Event[proto.PermissionRequest]:
  515. return pubsub.Event[permission.PermissionRequest]{
  516. Type: e.Type,
  517. Payload: permission.PermissionRequest{
  518. ID: e.Payload.ID,
  519. SessionID: e.Payload.SessionID,
  520. ToolCallID: e.Payload.ToolCallID,
  521. ToolName: e.Payload.ToolName,
  522. Description: e.Payload.Description,
  523. Action: e.Payload.Action,
  524. Path: e.Payload.Path,
  525. Params: e.Payload.Params,
  526. },
  527. }
  528. case pubsub.Event[proto.PermissionNotification]:
  529. return pubsub.Event[permission.PermissionNotification]{
  530. Type: e.Type,
  531. Payload: permission.PermissionNotification{
  532. ToolCallID: e.Payload.ToolCallID,
  533. Granted: e.Payload.Granted,
  534. Denied: e.Payload.Denied,
  535. },
  536. }
  537. case pubsub.Event[proto.Message]:
  538. return pubsub.Event[message.Message]{
  539. Type: e.Type,
  540. Payload: protoToMessage(e.Payload),
  541. }
  542. case pubsub.Event[proto.Session]:
  543. return pubsub.Event[session.Session]{
  544. Type: e.Type,
  545. Payload: protoToSession(e.Payload),
  546. }
  547. case pubsub.Event[proto.File]:
  548. return pubsub.Event[history.File]{
  549. Type: e.Type,
  550. Payload: protoToFile(e.Payload),
  551. }
  552. case pubsub.Event[proto.AgentEvent]:
  553. return pubsub.Event[notify.Notification]{
  554. Type: e.Type,
  555. Payload: notify.Notification{
  556. SessionID: e.Payload.SessionID,
  557. SessionTitle: e.Payload.SessionTitle,
  558. Type: notify.Type(e.Payload.Type),
  559. },
  560. }
  561. default:
  562. slog.Warn("Unknown event type in translateEvent", "type", fmt.Sprintf("%T", ev))
  563. return nil
  564. }
  565. }
  566. func protoToMCPEventType(t proto.MCPEventType) mcp.EventType {
  567. switch t {
  568. case proto.MCPEventStateChanged:
  569. return mcp.EventStateChanged
  570. case proto.MCPEventToolsListChanged:
  571. return mcp.EventToolsListChanged
  572. case proto.MCPEventPromptsListChanged:
  573. return mcp.EventPromptsListChanged
  574. case proto.MCPEventResourcesListChanged:
  575. return mcp.EventResourcesListChanged
  576. default:
  577. return mcp.EventStateChanged
  578. }
  579. }
  580. func protoToSession(s proto.Session) session.Session {
  581. return session.Session{
  582. ID: s.ID,
  583. ParentSessionID: s.ParentSessionID,
  584. Title: s.Title,
  585. SummaryMessageID: s.SummaryMessageID,
  586. MessageCount: s.MessageCount,
  587. PromptTokens: s.PromptTokens,
  588. CompletionTokens: s.CompletionTokens,
  589. Cost: s.Cost,
  590. CreatedAt: s.CreatedAt,
  591. UpdatedAt: s.UpdatedAt,
  592. }
  593. }
  594. func protoToFile(f proto.File) history.File {
  595. return history.File{
  596. ID: f.ID,
  597. SessionID: f.SessionID,
  598. Path: f.Path,
  599. Content: f.Content,
  600. Version: f.Version,
  601. CreatedAt: f.CreatedAt,
  602. UpdatedAt: f.UpdatedAt,
  603. }
  604. }
  605. func protoToMessage(m proto.Message) message.Message {
  606. msg := message.Message{
  607. ID: m.ID,
  608. SessionID: m.SessionID,
  609. Role: message.MessageRole(m.Role),
  610. Model: m.Model,
  611. Provider: m.Provider,
  612. CreatedAt: m.CreatedAt,
  613. UpdatedAt: m.UpdatedAt,
  614. }
  615. for _, p := range m.Parts {
  616. switch v := p.(type) {
  617. case proto.TextContent:
  618. msg.Parts = append(msg.Parts, message.TextContent{Text: v.Text})
  619. case proto.ReasoningContent:
  620. msg.Parts = append(msg.Parts, message.ReasoningContent{
  621. Thinking: v.Thinking,
  622. Signature: v.Signature,
  623. StartedAt: v.StartedAt,
  624. FinishedAt: v.FinishedAt,
  625. })
  626. case proto.ToolCall:
  627. msg.Parts = append(msg.Parts, message.ToolCall{
  628. ID: v.ID,
  629. Name: v.Name,
  630. Input: v.Input,
  631. Finished: v.Finished,
  632. })
  633. case proto.ToolResult:
  634. msg.Parts = append(msg.Parts, message.ToolResult{
  635. ToolCallID: v.ToolCallID,
  636. Name: v.Name,
  637. Content: v.Content,
  638. IsError: v.IsError,
  639. })
  640. case proto.Finish:
  641. msg.Parts = append(msg.Parts, message.Finish{
  642. Reason: message.FinishReason(v.Reason),
  643. Time: v.Time,
  644. Message: v.Message,
  645. Details: v.Details,
  646. })
  647. case proto.ImageURLContent:
  648. msg.Parts = append(msg.Parts, message.ImageURLContent{URL: v.URL, Detail: v.Detail})
  649. case proto.BinaryContent:
  650. msg.Parts = append(msg.Parts, message.BinaryContent{Path: v.Path, MIMEType: v.MIMEType, Data: v.Data})
  651. }
  652. }
  653. return msg
  654. }
  655. func protoToMessages(msgs []proto.Message) []message.Message {
  656. out := make([]message.Message, len(msgs))
  657. for i, m := range msgs {
  658. out[i] = protoToMessage(m)
  659. }
  660. return out
  661. }
  662. func protoToFiles(files []proto.File) []history.File {
  663. out := make([]history.File, len(files))
  664. for i, f := range files {
  665. out[i] = protoToFile(f)
  666. }
  667. return out
  668. }
  669. func sessionToProto(s session.Session) proto.Session {
  670. return proto.Session{
  671. ID: s.ID,
  672. ParentSessionID: s.ParentSessionID,
  673. Title: s.Title,
  674. SummaryMessageID: s.SummaryMessageID,
  675. MessageCount: s.MessageCount,
  676. PromptTokens: s.PromptTokens,
  677. CompletionTokens: s.CompletionTokens,
  678. Cost: s.Cost,
  679. CreatedAt: s.CreatedAt,
  680. UpdatedAt: s.UpdatedAt,
  681. }
  682. }