59from typing
import TYPE_CHECKING, Any, List, Dict, Union
95 """Split non-system messages into user-rooted turns (each starts with a user message)."""
98 body = messages[1:]
if messages[0].get(
"role") ==
"system" else list(messages)
101 turns: list[list[dict[str, Any]]] = []
102 cur: list[dict[str, Any]] = []
104 if m.get(
"role") ==
"user" and cur:
149def maybe_compress(session: ChatSession, client: ClientInterface, presenter: Presenter |
None =
None) -> bool:
150 """Compress history if the context budget is exceeded.
152 Uses API-reported ``prompt_tokens`` from the last turn against the
153 session model's context window. Older conversation turns are passed to
154 :meth:`DeepSeekClient.one_shot` on ``CONTEXT_COMPRESS_SUMMARY_MODEL`` with
155 successful tool bodies stripped; the last ``CONTEXT_KEEP_RECENT_TURNS``
156 turns stay verbatim in ``session.messages``.
158 func =
"maybe_compress"
159 messages = session.messages
160 max_tokens = get_max_context_tokens(session.model)
161 threshold = int(max_tokens * CONTEXT_COMPRESS_THRESHOLD)
164 _dbg(
"OC_CHAT.CTX", func,
165 f
"effective={effective} threshold={threshold} max_tokens={max_tokens}", 3)
167 if effective < threshold:
168 _dbg(
"OC_CHAT.CTX", func,
"below threshold — skipping", 3)
172 if len(turns) <= CONTEXT_KEEP_RECENT_TURNS:
173 _dbg(
"OC_CHAT.CTX", func,
174 f
"only {len(turns)} turns <= {CONTEXT_KEEP_RECENT_TURNS} — skipping", 3)
177 system_msg = messages[0]
if messages
and messages[0].get(
"role") ==
"system" else None
178 if system_msg
is None:
181 keep_turns = turns[-CONTEXT_KEEP_RECENT_TURNS :]
182 old_turns = turns[: -CONTEXT_KEEP_RECENT_TURNS]
184 _dbg(
"OC_CHAT.CTX", func,
185 f
"compressing: {len(old_turns)} old turns, keeping {len(keep_turns)} recent", 2)
187 abbreviated_old: list[dict[str, Any]] = []
190 abbreviated_old.append(m)
196 "Summarise the following older conversation turns in 4-8 short paragraphs. "
197 "Preserve critical user goals, file paths, errors, and conclusions. "
198 "Omit verbose tool dumps — they were already stripped to placeholders."
203 "content": json.dumps(abbreviated_old, ensure_ascii=
False),
207 summary_model = client.provider.tier(
"summary")
or session.model
208 _dbg(
"OC_CHAT.CTX", func, f
"summary model={summary_model}", 3)
209 with dbg_timed(
"OC_CHAT.CTX", func, label=
"summarise", level=3):
211 summary = client.one_shot(
217 except Exception
as exc:
218 _dbg(
"OC_CHAT.CTX", func,
219 f
"compression failed: {type(exc).__name__}: {exc}", 1, override=
True)
222 rebuilt: list[dict[str, Any]] = [system_msg]
224 {
"role":
"assistant",
"content": f
"[Earlier conversation summary: {summary}]"}
229 session.messages = rebuilt
230 session.last_turn_max_prompt_tokens = 0
233 _dbg(
"OC_CHAT.CTX", func,
234 f
"compressed: {len(old_turns)} old turns -> summary, messages={len(rebuilt)}", 2)
237 presenter.show_compressed()
list[dict[str, Any]] _abbreviate_turn_for_summary(list[dict[str, Any]] turn)
int _effective_prompt_trigger(session, list messages)
int _estimate_tokens(list[dict[str, Any]] messages)
list[list[dict[str, Any]]] _conversation_turns(list[dict[str, Any]] messages)
bool maybe_compress(ChatSession session, ClientInterface client, Presenter|None presenter=None)
dict[str, Any] _abbreviate_tool_for_summary(dict[str, Any] m)