tau-coding-agent 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tau/__init__.py +0 -0
- tau/agent/__init__.py +11 -0
- tau/agent/prompt/__init__.py +10 -0
- tau/agent/prompt/builder.py +302 -0
- tau/agent/prompt/types.py +33 -0
- tau/agent/service.py +369 -0
- tau/agent/types.py +61 -0
- tau/auth/manager.py +247 -0
- tau/auth/storage.py +82 -0
- tau/auth/types.py +41 -0
- tau/builtins/__init__.py +4 -0
- tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__init__.py +41 -0
- tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
- tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
- tau/builtins/commands/clear.py +16 -0
- tau/builtins/commands/compact.py +28 -0
- tau/builtins/commands/reload.py +27 -0
- tau/builtins/commands/session.py +19 -0
- tau/builtins/extensions/footer/__init__.py +76 -0
- tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
- tau/builtins/extensions/footer/git.py +26 -0
- tau/builtins/extensions/footer/model.py +69 -0
- tau/builtins/extensions/footer/utils.py +44 -0
- tau/builtins/extensions/header/__init__.py +18 -0
- tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__init__.py +0 -0
- tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/models/audio.py +43 -0
- tau/builtins/models/image.py +43 -0
- tau/builtins/models/text.py +482 -0
- tau/builtins/models/video.py +40 -0
- tau/builtins/prompts/commit.md +7 -0
- tau/builtins/prompts/docs.md +7 -0
- tau/builtins/prompts/explain.md +7 -0
- tau/builtins/prompts/fix.md +7 -0
- tau/builtins/prompts/refactor.md +7 -0
- tau/builtins/prompts/review.md +7 -0
- tau/builtins/prompts/test.md +7 -0
- tau/builtins/providers/__init__.py +0 -0
- tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
- tau/builtins/providers/audio.py +10 -0
- tau/builtins/providers/image.py +9 -0
- tau/builtins/providers/text.py +33 -0
- tau/builtins/providers/video.py +6 -0
- tau/builtins/skills/code-review/SKILL.md +4 -0
- tau/builtins/skills/debug/SKILL.md +4 -0
- tau/builtins/skills/git-commit/SKILL.md +4 -0
- tau/builtins/themes/dark.yaml +1 -0
- tau/builtins/themes/light.yaml +46 -0
- tau/builtins/tools/__init__.py +73 -0
- tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
- tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
- tau/builtins/tools/edit.py +215 -0
- tau/builtins/tools/glob.py +112 -0
- tau/builtins/tools/grep.py +146 -0
- tau/builtins/tools/ls.py +135 -0
- tau/builtins/tools/read.py +122 -0
- tau/builtins/tools/terminal.py +150 -0
- tau/builtins/tools/write.py +105 -0
- tau/commands/__init__.py +10 -0
- tau/commands/registry.py +71 -0
- tau/commands/types.py +33 -0
- tau/console/__init__.py +0 -0
- tau/console/cli.py +266 -0
- tau/console/commands/__init__.py +0 -0
- tau/console/commands/auth.py +193 -0
- tau/console/commands/packages.py +104 -0
- tau/console/commands/update.py +76 -0
- tau/core/__init__.py +0 -0
- tau/core/registry.py +102 -0
- tau/engine/__init__.py +47 -0
- tau/engine/service.py +768 -0
- tau/engine/types.py +163 -0
- tau/extensions/__init__.py +28 -0
- tau/extensions/api.py +928 -0
- tau/extensions/context.py +462 -0
- tau/extensions/events.py +70 -0
- tau/extensions/loader.py +386 -0
- tau/extensions/runtime.py +184 -0
- tau/extensions/settings.py +137 -0
- tau/hooks/__init__.py +112 -0
- tau/hooks/engine.py +237 -0
- tau/hooks/inference.py +21 -0
- tau/hooks/runtime.py +126 -0
- tau/hooks/service.py +121 -0
- tau/hooks/session.py +117 -0
- tau/hooks/tui.py +61 -0
- tau/hooks/types.py +72 -0
- tau/inference/__init__.py +180 -0
- tau/inference/api/__init__.py +0 -0
- tau/inference/api/audio/__init__.py +0 -0
- tau/inference/api/audio/base.py +29 -0
- tau/inference/api/audio/builtins.py +15 -0
- tau/inference/api/audio/elevenlabs_audio.py +183 -0
- tau/inference/api/audio/gemini_audio.py +95 -0
- tau/inference/api/audio/openai_audio.py +159 -0
- tau/inference/api/audio/registry.py +15 -0
- tau/inference/api/audio/sarvam_audio.py +163 -0
- tau/inference/api/audio/service.py +103 -0
- tau/inference/api/audio/utils.py +47 -0
- tau/inference/api/image/__init__.py +0 -0
- tau/inference/api/image/base.py +17 -0
- tau/inference/api/image/builtins.py +8 -0
- tau/inference/api/image/gemini_image.py +77 -0
- tau/inference/api/image/openai_image.py +103 -0
- tau/inference/api/image/openrouter.py +144 -0
- tau/inference/api/image/registry.py +15 -0
- tau/inference/api/image/service.py +71 -0
- tau/inference/api/registry.py +82 -0
- tau/inference/api/text/__init__.py +0 -0
- tau/inference/api/text/anthropic_claude_code.py +222 -0
- tau/inference/api/text/anthropic_messages.py +196 -0
- tau/inference/api/text/base.py +40 -0
- tau/inference/api/text/builtins.py +19 -0
- tau/inference/api/text/gemini_generate.py +234 -0
- tau/inference/api/text/github_copilot_chat.py +172 -0
- tau/inference/api/text/google_antigravity.py +522 -0
- tau/inference/api/text/mistral_chat.py +284 -0
- tau/inference/api/text/ollama_chat.py +200 -0
- tau/inference/api/text/openai_codex_responses.py +497 -0
- tau/inference/api/text/openai_completions.py +227 -0
- tau/inference/api/text/openai_responses.py +235 -0
- tau/inference/api/text/registry.py +50 -0
- tau/inference/api/text/service.py +297 -0
- tau/inference/api/text/types.py +7 -0
- tau/inference/api/text/utils.py +228 -0
- tau/inference/api/video/__init__.py +0 -0
- tau/inference/api/video/base.py +26 -0
- tau/inference/api/video/builtins.py +7 -0
- tau/inference/api/video/fal_video.py +119 -0
- tau/inference/api/video/openrouter_video.py +142 -0
- tau/inference/api/video/registry.py +15 -0
- tau/inference/api/video/service.py +72 -0
- tau/inference/model/__init__.py +0 -0
- tau/inference/model/registry.py +102 -0
- tau/inference/model/types.py +65 -0
- tau/inference/provider/__init__.py +0 -0
- tau/inference/provider/oauth/__init__.py +35 -0
- tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
- tau/inference/provider/oauth/github_copilot.py +333 -0
- tau/inference/provider/oauth/google_antigravity.py +258 -0
- tau/inference/provider/oauth/openai_codex.py +309 -0
- tau/inference/provider/oauth/pkce.py +14 -0
- tau/inference/provider/oauth/types.py +46 -0
- tau/inference/provider/oauth/utils.py +154 -0
- tau/inference/provider/registry.py +141 -0
- tau/inference/provider/types.py +114 -0
- tau/inference/types.py +549 -0
- tau/inference/utils.py +219 -0
- tau/message/__init__.py +0 -0
- tau/message/types.py +482 -0
- tau/message/utils.py +178 -0
- tau/packages/__init__.py +11 -0
- tau/packages/manager.py +190 -0
- tau/packages/types.py +20 -0
- tau/packages/utils.py +67 -0
- tau/prompts/expand.py +58 -0
- tau/prompts/loader.py +69 -0
- tau/prompts/registry.py +45 -0
- tau/prompts/types.py +24 -0
- tau/rpc/__init__.py +8 -0
- tau/rpc/mode.py +783 -0
- tau/rpc/types.py +252 -0
- tau/runtime/service.py +759 -0
- tau/runtime/types.py +303 -0
- tau/session/branch_summarization.py +312 -0
- tau/session/compaction.py +646 -0
- tau/session/manager.py +652 -0
- tau/session/types.py +188 -0
- tau/session/utils.py +233 -0
- tau/settings/manager.py +1077 -0
- tau/settings/paths.py +150 -0
- tau/settings/storage.py +63 -0
- tau/settings/types.py +173 -0
- tau/settings/utils.py +25 -0
- tau/skills/loader.py +91 -0
- tau/skills/registry.py +70 -0
- tau/skills/types.py +25 -0
- tau/themes/loader.py +238 -0
- tau/themes/registry.py +108 -0
- tau/themes/types.py +19 -0
- tau/tool/__init__.py +3 -0
- tau/tool/registry.py +117 -0
- tau/tool/render.py +21 -0
- tau/tool/types.py +244 -0
- tau/trust/__init__.py +13 -0
- tau/trust/manager.py +80 -0
- tau/trust/types.py +14 -0
- tau/trust/utils.py +72 -0
- tau/tui/__init__.py +54 -0
- tau/tui/agent_hooks.py +346 -0
- tau/tui/ansi.py +330 -0
- tau/tui/app.py +540 -0
- tau/tui/autocomplete.py +33 -0
- tau/tui/capabilities.py +119 -0
- tau/tui/commands/__init__.py +3 -0
- tau/tui/commands/appearance.py +498 -0
- tau/tui/commands/auth.py +232 -0
- tau/tui/commands/context.py +38 -0
- tau/tui/commands/misc.py +82 -0
- tau/tui/commands/model.py +118 -0
- tau/tui/commands/session.py +464 -0
- tau/tui/component.py +268 -0
- tau/tui/components/__init__.py +0 -0
- tau/tui/components/autocomplete_manager.py +267 -0
- tau/tui/components/autocomplete_picker.py +143 -0
- tau/tui/components/box.py +90 -0
- tau/tui/components/command_palette.py +144 -0
- tau/tui/components/dynamic_border.py +19 -0
- tau/tui/components/file_picker.py +233 -0
- tau/tui/components/image.py +181 -0
- tau/tui/components/inline_selector.py +71 -0
- tau/tui/components/layout.py +1194 -0
- tau/tui/components/message_list.py +692 -0
- tau/tui/components/modal.py +97 -0
- tau/tui/components/model_palette.py +204 -0
- tau/tui/components/picker_overlay.py +174 -0
- tau/tui/components/prompt_overlay.py +236 -0
- tau/tui/components/resume_modal.py +372 -0
- tau/tui/components/select_list.py +222 -0
- tau/tui/components/settings_modal.py +274 -0
- tau/tui/components/settings_schema.py +203 -0
- tau/tui/components/spinner.py +119 -0
- tau/tui/components/text_input.py +396 -0
- tau/tui/components/text_prompt.py +82 -0
- tau/tui/components/tree_select_list.py +580 -0
- tau/tui/components/trust_screen.py +97 -0
- tau/tui/diff.py +114 -0
- tau/tui/fuzzy.py +99 -0
- tau/tui/input.py +496 -0
- tau/tui/input_handler.py +716 -0
- tau/tui/keybindings.py +87 -0
- tau/tui/markdown.py +286 -0
- tau/tui/message_renderers.py +31 -0
- tau/tui/overlay.py +326 -0
- tau/tui/renderer.py +378 -0
- tau/tui/terminal.py +499 -0
- tau/tui/theme.py +148 -0
- tau/tui/tui.py +544 -0
- tau/tui/ui_context.py +768 -0
- tau/tui/utils.py +20 -0
- tau/utils/__init__.py +0 -0
- tau/utils/http_proxy.py +221 -0
- tau/utils/image_processing.py +172 -0
- tau/utils/secrets.py +59 -0
- tau/utils/version_check.py +60 -0
- tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
- tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
- tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
- tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
- tau_coding_agent-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context compaction for long agent sessions.
|
|
3
|
+
|
|
4
|
+
Compaction algorithm:
|
|
5
|
+
- Backwards-walk cut-point detection (never cuts mid-tool-result)
|
|
6
|
+
- Turn-split handling with a separate prefix summary
|
|
7
|
+
- Iterative summary merging (update prompt on subsequent compactions)
|
|
8
|
+
- File operation tracking appended to summary
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import json
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from tau.inference.api.text.service import TextLLM
|
|
19
|
+
from tau.session.types import SessionEntry
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Settings
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CompactionSettings:
|
|
28
|
+
"""Configuration for context compaction behavior."""
|
|
29
|
+
enabled: bool = True
|
|
30
|
+
reserve_tokens: int = 16_384
|
|
31
|
+
keep_recent_tokens: int = 20_000
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
DEFAULT_COMPACTION_SETTINGS = CompactionSettings()
|
|
35
|
+
|
|
36
|
+
TOOL_RESULT_MAX_CHARS = 2_000
|
|
37
|
+
ESTIMATED_IMAGE_CHARS = 4_800
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Result / preparation types
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class CompactionResult:
|
|
46
|
+
"""Result of a compaction operation."""
|
|
47
|
+
summary: str
|
|
48
|
+
first_kept_entry_id: str
|
|
49
|
+
tokens_before: int
|
|
50
|
+
details: dict[str, Any] | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class CompactionPreparation:
|
|
55
|
+
"""Preparation data for a compaction operation."""
|
|
56
|
+
first_kept_entry_id: str
|
|
57
|
+
messages_to_summarize: list
|
|
58
|
+
turn_prefix_messages: list
|
|
59
|
+
is_split_turn: bool
|
|
60
|
+
tokens_before: int
|
|
61
|
+
settings: CompactionSettings
|
|
62
|
+
previous_summary: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class ContextUsageEstimate:
|
|
67
|
+
"""Estimate of context token usage."""
|
|
68
|
+
tokens: int
|
|
69
|
+
usage_tokens: int
|
|
70
|
+
trailing_tokens: int
|
|
71
|
+
last_usage_index: int | None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class CutPointResult:
|
|
76
|
+
"""Result of finding a context cut point for compaction."""
|
|
77
|
+
first_kept_entry_index: int
|
|
78
|
+
is_split_turn: bool = False
|
|
79
|
+
turn_start_index: int = -1
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Token estimation
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def estimate_tokens(message: Any) -> int:
|
|
87
|
+
"""Estimate token count for any AgentMessage using chars/4 heuristic."""
|
|
88
|
+
from tau.message.types import (
|
|
89
|
+
UserMessage, AssistantMessage, ToolMessage, TerminalExecutionMessage,
|
|
90
|
+
CustomMessage, CompactionSummaryMessage, BranchSummaryMessage,
|
|
91
|
+
TextContent, ImageContent, ThinkingContent, ToolCallContent, ToolResultContent,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
chars = 0
|
|
95
|
+
if isinstance(message, UserMessage):
|
|
96
|
+
for c in message.contents:
|
|
97
|
+
if isinstance(c, TextContent):
|
|
98
|
+
chars += len(c.content)
|
|
99
|
+
elif isinstance(c, ImageContent):
|
|
100
|
+
chars += ESTIMATED_IMAGE_CHARS
|
|
101
|
+
elif isinstance(message, AssistantMessage):
|
|
102
|
+
for c in message.contents:
|
|
103
|
+
if isinstance(c, TextContent):
|
|
104
|
+
chars += len(c.content)
|
|
105
|
+
elif isinstance(c, ThinkingContent):
|
|
106
|
+
chars += len(c.content)
|
|
107
|
+
elif isinstance(c, ToolCallContent):
|
|
108
|
+
chars += len(c.name) + len(json.dumps(c.args))
|
|
109
|
+
elif isinstance(message, ToolMessage):
|
|
110
|
+
for c in message.contents:
|
|
111
|
+
if isinstance(c, ToolResultContent):
|
|
112
|
+
chars += len(c.content)
|
|
113
|
+
elif isinstance(message, TerminalExecutionMessage):
|
|
114
|
+
chars = len(message.command) + len(message.output)
|
|
115
|
+
elif isinstance(message, (CompactionSummaryMessage, BranchSummaryMessage)):
|
|
116
|
+
chars = len(message.summary)
|
|
117
|
+
elif isinstance(message, CustomMessage):
|
|
118
|
+
for c in message.contents:
|
|
119
|
+
if isinstance(c, TextContent):
|
|
120
|
+
chars += len(c.content)
|
|
121
|
+
|
|
122
|
+
return max(1, chars // 4)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def estimate_context_tokens(messages: list) -> ContextUsageEstimate:
|
|
126
|
+
"""
|
|
127
|
+
Estimate total context tokens.
|
|
128
|
+
|
|
129
|
+
Uses the Usage object from the last non-aborted/non-error assistant message
|
|
130
|
+
as a precise anchor, then estimates trailing messages with chars/4.
|
|
131
|
+
"""
|
|
132
|
+
from tau.message.types import AssistantMessage
|
|
133
|
+
from tau.inference.types import StopReason
|
|
134
|
+
|
|
135
|
+
last_usage: int | None = None
|
|
136
|
+
last_usage_idx: int | None = None
|
|
137
|
+
|
|
138
|
+
for i in range(len(messages) - 1, -1, -1):
|
|
139
|
+
msg = messages[i]
|
|
140
|
+
if isinstance(msg, AssistantMessage):
|
|
141
|
+
if msg.stop_reason not in (StopReason.Abort, StopReason.Error):
|
|
142
|
+
u = msg.usage
|
|
143
|
+
total = u.input_tokens + u.output_tokens + u.cache_read_tokens + u.cache_write_tokens
|
|
144
|
+
if total > 0:
|
|
145
|
+
last_usage = total
|
|
146
|
+
last_usage_idx = i
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
if last_usage is None:
|
|
150
|
+
estimated = sum(estimate_tokens(m) for m in messages)
|
|
151
|
+
return ContextUsageEstimate(
|
|
152
|
+
tokens=estimated,
|
|
153
|
+
usage_tokens=0,
|
|
154
|
+
trailing_tokens=estimated,
|
|
155
|
+
last_usage_index=None,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
trailing = sum(estimate_tokens(messages[i]) for i in range(last_usage_idx + 1, len(messages))) # type: ignore[operator]
|
|
159
|
+
return ContextUsageEstimate(
|
|
160
|
+
tokens=last_usage + trailing,
|
|
161
|
+
usage_tokens=last_usage,
|
|
162
|
+
trailing_tokens=trailing,
|
|
163
|
+
last_usage_index=last_usage_idx,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def should_compact(context_tokens: int, context_window: int, settings: CompactionSettings) -> bool:
|
|
168
|
+
if not settings.enabled or context_window <= 0:
|
|
169
|
+
return False
|
|
170
|
+
return context_tokens > context_window - settings.reserve_tokens
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Cut-point detection
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def _is_valid_cut_point(entry: Any) -> bool:
|
|
178
|
+
"""Return True if this session entry is a valid place to cut history."""
|
|
179
|
+
from tau.session.types import MessageEntry, CustomMessageEntry, BranchSummaryEntry
|
|
180
|
+
from tau.message.types import UserMessage, AssistantMessage, TerminalExecutionMessage, CustomMessage
|
|
181
|
+
from tau.inference.types import StopReason
|
|
182
|
+
|
|
183
|
+
if isinstance(entry, (CustomMessageEntry, BranchSummaryEntry)):
|
|
184
|
+
return True
|
|
185
|
+
if isinstance(entry, MessageEntry):
|
|
186
|
+
msg = entry.message
|
|
187
|
+
if isinstance(msg, (UserMessage, TerminalExecutionMessage, CustomMessage)):
|
|
188
|
+
return True
|
|
189
|
+
if isinstance(msg, AssistantMessage):
|
|
190
|
+
# Skip aborted-empty assistants — they are visual markers only
|
|
191
|
+
if msg.stop_reason == StopReason.Abort and not msg.contents:
|
|
192
|
+
return False
|
|
193
|
+
return True
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _entry_message(entry: Any) -> Any | None:
|
|
198
|
+
"""Extract AgentMessage from a session entry, skipping compaction entries."""
|
|
199
|
+
from tau.session.types import MessageEntry, CustomMessageEntry, CompactionEntry, BranchSummaryEntry
|
|
200
|
+
from tau.message.types import CustomMessage, BranchSummaryMessage
|
|
201
|
+
|
|
202
|
+
if isinstance(entry, CompactionEntry):
|
|
203
|
+
return None
|
|
204
|
+
if isinstance(entry, MessageEntry):
|
|
205
|
+
return entry.message
|
|
206
|
+
if isinstance(entry, CustomMessageEntry):
|
|
207
|
+
return CustomMessage.from_session(entry=entry)
|
|
208
|
+
if isinstance(entry, BranchSummaryEntry):
|
|
209
|
+
return BranchSummaryMessage(
|
|
210
|
+
summary=entry.summary,
|
|
211
|
+
from_id=entry.from_id,
|
|
212
|
+
timestamp=entry.timestamp,
|
|
213
|
+
)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def find_cut_point(
|
|
218
|
+
entries: list,
|
|
219
|
+
start_idx: int,
|
|
220
|
+
end_idx: int,
|
|
221
|
+
keep_recent_tokens: int,
|
|
222
|
+
) -> CutPointResult:
|
|
223
|
+
"""
|
|
224
|
+
Walk backwards from newest, accumulating estimated token sizes.
|
|
225
|
+
Stop when the budget is met. Return the first entry to keep.
|
|
226
|
+
|
|
227
|
+
Valid cut points: user, assistant, terminal, custom messages.
|
|
228
|
+
Never cuts at tool messages (must follow their assistant).
|
|
229
|
+
"""
|
|
230
|
+
from tau.session.types import MessageEntry, CompactionEntry
|
|
231
|
+
from tau.message.types import UserMessage, TerminalExecutionMessage
|
|
232
|
+
|
|
233
|
+
# Collect valid cut points in [start_idx, end_idx)
|
|
234
|
+
cut_points: list[int] = []
|
|
235
|
+
for i in range(start_idx, end_idx):
|
|
236
|
+
if _is_valid_cut_point(entries[i]):
|
|
237
|
+
cut_points.append(i)
|
|
238
|
+
|
|
239
|
+
if not cut_points:
|
|
240
|
+
return CutPointResult(first_kept_entry_index=start_idx)
|
|
241
|
+
|
|
242
|
+
accumulated = 0
|
|
243
|
+
cut_index = cut_points[0] # default: keep everything from boundary start
|
|
244
|
+
|
|
245
|
+
for i in range(end_idx - 1, start_idx - 1, -1):
|
|
246
|
+
entry = entries[i]
|
|
247
|
+
if not isinstance(entry, MessageEntry):
|
|
248
|
+
continue
|
|
249
|
+
accumulated += estimate_tokens(entry.message)
|
|
250
|
+
if accumulated >= keep_recent_tokens:
|
|
251
|
+
# Snap to the nearest valid cut point at or after i
|
|
252
|
+
for cp in cut_points:
|
|
253
|
+
if cp >= i:
|
|
254
|
+
cut_index = cp
|
|
255
|
+
break
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
# Walk backwards to include any adjacent non-message, non-compaction entries
|
|
259
|
+
while cut_index > start_idx:
|
|
260
|
+
prev = entries[cut_index - 1]
|
|
261
|
+
if isinstance(prev, (MessageEntry, CompactionEntry)):
|
|
262
|
+
break
|
|
263
|
+
cut_index -= 1
|
|
264
|
+
|
|
265
|
+
# Determine whether we're splitting a turn
|
|
266
|
+
cut_entry = entries[cut_index]
|
|
267
|
+
is_user_start = (
|
|
268
|
+
isinstance(cut_entry, MessageEntry)
|
|
269
|
+
and isinstance(cut_entry.message, (UserMessage, TerminalExecutionMessage))
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
from tau.session.types import CustomMessageEntry, BranchSummaryEntry
|
|
273
|
+
if is_user_start or isinstance(cut_entry, (CustomMessageEntry, BranchSummaryEntry)):
|
|
274
|
+
return CutPointResult(first_kept_entry_index=cut_index)
|
|
275
|
+
|
|
276
|
+
# Find the user message that started the turn containing cut_index
|
|
277
|
+
turn_start = -1
|
|
278
|
+
for i in range(cut_index, start_idx - 1, -1):
|
|
279
|
+
e = entries[i]
|
|
280
|
+
if isinstance(e, MessageEntry) and isinstance(e.message, (UserMessage, TerminalExecutionMessage)):
|
|
281
|
+
turn_start = i
|
|
282
|
+
break
|
|
283
|
+
if isinstance(e, (CustomMessageEntry, BranchSummaryEntry)):
|
|
284
|
+
turn_start = i
|
|
285
|
+
break
|
|
286
|
+
|
|
287
|
+
return CutPointResult(
|
|
288
|
+
first_kept_entry_index=cut_index,
|
|
289
|
+
is_split_turn=(turn_start != -1),
|
|
290
|
+
turn_start_index=turn_start,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ---------------------------------------------------------------------------
|
|
295
|
+
# Serialization (prevents the LLM from continuing the conversation)
|
|
296
|
+
# ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
def _truncate(text: str, max_chars: int) -> str:
|
|
299
|
+
if len(text) <= max_chars:
|
|
300
|
+
return text
|
|
301
|
+
truncated = len(text) - max_chars
|
|
302
|
+
return f"{text[:max_chars]}\n\n[... {truncated} more characters truncated]"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def serialize_conversation(messages: list) -> str:
|
|
306
|
+
"""
|
|
307
|
+
Render AgentMessages as flat text for the summarisation LLM.
|
|
308
|
+
Tool results are truncated to keep the prompt compact.
|
|
309
|
+
"""
|
|
310
|
+
from tau.message.types import (
|
|
311
|
+
UserMessage, AssistantMessage, ToolMessage, TerminalExecutionMessage,
|
|
312
|
+
CustomMessage, CompactionSummaryMessage, BranchSummaryMessage,
|
|
313
|
+
TextContent, ThinkingContent, ToolCallContent, ToolResultContent,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
parts: list[str] = []
|
|
317
|
+
|
|
318
|
+
for msg in messages:
|
|
319
|
+
if isinstance(msg, CompactionSummaryMessage):
|
|
320
|
+
parts.append(f"[Context Summary]:\n{msg.summary}")
|
|
321
|
+
|
|
322
|
+
elif isinstance(msg, BranchSummaryMessage):
|
|
323
|
+
parts.append(f"[Branch Summary]:\n{msg.summary}")
|
|
324
|
+
|
|
325
|
+
elif isinstance(msg, UserMessage):
|
|
326
|
+
text = "".join(
|
|
327
|
+
c.content for c in msg.contents if isinstance(c, TextContent)
|
|
328
|
+
)
|
|
329
|
+
if text:
|
|
330
|
+
parts.append(f"[User]: {text}")
|
|
331
|
+
|
|
332
|
+
elif isinstance(msg, AssistantMessage):
|
|
333
|
+
thinking_parts = [c.content for c in msg.contents if isinstance(c, ThinkingContent)]
|
|
334
|
+
text_parts = [c.content for c in msg.contents if isinstance(c, TextContent)]
|
|
335
|
+
tool_calls = [
|
|
336
|
+
f"{c.name}({', '.join(f'{k}={json.dumps(v)}' for k, v in c.args.items())})"
|
|
337
|
+
for c in msg.contents if isinstance(c, ToolCallContent)
|
|
338
|
+
]
|
|
339
|
+
if thinking_parts:
|
|
340
|
+
parts.append(f"[Assistant thinking]: {' '.join(thinking_parts)}")
|
|
341
|
+
if text_parts:
|
|
342
|
+
parts.append(f"[Assistant]: {' '.join(text_parts)}")
|
|
343
|
+
if tool_calls:
|
|
344
|
+
parts.append(f"[Assistant tool calls]: {'; '.join(tool_calls)}")
|
|
345
|
+
|
|
346
|
+
elif isinstance(msg, ToolMessage):
|
|
347
|
+
for c in msg.contents:
|
|
348
|
+
if isinstance(c, ToolResultContent) and c.content:
|
|
349
|
+
parts.append(f"[Tool result]: {_truncate(c.content, TOOL_RESULT_MAX_CHARS)}")
|
|
350
|
+
|
|
351
|
+
elif isinstance(msg, TerminalExecutionMessage):
|
|
352
|
+
text = f"Ran `{msg.command}`"
|
|
353
|
+
if msg.output:
|
|
354
|
+
text += f"\n```\n{_truncate(msg.output, TOOL_RESULT_MAX_CHARS)}\n```"
|
|
355
|
+
parts.append(f"[Terminal]: {text}")
|
|
356
|
+
|
|
357
|
+
elif isinstance(msg, CustomMessage):
|
|
358
|
+
text = "".join(
|
|
359
|
+
c.content for c in msg.contents if isinstance(c, TextContent)
|
|
360
|
+
)
|
|
361
|
+
if text:
|
|
362
|
+
parts.append(f"[{msg.custom_type}]: {text}")
|
|
363
|
+
|
|
364
|
+
return "\n\n".join(parts)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# ---------------------------------------------------------------------------
|
|
368
|
+
# Summarisation prompts
|
|
369
|
+
# ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
SUMMARIZATION_SYSTEM_PROMPT = (
|
|
372
|
+
"You are a context summarization assistant. "
|
|
373
|
+
"Read the conversation and produce a structured summary following the exact format requested. "
|
|
374
|
+
"Do NOT continue the conversation. Do NOT respond to questions in it. Output ONLY the summary."
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
SUMMARIZATION_PROMPT = """\
|
|
378
|
+
The messages above are a conversation to summarize. Create a structured context checkpoint \
|
|
379
|
+
summary that another LLM will use to continue the work.
|
|
380
|
+
|
|
381
|
+
Use this EXACT format:
|
|
382
|
+
|
|
383
|
+
## Goal
|
|
384
|
+
[What is the user trying to accomplish?]
|
|
385
|
+
|
|
386
|
+
## Constraints & Preferences
|
|
387
|
+
- [Any constraints or preferences the user mentioned, or "(none)"]
|
|
388
|
+
|
|
389
|
+
## Progress
|
|
390
|
+
### Done
|
|
391
|
+
- [x] [Completed tasks/changes]
|
|
392
|
+
|
|
393
|
+
### In Progress
|
|
394
|
+
- [ ] [Current work]
|
|
395
|
+
|
|
396
|
+
### Blocked
|
|
397
|
+
- [Issues preventing progress, if any]
|
|
398
|
+
|
|
399
|
+
## Key Decisions
|
|
400
|
+
- **[Decision]**: [Brief rationale]
|
|
401
|
+
|
|
402
|
+
## Next Steps
|
|
403
|
+
1. [Ordered list of what should happen next]
|
|
404
|
+
|
|
405
|
+
## Critical Context
|
|
406
|
+
- [Data, examples, or references needed to continue, or "(none)"]
|
|
407
|
+
|
|
408
|
+
Keep each section concise. Preserve exact file paths, function names, and error messages."""
|
|
409
|
+
|
|
410
|
+
UPDATE_SUMMARIZATION_PROMPT = """\
|
|
411
|
+
The messages above are NEW conversation messages to incorporate into the existing summary \
|
|
412
|
+
provided in <previous-summary> tags.
|
|
413
|
+
|
|
414
|
+
Update the existing structured summary with new information. RULES:
|
|
415
|
+
- PRESERVE all existing information from the previous summary
|
|
416
|
+
- ADD new progress, decisions, and context from the new messages
|
|
417
|
+
- UPDATE the Progress section: move items from "In Progress" to "Done" when completed
|
|
418
|
+
- UPDATE "Next Steps" based on what was accomplished
|
|
419
|
+
- PRESERVE exact file paths, function names, and error messages
|
|
420
|
+
|
|
421
|
+
Use this EXACT format:
|
|
422
|
+
|
|
423
|
+
## Goal
|
|
424
|
+
[Preserve existing goals, add new ones if the task expanded]
|
|
425
|
+
|
|
426
|
+
## Constraints & Preferences
|
|
427
|
+
- [Preserve existing, add new ones discovered]
|
|
428
|
+
|
|
429
|
+
## Progress
|
|
430
|
+
### Done
|
|
431
|
+
- [x] [Previously done items AND newly completed items]
|
|
432
|
+
|
|
433
|
+
### In Progress
|
|
434
|
+
- [ ] [Current work — updated based on progress]
|
|
435
|
+
|
|
436
|
+
### Blocked
|
|
437
|
+
- [Current blockers — remove if resolved]
|
|
438
|
+
|
|
439
|
+
## Key Decisions
|
|
440
|
+
- **[Decision]**: [Brief rationale] (preserve all previous, add new)
|
|
441
|
+
|
|
442
|
+
## Next Steps
|
|
443
|
+
1. [Update based on current state]
|
|
444
|
+
|
|
445
|
+
## Critical Context
|
|
446
|
+
- [Preserve important context, add new if needed]
|
|
447
|
+
|
|
448
|
+
Keep each section concise. Preserve exact file paths, function names, and error messages."""
|
|
449
|
+
|
|
450
|
+
TURN_PREFIX_SUMMARIZATION_PROMPT = """\
|
|
451
|
+
This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.
|
|
452
|
+
|
|
453
|
+
Summarize the prefix to provide context for the retained suffix:
|
|
454
|
+
|
|
455
|
+
## Original Request
|
|
456
|
+
[What did the user ask for in this turn?]
|
|
457
|
+
|
|
458
|
+
## Early Progress
|
|
459
|
+
- [Key decisions and work done in the prefix]
|
|
460
|
+
|
|
461
|
+
## Context for Suffix
|
|
462
|
+
- [Information needed to understand the retained recent work]
|
|
463
|
+
|
|
464
|
+
Be concise. Focus on what's needed to understand the kept suffix."""
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# ---------------------------------------------------------------------------
|
|
468
|
+
# Summary generation (LLM call)
|
|
469
|
+
# ---------------------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
async def _call_llm_for_summary(
|
|
472
|
+
prompt_text: str,
|
|
473
|
+
llm: TextLLM,
|
|
474
|
+
max_chars: int = 0,
|
|
475
|
+
) -> str:
|
|
476
|
+
from tau.message.types import UserMessage
|
|
477
|
+
from tau.inference.types import LLMContext, TextEndEvent, TextDeltaEvent
|
|
478
|
+
|
|
479
|
+
context = LLMContext(
|
|
480
|
+
messages=[UserMessage.from_text(prompt_text)],
|
|
481
|
+
system_prompt=SUMMARIZATION_SYSTEM_PROMPT,
|
|
482
|
+
)
|
|
483
|
+
events = await llm.invoke(context)
|
|
484
|
+
|
|
485
|
+
# Prefer TextEndEvent (full accumulated text); fall back to concatenating deltas
|
|
486
|
+
text_end = next((e for e in events if isinstance(e, TextEndEvent)), None)
|
|
487
|
+
if text_end:
|
|
488
|
+
return text_end.text.content
|
|
489
|
+
|
|
490
|
+
return "".join(
|
|
491
|
+
e.text.content for e in events if isinstance(e, TextDeltaEvent)
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
async def generate_summary(
|
|
496
|
+
messages: list,
|
|
497
|
+
llm: TextLLM,
|
|
498
|
+
reserve_tokens: int,
|
|
499
|
+
previous_summary: str | None = None,
|
|
500
|
+
custom_instructions: str | None = None,
|
|
501
|
+
) -> str:
|
|
502
|
+
conversation_text = serialize_conversation(messages)
|
|
503
|
+
|
|
504
|
+
base_prompt = UPDATE_SUMMARIZATION_PROMPT if previous_summary else SUMMARIZATION_PROMPT
|
|
505
|
+
if custom_instructions:
|
|
506
|
+
base_prompt = f"{base_prompt}\n\nAdditional focus: {custom_instructions}"
|
|
507
|
+
|
|
508
|
+
prompt = f"<conversation>\n{conversation_text}\n</conversation>\n\n"
|
|
509
|
+
if previous_summary:
|
|
510
|
+
prompt += f"<previous-summary>\n{previous_summary}\n</previous-summary>\n\n"
|
|
511
|
+
prompt += base_prompt
|
|
512
|
+
|
|
513
|
+
return await _call_llm_for_summary(prompt, llm)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
async def _generate_turn_prefix_summary(messages: list, llm: TextLLM) -> str:
|
|
517
|
+
conversation_text = serialize_conversation(messages)
|
|
518
|
+
prompt = f"<conversation>\n{conversation_text}\n</conversation>\n\n{TURN_PREFIX_SUMMARIZATION_PROMPT}"
|
|
519
|
+
return await _call_llm_for_summary(prompt, llm)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
# ---------------------------------------------------------------------------
|
|
523
|
+
# Prepare — pure, no I/O
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
def prepare_compaction(
|
|
527
|
+
entries: list,
|
|
528
|
+
settings: CompactionSettings,
|
|
529
|
+
) -> CompactionPreparation | None:
|
|
530
|
+
from tau.session.types import CompactionEntry, MessageEntry
|
|
531
|
+
|
|
532
|
+
if not entries:
|
|
533
|
+
return None
|
|
534
|
+
|
|
535
|
+
# Don't compact if the last entry is already a compaction
|
|
536
|
+
if isinstance(entries[-1], CompactionEntry):
|
|
537
|
+
return None
|
|
538
|
+
|
|
539
|
+
# Find the most recent previous compaction
|
|
540
|
+
prev_compaction: CompactionEntry | None = None
|
|
541
|
+
prev_compaction_idx = -1
|
|
542
|
+
for i in range(len(entries) - 1, -1, -1):
|
|
543
|
+
if isinstance(entries[i], CompactionEntry):
|
|
544
|
+
prev_compaction = entries[i]
|
|
545
|
+
prev_compaction_idx = i
|
|
546
|
+
break
|
|
547
|
+
|
|
548
|
+
previous_summary: str | None = None
|
|
549
|
+
boundary_start = 0
|
|
550
|
+
if prev_compaction is not None:
|
|
551
|
+
previous_summary = prev_compaction.summary
|
|
552
|
+
first_kept_idx = next(
|
|
553
|
+
(i for i, e in enumerate(entries) if e.id == prev_compaction.first_kept_entry_id),
|
|
554
|
+
prev_compaction_idx + 1,
|
|
555
|
+
)
|
|
556
|
+
boundary_start = first_kept_idx
|
|
557
|
+
|
|
558
|
+
boundary_end = len(entries)
|
|
559
|
+
|
|
560
|
+
# Estimate current context size from all messages in the branch
|
|
561
|
+
all_messages = [m for e in entries if (m := _entry_message(e)) is not None]
|
|
562
|
+
tokens_before = estimate_context_tokens(all_messages).tokens
|
|
563
|
+
|
|
564
|
+
cut = find_cut_point(entries, boundary_start, boundary_end, settings.keep_recent_tokens)
|
|
565
|
+
|
|
566
|
+
first_kept_entry = entries[cut.first_kept_entry_index]
|
|
567
|
+
first_kept_entry_id: str = getattr(first_kept_entry, "id", "")
|
|
568
|
+
if not first_kept_entry_id:
|
|
569
|
+
return None
|
|
570
|
+
|
|
571
|
+
# Nothing to cut — the entire conversation fits in the keep budget, so
|
|
572
|
+
# there is no history to summarise. Returning None skips compaction.
|
|
573
|
+
if cut.first_kept_entry_index == boundary_start and not cut.is_split_turn:
|
|
574
|
+
return None
|
|
575
|
+
|
|
576
|
+
history_end = cut.turn_start_index if cut.is_split_turn else cut.first_kept_entry_index
|
|
577
|
+
|
|
578
|
+
messages_to_summarize = [
|
|
579
|
+
m for i in range(boundary_start, history_end)
|
|
580
|
+
if (m := _entry_message(entries[i])) is not None
|
|
581
|
+
]
|
|
582
|
+
|
|
583
|
+
turn_prefix_messages: list = []
|
|
584
|
+
if cut.is_split_turn:
|
|
585
|
+
turn_prefix_messages = [
|
|
586
|
+
m for i in range(cut.turn_start_index, cut.first_kept_entry_index)
|
|
587
|
+
if (m := _entry_message(entries[i])) is not None
|
|
588
|
+
]
|
|
589
|
+
|
|
590
|
+
return CompactionPreparation(
|
|
591
|
+
first_kept_entry_id=first_kept_entry_id,
|
|
592
|
+
messages_to_summarize=messages_to_summarize,
|
|
593
|
+
turn_prefix_messages=turn_prefix_messages,
|
|
594
|
+
is_split_turn=cut.is_split_turn,
|
|
595
|
+
tokens_before=tokens_before,
|
|
596
|
+
previous_summary=previous_summary,
|
|
597
|
+
settings=settings,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
# ---------------------------------------------------------------------------
|
|
602
|
+
# Main compact() — async, calls LLM
|
|
603
|
+
# ---------------------------------------------------------------------------
|
|
604
|
+
|
|
605
|
+
async def compact(
|
|
606
|
+
preparation: CompactionPreparation,
|
|
607
|
+
llm: TextLLM,
|
|
608
|
+
custom_instructions: str | None = None,
|
|
609
|
+
) -> CompactionResult:
|
|
610
|
+
settings = preparation.settings
|
|
611
|
+
|
|
612
|
+
if preparation.is_split_turn and preparation.turn_prefix_messages:
|
|
613
|
+
# Generate both summaries in parallel
|
|
614
|
+
async def _no_history() -> str:
|
|
615
|
+
return "No prior history."
|
|
616
|
+
|
|
617
|
+
history_coro = (
|
|
618
|
+
generate_summary(
|
|
619
|
+
preparation.messages_to_summarize,
|
|
620
|
+
llm,
|
|
621
|
+
settings.reserve_tokens,
|
|
622
|
+
preparation.previous_summary,
|
|
623
|
+
custom_instructions,
|
|
624
|
+
)
|
|
625
|
+
if preparation.messages_to_summarize
|
|
626
|
+
else _no_history()
|
|
627
|
+
)
|
|
628
|
+
history_text, prefix_text = await asyncio.gather(
|
|
629
|
+
history_coro,
|
|
630
|
+
_generate_turn_prefix_summary(preparation.turn_prefix_messages, llm),
|
|
631
|
+
)
|
|
632
|
+
summary = f"{history_text}\n\n---\n\n**Turn Context (split turn):**\n\n{prefix_text}"
|
|
633
|
+
else:
|
|
634
|
+
summary = await generate_summary(
|
|
635
|
+
preparation.messages_to_summarize,
|
|
636
|
+
llm,
|
|
637
|
+
settings.reserve_tokens,
|
|
638
|
+
preparation.previous_summary,
|
|
639
|
+
custom_instructions,
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
return CompactionResult(
|
|
643
|
+
summary=summary,
|
|
644
|
+
first_kept_entry_id=preparation.first_kept_entry_id,
|
|
645
|
+
tokens_before=preparation.tokens_before,
|
|
646
|
+
)
|