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.
- monkeybot/__init__.py +3 -0
- monkeybot/cli/__init__.py +3 -0
- monkeybot/cli/__main__.py +8 -0
- monkeybot/cli/audio_io.py +8 -0
- monkeybot/cli/gateway_manager.py +17 -0
- monkeybot/cli/main.py +22 -0
- monkeybot/cli/push_to_talk.py +12 -0
- monkeybot/cli/realtime_client.py +13 -0
- monkeybot/core/__init__.py +19 -0
- monkeybot/core/attachments/__init__.py +22 -0
- monkeybot/core/attachments/catalog.py +62 -0
- monkeybot/core/attachments/config.py +52 -0
- monkeybot/core/attachments/freeze.py +158 -0
- monkeybot/core/attachments/resolve.py +70 -0
- monkeybot/core/attachments/store.py +180 -0
- monkeybot/core/attachments/text.py +72 -0
- monkeybot/core/attachments/tools.py +54 -0
- monkeybot/core/bootstrap.py +242 -0
- monkeybot/core/config/__init__.py +71 -0
- monkeybot/core/config/realtime_config.py +150 -0
- monkeybot/core/config/runtime_env.py +262 -0
- monkeybot/core/config/settings.py +341 -0
- monkeybot/core/config/validation.py +249 -0
- monkeybot/core/config/yaml_loader.py +45 -0
- monkeybot/core/context/__init__.py +781 -0
- monkeybot/core/context/campaign_context.py +8 -0
- monkeybot/core/context/common.py +14 -0
- monkeybot/core/context/curator.py +255 -0
- monkeybot/core/context/epoch.py +226 -0
- monkeybot/core/context/memory_prompt.py +222 -0
- monkeybot/core/context/tool_output_policy.py +270 -0
- monkeybot/core/context/tool_result_ingress.py +290 -0
- monkeybot/core/context/tool_shapers.py +361 -0
- monkeybot/core/hooks/__init__.py +261 -0
- monkeybot/core/llm/__init__.py +4 -0
- monkeybot/core/llm/provider.py +296 -0
- monkeybot/core/llm/realtime_provider.py +203 -0
- monkeybot/core/llm/usage.py +57 -0
- monkeybot/core/logging_utils.py +24 -0
- monkeybot/core/mcp/__init__.py +1 -0
- monkeybot/core/mcp/mcp_client.py +1215 -0
- monkeybot/core/mcp/ports_mcp.py +109 -0
- monkeybot/core/memory/__init__.py +24 -0
- monkeybot/core/memory/hook.py +413 -0
- monkeybot/core/memory/index_format.py +104 -0
- monkeybot/core/memory/integrity.py +180 -0
- monkeybot/core/memory/organizer.py +270 -0
- monkeybot/core/memory/storage_ops.py +139 -0
- monkeybot/core/memory/subsystem.py +91 -0
- monkeybot/core/messages/__init__.py +16 -0
- monkeybot/core/messages/convert_provider.py +41 -0
- monkeybot/core/messages/tool_integrity.py +262 -0
- monkeybot/core/messages/transform_context.py +84 -0
- monkeybot/core/path_safety.py +11 -0
- monkeybot/core/persistence/__init__.py +17 -0
- monkeybot/core/persistence/backends.py +236 -0
- monkeybot/core/persistence/db.py +28 -0
- monkeybot/core/persistence/durable_runs.py +286 -0
- monkeybot/core/persistence/firestore.py +658 -0
- monkeybot/core/persistence/firestore_scheduled_loops.py +336 -0
- monkeybot/core/persistence/history.py +156 -0
- monkeybot/core/persistence/postgres.py +895 -0
- monkeybot/core/persistence/runs.py +76 -0
- monkeybot/core/persistence/scheduled_loops.py +435 -0
- monkeybot/core/persistence/session_turn_locks.py +94 -0
- monkeybot/core/persistence/sqlite.py +218 -0
- monkeybot/core/persistence/sqlite_backend.py +74 -0
- monkeybot/core/persistence/thread_summary.py +61 -0
- monkeybot/core/persistence/transcript.py +194 -0
- monkeybot/core/persistence/usage.py +149 -0
- monkeybot/core/prompts/__init__.py +1 -0
- monkeybot/core/prompts/harness_prompt.py +197 -0
- monkeybot/core/prompts/prompt.py +215 -0
- monkeybot/core/runtime/__init__.py +1 -0
- monkeybot/core/runtime/context_budget.py +267 -0
- monkeybot/core/runtime/events.py +819 -0
- monkeybot/core/runtime/input_admission.py +154 -0
- monkeybot/core/runtime/loop.py +2374 -0
- monkeybot/core/runtime/provider_stream_mapper.py +159 -0
- monkeybot/core/runtime/realtime_loop.py +654 -0
- monkeybot/core/runtime/utterance_buffer.py +179 -0
- monkeybot/core/subagents/__init__.py +1 -0
- monkeybot/core/subagents/subagent_proto.py +331 -0
- monkeybot/core/subagents/subagent_worker.py +441 -0
- monkeybot/core/subagents/worker_pool.py +403 -0
- monkeybot/core/testing/__init__.py +1 -0
- monkeybot/core/testing/mocks_provider.py +86 -0
- monkeybot/core/testing/mocks_realtime_provider.py +137 -0
- monkeybot/core/tools/__init__.py +1 -0
- monkeybot/core/tools/core_tool_executor.py +1548 -0
- monkeybot/core/tools/inspector.py +226 -0
- monkeybot/core/tools/loop_inspector.py +45 -0
- monkeybot/core/tools/patch.py +480 -0
- monkeybot/core/tools/permission.py +284 -0
- monkeybot/core/tools/sandbox_executor.py +255 -0
- monkeybot/core/tools/spill_inventory.py +35 -0
- monkeybot/core/tools/terminal.py +381 -0
- monkeybot/core/tools/text_normalize.py +25 -0
- monkeybot/core/tools/types.py +33 -0
- monkeybot/core/tools/workspace_service.py +710 -0
- monkeybot/core/tools/workspace_tools.py +116 -0
- monkeybot/core/types/__init__.py +1 -0
- monkeybot/core/types/content_blocks.py +644 -0
- monkeybot/core/types/interfaces.py +156 -0
- monkeybot/core/types/types_tools.py +29 -0
- monkeybot/core/workspace/__init__.py +8 -0
- monkeybot/core/workspace/factory.py +45 -0
- monkeybot/core/workspace/gcs.py +130 -0
- monkeybot/core/workspace/local.py +162 -0
- monkeybot/core/workspace/protocol.py +45 -0
- monkeybot/core/workspace/s3.py +151 -0
- monkeybot/core/workspace_layout.py +27 -0
- monkeybot/gateway/__init__.py +1 -0
- monkeybot/gateway/bootstrap.py +18 -0
- monkeybot/gateway/main.py +47 -0
- monkeybot/gateway/realtime/__init__.py +31 -0
- monkeybot/gateway/realtime/app.py +321 -0
- monkeybot/gateway/realtime/deps.py +52 -0
- monkeybot/gateway/realtime/errors.py +81 -0
- monkeybot/gateway/realtime/guardrails.py +88 -0
- monkeybot/gateway/realtime/manager.py +77 -0
- monkeybot/gateway/realtime/metrics.py +144 -0
- monkeybot/gateway/realtime/routes.py +864 -0
- monkeybot/gateway/realtime/session.py +232 -0
- monkeybot/gateway/realtime/wire.py +412 -0
- monkeybot/gateway/realtime_main.py +49 -0
- monkeybot/gateway/sse/__init__.py +1 -0
- monkeybot/gateway/sse/app.py +733 -0
- monkeybot/gateway/sse/loop_port.py +31 -0
- monkeybot/gateway/sse/models.py +177 -0
- monkeybot/gateway/sse/reply_body.py +91 -0
- monkeybot/gateway/sse/routes.py +1101 -0
- monkeybot/gateway/sse/scheduler_routes.py +200 -0
- monkeybot/gateway/sse/scheduler_wiring.py +96 -0
- monkeybot/gateway/sse/session_bus.py +226 -0
- monkeybot/gateway/sse/sse.py +46 -0
- monkeybot/gateway/sse/workspace_layout.py +7 -0
- monkeybot/observability/__init__.py +220 -0
- monkeybot/observability/_state.py +10 -0
- monkeybot/observability/instrumentation.py +153 -0
- monkeybot/observability/propagation.py +65 -0
- monkeybot/observability/spans.py +455 -0
- monkeybot/providers/__init__.py +19 -0
- monkeybot/providers/_openai_compat.py +450 -0
- monkeybot/providers/_utils.py +473 -0
- monkeybot/providers/bedrock.py +145 -0
- monkeybot/providers/claude.py +125 -0
- monkeybot/providers/gemini.py +677 -0
- monkeybot/providers/gemini_live.py +398 -0
- monkeybot/providers/huggingface.py +129 -0
- monkeybot/providers/nvidia.py +104 -0
- monkeybot/providers/ollama.py +152 -0
- monkeybot/providers/openai.py +127 -0
- monkeybot/providers/pricing.py +60 -0
- monkeybot/providers/sampling.py +44 -0
- monkeybot/providers/vertex_claude.py +148 -0
- monkeybot/scaffold/__init__.py +33 -0
- monkeybot/scheduler/__init__.py +13 -0
- monkeybot/scheduler/__main__.py +4 -0
- monkeybot/scheduler/engine.py +333 -0
- monkeybot/scheduler/http_invoker.py +61 -0
- monkeybot/scheduler/interval.py +77 -0
- monkeybot/scheduler/tick_result.py +34 -0
- monkeybot/scheduler/worker.py +87 -0
- monkeybot/subagents/__init__.py +1 -0
- monkeybot/subagents/worker/__init__.py +1 -0
- monkeybot/subagents/worker/__main__.py +22 -0
- monkeybot/web_search/__init__.py +82 -0
- monkeybot/web_search/backends/__init__.py +5 -0
- monkeybot/web_search/backends/duckduckgo.py +32 -0
- monkeybot/web_search/backends/firecrawl.py +43 -0
- monkeybot/web_search/backends/tavily.py +45 -0
- monkeybot/web_search/protocol.py +25 -0
- monkeybot/web_search/tool.py +56 -0
- monkeybot-2.1.1.dist-info/METADATA +318 -0
- monkeybot-2.1.1.dist-info/RECORD +178 -0
- monkeybot-2.1.1.dist-info/WHEEL +4 -0
- monkeybot-2.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
"""Typed conversation content blocks (goose-style discriminated union).
|
|
2
|
+
|
|
3
|
+
Each message turn is a list of ContentBlock values serialized as JSON objects
|
|
4
|
+
with a ``type`` discriminator. Wire field names use camelCase; Python
|
|
5
|
+
dataclass fields use snake_case. This module owns serde invariants shared by
|
|
6
|
+
SQLite history (Story 2), provider adapters (Story 3), and gateway payloads.
|
|
7
|
+
|
|
8
|
+
The variant set is closed; unknown ``type`` tags fail deserialization with
|
|
9
|
+
ValueError. Optional fields are omitted from JSON when None.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import ClassVar, Literal, Self, TypeAlias, cast
|
|
18
|
+
|
|
19
|
+
_DISPATCH: dict[str, type[ContentBlock]] = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _drop_none(values: dict[str, object | None]) -> dict[str, object]:
|
|
23
|
+
"""Return a copy with key/value pairs whose value is not None."""
|
|
24
|
+
return {k: v for k, v in values.items() if v is not None}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _require_str(d: Mapping[str, object], key: str, *, context: str) -> str:
|
|
28
|
+
"""Return a required string field or raise ValueError."""
|
|
29
|
+
if key not in d:
|
|
30
|
+
raise ValueError(f"{context}: missing required field {key!r}")
|
|
31
|
+
val = d[key]
|
|
32
|
+
if not isinstance(val, str):
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"{context}: field {key!r} must be a string, got {type(val).__name__}"
|
|
35
|
+
)
|
|
36
|
+
return val
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _optional_str(d: Mapping[str, object], key: str) -> str | None:
|
|
40
|
+
"""Return optional string: absent or JSON null -> None."""
|
|
41
|
+
if key not in d or d[key] is None:
|
|
42
|
+
return None
|
|
43
|
+
val = d[key]
|
|
44
|
+
if not isinstance(val, str):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"optional field {key!r}: expected string or null, got {type(val).__name__}"
|
|
47
|
+
)
|
|
48
|
+
return val
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _require_dict(d: Mapping[str, object], key: str, context: str) -> dict[str, object]:
|
|
52
|
+
"""Return a required JSON object at key."""
|
|
53
|
+
if key not in d:
|
|
54
|
+
raise ValueError(f"{context}: missing required field {key!r}")
|
|
55
|
+
val = d[key]
|
|
56
|
+
if not isinstance(val, dict):
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"{context}: field {key!r} must be a JSON object, got {type(val).__name__}"
|
|
59
|
+
)
|
|
60
|
+
return val
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _optional_dict(
|
|
64
|
+
d: Mapping[str, object], key: str, *, context: str
|
|
65
|
+
) -> dict[str, object] | None:
|
|
66
|
+
"""Return optional JSON object; absent or null -> None."""
|
|
67
|
+
if key not in d or d[key] is None:
|
|
68
|
+
return None
|
|
69
|
+
val = d[key]
|
|
70
|
+
if not isinstance(val, dict):
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"{context}: field {key!r} must be a JSON object or null, "
|
|
73
|
+
f"got {type(val).__name__}"
|
|
74
|
+
)
|
|
75
|
+
return val
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _require_bool(d: Mapping[str, object], key: str, *, context: str) -> bool:
|
|
79
|
+
"""Return a required boolean field."""
|
|
80
|
+
if key not in d:
|
|
81
|
+
raise ValueError(f"{context}: missing required field {key!r}")
|
|
82
|
+
val = d[key]
|
|
83
|
+
if not isinstance(val, bool):
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"{context}: field {key!r} must be a boolean, got {type(val).__name__}"
|
|
86
|
+
)
|
|
87
|
+
return val
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _parse_tool_response_is_error(d: Mapping[str, object], *, context: str) -> bool:
|
|
91
|
+
"""Parse ``isError``; default False when absent (read tolerant)."""
|
|
92
|
+
if "isError" not in d or d["isError"] is None:
|
|
93
|
+
return False
|
|
94
|
+
return _require_bool(d, "isError", context=context)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _require_block_list(
|
|
98
|
+
d: Mapping[str, object], key: str, *, context: str
|
|
99
|
+
) -> list[ContentBlock]:
|
|
100
|
+
"""Parse a list of content block objects."""
|
|
101
|
+
if key not in d:
|
|
102
|
+
raise ValueError(f"{context}: missing required field {key!r}")
|
|
103
|
+
val = d[key]
|
|
104
|
+
if not isinstance(val, list):
|
|
105
|
+
raise ValueError(
|
|
106
|
+
f"{context}: field {key!r} must be a list, got {type(val).__name__}"
|
|
107
|
+
)
|
|
108
|
+
blocks: list[ContentBlock] = []
|
|
109
|
+
for i, item in enumerate(val):
|
|
110
|
+
if not isinstance(item, dict):
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"{context}: {key!r}[{i}] must be a JSON object, "
|
|
113
|
+
f"got {type(item).__name__}"
|
|
114
|
+
)
|
|
115
|
+
blocks.append(ContentBlock.from_dict(item))
|
|
116
|
+
return blocks
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _require_args_dict(d: Mapping[str, object], key: str, *, context: str) -> dict[str, object]:
|
|
120
|
+
"""Parse ``args`` / ``arguments`` as a JSON object."""
|
|
121
|
+
return _require_dict(d, key, context)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _action_data_from_mapping(data: Mapping[str, object]) -> ActionRequiredData:
|
|
125
|
+
"""Parse ActionRequired inner ``data`` object."""
|
|
126
|
+
action_type = _require_str(data, "actionType", context="ActionRequired.data")
|
|
127
|
+
ctx = "ActionRequired.data"
|
|
128
|
+
if action_type == ToolConfirmationAction.ACTION_TYPE:
|
|
129
|
+
return ToolConfirmationAction(
|
|
130
|
+
id=_require_str(data, "id", context=f"{ctx}.toolConfirmation"),
|
|
131
|
+
tool_name=_require_str(data, "toolName", context=f"{ctx}.toolConfirmation"),
|
|
132
|
+
arguments=_require_args_dict(
|
|
133
|
+
data, "arguments", context=f"{ctx}.toolConfirmation"
|
|
134
|
+
),
|
|
135
|
+
prompt=_optional_str(data, "prompt"),
|
|
136
|
+
)
|
|
137
|
+
if action_type == ElicitationAction.ACTION_TYPE:
|
|
138
|
+
return ElicitationAction(
|
|
139
|
+
id=_require_str(data, "id", context=f"{ctx}.elicitation"),
|
|
140
|
+
message=_require_str(data, "message", context=f"{ctx}.elicitation"),
|
|
141
|
+
requested_schema=_require_dict(
|
|
142
|
+
data, "requestedSchema", context=f"{ctx}.elicitation"
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
if action_type == ElicitationResponseAction.ACTION_TYPE:
|
|
146
|
+
return ElicitationResponseAction(
|
|
147
|
+
id=_require_str(data, "id", context=f"{ctx}.elicitationResponse"),
|
|
148
|
+
user_data=_require_dict(data, "userData", context=f"{ctx}.elicitationResponse"),
|
|
149
|
+
)
|
|
150
|
+
raise ValueError(f"{ctx}: unknown actionType: {action_type!r}")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _action_data_to_dict(data: ActionRequiredData) -> dict[str, object]:
|
|
154
|
+
"""Serialize ActionRequired inner data to wire JSON (camelCase)."""
|
|
155
|
+
if isinstance(data, ToolConfirmationAction):
|
|
156
|
+
inner: dict[str, object | None] = {
|
|
157
|
+
"actionType": data.ACTION_TYPE,
|
|
158
|
+
"id": data.id,
|
|
159
|
+
"toolName": data.tool_name,
|
|
160
|
+
"arguments": data.arguments,
|
|
161
|
+
"prompt": data.prompt,
|
|
162
|
+
}
|
|
163
|
+
return _drop_none(inner)
|
|
164
|
+
if isinstance(data, ElicitationAction):
|
|
165
|
+
return {
|
|
166
|
+
"actionType": data.ACTION_TYPE,
|
|
167
|
+
"id": data.id,
|
|
168
|
+
"message": data.message,
|
|
169
|
+
"requestedSchema": data.requested_schema,
|
|
170
|
+
}
|
|
171
|
+
if isinstance(data, ElicitationResponseAction):
|
|
172
|
+
return {
|
|
173
|
+
"actionType": data.ACTION_TYPE,
|
|
174
|
+
"id": data.id,
|
|
175
|
+
"userData": data.user_data,
|
|
176
|
+
}
|
|
177
|
+
raise TypeError(f"unsupported ActionRequiredData: {type(data)!r}")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _notification_type_from_wire(
|
|
181
|
+
d: Mapping[str, object], *, context: str
|
|
182
|
+
) -> SystemNotificationType:
|
|
183
|
+
"""Parse and validate ``notificationType``."""
|
|
184
|
+
raw = _require_str(d, "notificationType", context=context)
|
|
185
|
+
if raw not in ("thinkingMessage", "inlineMessage", "creditsExhausted"):
|
|
186
|
+
raise ValueError(f"{context}: invalid notificationType: {raw!r}")
|
|
187
|
+
return raw # type: ignore[return-value]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ContentBlock(ABC):
|
|
191
|
+
"""Abstract base for all typed blocks (see subclasses for variants)."""
|
|
192
|
+
|
|
193
|
+
TYPE: ClassVar[str]
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
def to_dict(self) -> dict[str, object]:
|
|
197
|
+
"""Serialize this block to a JSON-compatible dict (camelCase keys)."""
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
@abstractmethod
|
|
201
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
202
|
+
"""Parse validated payload (``type`` already checked by dispatcher)."""
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def from_dict(cls, d: Mapping[str, object]) -> Self:
|
|
206
|
+
"""Deserialize a content block or dispatch from the abstract base."""
|
|
207
|
+
if cls is ContentBlock:
|
|
208
|
+
tag = d.get("type")
|
|
209
|
+
if not isinstance(tag, str) or tag not in _DISPATCH:
|
|
210
|
+
raise ValueError(f"unknown ContentBlock type: {tag!r}")
|
|
211
|
+
concrete = _DISPATCH[tag]
|
|
212
|
+
return cast(Self, concrete._from_dict_payload(d))
|
|
213
|
+
tag = d.get("type")
|
|
214
|
+
if not isinstance(tag, str) or tag != cls.TYPE:
|
|
215
|
+
raise ValueError(f"expected ContentBlock type {cls.TYPE!r}, got {tag!r}")
|
|
216
|
+
return cls._from_dict_payload(d)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass(frozen=True, kw_only=True)
|
|
220
|
+
class ToolConfirmationAction:
|
|
221
|
+
"""Tool confirmation payload for :class:`ActionRequired`."""
|
|
222
|
+
|
|
223
|
+
ACTION_TYPE: ClassVar[str] = "toolConfirmation"
|
|
224
|
+
id: str
|
|
225
|
+
tool_name: str
|
|
226
|
+
arguments: dict[str, object]
|
|
227
|
+
prompt: str | None = None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@dataclass(frozen=True, kw_only=True)
|
|
231
|
+
class ElicitationAction:
|
|
232
|
+
"""Elicitation request payload for :class:`ActionRequired`."""
|
|
233
|
+
|
|
234
|
+
ACTION_TYPE: ClassVar[str] = "elicitation"
|
|
235
|
+
id: str
|
|
236
|
+
message: str
|
|
237
|
+
requested_schema: dict[str, object]
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True, kw_only=True)
|
|
241
|
+
class ElicitationResponseAction:
|
|
242
|
+
"""Elicitation response payload for :class:`ActionRequired`."""
|
|
243
|
+
|
|
244
|
+
ACTION_TYPE: ClassVar[str] = "elicitationResponse"
|
|
245
|
+
id: str
|
|
246
|
+
user_data: dict[str, object]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
ActionRequiredData: TypeAlias = (
|
|
250
|
+
ToolConfirmationAction | ElicitationAction | ElicitationResponseAction
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
SystemNotificationType = Literal["thinkingMessage", "inlineMessage", "creditsExhausted"]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@dataclass(frozen=True, kw_only=True)
|
|
257
|
+
class Text(ContentBlock):
|
|
258
|
+
"""Plain text segment."""
|
|
259
|
+
|
|
260
|
+
TYPE: ClassVar[str] = "text"
|
|
261
|
+
text: str
|
|
262
|
+
|
|
263
|
+
def to_dict(self) -> dict[str, object]:
|
|
264
|
+
return {"type": self.TYPE, "text": self.text}
|
|
265
|
+
|
|
266
|
+
@classmethod
|
|
267
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
268
|
+
return cls(text=_require_str(payload, "text", context="Text"))
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@dataclass(frozen=True, kw_only=True)
|
|
272
|
+
class Image(ContentBlock):
|
|
273
|
+
"""Base64 image payload."""
|
|
274
|
+
|
|
275
|
+
TYPE: ClassVar[str] = "image"
|
|
276
|
+
mime_type: str
|
|
277
|
+
data: str
|
|
278
|
+
metadata: dict[str, object] | None = None
|
|
279
|
+
|
|
280
|
+
def to_dict(self) -> dict[str, object]:
|
|
281
|
+
return _drop_none(
|
|
282
|
+
{
|
|
283
|
+
"type": self.TYPE,
|
|
284
|
+
"mimeType": self.mime_type,
|
|
285
|
+
"data": self.data,
|
|
286
|
+
"metadata": self.metadata,
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
@classmethod
|
|
291
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
292
|
+
return cls(
|
|
293
|
+
mime_type=_require_str(payload, "mimeType", context="Image"),
|
|
294
|
+
data=_require_str(payload, "data", context="Image"),
|
|
295
|
+
metadata=_optional_dict(payload, "metadata", context="Image"),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@dataclass(frozen=True, kw_only=True)
|
|
300
|
+
class File(ContentBlock):
|
|
301
|
+
"""Base64 document payload (e.g. PDF)."""
|
|
302
|
+
|
|
303
|
+
TYPE: ClassVar[str] = "file"
|
|
304
|
+
mime_type: str
|
|
305
|
+
data: str
|
|
306
|
+
metadata: dict[str, object] | None = None
|
|
307
|
+
|
|
308
|
+
def to_dict(self) -> dict[str, object]:
|
|
309
|
+
return _drop_none(
|
|
310
|
+
{
|
|
311
|
+
"type": self.TYPE,
|
|
312
|
+
"mimeType": self.mime_type,
|
|
313
|
+
"data": self.data,
|
|
314
|
+
"metadata": self.metadata,
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
@classmethod
|
|
319
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
320
|
+
return cls(
|
|
321
|
+
mime_type=_require_str(payload, "mimeType", context="File"),
|
|
322
|
+
data=_require_str(payload, "data", context="File"),
|
|
323
|
+
metadata=_optional_dict(payload, "metadata", context="File"),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dataclass(frozen=True, kw_only=True)
|
|
328
|
+
class AttachmentRef(ContentBlock):
|
|
329
|
+
"""Reference to a session-scoped uploaded attachment (client / pre-freeze user turn)."""
|
|
330
|
+
|
|
331
|
+
TYPE: ClassVar[str] = "attachmentRef"
|
|
332
|
+
attachment_id: str
|
|
333
|
+
mime_type: str
|
|
334
|
+
metadata: dict[str, object] | None = None
|
|
335
|
+
|
|
336
|
+
def to_dict(self) -> dict[str, object]:
|
|
337
|
+
return _drop_none(
|
|
338
|
+
{
|
|
339
|
+
"type": self.TYPE,
|
|
340
|
+
"attachmentId": self.attachment_id,
|
|
341
|
+
"mimeType": self.mime_type,
|
|
342
|
+
"metadata": self.metadata,
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
@classmethod
|
|
347
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
348
|
+
return cls(
|
|
349
|
+
attachment_id=_require_str(payload, "attachmentId", context="AttachmentRef"),
|
|
350
|
+
mime_type=_require_str(payload, "mimeType", context="AttachmentRef"),
|
|
351
|
+
metadata=_optional_dict(payload, "metadata", context="AttachmentRef"),
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@dataclass(frozen=True, kw_only=True)
|
|
356
|
+
class AttachmentDescriptor(ContentBlock):
|
|
357
|
+
"""Structured attachment metadata for SSE/UI (not persisted in history)."""
|
|
358
|
+
|
|
359
|
+
TYPE: ClassVar[str] = "attachmentDescriptor"
|
|
360
|
+
attachment_id: str
|
|
361
|
+
mime_type: str
|
|
362
|
+
description: str
|
|
363
|
+
metadata: dict[str, object] | None = None
|
|
364
|
+
|
|
365
|
+
def to_dict(self) -> dict[str, object]:
|
|
366
|
+
return _drop_none(
|
|
367
|
+
{
|
|
368
|
+
"type": self.TYPE,
|
|
369
|
+
"attachmentId": self.attachment_id,
|
|
370
|
+
"mimeType": self.mime_type,
|
|
371
|
+
"description": self.description,
|
|
372
|
+
"metadata": self.metadata,
|
|
373
|
+
}
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
@classmethod
|
|
377
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
378
|
+
return cls(
|
|
379
|
+
attachment_id=_require_str(payload, "attachmentId", context="AttachmentDescriptor"),
|
|
380
|
+
mime_type=_require_str(payload, "mimeType", context="AttachmentDescriptor"),
|
|
381
|
+
description=_require_str(payload, "description", context="AttachmentDescriptor"),
|
|
382
|
+
metadata=_optional_dict(payload, "metadata", context="AttachmentDescriptor"),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@dataclass(frozen=True, kw_only=True)
|
|
387
|
+
class ToolRequest(ContentBlock):
|
|
388
|
+
"""Model-issued tool invocation request.
|
|
389
|
+
|
|
390
|
+
``metadata`` carries opaque, provider-specific round-trip data (e.g. Vertex
|
|
391
|
+
Gemini's ``thoughtSignature``). It is omitted from JSON when None or empty.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
TYPE: ClassVar[str] = "toolRequest"
|
|
395
|
+
id: str
|
|
396
|
+
name: str
|
|
397
|
+
args: dict[str, object]
|
|
398
|
+
parse_error: str | None = None
|
|
399
|
+
metadata: dict[str, object] | None = None
|
|
400
|
+
|
|
401
|
+
def to_dict(self) -> dict[str, object]:
|
|
402
|
+
meta = self.metadata if self.metadata else None
|
|
403
|
+
return _drop_none(
|
|
404
|
+
{
|
|
405
|
+
"type": self.TYPE,
|
|
406
|
+
"id": self.id,
|
|
407
|
+
"name": self.name,
|
|
408
|
+
"args": self.args,
|
|
409
|
+
"parseError": self.parse_error,
|
|
410
|
+
"metadata": meta,
|
|
411
|
+
}
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
416
|
+
return cls(
|
|
417
|
+
id=_require_str(payload, "id", context="ToolRequest"),
|
|
418
|
+
name=_require_str(payload, "name", context="ToolRequest"),
|
|
419
|
+
args=_require_args_dict(payload, "args", context="ToolRequest"),
|
|
420
|
+
parse_error=_optional_str(payload, "parseError"),
|
|
421
|
+
metadata=_optional_dict(payload, "metadata", context="ToolRequest"),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@dataclass(frozen=True, kw_only=True)
|
|
426
|
+
class ToolResponse(ContentBlock):
|
|
427
|
+
"""Tool execution result as nested content blocks."""
|
|
428
|
+
|
|
429
|
+
TYPE: ClassVar[str] = "toolResponse"
|
|
430
|
+
id: str
|
|
431
|
+
tool_name: str
|
|
432
|
+
result: list[ContentBlock]
|
|
433
|
+
is_error: bool = False
|
|
434
|
+
|
|
435
|
+
def to_dict(self) -> dict[str, object]:
|
|
436
|
+
return {
|
|
437
|
+
"type": self.TYPE,
|
|
438
|
+
"id": self.id,
|
|
439
|
+
"toolName": self.tool_name,
|
|
440
|
+
"result": [b.to_dict() for b in self.result],
|
|
441
|
+
"isError": self.is_error,
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
@classmethod
|
|
445
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
446
|
+
return cls(
|
|
447
|
+
id=_require_str(payload, "id", context="ToolResponse"),
|
|
448
|
+
tool_name=_require_str(payload, "toolName", context="ToolResponse"),
|
|
449
|
+
result=_require_block_list(payload, "result", context="ToolResponse"),
|
|
450
|
+
is_error=_parse_tool_response_is_error(payload, context="ToolResponse"),
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@dataclass(frozen=True, kw_only=True)
|
|
455
|
+
class ToolConfirmationRequest(ContentBlock):
|
|
456
|
+
"""Request for user confirmation before running a tool."""
|
|
457
|
+
|
|
458
|
+
TYPE: ClassVar[str] = "toolConfirmationRequest"
|
|
459
|
+
id: str
|
|
460
|
+
tool_name: str
|
|
461
|
+
arguments: dict[str, object]
|
|
462
|
+
prompt: str | None = None
|
|
463
|
+
|
|
464
|
+
def to_dict(self) -> dict[str, object]:
|
|
465
|
+
return _drop_none(
|
|
466
|
+
{
|
|
467
|
+
"type": self.TYPE,
|
|
468
|
+
"id": self.id,
|
|
469
|
+
"toolName": self.tool_name,
|
|
470
|
+
"arguments": self.arguments,
|
|
471
|
+
"prompt": self.prompt,
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
@classmethod
|
|
476
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
477
|
+
return cls(
|
|
478
|
+
id=_require_str(payload, "id", context="ToolConfirmationRequest"),
|
|
479
|
+
tool_name=_require_str(payload, "toolName", context="ToolConfirmationRequest"),
|
|
480
|
+
arguments=_require_args_dict(
|
|
481
|
+
payload, "arguments", context="ToolConfirmationRequest"
|
|
482
|
+
),
|
|
483
|
+
prompt=_optional_str(payload, "prompt"),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
@dataclass(frozen=True, kw_only=True)
|
|
488
|
+
class FrontendToolRequest(ContentBlock):
|
|
489
|
+
"""Tool request directed at frontend tooling."""
|
|
490
|
+
|
|
491
|
+
TYPE: ClassVar[str] = "frontendToolRequest"
|
|
492
|
+
id: str
|
|
493
|
+
name: str
|
|
494
|
+
args: dict[str, object]
|
|
495
|
+
parse_error: str | None = None
|
|
496
|
+
|
|
497
|
+
def to_dict(self) -> dict[str, object]:
|
|
498
|
+
return _drop_none(
|
|
499
|
+
{
|
|
500
|
+
"type": self.TYPE,
|
|
501
|
+
"id": self.id,
|
|
502
|
+
"name": self.name,
|
|
503
|
+
"args": self.args,
|
|
504
|
+
"parseError": self.parse_error,
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
@classmethod
|
|
509
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
510
|
+
return cls(
|
|
511
|
+
id=_require_str(payload, "id", context="FrontendToolRequest"),
|
|
512
|
+
name=_require_str(payload, "name", context="FrontendToolRequest"),
|
|
513
|
+
args=_require_args_dict(payload, "args", context="FrontendToolRequest"),
|
|
514
|
+
parse_error=_optional_str(payload, "parseError"),
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@dataclass(frozen=True, kw_only=True)
|
|
519
|
+
class Thinking(ContentBlock):
|
|
520
|
+
"""Model reasoning text (opaque to most consumers)."""
|
|
521
|
+
|
|
522
|
+
TYPE: ClassVar[str] = "thinking"
|
|
523
|
+
thinking: str
|
|
524
|
+
signature: str = ""
|
|
525
|
+
|
|
526
|
+
def to_dict(self) -> dict[str, object]:
|
|
527
|
+
return {"type": self.TYPE, "thinking": self.thinking, "signature": self.signature}
|
|
528
|
+
|
|
529
|
+
@classmethod
|
|
530
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
531
|
+
thinking = _require_str(payload, "thinking", context="Thinking")
|
|
532
|
+
sig_raw = payload.get("signature")
|
|
533
|
+
if sig_raw is None:
|
|
534
|
+
signature = ""
|
|
535
|
+
elif not isinstance(sig_raw, str):
|
|
536
|
+
raise ValueError(
|
|
537
|
+
f"Thinking: field 'signature' must be a string, got {type(sig_raw).__name__}"
|
|
538
|
+
)
|
|
539
|
+
else:
|
|
540
|
+
signature = sig_raw
|
|
541
|
+
return cls(thinking=thinking, signature=signature)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@dataclass(frozen=True, kw_only=True)
|
|
545
|
+
class RedactedThinking(ContentBlock):
|
|
546
|
+
"""Opaque redacted reasoning blob."""
|
|
547
|
+
|
|
548
|
+
TYPE: ClassVar[str] = "redactedThinking"
|
|
549
|
+
data: str
|
|
550
|
+
|
|
551
|
+
def to_dict(self) -> dict[str, object]:
|
|
552
|
+
return {"type": self.TYPE, "data": self.data}
|
|
553
|
+
|
|
554
|
+
@classmethod
|
|
555
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
556
|
+
return cls(data=_require_str(payload, "data", context="RedactedThinking"))
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
@dataclass(frozen=True, kw_only=True)
|
|
560
|
+
class SystemNotification(ContentBlock):
|
|
561
|
+
"""System-originated notification block."""
|
|
562
|
+
|
|
563
|
+
TYPE: ClassVar[str] = "systemNotification"
|
|
564
|
+
notification_type: SystemNotificationType
|
|
565
|
+
msg: str
|
|
566
|
+
data: dict[str, object] | None = None
|
|
567
|
+
|
|
568
|
+
def to_dict(self) -> dict[str, object]:
|
|
569
|
+
return _drop_none(
|
|
570
|
+
{
|
|
571
|
+
"type": self.TYPE,
|
|
572
|
+
"notificationType": self.notification_type,
|
|
573
|
+
"msg": self.msg,
|
|
574
|
+
"data": self.data,
|
|
575
|
+
}
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
@classmethod
|
|
579
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
580
|
+
return cls(
|
|
581
|
+
notification_type=_notification_type_from_wire(
|
|
582
|
+
payload, context="SystemNotification"
|
|
583
|
+
),
|
|
584
|
+
msg=_require_str(payload, "msg", context="SystemNotification"),
|
|
585
|
+
data=_optional_dict(payload, "data", context="SystemNotification"),
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
@dataclass(frozen=True, kw_only=True)
|
|
590
|
+
class ActionRequired(ContentBlock):
|
|
591
|
+
"""User or UI must take action (sub-tagged by ``data.actionType``)."""
|
|
592
|
+
|
|
593
|
+
TYPE: ClassVar[str] = "actionRequired"
|
|
594
|
+
data: ActionRequiredData
|
|
595
|
+
|
|
596
|
+
def to_dict(self) -> dict[str, object]:
|
|
597
|
+
return {"type": self.TYPE, "data": _action_data_to_dict(self.data)}
|
|
598
|
+
|
|
599
|
+
@classmethod
|
|
600
|
+
def _from_dict_payload(cls, payload: Mapping[str, object]) -> Self:
|
|
601
|
+
inner = _require_dict(payload, "data", "ActionRequired")
|
|
602
|
+
return cls(data=_action_data_from_mapping(inner))
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
_DISPATCH.update(
|
|
606
|
+
{
|
|
607
|
+
"text": Text,
|
|
608
|
+
"image": Image,
|
|
609
|
+
"file": File,
|
|
610
|
+
"attachmentRef": AttachmentRef,
|
|
611
|
+
"attachmentDescriptor": AttachmentDescriptor,
|
|
612
|
+
"toolRequest": ToolRequest,
|
|
613
|
+
"toolResponse": ToolResponse,
|
|
614
|
+
"toolConfirmationRequest": ToolConfirmationRequest,
|
|
615
|
+
"actionRequired": ActionRequired,
|
|
616
|
+
"frontendToolRequest": FrontendToolRequest,
|
|
617
|
+
"thinking": Thinking,
|
|
618
|
+
"redactedThinking": RedactedThinking,
|
|
619
|
+
"systemNotification": SystemNotification,
|
|
620
|
+
}
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
__all__ = [
|
|
625
|
+
"ActionRequired",
|
|
626
|
+
"ActionRequiredData",
|
|
627
|
+
"AttachmentDescriptor",
|
|
628
|
+
"AttachmentRef",
|
|
629
|
+
"ContentBlock",
|
|
630
|
+
"ElicitationAction",
|
|
631
|
+
"ElicitationResponseAction",
|
|
632
|
+
"File",
|
|
633
|
+
"FrontendToolRequest",
|
|
634
|
+
"Image",
|
|
635
|
+
"RedactedThinking",
|
|
636
|
+
"SystemNotification",
|
|
637
|
+
"SystemNotificationType",
|
|
638
|
+
"Text",
|
|
639
|
+
"Thinking",
|
|
640
|
+
"ToolConfirmationAction",
|
|
641
|
+
"ToolConfirmationRequest",
|
|
642
|
+
"ToolRequest",
|
|
643
|
+
"ToolResponse",
|
|
644
|
+
]
|