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,187 @@
1
+ """LangChain ChatOpenAI adapter for the Responses API WebSocket transport."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections.abc import AsyncIterator
7
+ from typing import Any
8
+
9
+ from langchain_core.callbacks import AsyncCallbackManagerForLLMRun
10
+ from langchain_core.language_models.chat_models import agenerate_from_stream
11
+ from langchain_core.messages import BaseMessage
12
+ from langchain_core.outputs import ChatGenerationChunk, ChatResult
13
+ from langchain_openai import ChatOpenAI
14
+ from langchain_openai.chat_models.base import _convert_responses_chunk_to_generation_chunk
15
+ from pydantic import PrivateAttr
16
+
17
+ _TERMINAL_EVENT_TYPES = {
18
+ "response.completed",
19
+ "response.failed",
20
+ "response.incomplete",
21
+ }
22
+
23
+
24
+ def prepare_responses_websocket_event(payload: dict[str, Any]) -> dict[str, Any]:
25
+ """Convert a LangChain Responses HTTP payload into ``response.create``."""
26
+ event = dict(payload)
27
+ event.pop("stream", None)
28
+ event.pop("background", None)
29
+ extra_body = event.pop("extra_body", None)
30
+ if isinstance(extra_body, dict):
31
+ event.update(extra_body)
32
+ # ``thinking`` is a Chat Completions extension used by some compatible
33
+ # providers. Responses WebSocket servers reject it; LangChain has already
34
+ # mapped reasoning effort to the standard top-level ``reasoning`` field.
35
+ event.pop("thinking", None)
36
+ event["type"] = "response.create"
37
+ return event
38
+
39
+
40
+ class ResponsesWebSocketChatOpenAI(ChatOpenAI):
41
+ """ChatOpenAI using a reusable ordinary Responses API WebSocket.
42
+
43
+ The public LangChain model contract is unchanged. Only the Responses streaming
44
+ transport is replaced; message conversion and chunk conversion continue to use
45
+ langchain-openai's native implementation.
46
+ """
47
+
48
+ _responses_ws_manager: Any = PrivateAttr(default=None)
49
+ _responses_ws_connection: Any = PrivateAttr(default=None)
50
+ _responses_ws_lock: asyncio.Lock | None = PrivateAttr(default=None)
51
+
52
+ async def _ensure_responses_websocket(self) -> Any:
53
+ connection = self._responses_ws_connection
54
+ if connection is not None:
55
+ return connection
56
+ client = self.root_async_client
57
+ # ChatOpenAI accepts an async API-key callable so it can avoid creating an
58
+ # eager sync OpenAI client. HTTP requests refresh that callable in
59
+ # AsyncOpenAI._prepare_options(), but the SDK's responses.connect() path
60
+ # reads auth_headers directly. Refresh it here or the WebSocket handshake
61
+ # is sent without Authorization and OpenAI-compatible servers return 401.
62
+ refresh_api_key = getattr(client, "_refresh_api_key", None)
63
+ if callable(refresh_api_key):
64
+ await refresh_api_key()
65
+ # Some OpenAI-compatible gateways don't answer WebSocket ping frames while
66
+ # a model is reasoning. Keep sending pings to keep intermediaries awake,
67
+ # but don't terminate an otherwise healthy response solely for a missing
68
+ # pong (websockets defaults to a 20-second ping timeout).
69
+ manager = client.responses.connect(
70
+ websocket_connection_options={"ping_timeout": None}
71
+ )
72
+ connection = await manager.enter()
73
+ self._responses_ws_manager = manager
74
+ self._responses_ws_connection = connection
75
+ return connection
76
+
77
+ async def _reset_responses_websocket(self) -> None:
78
+ connection = self._responses_ws_connection
79
+ self._responses_ws_connection = None
80
+ self._responses_ws_manager = None
81
+ if connection is not None:
82
+ try:
83
+ await connection.close()
84
+ except Exception: # noqa: BLE001
85
+ pass
86
+
87
+ async def aclose(self) -> None:
88
+ """Close the persistent Responses WebSocket, if it was opened."""
89
+ await self._reset_responses_websocket()
90
+
91
+ async def _recv_event(self, connection: Any) -> Any:
92
+ timeout = self.stream_chunk_timeout
93
+ if timeout is None or timeout <= 0:
94
+ return await connection.recv()
95
+ return await asyncio.wait_for(connection.recv(), timeout=float(timeout))
96
+
97
+ async def _astream(
98
+ self,
99
+ *args: Any,
100
+ **kwargs: Any,
101
+ ) -> AsyncIterator[ChatGenerationChunk]:
102
+ """Route LangChain streaming calls to the Responses WebSocket."""
103
+ async for chunk in self._astream_responses(*args, **kwargs):
104
+ yield chunk
105
+
106
+ async def _agenerate(
107
+ self,
108
+ messages: list[BaseMessage],
109
+ stop: list[str] | None = None,
110
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
111
+ **kwargs: Any,
112
+ ) -> ChatResult:
113
+ """Make non-streaming LangChain calls use the same WebSocket transport."""
114
+ return await agenerate_from_stream(
115
+ self._astream_responses(
116
+ messages,
117
+ stop=stop,
118
+ run_manager=run_manager,
119
+ **kwargs,
120
+ )
121
+ )
122
+
123
+ async def _astream_responses(
124
+ self,
125
+ messages: list[BaseMessage],
126
+ stop: list[str] | None = None,
127
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
128
+ **kwargs: Any,
129
+ ) -> AsyncIterator[ChatGenerationChunk]:
130
+ """Stream one response over the model's persistent WebSocket."""
131
+ kwargs["stream"] = True
132
+ payload = self._get_request_payload(messages, stop=stop, **kwargs)
133
+ event = prepare_responses_websocket_event(payload)
134
+
135
+ if self._responses_ws_lock is None:
136
+ self._responses_ws_lock = asyncio.Lock()
137
+
138
+ async with self._responses_ws_lock:
139
+ connection = await self._ensure_responses_websocket()
140
+ try:
141
+ await connection.send(event)
142
+ current_index = -1
143
+ current_output_index = -1
144
+ current_sub_index = -1
145
+ has_reasoning = False
146
+ original_schema_obj = kwargs.get("response_format")
147
+
148
+ while True:
149
+ response_event = await self._recv_event(connection)
150
+ event_type = str(getattr(response_event, "type", ""))
151
+ if event_type == "error":
152
+ error = getattr(response_event, "error", None)
153
+ message = getattr(error, "message", None) or str(error or response_event)
154
+ raise RuntimeError(f"Responses WebSocket error: {message}")
155
+
156
+ (
157
+ current_index,
158
+ current_output_index,
159
+ current_sub_index,
160
+ generation_chunk,
161
+ ) = _convert_responses_chunk_to_generation_chunk(
162
+ response_event,
163
+ current_index,
164
+ current_output_index,
165
+ current_sub_index,
166
+ schema=original_schema_obj,
167
+ metadata={},
168
+ has_reasoning=has_reasoning,
169
+ output_version=self.output_version,
170
+ )
171
+ if generation_chunk is not None:
172
+ if run_manager is not None:
173
+ await run_manager.on_llm_new_token(
174
+ generation_chunk.text,
175
+ chunk=generation_chunk,
176
+ )
177
+ if "reasoning" in generation_chunk.message.additional_kwargs:
178
+ has_reasoning = True
179
+ yield generation_chunk
180
+
181
+ if event_type in _TERMINAL_EVENT_TYPES:
182
+ break
183
+ except BaseException:
184
+ # Cancellation or a protocol error may leave unread events on the
185
+ # socket. Reopen it before the next model turn.
186
+ await self._reset_responses_websocket()
187
+ raise