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.
Files changed (62) hide show
  1. bazaar_compute_node/__init__.py +3 -0
  2. bazaar_compute_node/app/__init__.py +1 -0
  3. bazaar_compute_node/app/application.py +398 -0
  4. bazaar_compute_node/app/attachments.py +154 -0
  5. bazaar_compute_node/app/command.py +342 -0
  6. bazaar_compute_node/app/config.py +121 -0
  7. bazaar_compute_node/app/registry.py +120 -0
  8. bazaar_compute_node/app/transport.py +264 -0
  9. bazaar_compute_node/app/windows_pipe.py +463 -0
  10. bazaar_compute_node/app/wrapper.py +63 -0
  11. bazaar_compute_node/bcc.py +524 -0
  12. bazaar_compute_node/cli.py +382 -0
  13. bazaar_compute_node/contrib/__init__.py +1 -0
  14. bazaar_compute_node/contrib/codex_app_server/__init__.py +63 -0
  15. bazaar_compute_node/contrib/codex_app_server/approval.py +168 -0
  16. bazaar_compute_node/contrib/codex_app_server/client.py +408 -0
  17. bazaar_compute_node/contrib/codex_app_server/events.py +431 -0
  18. bazaar_compute_node/contrib/codex_app_server/plugin.py +15 -0
  19. bazaar_compute_node/contrib/codex_app_server/process.py +583 -0
  20. bazaar_compute_node/contrib/codex_app_server/protocol.py +103 -0
  21. bazaar_compute_node/contrib/codex_app_server/runtime.py +513 -0
  22. bazaar_compute_node/contrib/logging/__init__.py +5 -0
  23. bazaar_compute_node/contrib/logging/audit.py +61 -0
  24. bazaar_compute_node/contrib/logging/plugin.py +11 -0
  25. bazaar_compute_node/contrib/sqlite/__init__.py +14 -0
  26. bazaar_compute_node/contrib/sqlite/codec.py +768 -0
  27. bazaar_compute_node/contrib/sqlite/database.py +282 -0
  28. bazaar_compute_node/contrib/sqlite/migrations.py +646 -0
  29. bazaar_compute_node/contrib/sqlite/plugin.py +11 -0
  30. bazaar_compute_node/contrib/sqlite/repository.py +1059 -0
  31. bazaar_compute_node/contrib/wecom/__init__.py +1 -0
  32. bazaar_compute_node/contrib/wecom/channel.py +960 -0
  33. bazaar_compute_node/contrib/wecom/markdown.py +146 -0
  34. bazaar_compute_node/contrib/wecom/plugin.py +29 -0
  35. bazaar_compute_node/core/__init__.py +5 -0
  36. bazaar_compute_node/core/approval.py +51 -0
  37. bazaar_compute_node/core/audit.py +101 -0
  38. bazaar_compute_node/core/channel.py +121 -0
  39. bazaar_compute_node/core/client.py +30 -0
  40. bazaar_compute_node/core/command.py +85 -0
  41. bazaar_compute_node/core/concurrency.py +29 -0
  42. bazaar_compute_node/core/correlation.py +48 -0
  43. bazaar_compute_node/core/instruction.py +224 -0
  44. bazaar_compute_node/core/lifecycle.py +48 -0
  45. bazaar_compute_node/core/models/__init__.py +63 -0
  46. bazaar_compute_node/core/models/entities.py +514 -0
  47. bazaar_compute_node/core/models/states.py +369 -0
  48. bazaar_compute_node/core/observability.py +47 -0
  49. bazaar_compute_node/core/orchestration/__init__.py +5 -0
  50. bazaar_compute_node/core/orchestration/command.py +614 -0
  51. bazaar_compute_node/core/orchestration/services.py +135 -0
  52. bazaar_compute_node/core/orchestration/session.py +891 -0
  53. bazaar_compute_node/core/orchestration/turn.py +451 -0
  54. bazaar_compute_node/core/outcomes.py +51 -0
  55. bazaar_compute_node/core/paths.py +19 -0
  56. bazaar_compute_node/core/runtime.py +118 -0
  57. bazaar_compute_node/core/storage.py +167 -0
  58. bazaar_compute_node-0.1.3.dist-info/METADATA +178 -0
  59. bazaar_compute_node-0.1.3.dist-info/RECORD +62 -0
  60. bazaar_compute_node-0.1.3.dist-info/WHEEL +4 -0
  61. bazaar_compute_node-0.1.3.dist-info/entry_points.txt +15 -0
  62. bazaar_compute_node-0.1.3.dist-info/licenses/LICENSE +613 -0
