agent.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. package agent
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "sync"
  8. "time"
  9. "log/slog"
  10. "github.com/opencode-ai/opencode/internal/config"
  11. "github.com/opencode-ai/opencode/internal/llm/models"
  12. "github.com/opencode-ai/opencode/internal/llm/prompt"
  13. "github.com/opencode-ai/opencode/internal/llm/provider"
  14. "github.com/opencode-ai/opencode/internal/llm/tools"
  15. "github.com/opencode-ai/opencode/internal/logging"
  16. "github.com/opencode-ai/opencode/internal/message"
  17. "github.com/opencode-ai/opencode/internal/permission"
  18. "github.com/opencode-ai/opencode/internal/session"
  19. "github.com/opencode-ai/opencode/internal/status"
  20. )
  21. // Common errors
  22. var (
  23. ErrRequestCancelled = errors.New("request cancelled by user")
  24. ErrSessionBusy = errors.New("session is currently processing another request")
  25. )
  26. type AgentEvent struct {
  27. message message.Message
  28. err error
  29. }
  30. func (e *AgentEvent) Err() error {
  31. return e.err
  32. }
  33. func (e *AgentEvent) Response() message.Message {
  34. return e.message
  35. }
  36. type Service interface {
  37. Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error)
  38. Cancel(sessionID string)
  39. IsSessionBusy(sessionID string) bool
  40. IsBusy() bool
  41. Update(agentName config.AgentName, modelID models.ModelID) (models.Model, error)
  42. CompactSession(ctx context.Context, sessionID string) error
  43. PauseSession(sessionID string) error
  44. ResumeSession(sessionID string) error
  45. }
  46. type agent struct {
  47. sessions session.Service
  48. messages message.Service
  49. tools []tools.BaseTool
  50. provider provider.Provider
  51. titleProvider provider.Provider
  52. activeRequests sync.Map
  53. pauseLock sync.RWMutex // Lock for pausing message processing
  54. }
  55. func NewAgent(
  56. agentName config.AgentName,
  57. sessions session.Service,
  58. messages message.Service,
  59. agentTools []tools.BaseTool,
  60. ) (Service, error) {
  61. agentProvider, err := createAgentProvider(agentName)
  62. if err != nil {
  63. return nil, err
  64. }
  65. var titleProvider provider.Provider
  66. // Only generate titles for the coder agent
  67. if agentName == config.AgentCoder {
  68. titleProvider, err = createAgentProvider(config.AgentTitle)
  69. if err != nil {
  70. return nil, err
  71. }
  72. }
  73. agent := &agent{
  74. provider: agentProvider,
  75. messages: messages,
  76. sessions: sessions,
  77. tools: agentTools,
  78. titleProvider: titleProvider,
  79. activeRequests: sync.Map{},
  80. }
  81. return agent, nil
  82. }
  83. func (a *agent) Cancel(sessionID string) {
  84. if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID); exists {
  85. if cancel, ok := cancelFunc.(context.CancelFunc); ok {
  86. status.Info(fmt.Sprintf("Request cancellation initiated for session: %s", sessionID))
  87. cancel()
  88. }
  89. }
  90. }
  91. func (a *agent) IsBusy() bool {
  92. busy := false
  93. a.activeRequests.Range(func(key, value interface{}) bool {
  94. if cancelFunc, ok := value.(context.CancelFunc); ok {
  95. if cancelFunc != nil {
  96. busy = true
  97. return false // Stop iterating
  98. }
  99. }
  100. return true // Continue iterating
  101. })
  102. return busy
  103. }
  104. func (a *agent) IsSessionBusy(sessionID string) bool {
  105. _, busy := a.activeRequests.Load(sessionID)
  106. return busy
  107. }
  108. func (a *agent) generateTitle(ctx context.Context, sessionID string, content string) error {
  109. if content == "" {
  110. return nil
  111. }
  112. if a.titleProvider == nil {
  113. return nil
  114. }
  115. session, err := a.sessions.Get(ctx, sessionID)
  116. if err != nil {
  117. return err
  118. }
  119. parts := []message.ContentPart{message.TextContent{Text: content}}
  120. response, err := a.titleProvider.SendMessages(
  121. ctx,
  122. []message.Message{
  123. {
  124. Role: message.User,
  125. Parts: parts,
  126. },
  127. },
  128. make([]tools.BaseTool, 0),
  129. )
  130. if err != nil {
  131. return err
  132. }
  133. title := strings.TrimSpace(strings.ReplaceAll(response.Content, "\n", " "))
  134. if title == "" {
  135. return nil
  136. }
  137. session.Title = title
  138. _, err = a.sessions.Update(ctx, session)
  139. return err
  140. }
  141. func (a *agent) err(err error) AgentEvent {
  142. return AgentEvent{
  143. err: err,
  144. }
  145. }
  146. func (a *agent) Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error) {
  147. if !a.provider.Model().SupportsAttachments && attachments != nil {
  148. attachments = nil
  149. }
  150. events := make(chan AgentEvent)
  151. if a.IsSessionBusy(sessionID) {
  152. return nil, ErrSessionBusy
  153. }
  154. genCtx, cancel := context.WithCancel(ctx)
  155. a.activeRequests.Store(sessionID, cancel)
  156. go func() {
  157. slog.Debug("Request started", "sessionID", sessionID)
  158. defer logging.RecoverPanic("agent.Run", func() {
  159. events <- a.err(fmt.Errorf("panic while running the agent"))
  160. })
  161. var attachmentParts []message.ContentPart
  162. for _, attachment := range attachments {
  163. attachmentParts = append(attachmentParts, message.BinaryContent{Path: attachment.FilePath, MIMEType: attachment.MimeType, Data: attachment.Content})
  164. }
  165. result := a.processGeneration(genCtx, sessionID, content, attachmentParts)
  166. if result.Err() != nil && !errors.Is(result.Err(), ErrRequestCancelled) && !errors.Is(result.Err(), context.Canceled) {
  167. status.Error(result.Err().Error())
  168. }
  169. slog.Debug("Request completed", "sessionID", sessionID)
  170. a.activeRequests.Delete(sessionID)
  171. cancel()
  172. events <- result
  173. close(events)
  174. }()
  175. return events, nil
  176. }
  177. func (a *agent) processGeneration(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) AgentEvent {
  178. // Get the current session to check for summary
  179. currentSession, err := a.sessions.Get(ctx, sessionID)
  180. if err != nil {
  181. return a.err(fmt.Errorf("failed to get session: %w", err))
  182. }
  183. // Fetch messages based on whether a summary exists
  184. var sessionMessages []message.Message
  185. if currentSession.Summary != "" && currentSession.SummarizedAt > 0 {
  186. // If summary exists, only fetch messages after the summarization timestamp
  187. sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt)
  188. if err != nil {
  189. return a.err(fmt.Errorf("failed to list messages after summary: %w", err))
  190. }
  191. } else {
  192. // If no summary, fetch all messages
  193. sessionMessages, err = a.messages.List(ctx, sessionID)
  194. if err != nil {
  195. return a.err(fmt.Errorf("failed to list messages: %w", err))
  196. }
  197. }
  198. // If this is a new session, start title generation asynchronously
  199. if len(sessionMessages) == 0 && currentSession.Summary == "" {
  200. go func() {
  201. defer logging.RecoverPanic("agent.Run", func() {
  202. status.Error("panic while generating title")
  203. })
  204. titleErr := a.generateTitle(context.Background(), sessionID, content)
  205. if titleErr != nil {
  206. status.Error(fmt.Sprintf("failed to generate title: %v", titleErr))
  207. }
  208. }()
  209. }
  210. userMsg, err := a.createUserMessage(ctx, sessionID, content, attachmentParts)
  211. if err != nil {
  212. return a.err(fmt.Errorf("failed to create user message: %w", err))
  213. }
  214. // Prepare the message history for the LLM
  215. var messages []message.Message
  216. if currentSession.Summary != "" && currentSession.SummarizedAt > 0 {
  217. // If summary exists, create a temporary message for the summary
  218. summaryMessage := message.Message{
  219. Role: message.Assistant,
  220. Parts: []message.ContentPart{
  221. message.TextContent{Text: currentSession.Summary},
  222. },
  223. }
  224. // Start with the summary, then add messages after the summary timestamp
  225. messages = append([]message.Message{summaryMessage}, sessionMessages...)
  226. } else {
  227. // If no summary, just use all messages
  228. messages = sessionMessages
  229. }
  230. // Append the new user message to the conversation history
  231. messages = append(messages, userMsg)
  232. for {
  233. // Check for cancellation before each iteration
  234. select {
  235. case <-ctx.Done():
  236. return a.err(ctx.Err())
  237. default:
  238. // Continue processing
  239. }
  240. agentMessage, toolResults, err := a.streamAndHandleEvents(ctx, sessionID, messages)
  241. if err != nil {
  242. if errors.Is(err, context.Canceled) {
  243. agentMessage.AddFinish(message.FinishReasonCanceled)
  244. a.messages.Update(context.Background(), agentMessage)
  245. return a.err(ErrRequestCancelled)
  246. }
  247. return a.err(fmt.Errorf("failed to process events: %w", err))
  248. }
  249. slog.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
  250. if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
  251. // We are not done, we need to respond with the tool response
  252. messages = append(messages, agentMessage, *toolResults)
  253. continue
  254. }
  255. return AgentEvent{
  256. message: agentMessage,
  257. }
  258. }
  259. }
  260. func (a *agent) createUserMessage(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) (message.Message, error) {
  261. parts := []message.ContentPart{message.TextContent{Text: content}}
  262. parts = append(parts, attachmentParts...)
  263. return a.messages.Create(ctx, sessionID, message.CreateMessageParams{
  264. Role: message.User,
  265. Parts: parts,
  266. })
  267. }
  268. func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msgHistory []message.Message) (message.Message, *message.Message, error) {
  269. eventChan := a.provider.StreamResponse(ctx, msgHistory, a.tools)
  270. assistantMsg, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
  271. Role: message.Assistant,
  272. Parts: []message.ContentPart{},
  273. Model: a.provider.Model().ID,
  274. })
  275. if err != nil {
  276. return assistantMsg, nil, fmt.Errorf("failed to create assistant message: %w", err)
  277. }
  278. // Add the session and message ID into the context if needed by tools.
  279. ctx = context.WithValue(ctx, tools.MessageIDContextKey, assistantMsg.ID)
  280. ctx = context.WithValue(ctx, tools.SessionIDContextKey, sessionID)
  281. // Process each event in the stream.
  282. for event := range eventChan {
  283. if processErr := a.processEvent(ctx, sessionID, &assistantMsg, event); processErr != nil {
  284. a.finishMessage(ctx, &assistantMsg, message.FinishReasonCanceled)
  285. return assistantMsg, nil, processErr
  286. }
  287. if ctx.Err() != nil {
  288. a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
  289. return assistantMsg, nil, ctx.Err()
  290. }
  291. }
  292. toolResults := make([]message.ToolResult, len(assistantMsg.ToolCalls()))
  293. toolCalls := assistantMsg.ToolCalls()
  294. for i, toolCall := range toolCalls {
  295. select {
  296. case <-ctx.Done():
  297. a.finishMessage(context.Background(), &assistantMsg, message.FinishReasonCanceled)
  298. // Make all future tool calls cancelled
  299. for j := i; j < len(toolCalls); j++ {
  300. toolResults[j] = message.ToolResult{
  301. ToolCallID: toolCalls[j].ID,
  302. Content: "Tool execution canceled by user",
  303. IsError: true,
  304. }
  305. }
  306. goto out
  307. default:
  308. // Continue processing
  309. var tool tools.BaseTool
  310. for _, availableTools := range a.tools {
  311. if availableTools.Info().Name == toolCall.Name {
  312. tool = availableTools
  313. }
  314. }
  315. // Tool not found
  316. if tool == nil {
  317. toolResults[i] = message.ToolResult{
  318. ToolCallID: toolCall.ID,
  319. Content: fmt.Sprintf("Tool not found: %s", toolCall.Name),
  320. IsError: true,
  321. }
  322. continue
  323. }
  324. toolResult, toolErr := tool.Run(ctx, tools.ToolCall{
  325. ID: toolCall.ID,
  326. Name: toolCall.Name,
  327. Input: toolCall.Input,
  328. })
  329. if toolErr != nil {
  330. if errors.Is(toolErr, permission.ErrorPermissionDenied) {
  331. toolResults[i] = message.ToolResult{
  332. ToolCallID: toolCall.ID,
  333. Content: "Permission denied",
  334. IsError: true,
  335. }
  336. for j := i + 1; j < len(toolCalls); j++ {
  337. toolResults[j] = message.ToolResult{
  338. ToolCallID: toolCalls[j].ID,
  339. Content: "Tool execution canceled by user",
  340. IsError: true,
  341. }
  342. }
  343. a.finishMessage(ctx, &assistantMsg, message.FinishReasonPermissionDenied)
  344. break
  345. }
  346. }
  347. toolResults[i] = message.ToolResult{
  348. ToolCallID: toolCall.ID,
  349. Content: toolResult.Content,
  350. Metadata: toolResult.Metadata,
  351. IsError: toolResult.IsError,
  352. }
  353. }
  354. }
  355. out:
  356. if len(toolResults) == 0 {
  357. return assistantMsg, nil, nil
  358. }
  359. parts := make([]message.ContentPart, 0)
  360. for _, tr := range toolResults {
  361. parts = append(parts, tr)
  362. }
  363. msg, err := a.messages.Create(context.Background(), assistantMsg.SessionID, message.CreateMessageParams{
  364. Role: message.Tool,
  365. Parts: parts,
  366. })
  367. if err != nil {
  368. return assistantMsg, nil, fmt.Errorf("failed to create cancelled tool message: %w", err)
  369. }
  370. return assistantMsg, &msg, err
  371. }
  372. func (a *agent) finishMessage(ctx context.Context, msg *message.Message, finishReson message.FinishReason) {
  373. msg.AddFinish(finishReson)
  374. _, _ = a.messages.Update(ctx, *msg)
  375. }
  376. func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg *message.Message, event provider.ProviderEvent) error {
  377. select {
  378. case <-ctx.Done():
  379. return ctx.Err()
  380. default:
  381. // Continue processing.
  382. }
  383. // Check if session is paused - use RLock to allow concurrent reads but block during pause
  384. a.pauseLock.RLock()
  385. defer a.pauseLock.RUnlock()
  386. switch event.Type {
  387. case provider.EventThinkingDelta:
  388. assistantMsg.AppendReasoningContent(event.Content)
  389. _, err := a.messages.Update(ctx, *assistantMsg)
  390. return err
  391. case provider.EventContentDelta:
  392. assistantMsg.AppendContent(event.Content)
  393. _, err := a.messages.Update(ctx, *assistantMsg)
  394. return err
  395. case provider.EventToolUseStart:
  396. assistantMsg.AddToolCall(*event.ToolCall)
  397. _, err := a.messages.Update(ctx, *assistantMsg)
  398. return err
  399. // TODO: see how to handle this
  400. // case provider.EventToolUseDelta:
  401. // tm := time.Unix(assistantMsg.UpdatedAt, 0)
  402. // assistantMsg.AppendToolCallInput(event.ToolCall.ID, event.ToolCall.Input)
  403. // if time.Since(tm) > 1000*time.Millisecond {
  404. // err := a.messages.Update(ctx, *assistantMsg)
  405. // assistantMsg.UpdatedAt = time.Now().Unix()
  406. // return err
  407. // }
  408. case provider.EventToolUseStop:
  409. assistantMsg.FinishToolCall(event.ToolCall.ID)
  410. _, err := a.messages.Update(ctx, *assistantMsg)
  411. return err
  412. case provider.EventError:
  413. if errors.Is(event.Error, context.Canceled) {
  414. status.Info(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
  415. return context.Canceled
  416. }
  417. status.Error(event.Error.Error())
  418. return event.Error
  419. case provider.EventComplete:
  420. assistantMsg.SetToolCalls(event.Response.ToolCalls)
  421. assistantMsg.AddFinish(event.Response.FinishReason)
  422. if _, err := a.messages.Update(ctx, *assistantMsg); err != nil {
  423. return fmt.Errorf("failed to update message: %w", err)
  424. }
  425. return a.TrackUsage(ctx, sessionID, a.provider.Model(), event.Response.Usage)
  426. }
  427. return nil
  428. }
  429. func (a *agent) GetUsage(ctx context.Context, sessionID string) (*int64, error) {
  430. session, err := a.sessions.Get(ctx, sessionID)
  431. if err != nil {
  432. return nil, fmt.Errorf("failed to get session: %w", err)
  433. }
  434. usage := session.PromptTokens + session.CompletionTokens
  435. return &usage, nil
  436. }
  437. func (a *agent) TrackUsage(ctx context.Context, sessionID string, model models.Model, usage provider.TokenUsage) error {
  438. sess, err := a.sessions.Get(ctx, sessionID)
  439. if err != nil {
  440. return fmt.Errorf("failed to get session: %w", err)
  441. }
  442. cost := model.CostPer1MInCached/1e6*float64(usage.CacheCreationTokens) +
  443. model.CostPer1MOutCached/1e6*float64(usage.CacheReadTokens) +
  444. model.CostPer1MIn/1e6*float64(usage.InputTokens) +
  445. model.CostPer1MOut/1e6*float64(usage.OutputTokens)
  446. sess.Cost += cost
  447. sess.CompletionTokens = usage.OutputTokens + usage.CacheReadTokens
  448. sess.PromptTokens = usage.InputTokens + usage.CacheCreationTokens
  449. _, err = a.sessions.Update(ctx, sess)
  450. if err != nil {
  451. return fmt.Errorf("failed to save session: %w", err)
  452. }
  453. return nil
  454. }
  455. func (a *agent) Update(agentName config.AgentName, modelID models.ModelID) (models.Model, error) {
  456. if a.IsBusy() {
  457. return models.Model{}, fmt.Errorf("cannot change model while processing requests")
  458. }
  459. if err := config.UpdateAgentModel(agentName, modelID); err != nil {
  460. return models.Model{}, fmt.Errorf("failed to update config: %w", err)
  461. }
  462. provider, err := createAgentProvider(agentName)
  463. if err != nil {
  464. return models.Model{}, fmt.Errorf("failed to create provider for model %s: %w", modelID, err)
  465. }
  466. a.provider = provider
  467. return a.provider.Model(), nil
  468. }
  469. // PauseSession pauses message processing for a specific session
  470. // This should be called before performing operations that require exclusive access
  471. func (a *agent) PauseSession(sessionID string) error {
  472. if !a.IsSessionBusy(sessionID) {
  473. return nil // Session is not active, no need to pause
  474. }
  475. status.Info(fmt.Sprintf("Pausing session: %s", sessionID))
  476. a.pauseLock.Lock() // Acquire write lock to block new operations
  477. return nil
  478. }
  479. // ResumeSession resumes message processing for a session
  480. // This should be called after completing operations that required exclusive access
  481. func (a *agent) ResumeSession(sessionID string) error {
  482. status.Info(fmt.Sprintf("Resuming session: %s", sessionID))
  483. a.pauseLock.Unlock() // Release write lock to allow operations to continue
  484. return nil
  485. }
  486. func (a *agent) CompactSession(ctx context.Context, sessionID string) error {
  487. // Check if the session is busy
  488. if a.IsSessionBusy(sessionID) {
  489. // Pause the session before compaction
  490. if err := a.PauseSession(sessionID); err != nil {
  491. return fmt.Errorf("failed to pause session: %w", err)
  492. }
  493. // Make sure to resume the session when we're done
  494. defer a.ResumeSession(sessionID)
  495. status.Info(fmt.Sprintf("Session %s paused for compaction", sessionID))
  496. }
  497. // Create a cancellable context
  498. ctx, cancel := context.WithCancel(ctx)
  499. defer cancel()
  500. // Mark the session as busy during compaction
  501. compactionCancelFunc := func() {}
  502. a.activeRequests.Store(sessionID+"-compact", compactionCancelFunc)
  503. defer a.activeRequests.Delete(sessionID + "-compact")
  504. // Fetch the session
  505. session, err := a.sessions.Get(ctx, sessionID)
  506. if err != nil {
  507. return fmt.Errorf("failed to get session: %w", err)
  508. }
  509. // Fetch all messages for the session
  510. sessionMessages, err := a.messages.List(ctx, sessionID)
  511. if err != nil {
  512. return fmt.Errorf("failed to list messages: %w", err)
  513. }
  514. var existingSummary string
  515. if session.Summary != "" && session.SummarizedAt > 0 {
  516. // Filter messages that were created after the last summarization
  517. var newMessages []message.Message
  518. for _, msg := range sessionMessages {
  519. if msg.CreatedAt > session.SummarizedAt {
  520. newMessages = append(newMessages, msg)
  521. }
  522. }
  523. sessionMessages = newMessages
  524. existingSummary = session.Summary
  525. }
  526. // If there are no messages to summarize and no existing summary, return early
  527. if len(sessionMessages) == 0 && existingSummary == "" {
  528. return nil
  529. }
  530. messages := []message.Message{
  531. message.Message{
  532. Role: message.System,
  533. Parts: []message.ContentPart{
  534. message.TextContent{
  535. Text: `You are a helpful AI assistant tasked with summarizing conversations.
  536. When asked to summarize, provide a detailed but concise summary of the conversation.
  537. Focus on information that would be helpful for continuing the conversation, including:
  538. - What was done
  539. - What is currently being worked on
  540. - Which files are being modified
  541. - What needs to be done next
  542. Your summary should be comprehensive enough to provide context but concise enough to be quickly understood.`,
  543. },
  544. },
  545. },
  546. }
  547. // If there's an existing summary, include it
  548. if existingSummary != "" {
  549. messages = append(messages, message.Message{
  550. Role: message.Assistant,
  551. Parts: []message.ContentPart{
  552. message.TextContent{
  553. Text: existingSummary,
  554. },
  555. },
  556. })
  557. }
  558. // Add all messages since the last summarized message
  559. messages = append(messages, sessionMessages...)
  560. // Add a final user message requesting the summary
  561. messages = append(messages, message.Message{
  562. Role: message.User,
  563. Parts: []message.ContentPart{
  564. message.TextContent{
  565. Text: "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.",
  566. },
  567. },
  568. })
  569. // Call provider to get the summary
  570. response, err := a.provider.SendMessages(ctx, messages, a.tools)
  571. if err != nil {
  572. return fmt.Errorf("failed to get summary from the assistant: %w", err)
  573. }
  574. // Extract the summary text
  575. summaryText := strings.TrimSpace(response.Content)
  576. if summaryText == "" {
  577. return fmt.Errorf("received empty summary from the assistant")
  578. }
  579. // Update the session with the new summary
  580. currentTime := time.Now().UnixMilli()
  581. session.Summary = summaryText
  582. session.SummarizedAt = currentTime
  583. // Save the updated session
  584. _, err = a.sessions.Update(ctx, session)
  585. if err != nil {
  586. return fmt.Errorf("failed to save session with summary: %w", err)
  587. }
  588. // Track token usage
  589. err = a.TrackUsage(ctx, sessionID, a.provider.Model(), response.Usage)
  590. if err != nil {
  591. return fmt.Errorf("failed to track usage: %w", err)
  592. }
  593. return nil
  594. }
  595. func createAgentProvider(agentName config.AgentName) (provider.Provider, error) {
  596. cfg := config.Get()
  597. agentConfig, ok := cfg.Agents[agentName]
  598. if !ok {
  599. return nil, fmt.Errorf("agent %s not found", agentName)
  600. }
  601. model, ok := models.SupportedModels[agentConfig.Model]
  602. if !ok {
  603. return nil, fmt.Errorf("model %s not supported", agentConfig.Model)
  604. }
  605. providerCfg, ok := cfg.Providers[model.Provider]
  606. if !ok {
  607. return nil, fmt.Errorf("provider %s not supported", model.Provider)
  608. }
  609. if providerCfg.Disabled {
  610. return nil, fmt.Errorf("provider %s is not enabled", model.Provider)
  611. }
  612. maxTokens := model.DefaultMaxTokens
  613. if agentConfig.MaxTokens > 0 {
  614. maxTokens = agentConfig.MaxTokens
  615. }
  616. opts := []provider.ProviderClientOption{
  617. provider.WithAPIKey(providerCfg.APIKey),
  618. provider.WithModel(model),
  619. provider.WithSystemMessage(prompt.GetAgentPrompt(agentName, model.Provider)),
  620. provider.WithMaxTokens(maxTokens),
  621. }
  622. if model.Provider == models.ProviderOpenAI && model.CanReason {
  623. opts = append(
  624. opts,
  625. provider.WithOpenAIOptions(
  626. provider.WithReasoningEffort(agentConfig.ReasoningEffort),
  627. ),
  628. )
  629. } else if model.Provider == models.ProviderAnthropic && model.CanReason && agentName == config.AgentCoder {
  630. opts = append(
  631. opts,
  632. provider.WithAnthropicOptions(
  633. provider.WithAnthropicShouldThinkFn(provider.DefaultShouldThinkFn),
  634. ),
  635. )
  636. }
  637. agentProvider, err := provider.NewProvider(
  638. model.Provider,
  639. opts...,
  640. )
  641. if err != nil {
  642. return nil, fmt.Errorf("could not create provider: %v", err)
  643. }
  644. return agentProvider, nil
  645. }