travis234 2.3.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.
- travis/__init__.py +8 -0
- travis/agent/__init__.py +69 -0
- travis/agent/agent.py +443 -0
- travis/agent/agent_loop.py +870 -0
- travis/agent/async_utils.py +33 -0
- travis/agent/run_lease.py +62 -0
- travis/agent/tool_coordinator.py +55 -0
- travis/agent/types.py +290 -0
- travis/ai/__init__.py +85 -0
- travis/ai/auth/__init__.py +35 -0
- travis/ai/auth/context.py +24 -0
- travis/ai/auth/credential_store.py +62 -0
- travis/ai/auth/helpers.py +58 -0
- travis/ai/auth/resolve.py +205 -0
- travis/ai/auth/types.py +90 -0
- travis/ai/builtin_models.json +1 -0
- travis/ai/builtin_models.py +56 -0
- travis/ai/catalog_generation.py +76 -0
- travis/ai/context_estimate.py +226 -0
- travis/ai/env_config.py +295 -0
- travis/ai/event_stream.py +102 -0
- travis/ai/image_types.py +69 -0
- travis/ai/images.py +266 -0
- travis/ai/lazy_stream.py +48 -0
- travis/ai/model_cost.py +60 -0
- travis/ai/model_resolver.py +470 -0
- travis/ai/models.py +546 -0
- travis/ai/oauth.py +16 -0
- travis/ai/overflow.py +124 -0
- travis/ai/provider_metadata.py +150 -0
- travis/ai/providers/__init__.py +45 -0
- travis/ai/providers/_shared.py +33 -0
- travis/ai/providers/all.py +169 -0
- travis/ai/providers/anthropic_stream.py +290 -0
- travis/ai/providers/base.py +199 -0
- travis/ai/providers/bedrock_stream.py +200 -0
- travis/ai/providers/capabilities.py +243 -0
- travis/ai/providers/catalog.py +349 -0
- travis/ai/providers/chat_stream.py +407 -0
- travis/ai/providers/codex_auth.py +57 -0
- travis/ai/providers/codex_runtime.py +582 -0
- travis/ai/providers/copilot_headers.py +40 -0
- travis/ai/providers/faux.py +116 -0
- travis/ai/providers/github_copilot_oauth.py +450 -0
- travis/ai/providers/google_stream.py +207 -0
- travis/ai/providers/message_translation.py +433 -0
- travis/ai/providers/mistral_stream.py +229 -0
- travis/ai/providers/openai_compat.py +136 -0
- travis/ai/providers/params.py +321 -0
- travis/ai/providers/provider_auth.py +49 -0
- travis/ai/providers/provider_errors.py +66 -0
- travis/ai/providers/provider_request.py +236 -0
- travis/ai/providers/responses_stream.py +396 -0
- travis/ai/providers/responses_translation.py +325 -0
- travis/ai/providers/sse_common.py +57 -0
- travis/ai/providers/streaming_json.py +266 -0
- travis/ai/providers/subscription_oauth.py +469 -0
- travis/ai/providers/transports.py +2269 -0
- travis/ai/providers/travis_env.py +200 -0
- travis/ai/stream_proxy.py +72 -0
- travis/ai/types.py +307 -0
- travis/ai/validation.py +343 -0
- travis/app.py +891 -0
- travis/cli.py +1022 -0
- travis/coding_agent/__init__.py +302 -0
- travis/coding_agent/agent_harness.py +257 -0
- travis/coding_agent/agent_session.py +416 -0
- travis/coding_agent/agent_session_runtime.py +542 -0
- travis/coding_agent/agent_session_services.py +554 -0
- travis/coding_agent/artifacts.py +107 -0
- travis/coding_agent/auth_storage.py +309 -0
- travis/coding_agent/automation.py +169 -0
- travis/coding_agent/bash_executor.py +118 -0
- travis/coding_agent/branch_summarization.py +345 -0
- travis/coding_agent/capabilities.py +51 -0
- travis/coding_agent/compaction_adapter.py +536 -0
- travis/coding_agent/compaction_coordinator.py +761 -0
- travis/coding_agent/config.py +92 -0
- travis/coding_agent/deep_compaction_command.py +491 -0
- travis/coding_agent/eval_trace.py +122 -0
- travis/coding_agent/event_bus.py +81 -0
- travis/coding_agent/execution_backend.py +69 -0
- travis/coding_agent/experimental.py +9 -0
- travis/coding_agent/export_html.py +2890 -0
- travis/coding_agent/export_html_assets/vendor/highlight.min.js +1213 -0
- travis/coding_agent/export_html_assets/vendor/marked.min.js +6 -0
- travis/coding_agent/extension_cli.py +83 -0
- travis/coding_agent/extensions.py +1401 -0
- travis/coding_agent/input_expansion.py +169 -0
- travis/coding_agent/mailbox.py +115 -0
- travis/coding_agent/message_utils.py +43 -0
- travis/coding_agent/model_registry.py +817 -0
- travis/coding_agent/object_utils.py +32 -0
- travis/coding_agent/output_utils.py +9 -0
- travis/coding_agent/package_cli.py +192 -0
- travis/coding_agent/package_manager.py +655 -0
- travis/coding_agent/process_context.py +195 -0
- travis/coding_agent/processes/__init__.py +53 -0
- travis/coding_agent/processes/completions.py +693 -0
- travis/coding_agent/processes/containment.py +77 -0
- travis/coding_agent/processes/local.py +267 -0
- travis/coding_agent/processes/output.py +344 -0
- travis/coding_agent/processes/service.py +1087 -0
- travis/coding_agent/processes/transport.py +38 -0
- travis/coding_agent/processes/types.py +195 -0
- travis/coding_agent/project_trust.py +233 -0
- travis/coding_agent/prompt_templates.py +148 -0
- travis/coding_agent/resolve_config_value.py +181 -0
- travis/coding_agent/resource_discovery.py +118 -0
- travis/coding_agent/resource_loader.py +712 -0
- travis/coding_agent/rpc.py +258 -0
- travis/coding_agent/session_bash.py +182 -0
- travis/coding_agent/session_catalog.py +177 -0
- travis/coding_agent/session_commands.py +94 -0
- travis/coding_agent/session_events.py +280 -0
- travis/coding_agent/session_extensions.py +750 -0
- travis/coding_agent/session_index.py +397 -0
- travis/coding_agent/session_lock.py +42 -0
- travis/coding_agent/session_models.py +354 -0
- travis/coding_agent/session_persistence.py +557 -0
- travis/coding_agent/session_policy_controller.py +78 -0
- travis/coding_agent/session_store.py +967 -0
- travis/coding_agent/session_subagents.py +505 -0
- travis/coding_agent/session_tooling.py +309 -0
- travis/coding_agent/session_turns.py +710 -0
- travis/coding_agent/session_types.py +595 -0
- travis/coding_agent/settings_manager.py +639 -0
- travis/coding_agent/skills.py +242 -0
- travis/coding_agent/source_info.py +57 -0
- travis/coding_agent/sqlite_utils.py +21 -0
- travis/coding_agent/subagent_trace.py +662 -0
- travis/coding_agent/subagents.py +922 -0
- travis/coding_agent/subprocess_environment.py +52 -0
- travis/coding_agent/system_prompt.py +144 -0
- travis/coding_agent/themes.py +62 -0
- travis/coding_agent/tools/__init__.py +150 -0
- travis/coding_agent/tools/atomic_file.py +41 -0
- travis/coding_agent/tools/bash.py +672 -0
- travis/coding_agent/tools/common.py +31 -0
- travis/coding_agent/tools/edit.py +191 -0
- travis/coding_agent/tools/edit_diff.py +199 -0
- travis/coding_agent/tools/file_mutation_queue.py +39 -0
- travis/coding_agent/tools/find.py +175 -0
- travis/coding_agent/tools/grep.py +248 -0
- travis/coding_agent/tools/ls.py +139 -0
- travis/coding_agent/tools/output_spool.py +190 -0
- travis/coding_agent/tools/path_utils.py +188 -0
- travis/coding_agent/tools/process.py +534 -0
- travis/coding_agent/tools/read.py +442 -0
- travis/coding_agent/tools/truncate.py +188 -0
- travis/coding_agent/tools/types.py +78 -0
- travis/coding_agent/tools/write.py +127 -0
- travis/compaction/__init__.py +41 -0
- travis/compaction/compressor.py +1898 -0
- travis/compaction/policy.py +113 -0
- travis/compaction/strategy.py +101 -0
- travis/compaction/timing.py +895 -0
- travis/extensions/__init__.py +1 -0
- travis/resources/README.md +5 -0
- travis/resources/docs/README.md +3 -0
- travis/resources/docs/extensions.md +37 -0
- travis/resources/examples/README.md +3 -0
- travis/resources/extensions/hypa/__init__.py +301 -0
- travis/resources/extensions/hypa/hypa_tools.py +342 -0
- travis/runtime_facade.py +20 -0
- travis/tui/__init__.py +236 -0
- travis/tui/builtin_themes.py +362 -0
- travis/tui/component.py +2 -0
- travis/tui/components/__init__.py +35 -0
- travis/tui/components/autocomplete.py +481 -0
- travis/tui/components/base.py +253 -0
- travis/tui/components/editor.py +651 -0
- travis/tui/components/footer.py +286 -0
- travis/tui/components/image.py +108 -0
- travis/tui/components/loaders.py +141 -0
- travis/tui/components/markdown.py +269 -0
- travis/tui/components/multiline_editor.py +210 -0
- travis/tui/components/pickers.py +436 -0
- travis/tui/dispatcher.py +110 -0
- travis/tui/footer_data.py +342 -0
- travis/tui/fuzzy.py +106 -0
- travis/tui/interactive.py +759 -0
- travis/tui/interactive_command_dispatcher.py +378 -0
- travis/tui/interactive_custom_dialog.py +75 -0
- travis/tui/interactive_extensions.py +565 -0
- travis/tui/interactive_mode.py +272 -0
- travis/tui/interactive_model_auth.py +503 -0
- travis/tui/interactive_process_commands.py +407 -0
- travis/tui/interactive_session_commands.py +543 -0
- travis/tui/interactive_shutdown.py +146 -0
- travis/tui/interactive_turn_controller.py +270 -0
- travis/tui/interactive_view.py +742 -0
- travis/tui/keybindings.py +157 -0
- travis/tui/keys.py +300 -0
- travis/tui/kill_ring.py +34 -0
- travis/tui/native_modifiers.py +10 -0
- travis/tui/stdin_buffer.py +257 -0
- travis/tui/terminal.py +433 -0
- travis/tui/terminal_colors.py +57 -0
- travis/tui/terminal_image.py +416 -0
- travis/tui/theme.py +268 -0
- travis/tui/theme_controller.py +184 -0
- travis/tui/tui.py +944 -0
- travis/tui/undo_stack.py +27 -0
- travis/tui/user_commands.py +353 -0
- travis/tui/utils.py +479 -0
- travis/tui/word_navigation.py +148 -0
- travis234-2.3.1.dist-info/METADATA +442 -0
- travis234-2.3.1.dist-info/RECORD +214 -0
- travis234-2.3.1.dist-info/WHEEL +5 -0
- travis234-2.3.1.dist-info/entry_points.txt +2 -0
- travis234-2.3.1.dist-info/licenses/LICENSE +23 -0
- travis234-2.3.1.dist-info/licenses/NOTICE.md +13 -0
- travis234-2.3.1.dist-info/top_level.txt +1 -0
travis/__init__.py
ADDED
travis/agent/__init__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Core stateful-agent and bounded-loop interfaces."""
|
|
2
|
+
|
|
3
|
+
from travis.agent.agent import Agent, AgentState
|
|
4
|
+
from travis.agent.agent_loop import (
|
|
5
|
+
AgentEventSink,
|
|
6
|
+
AgentEventStream,
|
|
7
|
+
agent_loop,
|
|
8
|
+
agent_loop_continue,
|
|
9
|
+
run_agent_loop,
|
|
10
|
+
run_agent_loop_async,
|
|
11
|
+
run_agent_loop_continue,
|
|
12
|
+
run_agent_loop_continue_async,
|
|
13
|
+
)
|
|
14
|
+
from travis.agent.async_utils import MaybeAwaitable, resolve, run_sync
|
|
15
|
+
from travis.agent.run_lease import RunLease, RunLeaseToken
|
|
16
|
+
from travis.agent.tool_coordinator import ToolCoordinator
|
|
17
|
+
from travis.agent.types import (
|
|
18
|
+
AbortSignal,
|
|
19
|
+
AfterToolCallContext,
|
|
20
|
+
AfterToolCallResult,
|
|
21
|
+
AgentContext,
|
|
22
|
+
AgentEvent,
|
|
23
|
+
AgentLoopConfig,
|
|
24
|
+
AgentLoopTurnUpdate,
|
|
25
|
+
AgentMessage,
|
|
26
|
+
AgentTool,
|
|
27
|
+
AgentToolResult,
|
|
28
|
+
BeforeToolCallContext,
|
|
29
|
+
BeforeToolCallResult,
|
|
30
|
+
ImmediateToolOutcome,
|
|
31
|
+
PreparedToolCall,
|
|
32
|
+
PrepareNextTurnContext,
|
|
33
|
+
ShouldStopAfterTurnContext,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"AbortSignal",
|
|
38
|
+
"AfterToolCallContext",
|
|
39
|
+
"AfterToolCallResult",
|
|
40
|
+
"Agent",
|
|
41
|
+
"AgentContext",
|
|
42
|
+
"AgentEvent",
|
|
43
|
+
"AgentEventSink",
|
|
44
|
+
"AgentEventStream",
|
|
45
|
+
"AgentLoopConfig",
|
|
46
|
+
"AgentLoopTurnUpdate",
|
|
47
|
+
"AgentMessage",
|
|
48
|
+
"AgentState",
|
|
49
|
+
"AgentTool",
|
|
50
|
+
"AgentToolResult",
|
|
51
|
+
"BeforeToolCallContext",
|
|
52
|
+
"BeforeToolCallResult",
|
|
53
|
+
"ImmediateToolOutcome",
|
|
54
|
+
"MaybeAwaitable",
|
|
55
|
+
"PreparedToolCall",
|
|
56
|
+
"RunLease",
|
|
57
|
+
"RunLeaseToken",
|
|
58
|
+
"ToolCoordinator",
|
|
59
|
+
"PrepareNextTurnContext",
|
|
60
|
+
"ShouldStopAfterTurnContext",
|
|
61
|
+
"agent_loop",
|
|
62
|
+
"agent_loop_continue",
|
|
63
|
+
"run_agent_loop",
|
|
64
|
+
"run_agent_loop_async",
|
|
65
|
+
"run_agent_loop_continue",
|
|
66
|
+
"run_agent_loop_continue_async",
|
|
67
|
+
"resolve",
|
|
68
|
+
"run_sync",
|
|
69
|
+
]
|
travis/agent/agent.py
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"""Stateful Agent wrapper."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import threading
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, Callable, Optional, Union
|
|
9
|
+
|
|
10
|
+
from travis.ai.types import AssistantMessage, ImageContent, Message, Model, TextContent, UserMessage, empty_usage, now_ms
|
|
11
|
+
from travis.agent.agent_loop import (
|
|
12
|
+
AgentEventSink,
|
|
13
|
+
run_agent_loop_async,
|
|
14
|
+
run_agent_loop_continue_async,
|
|
15
|
+
)
|
|
16
|
+
from travis.agent.async_utils import resolve, run_sync
|
|
17
|
+
from travis.agent.run_lease import RunLease, RunLeaseToken
|
|
18
|
+
from travis.agent.types import (
|
|
19
|
+
AbortSignal,
|
|
20
|
+
AgentEndEvent,
|
|
21
|
+
AgentContext,
|
|
22
|
+
AgentEvent,
|
|
23
|
+
AgentLoopConfig,
|
|
24
|
+
AgentMessage,
|
|
25
|
+
AgentTool,
|
|
26
|
+
MessageEndEvent,
|
|
27
|
+
MessageStartEvent,
|
|
28
|
+
QueueMode,
|
|
29
|
+
ThinkingLevel,
|
|
30
|
+
TurnEndEvent,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Listener = Callable[..., None]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PendingMessageQueue:
|
|
37
|
+
def __init__(self, mode: QueueMode = "one-at-a-time") -> None:
|
|
38
|
+
self.messages: list[AgentMessage] = []
|
|
39
|
+
self.mode = mode
|
|
40
|
+
|
|
41
|
+
def enqueue(self, message: AgentMessage) -> None:
|
|
42
|
+
self.messages.append(message)
|
|
43
|
+
|
|
44
|
+
def has_items(self) -> bool:
|
|
45
|
+
return bool(self.messages)
|
|
46
|
+
|
|
47
|
+
def drain(self) -> list[AgentMessage]:
|
|
48
|
+
if self.mode == "all":
|
|
49
|
+
drained = list(self.messages)
|
|
50
|
+
self.messages = []
|
|
51
|
+
return drained
|
|
52
|
+
if not self.messages:
|
|
53
|
+
return []
|
|
54
|
+
first = self.messages[0]
|
|
55
|
+
self.messages = self.messages[1:]
|
|
56
|
+
return [first]
|
|
57
|
+
|
|
58
|
+
def clear(self) -> None:
|
|
59
|
+
self.messages = []
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class AgentState:
|
|
64
|
+
system_prompt: str
|
|
65
|
+
model: Model
|
|
66
|
+
thinking_level: ThinkingLevel = "off"
|
|
67
|
+
tools: list[AgentTool] = field(default_factory=list)
|
|
68
|
+
messages: list[AgentMessage] = field(default_factory=list)
|
|
69
|
+
is_streaming: bool = False
|
|
70
|
+
streaming_message: AgentMessage | None = None
|
|
71
|
+
pending_tool_calls: set[str] = field(default_factory=set)
|
|
72
|
+
error_message: str | None = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Agent:
|
|
76
|
+
"""Owns conversation state and drives the functional agent loop."""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
*,
|
|
81
|
+
system_prompt: str,
|
|
82
|
+
model: Model,
|
|
83
|
+
convert_to_llm: Callable[[list[AgentMessage]], list[Message]],
|
|
84
|
+
tools: Optional[list[AgentTool]] = None,
|
|
85
|
+
thinking_level: ThinkingLevel = "off",
|
|
86
|
+
tool_execution: str = "parallel",
|
|
87
|
+
max_parallel_tools: int = 8,
|
|
88
|
+
before_tool_call=None,
|
|
89
|
+
after_tool_call=None,
|
|
90
|
+
should_stop_after_turn=None,
|
|
91
|
+
prepare_next_turn=None,
|
|
92
|
+
prepare_next_turn_with_context=None,
|
|
93
|
+
transform_context=None,
|
|
94
|
+
steering_mode: QueueMode = "one-at-a-time",
|
|
95
|
+
follow_up_mode: QueueMode = "one-at-a-time",
|
|
96
|
+
session_id: str | None = None,
|
|
97
|
+
thinking_budgets: dict[str, int] | None = None,
|
|
98
|
+
transport: str = "auto",
|
|
99
|
+
max_retry_delay_ms: int | None = None,
|
|
100
|
+
on_payload=None,
|
|
101
|
+
on_headers=None,
|
|
102
|
+
on_response=None,
|
|
103
|
+
stream_fn=None,
|
|
104
|
+
) -> None:
|
|
105
|
+
self._state = AgentState(
|
|
106
|
+
system_prompt=system_prompt,
|
|
107
|
+
model=model,
|
|
108
|
+
thinking_level=thinking_level,
|
|
109
|
+
tools=list(tools or []),
|
|
110
|
+
)
|
|
111
|
+
self._convert_to_llm = convert_to_llm
|
|
112
|
+
self._tool_execution = tool_execution
|
|
113
|
+
self._max_parallel_tools = max(1, int(max_parallel_tools))
|
|
114
|
+
self._before_tool_call = before_tool_call
|
|
115
|
+
self._after_tool_call = after_tool_call
|
|
116
|
+
self._should_stop_after_turn = should_stop_after_turn
|
|
117
|
+
self._prepare_next_turn = prepare_next_turn
|
|
118
|
+
self._prepare_next_turn_with_context = prepare_next_turn_with_context
|
|
119
|
+
self._transform_context = transform_context
|
|
120
|
+
self.session_id = session_id
|
|
121
|
+
self.thinking_budgets = thinking_budgets
|
|
122
|
+
self.transport = transport
|
|
123
|
+
self.max_retry_delay_ms = max_retry_delay_ms
|
|
124
|
+
self.on_payload = on_payload
|
|
125
|
+
self.on_headers = on_headers
|
|
126
|
+
self.on_response = on_response
|
|
127
|
+
self._stream_fn = stream_fn
|
|
128
|
+
self._listeners: list[Listener] = []
|
|
129
|
+
self._signal = AbortSignal()
|
|
130
|
+
self._run_state_lock = threading.Lock()
|
|
131
|
+
self._run_lease = RunLease()
|
|
132
|
+
self._active_run_token: RunLeaseToken | None = None
|
|
133
|
+
self._idle_event = threading.Event()
|
|
134
|
+
self._idle_event.set()
|
|
135
|
+
self._steering = PendingMessageQueue(steering_mode)
|
|
136
|
+
self._follow_up = PendingMessageQueue(follow_up_mode)
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def state(self) -> AgentState:
|
|
140
|
+
return self._state
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def signal(self) -> AbortSignal:
|
|
144
|
+
return self._signal
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def run_lease(self) -> RunLease:
|
|
148
|
+
return self._run_lease
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def steering_mode(self) -> QueueMode:
|
|
152
|
+
return self._steering.mode
|
|
153
|
+
|
|
154
|
+
@steering_mode.setter
|
|
155
|
+
def steering_mode(self, mode: QueueMode) -> None:
|
|
156
|
+
self._steering.mode = mode
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def follow_up_mode(self) -> QueueMode:
|
|
160
|
+
return self._follow_up.mode
|
|
161
|
+
|
|
162
|
+
@follow_up_mode.setter
|
|
163
|
+
def follow_up_mode(self, mode: QueueMode) -> None:
|
|
164
|
+
self._follow_up.mode = mode
|
|
165
|
+
|
|
166
|
+
def subscribe(self, listener: Listener) -> Callable[[], None]:
|
|
167
|
+
self._listeners.append(listener)
|
|
168
|
+
|
|
169
|
+
def _unsubscribe() -> None:
|
|
170
|
+
if listener in self._listeners:
|
|
171
|
+
self._listeners.remove(listener)
|
|
172
|
+
|
|
173
|
+
return _unsubscribe
|
|
174
|
+
|
|
175
|
+
def steer(self, message: AgentMessage) -> None:
|
|
176
|
+
self._steering.enqueue(message)
|
|
177
|
+
|
|
178
|
+
def follow_up(self, message: AgentMessage) -> None:
|
|
179
|
+
self._follow_up.enqueue(message)
|
|
180
|
+
|
|
181
|
+
def clear_steering_queue(self) -> None:
|
|
182
|
+
self._steering.clear()
|
|
183
|
+
|
|
184
|
+
def clear_follow_up_queue(self) -> None:
|
|
185
|
+
self._follow_up.clear()
|
|
186
|
+
|
|
187
|
+
def clear_all_queues(self) -> None:
|
|
188
|
+
self.clear_steering_queue()
|
|
189
|
+
self.clear_follow_up_queue()
|
|
190
|
+
|
|
191
|
+
def has_queued_messages(self) -> bool:
|
|
192
|
+
return self._steering.has_items() or self._follow_up.has_items()
|
|
193
|
+
|
|
194
|
+
def abort(self) -> None:
|
|
195
|
+
self._signal.abort()
|
|
196
|
+
|
|
197
|
+
def reset_abort_signal(self) -> AbortSignal:
|
|
198
|
+
if self._state.is_streaming:
|
|
199
|
+
return self._signal
|
|
200
|
+
if self._signal.aborted:
|
|
201
|
+
self._signal = AbortSignal()
|
|
202
|
+
return self._signal
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def wait_for_idle(self, timeout: float | None = None) -> bool:
|
|
206
|
+
return self._idle_event.wait(timeout)
|
|
207
|
+
|
|
208
|
+
def reset(self) -> None:
|
|
209
|
+
if self._run_lease.active:
|
|
210
|
+
raise RuntimeError("Cannot reset Agent while an active run is in progress; abort and wait for idle first")
|
|
211
|
+
self._state.messages = []
|
|
212
|
+
self._state.is_streaming = False
|
|
213
|
+
self._state.error_message = None
|
|
214
|
+
self._state.streaming_message = None
|
|
215
|
+
self._state.pending_tool_calls = set()
|
|
216
|
+
self.clear_all_queues()
|
|
217
|
+
|
|
218
|
+
def _build_config(self, *, skip_initial_steering_poll: bool = False) -> AgentLoopConfig:
|
|
219
|
+
skip_steering_poll = {"value": skip_initial_steering_poll}
|
|
220
|
+
|
|
221
|
+
def get_steering_messages() -> list[AgentMessage]:
|
|
222
|
+
if skip_steering_poll["value"]:
|
|
223
|
+
skip_steering_poll["value"] = False
|
|
224
|
+
return []
|
|
225
|
+
return self._drain_steering()
|
|
226
|
+
|
|
227
|
+
def prepare_next_turn_adapter(context):
|
|
228
|
+
if self._prepare_next_turn_with_context:
|
|
229
|
+
return self._prepare_next_turn_with_context(context, self._signal)
|
|
230
|
+
if self._prepare_next_turn:
|
|
231
|
+
return self._prepare_next_turn(self._signal)
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
return AgentLoopConfig(
|
|
235
|
+
model=self._state.model,
|
|
236
|
+
convert_to_llm=self._convert_to_llm,
|
|
237
|
+
get_steering_messages=get_steering_messages,
|
|
238
|
+
get_follow_up_messages=self._drain_follow_up,
|
|
239
|
+
prepare_next_turn=prepare_next_turn_adapter
|
|
240
|
+
if self._prepare_next_turn_with_context or self._prepare_next_turn
|
|
241
|
+
else None,
|
|
242
|
+
tool_execution=self._tool_execution,
|
|
243
|
+
max_parallel_tools=self._max_parallel_tools,
|
|
244
|
+
before_tool_call=self._before_tool_call,
|
|
245
|
+
after_tool_call=self._after_tool_call,
|
|
246
|
+
should_stop_after_turn=self._should_stop_after_turn,
|
|
247
|
+
transform_context=self._transform_context,
|
|
248
|
+
reasoning=None if self._state.thinking_level == "off" else self._state.thinking_level,
|
|
249
|
+
session_id=self.session_id,
|
|
250
|
+
transport=self.transport,
|
|
251
|
+
thinking_budgets=self.thinking_budgets,
|
|
252
|
+
max_retry_delay_ms=self.max_retry_delay_ms,
|
|
253
|
+
on_payload=self.on_payload,
|
|
254
|
+
on_headers=self.on_headers,
|
|
255
|
+
on_response=self.on_response,
|
|
256
|
+
max_tokens=self._state.model.max_tokens or None,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def _drain_steering(self) -> list[AgentMessage]:
|
|
260
|
+
return self._steering.drain()
|
|
261
|
+
|
|
262
|
+
def _drain_follow_up(self) -> list[AgentMessage]:
|
|
263
|
+
return self._follow_up.drain()
|
|
264
|
+
|
|
265
|
+
def _context(self) -> AgentContext:
|
|
266
|
+
return AgentContext(
|
|
267
|
+
system_prompt=self._state.system_prompt,
|
|
268
|
+
messages=list(self._state.messages),
|
|
269
|
+
tools=list(self._state.tools),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def prompt(
|
|
273
|
+
self,
|
|
274
|
+
prompt: Union[str, AgentMessage, list[AgentMessage]],
|
|
275
|
+
stream_fn=None,
|
|
276
|
+
images: list[ImageContent] | None = None,
|
|
277
|
+
) -> list[AgentMessage]:
|
|
278
|
+
return run_sync(self.async_prompt(prompt, stream_fn=stream_fn, images=images))
|
|
279
|
+
|
|
280
|
+
async def async_prompt(
|
|
281
|
+
self,
|
|
282
|
+
prompt: Union[str, AgentMessage, list[AgentMessage]],
|
|
283
|
+
stream_fn=None,
|
|
284
|
+
images: list[ImageContent] | None = None,
|
|
285
|
+
) -> list[AgentMessage]:
|
|
286
|
+
self._begin_run(
|
|
287
|
+
"Agent is already processing a prompt. Use steer() or follow_up() to queue messages, or wait for completion."
|
|
288
|
+
)
|
|
289
|
+
if isinstance(prompt, str):
|
|
290
|
+
content = [TextContent(text=prompt)]
|
|
291
|
+
if images:
|
|
292
|
+
content.extend(images)
|
|
293
|
+
messages: list[AgentMessage] = [UserMessage(content=content, timestamp=now_ms())]
|
|
294
|
+
elif isinstance(prompt, list):
|
|
295
|
+
messages = list(prompt)
|
|
296
|
+
else:
|
|
297
|
+
messages = [prompt]
|
|
298
|
+
try:
|
|
299
|
+
new_messages = await run_agent_loop_async(
|
|
300
|
+
messages,
|
|
301
|
+
self._context(),
|
|
302
|
+
self._build_config(),
|
|
303
|
+
self._make_sink(),
|
|
304
|
+
self._signal,
|
|
305
|
+
stream_fn or self._stream_fn,
|
|
306
|
+
)
|
|
307
|
+
except Exception as error: # noqa: BLE001
|
|
308
|
+
new_messages = await self._handle_run_failure(error, self._signal.aborted)
|
|
309
|
+
finally:
|
|
310
|
+
self._finish_run()
|
|
311
|
+
return new_messages
|
|
312
|
+
|
|
313
|
+
def continue_(self, stream_fn=None) -> list[AgentMessage]:
|
|
314
|
+
return run_sync(self.async_continue(stream_fn=stream_fn))
|
|
315
|
+
|
|
316
|
+
async def async_continue(self, stream_fn=None) -> list[AgentMessage]:
|
|
317
|
+
self._begin_run("Agent is already processing. Wait for completion before continuing.")
|
|
318
|
+
try:
|
|
319
|
+
context = self._context()
|
|
320
|
+
last_message = context.messages[-1] if context.messages else None
|
|
321
|
+
if last_message is None:
|
|
322
|
+
raise ValueError("No messages to continue from")
|
|
323
|
+
if getattr(last_message, "role", None) == "assistant":
|
|
324
|
+
queued_steering = self._drain_steering()
|
|
325
|
+
if queued_steering:
|
|
326
|
+
run_messages = queued_steering
|
|
327
|
+
run_config = self._build_config(skip_initial_steering_poll=True)
|
|
328
|
+
continue_existing = False
|
|
329
|
+
else:
|
|
330
|
+
queued_follow_up = self._drain_follow_up()
|
|
331
|
+
if not queued_follow_up:
|
|
332
|
+
raise ValueError("Cannot continue from message role: assistant")
|
|
333
|
+
run_messages = queued_follow_up
|
|
334
|
+
run_config = self._build_config()
|
|
335
|
+
continue_existing = False
|
|
336
|
+
else:
|
|
337
|
+
run_messages = []
|
|
338
|
+
run_config = self._build_config()
|
|
339
|
+
continue_existing = True
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
if continue_existing:
|
|
343
|
+
return await run_agent_loop_continue_async(
|
|
344
|
+
context,
|
|
345
|
+
run_config,
|
|
346
|
+
self._make_sink(),
|
|
347
|
+
self._signal,
|
|
348
|
+
stream_fn or self._stream_fn,
|
|
349
|
+
)
|
|
350
|
+
return await run_agent_loop_async(
|
|
351
|
+
run_messages,
|
|
352
|
+
context,
|
|
353
|
+
run_config,
|
|
354
|
+
self._make_sink(),
|
|
355
|
+
self._signal,
|
|
356
|
+
stream_fn or self._stream_fn,
|
|
357
|
+
)
|
|
358
|
+
except Exception as error: # noqa: BLE001
|
|
359
|
+
return await self._handle_run_failure(error, self._signal.aborted)
|
|
360
|
+
finally:
|
|
361
|
+
self._finish_run()
|
|
362
|
+
|
|
363
|
+
async def _handle_run_failure(self, error: BaseException, aborted: bool) -> list[AgentMessage]:
|
|
364
|
+
failure_message = AssistantMessage(
|
|
365
|
+
content=[TextContent(text="")],
|
|
366
|
+
api=self._state.model.api,
|
|
367
|
+
provider=self._state.model.provider,
|
|
368
|
+
model=self._state.model.id,
|
|
369
|
+
usage=empty_usage(),
|
|
370
|
+
stop_reason="aborted" if aborted else "error",
|
|
371
|
+
error_message=str(error),
|
|
372
|
+
timestamp=now_ms(),
|
|
373
|
+
)
|
|
374
|
+
sink = self._make_sink()
|
|
375
|
+
await sink(MessageStartEvent(message=failure_message))
|
|
376
|
+
await sink(MessageEndEvent(message=failure_message))
|
|
377
|
+
await sink(TurnEndEvent(message=failure_message, tool_results=[]))
|
|
378
|
+
await sink(AgentEndEvent(messages=[failure_message]))
|
|
379
|
+
return [failure_message]
|
|
380
|
+
|
|
381
|
+
def _begin_run(self, active_error: str) -> None:
|
|
382
|
+
with self._run_state_lock:
|
|
383
|
+
token = self._run_lease.acquire(active_error)
|
|
384
|
+
self._active_run_token = token
|
|
385
|
+
self._signal = AbortSignal()
|
|
386
|
+
self._idle_event.clear()
|
|
387
|
+
self._state.is_streaming = True
|
|
388
|
+
self._state.streaming_message = None
|
|
389
|
+
self._state.error_message = None
|
|
390
|
+
|
|
391
|
+
def _finish_run(self) -> None:
|
|
392
|
+
with self._run_state_lock:
|
|
393
|
+
self._state.is_streaming = False
|
|
394
|
+
self._state.streaming_message = None
|
|
395
|
+
self._state.pending_tool_calls = set()
|
|
396
|
+
self._idle_event.set()
|
|
397
|
+
token = self._active_run_token
|
|
398
|
+
self._active_run_token = None
|
|
399
|
+
if token is not None:
|
|
400
|
+
token.release()
|
|
401
|
+
|
|
402
|
+
def _make_sink(self) -> AgentEventSink:
|
|
403
|
+
async def _sink(event: AgentEvent) -> None:
|
|
404
|
+
self._process_event(event)
|
|
405
|
+
for listener in list(self._listeners):
|
|
406
|
+
if _listener_accepts_signal(listener):
|
|
407
|
+
await resolve(listener(event, self._signal))
|
|
408
|
+
else:
|
|
409
|
+
await resolve(listener(event))
|
|
410
|
+
|
|
411
|
+
return _sink
|
|
412
|
+
|
|
413
|
+
def _process_event(self, event: AgentEvent) -> None:
|
|
414
|
+
etype = event.type
|
|
415
|
+
if etype == "message_start":
|
|
416
|
+
if getattr(event.message, "role", None) == "assistant":
|
|
417
|
+
self._state.streaming_message = event.message
|
|
418
|
+
elif etype == "message_update":
|
|
419
|
+
self._state.streaming_message = event.message
|
|
420
|
+
elif etype == "message_end":
|
|
421
|
+
self._state.messages.append(event.message)
|
|
422
|
+
if getattr(event.message, "role", None) == "assistant":
|
|
423
|
+
self._state.streaming_message = None
|
|
424
|
+
if getattr(event.message, "stop_reason", None) in ("error", "aborted"):
|
|
425
|
+
self._state.error_message = getattr(event.message, "error_message", None)
|
|
426
|
+
elif etype == "tool_execution_start":
|
|
427
|
+
self._state.pending_tool_calls.add(event.tool_call_id)
|
|
428
|
+
elif etype == "tool_execution_end":
|
|
429
|
+
self._state.pending_tool_calls.discard(event.tool_call_id)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _listener_accepts_signal(listener: Listener) -> bool:
|
|
433
|
+
try:
|
|
434
|
+
signature = inspect.signature(listener)
|
|
435
|
+
except (TypeError, ValueError):
|
|
436
|
+
return False
|
|
437
|
+
positional_count = 0
|
|
438
|
+
for parameter in signature.parameters.values():
|
|
439
|
+
if parameter.kind == inspect.Parameter.VAR_POSITIONAL:
|
|
440
|
+
return True
|
|
441
|
+
if parameter.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD):
|
|
442
|
+
positional_count += 1
|
|
443
|
+
return positional_count >= 2
|