monkeybot 2.1.1__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 (178) hide show
  1. monkeybot/__init__.py +3 -0
  2. monkeybot/cli/__init__.py +3 -0
  3. monkeybot/cli/__main__.py +8 -0
  4. monkeybot/cli/audio_io.py +8 -0
  5. monkeybot/cli/gateway_manager.py +17 -0
  6. monkeybot/cli/main.py +22 -0
  7. monkeybot/cli/push_to_talk.py +12 -0
  8. monkeybot/cli/realtime_client.py +13 -0
  9. monkeybot/core/__init__.py +19 -0
  10. monkeybot/core/attachments/__init__.py +22 -0
  11. monkeybot/core/attachments/catalog.py +62 -0
  12. monkeybot/core/attachments/config.py +52 -0
  13. monkeybot/core/attachments/freeze.py +158 -0
  14. monkeybot/core/attachments/resolve.py +70 -0
  15. monkeybot/core/attachments/store.py +180 -0
  16. monkeybot/core/attachments/text.py +72 -0
  17. monkeybot/core/attachments/tools.py +54 -0
  18. monkeybot/core/bootstrap.py +242 -0
  19. monkeybot/core/config/__init__.py +71 -0
  20. monkeybot/core/config/realtime_config.py +150 -0
  21. monkeybot/core/config/runtime_env.py +262 -0
  22. monkeybot/core/config/settings.py +341 -0
  23. monkeybot/core/config/validation.py +249 -0
  24. monkeybot/core/config/yaml_loader.py +45 -0
  25. monkeybot/core/context/__init__.py +781 -0
  26. monkeybot/core/context/campaign_context.py +8 -0
  27. monkeybot/core/context/common.py +14 -0
  28. monkeybot/core/context/curator.py +255 -0
  29. monkeybot/core/context/epoch.py +226 -0
  30. monkeybot/core/context/memory_prompt.py +222 -0
  31. monkeybot/core/context/tool_output_policy.py +270 -0
  32. monkeybot/core/context/tool_result_ingress.py +290 -0
  33. monkeybot/core/context/tool_shapers.py +361 -0
  34. monkeybot/core/hooks/__init__.py +261 -0
  35. monkeybot/core/llm/__init__.py +4 -0
  36. monkeybot/core/llm/provider.py +296 -0
  37. monkeybot/core/llm/realtime_provider.py +203 -0
  38. monkeybot/core/llm/usage.py +57 -0
  39. monkeybot/core/logging_utils.py +24 -0
  40. monkeybot/core/mcp/__init__.py +1 -0
  41. monkeybot/core/mcp/mcp_client.py +1215 -0
  42. monkeybot/core/mcp/ports_mcp.py +109 -0
  43. monkeybot/core/memory/__init__.py +24 -0
  44. monkeybot/core/memory/hook.py +413 -0
  45. monkeybot/core/memory/index_format.py +104 -0
  46. monkeybot/core/memory/integrity.py +180 -0
  47. monkeybot/core/memory/organizer.py +270 -0
  48. monkeybot/core/memory/storage_ops.py +139 -0
  49. monkeybot/core/memory/subsystem.py +91 -0
  50. monkeybot/core/messages/__init__.py +16 -0
  51. monkeybot/core/messages/convert_provider.py +41 -0
  52. monkeybot/core/messages/tool_integrity.py +262 -0
  53. monkeybot/core/messages/transform_context.py +84 -0
  54. monkeybot/core/path_safety.py +11 -0
  55. monkeybot/core/persistence/__init__.py +17 -0
  56. monkeybot/core/persistence/backends.py +236 -0
  57. monkeybot/core/persistence/db.py +28 -0
  58. monkeybot/core/persistence/durable_runs.py +286 -0
  59. monkeybot/core/persistence/firestore.py +658 -0
  60. monkeybot/core/persistence/firestore_scheduled_loops.py +336 -0
  61. monkeybot/core/persistence/history.py +156 -0
  62. monkeybot/core/persistence/postgres.py +895 -0
  63. monkeybot/core/persistence/runs.py +76 -0
  64. monkeybot/core/persistence/scheduled_loops.py +435 -0
  65. monkeybot/core/persistence/session_turn_locks.py +94 -0
  66. monkeybot/core/persistence/sqlite.py +218 -0
  67. monkeybot/core/persistence/sqlite_backend.py +74 -0
  68. monkeybot/core/persistence/thread_summary.py +61 -0
  69. monkeybot/core/persistence/transcript.py +194 -0
  70. monkeybot/core/persistence/usage.py +149 -0
  71. monkeybot/core/prompts/__init__.py +1 -0
  72. monkeybot/core/prompts/harness_prompt.py +197 -0
  73. monkeybot/core/prompts/prompt.py +215 -0
  74. monkeybot/core/runtime/__init__.py +1 -0
  75. monkeybot/core/runtime/context_budget.py +267 -0
  76. monkeybot/core/runtime/events.py +819 -0
  77. monkeybot/core/runtime/input_admission.py +154 -0
  78. monkeybot/core/runtime/loop.py +2374 -0
  79. monkeybot/core/runtime/provider_stream_mapper.py +159 -0
  80. monkeybot/core/runtime/realtime_loop.py +654 -0
  81. monkeybot/core/runtime/utterance_buffer.py +179 -0
  82. monkeybot/core/subagents/__init__.py +1 -0
  83. monkeybot/core/subagents/subagent_proto.py +331 -0
  84. monkeybot/core/subagents/subagent_worker.py +441 -0
  85. monkeybot/core/subagents/worker_pool.py +403 -0
  86. monkeybot/core/testing/__init__.py +1 -0
  87. monkeybot/core/testing/mocks_provider.py +86 -0
  88. monkeybot/core/testing/mocks_realtime_provider.py +137 -0
  89. monkeybot/core/tools/__init__.py +1 -0
  90. monkeybot/core/tools/core_tool_executor.py +1548 -0
  91. monkeybot/core/tools/inspector.py +226 -0
  92. monkeybot/core/tools/loop_inspector.py +45 -0
  93. monkeybot/core/tools/patch.py +480 -0
  94. monkeybot/core/tools/permission.py +284 -0
  95. monkeybot/core/tools/sandbox_executor.py +255 -0
  96. monkeybot/core/tools/spill_inventory.py +35 -0
  97. monkeybot/core/tools/terminal.py +381 -0
  98. monkeybot/core/tools/text_normalize.py +25 -0
  99. monkeybot/core/tools/types.py +33 -0
  100. monkeybot/core/tools/workspace_service.py +710 -0
  101. monkeybot/core/tools/workspace_tools.py +116 -0
  102. monkeybot/core/types/__init__.py +1 -0
  103. monkeybot/core/types/content_blocks.py +644 -0
  104. monkeybot/core/types/interfaces.py +156 -0
  105. monkeybot/core/types/types_tools.py +29 -0
  106. monkeybot/core/workspace/__init__.py +8 -0
  107. monkeybot/core/workspace/factory.py +45 -0
  108. monkeybot/core/workspace/gcs.py +130 -0
  109. monkeybot/core/workspace/local.py +162 -0
  110. monkeybot/core/workspace/protocol.py +45 -0
  111. monkeybot/core/workspace/s3.py +151 -0
  112. monkeybot/core/workspace_layout.py +27 -0
  113. monkeybot/gateway/__init__.py +1 -0
  114. monkeybot/gateway/bootstrap.py +18 -0
  115. monkeybot/gateway/main.py +47 -0
  116. monkeybot/gateway/realtime/__init__.py +31 -0
  117. monkeybot/gateway/realtime/app.py +321 -0
  118. monkeybot/gateway/realtime/deps.py +52 -0
  119. monkeybot/gateway/realtime/errors.py +81 -0
  120. monkeybot/gateway/realtime/guardrails.py +88 -0
  121. monkeybot/gateway/realtime/manager.py +77 -0
  122. monkeybot/gateway/realtime/metrics.py +144 -0
  123. monkeybot/gateway/realtime/routes.py +864 -0
  124. monkeybot/gateway/realtime/session.py +232 -0
  125. monkeybot/gateway/realtime/wire.py +412 -0
  126. monkeybot/gateway/realtime_main.py +49 -0
  127. monkeybot/gateway/sse/__init__.py +1 -0
  128. monkeybot/gateway/sse/app.py +733 -0
  129. monkeybot/gateway/sse/loop_port.py +31 -0
  130. monkeybot/gateway/sse/models.py +177 -0
  131. monkeybot/gateway/sse/reply_body.py +91 -0
  132. monkeybot/gateway/sse/routes.py +1101 -0
  133. monkeybot/gateway/sse/scheduler_routes.py +200 -0
  134. monkeybot/gateway/sse/scheduler_wiring.py +96 -0
  135. monkeybot/gateway/sse/session_bus.py +226 -0
  136. monkeybot/gateway/sse/sse.py +46 -0
  137. monkeybot/gateway/sse/workspace_layout.py +7 -0
  138. monkeybot/observability/__init__.py +220 -0
  139. monkeybot/observability/_state.py +10 -0
  140. monkeybot/observability/instrumentation.py +153 -0
  141. monkeybot/observability/propagation.py +65 -0
  142. monkeybot/observability/spans.py +455 -0
  143. monkeybot/providers/__init__.py +19 -0
  144. monkeybot/providers/_openai_compat.py +450 -0
  145. monkeybot/providers/_utils.py +473 -0
  146. monkeybot/providers/bedrock.py +145 -0
  147. monkeybot/providers/claude.py +125 -0
  148. monkeybot/providers/gemini.py +677 -0
  149. monkeybot/providers/gemini_live.py +398 -0
  150. monkeybot/providers/huggingface.py +129 -0
  151. monkeybot/providers/nvidia.py +104 -0
  152. monkeybot/providers/ollama.py +152 -0
  153. monkeybot/providers/openai.py +127 -0
  154. monkeybot/providers/pricing.py +60 -0
  155. monkeybot/providers/sampling.py +44 -0
  156. monkeybot/providers/vertex_claude.py +148 -0
  157. monkeybot/scaffold/__init__.py +33 -0
  158. monkeybot/scheduler/__init__.py +13 -0
  159. monkeybot/scheduler/__main__.py +4 -0
  160. monkeybot/scheduler/engine.py +333 -0
  161. monkeybot/scheduler/http_invoker.py +61 -0
  162. monkeybot/scheduler/interval.py +77 -0
  163. monkeybot/scheduler/tick_result.py +34 -0
  164. monkeybot/scheduler/worker.py +87 -0
  165. monkeybot/subagents/__init__.py +1 -0
  166. monkeybot/subagents/worker/__init__.py +1 -0
  167. monkeybot/subagents/worker/__main__.py +22 -0
  168. monkeybot/web_search/__init__.py +82 -0
  169. monkeybot/web_search/backends/__init__.py +5 -0
  170. monkeybot/web_search/backends/duckduckgo.py +32 -0
  171. monkeybot/web_search/backends/firecrawl.py +43 -0
  172. monkeybot/web_search/backends/tavily.py +45 -0
  173. monkeybot/web_search/protocol.py +25 -0
  174. monkeybot/web_search/tool.py +56 -0
  175. monkeybot-2.1.1.dist-info/METADATA +318 -0
  176. monkeybot-2.1.1.dist-info/RECORD +178 -0
  177. monkeybot-2.1.1.dist-info/WHEEL +4 -0
  178. monkeybot-2.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,203 @@
