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,31 @@
1
+ """
2
+ Injectable ports between the SSE gateway and the agent loop / usage store.
3
+ """
4
+
5
+ from typing import Any, Protocol
6
+
7
+ from monkeybot.core.types.content_blocks import ContentBlock
8
+
9
+
10
+ class LoopPort(Protocol):
11
+ """Schedules a turn; implementations publish Agent events to the session bus."""
12
+
13
+ async def start_turn(
14
+ self,
15
+ session_id: str,
16
+ request_id: str,
17
+ user_content: list[ContentBlock],
18
+ ) -> None:
19
+ """Schedule background work; events MUST be published to that session's bus."""
20
+
21
+
22
+ class UsagePort(Protocol):
23
+ """Returns usage aggregates for GET /sessions/{id}/usage."""
24
+
25
+ async def session_usage(
26
+ self,
27
+ session_id: str,
28
+ *,
29
+ since: str | None,
30
+ ) -> dict[str, Any]:
31
+ """Return JSON-serializable dict matching GET /usage body."""
@@ -0,0 +1,177 @@
1
+ """
2
+ Pydantic models and API errors for the v2 SSE gateway.
3
+ """
4
+
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class ErrorBody(BaseModel):
11
+ """Error payload matching design contract 1b."""
12
+
13
+ code: str
14
+ message: str
15
+ request_id: str
16
+
17
+
18
+ class ErrorEnvelope(BaseModel):
19
+ """Top-level error response `{ \"error\": ... }`."""
20
+
21
+ error: ErrorBody
22
+
23
+
24
+ class APIError(Exception):
25
+ """Raised for API failures; maps to JSON error envelope via exception handler."""
26
+
27
+ def __init__(self, status_code: int, code: str, message: str, request_id: str) -> None:
28
+ super().__init__(message)
29
+ self.status_code = status_code
30
+ self.code = code
31
+ self.message = message
32
+ self.request_id = request_id
33
+
34
+
35
+ class CreateSessionRequest(BaseModel):
36
+ """POST /sessions body."""
37
+
38
+ session_id: str | None = Field(
39
+ default=None,
40
+ max_length=128,
41
+ description="Client id; server generates UUID when omitted.",
42
+ )
43
+ agent_md: str | None = Field(default=None, description="Optional AGENT.md path override.")
44
+ model_provider: str | None = Field(
45
+ default=None,
46
+ description="Canonical or alias provider id (e.g. 'openai', 'gemini', 'vertex-claude'). None → server env default.",
47
+ )
48
+ model_name: str | None = Field(
49
+ default=None,
50
+ max_length=200,
51
+ description="Model id for the chosen provider. None → server env default.",
52
+ )
53
+
54
+
55
+ class CreateSessionResponse(BaseModel):
56
+ """POST /sessions response (201)."""
57
+
58
+ session_id: str
59
+ created_at: int = Field(..., description="Unix time in milliseconds.")
60
+
61
+
62
+ class ReplyBodyFields(BaseModel):
63
+ """Shared ``message`` / ``content`` fields for reply, steer, and queue."""
64
+
65
+ message: str | None = Field(default=None, max_length=32000)
66
+ content: list[dict[str, Any]] | None = None
67
+
68
+
69
+ class ReplyRequest(ReplyBodyFields):
70
+ """POST /sessions/{id}/reply body."""
71
+
72
+ request_id: str
73
+
74
+
75
+ class SteerRequest(ReplyBodyFields):
76
+ """POST /sessions/{id}/steer — mid-turn user injection (requires busy session)."""
77
+
78
+
79
+ class QueueRequest(ReplyBodyFields):
80
+ """POST /sessions/{id}/queue — follow-up prompt drained when the session goes idle."""
81
+
82
+ request_id: str
83
+
84
+
85
+ class AttachmentUploadResponse(BaseModel):
86
+ """POST /sessions/{id}/attachments response (201)."""
87
+
88
+ attachment_id: str
89
+ mime_type: str
90
+ size_bytes: int
91
+ filename: str
92
+ created_at: int
93
+
94
+
95
+ class ReplyResponse(BaseModel):
96
+ """POST /sessions/{id}/reply response (200)."""
97
+
98
+ request_id: str
99
+
100
+
101
+ class AdmissionAcceptedResponse(BaseModel):
102
+ """POST /steer or /queue acceptance."""
103
+
104
+ request_id: str
105
+ queue: Literal["steer", "follow_up"]
106
+ position: int
107
+
108
+
109
+ class CancelRequest(BaseModel):
110
+ """POST /sessions/{id}/cancel body."""
111
+
112
+ request_id: str
113
+
114
+
115
+ class ToolConfirmationPOST(BaseModel):
116
+ """POST /sessions/{id}/tool-confirmations/{tool_call_id} body."""
117
+
118
+ approved: bool
119
+ reason: str | None = None
120
+ always: bool = False
121
+ """When true with approved, remember this tool+resource for the rest of the session."""
122
+
123
+
124
+ class ElicitationPOST(BaseModel):
125
+ """POST /sessions/{id}/elicitations/{id} body."""
126
+
127
+ user_data: Any | None = None
128
+
129
+
130
+ class FrontendToolResultPOST(BaseModel):
131
+ """POST /sessions/{id}/frontend-tool-results/{tool_call_id} body."""
132
+
133
+ result: list[dict[str, Any]]
134
+ is_error: bool
135
+
136
+
137
+ class HealthResponse(BaseModel):
138
+ """GET /health response."""
139
+
140
+ status: Literal["ok"]
141
+ version: Literal["2.0.0"]
142
+
143
+
144
+ class SessionUsageResponse(BaseModel):
145
+ """GET /sessions/{id}/usage response."""
146
+
147
+ session_id: str
148
+ turns: int
149
+ input_tokens: int
150
+ output_tokens: int
151
+ cached_tokens: int
152
+ cache_read_tokens: int = 0
153
+ cache_creation_tokens: int = 0
154
+ cost_usd: float
155
+ period_start: int
156
+ period_end: int
157
+ last_prompt_tokens: int = Field(
158
+ 0,
159
+ description="Input (prompt) tokens reported for the most recent completed turn in this session.",
160
+ )
161
+ estimated_prompt_tokens: int = Field(
162
+ 0,
163
+ description="Peak pre-stream prompt input token count for the latest turn (provider count_tokens / OpenAI tiktoken); aligns with summarization checks.",
164
+ )
165
+ summarization_threshold_tokens: int = Field(
166
+ 0,
167
+ description="``floor(MODEL_CONTEXT_WINDOW * 0.85)`` — same bar as sync summarization in the agent loop.",
168
+ )
169
+ context_window_tokens: int = Field(
170
+ 1_000_000,
171
+ description="Configured model context window cap used for UI fill estimates (see MODEL_CONTEXT_WINDOW).",
172
+ )
173
+
174
+
175
+ def error_payload_dict(code: str, message: str, request_id: str) -> dict[str, Any]:
176
+ """Build a JSON-serializable error body for JSONResponse."""
177
+ return {"error": {"code": code, "message": message, "request_id": request_id}}
@@ -0,0 +1,91 @@
1
+ """Normalize and validate POST /reply bodies into user ContentBlock lists."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from monkeybot.core.attachments.config import max_attachments_per_reply
6
+ from monkeybot.core.attachments.store import AttachmentStore
7
+ from monkeybot.core.types.content_blocks import (
8
+ AttachmentRef,
9
+ ContentBlock,
10
+ File,
11
+ Image,
12
+ Text,
13
+ )
14
+
15
+
16
+ class ReplyBodyError(ValueError):
17
+ """Validation failure for reply body normalization."""
18
+
19
+ def __init__(self, code: str, message: str) -> None:
20
+ super().__init__(message)
21
+ self.code = code
22
+
23
+
24
+ def normalize_reply_to_user_content(
25
+ *,
26
+ message: str | None,
27
+ content: list[dict[str, object]] | None,
28
+ session_id: str,
29
+ attachment_store: AttachmentStore | None,
30
+ ) -> list[ContentBlock]:
31
+ has_message = message is not None and message.strip() != ""
32
+ has_content = content is not None and len(content) > 0
33
+ if has_message and has_content:
34
+ raise ReplyBodyError("AMBIGUOUS_REPLY_BODY", "Send either message or content, not both")
35
+ if not has_message and not has_content:
36
+ raise ReplyBodyError("EMPTY_REPLY_BODY", "Reply must include message or content")
37
+
38
+ if has_message and not has_content:
39
+ return [Text(text=message or "")]
40
+
41
+ blocks: list[ContentBlock] = []
42
+ ref_count = 0
43
+ has_text = False
44
+ for raw in content or []:
45
+ if not isinstance(raw, dict):
46
+ raise ReplyBodyError("INVALID_CONTENT", "content blocks must be JSON objects")
47
+ tag = raw.get("type")
48
+ if tag in ("image", "file") and raw.get("data"):
49
+ raise ReplyBodyError(
50
+ "INLINE_ATTACHMENT_NOT_ALLOWED",
51
+ "Upload attachments via POST /attachments; use attachmentRef in content",
52
+ )
53
+ block = ContentBlock.from_dict(raw)
54
+ if isinstance(block, Image) or isinstance(block, File):
55
+ raise ReplyBodyError(
56
+ "INLINE_ATTACHMENT_NOT_ALLOWED",
57
+ "Upload attachments via POST /attachments; use attachmentRef in content",
58
+ )
59
+ if isinstance(block, Text):
60
+ if block.text.strip():
61
+ has_text = True
62
+ blocks.append(block)
63
+ elif isinstance(block, AttachmentRef):
64
+ ref_count += 1
65
+ if attachment_store is None or not attachment_store.exists(
66
+ session_id, block.attachment_id
67
+ ):
68
+ raise ReplyBodyError(
69
+ "ATTACHMENT_NOT_FOUND",
70
+ f"Unknown attachment_id: {block.attachment_id}",
71
+ )
72
+ blocks.append(block)
73
+ else:
74
+ raise ReplyBodyError(
75
+ "INVALID_CONTENT",
76
+ f"Unsupported content block type for /reply: {type(block).__name__}",
77
+ )
78
+
79
+ if ref_count > max_attachments_per_reply():
80
+ raise ReplyBodyError(
81
+ "TOO_MANY_ATTACHMENTS",
82
+ f"At most {max_attachments_per_reply()} attachments per reply",
83
+ )
84
+ if ref_count == 0 and not has_text:
85
+ raise ReplyBodyError(
86
+ "EMPTY_REPLY_BODY",
87
+ "Reply content must include text or at least one attachmentRef",
88
+ )
89
+ if not blocks:
90
+ raise ReplyBodyError("EMPTY_REPLY_BODY", "Reply content is empty")
91
+ return blocks