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.
Files changed (131) hide show
  1. synapse/__init__.py +13 -0
  2. synapse/__main__.py +6 -0
  3. synapse/app/__init__.py +1 -0
  4. synapse/app/agent.py +492 -0
  5. synapse/app/agent_md.py +107 -0
  6. synapse/cli.py +750 -0
  7. synapse/commands/__init__.py +1 -0
  8. synapse/commands/compression.py +573 -0
  9. synapse/commands/helpers.py +22 -0
  10. synapse/commands/mcp.py +406 -0
  11. synapse/commands/model.py +173 -0
  12. synapse/commands/result.py +34 -0
  13. synapse/commands/sessions.py +443 -0
  14. synapse/commands/slash_cmds.py +521 -0
  15. synapse/commands/slash_complete.py +816 -0
  16. synapse/commands/theme.py +99 -0
  17. synapse/config.py +27 -0
  18. synapse/content/__init__.py +1 -0
  19. synapse/content/input_history.py +122 -0
  20. synapse/content/multimodal.py +733 -0
  21. synapse/content/prompts.py +249 -0
  22. synapse/content/skills_catalog.py +128 -0
  23. synapse/integrations/__init__.py +1 -0
  24. synapse/integrations/checkpoint_seed.py +281 -0
  25. synapse/integrations/codex_history.py +375 -0
  26. synapse/integrations/codex_import.py +393 -0
  27. synapse/integrations/codex_sessions.py +629 -0
  28. synapse/integrations/describe_image.py +370 -0
  29. synapse/integrations/http_clients.py +199 -0
  30. synapse/integrations/llm_openai_compat.py +90 -0
  31. synapse/integrations/llm_openai_websocket.py +187 -0
  32. synapse/integrations/mcp_client.py +646 -0
  33. synapse/integrations/vision_middleware.py +62 -0
  34. synapse/models/__init__.py +5 -0
  35. synapse/models/config.py +240 -0
  36. synapse/models/helpers.py +206 -0
  37. synapse/models/profile.py +59 -0
  38. synapse/models/registry.py +722 -0
  39. synapse/models_registry.py +7 -0
  40. synapse/observability/__init__.py +1 -0
  41. synapse/observability/startup_trace.py +127 -0
  42. synapse/runtime/__init__.py +1 -0
  43. synapse/runtime/async_runtime.py +176 -0
  44. synapse/runtime/backends.py +458 -0
  45. synapse/runtime/context_compact.py +249 -0
  46. synapse/runtime/execute_capture.py +48 -0
  47. synapse/runtime/fs_permissions.py +79 -0
  48. synapse/runtime/harness.py +57 -0
  49. synapse/runtime/hitl.py +197 -0
  50. synapse/runtime/interaction_ledger.py +82 -0
  51. synapse/runtime/middleware.py +802 -0
  52. synapse/runtime/model_request_compression_middleware.py +745 -0
  53. synapse/runtime/pathing.py +146 -0
  54. synapse/runtime/safety.py +184 -0
  55. synapse/runtime/steer.py +240 -0
  56. synapse/runtime/subagents.py +207 -0
  57. synapse/runtime/tool_ignore.py +221 -0
  58. synapse/runtime/tool_output_eval.py +118 -0
  59. synapse/runtime/tool_output_middleware.py +585 -0
  60. synapse/runtime/tool_output_usage_middleware.py +60 -0
  61. synapse/sessions/__init__.py +31 -0
  62. synapse/sessions/cancel_repair.py +208 -0
  63. synapse/sessions/session_recap.py +174 -0
  64. synapse/sessions/store.py +695 -0
  65. synapse/sessions/transcript.py +754 -0
  66. synapse/settings/__init__.py +5 -0
  67. synapse/settings/config_paths.py +184 -0
  68. synapse/settings/schema.py +464 -0
  69. synapse/tool_output/__init__.py +59 -0
  70. synapse/tool_output/detection.py +170 -0
  71. synapse/tool_output/metrics.py +32 -0
  72. synapse/tool_output/models.py +173 -0
  73. synapse/tool_output/pipeline.py +330 -0
  74. synapse/tool_output/repository.py +721 -0
  75. synapse/tool_output/transformers.py +648 -0
  76. synapse/tools/__init__.py +5 -0
  77. synapse/tools/session_tools.py +204 -0
  78. synapse/ui/__init__.py +10 -0
  79. synapse/ui/bottombar/__init__.py +73 -0
  80. synapse/ui/bottombar/components/__init__.py +143 -0
  81. synapse/ui/bottombar/components/key_hints.py +30 -0
  82. synapse/ui/bottombar/components/mcp.py +64 -0
  83. synapse/ui/bottombar/components/mode.py +24 -0
  84. synapse/ui/bottombar/components/model.py +28 -0
  85. synapse/ui/bottombar/components/thread.py +29 -0
  86. synapse/ui/bottombar/context.py +36 -0
  87. synapse/ui/bottombar/core.py +74 -0
  88. synapse/ui/dialogs/__init__.py +25 -0
  89. synapse/ui/dialogs/base.py +362 -0
  90. synapse/ui/dialogs/codex_session_list.py +84 -0
  91. synapse/ui/dialogs/compression_diagnostics.py +210 -0
  92. synapse/ui/dialogs/git_explore.py +702 -0
  93. synapse/ui/dialogs/mcp_panel.py +407 -0
  94. synapse/ui/dialogs/model_picker.py +128 -0
  95. synapse/ui/dialogs/safety_panel.py +63 -0
  96. synapse/ui/dialogs/session_list.py +98 -0
  97. synapse/ui/dialogs/theme_designer.py +863 -0
  98. synapse/ui/dialogs/theme_picker.py +113 -0
  99. synapse/ui/git_explore/__init__.py +31 -0
  100. synapse/ui/git_explore/engine.py +82 -0
  101. synapse/ui/git_explore/provider.py +242 -0
  102. synapse/ui/git_explore/unified.py +85 -0
  103. synapse/ui/rendering.py +350 -0
  104. synapse/ui/sink.py +70 -0
  105. synapse/ui/steer_widget.py +367 -0
  106. synapse/ui/stream.py +1207 -0
  107. synapse/ui/stream_events.py +421 -0
  108. synapse/ui/stream_runtime.py +252 -0
  109. synapse/ui/theme.py +1154 -0
  110. synapse/ui/timeline.py +621 -0
  111. synapse/ui/topbar/__init__.py +97 -0
  112. synapse/ui/topbar/components/__init__.py +150 -0
  113. synapse/ui/topbar/components/branch.py +41 -0
  114. synapse/ui/topbar/components/title.py +24 -0
  115. synapse/ui/topbar/components/tool_output.py +24 -0
  116. synapse/ui/topbar/components/usage.py +24 -0
  117. synapse/ui/topbar/components/workspace.py +32 -0
  118. synapse/ui/topbar/context.py +32 -0
  119. synapse/ui/topbar/core.py +979 -0
  120. synapse/ui/topbar/git_changes_popover.py +178 -0
  121. synapse/ui/topbar/git_chrome.py +475 -0
  122. synapse/ui/topbar/tool_output_popover.py +84 -0
  123. synapse/ui/topbar/widget.py +474 -0
  124. synapse/ui/tui.py +5717 -0
  125. synapse/ui/turn_rail.py +71 -0
  126. synapse/ui/user_turn.py +83 -0
  127. synapse/ui/welcome.py +261 -0
  128. synapse_cli_agent-0.1.13.dist-info/METADATA +412 -0
  129. synapse_cli_agent-0.1.13.dist-info/RECORD +131 -0
  130. synapse_cli_agent-0.1.13.dist-info/WHEEL +4 -0
  131. synapse_cli_agent-0.1.13.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,210 @@