1
+ """Vendor-agnostic contract for persistent duplex realtime model sessions.
2
+
3
+ This protocol is parallel to :class:`~monkeybot.core.llm.provider.Provider` but is
4
+ not a variant of it. Realtime vendor APIs (Gemini Live, OpenAI Realtime, Bedrock
5
+ Nova Sonic) are persistent duplex sessions, not request/response-per-call streams.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import AsyncIterator, Sequence
11
+ from dataclasses import dataclass, field
12
+ from typing import Literal, Protocol, TypeAlias
13
+
14
+ from monkeybot.core.types.types_tools import ToolDef
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class AudioFormat:
19
+ """Audio wire format for realtime input/output."""
20
+
21
+ encoding: str
22
+ sample_rate_hz: int
23
+ channels: int
24
+ frame_ms: int
25
+
26
+ def __str__(self) -> str:
27
+ return f"{self.encoding}_{self.sample_rate_hz}hz_{self.channels}ch_{self.frame_ms}ms"
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class RealtimeSessionConfig:
32
+ """Parameters used to open a persistent realtime vendor session."""
33
+
34
+ model: str
35
+ system_prompt: str
36
+ tools: Sequence[ToolDef]
37
+ preferred_input_format: AudioFormat = AudioFormat(
38
+ encoding="pcm_s16le", sample_rate_hz=24000, channels=1, frame_ms=200
39
+ )
40
+ preferred_output_format: AudioFormat = AudioFormat(
41
+ encoding="pcm_s16le", sample_rate_hz=24000, channels=1, frame_ms=200
42
+ )
43
+ voice: str | None = None
44
+ max_output_tokens: int | None = None
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class RealtimePartialTranscript:
49
+ kind: Literal["RealtimePartialTranscript"] = "RealtimePartialTranscript"
50
+ text: str = ""
51
+ is_final: bool = False
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class RealtimeAudioDelta:
56
+ kind: Literal["RealtimeAudioDelta"] = "RealtimeAudioDelta"
57
+ chunk: bytes = b""
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class RealtimeTextDelta:
62
+ """Text delta emitted by the model during a realtime turn."""
63
+
64
+ kind: Literal["RealtimeTextDelta"] = "RealtimeTextDelta"
65
+ text: str = ""
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class RealtimeTurnBoundary:
70
+ """Provider signaled the end of the current user or assistant turn."""
71
+
72
+ kind: Literal["RealtimeTurnBoundary"] = "RealtimeTurnBoundary"
73
+ role: Literal["user", "assistant"] = "user"
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class RealtimeToolCall:
78
+ """Tool call emitted by a realtime provider at turn boundary."""
79
+
80
+ kind: Literal["RealtimeToolCall"] = "RealtimeToolCall"
81
+ call_id: str = ""
82
+ name: str = ""
83
+ args: dict[str, object] = field(default_factory=dict)
84
+ parse_error: str | None = None
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class RealtimeInterrupted:
89
+ """Provider acknowledged that the current turn was interrupted."""
90
+
91
+ kind: Literal["RealtimeInterrupted"] = "RealtimeInterrupted"
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class RealtimeUsage:
96
+ kind: Literal["RealtimeUsage"] = "RealtimeUsage"
97
+ input_tokens: int = 0
98
+ output_tokens: int = 0
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class RealtimeError:
103
+ kind: Literal["RealtimeError"] = "RealtimeError"
104
+ error: str = ""
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class RealtimeDone:
109
+ """Provider has finished emitting output for the current turn."""
110
+
111
+ kind: Literal["RealtimeDone"] = "RealtimeDone"
112
+
113
+
114
+ RealtimeEvent: TypeAlias = (
115
+ RealtimePartialTranscript
116
+ | RealtimeAudioDelta
117
+ | RealtimeTextDelta
118
+ | RealtimeTurnBoundary
119
+ | RealtimeToolCall
120
+ | RealtimeInterrupted
121
+ | RealtimeUsage
122
+ | RealtimeError
123
+ | RealtimeDone
124
+ )
125
+
126
+
127
+ class RealtimeSession(Protocol):
128
+ """One open realtime vendor session."""
129
+
130
+ @property
131
+ def input_format(self) -> AudioFormat:
132
+ """Actual negotiated input format (may differ from requested)."""
133
+
134
+ @property
135
+ def output_format(self) -> AudioFormat:
136
+ """Actual negotiated output format (may differ from requested)."""
137
+
138
+ async def send_audio(self, chunk: bytes) -> None:
139
+ """Send one audio chunk from the user."""
140
+
141
+ async def end_audio_turn(self) -> None:
142
+ """Signal that the user finished speaking (end of push-to-talk utterance).
143
+
144
+ Providers that use voice-activity detection can treat this as an explicit
145
+ end-of-speech cue so the model responds immediately instead of waiting for
146
+ silence timeout.
147
+ """
148
+
149
+ async def send_text(self, text: str) -> None:
150
+ """Send text from the user."""
151
+
152
+ async def send_context(self, text: str) -> None:
153
+ """Inject non-user context (e.g., a tool result) into the live session."""
154
+
155
+ async def send_tool_results(
156
+ self,
157
+ results: Sequence[tuple[str, str, dict[str, object], bool]],
158
+ ) -> None:
159
+ """Return tool results to the live session.
160
+
161
+ Each result is ``(call_id, tool_name, response_payload, is_error)``.
162
+ Providers that require a dedicated tool-response RPC (e.g. Gemini Live
163
+ ``send_tool_response``) must implement that here. Providers without a
164
+ dedicated RPC may fall back to ``send_context``.
165
+ """
166
+
167
+ async def interrupt(self) -> None:
168
+ """Tell the provider to cancel the current assistant turn."""
169
+
170
+ def events(self) -> AsyncIterator[RealtimeEvent]:
171
+ """Yield provider events for the lifetime of the session."""
172
+
173
+ async def close(self, *, reason: str = "session_end") -> None:
174
+ """Close the vendor session gracefully."""
175
+
176
+
177
+ class RealtimeProvider(Protocol):
178
+ """Factory for persistent duplex realtime sessions."""
179
+
180
+ @property
181
+ def name(self) -> str:
182
+ """Stable provider id (e.g. ``"gemini-live"``)."""
183
+
184
+ async def connect(self, *, config: RealtimeSessionConfig) -> RealtimeSession:
185
+ """Open a new vendor session and return it."""
186
+
187
+
188
+ __all__ = [
189
+ "AudioFormat",
190
+ "RealtimeDone",
191
+ "RealtimeError",
192
+ "RealtimeEvent",
193
+ "RealtimeInterrupted",
194
+ "RealtimePartialTranscript",
195
+ "RealtimeProvider",
196
+ "RealtimeSession",
197
+ "RealtimeSessionConfig",
198
+ "RealtimeToolCall",
199
+ "RealtimeTextDelta",
200
+ "RealtimeTurnBoundary",
201
+ "RealtimeUsage",
202
+ "RealtimeAudioDelta",
203
+ ]
@@ -0,0 +1,57 @@
1
+ """Per-turn token and cost data types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from monkeybot.core.runtime.events import UsageTotals
8
+
9
+
10
+ @dataclass
11
+ class Usage:
12
+ """Aggregated usage for one model turn."""
13
+
14
+ input_tokens: int = 0
15
+ output_tokens: int = 0
16
+ cached_tokens: int = 0
17
+ cache_read_tokens: int = 0
18
+ cache_creation_tokens: int = 0
19
+ cost_usd: float = 0.0
20
+ duration_ms: int = 0
21
+ estimated_prompt_tokens: int = 0
22
+ """Peak pre-stream input token count for this user turn (``Provider.count_input_tokens``).
23
+
24
+ Same payload as each outbound ``stream`` call (messages + tools), using the
25
+ vendor count API or tokenizer where implemented.
26
+ """
27
+
28
+
29
+ def usage_from_totals(t: UsageTotals) -> Usage:
30
+ """Build a :class:`Usage` row from :class:`~monkeybot.core.runtime.events.UsageTotals` (e.g. ``TurnComplete.usage``)."""
31
+ return Usage(
32
+ input_tokens=t.input_tokens,
33
+ output_tokens=t.output_tokens,
34
+ cached_tokens=t.cached_tokens,
35
+ cache_read_tokens=t.cache_read_tokens,
36
+ cache_creation_tokens=t.cache_creation_tokens,
37
+ cost_usd=t.cost_usd,
38
+ duration_ms=t.duration_ms,
39
+ estimated_prompt_tokens=t.estimated_prompt_tokens,
40
+ )
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class UsageSummary:
45
+ """Aggregated totals returned by a usage store's ``summary`` method."""
46
+
47
+ turns: int
48
+ input_tokens: int
49
+ output_tokens: int
50
+ cached_tokens: int
51
+ cost_usd: float
52
+ period_start_ms: int | None
53
+ period_end_ms: int | None
54
+ last_prompt_tokens: int
55
+ last_estimated_prompt_tokens: int
56
+ cache_read_tokens: int = 0
57
+ cache_creation_tokens: int = 0
@@ -0,0 +1,24 @@
1
+ """Shared logging helpers for gateway, workers, and deploy entrypoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+
8
+ def normalize_log_level(raw: str | None, *, default: str = "INFO") -> int:
9
+ """Map ``LOG_LEVEL`` env strings to :mod:`logging` level ints (case-insensitive)."""
10
+ name = (raw or default).strip().upper()
11
+ level = logging.getLevelName(name)
12
+ if isinstance(level, int):
13
+ return level
14
+ raise ValueError(f"invalid log level: {raw!r}")
15
+
16
+
17
+ def kv(**fields: object) -> str:
18
+ """Format ``key=value`` pairs for structured log messages; skip ``None`` values."""
19
+ parts: list[str] = []
20
+ for key, value in fields.items():
21
+ if value is None:
22
+ continue
23
+ parts.append(f"{key}={value}")
24
+ return " ".join(parts)
@@ -0,0 +1 @@
1
+ """Model Context Protocol client and port definitions."""