soothe-client-python 0.9.8__tar.gz → 0.9.10__tar.gz
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.
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/PKG-INFO +6 -3
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/README.md +5 -2
- soothe_client_python-0.9.10/src/soothe_client/__init__.py +189 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/__init__.py +9 -44
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/daemon_session.py +52 -12
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/helpers.py +5 -1
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/protocol_params.py +5 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/session.py +6 -9
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/stream_terminal.py +3 -3
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/turn_boundary.py +18 -3
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/ws_command_client.py +121 -156
- soothe_client_python-0.9.8/src/soothe_client/__init__.py +0 -194
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/.gitignore +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/LICENSE +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/pyproject.toml +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/attachments.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/broadcaster.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/chunk_filter.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/classifier.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/events.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/managed_client.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/observability.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/pool.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/query_gate.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/session_store.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/thinking_step.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/turn.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/turn_runner.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/errors.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/intent_hints.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/py.typed +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/schemas.py +0 -0
- {soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soothe-client-python
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.10
|
|
4
4
|
Summary: WebSocket client + appkit for soothe-daemon (Python)
|
|
5
5
|
Project-URL: Homepage, https://github.com/mirasoth/soothe-client-python
|
|
6
6
|
Project-URL: Documentation, https://soothe.readthedocs.io
|
|
@@ -69,10 +69,13 @@ More patterns: [`examples/`](examples/) (hello → streaming → multi-turn →
|
|
|
69
69
|
|
|
70
70
|
| Need | Use |
|
|
71
71
|
|------|-----|
|
|
72
|
-
| One conversation, stream replies | `DaemonSession` |
|
|
72
|
+
| One conversation, stream replies | `DaemonSession` (`soothe_client.appkit`) |
|
|
73
|
+
| Jobs / cron (async) | `AsyncCommandClient` (alias of `WsCommandClient`) |
|
|
74
|
+
| Jobs / cron (scripts / sync) | `CommandClient` (alias of `SyncWsCommandClient`) |
|
|
73
75
|
| Raw WebSocket / custom RPCs | `WebSocketClient` |
|
|
74
76
|
| Many users / HTTP backend | `ConnectionPool` + `TurnRunner` |
|
|
75
|
-
|
|
77
|
+
|
|
78
|
+
Wire request param models live in `soothe_client.protocol_params` (not at package root).
|
|
76
79
|
|
|
77
80
|
## Develop
|
|
78
81
|
|
|
@@ -36,10 +36,13 @@ More patterns: [`examples/`](examples/) (hello → streaming → multi-turn →
|
|
|
36
36
|
|
|
37
37
|
| Need | Use |
|
|
38
38
|
|------|-----|
|
|
39
|
-
| One conversation, stream replies | `DaemonSession` |
|
|
39
|
+
| One conversation, stream replies | `DaemonSession` (`soothe_client.appkit`) |
|
|
40
|
+
| Jobs / cron (async) | `AsyncCommandClient` (alias of `WsCommandClient`) |
|
|
41
|
+
| Jobs / cron (scripts / sync) | `CommandClient` (alias of `SyncWsCommandClient`) |
|
|
40
42
|
| Raw WebSocket / custom RPCs | `WebSocketClient` |
|
|
41
43
|
| Many users / HTTP backend | `ConnectionPool` + `TurnRunner` |
|
|
42
|
-
|
|
44
|
+
|
|
45
|
+
Wire request param models live in `soothe_client.protocol_params` (not at package root).
|
|
43
46
|
|
|
44
47
|
## Develop
|
|
45
48
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Soothe WebSocket client for talking to a running soothe-daemon.
|
|
2
|
+
|
|
3
|
+
Public surface (community apps)::
|
|
4
|
+
|
|
5
|
+
from soothe_client import WebSocketClient, is_daemon_live
|
|
6
|
+
from soothe_client.appkit import DaemonSession
|
|
7
|
+
from soothe_client import AsyncCommandClient, CommandClient # preferred aliases
|
|
8
|
+
|
|
9
|
+
Advanced wire validation lives in ``soothe_client.protocol_params``.
|
|
10
|
+
Higher-level session / multi-user helpers live in ``soothe_client.appkit``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib.metadata
|
|
16
|
+
import warnings
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from soothe_sdk.wire.codec import ProtocolError
|
|
20
|
+
|
|
21
|
+
from soothe_client.errors import (
|
|
22
|
+
DaemonError,
|
|
23
|
+
DisconnectCause,
|
|
24
|
+
ReconnectError,
|
|
25
|
+
StaleLoopError,
|
|
26
|
+
disconnect_cause_name,
|
|
27
|
+
)
|
|
28
|
+
from soothe_client.helpers import (
|
|
29
|
+
check_daemon_status,
|
|
30
|
+
connected_websocket,
|
|
31
|
+
fetch_config_section,
|
|
32
|
+
fetch_loop_cards,
|
|
33
|
+
fetch_loop_history,
|
|
34
|
+
fetch_loop_messages,
|
|
35
|
+
fetch_skills_catalog,
|
|
36
|
+
is_daemon_live,
|
|
37
|
+
protocol1_rpc,
|
|
38
|
+
request_daemon_config_reload,
|
|
39
|
+
request_daemon_shutdown,
|
|
40
|
+
websocket_url_from_config,
|
|
41
|
+
)
|
|
42
|
+
from soothe_client.intent_hints import (
|
|
43
|
+
DEFAULT_DELIVERABLE_PHASES,
|
|
44
|
+
EMBED,
|
|
45
|
+
IMAGE_TO_TEXT,
|
|
46
|
+
OCR,
|
|
47
|
+
TEXT_COMPLETION,
|
|
48
|
+
validate_loop_input_intent_hint,
|
|
49
|
+
)
|
|
50
|
+
from soothe_client.session import (
|
|
51
|
+
bootstrap_loop_session,
|
|
52
|
+
connect_websocket_with_retries,
|
|
53
|
+
)
|
|
54
|
+
from soothe_client.websocket import WebSocketClient
|
|
55
|
+
from soothe_client.ws_command_client import (
|
|
56
|
+
AsyncCommandClient,
|
|
57
|
+
CommandClient,
|
|
58
|
+
SyncWsCommandClient,
|
|
59
|
+
WsCommandClient,
|
|
60
|
+
async_command_client_from_config,
|
|
61
|
+
async_ws_command_client_from_config,
|
|
62
|
+
command_client_from_config,
|
|
63
|
+
ws_command_client_from_config,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
__version__ = importlib.metadata.version("soothe-client-python")
|
|
68
|
+
except importlib.metadata.PackageNotFoundError:
|
|
69
|
+
__version__ = "0.0.0"
|
|
70
|
+
|
|
71
|
+
# Legacy ``*Params`` names formerly re-exported at package root.
|
|
72
|
+
_LEGACY_PARAM_NAMES = frozenset(
|
|
73
|
+
{
|
|
74
|
+
"AuthParams",
|
|
75
|
+
"AuthRefreshParams",
|
|
76
|
+
"AutopilotCancelAllParams",
|
|
77
|
+
"AutopilotCancelGoalParams",
|
|
78
|
+
"AutopilotDreamParams",
|
|
79
|
+
"AutopilotGetGoalParams",
|
|
80
|
+
"AutopilotGetJobParams",
|
|
81
|
+
"AutopilotListGoalsParams",
|
|
82
|
+
"AutopilotListJobsParams",
|
|
83
|
+
"AutopilotResumeParams",
|
|
84
|
+
"AutopilotStatusParams",
|
|
85
|
+
"AutopilotSubmitParams",
|
|
86
|
+
"AutopilotSubscribeParams",
|
|
87
|
+
"AutopilotWakeParams",
|
|
88
|
+
"ConfigGetParams",
|
|
89
|
+
"ConfigReloadParams",
|
|
90
|
+
"CronAddParams",
|
|
91
|
+
"CronCancelParams",
|
|
92
|
+
"CronListParams",
|
|
93
|
+
"CronShowParams",
|
|
94
|
+
"DaemonShutdownParams",
|
|
95
|
+
"DaemonStatusParams",
|
|
96
|
+
"DisconnectParams",
|
|
97
|
+
"InvokeSkillParams",
|
|
98
|
+
"JobCancelParams",
|
|
99
|
+
"JobCreateParams",
|
|
100
|
+
"JobDagParams",
|
|
101
|
+
"JobGuidanceParams",
|
|
102
|
+
"JobPauseParams",
|
|
103
|
+
"JobResumeParams",
|
|
104
|
+
"JobStatusParams",
|
|
105
|
+
"LoopCardsFetchParams",
|
|
106
|
+
"LoopDeleteParams",
|
|
107
|
+
"LoopDetachParams",
|
|
108
|
+
"LoopGetParams",
|
|
109
|
+
"LoopInputParams",
|
|
110
|
+
"LoopListParams",
|
|
111
|
+
"LoopMessagesParams",
|
|
112
|
+
"LoopNewParams",
|
|
113
|
+
"LoopPruneParams",
|
|
114
|
+
"LoopReattachParams",
|
|
115
|
+
"LoopStateGetParams",
|
|
116
|
+
"LoopStateUpdateParams",
|
|
117
|
+
"LoopTreeParams",
|
|
118
|
+
"McpStatusParams",
|
|
119
|
+
"ModelsListParams",
|
|
120
|
+
"RpcCommandParams",
|
|
121
|
+
"SkillsListParams",
|
|
122
|
+
"SlashCommandParams",
|
|
123
|
+
"SubscribeParams",
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
__all__ = [
|
|
128
|
+
"__version__",
|
|
129
|
+
# Transport
|
|
130
|
+
"WebSocketClient",
|
|
131
|
+
"ProtocolError",
|
|
132
|
+
# Errors
|
|
133
|
+
"DaemonError",
|
|
134
|
+
"DisconnectCause",
|
|
135
|
+
"ReconnectError",
|
|
136
|
+
"StaleLoopError",
|
|
137
|
+
"disconnect_cause_name",
|
|
138
|
+
# Command clients (canonical + preferred aliases)
|
|
139
|
+
"WsCommandClient",
|
|
140
|
+
"SyncWsCommandClient",
|
|
141
|
+
"AsyncCommandClient",
|
|
142
|
+
"CommandClient",
|
|
143
|
+
"ws_command_client_from_config",
|
|
144
|
+
"async_ws_command_client_from_config",
|
|
145
|
+
"command_client_from_config",
|
|
146
|
+
"async_command_client_from_config",
|
|
147
|
+
# Session bootstrap
|
|
148
|
+
"bootstrap_loop_session",
|
|
149
|
+
"connect_websocket_with_retries",
|
|
150
|
+
# Helpers
|
|
151
|
+
"websocket_url_from_config",
|
|
152
|
+
"connected_websocket",
|
|
153
|
+
"protocol1_rpc",
|
|
154
|
+
"check_daemon_status",
|
|
155
|
+
"is_daemon_live",
|
|
156
|
+
"request_daemon_config_reload",
|
|
157
|
+
"request_daemon_shutdown",
|
|
158
|
+
"fetch_skills_catalog",
|
|
159
|
+
"fetch_config_section",
|
|
160
|
+
"fetch_loop_history",
|
|
161
|
+
"fetch_loop_cards",
|
|
162
|
+
"fetch_loop_messages",
|
|
163
|
+
# Intent hints
|
|
164
|
+
"TEXT_COMPLETION",
|
|
165
|
+
"IMAGE_TO_TEXT",
|
|
166
|
+
"OCR",
|
|
167
|
+
"EMBED",
|
|
168
|
+
"DEFAULT_DELIVERABLE_PHASES",
|
|
169
|
+
"validate_loop_input_intent_hint",
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def __getattr__(name: str) -> Any:
|
|
174
|
+
"""Lazy-load legacy ``*Params`` with a deprecation warning.
|
|
175
|
+
|
|
176
|
+
Prefer ``from soothe_client.protocol_params import LoopInputParams``.
|
|
177
|
+
"""
|
|
178
|
+
if name in _LEGACY_PARAM_NAMES:
|
|
179
|
+
warnings.warn(
|
|
180
|
+
f"Importing {name!r} from soothe_client is deprecated; "
|
|
181
|
+
f"use soothe_client.protocol_params.{name} instead.",
|
|
182
|
+
DeprecationWarning,
|
|
183
|
+
stacklevel=2,
|
|
184
|
+
)
|
|
185
|
+
from soothe_client import protocol_params as _params
|
|
186
|
+
|
|
187
|
+
return getattr(_params, name)
|
|
188
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
189
|
+
raise AttributeError(msg)
|
{soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/appkit/__init__.py
RENAMED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"""Application helpers built on WebSocketClient.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Primary building blocks for agent UIs and backends:
|
|
4
4
|
|
|
5
5
|
- ``DaemonSession`` — dual-socket loop session + streamed turns
|
|
6
6
|
- ``ConnectionPool`` / ``TurnRunner`` — multi-session turn execution
|
|
7
7
|
- ``QueryGate`` — one-in-flight query per session
|
|
8
|
-
- ``EventClassifier``
|
|
8
|
+
- ``EventClassifier`` — stream → deliverable mapping
|
|
9
9
|
- ``SSEBroadcaster`` — drop-on-full fan-out to subscribers
|
|
10
10
|
- ``SessionStore`` — persistence seam (Protocol)
|
|
11
|
+
|
|
12
|
+
Advanced stream/pipeline plumbing lives in submodules
|
|
13
|
+
(``events``, ``chunk_filter``, ``turn``, ``managed_client``).
|
|
11
14
|
"""
|
|
12
15
|
|
|
13
16
|
from __future__ import annotations
|
|
@@ -18,7 +21,6 @@ from soothe_client.appkit.attachments import (
|
|
|
18
21
|
compact_image_attachment,
|
|
19
22
|
)
|
|
20
23
|
from soothe_client.appkit.broadcaster import SSEBroadcaster, SSEEvent
|
|
21
|
-
from soothe_client.appkit.chunk_filter import should_drop_stream_chunk_early
|
|
22
24
|
from soothe_client.appkit.classifier import (
|
|
23
25
|
EVENT_FINAL_REPORT,
|
|
24
26
|
ChatEventResult,
|
|
@@ -26,17 +28,7 @@ from soothe_client.appkit.classifier import (
|
|
|
26
28
|
ClassifierConfig,
|
|
27
29
|
EventClassifier,
|
|
28
30
|
)
|
|
29
|
-
from soothe_client.appkit.daemon_session import
|
|
30
|
-
from soothe_client.appkit.events import is_loop_scoped_event, unwrap_next
|
|
31
|
-
from soothe_client.appkit.managed_client import (
|
|
32
|
-
BootstrapFunc,
|
|
33
|
-
ClientFactory,
|
|
34
|
-
ManagedClient,
|
|
35
|
-
WebSocketManagedClient,
|
|
36
|
-
default_bootstrap_func,
|
|
37
|
-
default_client_factory,
|
|
38
|
-
)
|
|
39
|
-
from soothe_client.appkit.observability import TurnEventStats
|
|
31
|
+
from soothe_client.appkit.daemon_session import DaemonSession
|
|
40
32
|
from soothe_client.appkit.pool import (
|
|
41
33
|
ConnectionPool,
|
|
42
34
|
ErrPoolExhausted,
|
|
@@ -50,15 +42,6 @@ from soothe_client.appkit.thinking_step import (
|
|
|
50
42
|
DEFAULT_THINKING_STEP_EVENTS,
|
|
51
43
|
extract_thinking_step,
|
|
52
44
|
)
|
|
53
|
-
from soothe_client.appkit.turn import (
|
|
54
|
-
PRIORITY_CRITICAL,
|
|
55
|
-
PRIORITY_HIGH,
|
|
56
|
-
PRIORITY_LOW,
|
|
57
|
-
PRIORITY_NORMAL,
|
|
58
|
-
TurnApplyBatcher,
|
|
59
|
-
TurnEventPipeline,
|
|
60
|
-
run_turn_pipeline,
|
|
61
|
-
)
|
|
62
45
|
from soothe_client.appkit.turn_runner import (
|
|
63
46
|
STREAM_CLOSE_FAIL,
|
|
64
47
|
STREAM_CLOSE_SOFT_COMPLETE,
|
|
@@ -77,13 +60,13 @@ from soothe_client.appkit.turn_runner import (
|
|
|
77
60
|
)
|
|
78
61
|
|
|
79
62
|
__all__ = [
|
|
80
|
-
"DEFAULT_POST_IDLE_DRAIN_S",
|
|
81
63
|
"DEFAULT_THINKING_STEP_EVENTS",
|
|
82
64
|
"STREAM_CLOSE_FAIL",
|
|
83
65
|
"STREAM_CLOSE_SOFT_COMPLETE",
|
|
84
66
|
"Attachment",
|
|
85
|
-
"
|
|
86
|
-
"
|
|
67
|
+
"ChatEventResult",
|
|
68
|
+
"ChatEventTerminal",
|
|
69
|
+
"ClassifierConfig",
|
|
87
70
|
"CompactImageOptions",
|
|
88
71
|
"ConnectionPool",
|
|
89
72
|
"DaemonSession",
|
|
@@ -92,18 +75,10 @@ __all__ = [
|
|
|
92
75
|
"ErrPoolExhausted",
|
|
93
76
|
"ErrQueryBusy",
|
|
94
77
|
"ErrQueryTimeout",
|
|
95
|
-
"ChatEventResult",
|
|
96
|
-
"ChatEventTerminal",
|
|
97
|
-
"ClassifierConfig",
|
|
98
78
|
"EventClassifier",
|
|
99
79
|
"InputOpts",
|
|
100
|
-
"ManagedClient",
|
|
101
80
|
"OnComplete",
|
|
102
81
|
"OnError",
|
|
103
|
-
"PRIORITY_CRITICAL",
|
|
104
|
-
"PRIORITY_HIGH",
|
|
105
|
-
"PRIORITY_LOW",
|
|
106
|
-
"PRIORITY_NORMAL",
|
|
107
82
|
"PoolConfig",
|
|
108
83
|
"PooledConn",
|
|
109
84
|
"QueryGate",
|
|
@@ -114,22 +89,12 @@ __all__ = [
|
|
|
114
89
|
"SessionStore",
|
|
115
90
|
"StreamClosePolicy",
|
|
116
91
|
"TimeoutPolicy",
|
|
117
|
-
"TurnApplyBatcher",
|
|
118
92
|
"TurnConfig",
|
|
119
|
-
"TurnEventPipeline",
|
|
120
|
-
"TurnEventStats",
|
|
121
93
|
"TurnRunner",
|
|
122
|
-
"WebSocketManagedClient",
|
|
123
94
|
"compact_attachments",
|
|
124
95
|
"compact_image_attachment",
|
|
125
|
-
"default_bootstrap_func",
|
|
126
|
-
"default_client_factory",
|
|
127
96
|
"default_pool_config",
|
|
128
97
|
"extract_thinking_step",
|
|
129
98
|
"idle_timeout_for_turn",
|
|
130
99
|
"input_message_for_loop",
|
|
131
|
-
"is_loop_scoped_event",
|
|
132
|
-
"run_turn_pipeline",
|
|
133
|
-
"should_drop_stream_chunk_early",
|
|
134
|
-
"unwrap_next",
|
|
135
100
|
]
|
|
@@ -23,7 +23,7 @@ from soothe_client.stream_terminal import (
|
|
|
23
23
|
is_turn_end_custom_data,
|
|
24
24
|
is_turn_progress_chunk,
|
|
25
25
|
)
|
|
26
|
-
from soothe_client.turn_boundary import frame_seq, frame_turn_id
|
|
26
|
+
from soothe_client.turn_boundary import frame_seq, frame_turn_id, parse_turn_generation
|
|
27
27
|
from soothe_client.websocket import WebSocketClient
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
@@ -37,7 +37,21 @@ StreamDeliveryResolver = Callable[[], str]
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class DaemonSession:
|
|
40
|
-
"""
|
|
40
|
+
"""One conversation with a daemon loop: connect, send turns, stream chunks.
|
|
41
|
+
|
|
42
|
+
Owns a subscribed stream WebSocket plus an RPC sidecar so metadata calls
|
|
43
|
+
do not starve loop events. Typical usage::
|
|
44
|
+
|
|
45
|
+
session = DaemonSession("ws://127.0.0.1:8765")
|
|
46
|
+
await session.connect()
|
|
47
|
+
await session.send_turn("Hello")
|
|
48
|
+
async for _ns, mode, data in session.iter_turn_chunks():
|
|
49
|
+
...
|
|
50
|
+
await session.close()
|
|
51
|
+
|
|
52
|
+
Advanced: ``early_drop_fn`` / ``stats_factory`` customize stream filtering
|
|
53
|
+
and metrics for product UIs; most apps leave the defaults.
|
|
54
|
+
"""
|
|
41
55
|
|
|
42
56
|
def __init__(
|
|
43
57
|
self,
|
|
@@ -67,7 +81,7 @@ class DaemonSession:
|
|
|
67
81
|
self.last_turn_end_state: str | None = None
|
|
68
82
|
self.last_turn_cancellation_seen: bool = False
|
|
69
83
|
self.last_turn_error_message: str | None = None
|
|
70
|
-
#
|
|
84
|
+
# Last completed turn's seq floor (drop stale prior-turn frames).
|
|
71
85
|
self._last_turn_end_seq: int = 0
|
|
72
86
|
self._expected_turn_id: str | None = None
|
|
73
87
|
|
|
@@ -108,7 +122,11 @@ class DaemonSession:
|
|
|
108
122
|
)
|
|
109
123
|
|
|
110
124
|
async def connect(self, *, resume_loop_id: str | None = None) -> dict[str, Any]:
|
|
111
|
-
"""
|
|
125
|
+
"""Open the stream socket and bootstrap (or resume) a loop session.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Status event from the daemon (includes ``loop_id`` on success).
|
|
129
|
+
"""
|
|
112
130
|
await connect_websocket_with_retries(self._client)
|
|
113
131
|
return await self._bootstrap_loop(resume_loop_id=resume_loop_id)
|
|
114
132
|
|
|
@@ -182,7 +200,7 @@ class DaemonSession:
|
|
|
182
200
|
await self._bootstrap_loop(resume_loop_id=resume_loop_id)
|
|
183
201
|
|
|
184
202
|
async def close(self, *, handshake_timeout: float = 2.0) -> None:
|
|
185
|
-
"""Close stream and RPC sockets
|
|
203
|
+
"""Close stream and RPC sockets. Safe to call more than once."""
|
|
186
204
|
if self._closed:
|
|
187
205
|
return
|
|
188
206
|
self._closed = True
|
|
@@ -219,7 +237,11 @@ class DaemonSession:
|
|
|
219
237
|
clarification_answers: list[str] | None = None,
|
|
220
238
|
intent_hint: str | None = None,
|
|
221
239
|
) -> None:
|
|
222
|
-
"""Send a
|
|
240
|
+
"""Send a user message on the active loop (start of a turn).
|
|
241
|
+
|
|
242
|
+
Call ``iter_turn_chunks`` afterward to consume the streamed reply.
|
|
243
|
+
Requires ``connect()`` (or another bootstrap) first.
|
|
244
|
+
"""
|
|
223
245
|
if not self._loop_id:
|
|
224
246
|
raise RuntimeError("No active loop session")
|
|
225
247
|
await self._client.send_input(
|
|
@@ -303,13 +325,15 @@ class DaemonSession:
|
|
|
303
325
|
*,
|
|
304
326
|
max_wait_s: float | None = None,
|
|
305
327
|
) -> AsyncIterator[tuple[tuple[Any, ...], str, Any]]:
|
|
306
|
-
"""Yield ``(namespace, mode, data)`` chunks
|
|
328
|
+
"""Yield ``(namespace, mode, data)`` stream chunks until the turn ends.
|
|
329
|
+
|
|
330
|
+
Handles idle timeout, post-idle drain, and turn boundary filtering.
|
|
331
|
+
Call after ``send_turn``.
|
|
307
332
|
|
|
308
333
|
Args:
|
|
309
334
|
max_wait_s: Optional absolute deadline for the whole turn. When set,
|
|
310
335
|
raises ``TimeoutError`` if the daemon never emits a turn-end
|
|
311
|
-
signal in time
|
|
312
|
-
strange_loop.completed).
|
|
336
|
+
signal in time.
|
|
313
337
|
"""
|
|
314
338
|
self.turn_event_stats = self._new_turn_stats()
|
|
315
339
|
self.last_turn_end_state = None
|
|
@@ -392,13 +416,19 @@ class DaemonSession:
|
|
|
392
416
|
if ev_seq is not None and last_end_seq > 0 and ev_seq <= last_end_seq:
|
|
393
417
|
continue
|
|
394
418
|
|
|
395
|
-
#
|
|
419
|
+
# Drop mismatched turn_id once bound — except status=running,
|
|
420
|
+
# which may rebind to a newer generation (pre-admit early
|
|
421
|
+
# running can briefly carry a stale or empty turn_id).
|
|
396
422
|
ev_turn_id = frame_turn_id(event)
|
|
423
|
+
is_running_status = (
|
|
424
|
+
event_type == "status" and str(event.get("state") or "") == "running"
|
|
425
|
+
)
|
|
397
426
|
if (
|
|
398
427
|
expected_turn_id
|
|
399
428
|
and ev_turn_id
|
|
400
429
|
and ev_turn_id != expected_turn_id
|
|
401
430
|
and event_type in {"event", "status"}
|
|
431
|
+
and not is_running_status
|
|
402
432
|
):
|
|
403
433
|
logger.debug(
|
|
404
434
|
"Ignoring mismatched turn_id frame type=%s got=%s expected=%s",
|
|
@@ -426,8 +456,18 @@ class DaemonSession:
|
|
|
426
456
|
progress_seen = True
|
|
427
457
|
status_turn = frame_turn_id(event)
|
|
428
458
|
if status_turn:
|
|
429
|
-
|
|
430
|
-
|
|
459
|
+
new_gen = parse_turn_generation(status_turn)
|
|
460
|
+
old_gen = parse_turn_generation(expected_turn_id)
|
|
461
|
+
if expected_turn_id is None or (
|
|
462
|
+
new_gen is not None and (old_gen is None or new_gen >= old_gen)
|
|
463
|
+
):
|
|
464
|
+
if expected_turn_id and status_turn != expected_turn_id:
|
|
465
|
+
# Successor turn admitted while this reader
|
|
466
|
+
# still held a stale/early binding.
|
|
467
|
+
stream_payload_seen = False
|
|
468
|
+
turn_progress_seen = False
|
|
469
|
+
expected_turn_id = status_turn
|
|
470
|
+
self._expected_turn_id = status_turn
|
|
431
471
|
elif query_started and state == "stopped":
|
|
432
472
|
self.last_turn_end_state = state
|
|
433
473
|
if ev_seq is not None:
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""WebSocket
|
|
1
|
+
"""Small WebSocket helpers for CLI and scripts (status, fetch, one-shot RPC).
|
|
2
|
+
|
|
3
|
+
For interactive turns use ``DaemonSession``. For jobs/cron use
|
|
4
|
+
``WsCommandClient`` / ``CommandClient``.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
{soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/protocol_params.py
RENAMED
|
@@ -32,6 +32,7 @@ __all__ = [
|
|
|
32
32
|
"AutopilotListGoalsParams",
|
|
33
33
|
"AutopilotGetGoalParams",
|
|
34
34
|
"AutopilotCancelGoalParams",
|
|
35
|
+
"AutopilotCancelAllParams",
|
|
35
36
|
"AutopilotWakeParams",
|
|
36
37
|
"AutopilotDreamParams",
|
|
37
38
|
"AutopilotResumeParams",
|
|
@@ -363,6 +364,10 @@ class AutopilotCancelGoalParams(ParamsBase):
|
|
|
363
364
|
goal_id: str = Field(..., min_length=1)
|
|
364
365
|
|
|
365
366
|
|
|
367
|
+
class AutopilotCancelAllParams(EmptyParams):
|
|
368
|
+
"""Params for ``method=autopilot_cancel_all`` — no required fields."""
|
|
369
|
+
|
|
370
|
+
|
|
366
371
|
class AutopilotWakeParams(EmptyParams):
|
|
367
372
|
"""Params for ``method=autopilot_wake`` — no required fields."""
|
|
368
373
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Session bootstrap helpers for CLI, TUI, and appkit.
|
|
2
|
+
|
|
3
|
+
Prefer ``DaemonSession`` for application code. These helpers are the lower-level
|
|
4
|
+
connect + loop-subscribe steps used when you own the ``WebSocketClient``.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
@@ -22,14 +26,7 @@ _LEGACY_LOOP_AUTOPILOT_MODE = "solo"
|
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
async def connect_websocket_with_retries(client: Any) -> None:
|
|
25
|
-
"""Connect
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
client: WebSocketClient instance.
|
|
29
|
-
|
|
30
|
-
Raises:
|
|
31
|
-
ConnectionError: If connection fails after all retries.
|
|
32
|
-
"""
|
|
29
|
+
"""Connect a ``WebSocketClient`` with retries (daemon cold-start safe)."""
|
|
33
30
|
last_error: OSError | ConnectionError | TimeoutError | None = None
|
|
34
31
|
for attempt in range(_CONNECT_RETRY_COUNT):
|
|
35
32
|
try:
|
{soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/stream_terminal.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Internal stream/turn terminal frame helpers.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Used by ``WebSocketClient`` and ``DaemonSession`` so leftover prior-turn
|
|
4
|
+
terminals cannot blank the next query. Not part of the community public API.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
{soothe_client_python-0.9.8 → soothe_client_python-0.9.10}/src/soothe_client/turn_boundary.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Internal turn / stream boundary helpers.
|
|
2
2
|
|
|
3
|
-
``
|
|
4
|
-
|
|
3
|
+
Used by ``DaemonSession`` and ``WebSocketClient`` for ``turn_id`` / ``seq``
|
|
4
|
+
filtering. Not part of the community public API — import only if you are
|
|
5
|
+
extending stream handling.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
|
@@ -18,6 +19,19 @@ def format_turn_id(loop_id: str, generation: int) -> str:
|
|
|
18
19
|
return f"{lid}:{gen}"
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def parse_turn_generation(turn_id: str | None) -> int | None:
|
|
23
|
+
"""Extract generation int from ``turn_id``, or None if malformed."""
|
|
24
|
+
raw = str(turn_id or "").strip()
|
|
25
|
+
if not raw or ":" not in raw:
|
|
26
|
+
return None
|
|
27
|
+
suffix = raw.rsplit(":", 1)[-1]
|
|
28
|
+
try:
|
|
29
|
+
gen = int(suffix)
|
|
30
|
+
except ValueError:
|
|
31
|
+
return None
|
|
32
|
+
return gen if gen > 0 else None
|
|
33
|
+
|
|
34
|
+
|
|
21
35
|
def frame_turn_id(frame: dict[str, Any] | None) -> str | None:
|
|
22
36
|
"""Return ``turn_id`` from a status/event frame or nested custom data."""
|
|
23
37
|
if not isinstance(frame, dict):
|
|
@@ -51,4 +65,5 @@ __all__ = [
|
|
|
51
65
|
"format_turn_id",
|
|
52
66
|
"frame_seq",
|
|
53
67
|
"frame_turn_id",
|
|
68
|
+
"parse_turn_generation",
|
|
54
69
|
]
|