1
+ """Compression decision diagnostics for the active session."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from textual.app import ComposeResult
8
+ from textual.widgets import Static
9
+
10
+ from synapse.tool_output.repository import ToolOutputRepository
11
+ from synapse.ui.dialogs.base import DialogBase, OptionItem, SectionHeader
12
+
13
+
14
+ def _fmt_tokens(value: Any) -> str:
15
+ amount = max(0, int(value or 0))
16
+ if amount >= 1_000_000:
17
+ return f"~{amount / 1_000_000:.1f}M tok"
18
+ if amount >= 1_000:
19
+ return f"~{amount / 1_000:.1f}K tok"
20
+ return f"~{amount} tok"
21
+
22
+
23
+ def _fmt_bytes(value: Any) -> str:
24
+ amount = max(0, int(value or 0))
25
+ if amount >= 1024**2:
26
+ return f"{amount / 1024**2:.1f} MiB"
27
+ if amount >= 1024:
28
+ return f"{amount / 1024:.1f} KiB"
29
+ return f"{amount} B"
30
+
31
+
32
+ class CompressionDiagnosticsDialog(DialogBase):
33
+ """Show aggregate compression outcomes and recent per-tool decisions."""
34
+
35
+ _title_icon = "◇"
36
+ _title_keys = "↑↓ · esc"
37
+
38
+ def __init__(
39
+ self,
40
+ repository: ToolOutputRepository,
41
+ thread_id: str,
42
+ *,
43
+ limit: int = 30,
44
+ ) -> None:
45
+ super().__init__()
46
+ self._repository = repository
47
+ self._thread_id = thread_id
48
+ self._limit = max(1, min(100, int(limit)))
49
+ self._stats = repository.stats(thread_id=thread_id)
50
+ self._events = repository.events(thread_id=thread_id, limit=self._limit)
51
+ self._request_events = repository.model_request_events(
52
+ thread_id=thread_id, limit=self._limit
53
+ )
54
+ self._items = self._build_items()
55
+
56
+ @property
57
+ def title_text(self) -> str:
58
+ return "Compression Diagnostics"
59
+
60
+ def _build_items(self) -> list[OptionItem]:
61
+ items: list[OptionItem] = []
62
+ for zone, tokens in sorted((self._stats.get("live_zone_tokens") or {}).items()):
63
+ items.append(
64
+ OptionItem(
65
+ key=f"zone:{zone}",
66
+ label=f"live-zone · {zone}",
67
+ detail="provider-aware request mutation boundary",
68
+ meta=_fmt_tokens(tokens),
69
+ show_bullet=False,
70
+ )
71
+ )
72
+ for name, tokens in self._stats.get("top_schema_tools") or []:
73
+ items.append(
74
+ OptionItem(
75
+ key=f"schema:{name}",
76
+ label=f"tool schema · {name}",
77
+ detail="cumulative request schema overhead",
78
+ meta=_fmt_tokens(tokens),
79
+ show_bullet=False,
80
+ )
81
+ )
82
+ breakdown = self._stats.get("content_breakdown") or {}
83
+ for source, tokens in sorted(
84
+ breakdown.items(), key=lambda item: int(item[1] or 0), reverse=True
85
+ ):
86
+ items.append(
87
+ OptionItem(
88
+ key=f"source:{source}",
89
+ label=f"request source · {source}",
90
+ detail="final model request content",
91
+ meta=_fmt_tokens(tokens),
92
+ show_bullet=False,
93
+ )
94
+ )
95
+ for reason, tokens in self._stats.get("top_opportunities") or []:
96
+ items.append(
97
+ OptionItem(
98
+ key=f"opportunity:{reason}",
99
+ label=f"opportunity · {reason}",
100
+ detail="candidate for the next profiling-driven optimization",
101
+ meta=_fmt_tokens(tokens),
102
+ show_bullet=False,
103
+ )
104
+ )
105
+ for reason, tokens in self._stats.get("top_protected_sources") or []:
106
+ items.append(
107
+ OptionItem(
108
+ key=f"protected:{reason}",
109
+ label=f"protected · {reason}",
110
+ detail="provider safety boundary; not ranked as an optimization target",
111
+ meta=_fmt_tokens(tokens),
112
+ show_bullet=False,
113
+ )
114
+ )
115
+ reasons = self._stats.get("reasons") or {}
116
+ tokens_by_reason = self._stats.get("tokens_by_reason") or {}
117
+ for reason, count in sorted(
118
+ reasons.items(),
119
+ key=lambda item: int(tokens_by_reason.get(item[0], 0) or 0),
120
+ reverse=True,
121
+ ):
122
+ items.append(
123
+ OptionItem(
124
+ key=f"reason:{reason}",
125
+ label=str(reason),
126
+ detail=f"{count} event(s)",
127
+ meta=_fmt_tokens(tokens_by_reason.get(reason, 0)),
128
+ show_bullet=False,
129
+ )
130
+ )
131
+ for request in self._request_events:
132
+ before = _fmt_tokens(request.get("input_tokens_before", 0))
133
+ after = _fmt_tokens(request.get("input_tokens_after", 0))
134
+ provider = str(request.get("provider") or "unknown")
135
+ api_style = str(request.get("api_style") or "unknown")
136
+ request_id = str(request.get("request_id") or "-")
137
+ protected = request.get("protected_tokens_by_reason") or {}
138
+ items.append(
139
+ OptionItem(
140
+ key=f"request:{request_id}",
141
+ label=f"model request · {provider}/{api_style}",
142
+ detail=f"{request_id} · {before} → {after} · protected {protected}",
143
+ meta=_fmt_tokens(request.get("total_saved_tokens", 0)),
144
+ show_bullet=False,
145
+ )
146
+ )
147
+ for event in self._events:
148
+ decision = str(
149
+ event.get("decision")
150
+ or ("transformed" if event.get("outcome") == "transformed" else "fallback")
151
+ )
152
+ reason = str(event.get("reason_code") or "legacy_passthrough")
153
+ tool = str(event.get("tool_name") or "tool")
154
+ call_id = str(event.get("tool_call_id") or "-")
155
+ content_type = str(event.get("content_type") or "unknown")
156
+ transformer = str(event.get("transformer") or "none")
157
+ original = _fmt_bytes(event.get("original_bytes", 0))
158
+ visible = _fmt_bytes(event.get("visible_bytes", 0))
159
+ items.append(
160
+ OptionItem(
161
+ key=f"event:{event.get('id', call_id)}",
162
+ label=f"{tool} · {decision} · {reason}",
163
+ detail=(
164
+ f"{call_id} · {content_type} · {transformer} · "
165
+ f"{original} → {visible}"
166
+ ),
167
+ meta=_fmt_tokens(event.get("estimated_saved_tokens", 0)),
168
+ show_bullet=False,
169
+ )
170
+ )
171
+ if not items:
172
+ items.append(
173
+ OptionItem(
174
+ key="empty",
175
+ label="No compression decisions recorded",
176
+ show_bullet=False,
177
+ )
178
+ )
179
+ return items
180
+
181
+ def compose_body(self) -> ComposeResult:
182
+ stats = self._stats
183
+ yield SectionHeader("Session summary")
184
+ yield Static(
185
+ f" {stats.get('outputs_considered', 0)} candidates · "
186
+ f"{stats.get('transformed', 0)} transformed · "
187
+ f"{stats.get('skipped', 0)} skipped · "
188
+ f"{stats.get('fallback', 0)} fallback\n"
189
+ f" {_fmt_bytes(stats.get('original_bytes', 0))} original → "
190
+ f"{_fmt_bytes(stats.get('visible_bytes', 0))} model-visible\n"
191
+ f" saved {_fmt_tokens(stats.get('estimated_saved_tokens', 0))} · "
192
+ f"reused {_fmt_tokens(stats.get('estimated_reused_tokens', 0))}\n"
193
+ f" {stats.get('turns', 0)} turns · {stats.get('model_requests', 0)} model calls · "
194
+ f"{stats.get('tool_calls', 0)} tool calls\n"
195
+ f" {stats.get('compression_managed_tool_calls', 0)} compression-managed · "
196
+ f"{stats.get('cache_bust_suspected_requests', 0)} cache-bust suspects\n"
197
+ f" whole {stats.get('whole_request_savings_ratio', 0.0):.1%} · "
198
+ f"new input {stats.get('new_input_savings_ratio', 0.0):.1%}\n"
199
+ f" {len(stats.get('content_breakdown') or {})} profiled sources · "
200
+ f"{len(stats.get('top_opportunities') or [])} ranked opportunities"
201
+ )
202
+ yield SectionHeader("Reasons and recent decisions")
203
+
204
+ def on_mount(self) -> None:
205
+ super().on_mount()
206
+ body = self.query_one("#dialog-body")
207
+ body.set_options(self._items, mark=" ")
208
+
209
+ def _on_selected(self, key: str | None) -> None:
210
+ self.dismiss(None)