bazaar-compute-node 0.1.10__tar.gz → 0.1.12__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.
Files changed (96) hide show
  1. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/PKG-INFO +2 -2
  2. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/README.md +1 -1
  3. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/pyproject.toml +2 -1
  4. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/pyproject.toml.orig +2 -1
  5. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/agent.py +4 -3
  6. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/transport.py +2 -1
  7. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/wrapper.py +4 -0
  8. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/cli.py +4 -1
  9. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/client.py +3 -3
  10. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/process.py +2 -2
  11. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/protocol.py +1 -1
  12. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/runtime.py +1 -1
  13. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/codec.py +21 -21
  14. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/reminder_repository.py +32 -32
  15. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/repository.py +58 -59
  16. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/scoped_repository.py +47 -45
  17. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/approval.py +52 -19
  18. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/channel.py +3 -1
  19. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/markdown.py +1 -1
  20. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/outbound.py +1 -1
  21. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/wecom/channel.py +2 -1
  22. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/wecom/markdown.py +1 -3
  23. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/channel.py +2 -0
  24. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/models/states.py +2 -2
  25. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/reminder.py +1 -1
  26. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/reminder_schedule.py +3 -3
  27. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/i18n/catalog.py +1 -1
  28. bazaar_compute_node-0.1.12/src/bazaar_compute_node/i18n/english.py +26 -0
  29. bazaar_compute_node-0.1.12/src/bazaar_compute_node/i18n/schinese.py +26 -0
  30. bazaar_compute_node-0.1.10/src/bazaar_compute_node/i18n/english.py +0 -10
  31. bazaar_compute_node-0.1.10/src/bazaar_compute_node/i18n/schinese.py +0 -10
  32. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/LICENSE +0 -0
  33. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/__init__.py +0 -0
  34. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/__init__.py +0 -0
  35. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/agent_management.py +0 -0
  36. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/application.py +0 -0
  37. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/attachments.py +0 -0
  38. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/command.py +0 -0
  39. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/config.py +0 -0
  40. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/registry.py +0 -0
  41. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/reminder_dispatch.py +0 -0
  42. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
  43. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/bcc.py +0 -0
  44. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  45. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  46. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
  47. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
  48. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  49. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  50. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  51. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  52. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  53. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/agent_migration.py +0 -0
  54. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
  55. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/migrations.py +0 -0
  56. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  57. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  58. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/reminder_database.py +0 -0
  59. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/sqlite/reminder_migration.py +0 -0
  60. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
  61. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/api.py +0 -0
  62. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
  63. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
  64. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
  65. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  66. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  67. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
  68. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/__init__.py +0 -0
  69. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/approval.py +0 -0
  70. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/audit.py +0 -0
  71. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/client.py +0 -0
  72. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/command.py +0 -0
  73. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/concurrency.py +0 -0
  74. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/correlation.py +0 -0
  75. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/instruction.py +0 -0
  76. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/lifecycle.py +0 -0
  77. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/models/__init__.py +0 -0
  78. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/models/entities.py +0 -0
  79. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/models/reminder.py +0 -0
  80. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
  81. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/observability.py +0 -0
  82. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  83. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/command.py +0 -0
  84. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
  85. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/error_feedback.py +0 -0
  86. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
  87. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
  88. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
  89. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/session.py +0 -0
  90. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/orchestration/turn.py +0 -0
  91. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/outcomes.py +0 -0
  92. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/paths.py +0 -0
  93. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/runtime.py +0 -0
  94. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/storage.py +0 -0
  95. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/core/timerwheel.py +0 -0
  96. {bazaar_compute_node-0.1.10 → bazaar_compute_node-0.1.12}/src/bazaar_compute_node/i18n/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bazaar-compute-node
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: Runtime-agnostic computer node daemon for agents and channels.
5
5
  License-Expression: AGPL-3.0-only
6
6
  License-File: LICENSE
