synapse-cli-agent 0.1.13__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.
- synapse/__init__.py +13 -0
- synapse/__main__.py +6 -0
- synapse/app/__init__.py +1 -0
- synapse/app/agent.py +492 -0
- synapse/app/agent_md.py +107 -0
- synapse/cli.py +750 -0
- synapse/commands/__init__.py +1 -0
- synapse/commands/compression.py +573 -0
- synapse/commands/helpers.py +22 -0
- synapse/commands/mcp.py +406 -0
- synapse/commands/model.py +173 -0
- synapse/commands/result.py +34 -0
- synapse/commands/sessions.py +443 -0
- synapse/commands/slash_cmds.py +521 -0
- synapse/commands/slash_complete.py +816 -0
- synapse/commands/theme.py +99 -0
- synapse/config.py +27 -0
- synapse/content/__init__.py +1 -0
- synapse/content/input_history.py +122 -0
- synapse/content/multimodal.py +733 -0
- synapse/content/prompts.py +249 -0
- synapse/content/skills_catalog.py +128 -0
- synapse/integrations/__init__.py +1 -0
- synapse/integrations/checkpoint_seed.py +281 -0
- synapse/integrations/codex_history.py +375 -0
- synapse/integrations/codex_import.py +393 -0
- synapse/integrations/codex_sessions.py +629 -0
- synapse/integrations/describe_image.py +370 -0
- synapse/integrations/http_clients.py +199 -0
- synapse/integrations/llm_openai_compat.py +90 -0
- synapse/integrations/llm_openai_websocket.py +187 -0
- synapse/integrations/mcp_client.py +646 -0
- synapse/integrations/vision_middleware.py +62 -0
- synapse/models/__init__.py +5 -0
- synapse/models/config.py +240 -0
- synapse/models/helpers.py +206 -0
- synapse/models/profile.py +59 -0
- synapse/models/registry.py +722 -0
- synapse/models_registry.py +7 -0
- synapse/observability/__init__.py +1 -0
- synapse/observability/startup_trace.py +127 -0
- synapse/runtime/__init__.py +1 -0
- synapse/runtime/async_runtime.py +176 -0
- synapse/runtime/backends.py +458 -0
- synapse/runtime/context_compact.py +249 -0
- synapse/runtime/execute_capture.py +48 -0
- synapse/runtime/fs_permissions.py +79 -0
- synapse/runtime/harness.py +57 -0
- synapse/runtime/hitl.py +197 -0
- synapse/runtime/interaction_ledger.py +82 -0
- synapse/runtime/middleware.py +802 -0
- synapse/runtime/model_request_compression_middleware.py +745 -0
- synapse/runtime/pathing.py +146 -0
- synapse/runtime/safety.py +184 -0
- synapse/runtime/steer.py +240 -0
- synapse/runtime/subagents.py +207 -0
- synapse/runtime/tool_ignore.py +221 -0
- synapse/runtime/tool_output_eval.py +118 -0
- synapse/runtime/tool_output_middleware.py +585 -0
- synapse/runtime/tool_output_usage_middleware.py +60 -0
- synapse/sessions/__init__.py +31 -0
- synapse/sessions/cancel_repair.py +208 -0
- synapse/sessions/session_recap.py +174 -0
- synapse/sessions/store.py +695 -0
- synapse/sessions/transcript.py +754 -0
- synapse/settings/__init__.py +5 -0
- synapse/settings/config_paths.py +184 -0
- synapse/settings/schema.py +464 -0
- synapse/tool_output/__init__.py +59 -0
- synapse/tool_output/detection.py +170 -0
- synapse/tool_output/metrics.py +32 -0
- synapse/tool_output/models.py +173 -0
- synapse/tool_output/pipeline.py +330 -0
- synapse/tool_output/repository.py +721 -0
- synapse/tool_output/transformers.py +648 -0
- synapse/tools/__init__.py +5 -0
- synapse/tools/session_tools.py +204 -0
- synapse/ui/__init__.py +10 -0
- synapse/ui/bottombar/__init__.py +73 -0
- synapse/ui/bottombar/components/__init__.py +143 -0
- synapse/ui/bottombar/components/key_hints.py +30 -0
- synapse/ui/bottombar/components/mcp.py +64 -0
- synapse/ui/bottombar/components/mode.py +24 -0
- synapse/ui/bottombar/components/model.py +28 -0
- synapse/ui/bottombar/components/thread.py +29 -0
- synapse/ui/bottombar/context.py +36 -0
- synapse/ui/bottombar/core.py +74 -0
- synapse/ui/dialogs/__init__.py +25 -0
- synapse/ui/dialogs/base.py +362 -0
- synapse/ui/dialogs/codex_session_list.py +84 -0
- synapse/ui/dialogs/compression_diagnostics.py +210 -0
- synapse/ui/dialogs/git_explore.py +702 -0
- synapse/ui/dialogs/mcp_panel.py +407 -0
- synapse/ui/dialogs/model_picker.py +128 -0
- synapse/ui/dialogs/safety_panel.py +63 -0
- synapse/ui/dialogs/session_list.py +98 -0
- synapse/ui/dialogs/theme_designer.py +863 -0
- synapse/ui/dialogs/theme_picker.py +113 -0
- synapse/ui/git_explore/__init__.py +31 -0
- synapse/ui/git_explore/engine.py +82 -0
- synapse/ui/git_explore/provider.py +242 -0
- synapse/ui/git_explore/unified.py +85 -0
- synapse/ui/rendering.py +350 -0
- synapse/ui/sink.py +70 -0
- synapse/ui/steer_widget.py +367 -0
- synapse/ui/stream.py +1207 -0
- synapse/ui/stream_events.py +421 -0
- synapse/ui/stream_runtime.py +252 -0
- synapse/ui/theme.py +1154 -0
- synapse/ui/timeline.py +621 -0
- synapse/ui/topbar/__init__.py +97 -0
- synapse/ui/topbar/components/__init__.py +150 -0
- synapse/ui/topbar/components/branch.py +41 -0
- synapse/ui/topbar/components/title.py +24 -0
- synapse/ui/topbar/components/tool_output.py +24 -0
- synapse/ui/topbar/components/usage.py +24 -0
- synapse/ui/topbar/components/workspace.py +32 -0
- synapse/ui/topbar/context.py +32 -0
- synapse/ui/topbar/core.py +979 -0
- synapse/ui/topbar/git_changes_popover.py +178 -0
- synapse/ui/topbar/git_chrome.py +475 -0
- synapse/ui/topbar/tool_output_popover.py +84 -0
- synapse/ui/topbar/widget.py +474 -0
- synapse/ui/tui.py +5717 -0
- synapse/ui/turn_rail.py +71 -0
- synapse/ui/user_turn.py +83 -0
- synapse/ui/welcome.py +261 -0
- synapse_cli_agent-0.1.13.dist-info/METADATA +412 -0
- synapse_cli_agent-0.1.13.dist-info/RECORD +131 -0
- synapse_cli_agent-0.1.13.dist-info/WHEEL +4 -0
- synapse_cli_agent-0.1.13.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Reversible tool-output transformation domain."""
|
|
2
|
+
|
|
3
|
+
from synapse.tool_output.detection import detect_content_type
|
|
4
|
+
from synapse.tool_output.metrics import clear_metrics_notifier, set_metrics_notifier
|
|
5
|
+
from synapse.tool_output.models import (
|
|
6
|
+
CompressionStageEvent,
|
|
7
|
+
ContentType,
|
|
8
|
+
Detection,
|
|
9
|
+
ModelRequestCompressionEvent,
|
|
10
|
+
ToolOutputRecord,
|
|
11
|
+
TransformContext,
|
|
12
|
+
TransformEvent,
|
|
13
|
+
TransformResult,
|
|
14
|
+
)
|
|
15
|
+
from synapse.tool_output.pipeline import ToolOutputTransformPipeline
|
|
16
|
+
from synapse.tool_output.repository import ToolOutputRepository, content_to_text
|
|
17
|
+
from synapse.tool_output.transformers import (
|
|
18
|
+
CodeTransformer,
|
|
19
|
+
DiffTransformer,
|
|
20
|
+
GenericTransformer,
|
|
21
|
+
GitSummaryTransformer,
|
|
22
|
+
JsonTransformer,
|
|
23
|
+
LogTransformer,
|
|
24
|
+
NativeTransformer,
|
|
25
|
+
PathListTransformer,
|
|
26
|
+
SearchTransformer,
|
|
27
|
+
ToolOutputTransformer,
|
|
28
|
+
load_native_transformers,
|
|
29
|
+
load_transformer_plugins,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"CodeTransformer",
|
|
34
|
+
"CompressionStageEvent",
|
|
35
|
+
"ContentType",
|
|
36
|
+
"Detection",
|
|
37
|
+
"DiffTransformer",
|
|
38
|
+
"GenericTransformer",
|
|
39
|
+
"GitSummaryTransformer",
|
|
40
|
+
"JsonTransformer",
|
|
41
|
+
"LogTransformer",
|
|
42
|
+
"ModelRequestCompressionEvent",
|
|
43
|
+
"NativeTransformer",
|
|
44
|
+
"PathListTransformer",
|
|
45
|
+
"SearchTransformer",
|
|
46
|
+
"ToolOutputRecord",
|
|
47
|
+
"ToolOutputRepository",
|
|
48
|
+
"ToolOutputTransformer",
|
|
49
|
+
"ToolOutputTransformPipeline",
|
|
50
|
+
"TransformContext",
|
|
51
|
+
"TransformEvent",
|
|
52
|
+
"TransformResult",
|
|
53
|
+
"clear_metrics_notifier",
|
|
54
|
+
"content_to_text",
|
|
55
|
+
"detect_content_type",
|
|
56
|
+
"load_native_transformers",
|
|
57
|
+
"load_transformer_plugins",
|
|
58
|
+
"set_metrics_notifier",
|
|
59
|
+
]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Content classification rules for tool-output transformation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from synapse.tool_output.models import ContentType, Detection
|
|
9
|
+
|
|
10
|
+
_SEARCH_LINE = re.compile(
|
|
11
|
+
r"^(?P<path>.+?)(?P<separator>[:\-])(?P<line>\d+)(?P=separator)(?P<body>.*)$"
|
|
12
|
+
)
|
|
13
|
+
_ERROR_LINE = re.compile(
|
|
14
|
+
r"\b(error|fatal|failed|failure|exception|traceback|critical)\b", re.I
|
|
15
|
+
)
|
|
16
|
+
_LOG_SUMMARY = re.compile(r"\b(passed|failed|skipped|collected|tests? run|exit code)\b", re.I)
|
|
17
|
+
_NUMBERED_SOURCE_LINE = re.compile(r"^(?P<indent>\s*)\d+(?:\.\d+)?\t(?P<body>.*)$")
|
|
18
|
+
_PATH_ROW = re.compile(r"^(?:(?:[A-Za-z]:)?[/\\]|\.{0,2}[/\\])?(?:[^/\\\s:]+[/\\])+[^/\\\s:]+$")
|
|
19
|
+
_CODE_SUFFIXES = frozenset(
|
|
20
|
+
{
|
|
21
|
+
".c", ".cc", ".cpp", ".cs", ".go", ".h", ".hpp", ".java", ".js", ".jsx",
|
|
22
|
+
".kt", ".php", ".py", ".pyi", ".rb", ".rs", ".scala", ".sh", ".swift", ".ts",
|
|
23
|
+
".tsx",
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def _strip_numbered_source_lines(content: str) -> tuple[str, int, int]:
|
|
28
|
+
"""Return a detection-only view without read_file's cat-n line prefixes."""
|
|
29
|
+
normalized: list[str] = []
|
|
30
|
+
numbered = 0
|
|
31
|
+
non_empty = 0
|
|
32
|
+
for line in content.splitlines():
|
|
33
|
+
if line.strip():
|
|
34
|
+
non_empty += 1
|
|
35
|
+
match = _NUMBERED_SOURCE_LINE.match(line)
|
|
36
|
+
if match:
|
|
37
|
+
numbered += 1
|
|
38
|
+
normalized.append(match.group("indent") + match.group("body"))
|
|
39
|
+
else:
|
|
40
|
+
normalized.append(line)
|
|
41
|
+
return "\n".join(normalized), numbered, non_empty
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _code_marker_count(content: str) -> int:
|
|
45
|
+
return sum(
|
|
46
|
+
bool(
|
|
47
|
+
re.match(
|
|
48
|
+
r"^\s*(?:async\s+def|def|class|function|func|fn|import|from|use)\b",
|
|
49
|
+
line,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
for line in content.splitlines()[:200]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _diff_bloat_metadata(content: str) -> dict[str, Any]:
|
|
57
|
+
total_lines = 0
|
|
58
|
+
change_lines = 0
|
|
59
|
+
context_lines = 0
|
|
60
|
+
in_hunk = False
|
|
61
|
+
for line in content.splitlines():
|
|
62
|
+
total_lines += 1
|
|
63
|
+
if line.startswith("@@"):
|
|
64
|
+
in_hunk = True
|
|
65
|
+
continue
|
|
66
|
+
if line.startswith("diff --git"):
|
|
67
|
+
in_hunk = False
|
|
68
|
+
continue
|
|
69
|
+
if not in_hunk or line.startswith(("+++", "---")):
|
|
70
|
+
continue
|
|
71
|
+
if line.startswith(("+", "-")):
|
|
72
|
+
change_lines += 1
|
|
73
|
+
elif line.startswith(" "):
|
|
74
|
+
context_lines += 1
|
|
75
|
+
denominator = context_lines + change_lines
|
|
76
|
+
context_ratio = context_lines / denominator if denominator else 0.0
|
|
77
|
+
normal_context_ratio = 0.6
|
|
78
|
+
bloat_score = (
|
|
79
|
+
max(0.0, min(1.0, (context_ratio - normal_context_ratio) / (1 - normal_context_ratio)))
|
|
80
|
+
if denominator
|
|
81
|
+
else 0.0
|
|
82
|
+
)
|
|
83
|
+
return {
|
|
84
|
+
"total_lines": total_lines,
|
|
85
|
+
"change_lines": change_lines,
|
|
86
|
+
"context_lines": context_lines,
|
|
87
|
+
"context_ratio": round(context_ratio, 4),
|
|
88
|
+
"bloat_score": round(bloat_score, 4),
|
|
89
|
+
"dense_diff": bool(total_lines >= 50 and context_ratio <= normal_context_ratio),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def detect_content_type(content: str) -> Detection:
|
|
94
|
+
lines = content.splitlines()
|
|
95
|
+
if not lines:
|
|
96
|
+
return Detection(ContentType.TEXT, 1.0)
|
|
97
|
+
search_count = sum(bool(_SEARCH_LINE.match(line)) for line in lines[:100])
|
|
98
|
+
timestamp_count = sum(
|
|
99
|
+
bool(re.match(r"^\d{4}-\d{2}-\d{2}(?:[ T]|$)", line)) for line in lines[:100]
|
|
100
|
+
)
|
|
101
|
+
enough_search_lines = search_count >= max(3, len(lines[:100]) // 3)
|
|
102
|
+
mostly_timestamped = timestamp_count >= max(3, len(lines[:100]) // 2)
|
|
103
|
+
if enough_search_lines and not mostly_timestamped:
|
|
104
|
+
return Detection(
|
|
105
|
+
ContentType.SEARCH, min(1.0, search_count / max(1, len(lines[:100])) + 0.3)
|
|
106
|
+
)
|
|
107
|
+
path_rows = sum(bool(_PATH_ROW.match(line)) for line in lines)
|
|
108
|
+
if len(lines) >= 3 and path_rows == len(lines):
|
|
109
|
+
return Detection(ContentType.PATHS, 1.0)
|
|
110
|
+
if any(line.startswith(("diff --git", "--- a/", "+++ b/", "@@")) for line in lines[:20]):
|
|
111
|
+
return Detection(ContentType.DIFF, 0.95)
|
|
112
|
+
git_summary_markers = sum(
|
|
113
|
+
bool(
|
|
114
|
+
re.match(
|
|
115
|
+
r"^(?:Merge made by the .+ strategy\.|(?:create|delete) mode \d+ |"
|
|
116
|
+
r"rename .+ => .+|\s*\d+ files? changed(?:,|$)|\s*\d+ insertions?\(\+\)|"
|
|
117
|
+
r"\s*\d+ deletions?\(-\)| .+\s+\|\s+\d+\s+[+\-]+$)",
|
|
118
|
+
line,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
for line in lines[:200]
|
|
122
|
+
)
|
|
123
|
+
if git_summary_markers >= 2:
|
|
124
|
+
return Detection(ContentType.GIT_SUMMARY, min(0.95, 0.45 + git_summary_markers / 20))
|
|
125
|
+
sampled = lines[:100]
|
|
126
|
+
log_markers = sum(
|
|
127
|
+
bool(_ERROR_LINE.search(line) or _LOG_SUMMARY.search(line)) for line in sampled
|
|
128
|
+
)
|
|
129
|
+
timestamped = timestamp_count
|
|
130
|
+
if log_markers >= 3 or (
|
|
131
|
+
timestamped >= max(3, len(sampled) // 2) and any(_ERROR_LINE.search(line) for line in lines)
|
|
132
|
+
):
|
|
133
|
+
return Detection(ContentType.LOG, 0.8)
|
|
134
|
+
code_markers = _code_marker_count(content)
|
|
135
|
+
if code_markers >= 3:
|
|
136
|
+
return Detection(ContentType.CODE, min(0.95, 0.4 + code_markers / max(1, len(lines[:200]))))
|
|
137
|
+
try:
|
|
138
|
+
parsed = json.loads(content)
|
|
139
|
+
if isinstance(parsed, (list, dict)):
|
|
140
|
+
return Detection(ContentType.JSON, 0.9)
|
|
141
|
+
except (ValueError, TypeError):
|
|
142
|
+
pass
|
|
143
|
+
return Detection(ContentType.TEXT, 0.3)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _critical_lines(content: str, content_type: ContentType) -> list[str]:
|
|
147
|
+
lines = content.splitlines()
|
|
148
|
+
if content_type is ContentType.LOG:
|
|
149
|
+
return [
|
|
150
|
+
line
|
|
151
|
+
for line in lines
|
|
152
|
+
if _ERROR_LINE.search(line) or re.search(r"\b[A-Za-z_]+(?:Error|Exception):", line)
|
|
153
|
+
]
|
|
154
|
+
if content_type is ContentType.DIFF:
|
|
155
|
+
return [
|
|
156
|
+
line
|
|
157
|
+
for line in lines
|
|
158
|
+
if line.startswith(("+", "-")) and not line.startswith(("+++", "---"))
|
|
159
|
+
]
|
|
160
|
+
if content_type is ContentType.GIT_SUMMARY:
|
|
161
|
+
return [
|
|
162
|
+
line
|
|
163
|
+
for line in lines
|
|
164
|
+
if re.match(
|
|
165
|
+
r"^(?:Merge made by the .+ strategy\.|\s*\d+ files? changed(?:,|$)|"
|
|
166
|
+
r"\s*\d+ insertions?\(\+\)|\s*\d+ deletions?\(-\))",
|
|
167
|
+
line,
|
|
168
|
+
)
|
|
169
|
+
]
|
|
170
|
+
return []
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Best-effort process-local notifications for tool-output metrics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
_metrics_notifier: Callable[[str], None] | None = None
|
|
9
|
+
_metrics_notifier_lock = threading.RLock()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def set_metrics_notifier(notifier: Callable[[str], None] | None) -> None:
|
|
13
|
+
"""Install a callback invoked after metrics-changing persistence writes."""
|
|
14
|
+
global _metrics_notifier
|
|
15
|
+
with _metrics_notifier_lock:
|
|
16
|
+
_metrics_notifier = notifier
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def clear_metrics_notifier() -> None:
|
|
20
|
+
"""Remove the active process-local metrics callback."""
|
|
21
|
+
set_metrics_notifier(None)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def notify_metrics_changed(thread_id: str) -> None:
|
|
25
|
+
"""Notify an observer without allowing UI failures to affect tool execution."""
|
|
26
|
+
with _metrics_notifier_lock:
|
|
27
|
+
notifier = _metrics_notifier
|
|
28
|
+
if notifier is not None:
|
|
29
|
+
try:
|
|
30
|
+
notifier(thread_id)
|
|
31
|
+
except Exception: # noqa: BLE001
|
|
32
|
+
pass
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Data models for reversible tool-output transformation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import asdict, dataclass, field
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ContentType(StrEnum):
|
|
10
|
+
SEARCH = "search"
|
|
11
|
+
PATHS = "paths"
|
|
12
|
+
LOG = "log"
|
|
13
|
+
DIFF = "diff"
|
|
14
|
+
GIT_SUMMARY = "git-summary"
|
|
15
|
+
JSON = "json"
|
|
16
|
+
CODE = "code"
|
|
17
|
+
TEXT = "text"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Detection:
|
|
22
|
+
content_type: ContentType
|
|
23
|
+
confidence: float
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class TransformContext:
|
|
28
|
+
tool_name: str
|
|
29
|
+
status: str
|
|
30
|
+
query: str = ""
|
|
31
|
+
tool_args: dict[str, Any] = field(default_factory=dict)
|
|
32
|
+
file_path: str = ""
|
|
33
|
+
file_suffix: str = ""
|
|
34
|
+
fresh_read_source: bool = True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class CompressionStageEvent:
|
|
39
|
+
"""One observable stage in a tool-output compression decision."""
|
|
40
|
+
|
|
41
|
+
phase: str
|
|
42
|
+
algorithm: str
|
|
43
|
+
applied: bool
|
|
44
|
+
reason_code: str
|
|
45
|
+
input_bytes: int = 0
|
|
46
|
+
output_bytes: int = 0
|
|
47
|
+
input_tokens: int = 0
|
|
48
|
+
output_tokens: int = 0
|
|
49
|
+
duration_ms: float = 0.0
|
|
50
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
|
|
52
|
+
def as_dict(self) -> dict[str, Any]:
|
|
53
|
+
return asdict(self)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class TransformResult:
|
|
58
|
+
content: str
|
|
59
|
+
transformer: str
|
|
60
|
+
content_type: ContentType
|
|
61
|
+
critical_total: int
|
|
62
|
+
critical_retained: int
|
|
63
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
64
|
+
stages: tuple[CompressionStageEvent, ...] = ()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class ToolOutputRecord:
|
|
69
|
+
ref: str
|
|
70
|
+
thread_id: str
|
|
71
|
+
checkpoint_ns: str
|
|
72
|
+
tool_call_id: str
|
|
73
|
+
tool_name: str
|
|
74
|
+
status: str
|
|
75
|
+
content: str
|
|
76
|
+
size_bytes: int
|
|
77
|
+
sha256: str
|
|
78
|
+
created_at: str
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class ModelRequestCompressionEvent:
|
|
83
|
+
"""One model call's compression and provider-safety accounting."""
|
|
84
|
+
|
|
85
|
+
request_id: str
|
|
86
|
+
provider: str
|
|
87
|
+
api_style: str
|
|
88
|
+
auth_mode: str
|
|
89
|
+
model: str
|
|
90
|
+
input_tokens_before: int
|
|
91
|
+
input_tokens_after: int
|
|
92
|
+
provider_input_tokens: int = 0
|
|
93
|
+
cache_read_tokens: int = 0
|
|
94
|
+
cache_write_tokens: int = 0
|
|
95
|
+
uncached_input_tokens: int = 0
|
|
96
|
+
output_tokens: int = 0
|
|
97
|
+
tool_output_saved_tokens: int = 0
|
|
98
|
+
prompt_saved_tokens: int = 0
|
|
99
|
+
summarization_saved_tokens: int = 0
|
|
100
|
+
total_saved_tokens: int = 0
|
|
101
|
+
candidate_blocks: int = 0
|
|
102
|
+
transformed_blocks: int = 0
|
|
103
|
+
protected_tokens_by_reason: dict[str, int] = field(default_factory=dict)
|
|
104
|
+
content_breakdown: dict[str, int] = field(default_factory=dict)
|
|
105
|
+
opportunity_tokens_by_reason: dict[str, int] = field(default_factory=dict)
|
|
106
|
+
turn_id: str = ""
|
|
107
|
+
turn_index: int = 0
|
|
108
|
+
model_call_index: int = 0
|
|
109
|
+
live_zone_plan: list[dict[str, Any]] = field(default_factory=list)
|
|
110
|
+
live_zone_tokens: dict[str, int] = field(default_factory=dict)
|
|
111
|
+
wire_fingerprints: dict[str, Any] = field(default_factory=dict)
|
|
112
|
+
cache_diagnostics: dict[str, Any] = field(default_factory=dict)
|
|
113
|
+
tool_schema_profiles: list[dict[str, Any]] = field(default_factory=list)
|
|
114
|
+
token_count_method: str = "langchain_approximate"
|
|
115
|
+
duration_ms: float = 0.0
|
|
116
|
+
|
|
117
|
+
def as_dict(self) -> dict[str, Any]:
|
|
118
|
+
data = asdict(self)
|
|
119
|
+
whole_denominator = self.input_tokens_after + self.total_saved_tokens
|
|
120
|
+
new_input = self.uncached_input_tokens + self.cache_write_tokens
|
|
121
|
+
data["whole_request_savings_ratio"] = (
|
|
122
|
+
round(self.total_saved_tokens / whole_denominator, 4) if whole_denominator else 0.0
|
|
123
|
+
)
|
|
124
|
+
data["new_input_savings_ratio"] = (
|
|
125
|
+
round(self.total_saved_tokens / (new_input + self.total_saved_tokens), 4)
|
|
126
|
+
if new_input > 0
|
|
127
|
+
else 0.0
|
|
128
|
+
)
|
|
129
|
+
return data
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True)
|
|
133
|
+
class TransformEvent:
|
|
134
|
+
content_type: str
|
|
135
|
+
transformer: str
|
|
136
|
+
outcome: str
|
|
137
|
+
original_bytes: int
|
|
138
|
+
visible_bytes: int
|
|
139
|
+
duration_ms: float
|
|
140
|
+
critical_total: int
|
|
141
|
+
critical_retained: int
|
|
142
|
+
ref_created: bool
|
|
143
|
+
execution_path: str = "python_only"
|
|
144
|
+
estimated_original_tokens: int = 0
|
|
145
|
+
estimated_visible_tokens: int = 0
|
|
146
|
+
decision: str = "transformed"
|
|
147
|
+
reason_code: str = "compressed"
|
|
148
|
+
reason_detail: str = ""
|
|
149
|
+
eligible: bool = True
|
|
150
|
+
source_kind: str = "tool-output"
|
|
151
|
+
detection_confidence: float = 0.0
|
|
152
|
+
threshold_bytes: int = 0
|
|
153
|
+
tool_call_id: str = ""
|
|
154
|
+
tool_name: str = ""
|
|
155
|
+
status: str = "success"
|
|
156
|
+
checkpoint_ns: str = ""
|
|
157
|
+
message_id: str = ""
|
|
158
|
+
algorithm_output_bytes: int = 0
|
|
159
|
+
algorithm_output_tokens: int = 0
|
|
160
|
+
token_count_method: str = "langchain_approximate"
|
|
161
|
+
content_sha256: str = ""
|
|
162
|
+
stages: tuple[CompressionStageEvent, ...] = ()
|
|
163
|
+
|
|
164
|
+
def as_dict(self) -> dict[str, Any]:
|
|
165
|
+
data = asdict(self)
|
|
166
|
+
data["saved_bytes"] = max(0, self.original_bytes - self.visible_bytes)
|
|
167
|
+
data["estimated_saved_tokens"] = max(
|
|
168
|
+
0, self.estimated_original_tokens - self.estimated_visible_tokens
|
|
169
|
+
)
|
|
170
|
+
data["savings_ratio"] = (
|
|
171
|
+
round(1 - self.visible_bytes / self.original_bytes, 4) if self.original_bytes else 0.0
|
|
172
|
+
)
|
|
173
|
+
return data
|