bazaar-compute-node 0.1.3__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.
- bazaar_compute_node/__init__.py +3 -0
- bazaar_compute_node/app/__init__.py +1 -0
- bazaar_compute_node/app/application.py +398 -0
- bazaar_compute_node/app/attachments.py +154 -0
- bazaar_compute_node/app/command.py +342 -0
- bazaar_compute_node/app/config.py +121 -0
- bazaar_compute_node/app/registry.py +120 -0
- bazaar_compute_node/app/transport.py +264 -0
- bazaar_compute_node/app/windows_pipe.py +463 -0
- bazaar_compute_node/app/wrapper.py +63 -0
- bazaar_compute_node/bcc.py +524 -0
- bazaar_compute_node/cli.py +382 -0
- bazaar_compute_node/contrib/__init__.py +1 -0
- bazaar_compute_node/contrib/codex_app_server/__init__.py +63 -0
- bazaar_compute_node/contrib/codex_app_server/approval.py +168 -0
- bazaar_compute_node/contrib/codex_app_server/client.py +408 -0
- bazaar_compute_node/contrib/codex_app_server/events.py +431 -0
- bazaar_compute_node/contrib/codex_app_server/plugin.py +15 -0
- bazaar_compute_node/contrib/codex_app_server/process.py +583 -0
- bazaar_compute_node/contrib/codex_app_server/protocol.py +103 -0
- bazaar_compute_node/contrib/codex_app_server/runtime.py +513 -0
- bazaar_compute_node/contrib/logging/__init__.py +5 -0
- bazaar_compute_node/contrib/logging/audit.py +61 -0
- bazaar_compute_node/contrib/logging/plugin.py +11 -0
- bazaar_compute_node/contrib/sqlite/__init__.py +14 -0
- bazaar_compute_node/contrib/sqlite/codec.py +768 -0
- bazaar_compute_node/contrib/sqlite/database.py +282 -0
- bazaar_compute_node/contrib/sqlite/migrations.py +646 -0
- bazaar_compute_node/contrib/sqlite/plugin.py +11 -0
- bazaar_compute_node/contrib/sqlite/repository.py +1059 -0
- bazaar_compute_node/contrib/wecom/__init__.py +1 -0
- bazaar_compute_node/contrib/wecom/channel.py +960 -0
- bazaar_compute_node/contrib/wecom/markdown.py +146 -0
- bazaar_compute_node/contrib/wecom/plugin.py +29 -0
- bazaar_compute_node/core/__init__.py +5 -0
- bazaar_compute_node/core/approval.py +51 -0
- bazaar_compute_node/core/audit.py +101 -0
- bazaar_compute_node/core/channel.py +121 -0
- bazaar_compute_node/core/client.py +30 -0
- bazaar_compute_node/core/command.py +85 -0
- bazaar_compute_node/core/concurrency.py +29 -0
- bazaar_compute_node/core/correlation.py +48 -0
- bazaar_compute_node/core/instruction.py +224 -0
- bazaar_compute_node/core/lifecycle.py +48 -0
- bazaar_compute_node/core/models/__init__.py +63 -0
- bazaar_compute_node/core/models/entities.py +514 -0
- bazaar_compute_node/core/models/states.py +369 -0
- bazaar_compute_node/core/observability.py +47 -0
- bazaar_compute_node/core/orchestration/__init__.py +5 -0
- bazaar_compute_node/core/orchestration/command.py +614 -0
- bazaar_compute_node/core/orchestration/services.py +135 -0
- bazaar_compute_node/core/orchestration/session.py +891 -0
- bazaar_compute_node/core/orchestration/turn.py +451 -0
- bazaar_compute_node/core/outcomes.py +51 -0
- bazaar_compute_node/core/paths.py +19 -0
- bazaar_compute_node/core/runtime.py +118 -0
- bazaar_compute_node/core/storage.py +167 -0
- bazaar_compute_node-0.1.3.dist-info/METADATA +178 -0
- bazaar_compute_node-0.1.3.dist-info/RECORD +62 -0
- bazaar_compute_node-0.1.3.dist-info/WHEEL +4 -0
- bazaar_compute_node-0.1.3.dist-info/entry_points.txt +15 -0
- bazaar_compute_node-0.1.3.dist-info/licenses/LICENSE +613 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from dataclasses import dataclass, field, replace
|
|
5
|
+
from typing import Self
|
|
6
|
+
|
|
7
|
+
from .states import (
|
|
8
|
+
FRESH_CHECK_TRANSITIONS,
|
|
9
|
+
OUTBOUND_DELIVERY_TRANSITIONS,
|
|
10
|
+
RUNTIME_TURN_TRANSITIONS,
|
|
11
|
+
ApprovalDecision,
|
|
12
|
+
ChannelTargetKind,
|
|
13
|
+
FreshCheckState,
|
|
14
|
+
OutboundDeliveryState,
|
|
15
|
+
RuntimeEventState,
|
|
16
|
+
RuntimeTurnState,
|
|
17
|
+
StreamEventKind,
|
|
18
|
+
ensure_transition,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
Metadata = Mapping[str, object]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _validate_text(value: str, field_name: str) -> None:
|
|
25
|
+
if not isinstance(value, str) or not value:
|
|
26
|
+
raise ValueError(f"{field_name} must be a non-empty string")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _validate_non_negative(value: int, field_name: str) -> None:
|
|
30
|
+
if not isinstance(value, int) or value < 0:
|
|
31
|
+
raise ValueError(f"{field_name} must be a non-negative integer")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class ChannelSession:
|
|
36
|
+
id: str
|
|
37
|
+
channel: str
|
|
38
|
+
provider_thread_id: str
|
|
39
|
+
created_at_ms: int
|
|
40
|
+
updated_at_ms: int
|
|
41
|
+
target_kind: ChannelTargetKind = ChannelTargetKind.DM
|
|
42
|
+
following: bool = True
|
|
43
|
+
last_inbound_at_ms: int | None = None
|
|
44
|
+
last_outbound_at_ms: int | None = None
|
|
45
|
+
metadata: Metadata = field(default_factory=dict)
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
_validate_text(self.id, "id")
|
|
49
|
+
_validate_text(self.channel, "channel")
|
|
50
|
+
_validate_text(self.provider_thread_id, "provider_thread_id")
|
|
51
|
+
_validate_non_negative(self.created_at_ms, "created_at_ms")
|
|
52
|
+
_validate_non_negative(self.updated_at_ms, "updated_at_ms")
|
|
53
|
+
if not isinstance(self.target_kind, ChannelTargetKind):
|
|
54
|
+
raise TypeError("target_kind must be a ChannelTargetKind")
|
|
55
|
+
if self.last_inbound_at_ms is not None:
|
|
56
|
+
_validate_non_negative(self.last_inbound_at_ms, "last_inbound_at_ms")
|
|
57
|
+
if self.last_outbound_at_ms is not None:
|
|
58
|
+
_validate_non_negative(self.last_outbound_at_ms, "last_outbound_at_ms")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True, slots=True)
|
|
62
|
+
class BcnSession:
|
|
63
|
+
id: str
|
|
64
|
+
channel_session_id: str
|
|
65
|
+
workspace_id: str
|
|
66
|
+
created_at_ms: int
|
|
67
|
+
updated_at_ms: int
|
|
68
|
+
last_activity_at_ms: int | None = None
|
|
69
|
+
metadata: Metadata = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
def __post_init__(self) -> None:
|
|
72
|
+
_validate_text(self.id, "id")
|
|
73
|
+
_validate_text(self.channel_session_id, "channel_session_id")
|
|
74
|
+
_validate_text(self.workspace_id, "workspace_id")
|
|
75
|
+
_validate_non_negative(self.created_at_ms, "created_at_ms")
|
|
76
|
+
_validate_non_negative(self.updated_at_ms, "updated_at_ms")
|
|
77
|
+
if self.last_activity_at_ms is not None:
|
|
78
|
+
_validate_non_negative(self.last_activity_at_ms, "last_activity_at_ms")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True, slots=True)
|
|
82
|
+
class RuntimeSession:
|
|
83
|
+
id: str
|
|
84
|
+
bcn_session_id: str
|
|
85
|
+
channel_session_id: str
|
|
86
|
+
runtime: str
|
|
87
|
+
workspace_id: str
|
|
88
|
+
created_at_ms: int
|
|
89
|
+
updated_at_ms: int
|
|
90
|
+
provider_thread_id: str | None = None
|
|
91
|
+
metadata: Metadata = field(default_factory=dict)
|
|
92
|
+
|
|
93
|
+
def __post_init__(self) -> None:
|
|
94
|
+
_validate_text(self.id, "id")
|
|
95
|
+
_validate_text(self.bcn_session_id, "bcn_session_id")
|
|
96
|
+
_validate_text(self.channel_session_id, "channel_session_id")
|
|
97
|
+
_validate_text(self.runtime, "runtime")
|
|
98
|
+
_validate_text(self.workspace_id, "workspace_id")
|
|
99
|
+
_validate_non_negative(self.created_at_ms, "created_at_ms")
|
|
100
|
+
_validate_non_negative(self.updated_at_ms, "updated_at_ms")
|
|
101
|
+
if self.provider_thread_id is not None:
|
|
102
|
+
_validate_text(self.provider_thread_id, "provider_thread_id")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True, slots=True)
|
|
106
|
+
class RuntimeAttempt:
|
|
107
|
+
turn_id: str
|
|
108
|
+
session_id: str
|
|
109
|
+
client_user_message_id: str
|
|
110
|
+
started_at_ms: int
|
|
111
|
+
|
|
112
|
+
def __post_init__(self) -> None:
|
|
113
|
+
_validate_text(self.turn_id, "turn_id")
|
|
114
|
+
_validate_text(self.session_id, "session_id")
|
|
115
|
+
_validate_text(self.client_user_message_id, "client_user_message_id")
|
|
116
|
+
_validate_non_negative(self.started_at_ms, "started_at_ms")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True, slots=True)
|
|
120
|
+
class RuntimeTurn:
|
|
121
|
+
turn_id: str
|
|
122
|
+
session_id: str
|
|
123
|
+
state: RuntimeTurnState
|
|
124
|
+
started_at_ms: int
|
|
125
|
+
provider_turn_id: str | None = None
|
|
126
|
+
client_user_message_id: str | None = None
|
|
127
|
+
completed_at_ms: int | None = None
|
|
128
|
+
latest_event_name: str | None = None
|
|
129
|
+
error_kind: str | None = None
|
|
130
|
+
error_message: str | None = None
|
|
131
|
+
metadata: Metadata = field(default_factory=dict)
|
|
132
|
+
|
|
133
|
+
def __post_init__(self) -> None:
|
|
134
|
+
_validate_text(self.turn_id, "turn_id")
|
|
135
|
+
_validate_text(self.session_id, "session_id")
|
|
136
|
+
_validate_non_negative(self.started_at_ms, "started_at_ms")
|
|
137
|
+
if self.provider_turn_id is not None:
|
|
138
|
+
_validate_text(self.provider_turn_id, "provider_turn_id")
|
|
139
|
+
if self.client_user_message_id is not None:
|
|
140
|
+
_validate_text(self.client_user_message_id, "client_user_message_id")
|
|
141
|
+
if self.completed_at_ms is not None:
|
|
142
|
+
_validate_non_negative(self.completed_at_ms, "completed_at_ms")
|
|
143
|
+
|
|
144
|
+
def transition_to(
|
|
145
|
+
self,
|
|
146
|
+
state: RuntimeTurnState,
|
|
147
|
+
*,
|
|
148
|
+
at_ms: int,
|
|
149
|
+
error_kind: str | None = None,
|
|
150
|
+
error_message: str | None = None,
|
|
151
|
+
latest_event_name: str | None = None,
|
|
152
|
+
) -> Self:
|
|
153
|
+
_validate_non_negative(at_ms, "at_ms")
|
|
154
|
+
ensure_transition("runtime_turn", self.state, state, RUNTIME_TURN_TRANSITIONS)
|
|
155
|
+
if state is self.state:
|
|
156
|
+
return self
|
|
157
|
+
completed_at_ms = (
|
|
158
|
+
at_ms
|
|
159
|
+
if state
|
|
160
|
+
in {
|
|
161
|
+
RuntimeTurnState.COMPLETED,
|
|
162
|
+
RuntimeTurnState.FAILED,
|
|
163
|
+
RuntimeTurnState.CANCELLED,
|
|
164
|
+
RuntimeTurnState.UNKNOWN,
|
|
165
|
+
}
|
|
166
|
+
else self.completed_at_ms
|
|
167
|
+
)
|
|
168
|
+
return replace(
|
|
169
|
+
self,
|
|
170
|
+
state=state,
|
|
171
|
+
completed_at_ms=completed_at_ms,
|
|
172
|
+
latest_event_name=latest_event_name or self.latest_event_name,
|
|
173
|
+
error_kind=error_kind or self.error_kind,
|
|
174
|
+
error_message=error_message or self.error_message,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass(frozen=True, slots=True)
|
|
179
|
+
class InboundAttachment:
|
|
180
|
+
attachment_id: str
|
|
181
|
+
name: str
|
|
182
|
+
kind: str
|
|
183
|
+
state: str
|
|
184
|
+
media_type: str | None = None
|
|
185
|
+
relative_path: str | None = None
|
|
186
|
+
size_bytes: int | None = None
|
|
187
|
+
error: str | None = None
|
|
188
|
+
|
|
189
|
+
def __post_init__(self) -> None:
|
|
190
|
+
for value, field_name in (
|
|
191
|
+
(self.attachment_id, "attachment_id"),
|
|
192
|
+
(self.name, "name"),
|
|
193
|
+
(self.kind, "kind"),
|
|
194
|
+
(self.state, "state"),
|
|
195
|
+
):
|
|
196
|
+
_validate_text(value, field_name)
|
|
197
|
+
if self.state not in {"ready", "failed"}:
|
|
198
|
+
raise ValueError("attachment state must be ready or failed")
|
|
199
|
+
if self.state == "ready" and self.relative_path is None:
|
|
200
|
+
raise ValueError("ready attachment must have a relative path")
|
|
201
|
+
if self.state == "failed" and self.relative_path is not None:
|
|
202
|
+
raise ValueError("failed attachment cannot have a relative path")
|
|
203
|
+
if self.size_bytes is not None:
|
|
204
|
+
_validate_non_negative(self.size_bytes, "size_bytes")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass(frozen=True, slots=True)
|
|
208
|
+
class InboundMessage:
|
|
209
|
+
seq: int
|
|
210
|
+
message_id: str
|
|
211
|
+
session_id: str
|
|
212
|
+
channel_session_id: str
|
|
213
|
+
channel: str
|
|
214
|
+
provider_thread_id: str
|
|
215
|
+
provider_message_id: str
|
|
216
|
+
received_at_ms: int
|
|
217
|
+
sender: str | None
|
|
218
|
+
message_type: str
|
|
219
|
+
canonical_target: str
|
|
220
|
+
body: str
|
|
221
|
+
target_kind: ChannelTargetKind = ChannelTargetKind.DM
|
|
222
|
+
mentions_agent: bool = False
|
|
223
|
+
notifies_runtime: bool = True
|
|
224
|
+
attachments: tuple[InboundAttachment, ...] = ()
|
|
225
|
+
provider_time_ms: int | None = None
|
|
226
|
+
reply_to_message_id: str | None = None
|
|
227
|
+
provider_payload_ref: str | None = None
|
|
228
|
+
metadata: Metadata = field(default_factory=dict)
|
|
229
|
+
|
|
230
|
+
def __post_init__(self) -> None:
|
|
231
|
+
_validate_non_negative(self.seq, "seq")
|
|
232
|
+
for value, field_name in (
|
|
233
|
+
(self.message_id, "message_id"),
|
|
234
|
+
(self.session_id, "session_id"),
|
|
235
|
+
(self.channel_session_id, "channel_session_id"),
|
|
236
|
+
(self.channel, "channel"),
|
|
237
|
+
(self.provider_thread_id, "provider_thread_id"),
|
|
238
|
+
(self.provider_message_id, "provider_message_id"),
|
|
239
|
+
(self.message_type, "message_type"),
|
|
240
|
+
(self.canonical_target, "canonical_target"),
|
|
241
|
+
):
|
|
242
|
+
_validate_text(value, field_name)
|
|
243
|
+
_validate_non_negative(self.received_at_ms, "received_at_ms")
|
|
244
|
+
if self.sender is not None:
|
|
245
|
+
_validate_text(self.sender, "sender")
|
|
246
|
+
if self.reply_to_message_id is not None:
|
|
247
|
+
_validate_text(self.reply_to_message_id, "reply_to_message_id")
|
|
248
|
+
if not isinstance(self.target_kind, ChannelTargetKind):
|
|
249
|
+
raise TypeError("target_kind must be a ChannelTargetKind")
|
|
250
|
+
if self.provider_time_ms is not None:
|
|
251
|
+
_validate_non_negative(self.provider_time_ms, "provider_time_ms")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@dataclass(frozen=True, slots=True)
|
|
255
|
+
class OutboundMessage:
|
|
256
|
+
outbound_message_id: str
|
|
257
|
+
command_id: str
|
|
258
|
+
session_id: str
|
|
259
|
+
channel_session_id: str
|
|
260
|
+
target: str
|
|
261
|
+
body: str
|
|
262
|
+
state: OutboundDeliveryState
|
|
263
|
+
fresh_check_state: FreshCheckState
|
|
264
|
+
created_at_ms: int
|
|
265
|
+
reply_to_message_id: str | None = None
|
|
266
|
+
snapshot_seq: int | None = None
|
|
267
|
+
current_inbound_seq: int | None = None
|
|
268
|
+
provider_message_id: str | None = None
|
|
269
|
+
provider_receipt_ref: str | None = None
|
|
270
|
+
provider_attempted_at_ms: int | None = None
|
|
271
|
+
completed_at_ms: int | None = None
|
|
272
|
+
draft_saved_at_ms: int | None = None
|
|
273
|
+
error_kind: str | None = None
|
|
274
|
+
error_message: str | None = None
|
|
275
|
+
next_action: str | None = None
|
|
276
|
+
metadata: Metadata = field(default_factory=dict)
|
|
277
|
+
|
|
278
|
+
def __post_init__(self) -> None:
|
|
279
|
+
for value, field_name in (
|
|
280
|
+
(self.outbound_message_id, "outbound_message_id"),
|
|
281
|
+
(self.command_id, "command_id"),
|
|
282
|
+
(self.session_id, "session_id"),
|
|
283
|
+
(self.channel_session_id, "channel_session_id"),
|
|
284
|
+
(self.target, "target"),
|
|
285
|
+
):
|
|
286
|
+
_validate_text(value, field_name)
|
|
287
|
+
_validate_non_negative(self.created_at_ms, "created_at_ms")
|
|
288
|
+
for value, field_name in ((self.reply_to_message_id, "reply_to_message_id"),):
|
|
289
|
+
if value is not None:
|
|
290
|
+
_validate_text(value, field_name)
|
|
291
|
+
for value, field_name in (
|
|
292
|
+
(self.snapshot_seq, "snapshot_seq"),
|
|
293
|
+
(self.current_inbound_seq, "current_inbound_seq"),
|
|
294
|
+
(self.provider_attempted_at_ms, "provider_attempted_at_ms"),
|
|
295
|
+
(self.completed_at_ms, "completed_at_ms"),
|
|
296
|
+
(self.draft_saved_at_ms, "draft_saved_at_ms"),
|
|
297
|
+
):
|
|
298
|
+
if value is not None:
|
|
299
|
+
_validate_non_negative(value, field_name)
|
|
300
|
+
|
|
301
|
+
def record_fresh_check(
|
|
302
|
+
self,
|
|
303
|
+
state: FreshCheckState,
|
|
304
|
+
*,
|
|
305
|
+
snapshot_seq: int | None,
|
|
306
|
+
current_inbound_seq: int | None,
|
|
307
|
+
) -> Self:
|
|
308
|
+
if snapshot_seq is not None:
|
|
309
|
+
_validate_non_negative(snapshot_seq, "snapshot_seq")
|
|
310
|
+
if current_inbound_seq is not None:
|
|
311
|
+
_validate_non_negative(current_inbound_seq, "current_inbound_seq")
|
|
312
|
+
if state is FreshCheckState.PASSED:
|
|
313
|
+
if snapshot_seq is None or current_inbound_seq is None:
|
|
314
|
+
raise ValueError(
|
|
315
|
+
"a passed fresh check requires both sequence boundaries"
|
|
316
|
+
)
|
|
317
|
+
if current_inbound_seq > snapshot_seq:
|
|
318
|
+
raise ValueError(
|
|
319
|
+
"a passed fresh check cannot observe a newer inbound sequence"
|
|
320
|
+
)
|
|
321
|
+
ensure_transition(
|
|
322
|
+
"fresh_check",
|
|
323
|
+
self.fresh_check_state,
|
|
324
|
+
state,
|
|
325
|
+
FRESH_CHECK_TRANSITIONS,
|
|
326
|
+
)
|
|
327
|
+
return replace(
|
|
328
|
+
self,
|
|
329
|
+
fresh_check_state=state,
|
|
330
|
+
snapshot_seq=snapshot_seq,
|
|
331
|
+
current_inbound_seq=current_inbound_seq,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def transition_to(
|
|
335
|
+
self,
|
|
336
|
+
state: OutboundDeliveryState,
|
|
337
|
+
*,
|
|
338
|
+
at_ms: int,
|
|
339
|
+
save_draft: bool = True,
|
|
340
|
+
provider_message_id: str | None = None,
|
|
341
|
+
provider_receipt_ref: str | None = None,
|
|
342
|
+
error_kind: str | None = None,
|
|
343
|
+
error_message: str | None = None,
|
|
344
|
+
next_action: str | None = None,
|
|
345
|
+
) -> Self:
|
|
346
|
+
_validate_non_negative(at_ms, "at_ms")
|
|
347
|
+
ensure_transition(
|
|
348
|
+
"outbound_delivery", self.state, state, OUTBOUND_DELIVERY_TRANSITIONS
|
|
349
|
+
)
|
|
350
|
+
if state is self.state:
|
|
351
|
+
return self
|
|
352
|
+
if (
|
|
353
|
+
state
|
|
354
|
+
in {
|
|
355
|
+
OutboundDeliveryState.PENDING,
|
|
356
|
+
OutboundDeliveryState.QUEUED,
|
|
357
|
+
OutboundDeliveryState.SENT,
|
|
358
|
+
OutboundDeliveryState.PARTIAL,
|
|
359
|
+
}
|
|
360
|
+
and self.fresh_check_state is not FreshCheckState.PASSED
|
|
361
|
+
):
|
|
362
|
+
raise ValueError("outbound delivery requires a passed fresh check")
|
|
363
|
+
completed_at_ms = (
|
|
364
|
+
at_ms
|
|
365
|
+
if state
|
|
366
|
+
in {
|
|
367
|
+
OutboundDeliveryState.SENT,
|
|
368
|
+
OutboundDeliveryState.PARTIAL,
|
|
369
|
+
OutboundDeliveryState.FAILED,
|
|
370
|
+
OutboundDeliveryState.UNKNOWN,
|
|
371
|
+
OutboundDeliveryState.REJECTED,
|
|
372
|
+
}
|
|
373
|
+
else self.completed_at_ms
|
|
374
|
+
)
|
|
375
|
+
draft_saved_at_ms = (
|
|
376
|
+
at_ms
|
|
377
|
+
if state is OutboundDeliveryState.REJECTED and save_draft
|
|
378
|
+
else self.draft_saved_at_ms
|
|
379
|
+
)
|
|
380
|
+
return replace(
|
|
381
|
+
self,
|
|
382
|
+
state=state,
|
|
383
|
+
provider_message_id=provider_message_id or self.provider_message_id,
|
|
384
|
+
provider_receipt_ref=provider_receipt_ref or self.provider_receipt_ref,
|
|
385
|
+
completed_at_ms=completed_at_ms,
|
|
386
|
+
draft_saved_at_ms=draft_saved_at_ms,
|
|
387
|
+
error_kind=error_kind or self.error_kind,
|
|
388
|
+
error_message=error_message or self.error_message,
|
|
389
|
+
next_action=next_action or self.next_action,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@dataclass(frozen=True, slots=True)
|
|
394
|
+
class ConsumerCursor:
|
|
395
|
+
session_id: str
|
|
396
|
+
delivered_through_seq: int = 0
|
|
397
|
+
inbox_snapshot_seq: int | None = None
|
|
398
|
+
inbox_snapshot_source: str | None = None
|
|
399
|
+
inbox_snapshot_at_ms: int | None = None
|
|
400
|
+
last_check_at_ms: int | None = None
|
|
401
|
+
last_read_at_ms: int | None = None
|
|
402
|
+
updated_at_ms: int = 0
|
|
403
|
+
|
|
404
|
+
def __post_init__(self) -> None:
|
|
405
|
+
_validate_text(self.session_id, "session_id")
|
|
406
|
+
_validate_non_negative(self.delivered_through_seq, "delivered_through_seq")
|
|
407
|
+
_validate_non_negative(self.updated_at_ms, "updated_at_ms")
|
|
408
|
+
for value, field_name in (
|
|
409
|
+
(self.inbox_snapshot_seq, "inbox_snapshot_seq"),
|
|
410
|
+
(self.inbox_snapshot_at_ms, "inbox_snapshot_at_ms"),
|
|
411
|
+
(self.last_check_at_ms, "last_check_at_ms"),
|
|
412
|
+
(self.last_read_at_ms, "last_read_at_ms"),
|
|
413
|
+
):
|
|
414
|
+
if value is not None:
|
|
415
|
+
_validate_non_negative(value, field_name)
|
|
416
|
+
if (
|
|
417
|
+
self.inbox_snapshot_seq is not None
|
|
418
|
+
and self.inbox_snapshot_seq < self.delivered_through_seq
|
|
419
|
+
):
|
|
420
|
+
raise ValueError("inbox_snapshot_seq cannot precede delivered_through_seq")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@dataclass(frozen=True, slots=True)
|
|
424
|
+
class ApprovalRequest:
|
|
425
|
+
request_id: str
|
|
426
|
+
session_id: str
|
|
427
|
+
runtime_session_id: str
|
|
428
|
+
action: str
|
|
429
|
+
created_at_ms: int
|
|
430
|
+
turn_id: str | None = None
|
|
431
|
+
metadata: Metadata = field(default_factory=dict)
|
|
432
|
+
|
|
433
|
+
def __post_init__(self) -> None:
|
|
434
|
+
for value, field_name in (
|
|
435
|
+
(self.request_id, "request_id"),
|
|
436
|
+
(self.session_id, "session_id"),
|
|
437
|
+
(self.runtime_session_id, "runtime_session_id"),
|
|
438
|
+
(self.action, "action"),
|
|
439
|
+
):
|
|
440
|
+
_validate_text(value, field_name)
|
|
441
|
+
_validate_non_negative(self.created_at_ms, "created_at_ms")
|
|
442
|
+
if self.turn_id is not None:
|
|
443
|
+
_validate_text(self.turn_id, "turn_id")
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
@dataclass(frozen=True, slots=True)
|
|
447
|
+
class ApprovalResult:
|
|
448
|
+
request_id: str
|
|
449
|
+
decision: ApprovalDecision
|
|
450
|
+
decided_at_ms: int
|
|
451
|
+
reason: str | None = None
|
|
452
|
+
|
|
453
|
+
def __post_init__(self) -> None:
|
|
454
|
+
_validate_text(self.request_id, "request_id")
|
|
455
|
+
_validate_non_negative(self.decided_at_ms, "decided_at_ms")
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@dataclass(frozen=True, slots=True)
|
|
459
|
+
class StreamEvent:
|
|
460
|
+
kind: StreamEventKind
|
|
461
|
+
created_at_ms: int
|
|
462
|
+
session_id: str
|
|
463
|
+
stream_id: str | None = None
|
|
464
|
+
content: str | None = None
|
|
465
|
+
|
|
466
|
+
def __post_init__(self) -> None:
|
|
467
|
+
if not isinstance(self.kind, StreamEventKind):
|
|
468
|
+
raise TypeError("kind must be a StreamEventKind")
|
|
469
|
+
_validate_non_negative(self.created_at_ms, "created_at_ms")
|
|
470
|
+
_validate_text(self.session_id, "session_id")
|
|
471
|
+
if self.stream_id is not None:
|
|
472
|
+
_validate_text(self.stream_id, "stream_id")
|
|
473
|
+
if self.content is not None and not isinstance(self.content, str):
|
|
474
|
+
raise TypeError("content must be a string when present")
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@dataclass(frozen=True, slots=True)
|
|
478
|
+
class RuntimeEvent:
|
|
479
|
+
event_seq: int
|
|
480
|
+
event_id: str
|
|
481
|
+
created_at_ms: int
|
|
482
|
+
level: str
|
|
483
|
+
event_name: str
|
|
484
|
+
state: RuntimeEventState
|
|
485
|
+
duration_ms: int | None = None
|
|
486
|
+
node_id: str | None = None
|
|
487
|
+
channel: str | None = None
|
|
488
|
+
channel_session_id: str | None = None
|
|
489
|
+
bcn_session_id: str | None = None
|
|
490
|
+
runtime_session_id: str | None = None
|
|
491
|
+
turn_id: str | None = None
|
|
492
|
+
request_id: str | None = None
|
|
493
|
+
command_id: str | None = None
|
|
494
|
+
inbound_seq: int | None = None
|
|
495
|
+
outbound_message_id: str | None = None
|
|
496
|
+
error_kind: str | None = None
|
|
497
|
+
error_type: str | None = None
|
|
498
|
+
error_message: str | None = None
|
|
499
|
+
traceback_ref: str | None = None
|
|
500
|
+
runtime: str | None = None
|
|
501
|
+
metadata: Metadata = field(default_factory=dict)
|
|
502
|
+
|
|
503
|
+
def __post_init__(self) -> None:
|
|
504
|
+
_validate_non_negative(self.event_seq, "event_seq")
|
|
505
|
+
_validate_text(self.event_id, "event_id")
|
|
506
|
+
_validate_non_negative(self.created_at_ms, "created_at_ms")
|
|
507
|
+
_validate_text(self.level, "level")
|
|
508
|
+
_validate_text(self.event_name, "event_name")
|
|
509
|
+
for value, field_name in (
|
|
510
|
+
(self.duration_ms, "duration_ms"),
|
|
511
|
+
(self.inbound_seq, "inbound_seq"),
|
|
512
|
+
):
|
|
513
|
+
if value is not None:
|
|
514
|
+
_validate_non_negative(value, field_name)
|