@@ -0,0 +1,431 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Callable, Mapping
5
+ from time import time_ns
6
+ from typing import Self, cast
7
+ from uuid import uuid7
8
+
9
+ from ...core.approval import IApprovalHandler
10
+ from ...core.models import (
11
+ RuntimeEvent,
12
+ RuntimeEventState,
13
+ StreamEvent,
14
+ StreamEventKind,
15
+ )
16
+ from ...core.runtime import IRuntimeTurnStream, RuntimeStreamItem
17
+ from .approval import (
18
+ approval_error,
19
+ build_approval_response,
20
+ is_approval_method,
21
+ parse_approval_request,
22
+ )
23
+
24
+ _DURABLE_ITEM_METHODS = {
25
+ "item/started",
26
+ "item/completed",
27
+ "item/autoApprovalReview/started",
28
+ "item/autoApprovalReview/completed",
29
+ }
30
+ _STREAM_EVENT_KINDS = {
31
+ "item/agentMessage/delta": StreamEventKind.AGENT_MESSAGE_DELTA,
32
+ "item/plan/delta": StreamEventKind.PLAN_DELTA,
33
+ "item/reasoning/summaryTextDelta": StreamEventKind.REASONING_SUMMARY_DELTA,
34
+ "item/reasoning/textDelta": StreamEventKind.REASONING_TEXT_DELTA,
35
+ "item/commandExecution/outputDelta": StreamEventKind.COMMAND_OUTPUT_DELTA,
36
+ "item/commandExecution/terminalInteraction": StreamEventKind.COMMAND_INTERACTION,
37
+ "item/fileChange/outputDelta": StreamEventKind.FILE_CHANGE_UPDATE,
38
+ "item/fileChange/patchUpdated": StreamEventKind.FILE_CHANGE_UPDATE,
39
+ "item/mcpToolCall/progress": StreamEventKind.TOOL_PROGRESS,
40
+ }
41
+ from .client import parse_error_notification, parse_turn_notification
42
+ from .process import JsonlProcessSupervisor
43
+ from .protocol import (
44
+ CodexAppServerProtocolError,
45
+ JsonlMessage,
46
+ JsonlRequestId,
47
+ JsonlTransportError,
48
+ is_request_id,
49
+ )
50
+
51
+
52
+ class CodexTurnEventStream(IRuntimeTurnStream):
53
+ """Normalize one Codex turn's notifications into runtime-neutral items."""
54
+
55
+ def __init__(
56
+ self,
57
+ supervisor: JsonlProcessSupervisor,
58
+ *,
59
+ node_id: str,
60
+ runtime: str,
61
+ session_id: str,
62
+ runtime_session_id: str,
63
+ turn_id: str,
64
+ provider_thread_id: str,
65
+ provider_turn_id: str | None,
66
+ approval_handler: IApprovalHandler | None = None,
67
+ approval_timeout: float = 30,
68
+ initial_error: BaseException | None = None,
69
+ initial_error_kind: str = "provider_unknown",
70
+ initial_error_state: RuntimeEventState = RuntimeEventState.UNKNOWN,
71
+ on_closed: Callable[[], None] | None = None,
72
+ ) -> None:
73
+ self._supervisor = supervisor
74
+ self._node_id = node_id
75
+ self._runtime = runtime
76
+ self._session_id = session_id
77
+ self._runtime_session_id = runtime_session_id
78
+ self._turn_id = turn_id
79
+ self._provider_thread_id = provider_thread_id
80
+ self._provider_turn_id = provider_turn_id
81
+ self._approval_handler = approval_handler
82
+ self._approval_timeout = approval_timeout
83
+ self._responded_request_ids: set[JsonlRequestId] = set()
84
+ self._initial_error = initial_error
85
+ self._initial_error_kind = initial_error_kind
86
+ self._initial_error_state = initial_error_state
87
+ self._initial_emitted = False
88
+ self._terminal_emitted = False
89
+ self._closed = False
90
+ self._on_closed = on_closed
91
+ self._closed_callback_called = False
92
+
93
+ def __aiter__(self) -> Self:
94
+ return self
95
+
96
+ async def __anext__(self) -> RuntimeStreamItem:
97
+ if self._closed or self._terminal_emitted:
98
+ raise StopAsyncIteration
99
+ if not self._initial_emitted:
100
+ self._initial_emitted = True
101
+ if self._initial_error is not None:
102
+ return self._terminal_event(
103
+ self._initial_error_state,
104
+ event_name="codex.turn.start.unknown",
105
+ error_kind=self._initial_error_kind,
106
+ error_message=_safe_error_message(self._initial_error),
107
+ metadata={"provider_method": "turn/start"},
108
+ )
109
+ return self._event(
110
+ event_name="codex.turn.started",
111
+ state=RuntimeEventState.STARTED,
112
+ metadata={
113
+ "provider_method": "turn/start",
114
+ "provider_thread_id": self._provider_thread_id,
115
+ "provider_turn_id": self._provider_turn_id,
116
+ },
117
+ )
118
+
119
+ while not self._closed:
120
+ try:
121
+ message = await self._supervisor.receive()
122
+ except JsonlTransportError as error:
123
+ return self._terminal_event(
124
+ RuntimeEventState.UNKNOWN,
125
+ event_name="codex.turn.transport.unknown",
126
+ error_kind="provider_unknown",
127
+ error_message=_safe_error_message(error),
128
+ metadata={"provider_method": "transport"},
129
+ )
130
+ try:
131
+ if await self._handle_provider_request(message):
132
+ continue
133
+ event = self._map_message(message)
134
+ except JsonlTransportError as error:
135
+ return self._terminal_event(
136
+ RuntimeEventState.UNKNOWN,
137
+ event_name="codex.turn.transport.unknown",
138
+ error_kind="provider_unknown",
139
+ error_message=_safe_error_message(error),
140
+ metadata={"provider_method": "transport"},
141
+ )
142
+ except (CodexAppServerProtocolError, TypeError, ValueError) as error:
143
+ return self._terminal_event(
144
+ RuntimeEventState.UNKNOWN,
145
+ event_name="codex.turn.protocol.unknown",
146
+ error_kind="provider_unknown",
147
+ error_message=_safe_error_message(error),
148
+ metadata={"provider_method": "protocol"},
149
+ )
150
+ if event is not None:
151
+ return event
152
+ raise StopAsyncIteration
153
+
154
+ async def aclose(self) -> None:
155
+ self._closed = True
156
+ self._call_closed_callback()
157
+
158
+ def _map_message(self, message: JsonlMessage) -> RuntimeStreamItem | None:
159
+ method = message.get("method")
160
+ params = message.get("params")
161
+ if not isinstance(method, str) or not method:
162
+ return None
163
+ if not isinstance(params, Mapping):
164
+ return None
165
+ if not _belongs_to_thread(params, self._provider_thread_id):
166
+ return None
167
+
168
+ if method == "turn/completed":
169
+ thread_id, turn = parse_turn_notification(message)
170
+ if thread_id != self._provider_thread_id or turn.turn_id != (
171
+ self._provider_turn_id
172
+ ):
173
+ return None
174
+ metadata = self._provider_metadata(method, params)
175
+ metadata["provider_status"] = turn.status
176
+ if turn.status == "completed":
177
+ return self._terminal_event(
178
+ RuntimeEventState.COMPLETED,
179
+ event_name="codex.turn.completed",
180
+ metadata=metadata,
181
+ )
182
+ if turn.status == "failed":
183
+ return self._terminal_event(
184
+ RuntimeEventState.FAILED,
185
+ event_name="codex.turn.failed",
186
+ error_kind="provider_failed",
187
+ error_message=turn.error_message or "Codex turn failed",
188
+ metadata=metadata,
189
+ )
190
+ if turn.status == "interrupted":
191
+ return self._terminal_event(
192
+ RuntimeEventState.CANCELLED,
193
+ event_name="codex.turn.interrupted",
194
+ error_kind="cancelled",
195
+ error_message=turn.error_message,
196
+ metadata=metadata,
197
+ )
198
+ return self._terminal_event(
199
+ RuntimeEventState.UNKNOWN,
200
+ event_name="codex.turn.unknown",
201
+ error_kind="provider_unknown",
202
+ error_message=f"Unsupported Codex turn status: {turn.status}",
203
+ metadata=metadata,
204
+ )
205
+
206
+ if method == "error":
207
+ error = parse_error_notification(message)
208
+ if error.thread_id != self._provider_thread_id or (
209
+ self._provider_turn_id is not None
210
+ and error.turn_id != self._provider_turn_id
211
+ ):
212
+ return None
213
+ metadata = self._provider_metadata(method, params)
214
+ metadata["will_retry"] = error.will_retry
215
+ if error.error_type is not None:
216
+ metadata["provider_error_type"] = error.error_type
217
+ return self._event(
218
+ event_name="codex.turn.error",
219
+ state=RuntimeEventState.STARTED,
220
+ error_type=error.error_type,
221
+ error_message=error.message,
222
+ metadata=metadata,
223
+ )
224
+
225
+ provider_turn_id = _provider_turn_id(params)
226
+ if (
227
+ provider_turn_id is not None
228
+ and self._provider_turn_id is not None
229
+ and provider_turn_id != self._provider_turn_id
230
+ ):
231
+ return None
232
+ if method == "item/reasoning/summaryPartAdded":
233
+ return None
234
+ if method == "turn/progress" or (
235
+ method.startswith("item/") and method not in _DURABLE_ITEM_METHODS
236
+ ):
237
+ stream_id = params.get("itemId")
238
+ if not isinstance(stream_id, str) or not stream_id:
239
+ stream_id = None
240
+ content = params.get("delta")
241
+ if method == "item/mcpToolCall/progress":
242
+ content = params.get("message")
243
+ elif method == "item/commandExecution/terminalInteraction":
244
+ content = params.get("stdin")
245
+ if not isinstance(content, str):
246
+ content = None
247
+ return StreamEvent(
248
+ kind=(
249
+ StreamEventKind.TURN_PROGRESS
250
+ if method == "turn/progress"
251
+ else _STREAM_EVENT_KINDS.get(method, StreamEventKind.ITEM_PROGRESS)
252
+ ),
253
+ created_at_ms=time_ns() // 1_000_000,
254
+ session_id=self._session_id,
255
+ stream_id=stream_id,
256
+ content=content,
257
+ )
258
+ if method == "turn/started" or method in _DURABLE_ITEM_METHODS:
259
+ metadata = self._provider_metadata(method, params)
260
+ if provider_turn_id is not None:
261
+ metadata["provider_turn_id"] = provider_turn_id
262
+ return self._event(
263
+ event_name="codex.turn.progress",
264
+ state=RuntimeEventState.STARTED,
265
+ metadata=metadata,
266
+ )
267
+ return None
268
+
269
+ def _event(
270
+ self,
271
+ *,
272
+ event_name: str,
273
+ state: RuntimeEventState,
274
+ error_kind: str | None = None,
275
+ error_type: str | None = None,
276
+ error_message: str | None = None,
277
+ metadata: Mapping[str, object] | None = None,
278
+ ) -> RuntimeEvent:
279
+ return RuntimeEvent(
280
+ event_seq=0,
281
+ event_id=str(uuid7()),
282
+ created_at_ms=time_ns() // 1_000_000,
283
+ level="error" if error_kind else "info",
284
+ event_name=event_name,
285
+ state=state,
286
+ node_id=self._node_id,
287
+ bcn_session_id=self._session_id,
288
+ runtime_session_id=self._runtime_session_id,
289
+ turn_id=self._turn_id,
290
+ error_kind=error_kind,
291
+ error_type=error_type,
292
+ error_message=error_message,
293
+ runtime=self._runtime,
294
+ metadata=dict(metadata or {}),
295
+ )
296
+
297
+ def _terminal_event(
298
+ self,
299
+ state: RuntimeEventState,
300
+ *,
301
+ event_name: str,
302
+ error_kind: str | None = None,
303
+ error_message: str | None = None,
304
+ metadata: Mapping[str, object] | None = None,
305
+ ) -> RuntimeEvent:
306
+ self._terminal_emitted = True
307
+ self._call_closed_callback()
308
+ return self._event(
309
+ event_name=event_name,
310
+ state=state,
311
+ error_kind=error_kind,
312
+ error_message=error_message,
313
+ metadata=metadata,
314
+ )
315
+
316
+ def _provider_metadata(
317
+ self,
318
+ method: str,
319
+ params: Mapping[str, object],
320
+ ) -> dict[str, object]:
321
+ metadata: dict[str, object] = {
322
+ "provider_method": method,
323
+ "provider_thread_id": self._provider_thread_id,
324
+ }
325
+ if self._provider_turn_id is not None:
326
+ metadata["provider_turn_id"] = self._provider_turn_id
327
+ request_id = params.get("requestId")
328
+ if isinstance(request_id, (int, str)) and not isinstance(request_id, bool):
329
+ metadata["provider_request_id"] = str(request_id)
330
+ return metadata
331
+
332
+ def _call_closed_callback(self) -> None:
333
+ if self._closed_callback_called:
334
+ return
335
+ self._closed_callback_called = True
336
+ if self._on_closed is not None:
337
+ self._on_closed()
338
+
339
+ async def _handle_provider_request(self, message: JsonlMessage) -> bool:
340
+ method = message.get("method")
341
+ request_id = message.get("id")
342
+ if not isinstance(method, str) or request_id is None:
343
+ return False
344
+ if not is_request_id(request_id):
345
+ raise CodexAppServerProtocolError(
346
+ "provider request id must be an integer or string"
347
+ )
348
+ request_id = cast(JsonlRequestId, request_id)
349
+ if request_id in self._responded_request_ids:
350
+ return True
351
+ self._responded_request_ids.add(request_id)
352
+ if not is_approval_method(method):
353
+ await self._respond_with_error(
354
+ request_id,
355
+ CodexAppServerProtocolError(
356
+ f"unsupported provider request method: {method}"
357
+ ),
358
+ )
359
+ raise CodexAppServerProtocolError("unsupported provider request")
360
+ response_attempted = False
361
+ try:
362
+ approval = parse_approval_request(
363
+ message,
364
+ session_id=self._session_id,
365
+ runtime_session_id=self._runtime_session_id,
366
+ turn_id=self._turn_id,
367
+ provider_thread_id=self._provider_thread_id,
368
+ provider_turn_id=self._provider_turn_id,
369
+ )
370
+ if self._approval_handler is None:
371
+ raise CodexAppServerProtocolError(
372
+ "runtime approval handler is not configured"
373
+ )
374
+ result = await self._approval_handler.request_approval(
375
+ approval.request,
376
+ timeout=self._approval_timeout,
377
+ )
378
+ response = build_approval_response(approval, result)
379
+ response_attempted = True
380
+ await self._supervisor.respond(
381
+ request_id,
382
+ result=response,
383
+ timeout=self._approval_timeout,
384
+ )
385
+ except asyncio.CancelledError:
386
+ raise
387
+ except Exception as error:
388
+ if not response_attempted:
389
+ await self._respond_with_error(request_id, error)
390
+ if response_attempted and isinstance(error, JsonlTransportError):
391
+ raise
392
+ raise CodexAppServerProtocolError(
393
+ f"approval bridge failed: {type(error).__name__}"
394
+ ) from error
395
+ return True
396
+
397
+ async def _respond_with_error(
398
+ self,
399
+ request_id: JsonlRequestId,
400
+ error: BaseException,
401
+ ) -> None:
402
+ await self._supervisor.respond(
403
+ request_id,
404
+ error=approval_error(error),
405
+ timeout=self._approval_timeout,
406
+ )
407
+
408
+
409
+ def _belongs_to_thread(params: Mapping[str, object], thread_id: str) -> bool:
410
+ value = params.get("threadId")
411
+ return value == thread_id
412
+
413
+
414
+ def _provider_turn_id(params: Mapping[str, object]) -> str | None:
415
+ value = params.get("turnId")
416
+ if isinstance(value, str) and value:
417
+ return value
418
+ turn = params.get("turn")
419
+ if isinstance(turn, Mapping):
420
+ value = turn.get("id")
421
+ if isinstance(value, str) and value:
422
+ return value
423
+ return None
424
+
425
+
426
+ def _safe_error_message(error: BaseException) -> str:
427
+ message = str(error).strip()
428
+ return message or type(error).__name__
429
+
430
+
431
+ __all__ = ["CodexTurnEventStream"]
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from ...core.runtime import IRuntime, RuntimeCommandContext
4
+ from .runtime import CodexAppServerRuntime
5
+
6
+
7
+ def create_runtime(context: RuntimeCommandContext) -> IRuntime:
8
+ return CodexAppServerRuntime(
9
+ context,
10
+ model=context.runtime_options.get("model"),
11
+ effort=context.runtime_options.get("effort"),
12
+ )
13
+
14
+
15
+ __all__ = ["create_runtime"]