( params: QueryParams, consumedCommandUuids: string[], )
| 290 | } |
| 291 | |
| 292 | async function* queryLoop( |
| 293 | params: QueryParams, |
| 294 | consumedCommandUuids: string[], |
| 295 | ): AsyncGenerator< |
| 296 | | StreamEvent |
| 297 | | RequestStartEvent |
| 298 | | Message |
| 299 | | TombstoneMessage |
| 300 | | ToolUseSummaryMessage, |
| 301 | Terminal |
| 302 | > { |
| 303 | // Immutable params — never reassigned during the query loop. |
| 304 | const { |
| 305 | systemPrompt, |
| 306 | userContext, |
| 307 | systemContext, |
| 308 | canUseTool, |
| 309 | fallbackModel, |
| 310 | querySource, |
| 311 | maxTurns, |
| 312 | skipCacheWrite, |
| 313 | } = params |
| 314 | const deps = params.deps ?? productionDeps() |
| 315 | |
| 316 | // Mutable cross-iteration state. The loop body destructures this at the top |
| 317 | // of each iteration so reads stay bare-name (`messages`, `toolUseContext`). |
| 318 | // Continue sites write `state = { ... }` instead of 9 separate assignments. |
| 319 | let state: State = { |
| 320 | messages: params.messages, |
| 321 | toolUseContext: params.toolUseContext, |
| 322 | maxOutputTokensOverride: params.maxOutputTokensOverride, |
| 323 | autoCompactTracking: undefined, |
| 324 | stopHookActive: undefined, |
| 325 | maxOutputTokensRecoveryCount: 0, |
| 326 | hasAttemptedReactiveCompact: false, |
| 327 | turnCount: 1, |
| 328 | pendingToolUseSummary: undefined, |
| 329 | transition: undefined, |
| 330 | } |
| 331 | const budgetTracker = feature('TOKEN_BUDGET') ? createBudgetTracker() : null |
| 332 | |
| 333 | // task_budget.remaining tracking across compaction boundaries. Undefined |
| 334 | // until first compact fires — while context is uncompacted the server can |
| 335 | // see the full history and handles the countdown from {total} itself (see |
| 336 | // api/api/sampling/prompt/renderer.py:292). After a compact, the server sees |
| 337 | // only the summary and would under-count spend; remaining tells it the |
| 338 | // pre-compact final window that got summarized away. Cumulative across |
| 339 | // multiple compacts: each subtracts the final context at that compact's |
| 340 | // trigger point. Loop-local (not on State) to avoid touching the 7 continue |
| 341 | // sites. |
| 342 | let taskBudgetRemaining: number | undefined = undefined |
| 343 | |
| 344 | // Snapshot immutable env/statsig/session state once at entry. See QueryConfig |
| 345 | // for what's included and why feature() gates are intentionally excluded. |
| 346 | const config = buildQueryConfig() |
| 347 | let malformedToolOutputRecoveryCount = 0 |
| 348 | |
| 349 | // Fired once per user turn — the prompt is invariant across loop iterations, |
no test coverage detected