processLLMRequest is the chat-mode turn entrypoint. It owns the animation/spinner lifecycle and the typed-ahead message queue; the per-turn pipeline phases (system-prompt assembly, history splicing, model/effort resolution, LLM execution, response handling) live in chat_pipeline.go so each step can
(parentCtx context.Context, in string)
| 29 | // model/effort resolution, LLM execution, response handling) live in |
| 30 | // chat_pipeline.go so each step can be read and tested in isolation. |
| 31 | func (cli *ChatCLI) processLLMRequest(parentCtx context.Context, in string) { |
| 32 | stopSpinner := cli.startProcessingLifecycle() |
| 33 | defer cli.endProcessingLifecycle(parentCtx, stopSpinner) |
| 34 | |
| 35 | ctx, releaseCtx := cli.acquireOperationContext(parentCtx) |
| 36 | defer releaseCtx() |
| 37 | |
| 38 | cli.saveCheckpoint() |
| 39 | cli.fireUserPromptSubmitHook(ctx, in) |
| 40 | cli.animation.ShowThinkingAnimation(cli.Client.GetModelName()) |
| 41 | |
| 42 | userInput, additionalContext, images := cli.processSpecialCommands(ctx, in) |
| 43 | // Vision gating (hybrid B+A): keep native images for vision models, or |
| 44 | // fold a textual description into the prompt for text-only models. |
| 45 | images, visionDesc := cli.gateImagesForModel(ctx, images) |
| 46 | additionalContext += visionDesc |
| 47 | // Pull turns that arrived on other channels (Telegram/…) into history so the |
| 48 | // model has cross-channel context. Silent — nothing is printed. |
| 49 | cli.syncHubContext(ctx) |
| 50 | cli.compactHistoryIfNeeded(ctx) |
| 51 | |
| 52 | assembly := cli.assembleChatSystemPrompt(ctx, userInput, additionalContext) |
| 53 | tempHistory := cli.buildChatTempHistory(assembly.parts, userInput, additionalContext, images) |
| 54 | userMessage := models.Message{Role: "user", Content: userInput + additionalContext, Images: images} |
| 55 | |
| 56 | effectiveMaxTokens := cli.getMaxTokensForCurrentLLM() |
| 57 | cli.ensureModelCacheWarm(ctx) |
| 58 | resolution := cli.resolveSkillClient(assembly.modelHint) |
| 59 | cli.noticeSkillResolution(resolution) |
| 60 | ctx = cli.applyChatEffortHint(ctx, routeEffortForPrompt(userInput, assembly.effort)) |
| 61 | |
| 62 | turnStart := time.Now() |
| 63 | aiResponse, llmErr := cli.executeLLMTurn( |
| 64 | ctx, resolution.Client, userInput, additionalContext, |
| 65 | tempHistory, effectiveMaxTokens, resolution, stopSpinner, |
| 66 | ) |
| 67 | cli.handleChatTurnResult( |
| 68 | ctx, llmErr, userMessage, aiResponse, resolution.Client, resolution, |
| 69 | userInput, additionalContext, time.Since(turnStart), |
| 70 | ) |
| 71 | } |
| 72 | |
| 73 | // startProcessingLifecycle suppresses the foreground animation (so it never |
| 74 | // fights go-prompt's prefix), starts the prompt-prefix spinner goroutine, |
no test coverage detected