* @brief Returns the longest recent suffix of history that fits the provider context window, * cut only at fresh user-turn boundaries so tool_use/tool_result pairs stay intact. */
| 2555 | * cut only at fresh user-turn boundaries so tool_use/tool_result pairs stay intact. |
| 2556 | */ |
| 2557 | QJsonArray AI::Conversation::budgetedHistory(const QJsonArray& tools) const |
| 2558 | { |
| 2559 | Q_ASSERT(m_provider); |
| 2560 | if (!m_provider) |
| 2561 | return m_history; |
| 2562 | |
| 2563 | const auto caps = m_provider->capabilities(); |
| 2564 | const int budget = |
| 2565 | caps.contextWindowTokens - caps.maxOutputTokens - kSystemReserveTokens - estimateTokens(tools); |
| 2566 | if (budget <= 0 || estimateTokens(m_history) <= budget) |
| 2567 | return m_history; |
| 2568 | |
| 2569 | auto suffixFrom = [this](int from) { |
| 2570 | QJsonArray out; |
| 2571 | for (int i = from; i < m_history.size(); ++i) |
| 2572 | out.append(m_history.at(i)); |
| 2573 | |
| 2574 | return out; |
| 2575 | }; |
| 2576 | |
| 2577 | QList<int> boundaries; |
| 2578 | for (int at = firstFreshUserTurnAt(0); at >= 0; at = firstFreshUserTurnAt(at + 1)) |
| 2579 | boundaries.append(at); |
| 2580 | |
| 2581 | int chosen = boundaries.isEmpty() ? 0 : boundaries.constLast(); |
| 2582 | for (const int b : boundaries) |
| 2583 | if (estimateTokens(suffixFrom(b)) <= budget) { |
| 2584 | chosen = b; |
| 2585 | break; |
| 2586 | } |
| 2587 | |
| 2588 | Q_ASSERT(chosen >= 0 && chosen <= m_history.size()); |
| 2589 | return suffixFrom(chosen); |
| 2590 | } |
nothing calls this directly
no test coverage detected