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,330 @@
1
+ """Tool-output transformation orchestration and safety guards."""
2
+ from __future__ import annotations
3
+
4
+ import time
5
+
6
+ from synapse.tool_output.detection import (
7
+ _CODE_SUFFIXES,
8
+ _code_marker_count,
9
+ _diff_bloat_metadata,
10
+ _strip_numbered_source_lines,
11
+ detect_content_type,
12
+ )
13
+ from synapse.tool_output.models import (
14
+ CompressionStageEvent,
15
+ ContentType,
16
+ Detection,
17
+ TransformContext,
18
+ TransformResult,
19
+ )
20
+ from synapse.tool_output.transformers import (
21
+ CodeTransformer,
22
+ DiffTransformer,
23
+ GenericTransformer,
24
+ GitSummaryTransformer,
25
+ JsonTransformer,
26
+ LogTransformer,
27
+ NativeTransformer,
28
+ PathListTransformer,
29
+ SearchTransformer,
30
+ ToolOutputTransformer,
31
+ load_native_transformers,
32
+ )
33
+
34
+
35
+ class ToolOutputTransformPipeline:
36
+ """Deterministic Headroom-inspired algorithms with optional local plugins."""
37
+
38
+ def __init__(
39
+ self,
40
+ *,
41
+ transformers: list[ToolOutputTransformer] | None = None,
42
+ disabled_types: set[str] | frozenset[str] | None = None,
43
+ use_native: bool = True,
44
+ ) -> None:
45
+ builtins: list[ToolOutputTransformer] = [
46
+ SearchTransformer(),
47
+ PathListTransformer(),
48
+ LogTransformer(),
49
+ DiffTransformer(),
50
+ GitSummaryTransformer(),
51
+ JsonTransformer(),
52
+ CodeTransformer(),
53
+ GenericTransformer(),
54
+ ]
55
+ self.transformers = [
56
+ *(transformers or []),
57
+ *load_native_transformers(enabled=use_native),
58
+ *builtins,
59
+ ]
60
+ self.disabled_types = frozenset(str(item) for item in (disabled_types or set()))
61
+
62
+ def transform(self, content: str, context: TransformContext) -> TransformResult:
63
+ original_bytes = len(content.encode("utf-8"))
64
+ detection_started = time.perf_counter()
65
+ raw_detection = detect_content_type(content)
66
+ detection = raw_detection
67
+ detection_view = content
68
+ numbered_lines = 0
69
+ non_empty_lines = 0
70
+ normalized_code_markers = 0
71
+ suffix_is_code = context.file_suffix.casefold() in _CODE_SUFFIXES
72
+ numbered_source_hint = bool(
73
+ suffix_is_code
74
+ and numbered_lines
75
+ and numbered_lines >= max(1, non_empty_lines // 2)
76
+ )
77
+ if context.tool_name == "read_file":
78
+ normalized, numbered_lines, non_empty_lines = _strip_numbered_source_lines(content)
79
+ normalized_code_markers = _code_marker_count(normalized)
80
+ numbered_source_hint = bool(
81
+ suffix_is_code
82
+ and numbered_lines
83
+ and numbered_lines >= max(1, non_empty_lines // 2)
84
+ )
85
+ if numbered_lines and normalized_code_markers >= 3:
86
+ detection_view = normalized
87
+ detection = detect_content_type(normalized)
88
+ if numbered_source_hint:
89
+ detection_view = normalized
90
+ detection = Detection(ContentType.CODE, max(0.8, detection.confidence))
91
+ detection_metadata = {
92
+ "content_type": detection.content_type.value,
93
+ "confidence": detection.confidence,
94
+ "raw_content_type": raw_detection.content_type.value,
95
+ "raw_confidence": raw_detection.confidence,
96
+ "tool_name": context.tool_name,
97
+ "file_suffix": context.file_suffix,
98
+ "numbered_lines": numbered_lines,
99
+ "non_empty_lines": non_empty_lines,
100
+ "normalized_code_markers": normalized_code_markers,
101
+ "numbered_source_hint": numbered_source_hint,
102
+ "classification_conflict": raw_detection.content_type is not detection.content_type,
103
+ }
104
+ if detection.content_type is ContentType.DIFF:
105
+ detection_metadata.update(_diff_bloat_metadata(content))
106
+ stages: list[CompressionStageEvent] = [
107
+ CompressionStageEvent(
108
+ phase="detect",
109
+ algorithm="tool-aware-content-detector-v2",
110
+ applied=True,
111
+ reason_code="classified",
112
+ input_bytes=original_bytes,
113
+ output_bytes=len(detection_view.encode("utf-8")),
114
+ duration_ms=(time.perf_counter() - detection_started) * 1000,
115
+ metadata=detection_metadata,
116
+ )
117
+ ]
118
+ if (
119
+ context.tool_name == "read_file"
120
+ and context.status == "success"
121
+ and suffix_is_code
122
+ and detection.content_type is ContentType.CODE
123
+ and context.fresh_read_source
124
+ ):
125
+ stages.append(
126
+ CompressionStageEvent(
127
+ phase="eligibility",
128
+ algorithm="fresh-read-source-policy-v1",
129
+ applied=False,
130
+ reason_code="fresh_read_source_protected",
131
+ input_bytes=original_bytes,
132
+ output_bytes=original_bytes,
133
+ metadata={
134
+ "file_path": context.file_path,
135
+ "file_suffix": context.file_suffix,
136
+ "normalized_code_markers": normalized_code_markers,
137
+ "fresh_read_source": context.fresh_read_source,
138
+ },
139
+ )
140
+ )
141
+ return TransformResult(
142
+ content,
143
+ "fresh-read-source-policy-v1",
144
+ ContentType.CODE,
145
+ 0,
146
+ 0,
147
+ {
148
+ "fallback": "fresh_read_source_protected",
149
+ "detection_confidence": detection.confidence,
150
+ **detection_metadata,
151
+ },
152
+ tuple(stages),
153
+ )
154
+ if detection.content_type.value in self.disabled_types:
155
+ stages.append(
156
+ CompressionStageEvent(
157
+ phase="eligibility",
158
+ algorithm="disabled-types-policy",
159
+ applied=False,
160
+ reason_code="disabled_content_type",
161
+ input_bytes=original_bytes,
162
+ output_bytes=original_bytes,
163
+ )
164
+ )
165
+ return TransformResult(
166
+ content,
167
+ "disabled",
168
+ detection.content_type,
169
+ 0,
170
+ 0,
171
+ {"fallback": "disabled", "detection_confidence": detection.confidence},
172
+ tuple(stages),
173
+ )
174
+ transformer = next(
175
+ (
176
+ item
177
+ for item in self.transformers
178
+ if detection.content_type in getattr(item, "content_types", set())
179
+ ),
180
+ GenericTransformer(),
181
+ )
182
+ transform_started = time.perf_counter()
183
+ result = transformer.transform(content, context)
184
+ result_bytes = len(result.content.encode("utf-8"))
185
+ native = isinstance(transformer, NativeTransformer)
186
+ native_reason = str(result.metadata.get("fallback") or "compressed")
187
+ stages.append(
188
+ CompressionStageEvent(
189
+ phase="native-transform" if native else "transform",
190
+ algorithm=str(getattr(transformer, "name", result.transformer)),
191
+ applied=result.content != content,
192
+ reason_code=native_reason,
193
+ input_bytes=original_bytes,
194
+ output_bytes=result_bytes,
195
+ duration_ms=(time.perf_counter() - transform_started) * 1000,
196
+ metadata=dict(result.metadata),
197
+ )
198
+ )
199
+ execution_path = "native" if native else "python_only"
200
+ native_result_is_unsafe_or_unhelpful = native and (
201
+ result.metadata.get("fallback") == "native_error"
202
+ or result.critical_retained < result.critical_total
203
+ or result_bytes >= original_bytes
204
+ )
205
+ if native_result_is_unsafe_or_unhelpful:
206
+ fallback_transformer = next(
207
+ (
208
+ item
209
+ for item in self.transformers
210
+ if detection.content_type in getattr(item, "content_types", set())
211
+ and not isinstance(item, NativeTransformer)
212
+ ),
213
+ None,
214
+ )
215
+ if fallback_transformer is not None:
216
+ fallback_started = time.perf_counter()
217
+ result = fallback_transformer.transform(content, context)
218
+ result_bytes = len(result.content.encode("utf-8"))
219
+ stages.append(
220
+ CompressionStageEvent(
221
+ phase="python-fallback",
222
+ algorithm=str(getattr(fallback_transformer, "name", result.transformer)),
223
+ applied=result.content != content,
224
+ reason_code="python_fallback_used",
225
+ input_bytes=original_bytes,
226
+ output_bytes=result_bytes,
227
+ duration_ms=(time.perf_counter() - fallback_started) * 1000,
228
+ metadata=dict(result.metadata),
229
+ )
230
+ )
231
+ execution_path = "python_fallback_after_native"
232
+ result = TransformResult(
233
+ result.content,
234
+ result.transformer,
235
+ result.content_type,
236
+ result.critical_total,
237
+ result.critical_retained,
238
+ {
239
+ **detection_metadata,
240
+ **result.metadata,
241
+ "execution_path": execution_path,
242
+ "detection_confidence": detection.confidence,
243
+ },
244
+ tuple(stages),
245
+ )
246
+ if result.critical_retained < result.critical_total:
247
+ stages.append(
248
+ CompressionStageEvent(
249
+ phase="critical-guard",
250
+ algorithm="critical-retention-v1",
251
+ applied=False,
252
+ reason_code="critical_content_lost",
253
+ input_bytes=original_bytes,
254
+ output_bytes=result_bytes,
255
+ metadata={
256
+ "critical_total": result.critical_total,
257
+ "critical_retained": result.critical_retained,
258
+ },
259
+ )
260
+ )
261
+ return TransformResult(
262
+ content,
263
+ "passthrough",
264
+ detection.content_type,
265
+ result.critical_total,
266
+ result.critical_total,
267
+ {
268
+ "fallback": "critical_content_lost",
269
+ "execution_path": execution_path,
270
+ "detection_confidence": detection.confidence,
271
+ },
272
+ tuple(stages),
273
+ )
274
+ stages.append(
275
+ CompressionStageEvent(
276
+ phase="critical-guard",
277
+ algorithm="critical-retention-v1",
278
+ applied=True,
279
+ reason_code="accepted",
280
+ input_bytes=original_bytes,
281
+ output_bytes=result_bytes,
282
+ metadata={
283
+ "critical_total": result.critical_total,
284
+ "critical_retained": result.critical_retained,
285
+ },
286
+ )
287
+ )
288
+ if result_bytes >= original_bytes:
289
+ stages.append(
290
+ CompressionStageEvent(
291
+ phase="byte-guard",
292
+ algorithm="non-increase-v1",
293
+ applied=False,
294
+ reason_code="no_byte_savings",
295
+ input_bytes=original_bytes,
296
+ output_bytes=result_bytes,
297
+ )
298
+ )
299
+ return TransformResult(
300
+ content,
301
+ "passthrough",
302
+ detection.content_type,
303
+ result.critical_total,
304
+ result.critical_total,
305
+ {
306
+ "fallback": "no_byte_savings",
307
+ "execution_path": execution_path,
308
+ "detection_confidence": detection.confidence,
309
+ },
310
+ tuple(stages),
311
+ )
312
+ stages.append(
313
+ CompressionStageEvent(
314
+ phase="byte-guard",
315
+ algorithm="non-increase-v1",
316
+ applied=True,
317
+ reason_code="accepted",
318
+ input_bytes=original_bytes,
319
+ output_bytes=result_bytes,
320
+ )
321
+ )
322
+ return TransformResult(
323
+ result.content,
324
+ result.transformer,
325
+ result.content_type,
326
+ result.critical_total,
327
+ result.critical_retained,
328
+ result.metadata,
329
+ tuple(stages),
330
+ )