RunSession runs a session with the given messages. When modelOverride is non-empty, it is applied to the session's current agent before any user messages are appended (and persisted via SetSessionAgentModel) so the override is in effect for this turn and every subsequent one. Validation happens bef
(ctx context.Context, sessionID, agentFilename, currentAgent string, messages []api.Message, modelOverride string)
| 585 | // recorded so a bad ref does not leave an orphaned user message in the |
| 586 | // history. |
| 587 | func (sm *SessionManager) RunSession(ctx context.Context, sessionID, agentFilename, currentAgent string, messages []api.Message, modelOverride string) (<-chan runtime.Event, error) { |
| 588 | sm.mux.Lock() |
| 589 | defer sm.mux.Unlock() |
| 590 | sess, err := sm.sessionStore.GetSession(ctx, sessionID) |
| 591 | if err != nil { |
| 592 | return nil, err |
| 593 | } |
| 594 | |
| 595 | rc := sm.runConfig.Clone() |
| 596 | rc.WorkingDir = sess.WorkingDir |
| 597 | |
| 598 | runtimeSession, exists := sm.runtimeSessions.Load(sessionID) |
| 599 | |
| 600 | streamCtx, cancel := context.WithCancel(ctx) |
| 601 | var titleGen *sessiontitle.Generator |
| 602 | if !exists { |
| 603 | var rt runtime.Runtime |
| 604 | rt, titleGen, err = sm.runtimeForSession(ctx, sess, agentFilename, currentAgent, rc) |
| 605 | if err != nil { |
| 606 | cancel() |
| 607 | return nil, err |
| 608 | } |
| 609 | runtimeSession = &activeRuntimes{ |
| 610 | runtime: rt, |
| 611 | cancel: cancel, |
| 612 | session: sess, |
| 613 | titleGen: titleGen, |
| 614 | } |
| 615 | sm.runtimeSessions.Store(sessionID, runtimeSession) |
| 616 | sm.markReady() |
| 617 | } else { |
| 618 | titleGen = runtimeSession.titleGen |
| 619 | } |
| 620 | |
| 621 | // Reject the request immediately if the session is already streaming. |
| 622 | // This prevents interleaving user messages while a tool call is in |
| 623 | // progress, which would produce a tool_use without a matching |
| 624 | // tool_result and cause provider errors. |
| 625 | if !runtimeSession.streaming.TryLock() { |
| 626 | cancel() |
| 627 | return nil, ErrSessionBusy |
| 628 | } |
| 629 | |
| 630 | // Apply the model override (if any) before persisting the user |
| 631 | // messages so that an invalid ref does not leave an orphaned user |
| 632 | // message in the history. We hold both sm.mux and streaming, so we |
| 633 | // can mutate session fields directly; on store-write failure below |
| 634 | // we roll the runtime back to its previous override. |
| 635 | prevOverride, hadPrevOverride, undoModelOverride, err := sm.applyRunModelOverride(ctx, runtimeSession, modelOverride) |
| 636 | if err != nil { |
| 637 | runtimeSession.streaming.Unlock() |
| 638 | cancel() |
| 639 | return nil, err |
| 640 | } |
| 641 | |
| 642 | // Now that we hold the streaming lock, it is safe to mutate the session. |
| 643 | // Collect user messages for potential title generation |
| 644 | var userMessages []string |