( params: QueryParams, consumedCommandUuids: string[], )
| 239 | } |
| 240 | |
| 241 | async function* queryLoop( |
| 242 | params: QueryParams, |
| 243 | consumedCommandUuids: string[], |
| 244 | ): AsyncGenerator< |
| 245 | | StreamEvent |
| 246 | | RequestStartEvent |
| 247 | | Message |
| 248 | | TombstoneMessage |
| 249 | | ToolUseSummaryMessage, |
| 250 | Terminal |
| 251 | > { |
| 252 | // Immutable params — never reassigned during the query loop. |
| 253 | const { |
| 254 | systemPrompt, |
| 255 | userContext, |
| 256 | systemContext, |
| 257 | canUseTool, |
| 258 | fallbackModel, |
| 259 | querySource, |
| 260 | maxTurns, |
| 261 | skipCacheWrite, |
| 262 | } = params |
| 263 | const deps = params.deps ?? productionDeps() |
| 264 | |
| 265 | // Mutable cross-iteration state. The loop body destructures this at the top |
| 266 | // of each iteration so reads stay bare-name (`messages`, `toolUseContext`). |
| 267 | // Continue sites write `state = { ... }` instead of 9 separate assignments. |
| 268 | let state: State = { |
| 269 | messages: params.messages, |
| 270 | toolUseContext: params.toolUseContext, |
| 271 | maxOutputTokensOverride: params.maxOutputTokensOverride, |
| 272 | autoCompactTracking: undefined, |
| 273 | stopHookActive: undefined, |
| 274 | maxOutputTokensRecoveryCount: 0, |
| 275 | hasAttemptedReactiveCompact: false, |
| 276 | turnCount: 1, |
| 277 | pendingToolUseSummary: undefined, |
| 278 | transition: undefined, |
| 279 | } |
| 280 | const budgetTracker = feature('TOKEN_BUDGET') ? createBudgetTracker() : null |
| 281 | |
| 282 | // task_budget.remaining tracking across compaction boundaries. Undefined |
| 283 | // until first compact fires — while context is uncompacted the server can |
| 284 | // see the full history and handles the countdown from {total} itself (see |
| 285 | // api/api/sampling/prompt/renderer.py:292). After a compact, the server sees |
| 286 | // only the summary and would under-count spend; remaining tells it the |
| 287 | // pre-compact final window that got summarized away. Cumulative across |
| 288 | // multiple compacts: each subtracts the final context at that compact's |
| 289 | // trigger point. Loop-local (not on State) to avoid touching the 7 continue |
| 290 | // sites. |
| 291 | let taskBudgetRemaining: number | undefined = undefined |
| 292 | |
| 293 | // Snapshot immutable env/statsig/session state once at entry. See QueryConfig |
| 294 | // for what's included and why feature() gates are intentionally excluded. |
| 295 | const config = buildQueryConfig() |
| 296 | |
| 297 | // Fired once per user turn — the prompt is invariant across loop iterations, |
| 298 | // so per-iteration firing would ask sideQuery the same question N times. |
no test coverage detected