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 @@
|
|
|
1
|
+
"""Interactive command parsing and dispatch."""
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
"""Compression diagnostics slash-command handler."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import csv
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from synapse.commands.helpers import format_bytes, markdown_escape
|
|
12
|
+
from synapse.commands.result import SlashResult
|
|
13
|
+
from synapse.sessions.store import SessionStore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _store(settings: Any) -> SessionStore:
|
|
17
|
+
return SessionStore(settings.resolved_sessions_path())
|
|
18
|
+
|
|
19
|
+
def _resolve_session_ref(
|
|
20
|
+
settings: Any, current_thread_id: str, args: list[str]
|
|
21
|
+
) -> tuple[str | None, str | None]:
|
|
22
|
+
"""Resolve an optional thread id/title argument to a unique session id."""
|
|
23
|
+
if not args:
|
|
24
|
+
return current_thread_id, None
|
|
25
|
+
query = " ".join(args).strip()
|
|
26
|
+
info = _store(settings).resolve_session_ref(query)
|
|
27
|
+
if info is None:
|
|
28
|
+
return None, f"session not found or ambiguous: {query}"
|
|
29
|
+
return info.thread_id, None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_COMPRESSION_EXPORT_FORMAT_ALIASES = {
|
|
33
|
+
"json": "json",
|
|
34
|
+
"j": "json",
|
|
35
|
+
"csv": "csv",
|
|
36
|
+
"c": "csv",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _default_compression_export_path(settings: Any, thread_id: str, fmt: str) -> Path:
|
|
41
|
+
safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in (thread_id or "session"))
|
|
42
|
+
safe = (safe or "session")[:80]
|
|
43
|
+
parent = Path(settings.checkpoint_path).expanduser().resolve().parent
|
|
44
|
+
return parent / "exports" / f"{safe}.compression.{fmt}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _compression_export_csv(payload: dict[str, Any]) -> str:
|
|
48
|
+
"""Flatten heterogeneous diagnostics into one portable CSV table."""
|
|
49
|
+
rows: list[dict[str, Any]] = [
|
|
50
|
+
{
|
|
51
|
+
"record_type": "metadata",
|
|
52
|
+
"thread_id": payload["thread_id"],
|
|
53
|
+
"metric": "schema_version",
|
|
54
|
+
"value": payload["schema_version"],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"record_type": "metadata",
|
|
58
|
+
"thread_id": payload["thread_id"],
|
|
59
|
+
"metric": "exported_at",
|
|
60
|
+
"value": payload["exported_at"],
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
rows.extend(
|
|
64
|
+
{
|
|
65
|
+
"record_type": "summary",
|
|
66
|
+
"thread_id": payload["thread_id"],
|
|
67
|
+
"metric": key,
|
|
68
|
+
"value": value,
|
|
69
|
+
}
|
|
70
|
+
for key, value in sorted(dict(payload.get("summary") or {}).items())
|
|
71
|
+
)
|
|
72
|
+
for record_type, key in (
|
|
73
|
+
("model_request", "model_request_events"),
|
|
74
|
+
("interaction", "interaction_events"),
|
|
75
|
+
("tool_output", "tool_output_events"),
|
|
76
|
+
("retrieval", "retrieval_events"),
|
|
77
|
+
("model_reuse", "model_reuse_events"),
|
|
78
|
+
):
|
|
79
|
+
rows.extend(
|
|
80
|
+
{"record_type": record_type, **dict(item)} for item in payload.get(key, [])
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
preferred = [
|
|
84
|
+
"record_type",
|
|
85
|
+
"thread_id",
|
|
86
|
+
"id",
|
|
87
|
+
"created_at",
|
|
88
|
+
"metric",
|
|
89
|
+
"value",
|
|
90
|
+
"request_id",
|
|
91
|
+
"provider",
|
|
92
|
+
"api_style",
|
|
93
|
+
"auth_mode",
|
|
94
|
+
"model",
|
|
95
|
+
"tool_call_id",
|
|
96
|
+
"tool_name",
|
|
97
|
+
"decision",
|
|
98
|
+
"reason_code",
|
|
99
|
+
]
|
|
100
|
+
all_fields = {key for row in rows for key in row}
|
|
101
|
+
fieldnames = [key for key in preferred if key in all_fields]
|
|
102
|
+
fieldnames.extend(sorted(all_fields - set(fieldnames)))
|
|
103
|
+
|
|
104
|
+
def cell(value: Any) -> Any:
|
|
105
|
+
if isinstance(value, dict | list | tuple):
|
|
106
|
+
return json.dumps(value, ensure_ascii=False, sort_keys=True, default=str)
|
|
107
|
+
return "" if value is None else value
|
|
108
|
+
|
|
109
|
+
output = io.StringIO(newline="")
|
|
110
|
+
writer = csv.DictWriter(output, fieldnames=fieldnames, lineterminator="\n")
|
|
111
|
+
writer.writeheader()
|
|
112
|
+
writer.writerows({key: cell(value) for key, value in row.items()} for row in rows)
|
|
113
|
+
return output.getvalue()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _compression_export_result(
|
|
117
|
+
settings: Any, current_thread_id: str, args: list[str]
|
|
118
|
+
) -> SlashResult:
|
|
119
|
+
"""Export complete compression diagnostics for one session to JSON or CSV."""
|
|
120
|
+
fmt = "json"
|
|
121
|
+
session_args: list[str] = []
|
|
122
|
+
path_args: list[str] = []
|
|
123
|
+
format_index = next(
|
|
124
|
+
(
|
|
125
|
+
index
|
|
126
|
+
for index, value in enumerate(args)
|
|
127
|
+
if value.casefold() in _COMPRESSION_EXPORT_FORMAT_ALIASES
|
|
128
|
+
),
|
|
129
|
+
None,
|
|
130
|
+
)
|
|
131
|
+
if format_index is not None:
|
|
132
|
+
fmt = _COMPRESSION_EXPORT_FORMAT_ALIASES[args[format_index].casefold()]
|
|
133
|
+
session_args = args[:format_index]
|
|
134
|
+
path_args = args[format_index + 1 :]
|
|
135
|
+
elif args:
|
|
136
|
+
candidate = Path(" ".join(args)).expanduser()
|
|
137
|
+
if candidate.suffix.casefold() in {".json", ".csv"}:
|
|
138
|
+
fmt = candidate.suffix.casefold().lstrip(".")
|
|
139
|
+
path_args = args
|
|
140
|
+
else:
|
|
141
|
+
session_args = args
|
|
142
|
+
|
|
143
|
+
thread_id, error = _resolve_session_ref(settings, current_thread_id, session_args)
|
|
144
|
+
if error or thread_id is None:
|
|
145
|
+
return SlashResult(handled=True, lines=[error or "session not found"], error=True)
|
|
146
|
+
|
|
147
|
+
from synapse.tool_output.repository import ToolOutputRepository
|
|
148
|
+
|
|
149
|
+
repo = ToolOutputRepository(settings.resolved_tool_output_db_path())
|
|
150
|
+
diagnostics = repo.export_diagnostics(thread_id=thread_id)
|
|
151
|
+
payload = {
|
|
152
|
+
"schema_version": 2,
|
|
153
|
+
"exported_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
154
|
+
"thread_id": thread_id,
|
|
155
|
+
**diagnostics,
|
|
156
|
+
}
|
|
157
|
+
text = (
|
|
158
|
+
json.dumps(payload, ensure_ascii=False, indent=2, default=str)
|
|
159
|
+
if fmt == "json"
|
|
160
|
+
else _compression_export_csv(payload)
|
|
161
|
+
)
|
|
162
|
+
target = (
|
|
163
|
+
Path(" ".join(path_args)).expanduser()
|
|
164
|
+
if path_args
|
|
165
|
+
else _default_compression_export_path(settings, thread_id, fmt)
|
|
166
|
+
)
|
|
167
|
+
try:
|
|
168
|
+
target = (Path.cwd() / target).resolve() if not target.is_absolute() else target.resolve()
|
|
169
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
target.write_text(text, encoding="utf-8")
|
|
171
|
+
except OSError as exc:
|
|
172
|
+
return SlashResult(
|
|
173
|
+
handled=True,
|
|
174
|
+
lines=[f"compression export failed: {exc}"],
|
|
175
|
+
error=True,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
counts = {
|
|
179
|
+
"model requests": len(payload["model_request_events"]),
|
|
180
|
+
"tool calls": len(payload["interaction_events"]),
|
|
181
|
+
"tool outputs": len(payload["tool_output_events"]),
|
|
182
|
+
"retrievals": len(payload["retrieval_events"]),
|
|
183
|
+
"model reuses": len(payload["model_reuse_events"]),
|
|
184
|
+
}
|
|
185
|
+
confirm = f"exported compression {fmt} -> {target}"
|
|
186
|
+
return SlashResult(
|
|
187
|
+
handled=True,
|
|
188
|
+
lines=[
|
|
189
|
+
confirm,
|
|
190
|
+
f"thread_id={thread_id}",
|
|
191
|
+
", ".join(f"{key}={value}" for key, value in counts.items()),
|
|
192
|
+
],
|
|
193
|
+
notice=confirm,
|
|
194
|
+
markdown=(
|
|
195
|
+
"## Compression Export\n\n"
|
|
196
|
+
f"- **thread**: `{thread_id}`\n"
|
|
197
|
+
f"- **format**: {fmt}\n"
|
|
198
|
+
f"- **path**: `{target}`\n"
|
|
199
|
+
+ "".join(f"- **{key}**: {value}\n" for key, value in counts.items())
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def handle_compression(settings: Any, current_thread_id: str, args: list[str]) -> SlashResult:
|
|
205
|
+
"""Render persistent compression diagnostics or recent decision events."""
|
|
206
|
+
mode = args[0].casefold() if args else "stats"
|
|
207
|
+
if mode == "export":
|
|
208
|
+
return _compression_export_result(settings, current_thread_id, args[1:])
|
|
209
|
+
show_profile = mode in {"profile", "report"}
|
|
210
|
+
show_requests = mode in {"requests", "request"}
|
|
211
|
+
show_events = mode in {"events", "skipped", "fallback", "tool"}
|
|
212
|
+
rest = args[1:] if show_events or show_requests or show_profile else args
|
|
213
|
+
decision_filter = mode if mode in {"skipped", "fallback"} else ""
|
|
214
|
+
tool_filter = ""
|
|
215
|
+
request_filter = ""
|
|
216
|
+
if mode == "request":
|
|
217
|
+
if not rest:
|
|
218
|
+
return SlashResult(
|
|
219
|
+
handled=True,
|
|
220
|
+
lines=["usage: /compression request <request_id> [session]"],
|
|
221
|
+
error=True,
|
|
222
|
+
)
|
|
223
|
+
request_filter = rest[0]
|
|
224
|
+
rest = rest[1:]
|
|
225
|
+
if mode == "tool":
|
|
226
|
+
if not rest:
|
|
227
|
+
return SlashResult(
|
|
228
|
+
handled=True,
|
|
229
|
+
lines=["usage: /compression tool <tool_call_id> [session] [limit]"],
|
|
230
|
+
error=True,
|
|
231
|
+
)
|
|
232
|
+
tool_filter = rest[0]
|
|
233
|
+
rest = rest[1:]
|
|
234
|
+
limit = 10
|
|
235
|
+
if (show_events or show_requests) and rest and rest[-1].isdigit():
|
|
236
|
+
limit = max(1, min(50, int(rest[-1])))
|
|
237
|
+
rest = rest[:-1]
|
|
238
|
+
thread_id, error = _resolve_session_ref(settings, current_thread_id, rest)
|
|
239
|
+
if error or thread_id is None:
|
|
240
|
+
return SlashResult(handled=True, lines=[error or "session not found"], error=True)
|
|
241
|
+
|
|
242
|
+
from synapse.tool_output.repository import ToolOutputRepository
|
|
243
|
+
|
|
244
|
+
repo = ToolOutputRepository(settings.resolved_tool_output_db_path())
|
|
245
|
+
if show_profile:
|
|
246
|
+
stats = repo.stats(thread_id=thread_id)
|
|
247
|
+
breakdown = stats.get("content_breakdown") or {}
|
|
248
|
+
opportunities = stats.get("top_opportunities") or []
|
|
249
|
+
md = [
|
|
250
|
+
"## Compression Profile",
|
|
251
|
+
"",
|
|
252
|
+
f"Thread: `{thread_id}`",
|
|
253
|
+
"",
|
|
254
|
+
(
|
|
255
|
+
f"Turns: {stats.get('turns', 0)} — model calls: "
|
|
256
|
+
f"{stats.get('model_requests', 0)} — tool calls: "
|
|
257
|
+
f"{stats.get('tool_calls', 0)} — compression-managed: "
|
|
258
|
+
f"{stats.get('compression_managed_tool_calls', 0)}"
|
|
259
|
+
),
|
|
260
|
+
(
|
|
261
|
+
f"Cache bust suspected: {stats.get('cache_bust_suspected_requests', 0)} requests"
|
|
262
|
+
),
|
|
263
|
+
"",
|
|
264
|
+
"### Live-zone distribution",
|
|
265
|
+
"",
|
|
266
|
+
"| Zone | Estimated tokens |",
|
|
267
|
+
"|---|---:|",
|
|
268
|
+
*[
|
|
269
|
+
f"| {markdown_escape(str(zone))} | ~{int(tokens or 0)} |"
|
|
270
|
+
for zone, tokens in sorted((stats.get("live_zone_tokens") or {}).items())
|
|
271
|
+
],
|
|
272
|
+
"",
|
|
273
|
+
"### Tool schema ranking",
|
|
274
|
+
"",
|
|
275
|
+
"| Tool | Cumulative estimated tokens |",
|
|
276
|
+
"|---|---:|",
|
|
277
|
+
*[
|
|
278
|
+
f"| {markdown_escape(str(name))} | ~{int(tokens or 0)} |"
|
|
279
|
+
for name, tokens in stats.get("top_schema_tools") or []
|
|
280
|
+
],
|
|
281
|
+
"",
|
|
282
|
+
"### Request content breakdown",
|
|
283
|
+
"",
|
|
284
|
+
"| Source | Estimated tokens | Share |",
|
|
285
|
+
"|---|---:|---:|",
|
|
286
|
+
]
|
|
287
|
+
# ``tool_output_original`` reconstructs the pre-compression baseline;
|
|
288
|
+
# it is not part of the final model-visible request. Excluding it keeps
|
|
289
|
+
# model-visible shares additive instead of counting tool output twice.
|
|
290
|
+
reference_sources = {"tool_output_original"}
|
|
291
|
+
total = sum(
|
|
292
|
+
max(0, int(value or 0))
|
|
293
|
+
for source, value in breakdown.items()
|
|
294
|
+
if source not in reference_sources
|
|
295
|
+
)
|
|
296
|
+
lines = [f"thread_id={thread_id}", f"profile_total_tokens=~{total}"]
|
|
297
|
+
for source, tokens in sorted(
|
|
298
|
+
breakdown.items(), key=lambda item: int(item[1] or 0), reverse=True
|
|
299
|
+
):
|
|
300
|
+
amount = max(0, int(tokens or 0))
|
|
301
|
+
if source in reference_sources:
|
|
302
|
+
md.append(f"| {markdown_escape(str(source))} | ~{amount} | — |")
|
|
303
|
+
lines.append(f"{source}=~{amount} (reference; excluded from total)")
|
|
304
|
+
continue
|
|
305
|
+
share = amount / total if total else 0.0
|
|
306
|
+
md.append(f"| {markdown_escape(str(source))} | ~{amount} | {share:.1%} |")
|
|
307
|
+
lines.append(f"{source}=~{amount} ({share:.1%})")
|
|
308
|
+
if any(source in breakdown for source in reference_sources):
|
|
309
|
+
md.extend(
|
|
310
|
+
[
|
|
311
|
+
"",
|
|
312
|
+
(
|
|
313
|
+
"`tool_output_original` is the reconstructed pre-compression "
|
|
314
|
+
"baseline and is excluded from model-visible totals and shares."
|
|
315
|
+
),
|
|
316
|
+
]
|
|
317
|
+
)
|
|
318
|
+
md.extend(
|
|
319
|
+
[
|
|
320
|
+
"",
|
|
321
|
+
"### Ranked optimization opportunities",
|
|
322
|
+
"",
|
|
323
|
+
"| Rank | Reason | Estimated tokens |",
|
|
324
|
+
"|---:|---|---:|",
|
|
325
|
+
]
|
|
326
|
+
)
|
|
327
|
+
if opportunities:
|
|
328
|
+
for rank, item in enumerate(opportunities, 1):
|
|
329
|
+
reason, tokens = item
|
|
330
|
+
md.append(f"| {rank} | {markdown_escape(str(reason))} | ~{int(tokens or 0)} |")
|
|
331
|
+
else:
|
|
332
|
+
md.append("| - | No request profile events yet | 0 |")
|
|
333
|
+
protected = stats.get("top_protected_sources") or []
|
|
334
|
+
md.extend(
|
|
335
|
+
[
|
|
336
|
+
"",
|
|
337
|
+
"### Provider-protected context",
|
|
338
|
+
"",
|
|
339
|
+
"| Reason | Estimated tokens |",
|
|
340
|
+
"|---|---:|",
|
|
341
|
+
]
|
|
342
|
+
)
|
|
343
|
+
if protected:
|
|
344
|
+
for reason, tokens in protected:
|
|
345
|
+
md.append(f"| {markdown_escape(str(reason))} | ~{int(tokens or 0)} |")
|
|
346
|
+
else:
|
|
347
|
+
md.append("| - | 0 |")
|
|
348
|
+
return SlashResult(handled=True, lines=lines, markdown="\n".join(md))
|
|
349
|
+
if show_requests:
|
|
350
|
+
requests = repo.model_request_events(thread_id=thread_id, limit=max(limit, 50))
|
|
351
|
+
if request_filter:
|
|
352
|
+
requests = [item for item in requests if item.get("request_id") == request_filter]
|
|
353
|
+
requests = requests[:limit]
|
|
354
|
+
if not requests:
|
|
355
|
+
return SlashResult(
|
|
356
|
+
handled=True,
|
|
357
|
+
lines=[f"thread_id={thread_id}", "no model request compression events"],
|
|
358
|
+
)
|
|
359
|
+
md = [
|
|
360
|
+
"## Model Request Compression",
|
|
361
|
+
"",
|
|
362
|
+
f"Thread: `{thread_id}`",
|
|
363
|
+
"",
|
|
364
|
+
(
|
|
365
|
+
"| Time | Request | Provider / API | Input before | Input after | "
|
|
366
|
+
"Saved | Cache | Output |"
|
|
367
|
+
),
|
|
368
|
+
"|---|---|---|---:|---:|---:|---:|---:|",
|
|
369
|
+
]
|
|
370
|
+
lines = [f"thread_id={thread_id}"]
|
|
371
|
+
for item in requests:
|
|
372
|
+
request_id = str(item.get("request_id") or "-")
|
|
373
|
+
before = int(item.get("input_tokens_before", 0) or 0)
|
|
374
|
+
after = int(item.get("input_tokens_after", 0) or 0)
|
|
375
|
+
saved = int(item.get("total_saved_tokens", 0) or 0)
|
|
376
|
+
cache = int(item.get("cache_read_tokens", 0) or 0)
|
|
377
|
+
output = int(item.get("output_tokens", 0) or 0)
|
|
378
|
+
md.append(
|
|
379
|
+
"| {time} | `{request}` | {provider}/{api} | ~{before} | ~{after} | "
|
|
380
|
+
"~{saved} | {cache} | {output} |".format(
|
|
381
|
+
time=markdown_escape(str(item.get("created_at") or "-")),
|
|
382
|
+
request=markdown_escape(request_id),
|
|
383
|
+
provider=markdown_escape(str(item.get("provider") or "unknown")),
|
|
384
|
+
api=markdown_escape(str(item.get("api_style") or "unknown")),
|
|
385
|
+
before=before,
|
|
386
|
+
after=after,
|
|
387
|
+
saved=saved,
|
|
388
|
+
cache=cache,
|
|
389
|
+
output=output,
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
protected = item.get("protected_tokens_by_reason") or {}
|
|
393
|
+
breakdown = item.get("content_breakdown") or {}
|
|
394
|
+
opportunities = item.get("opportunity_tokens_by_reason") or {}
|
|
395
|
+
lines.append(
|
|
396
|
+
f"{request_id} turn={item.get('turn_index', 0)} "
|
|
397
|
+
f"call={item.get('model_call_index', 0)} before=~{before} after=~{after} "
|
|
398
|
+
f"saved=~{saved} protected={protected} breakdown={breakdown} "
|
|
399
|
+
f"opportunities={opportunities} live_zone={item.get('live_zone_tokens') or {}} "
|
|
400
|
+
f"cache={item.get('cache_diagnostics') or {}}"
|
|
401
|
+
)
|
|
402
|
+
if request_filter:
|
|
403
|
+
md.extend(
|
|
404
|
+
[
|
|
405
|
+
"",
|
|
406
|
+
"### Content breakdown",
|
|
407
|
+
"",
|
|
408
|
+
"| Source | Estimated tokens |",
|
|
409
|
+
"|---|---:|",
|
|
410
|
+
*[
|
|
411
|
+
f"| {markdown_escape(str(key))} | ~{int(value or 0)} |"
|
|
412
|
+
for key, value in sorted(
|
|
413
|
+
breakdown.items(),
|
|
414
|
+
key=lambda pair: int(pair[1] or 0),
|
|
415
|
+
reverse=True,
|
|
416
|
+
)
|
|
417
|
+
],
|
|
418
|
+
"",
|
|
419
|
+
"### Optimization opportunities",
|
|
420
|
+
"",
|
|
421
|
+
"| Reason | Estimated tokens |",
|
|
422
|
+
"|---|---:|",
|
|
423
|
+
*[
|
|
424
|
+
f"| {markdown_escape(str(key))} | ~{int(value or 0)} |"
|
|
425
|
+
for key, value in sorted(
|
|
426
|
+
opportunities.items(),
|
|
427
|
+
key=lambda pair: int(pair[1] or 0),
|
|
428
|
+
reverse=True,
|
|
429
|
+
)
|
|
430
|
+
],
|
|
431
|
+
]
|
|
432
|
+
)
|
|
433
|
+
return SlashResult(handled=True, lines=lines, markdown="\n".join(md))
|
|
434
|
+
if show_events:
|
|
435
|
+
fetch_limit = min(500, max(limit, 50) if decision_filter or tool_filter else limit)
|
|
436
|
+
events = repo.events(thread_id=thread_id, limit=fetch_limit)
|
|
437
|
+
if decision_filter:
|
|
438
|
+
events = [
|
|
439
|
+
event
|
|
440
|
+
for event in events
|
|
441
|
+
if str(event.get("decision") or "").casefold() == decision_filter
|
|
442
|
+
]
|
|
443
|
+
if tool_filter:
|
|
444
|
+
events = [
|
|
445
|
+
event
|
|
446
|
+
for event in events
|
|
447
|
+
if str(event.get("tool_call_id") or "") == tool_filter
|
|
448
|
+
]
|
|
449
|
+
events = events[:limit]
|
|
450
|
+
if not events:
|
|
451
|
+
return SlashResult(
|
|
452
|
+
handled=True,
|
|
453
|
+
lines=[f"thread_id={thread_id}", "no tool-output events"],
|
|
454
|
+
markdown=(
|
|
455
|
+
f"## Tool Output Events\n\nThread: `{thread_id}`\n\n"
|
|
456
|
+
"No tool-output events."
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
md = [
|
|
460
|
+
"## Compression Decision Events",
|
|
461
|
+
"",
|
|
462
|
+
f"Thread: `{thread_id}`",
|
|
463
|
+
"",
|
|
464
|
+
(
|
|
465
|
+
"| Time | Tool / ID | Type | Decision | Reason | Pipeline | "
|
|
466
|
+
"Original | Final | Saved tok |"
|
|
467
|
+
),
|
|
468
|
+
"|---|---|---|---|---|---|---:|---:|---:|",
|
|
469
|
+
]
|
|
470
|
+
lines = [f"thread_id={thread_id}"]
|
|
471
|
+
for event in events:
|
|
472
|
+
saved = int(event.get("estimated_saved_tokens", 0) or 0)
|
|
473
|
+
tool = str(event.get("tool_name") or "-")
|
|
474
|
+
call_id = str(event.get("tool_call_id") or "-")
|
|
475
|
+
decision = str(
|
|
476
|
+
event.get("decision")
|
|
477
|
+
or ("transformed" if event.get("outcome") == "transformed" else "fallback")
|
|
478
|
+
)
|
|
479
|
+
reason = str(event.get("reason_code") or "legacy_passthrough")
|
|
480
|
+
row = (
|
|
481
|
+
"| {time} | {tool}<br>`{call_id}` | {type} | {decision} | {reason} | "
|
|
482
|
+
"{transformer} | {original} | {visible} | {saved} |"
|
|
483
|
+
)
|
|
484
|
+
md.append(
|
|
485
|
+
row.format(
|
|
486
|
+
time=markdown_escape(str(event.get("created_at", "-"))),
|
|
487
|
+
tool=markdown_escape(tool),
|
|
488
|
+
call_id=markdown_escape(call_id),
|
|
489
|
+
type=markdown_escape(str(event.get("content_type", "-"))),
|
|
490
|
+
decision=markdown_escape(decision),
|
|
491
|
+
reason=markdown_escape(reason),
|
|
492
|
+
transformer=markdown_escape(str(event.get("transformer", "-"))),
|
|
493
|
+
original=format_bytes(event.get("original_bytes", 0)),
|
|
494
|
+
visible=format_bytes(event.get("visible_bytes", 0)),
|
|
495
|
+
saved=f"~{saved}" if saved else "0",
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
lines.append(
|
|
499
|
+
f"{event.get('created_at', '-')} {tool}/{call_id} "
|
|
500
|
+
f"{decision}:{reason} saved_tokens=~{saved}"
|
|
501
|
+
)
|
|
502
|
+
return SlashResult(handled=True, lines=lines, markdown="\n".join(md))
|
|
503
|
+
|
|
504
|
+
stats = repo.stats(thread_id=thread_id)
|
|
505
|
+
effective_saved = format_bytes(stats["effective_saved_bytes"])
|
|
506
|
+
effective_ratio = f"{stats['effective_savings_ratio']:.1%}"
|
|
507
|
+
rows = [
|
|
508
|
+
("thread_id", thread_id),
|
|
509
|
+
("outputs considered", str(stats["outputs_considered"])),
|
|
510
|
+
("transformed", str(stats["transformed"])),
|
|
511
|
+
("skipped", str(stats.get("skipped", 0) or 0)),
|
|
512
|
+
("fallback", str(stats.get("fallback", 0) or 0)),
|
|
513
|
+
("model requests", str(stats.get("model_requests", 0) or 0)),
|
|
514
|
+
(
|
|
515
|
+
"request input before/after",
|
|
516
|
+
f"~{stats.get('request_input_tokens_before', 0)}/"
|
|
517
|
+
f"~{stats.get('request_input_tokens_after', 0)}",
|
|
518
|
+
),
|
|
519
|
+
("request saved tokens", f"~{stats.get('request_saved_tokens', 0) or 0}"),
|
|
520
|
+
("whole request savings", f"{stats.get('whole_request_savings_ratio', 0.0):.1%}"),
|
|
521
|
+
("new input savings", f"{stats.get('new_input_savings_ratio', 0.0):.1%}"),
|
|
522
|
+
(
|
|
523
|
+
"provider input/cache/output",
|
|
524
|
+
f"{stats.get('provider_input_tokens', 0)}/"
|
|
525
|
+
f"{stats.get('cache_read_tokens', 0)}/"
|
|
526
|
+
f"{stats.get('request_output_tokens', 0)}",
|
|
527
|
+
),
|
|
528
|
+
("original bytes", format_bytes(stats["original_bytes"])),
|
|
529
|
+
("visible bytes", format_bytes(stats["visible_bytes"])),
|
|
530
|
+
(
|
|
531
|
+
"estimated static token saving",
|
|
532
|
+
str(stats.get("estimated_saved_tokens", 0) or 0),
|
|
533
|
+
),
|
|
534
|
+
(
|
|
535
|
+
"estimated reused token saving",
|
|
536
|
+
str(stats.get("estimated_reused_tokens", 0) or 0),
|
|
537
|
+
),
|
|
538
|
+
("saved", f"{format_bytes(stats['saved_bytes'])} ({stats['savings_ratio']:.1%})"),
|
|
539
|
+
("retrieval bytes", format_bytes(stats["retrieval_bytes"])),
|
|
540
|
+
("effective saved", f"{effective_saved} ({effective_ratio})"),
|
|
541
|
+
("critical retention", f"{stats['critical_retention']:.1%}"),
|
|
542
|
+
]
|
|
543
|
+
paths = stats.get("execution_paths") or {}
|
|
544
|
+
if paths:
|
|
545
|
+
rows.append(
|
|
546
|
+
(
|
|
547
|
+
"execution paths",
|
|
548
|
+
", ".join(f"{name}={count}" for name, count in sorted(paths.items())),
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
reasons = stats.get("reasons") or {}
|
|
552
|
+
tokens_by_reason = stats.get("tokens_by_reason") or {}
|
|
553
|
+
if reasons:
|
|
554
|
+
rows.append(
|
|
555
|
+
(
|
|
556
|
+
"decision reasons",
|
|
557
|
+
", ".join(
|
|
558
|
+
f"{name}={count}/~{int(tokens_by_reason.get(name, 0) or 0)}tok"
|
|
559
|
+
for name, count in sorted(
|
|
560
|
+
reasons.items(),
|
|
561
|
+
key=lambda item: int(tokens_by_reason.get(item[0], 0) or 0),
|
|
562
|
+
reverse=True,
|
|
563
|
+
)
|
|
564
|
+
),
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
md = ["## Compression Diagnostics", "", "| Metric | Value |", "|---|---|"]
|
|
568
|
+
md.extend(f"| {markdown_escape(key)} | {markdown_escape(value)} |" for key, value in rows)
|
|
569
|
+
return SlashResult(
|
|
570
|
+
handled=True,
|
|
571
|
+
lines=[f"{key}: {value}" for key, value in rows],
|
|
572
|
+
markdown="\n".join(md),
|
|
573
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Shared parsing and presentation helpers for slash-command handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parts(text: str) -> list[str]:
|
|
7
|
+
return text.strip().split()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_bytes(value: int | float) -> str:
|
|
11
|
+
"""Format byte counts compactly for command tables."""
|
|
12
|
+
amount = max(0, int(value or 0))
|
|
13
|
+
for unit, size in (("G", 1024**3), ("M", 1024**2), ("K", 1024)):
|
|
14
|
+
if amount >= size:
|
|
15
|
+
rendered = amount / size
|
|
16
|
+
return f"{rendered:.1f}{unit}" if rendered < 10 else f"{rendered:.0f}{unit}"
|
|
17
|
+
return f"{amount}B"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def markdown_escape(text: str) -> str:
|
|
21
|
+
"""Escape pipe and backtick for Markdown table cells."""
|
|
22
|
+
return str(text).replace("|", "\\|").replace("`", "\\`")
|