@@ -67,7 +67,7 @@ bcn stop
67
67
  | --- | --- |
68
68
  | ✅ | Reminders / 定时器 |
69
69
  | ✅ | Attachments / 附件 |
70
- | 🚧 | Multi-Agents / 多 Agent |
70
+ | ✅ | Multi-Agents / 多 Agent |
71
71
  | 🚧 | Teams / 团队协作 |
72
72
 
73
73
  ### Channels / 渠道
@@ -53,7 +53,7 @@ bcn stop
53
53
  | --- | --- |
54
54
  | ✅ | Reminders / 定时器 |
55
55
  | ✅ | Attachments / 附件 |
56
- | 🚧 | Multi-Agents / 多 Agent |
56
+ | ✅ | Multi-Agents / 多 Agent |
57
57
  | 🚧 | Teams / 团队协作 |
58
58
 
59
59
  ### Channels / 渠道
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.10"
3
+ version = "0.1.12"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -35,6 +35,7 @@ logging = "bazaar_compute_node.contrib.logging.plugin:create_audit"
35
35
  [dependency-groups]
36
36
  dev = [
37
37
  "bcn-test-support",
38
+ "pyright==1.1.408",
38
39
  "pytest>=8.3.0",
39
40
  "pytest-asyncio>=0.24.0",
40
41
  "ruff>=0.9.0",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.10"
3
+ version = "0.1.12"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -35,6 +35,7 @@ logging = "bazaar_compute_node.contrib.logging.plugin:create_audit"
35
35
  [dependency-groups]
36
36
  dev = [
37
37
  "bcn-test-support",
38
+ "pyright==1.1.408",
38
39
  "pytest>=8.3.0",
39
40
  "pytest-asyncio>=0.24.0",
40
41
  "ruff>=0.9.0",
@@ -101,6 +101,7 @@ class AgentApplication:
101
101
  attachments=self._attachment_materializer,
102
102
  options=dict(configuration.channel.options),
103
103
  workspace=self.workspace_path,
104
+ translator=self.translator,
104
105
  )
105
106
  )
106
107
  self.channel: IChannel = AgentScopedChannel(self.agent_id, provider_channel)
@@ -443,8 +444,8 @@ class AgentApplication:
443
444
 
444
445
  def _error_feedback_detail(
445
446
  self,
446
- session_id: str,
447
- error_message: str,
447
+ session_id: object,
448
+ error_message: object,
448
449
  ) -> str:
449
450
  if not isinstance(session_id, str) or not session_id:
450
451
  raise ValueError("session_id must be a non-empty string")
@@ -490,7 +491,7 @@ class AgentApplication:
490
491
  )
491
492
  input_data = body.encode() if body is not None else None
492
493
  try:
493
- _stdout, stderr = await process.communicate(input=input_data)
494
+ _, stderr = await process.communicate(input=input_data)
494
495
  except asyncio.CancelledError:
495
496
  if process.returncode is None:
496
497
  process.terminate()
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import json
5
5
  import os
6
+ import platform
6
7
  import secrets
7
8
  import sys
8
9
  import tempfile
@@ -223,7 +224,7 @@ class LocalCommandClient:
223
224
  parsed = urlsplit(endpoint)
224
225
  request = dict(payload)
225
226
  if parsed.scheme == "pipe":
226
- if sys.platform != "win32":
227
+ if platform.system() != "Windows":
227
228
  raise ValueError(
228
229
  "Windows named pipe endpoints are not supported on this platform"
229
230
  )
@@ -32,11 +32,15 @@ def install_bcc_wrapper(bin_dir: Path, *, agent_id: str) -> Path:
32
32
  command_path.write_text(
33
33
  "@echo off\n"
34
34
  f'set "BCN_AGENT_ID={agent_id}"\n'
35
+ 'set "PYTHONUTF8=1"\n'
36
+ 'set "PYTHONIOENCODING=utf-8"\n'
35
37
  f'"{python_executable}" -m bazaar_compute_node.bcc %*\n',
36
38
  encoding="utf-8",
37
39
  )
