FollowUpSession enqueues user messages for end-of-turn processing in a running session. Each message is popped one at a time after the current turn finishes, giving each follow-up a full undivided agent turn. idempotencyKey, when non-empty, makes the call safe to retry: if a request with the same k
(ctx context.Context, sessionID string, messages []api.Message, idempotencyKey string)
| 764 | // still enqueued but are not consumed until the next RunSession starts a |
| 765 | // stream; the returned boolean indicates whether a stream is active. |
| 766 | func (sm *SessionManager) FollowUpSession(ctx context.Context, sessionID string, messages []api.Message, idempotencyKey string) (streaming, duplicate bool, err error) { |
| 767 | rt, exists := sm.runtimeSessions.Load(sessionID) |
| 768 | if !exists { |
| 769 | return false, false, ErrSessionNotRunning |
| 770 | } |
| 771 | |
| 772 | if idempotencyKey != "" { |
| 773 | cache, _ := sm.followUpKeys.LoadOrStore(sessionID, newIdempotencyCache(defaultIdempotencyCapacity)) |
| 774 | if cache.reserve(idempotencyKey) { |
| 775 | return false, true, nil |
| 776 | } |
| 777 | // Roll the reservation back if we end up returning an error, so the |
| 778 | // caller can safely retry a failed request with the same key. |
| 779 | defer func() { |
| 780 | if err != nil { |
| 781 | cache.release(idempotencyKey) |
| 782 | } |
| 783 | }() |
| 784 | } |
| 785 | |
| 786 | // Attached session: hand the follow-up to its owner (the TUI App) so a |
| 787 | // real turn starts and events reach all subscribers. |
| 788 | if inject, ok := sm.followUpInjectors.Load(sessionID); ok { |
| 789 | for _, msg := range messages { |
| 790 | inject(ctx, msg.Content) |
| 791 | } |
| 792 | return true, false, nil |
| 793 | } |
| 794 | |
| 795 | for _, msg := range messages { |
| 796 | if err := rt.runtime.FollowUp(ctx, runtime.QueuedMessage{ |
| 797 | Content: msg.Content, |
| 798 | MultiContent: msg.MultiContent, |
| 799 | }); err != nil { |
| 800 | return false, false, err |
| 801 | } |
| 802 | } |
| 803 | |
| 804 | // Probe streaming state so the caller knows whether the follow-up |
| 805 | // will be consumed by the current turn or sit idle until the next. |
| 806 | streaming = !rt.streaming.TryLock() |
| 807 | if !streaming { |
| 808 | rt.streaming.Unlock() |
| 809 | } |
| 810 | |
| 811 | return streaming, false, nil |
| 812 | } |
| 813 | |
| 814 | // ResumeElicitation resumes an elicitation request. |
| 815 | func (sm *SessionManager) ResumeElicitation(ctx context.Context, sessionID, action string, content map[string]any) error { |
nothing calls this directly
no test coverage detected