codemaster-cli 2.2.0__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.
- codemaster_cli-2.2.0.dist-info/METADATA +645 -0
- codemaster_cli-2.2.0.dist-info/RECORD +170 -0
- codemaster_cli-2.2.0.dist-info/WHEEL +4 -0
- codemaster_cli-2.2.0.dist-info/entry_points.txt +3 -0
- vibe/__init__.py +6 -0
- vibe/acp/__init__.py +0 -0
- vibe/acp/acp_agent_loop.py +746 -0
- vibe/acp/entrypoint.py +81 -0
- vibe/acp/tools/__init__.py +0 -0
- vibe/acp/tools/base.py +100 -0
- vibe/acp/tools/builtins/bash.py +134 -0
- vibe/acp/tools/builtins/read_file.py +54 -0
- vibe/acp/tools/builtins/search_replace.py +129 -0
- vibe/acp/tools/builtins/todo.py +65 -0
- vibe/acp/tools/builtins/write_file.py +98 -0
- vibe/acp/tools/session_update.py +118 -0
- vibe/acp/utils.py +213 -0
- vibe/cli/__init__.py +0 -0
- vibe/cli/autocompletion/__init__.py +0 -0
- vibe/cli/autocompletion/base.py +22 -0
- vibe/cli/autocompletion/path_completion.py +177 -0
- vibe/cli/autocompletion/slash_command.py +99 -0
- vibe/cli/cli.py +188 -0
- vibe/cli/clipboard.py +69 -0
- vibe/cli/commands.py +116 -0
- vibe/cli/entrypoint.py +163 -0
- vibe/cli/history_manager.py +91 -0
- vibe/cli/plan_offer/adapters/http_whoami_gateway.py +67 -0
- vibe/cli/plan_offer/decide_plan_offer.py +87 -0
- vibe/cli/plan_offer/ports/whoami_gateway.py +23 -0
- vibe/cli/terminal_setup.py +323 -0
- vibe/cli/textual_ui/__init__.py +0 -0
- vibe/cli/textual_ui/ansi_markdown.py +58 -0
- vibe/cli/textual_ui/app.py +1546 -0
- vibe/cli/textual_ui/app.tcss +1020 -0
- vibe/cli/textual_ui/external_editor.py +32 -0
- vibe/cli/textual_ui/handlers/__init__.py +5 -0
- vibe/cli/textual_ui/handlers/event_handler.py +147 -0
- vibe/cli/textual_ui/widgets/__init__.py +0 -0
- vibe/cli/textual_ui/widgets/approval_app.py +192 -0
- vibe/cli/textual_ui/widgets/banner/banner.py +85 -0
- vibe/cli/textual_ui/widgets/banner/petit_chat.py +195 -0
- vibe/cli/textual_ui/widgets/braille_renderer.py +58 -0
- vibe/cli/textual_ui/widgets/chat_input/__init__.py +7 -0
- vibe/cli/textual_ui/widgets/chat_input/body.py +214 -0
- vibe/cli/textual_ui/widgets/chat_input/completion_manager.py +58 -0
- vibe/cli/textual_ui/widgets/chat_input/completion_popup.py +43 -0
- vibe/cli/textual_ui/widgets/chat_input/container.py +195 -0
- vibe/cli/textual_ui/widgets/chat_input/text_area.py +365 -0
- vibe/cli/textual_ui/widgets/compact.py +41 -0
- vibe/cli/textual_ui/widgets/config_app.py +171 -0
- vibe/cli/textual_ui/widgets/context_progress.py +30 -0
- vibe/cli/textual_ui/widgets/load_more.py +43 -0
- vibe/cli/textual_ui/widgets/loading.py +201 -0
- vibe/cli/textual_ui/widgets/messages.py +277 -0
- vibe/cli/textual_ui/widgets/no_markup_static.py +11 -0
- vibe/cli/textual_ui/widgets/path_display.py +28 -0
- vibe/cli/textual_ui/widgets/proxy_setup_app.py +127 -0
- vibe/cli/textual_ui/widgets/question_app.py +496 -0
- vibe/cli/textual_ui/widgets/spinner.py +194 -0
- vibe/cli/textual_ui/widgets/status_message.py +76 -0
- vibe/cli/textual_ui/widgets/teleport_message.py +31 -0
- vibe/cli/textual_ui/widgets/tool_widgets.py +371 -0
- vibe/cli/textual_ui/widgets/tools.py +201 -0
- vibe/cli/textual_ui/windowing/__init__.py +29 -0
- vibe/cli/textual_ui/windowing/history.py +105 -0
- vibe/cli/textual_ui/windowing/history_windowing.py +71 -0
- vibe/cli/textual_ui/windowing/state.py +105 -0
- vibe/cli/update_notifier/__init__.py +47 -0
- vibe/cli/update_notifier/adapters/filesystem_update_cache_repository.py +59 -0
- vibe/cli/update_notifier/adapters/github_update_gateway.py +101 -0
- vibe/cli/update_notifier/adapters/pypi_update_gateway.py +107 -0
- vibe/cli/update_notifier/ports/update_cache_repository.py +16 -0
- vibe/cli/update_notifier/ports/update_gateway.py +53 -0
- vibe/cli/update_notifier/update.py +139 -0
- vibe/cli/update_notifier/whats_new.py +49 -0
- vibe/core/__init__.py +5 -0
- vibe/core/agent_loop.py +1075 -0
- vibe/core/agents/__init__.py +31 -0
- vibe/core/agents/manager.py +165 -0
- vibe/core/agents/models.py +122 -0
- vibe/core/auth/__init__.py +6 -0
- vibe/core/auth/crypto.py +137 -0
- vibe/core/auth/github.py +178 -0
- vibe/core/autocompletion/__init__.py +0 -0
- vibe/core/autocompletion/completers.py +257 -0
- vibe/core/autocompletion/file_indexer/__init__.py +10 -0
- vibe/core/autocompletion/file_indexer/ignore_rules.py +156 -0
- vibe/core/autocompletion/file_indexer/indexer.py +179 -0
- vibe/core/autocompletion/file_indexer/store.py +169 -0
- vibe/core/autocompletion/file_indexer/watcher.py +71 -0
- vibe/core/autocompletion/fuzzy.py +189 -0
- vibe/core/autocompletion/path_prompt.py +108 -0
- vibe/core/autocompletion/path_prompt_adapter.py +149 -0
- vibe/core/config.py +673 -0
- vibe/core/config_PATCH_INSTRUCTIONS.md +77 -0
- vibe/core/llm/__init__.py +0 -0
- vibe/core/llm/backend/anthropic.py +630 -0
- vibe/core/llm/backend/base.py +38 -0
- vibe/core/llm/backend/factory.py +7 -0
- vibe/core/llm/backend/generic.py +425 -0
- vibe/core/llm/backend/mistral.py +381 -0
- vibe/core/llm/backend/vertex.py +115 -0
- vibe/core/llm/exceptions.py +195 -0
- vibe/core/llm/format.py +184 -0
- vibe/core/llm/message_utils.py +24 -0
- vibe/core/llm/types.py +120 -0
- vibe/core/middleware.py +209 -0
- vibe/core/output_formatters.py +85 -0
- vibe/core/paths/__init__.py +0 -0
- vibe/core/paths/config_paths.py +68 -0
- vibe/core/paths/global_paths.py +40 -0
- vibe/core/programmatic.py +56 -0
- vibe/core/prompts/__init__.py +32 -0
- vibe/core/prompts/cli.md +111 -0
- vibe/core/prompts/compact.md +48 -0
- vibe/core/prompts/dangerous_directory.md +5 -0
- vibe/core/prompts/explore.md +50 -0
- vibe/core/prompts/project_context.md +8 -0
- vibe/core/prompts/tests.md +1 -0
- vibe/core/proxy_setup.py +65 -0
- vibe/core/session/session_loader.py +222 -0
- vibe/core/session/session_logger.py +318 -0
- vibe/core/session/session_migration.py +41 -0
- vibe/core/skills/__init__.py +7 -0
- vibe/core/skills/manager.py +132 -0
- vibe/core/skills/models.py +92 -0
- vibe/core/skills/parser.py +39 -0
- vibe/core/system_prompt.py +466 -0
- vibe/core/telemetry/__init__.py +0 -0
- vibe/core/telemetry/send.py +185 -0
- vibe/core/teleport/errors.py +9 -0
- vibe/core/teleport/git.py +196 -0
- vibe/core/teleport/nuage.py +180 -0
- vibe/core/teleport/teleport.py +208 -0
- vibe/core/teleport/types.py +54 -0
- vibe/core/tools/base.py +336 -0
- vibe/core/tools/builtins/ask_user_question.py +134 -0
- vibe/core/tools/builtins/bash.py +357 -0
- vibe/core/tools/builtins/grep.py +310 -0
- vibe/core/tools/builtins/prompts/__init__.py +0 -0
- vibe/core/tools/builtins/prompts/ask_user_question.md +84 -0
- vibe/core/tools/builtins/prompts/bash.md +73 -0
- vibe/core/tools/builtins/prompts/grep.md +4 -0
- vibe/core/tools/builtins/prompts/read_file.md +13 -0
- vibe/core/tools/builtins/prompts/search_replace.md +43 -0
- vibe/core/tools/builtins/prompts/task.md +24 -0
- vibe/core/tools/builtins/prompts/todo.md +199 -0
- vibe/core/tools/builtins/prompts/write_file.md +42 -0
- vibe/core/tools/builtins/read_file.py +222 -0
- vibe/core/tools/builtins/search_replace.py +456 -0
- vibe/core/tools/builtins/task.py +154 -0
- vibe/core/tools/builtins/todo.py +134 -0
- vibe/core/tools/builtins/write_file.py +160 -0
- vibe/core/tools/manager.py +341 -0
- vibe/core/tools/mcp.py +397 -0
- vibe/core/tools/ui.py +68 -0
- vibe/core/trusted_folders.py +86 -0
- vibe/core/types.py +405 -0
- vibe/core/utils.py +396 -0
- vibe/setup/onboarding/__init__.py +39 -0
- vibe/setup/onboarding/base.py +14 -0
- vibe/setup/onboarding/onboarding.tcss +134 -0
- vibe/setup/onboarding/screens/__init__.py +5 -0
- vibe/setup/onboarding/screens/api_key.py +200 -0
- vibe/setup/onboarding/screens/provider_selection.py +87 -0
- vibe/setup/onboarding/screens/welcome.py +136 -0
- vibe/setup/trusted_folders/trust_folder_dialog.py +180 -0
- vibe/setup/trusted_folders/trust_folder_dialog.tcss +83 -0
- vibe/whats_new.md +5 -0
vibe/core/types.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
import copy
|
|
7
|
+
from enum import StrEnum, auto
|
|
8
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from vibe.core.tools.base import BaseTool
|
|
13
|
+
else:
|
|
14
|
+
BaseTool = Any
|
|
15
|
+
|
|
16
|
+
from pydantic import (
|
|
17
|
+
BaseModel,
|
|
18
|
+
BeforeValidator,
|
|
19
|
+
ConfigDict,
|
|
20
|
+
Field,
|
|
21
|
+
PrivateAttr,
|
|
22
|
+
computed_field,
|
|
23
|
+
model_validator,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AgentStats(BaseModel):
|
|
28
|
+
steps: int = 0
|
|
29
|
+
session_prompt_tokens: int = 0
|
|
30
|
+
session_completion_tokens: int = 0
|
|
31
|
+
tool_calls_agreed: int = 0
|
|
32
|
+
tool_calls_rejected: int = 0
|
|
33
|
+
tool_calls_failed: int = 0
|
|
34
|
+
tool_calls_succeeded: int = 0
|
|
35
|
+
|
|
36
|
+
context_tokens: int = 0
|
|
37
|
+
|
|
38
|
+
last_turn_prompt_tokens: int = 0
|
|
39
|
+
last_turn_completion_tokens: int = 0
|
|
40
|
+
last_turn_duration: float = 0.0
|
|
41
|
+
tokens_per_second: float = 0.0
|
|
42
|
+
|
|
43
|
+
input_price_per_million: float = 0.0
|
|
44
|
+
output_price_per_million: float = 0.0
|
|
45
|
+
|
|
46
|
+
_listeners: dict[str, Callable[[AgentStats], None]] = PrivateAttr(
|
|
47
|
+
default_factory=dict
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
51
|
+
super().__setattr__(name, value)
|
|
52
|
+
if name in self._listeners:
|
|
53
|
+
self._listeners[name](self)
|
|
54
|
+
|
|
55
|
+
def trigger_listeners(self) -> None:
|
|
56
|
+
for listener in self._listeners.values():
|
|
57
|
+
listener(self)
|
|
58
|
+
|
|
59
|
+
def add_listener(
|
|
60
|
+
self, attr_name: str, listener: Callable[[AgentStats], None]
|
|
61
|
+
) -> None:
|
|
62
|
+
self._listeners[attr_name] = listener
|
|
63
|
+
|
|
64
|
+
@computed_field
|
|
65
|
+
@property
|
|
66
|
+
def session_total_llm_tokens(self) -> int:
|
|
67
|
+
return self.session_prompt_tokens + self.session_completion_tokens
|
|
68
|
+
|
|
69
|
+
@computed_field
|
|
70
|
+
@property
|
|
71
|
+
def last_turn_total_tokens(self) -> int:
|
|
72
|
+
return self.last_turn_prompt_tokens + self.last_turn_completion_tokens
|
|
73
|
+
|
|
74
|
+
@computed_field
|
|
75
|
+
@property
|
|
76
|
+
def session_cost(self) -> float:
|
|
77
|
+
"""Calculate the total session cost in dollars based on token usage and pricing.
|
|
78
|
+
|
|
79
|
+
NOTE: This is a rough estimate and is worst-case scenario.
|
|
80
|
+
The actual cost may be lower due to prompt caching.
|
|
81
|
+
If the model changes mid-session, this uses current pricing for all tokens.
|
|
82
|
+
"""
|
|
83
|
+
input_cost = (
|
|
84
|
+
self.session_prompt_tokens / 1_000_000
|
|
85
|
+
) * self.input_price_per_million
|
|
86
|
+
output_cost = (
|
|
87
|
+
self.session_completion_tokens / 1_000_000
|
|
88
|
+
) * self.output_price_per_million
|
|
89
|
+
return input_cost + output_cost
|
|
90
|
+
|
|
91
|
+
def update_pricing(self, input_price: float, output_price: float) -> None:
|
|
92
|
+
"""Update pricing info when model changes.
|
|
93
|
+
|
|
94
|
+
NOTE: session_cost will be recalculated using new pricing for all
|
|
95
|
+
accumulated tokens. This is a known approximation when models change.
|
|
96
|
+
This should not be a big issue, pricing is only used for max_price which is in
|
|
97
|
+
programmatic mode, so user should not update models there.
|
|
98
|
+
"""
|
|
99
|
+
self.input_price_per_million = input_price
|
|
100
|
+
self.output_price_per_million = output_price
|
|
101
|
+
|
|
102
|
+
def reset_context_state(self) -> None:
|
|
103
|
+
"""Reset context-related fields while preserving cumulative session stats.
|
|
104
|
+
|
|
105
|
+
Used after config reload or similar operations where the context
|
|
106
|
+
changes but we want to preserve session totals.
|
|
107
|
+
"""
|
|
108
|
+
self.context_tokens = 0
|
|
109
|
+
self.last_turn_prompt_tokens = 0
|
|
110
|
+
self.last_turn_completion_tokens = 0
|
|
111
|
+
self.last_turn_duration = 0.0
|
|
112
|
+
self.tokens_per_second = 0.0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class SessionInfo(BaseModel):
|
|
116
|
+
session_id: str
|
|
117
|
+
start_time: str
|
|
118
|
+
message_count: int
|
|
119
|
+
stats: AgentStats
|
|
120
|
+
save_dir: str
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class SessionMetadata(BaseModel):
|
|
124
|
+
session_id: str
|
|
125
|
+
start_time: str
|
|
126
|
+
end_time: str | None
|
|
127
|
+
git_commit: str | None
|
|
128
|
+
git_branch: str | None
|
|
129
|
+
environment: dict[str, str | None]
|
|
130
|
+
username: str
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
StrToolChoice = Literal["auto", "none", "any", "required"]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class AvailableFunction(BaseModel):
|
|
137
|
+
name: str
|
|
138
|
+
description: str
|
|
139
|
+
parameters: dict[str, Any]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class AvailableTool(BaseModel):
|
|
143
|
+
type: Literal["function"] = "function"
|
|
144
|
+
function: AvailableFunction
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class FunctionCall(BaseModel):
|
|
148
|
+
name: str | None = None
|
|
149
|
+
arguments: str | None = None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ToolCall(BaseModel):
|
|
153
|
+
id: str | None = None
|
|
154
|
+
index: int | None = None
|
|
155
|
+
function: FunctionCall = Field(default_factory=FunctionCall)
|
|
156
|
+
type: str = "function"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _content_before(v: Any) -> str:
|
|
160
|
+
if isinstance(v, str):
|
|
161
|
+
return v
|
|
162
|
+
if isinstance(v, list):
|
|
163
|
+
parts: list[str] = []
|
|
164
|
+
for p in v:
|
|
165
|
+
if isinstance(p, dict) and isinstance(p.get("text"), str):
|
|
166
|
+
parts.append(p["text"])
|
|
167
|
+
else:
|
|
168
|
+
parts.append(str(p))
|
|
169
|
+
return "\n".join(parts)
|
|
170
|
+
return str(v)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
Content = Annotated[str, BeforeValidator(_content_before)]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class Role(StrEnum):
|
|
177
|
+
system = auto()
|
|
178
|
+
user = auto()
|
|
179
|
+
assistant = auto()
|
|
180
|
+
tool = auto()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class ApprovalResponse(StrEnum):
|
|
184
|
+
YES = "y"
|
|
185
|
+
NO = "n"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class LLMMessage(BaseModel):
|
|
189
|
+
model_config = ConfigDict(extra="ignore")
|
|
190
|
+
|
|
191
|
+
role: Role
|
|
192
|
+
content: Content | None = None
|
|
193
|
+
reasoning_content: Content | None = None
|
|
194
|
+
reasoning_signature: str | None = None
|
|
195
|
+
tool_calls: list[ToolCall] | None = None
|
|
196
|
+
name: str | None = None
|
|
197
|
+
tool_call_id: str | None = None
|
|
198
|
+
message_id: str | None = None
|
|
199
|
+
|
|
200
|
+
@model_validator(mode="before")
|
|
201
|
+
@classmethod
|
|
202
|
+
def _from_any(cls, v: Any) -> dict[str, Any] | Any:
|
|
203
|
+
if isinstance(v, dict):
|
|
204
|
+
v.setdefault("content", "")
|
|
205
|
+
v.setdefault("role", "assistant")
|
|
206
|
+
if "message_id" not in v and v.get("role") != "tool":
|
|
207
|
+
v["message_id"] = str(uuid4())
|
|
208
|
+
return v
|
|
209
|
+
role = str(getattr(v, "role", "assistant"))
|
|
210
|
+
return {
|
|
211
|
+
"role": role,
|
|
212
|
+
"content": getattr(v, "content", ""),
|
|
213
|
+
"reasoning_content": getattr(v, "reasoning_content", None),
|
|
214
|
+
"reasoning_signature": getattr(v, "reasoning_signature", None),
|
|
215
|
+
"tool_calls": getattr(v, "tool_calls", None),
|
|
216
|
+
"name": getattr(v, "name", None),
|
|
217
|
+
"tool_call_id": getattr(v, "tool_call_id", None),
|
|
218
|
+
"message_id": getattr(v, "message_id", None)
|
|
219
|
+
or (str(uuid4()) if role != "tool" else None),
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
def __add__(self, other: LLMMessage) -> LLMMessage:
|
|
223
|
+
"""Careful: this is not commutative!"""
|
|
224
|
+
if self.role != other.role:
|
|
225
|
+
raise ValueError("Can't accumulate messages with different roles")
|
|
226
|
+
|
|
227
|
+
if self.name != other.name:
|
|
228
|
+
raise ValueError("Can't accumulate messages with different names")
|
|
229
|
+
|
|
230
|
+
if self.tool_call_id != other.tool_call_id:
|
|
231
|
+
raise ValueError("Can't accumulate messages with different tool_call_ids")
|
|
232
|
+
|
|
233
|
+
content = (self.content or "") + (other.content or "")
|
|
234
|
+
if not content:
|
|
235
|
+
content = None
|
|
236
|
+
|
|
237
|
+
reasoning_content = (self.reasoning_content or "") + (
|
|
238
|
+
other.reasoning_content or ""
|
|
239
|
+
)
|
|
240
|
+
if not reasoning_content:
|
|
241
|
+
reasoning_content = None
|
|
242
|
+
|
|
243
|
+
reasoning_signature = (self.reasoning_signature or "") + (
|
|
244
|
+
other.reasoning_signature or ""
|
|
245
|
+
)
|
|
246
|
+
if not reasoning_signature:
|
|
247
|
+
reasoning_signature = None
|
|
248
|
+
|
|
249
|
+
tool_calls_map = OrderedDict[int, ToolCall]()
|
|
250
|
+
for tool_calls in [self.tool_calls or [], other.tool_calls or []]:
|
|
251
|
+
for tc in tool_calls:
|
|
252
|
+
if tc.index is None:
|
|
253
|
+
raise ValueError("Tool call chunk missing index")
|
|
254
|
+
if tc.index not in tool_calls_map:
|
|
255
|
+
tool_calls_map[tc.index] = copy.deepcopy(tc)
|
|
256
|
+
else:
|
|
257
|
+
existing_name = tool_calls_map[tc.index].function.name
|
|
258
|
+
new_name = tc.function.name
|
|
259
|
+
if existing_name and new_name and existing_name != new_name:
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"Can't accumulate messages with different tool call names"
|
|
262
|
+
)
|
|
263
|
+
if new_name and not existing_name:
|
|
264
|
+
tool_calls_map[tc.index].function.name = new_name
|
|
265
|
+
new_args = (tool_calls_map[tc.index].function.arguments or "") + (
|
|
266
|
+
tc.function.arguments or ""
|
|
267
|
+
)
|
|
268
|
+
tool_calls_map[tc.index].function.arguments = new_args
|
|
269
|
+
|
|
270
|
+
return LLMMessage(
|
|
271
|
+
role=self.role,
|
|
272
|
+
content=content,
|
|
273
|
+
reasoning_content=reasoning_content,
|
|
274
|
+
reasoning_signature=reasoning_signature,
|
|
275
|
+
tool_calls=list(tool_calls_map.values()) or None,
|
|
276
|
+
name=self.name,
|
|
277
|
+
tool_call_id=self.tool_call_id,
|
|
278
|
+
message_id=self.message_id,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class LLMUsage(BaseModel):
|
|
283
|
+
model_config = ConfigDict(frozen=True)
|
|
284
|
+
prompt_tokens: int = 0
|
|
285
|
+
completion_tokens: int = 0
|
|
286
|
+
|
|
287
|
+
def __add__(self, other: LLMUsage) -> LLMUsage:
|
|
288
|
+
return LLMUsage(
|
|
289
|
+
prompt_tokens=self.prompt_tokens + other.prompt_tokens,
|
|
290
|
+
completion_tokens=self.completion_tokens + other.completion_tokens,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class LLMChunk(BaseModel):
|
|
295
|
+
model_config = ConfigDict(frozen=True)
|
|
296
|
+
message: LLMMessage
|
|
297
|
+
usage: LLMUsage | None = None
|
|
298
|
+
|
|
299
|
+
def __add__(self, other: LLMChunk) -> LLMChunk:
|
|
300
|
+
if self.usage is None and other.usage is None:
|
|
301
|
+
new_usage = None
|
|
302
|
+
else:
|
|
303
|
+
new_usage = (self.usage or LLMUsage()) + (other.usage or LLMUsage())
|
|
304
|
+
return LLMChunk(message=self.message + other.message, usage=new_usage)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class BaseEvent(BaseModel, ABC):
|
|
308
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class UserMessageEvent(BaseEvent):
|
|
312
|
+
content: str
|
|
313
|
+
message_id: str
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class AssistantEvent(BaseEvent):
|
|
317
|
+
content: str
|
|
318
|
+
stopped_by_middleware: bool = False
|
|
319
|
+
message_id: str | None = None
|
|
320
|
+
|
|
321
|
+
def __add__(self, other: AssistantEvent) -> AssistantEvent:
|
|
322
|
+
return AssistantEvent(
|
|
323
|
+
content=self.content + other.content,
|
|
324
|
+
stopped_by_middleware=self.stopped_by_middleware
|
|
325
|
+
or other.stopped_by_middleware,
|
|
326
|
+
message_id=self.message_id or other.message_id,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class ReasoningEvent(BaseEvent):
|
|
331
|
+
content: str
|
|
332
|
+
message_id: str | None = None
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class ToolCallEvent(BaseEvent):
|
|
336
|
+
tool_name: str
|
|
337
|
+
tool_class: type[BaseTool]
|
|
338
|
+
args: BaseModel
|
|
339
|
+
tool_call_id: str
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class ToolResultEvent(BaseEvent):
|
|
343
|
+
tool_name: str
|
|
344
|
+
tool_class: type[BaseTool] | None
|
|
345
|
+
result: BaseModel | None = None
|
|
346
|
+
error: str | None = None
|
|
347
|
+
skipped: bool = False
|
|
348
|
+
skip_reason: str | None = None
|
|
349
|
+
duration: float | None = None
|
|
350
|
+
tool_call_id: str
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class ToolStreamEvent(BaseEvent):
|
|
354
|
+
tool_name: str
|
|
355
|
+
message: str
|
|
356
|
+
tool_call_id: str
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class CompactStartEvent(BaseEvent):
|
|
360
|
+
current_context_tokens: int
|
|
361
|
+
threshold: int
|
|
362
|
+
# WORKAROUND: Using tool_call to communicate compact events to the client.
|
|
363
|
+
# This should be revisited when the ACP protocol defines how compact events
|
|
364
|
+
# should be represented.
|
|
365
|
+
# [RFD](https://agentclientprotocol.com/rfds/session-usage)
|
|
366
|
+
tool_call_id: str
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class CompactEndEvent(BaseEvent):
|
|
370
|
+
old_context_tokens: int
|
|
371
|
+
new_context_tokens: int
|
|
372
|
+
summary_length: int
|
|
373
|
+
# WORKAROUND: Using tool_call to communicate compact events to the client.
|
|
374
|
+
# This should be revisited when the ACP protocol defines how compact events
|
|
375
|
+
# should be represented.
|
|
376
|
+
# [RFD](https://agentclientprotocol.com/rfds/session-usage)
|
|
377
|
+
tool_call_id: str
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class OutputFormat(StrEnum):
|
|
381
|
+
TEXT = auto()
|
|
382
|
+
JSON = auto()
|
|
383
|
+
STREAMING = auto()
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
type AsyncApprovalCallback = Callable[
|
|
387
|
+
[str, BaseModel, str], Awaitable[tuple[ApprovalResponse, str | None]]
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
type SyncApprovalCallback = Callable[
|
|
391
|
+
[str, BaseModel, str], tuple[ApprovalResponse, str | None]
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
type ApprovalCallback = AsyncApprovalCallback | SyncApprovalCallback
|
|
395
|
+
|
|
396
|
+
type UserInputCallback = Callable[[BaseModel], Awaitable[BaseModel]]
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class RateLimitError(Exception):
|
|
400
|
+
def __init__(self, provider: str, model: str) -> None:
|
|
401
|
+
self.provider = provider
|
|
402
|
+
self.model = model
|
|
403
|
+
super().__init__(
|
|
404
|
+
"Rate limits exceeded. Please wait a moment before trying again."
|
|
405
|
+
)
|