38
40
  (bin_dir / "bcc.ps1").write_text(
39
41
  f"$env:BCN_AGENT_ID = '{agent_id}'\n"
42
+ "$env:PYTHONUTF8 = '1'\n"
43
+ "$env:PYTHONIOENCODING = 'utf-8'\n"
40
44
  f'& "{python_executable}" -m bazaar_compute_node.bcc @args\n'
41
45
  "exit $LASTEXITCODE\n",
42
46
  encoding="utf-8",
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import argparse
4
4
  import asyncio
5
5
  import os
6
+ import platform
6
7
  import subprocess
7
8
  import sys
8
9
  from collections.abc import Mapping, Sequence
@@ -334,7 +335,9 @@ async def _start_daemon(
334
335
  await asyncio.to_thread(data_dir.mkdir, parents=True, exist_ok=True)
335
336
  endpoint_path = _endpoint_path(args, data_dir)
336
337
  endpoint = local_endpoint_for_path(endpoint_path)
337
- if os.name == "nt" and await _endpoint_is_reachable(endpoint, timeout=0.5):
338
+ if platform.system() == "Windows" and await _endpoint_is_reachable(
339
+ endpoint, timeout=0.5
340
+ ):
338
341
  parser.error("bcn is already running")
339
342
  if os.name != "nt" and await asyncio.to_thread(endpoint_path.exists):
340
343
  parser.error(f"bcn endpoint already exists: {endpoint_path}")
@@ -63,7 +63,7 @@ def build_initialize_params(
63
63
  }
64
64
 
65
65
 
66
- def build_fs_watch_params(path: Path, watch_id: str) -> dict[str, object]:
66
+ def build_fs_watch_params(path: object, watch_id: str) -> dict[str, object]:
67
67
  if not isinstance(path, Path):
68
68
  raise TypeError("path must be a Path")
69
69
  if not path.is_absolute():
@@ -78,7 +78,7 @@ def build_thread_start_params(
78
78
  model: str | None = None,
79
79
  approval_policy: str | None = None,
80
80
  cwd: Path | None = None,
81
- ephemeral: bool | None = None,
81
+ ephemeral: object = None,
82
82
  ) -> dict[str, object]:
83
83
  """Build provider-local parameters for a Codex ``thread/start`` request."""
84
84
 
@@ -509,7 +509,7 @@ def _optional_text(value: object, field_name: str) -> str | None:
509
509
  return value
510
510
 
511
511
 
512
- def _path_text(value: Path, field_name: str) -> str:
512
+ def _path_text(value: object, field_name: str) -> str:
513
513
  if not isinstance(value, Path):
514
514
  raise TypeError(f"{field_name} must be a Path")
515
515
  return str(value)
@@ -253,8 +253,8 @@ class JsonlProcessSupervisor:
253
253
  self,
254
254
  request_id: JsonlRequestId,
255
255
  *,
256
- result: Mapping[str, object] | None = None,
257
- error: Mapping[str, object] | None = None,
256
+ result: object = None,
257
+ error: object = None,
258
258
  timeout: float,
259
259
  ) -> None:
260
260
  """Write one response to a provider-initiated JSON-RPC request."""
@@ -76,7 +76,7 @@ class AppServerProtocolError(ValueError):
76
76
  """The Codex App Server response does not match its provider contract."""
77
77
 
78
78
 
79
- def validate_message(payload: Mapping[str, object]) -> JsonlMessage:
79
+ def validate_message(payload: object) -> JsonlMessage:
80
80
  """Copy a mapping into the transport's provider-local message shape."""
81
81
 
82
82
  if not isinstance(payload, Mapping):
@@ -120,7 +120,7 @@ class Runtime(IRuntime, IAsyncLifecycle):
120
120
  self._stopping = True
121
121
  connections = tuple(self._connections.items())
122
122
  self._connections.clear()
123
- for _session_id, connection in connections:
123
+ for _, connection in connections:
124
124
  try:
125
125
  await connection.supervisor.stop(timeout=timeout)
126
126
  except asyncio.CancelledError:
@@ -22,7 +22,7 @@ from ...core.models import (
22
22
  )
23
23
 
24
24
 
25
- def _channel_session_from_row(row: aiosqlite.Row) -> ChannelSession:
25
+ def channel_session_from_row(row: aiosqlite.Row) -> ChannelSession:
26
26
  following = row["following"]
27
27
  if (
28
28
  isinstance(following, bool)
@@ -54,7 +54,7 @@ def _channel_session_from_row(row: aiosqlite.Row) -> ChannelSession:
54
54
  )
55
55
 
56
56
 
57
- def _bcn_session_from_row(row: aiosqlite.Row) -> BcnSession:
57
+ def bcn_session_from_row(row: aiosqlite.Row) -> BcnSession:
58
58
  return BcnSession(
59
59
  id=_required_text(row["id"], "id"),
60
60
  channel_session_id=_required_text(
@@ -70,7 +70,7 @@ def _bcn_session_from_row(row: aiosqlite.Row) -> BcnSession:
70
70
  )
71
71
 
72
72
 
73
- def _runtime_attempt_from_row(row: aiosqlite.Row) -> RuntimeAttempt:
73
+ def runtime_attempt_from_row(row: aiosqlite.Row) -> RuntimeAttempt:
74
74
  return RuntimeAttempt(
75
75
  turn_id=_required_text(row["turn_id"], "turn_id"),
76
76
  session_id=_required_text(row["session_id"], "session_id"),
@@ -81,7 +81,7 @@ def _runtime_attempt_from_row(row: aiosqlite.Row) -> RuntimeAttempt:
81
81
  )
82
82
 
83
83
 
84
- def _inbound_message_from_row(
84
+ def inbound_message_from_row(
85
85
  row: aiosqlite.Row,
86
86
  attachments: tuple[InboundAttachment, ...] = (),
87
87
  ) -> InboundMessage:
@@ -133,7 +133,7 @@ def _inbound_message_from_row(
133
133
  )
134
134
 
135
135
 
136
- def _inbound_attachment_from_row(row: aiosqlite.Row) -> InboundAttachment:
136
+ def inbound_attachment_from_row(row: aiosqlite.Row) -> InboundAttachment:
137
137
  return InboundAttachment(
138
138
  attachment_id=_required_text(row["attachment_id"], "attachment_id"),
139
139
  name=_required_text(row["name"], "attachment.name"),
@@ -148,7 +148,7 @@ def _inbound_attachment_from_row(row: aiosqlite.Row) -> InboundAttachment:
148
148
  )
149
149
 
150
150
 
151
- def _outbound_message_from_row(row: aiosqlite.Row) -> OutboundMessage:
151
+ def outbound_message_from_row(row: aiosqlite.Row) -> OutboundMessage:
152
152
  raw_attachments = row["attachments_json"]
153
153
  if not isinstance(raw_attachments, str):
154
154
  raise TypeError("attachments_json must be text")
@@ -231,7 +231,7 @@ def _outbound_message_from_row(row: aiosqlite.Row) -> OutboundMessage:
231
231
  )
232
232
 
233
233
 
234
- def _consumer_cursor_from_row(row: aiosqlite.Row) -> ConsumerCursor:
234
+ def consumer_cursor_from_row(row: aiosqlite.Row) -> ConsumerCursor:
235
235
  return ConsumerCursor(
236
236
  session_id=_required_text(row["session_id"], "session_id"),
237
237
  delivered_through_seq=_required_non_negative_int(
@@ -258,12 +258,12 @@ def _consumer_cursor_from_row(row: aiosqlite.Row) -> ConsumerCursor:
258
258
  )
259
259
 
260
260
 
261
- def _validate_inbound_message_input(message: InboundMessage) -> None:
261
+ def validate_inbound_message_input(message: object) -> None:
262
262
  if not isinstance(message, InboundMessage):
263
263
  raise TypeError("message must be an InboundMessage")
264
264
 
265
265
 
266
- def _validate_outbound_message_input(message: OutboundMessage) -> None:
266
+ def validate_outbound_message_input(message: object) -> None:
267
267
  if not isinstance(message, OutboundMessage):
268
268
  raise TypeError("message must be an OutboundMessage")
269
269
  if not isinstance(message.state, OutboundDeliveryState):
@@ -369,7 +369,7 @@ def _validate_outbound_message_input(message: OutboundMessage) -> None:
369
369
  raise ValueError("delivered outbound message requires a provider receipt")
370
370
 
371
371
 
372
- def _validate_outbound_insert(message: OutboundMessage) -> None:
372
+ def validate_outbound_insert(message: OutboundMessage) -> None:
373
373
  if message.state is not OutboundDeliveryState.DRAFT:
374
374
  raise ValueError("a new outbound message must start in draft state")
375
375
  if message.fresh_check_state is not FreshCheckState.REQUIRED:
@@ -387,7 +387,7 @@ def _validate_outbound_insert(message: OutboundMessage) -> None:
387
387
  raise ValueError("a new outbound draft cannot contain delivery timestamps")
388
388
 
389
389
 
390
- def _validate_outbound_update(
390
+ def validate_outbound_update(
391
391
  existing: OutboundMessage,
392
392
  incoming: OutboundMessage,
393
393
  ) -> OutboundMessage:
@@ -496,7 +496,7 @@ def _validate_optional_input_text(value: object, field_name: str) -> None:
496
496
  raise ValueError(f"{field_name} must be a non-empty string when present")
497
497
 
498
498
 
499
- def _validate_consumer_cursor_input(cursor: ConsumerCursor) -> None:
499
+ def validate_consumer_cursor_input(cursor: object) -> None:
500
500
  if not isinstance(cursor, ConsumerCursor):
501
501
  raise TypeError("cursor must be a ConsumerCursor")
502
502
  source = cursor.inbox_snapshot_source
@@ -511,14 +511,14 @@ def _validate_consumer_cursor_input(cursor: ConsumerCursor) -> None:
511
511
  raise ValueError("inbox snapshot sequence requires a snapshot time")
512
512
 
513
513
 
514
- def _validate_cursor_bounds(cursor: ConsumerCursor, latest_seq: int) -> None:
514
+ def validate_cursor_bounds(cursor: ConsumerCursor, latest_seq: int) -> None:
515
515
  if cursor.delivered_through_seq > latest_seq:
516
516
  raise ValueError("delivered cursor cannot exceed the latest inbound sequence")
517
517
  if cursor.inbox_snapshot_seq is not None and cursor.inbox_snapshot_seq > latest_seq:
518
518
  raise ValueError("inbox snapshot cannot exceed the latest inbound sequence")
519
519
 
520
520
 
521
- def _validate_consumer_cursor_update(
521
+ def validate_consumer_cursor_update(
522
522
  existing: ConsumerCursor,
523
523
  incoming: ConsumerCursor,
524
524
  ) -> None:
@@ -553,12 +553,12 @@ def _validate_consumer_cursor_update(
553
553
  raise ValueError(f"{field_name} cannot move backwards")
554
554
 
555
555
 
556
- def _validate_channel_session_input(session: ChannelSession) -> None:
556
+ def validate_channel_session_input(session: ChannelSession) -> None:
557
557
  if not isinstance(session.following, bool):
558
558
  raise TypeError("channel session following must be a boolean")
559
559
 
560
560
 
561
- def _validate_channel_session_update(
561
+ def validate_channel_session_update(
562
562
  existing: ChannelSession,
563
563
  incoming: ChannelSession,
564
564
  ) -> ChannelSession:
@@ -580,7 +580,7 @@ def _validate_channel_session_update(
580
580
  )
581
581
 
582
582
 
583
- def _validate_bcn_session_update(
583
+ def validate_bcn_session_update(
584
584
  existing: BcnSession,
585
585
  incoming: BcnSession,
586
586
  ) -> BcnSession:
@@ -608,15 +608,15 @@ def _required_text(value: object, field_name: str) -> str:
608
608
  return _string_value(value, field_name, allow_empty=False)
609
609
 
610
610
 
611
- def _validate_non_empty_text(value: object, field_name: str) -> None:
611
+ def validate_non_empty_text(value: object, field_name: str) -> None:
612
612
  _required_text(value, field_name)
613
613
 
614
614
 
615
- def _validate_non_negative_int(value: object, field_name: str) -> None:
615
+ def validate_non_negative_int(value: object, field_name: str) -> None:
616
616
  _required_non_negative_int(value, field_name)
617
617
 
618
618
 
619
- def _validate_positive_int(value: object, field_name: str) -> None:
619
+ def validate_positive_int(value: object, field_name: str) -> None:
620
620
  _required_positive_int(value, field_name)
621
621
 
622
622
 
@@ -669,7 +669,7 @@ def _optional_non_negative_int(value: object, field_name: str) -> int | None:
669
669
  return _required_non_negative_int(value, field_name)
670
670
 
671
671
 
672
- def _encode_metadata(metadata: Mapping[str, object]) -> str:
672
+ def encode_metadata(metadata: object) -> str:
673
673
  if not isinstance(metadata, Mapping):
674
674
  raise TypeError("metadata must be a mapping")
675
675
  if any(not isinstance(key, str) for key in metadata):
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import replace
4
+ from typing import cast
4
5
  from uuid import uuid7
5
6
 
6
7
  from ...core.models import (
@@ -14,12 +15,12 @@ from ...core.models import (
14
15
  )
15
16
  from ...core.reminder import canonical_id_reference
16
17
  from .codec import (
17
- _inbound_message_from_row,
18
18
  _required_non_negative_int,
19
19
  _required_text,
20
- _validate_non_empty_text,
21
- _validate_non_negative_int,
22
- _validate_positive_int,
20
+ inbound_message_from_row,
21
+ validate_non_empty_text,
22
+ validate_non_negative_int,
23
+ validate_positive_int,
23
24
  )
24
25
  from .reminder_codec import reminder_from_row, reminder_occurrence_from_row
25
26
  from .repository import SqliteTransaction
@@ -50,7 +51,7 @@ class ReminderTransaction(SqliteTransaction):
50
51
  session_id: str,
51
52
  message_id_or_prefix: str,
52
53
  ) -> InboundMessage | None:
53
- _validate_non_empty_text(session_id, "session_id")
54
+ validate_non_empty_text(session_id, "session_id")
54
55
  reference = canonical_id_reference(message_id_or_prefix)
55
56
  if len(reference) == 8:
56
57
  rows = await self.fetchall(
@@ -69,16 +70,14 @@ class ReminderTransaction(SqliteTransaction):
69
70
  )
70
71
  if row is None:
71
72
  return None
72
- return _inbound_message_from_row(
73
- row, await self._attachments(row["message_id"])
74
- )
73
+ return inbound_message_from_row(row, await self._attachments(row["message_id"]))
75
74
 
76
75
  async def get_reminder(
77
76
  self,
78
77
  owner_session_id: str,
79
78
  reminder_id_or_prefix: str,
80
79
  ) -> Reminder | None:
81
- _validate_non_empty_text(owner_session_id, "owner_session_id")
80
+ validate_non_empty_text(owner_session_id, "owner_session_id")
82
81
  reference = canonical_id_reference(reminder_id_or_prefix)
83
82
  if len(reference) == 8:
84
83
  rows = await self.fetchall(
@@ -103,7 +102,7 @@ class ReminderTransaction(SqliteTransaction):
103
102
  owner_session_id: str,
104
103
  statuses: frozenset[ReminderState],
105
104
  ) -> tuple[Reminder, ...]:
106
- _validate_non_empty_text(owner_session_id, "owner_session_id")
105
+ validate_non_empty_text(owner_session_id, "owner_session_id")
107
106
  if not isinstance(statuses, frozenset) or not statuses:
108
107
  raise ValueError("statuses must be a non-empty frozenset")
109
108
  if not all(isinstance(status, ReminderState) for status in statuses):
@@ -118,7 +117,7 @@ class ReminderTransaction(SqliteTransaction):
118
117
  )
119
118
  return tuple(reminder_from_row(row) for row in rows)
120
119
 
121
- async def save_new_reminder(self, reminder: Reminder) -> Reminder:
120
+ async def save_new_reminder(self, reminder: object) -> Reminder:
122
121
  if not isinstance(reminder, Reminder):
123
122
  raise TypeError("reminder must be a Reminder")
124
123
  if reminder.state is not ReminderState.SCHEDULED:
@@ -166,9 +165,9 @@ class ReminderTransaction(SqliteTransaction):
166
165
  async def save_reminder_transition(
167
166
  self,
168
167
  expected_revision: int,
169
- reminder: Reminder,
168
+ reminder: object,
170
169
  ) -> Reminder:
171
- _validate_positive_int(expected_revision, "expected_revision")
170
+ validate_positive_int(expected_revision, "expected_revision")
172
171
  if not isinstance(reminder, Reminder):
173
172
  raise TypeError("reminder must be a Reminder")
174
173
  existing = await self.get_reminder(
@@ -216,8 +215,8 @@ class ReminderTransaction(SqliteTransaction):
216
215
  *,
217
216
  limit: int,
218
217
  ) -> tuple[Reminder, ...]:
219
- _validate_non_negative_int(now_ms, "now_ms")
220
- _validate_positive_int(limit, "limit")
218
+ validate_non_negative_int(now_ms, "now_ms")
219
+ validate_positive_int(limit, "limit")
221
220
  rows = await self.fetchall(
222
221
  f"SELECT {_REMINDER_COLUMNS} FROM reminders "
223
222
  "WHERE state = ? AND next_fire_at_ms <= ? "
@@ -232,9 +231,9 @@ class ReminderTransaction(SqliteTransaction):
232
231
  owner_session_id: str,
233
232
  reminder_id: str,
234
233
  ) -> OwnedReminder | None:
235
- _validate_non_empty_text(agent_id, "agent_id")
236
- _validate_non_empty_text(owner_session_id, "owner_session_id")
237
- _validate_non_empty_text(reminder_id, "reminder_id")
234
+ validate_non_empty_text(agent_id, "agent_id")
235
+ validate_non_empty_text(owner_session_id, "owner_session_id")
236
+ validate_non_empty_text(reminder_id, "reminder_id")
238
237
  bound_agent_id = self._bound_agent_id()
239
238
  if bound_agent_id is not None and bound_agent_id != agent_id:
240
239
  return None
@@ -267,8 +266,8 @@ class ReminderTransaction(SqliteTransaction):
267
266
  *,
268
267
  limit: int,
269
268
  ) -> tuple[OwnedReminder, ...]:
270
- _validate_non_negative_int(now_ms, "now_ms")
271
- _validate_positive_int(limit, "limit")
269
+ validate_non_negative_int(now_ms, "now_ms")
270
+ validate_positive_int(limit, "limit")
272
271
  predicates = ["state = ?", "next_fire_at_ms <= ?"]
273
272
  parameters: list[object] = [ReminderState.SCHEDULED.value, now_ms]
274
273
  bound_agent_id = self._bound_agent_id()
@@ -286,10 +285,10 @@ class ReminderTransaction(SqliteTransaction):
286
285
  async def save_owned_fired_occurrence(
287
286
  self,
288
287
  expected_revision: int,
289
- reminder: OwnedReminder,
290
- occurrence: OwnedReminderOccurrence,
288
+ reminder: object,
289
+ occurrence: object,
291
290
  ) -> OwnedReminderOccurrence:
292
- _validate_positive_int(expected_revision, "expected_revision")
291
+ validate_positive_int(expected_revision, "expected_revision")
293
292
  if not isinstance(reminder, OwnedReminder):
294
293
  raise TypeError("reminder must be an OwnedReminder")
295
294
  if not isinstance(occurrence, OwnedReminderOccurrence):
@@ -434,10 +433,10 @@ class ReminderTransaction(SqliteTransaction):
434
433
  async def save_fired_occurrence(
435
434
  self,
436
435
  expected_revision: int,
437
- reminder: Reminder,
438
- occurrence: ReminderOccurrence,
436
+ reminder: object,
437
+ occurrence: object,
439
438
  ) -> ReminderOccurrence:
440
- _validate_positive_int(expected_revision, "expected_revision")
439
+ validate_positive_int(expected_revision, "expected_revision")
441
440
  if not isinstance(reminder, Reminder):
442
441
  raise TypeError("reminder must be a Reminder")
443
442
  if not isinstance(occurrence, ReminderOccurrence):
@@ -516,8 +515,8 @@ class ReminderTransaction(SqliteTransaction):
516
515
  *,
517
516
  limit: int,
518
517
  ) -> tuple[ReminderOccurrence, ...]:
519
- _validate_non_empty_text(owner_session_id, "owner_session_id")
520
- _validate_positive_int(limit, "limit")
518
+ validate_non_empty_text(owner_session_id, "owner_session_id")
519
+ validate_positive_int(limit, "limit")
521
520
  rows = await self.fetchall(
522
521
  f"SELECT {_OCCURRENCE_COLUMNS} FROM reminder_occurrences "
523
522
  "WHERE owner_session_id = ? AND read_at_ms IS NULL "
@@ -527,7 +526,7 @@ class ReminderTransaction(SqliteTransaction):
527
526
  return tuple(reminder_occurrence_from_row(row) for row in rows)
528
527
 
529
528
  async def count_pending_reminder_occurrences(self, owner_session_id: str) -> int:
530
- _validate_non_empty_text(owner_session_id, "owner_session_id")
529
+ validate_non_empty_text(owner_session_id, "owner_session_id")
531
530
  row = await self.fetchone(
532
531
  "SELECT COUNT(*) AS pending_count FROM reminder_occurrences "
533
532
  "WHERE owner_session_id = ? AND read_at_ms IS NULL",
@@ -540,14 +539,15 @@ class ReminderTransaction(SqliteTransaction):
540
539
  async def mark_reminder_occurrences_read(
541
540
  self,
542
541
  owner_session_id: str,
543
- occurrence_ids: tuple[str, ...],
542
+ occurrence_ids: object,
544
543
  *,
545
544
  read_at_ms: int,
546
545
  ) -> tuple[ReminderOccurrence, ...]:
547
- _validate_non_empty_text(owner_session_id, "owner_session_id")
548
- _validate_non_negative_int(read_at_ms, "read_at_ms")
546
+ validate_non_empty_text(owner_session_id, "owner_session_id")
547
+ validate_non_negative_int(read_at_ms, "read_at_ms")
549
548
  if not isinstance(occurrence_ids, tuple):
550
549
  raise TypeError("occurrence_ids must be a tuple")
550
+ occurrence_ids = cast(tuple[str, ...], occurrence_ids)
551
551
  if not occurrence_ids:
552
552
  return ()
553
553
  if len(set(occurrence_ids)) != len(occurrence_ids):