InterruptChat interrupts execution, sets waiting status, and broadcasts status updates.
( ctx context.Context, chat database.Chat, )
| 2853 | |
| 2854 | // InterruptChat interrupts execution, sets waiting status, and broadcasts status updates. |
| 2855 | func (p *Server) InterruptChat( |
| 2856 | ctx context.Context, |
| 2857 | chat database.Chat, |
| 2858 | ) database.Chat { |
| 2859 | if chat.ID == uuid.Nil { |
| 2860 | return chat |
| 2861 | } |
| 2862 | |
| 2863 | // If the chat is in requires_action, insert synthetic error |
| 2864 | // tool-result messages for each pending dynamic tool call |
| 2865 | // before transitioning to waiting. Without this, the LLM |
| 2866 | // would see unmatched tool-call parts on the next run. |
| 2867 | if chat.Status == database.ChatStatusRequiresAction { |
| 2868 | if txErr := p.db.InTx(func(tx database.Store) error { |
| 2869 | locked, lockErr := tx.GetChatByIDForUpdate(ctx, chat.ID) |
| 2870 | if lockErr != nil { |
| 2871 | return xerrors.Errorf("lock chat for interrupt: %w", lockErr) |
| 2872 | } |
| 2873 | // Another request may have already transitioned |
| 2874 | // the chat (e.g. SubmitToolResults committed |
| 2875 | // between our snapshot and this lock). |
| 2876 | if locked.Status != database.ChatStatusRequiresAction { |
| 2877 | return nil |
| 2878 | } |
| 2879 | _, err := insertSyntheticToolResultsTx(ctx, tx, locked, "Tool execution interrupted by user") |
| 2880 | return err |
| 2881 | }, nil); txErr != nil { |
| 2882 | p.logger.Error(ctx, "failed to insert synthetic tool results during interrupt", |
| 2883 | slog.F("chat_id", chat.ID), |
| 2884 | slog.Error(txErr), |
| 2885 | ) |
| 2886 | // Fall through — still try to set waiting status. |
| 2887 | } |
| 2888 | } |
| 2889 | |
| 2890 | // Debug runs are finalized in the execution path when the owning |
| 2891 | // goroutine observes cancellation, so we do not mutate debug state here. |
| 2892 | updatedChat, err := p.setChatWaiting(ctx, chat.ID) |
| 2893 | if err != nil { |
| 2894 | p.logger.Error(ctx, "failed to mark chat as waiting", |
| 2895 | slog.F("chat_id", chat.ID), |
| 2896 | slog.Error(err), |
| 2897 | ) |
| 2898 | return chat |
| 2899 | } |
| 2900 | return updatedChat |
| 2901 | } |
| 2902 | |
| 2903 | const manualTitleMessageWindowLimit = 50 |
| 2904 |