stirrup 0.1.2__py3-none-any.whl → 0.1.4__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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
+ from pydantic import ValidationError
6
+
5
7
  try:
6
8
  from e2b import InvalidArgumentException, TimeoutException
7
9
  from e2b.sandbox.filesystem.filesystem import FileType
@@ -13,7 +15,7 @@ except ImportError as e:
13
15
 
14
16
  import logging
15
17
 
16
- from stirrup.constants import SUBMISSION_SANDBOX_TIMEOUT
18
+ from stirrup.constants import SANDBOX_REQUEST_TIMEOUT, SANDBOX_TIMEOUT
17
19
  from stirrup.core.models import ImageContentBlock, Tool, ToolUseCountMetadata
18
20
 
19
21
  from .base import (
@@ -51,7 +53,8 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
51
53
  def __init__(
52
54
  self,
53
55
  *,
54
- timeout: int = SUBMISSION_SANDBOX_TIMEOUT,
56
+ timeout: int = SANDBOX_TIMEOUT,
57
+ request_timeout: int = SANDBOX_REQUEST_TIMEOUT,
55
58
  template: str | None = None,
56
59
  allowed_commands: list[str] | None = None,
57
60
  ) -> None:
@@ -67,6 +70,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
67
70
  """
68
71
  super().__init__(allowed_commands=allowed_commands)
69
72
  self._timeout = timeout
73
+ self._request_timeout = request_timeout
70
74
  self._template = template
71
75
  self._sbx: AsyncSandbox | None = None
72
76
 
@@ -109,7 +113,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
109
113
  if not await self._sbx.files.exists(path):
110
114
  raise FileNotFoundError(f"File not found: {path}")
111
115
 
112
- file_bytes = await self._sbx.files.read(path, format="bytes")
116
+ file_bytes = await self._sbx.files.read(path, format="bytes", request_timeout=self._request_timeout)
113
117
  return bytes(file_bytes)
114
118
 
115
119
  async def write_file_bytes(self, path: str, content: bytes) -> None:
@@ -126,7 +130,25 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
126
130
  if self._sbx is None:
127
131
  raise RuntimeError("ExecutionEnvironment not started.")
128
132
 
129
- await self._sbx.files.write(path, content)
133
+ await self._sbx.files.write(path, content, request_timeout=self._request_timeout)
134
+
135
+ async def file_exists(self, path: str) -> bool:
136
+ """Check if a file exists in the E2B sandbox.
137
+
138
+ Args:
139
+ path: File path within the sandbox.
140
+
141
+ Returns:
142
+ True if the file exists, False otherwise.
143
+
144
+ Raises:
145
+ RuntimeError: If environment not started.
146
+
147
+ """
148
+ if self._sbx is None:
149
+ raise RuntimeError("ExecutionEnvironment not started.")
150
+
151
+ return await self._sbx.files.exists(path)
130
152
 
131
153
  async def run_command(self, cmd: str, *, timeout: int = SHELL_TIMEOUT) -> CommandResult:
132
154
  """Execute command in E2B execution environment, returning raw CommandResult."""
@@ -146,7 +168,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
146
168
  )
147
169
 
148
170
  try:
149
- r = await self._sbx.commands.run(cmd, timeout=timeout)
171
+ r = await self._sbx.commands.run(cmd, timeout=timeout, request_timeout=self._request_timeout)
150
172
 
151
173
  return CommandResult(
152
174
  exit_code=getattr(r, "exit_code", 0),
@@ -231,7 +253,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
231
253
  continue
232
254
 
233
255
  # Read file content from execution environment
234
- file_bytes = await self._sbx.files.read(env_path, format="bytes")
256
+ file_bytes = await self._sbx.files.read(env_path, format="bytes", request_timeout=self._request_timeout)
235
257
  content = bytes(file_bytes)
236
258
 
237
259
  # Save with original filename directly in output_dir
@@ -297,6 +319,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
297
319
  result = UploadFilesResult()
298
320
 
299
321
  for source in paths:
322
+ original_name = Path(source).name # Get name BEFORE resolve
300
323
  source = Path(source).resolve()
301
324
 
302
325
  if not source.exists():
@@ -306,9 +329,10 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
306
329
 
307
330
  try:
308
331
  if source.is_file():
309
- dest = f"{dest_base}/{source.name}"
332
+ source = Path(source).resolve()
333
+ dest = f"{dest_base}/{original_name}"
310
334
  content = source.read_bytes()
311
- await self._sbx.files.write(dest, content)
335
+ await self._sbx.files.write(dest, content, request_timeout=self._request_timeout)
312
336
  result.uploaded.append(
313
337
  UploadedFile(
314
338
  source_path=source,
@@ -327,7 +351,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
327
351
  relative = file_path.relative_to(source)
328
352
  dest = f"{dest_base}/{relative}" if dest_dir else f"{dest_base}/{source.name}/{relative}"
329
353
  content = file_path.read_bytes()
330
- await self._sbx.files.write(dest, content)
354
+ await self._sbx.files.write(dest, content, request_timeout=self._request_timeout)
331
355
  result.uploaded.append(
332
356
  UploadedFile(
333
357
  source_path=file_path,
@@ -360,5 +384,13 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
360
384
  FileNotFoundError: If file does not exist.
361
385
 
362
386
  """
363
- file_bytes = await self.read_file_bytes(path)
364
- return ImageContentBlock(data=file_bytes)
387
+ if not path.lower().endswith((".png", ".jpg", ".jpeg")):
388
+ raise ValueError(f"Unsupported image type for `{path}`. Only .png, .jpg, or .jpeg are allowed.")
389
+
390
+ try:
391
+ file_bytes = await self.read_file_bytes(path)
392
+ image = ImageContentBlock(data=file_bytes)
393
+ except ValidationError as e:
394
+ raise ValueError("You submitted a corrupt/unsupported image file.") from e
395
+
396
+ return image
@@ -122,7 +122,7 @@ class LocalCodeExecToolProvider(CodeExecToolProvider):
122
122
  if self._temp_base_dir:
123
123
  self._temp_base_dir.mkdir(parents=True, exist_ok=True)
124
124
  self._temp_dir = Path(tempfile.mkdtemp(prefix="local_exec_env_", dir=self._temp_base_dir))
125
- logger.info("Created local execution environment temp directory: %s", self._temp_dir)
125
+ logger.debug("Created local execution environment temp directory: %s", self._temp_dir)
126
126
  return self.get_code_exec_tool(description=self._description)
127
127
 
128
128
  async def __aexit__(
@@ -205,6 +205,23 @@ class LocalCodeExecToolProvider(CodeExecToolProvider):
205
205
  resolved.parent.mkdir(parents=True, exist_ok=True)
206
206
  resolved.write_bytes(content)
207
207
 
208
+ async def file_exists(self, path: str) -> bool:
209
+ """Check if a file exists in the temp directory.
210
+
211
+ Args:
212
+ path: File path (relative or absolute within the temp dir).
213
+
214
+ Returns:
215
+ True if the file exists, False otherwise.
216
+
217
+ Raises:
218
+ RuntimeError: If environment not started.
219
+ ValueError: If path is outside temp directory.
220
+
221
+ """
222
+ resolved = self._resolve_and_validate_path(path)
223
+ return resolved.exists() and resolved.is_file()
224
+
208
225
  async def run_command(self, cmd: str, *, timeout: int = SHELL_TIMEOUT) -> CommandResult:
209
226
  """Execute command in the temp directory.
210
227
 
@@ -359,7 +376,7 @@ class LocalCodeExecToolProvider(CodeExecToolProvider):
359
376
 
360
377
  # Move file (overwrites if exists)
361
378
  shutil.move(str(source_path), str(dest_path))
362
- logger.info("Moved file: %s -> %s", source_path, dest_path)
379
+ logger.debug("Moved file: %s -> %s", source_path, dest_path)
363
380
 
364
381
  result.saved.append(
365
382
  SavedFile(
stirrup/tools/finish.py CHANGED
@@ -1,3 +1,7 @@
1
+ """Simple finish tool with file existence validation."""
2
+
3
+ from __future__ import annotations
4
+
1
5
  from typing import Annotated
2
6
 
3
7
  from pydantic import BaseModel, Field
@@ -15,9 +19,31 @@ class FinishParams(BaseModel):
15
19
  ]
16
20
 
17
21
 
22
+ async def _validating_finish_executor(params: FinishParams) -> ToolResult[ToolUseCountMetadata]:
23
+ """Validates all reported files exist before completing."""
24
+ from stirrup.core.agent import _SESSION_STATE
25
+
26
+ try:
27
+ state = _SESSION_STATE.get(None)
28
+ exec_env = state.exec_env if state else None
29
+ except LookupError:
30
+ exec_env = None
31
+
32
+ if exec_env and params.paths:
33
+ missing = [p for p in params.paths if not await exec_env.file_exists(p)]
34
+ if missing:
35
+ return ToolResult(
36
+ content=f"ERROR: Files do not exist: {missing}. Verify paths and ensure files were saved.",
37
+ metadata=ToolUseCountMetadata(),
38
+ success=False,
39
+ )
40
+
41
+ return ToolResult(content=params.reason, metadata=ToolUseCountMetadata(), success=True)
42
+
43
+
18
44
  SIMPLE_FINISH_TOOL: Tool[FinishParams, ToolUseCountMetadata] = Tool[FinishParams, ToolUseCountMetadata](
19
45
  name=FINISH_TOOL_NAME,
20
46
  description="Signal task completion with a reason. Use when the task is finished or cannot proceed further. Note that you will need a separate turn to finish.",
21
47
  parameters=FinishParams,
22
- executor=lambda params: ToolResult(content=params.reason, metadata=ToolUseCountMetadata()),
48
+ executor=_validating_finish_executor,
23
49
  )
@@ -0,0 +1,130 @@
1
+ """User input tool for interactive clarification during agent execution.
2
+
3
+ This module provides the user_input tool that allows agents to ask questions
4
+ and receive text responses from users during task execution.
5
+
6
+ Example usage:
7
+ from stirrup.clients.chat_completions_client import ChatCompletionsClient
8
+ from stirrup.tools import DEFAULT_TOOLS, USER_INPUT_TOOL
9
+
10
+ client = ChatCompletionsClient(model="gpt-5")
11
+ agent = Agent(
12
+ client=client,
13
+ name="assistant",
14
+ tools=[*DEFAULT_TOOLS, USER_INPUT_TOOL],
15
+ )
16
+
17
+ async with agent.session() as session:
18
+ await session.run("Help me configure my project")
19
+ """
20
+
21
+ from typing import Annotated, Literal
22
+
23
+ from pydantic import BaseModel, Field
24
+ from rich.panel import Panel
25
+ from rich.prompt import Confirm, Prompt
26
+
27
+ from stirrup.core.models import Tool, ToolResult, ToolUseCountMetadata
28
+ from stirrup.utils.logging import AgentLoggerBase, console
29
+
30
+ __all__ = ["USER_INPUT_TOOL", "UserInputParams"]
31
+
32
+
33
+ class UserInputParams(BaseModel):
34
+ """Parameters for asking the user a single question.
35
+
36
+ Supports three question types:
37
+ - "text": Free-form text input (default)
38
+ - "choice": Multiple choice from a list of options
39
+ - "confirm": Yes/no confirmation
40
+ """
41
+
42
+ question: Annotated[str, Field(description="A single question to ask the user (*not* multiple questions)")]
43
+ question_type: Annotated[
44
+ Literal["text", "choice", "confirm"],
45
+ Field(
46
+ default="text",
47
+ description="Type of question: 'text' for free-form, 'choice' for multiple choice, 'confirm' for yes/no",
48
+ ),
49
+ ]
50
+ choices: Annotated[
51
+ list[str] | None,
52
+ Field(default=None, description="List of valid choices (required when question_type is 'choice')"),
53
+ ]
54
+ default: Annotated[
55
+ str,
56
+ Field(default="", description="Default value if user presses Enter without input"),
57
+ ]
58
+
59
+
60
+ def _get_logger() -> "AgentLoggerBase | None":
61
+ """Get the current session's logger for pause/resume.
62
+
63
+ Returns the logger from SessionState if available, None otherwise.
64
+ """
65
+ from stirrup.core.agent import _SESSION_STATE
66
+
67
+ state = _SESSION_STATE.get(None)
68
+ return state.logger if state else None
69
+
70
+
71
+ def user_input_executor(params: UserInputParams) -> ToolResult[ToolUseCountMetadata]:
72
+ """Prompt the user for input and return their response."""
73
+ logger = _get_logger()
74
+
75
+ # Pause spinner before prompting
76
+ if logger:
77
+ logger.pause_live()
78
+
79
+ try:
80
+ # Print newline to separate from spinner, then display question in a styled panel
81
+ console.print()
82
+ panel = Panel(
83
+ params.question,
84
+ title="[bold cyan]🤔 Agent Question[/]",
85
+ title_align="left",
86
+ border_style="cyan",
87
+ padding=(0, 1),
88
+ )
89
+ console.print(panel)
90
+
91
+ # Get user input based on question type
92
+ if params.question_type == "confirm":
93
+ # Yes/no confirmation
94
+ default_bool = params.default.lower() in ("yes", "y", "true", "1") if params.default else False
95
+ result = Confirm.ask("[bold]Your answer[/]", default=default_bool, console=console)
96
+ answer = "yes" if result else "no"
97
+
98
+ elif params.question_type == "choice" and params.choices:
99
+ # Multiple choice
100
+ answer = Prompt.ask(
101
+ "[bold]Your answer[/]",
102
+ choices=params.choices,
103
+ default=params.default,
104
+ console=console,
105
+ )
106
+
107
+ else:
108
+ # Free-form text (default)
109
+ answer = Prompt.ask("[bold]Your answer[/]", default=params.default or "", console=console)
110
+
111
+ return ToolResult(content=answer, metadata=ToolUseCountMetadata())
112
+
113
+ finally:
114
+ # Always resume spinner, even if an exception occurs
115
+ if logger:
116
+ logger.resume_live()
117
+
118
+
119
+ USER_INPUT_TOOL: Tool[UserInputParams, ToolUseCountMetadata] = Tool(
120
+ name="user_input",
121
+ description=(
122
+ "Ask the user a question when you need clarification or are uncertain. "
123
+ "Supports three types: 'text' for free-form input, 'choice' for multiple choice "
124
+ "(provide choices list), 'confirm' for yes/no questions. Returns the user's response."
125
+ "There should only EVER be one question per call to this tool."
126
+ "If you need to ask multiple questions, you should call this tool multiple times."
127
+ ),
128
+ parameters=UserInputParams,
129
+ executor=user_input_executor,
130
+ )
stirrup/tools/web.py CHANGED
@@ -125,6 +125,7 @@ def _get_fetch_web_page_tool(client: httpx.AsyncClient | None = None) -> Tool[Fe
125
125
  return ToolResult(
126
126
  content=f"<web_fetch><url>{params.url}</url><error>"
127
127
  f"{truncate_msg(str(exc), MAX_LENGTH_WEB_FETCH_HTML)}</error></web_fetch>",
128
+ success=False,
128
129
  metadata=WebFetchMetadata(pages_fetched=[params.url]),
129
130
  )
130
131
 
stirrup/utils/logging.py CHANGED
@@ -23,10 +23,12 @@ from rich.text import Text
23
23
  from rich.tree import Tree
24
24
 
25
25
  from stirrup.core.models import AssistantMessage, ToolMessage, UserMessage, _aggregate_list, aggregate_metadata
26
+ from stirrup.utils.text import truncate_msg
26
27
 
27
28
  __all__ = [
28
29
  "AgentLogger",
29
30
  "AgentLoggerBase",
31
+ "console",
30
32
  ]
31
33
 
32
34
  # Shared console instance
@@ -247,6 +249,12 @@ class AgentLoggerBase(ABC):
247
249
  """Log an error message."""
248
250
  ...
249
251
 
252
+ def pause_live(self) -> None: # noqa: B027
253
+ """Pause live display (e.g., spinner) before user interaction."""
254
+
255
+ def resume_live(self) -> None: # noqa: B027
256
+ """Resume live display after user interaction."""
257
+
250
258
 
251
259
  class AgentLogger(AgentLoggerBase):
252
260
  """Rich console logger for agent workflows.
@@ -655,6 +663,23 @@ class AgentLogger(AgentLoggerBase):
655
663
  if self._live:
656
664
  self._live.update(self._make_spinner())
657
665
 
666
+ def pause_live(self) -> None:
667
+ """Pause the live spinner display.
668
+
669
+ Call this before prompting for user input to prevent the spinner
670
+ from interfering with the input prompt.
671
+ """
672
+ if self._live is not None:
673
+ self._live.stop()
674
+
675
+ def resume_live(self) -> None:
676
+ """Resume the live spinner display.
677
+
678
+ Call this after user input is complete to restart the spinner.
679
+ """
680
+ if self._live is not None:
681
+ self._live.start()
682
+
658
683
  def set_level(self, level: int) -> None:
659
684
  """Set the logging level."""
660
685
  self._level = level
@@ -745,11 +770,12 @@ class AgentLogger(AgentLoggerBase):
745
770
  content.append("\n\n")
746
771
  content.append("Tool Calls:\n", style="bold magenta")
747
772
  for tc in assistant_message.tool_calls:
748
- args_parsed = json.loads(tc.arguments)
749
- args_formatted = json.dumps(args_parsed, indent=2, ensure_ascii=False)
750
- args_preview = args_formatted[:1000] + "..." if len(args_formatted) > 1000 else args_formatted
751
773
  content.append(f" 🔧 {tc.name}", style="magenta")
752
- content.append(args_preview, style="dim")
774
+ if tc.arguments and tc.arguments.strip():
775
+ args_parsed = json.loads(tc.arguments)
776
+ args_formatted = json.dumps(args_parsed, indent=2, ensure_ascii=False)
777
+ args_preview = args_formatted[:1000] + "..." if len(args_formatted) > 1000 else args_formatted
778
+ content.append(args_preview, style="dim")
753
779
 
754
780
  # Create and print panel with agent name in title
755
781
  title = f"[bold]AssistantMessage[/bold] │ {self.name} │ Turn {turn}/{max_turns}"
@@ -851,9 +877,8 @@ class AgentLogger(AgentLoggerBase):
851
877
  # Unescape HTML entities (e.g., &lt; -> <, &gt; -> >, &amp; -> &)
852
878
  result_text = html.unescape(result_text)
853
879
 
854
- # Truncate long results
855
- if len(result_text) > 1000:
856
- result_text = result_text[:1000] + "..."
880
+ # Truncate long results (keeps start and end, removes middle)
881
+ result_text = truncate_msg(result_text, 1000)
857
882
 
858
883
  # Format as XML with syntax highlighting
859
884
  content = Syntax(result_text, "xml", theme="monokai", word_wrap=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stirrup
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: The lightweight foundation for building agents
5
5
  Keywords: ai,agent,llm,openai,anthropic,tools,framework
6
6
  Author: Artificial Analysis, Inc.
@@ -47,17 +47,19 @@ Requires-Dist: pydantic>=2.0.0
47
47
  Requires-Dist: rich>=13.0.0
48
48
  Requires-Dist: tenacity>=5.0.0
49
49
  Requires-Dist: trafilatura>=1.9.0
50
- Requires-Dist: stirrup[litellm,e2b,docker,mcp] ; extra == 'all'
50
+ Requires-Dist: stirrup[litellm,e2b,docker,mcp,browser] ; extra == 'all'
51
+ Requires-Dist: browser-use>=0.11.3 ; extra == 'browser'
51
52
  Requires-Dist: docker>=7.0.0 ; extra == 'docker'
52
53
  Requires-Dist: python-dotenv>=1.0.0 ; extra == 'docker'
53
54
  Requires-Dist: e2b-code-interpreter>=2.3.0 ; extra == 'e2b'
54
55
  Requires-Dist: litellm>=1.79.3 ; extra == 'litellm'
55
56
  Requires-Dist: mcp>=1.9.0 ; extra == 'mcp'
56
57
  Requires-Python: >=3.12
57
- Project-URL: Documentation, https://stirrup.artificialanalysis.ai
58
58
  Project-URL: Homepage, https://github.com/ArtificialAnalysis/Stirrup
59
+ Project-URL: Documentation, https://stirrup.artificialanalysis.ai
59
60
  Project-URL: Repository, https://github.com/ArtificialAnalysis/Stirrup
60
61
  Provides-Extra: all
62
+ Provides-Extra: browser
61
63
  Provides-Extra: docker
62
64
  Provides-Extra: e2b
63
65
  Provides-Extra: litellm
@@ -91,16 +93,16 @@ Stirrup is a lightweight framework, or starting point template, for building age
91
93
 
92
94
  ## Features
93
95
 
94
- - **Essential tools built-in:**
95
- - Online search / web browsing
96
- - Code execution (local, Docker container, E2B sandbox)
97
- - MCP client
98
- - Document input and output
99
- - **Skills system:** Extend agent capabilities with modular, domain-specific instruction packages
100
- - **Flexible tool execution:** A generic `Tool` class allows easy tool definition and extension
101
- - **Context management:** Automatically summarizes conversation history when approaching context limits
102
- - **Flexible provider support:** Pre-built support for OpenAI-compatible APIs and LiteLLM, or bring your own client
103
- - **Multimodal support:** Process images, video, and audio with automatic format conversion
96
+ - 🧪 **Code execution:** Run code locally, in Docker, or in an E2B sandbox
97
+ - 🔎 **Online search / web browsing:** Search and fetch web pages
98
+ - 🔌 **MCP client support:** Connect to MCP servers and use their tools/resources
99
+ - 📄 **Document input and output:** Import files into context and produce file outputs
100
+ - 🧩 **Skills system:** Extend agents with modular, domain-specific instruction packages
101
+ - 🛠️ **Flexible tool execution:** A generic `Tool` interface allows easy tool definition
102
+ - 👤 **Human-in-the-loop:** Includes a built-in user input tool that enables human feedback or clarification during agent execution
103
+ - 🧠 **Context management:** Automatically summarizes conversation history when approaching context limits
104
+ - 🔁 **Flexible provider support:** Pre-built support for OpenAI-compatible APIs, LiteLLM, or bring your own client
105
+ - 🖼️ **Multimodal support:** Process images, video, and audio with automatic format conversion
104
106
 
105
107
  ## Installation
106
108
 
@@ -116,6 +118,7 @@ pip install 'stirrup[litellm]' # or: uv add 'stirrup[litellm]'
116
118
  pip install 'stirrup[docker]' # or: uv add 'stirrup[docker]'
117
119
  pip install 'stirrup[e2b]' # or: uv add 'stirrup[e2b]'
118
120
  pip install 'stirrup[mcp]' # or: uv add 'stirrup[mcp]'
121
+ pip install 'stirrup[browser]' # or: uv add 'stirrup[browser]'
119
122
  ```
120
123
 
121
124
  ## Quick Start
@@ -0,0 +1,38 @@
1
+ stirrup/__init__.py,sha256=4p5Rw7f_wdxVu1FJTgJROe0aTlnc8tOsainBEzDRGEY,1905
2
+ stirrup/clients/__init__.py,sha256=vHtR1rqK7w9LMWCBJXJde_R1pwdbuj9f6AbAJ4ib3xk,622
3
+ stirrup/clients/chat_completions_client.py,sha256=P0VOGFQcfLIVf7zCGYbopQDjNVEJlpeLBeGvB87sQQg,7390
4
+ stirrup/clients/litellm_client.py,sha256=2ZrZKKAEV2dEFs6ze4qBl3it5VwiGH8s7wHVuHdw-uY,5507
5
+ stirrup/clients/open_responses_client.py,sha256=AEM2z_LZhT4hW7VUSFb4mqzV5q_mavnLCwiBTUmt340,15303
6
+ stirrup/clients/utils.py,sha256=Z_8KiENDZVD9fChUm7PA-RLhvoChswHVQsjrHXlIfkg,5684
7
+ stirrup/constants.py,sha256=WpVPm2jRN2AqYMyoMYeJimiggoquP7M3IrcHNpduFF4,644
8
+ stirrup/core/__init__.py,sha256=ReBVl7B9h_FNkZ77vCx2xlfuK1JuQ0yTSXrEgc4tONU,39
9
+ stirrup/core/agent.py,sha256=I-SDjXxUOYSVxowy7SHXP_bjEEZD2JIouNnS7VarmxI,58120
10
+ stirrup/core/cache.py,sha256=lAbbBJzgYInewoBBPMzNooroU2Q7JbF21riggfdIDa8,16697
11
+ stirrup/core/exceptions.py,sha256=CzLVAi7Ns-t9BWSkqQUCB7ypVHAesV2s4a09-i0NXyQ,213
12
+ stirrup/core/models.py,sha256=hAwquChulASzwf0yirmsWkfgBEnLTxX9pTAkJ7CwVvM,22591
13
+ stirrup/prompts/__init__.py,sha256=e4bpTktBaFPuO_bIW5DelGNWtT6_NIUqnD2lRv8n89I,796
14
+ stirrup/prompts/base_system_prompt.txt,sha256=D_UlDWEnG2yaPCMFrE7IqMHI8VCzi4BZ-GnuL3qs5q4,288
15
+ stirrup/prompts/message_summarizer.txt,sha256=uQoTxreMuC42rTGSZmoH1Dnj06WrEQb0gLkDvVMhosQ,1173
16
+ stirrup/prompts/message_summarizer_bridge.txt,sha256=sWbfnHtI6RWemBIyQsnqHMGpnU-E6FTbfUC6rvkEHLY,372
17
+ stirrup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ stirrup/skills/__init__.py,sha256=BEcmdSskfBzx_QK4eKXECucndIKRjHXzzwwwsaez8k4,700
19
+ stirrup/skills/skills.py,sha256=qhA3HI55kaRqLyvn_56Cs71833Xacg-8qP7muHrwruE,4282
20
+ stirrup/tools/__init__.py,sha256=TJ3AqPhAkfVPboyMGHscIfq-oUc8brPPVnKtUrf9ICU,2879
21
+ stirrup/tools/browser_use.py,sha256=m7y5F9mO_Qhitmpu6NFh9-wYAxqgXb4_T3VuZdgGInM,21111
22
+ stirrup/tools/calculator.py,sha256=Cckt-8TtltxtuyY_Hh0wOr8efUzBZzg7rG4dBpvpuRM,1293
23
+ stirrup/tools/code_backends/__init__.py,sha256=O3Rs76r0YcQ27voTrx_zuhIEFawK3b1TQdKi70MORG8,987
24
+ stirrup/tools/code_backends/base.py,sha256=aOX09b4sCxjtFecnnklE_dINIfQ_lH3oYZJ3-N7rCMA,17720
25
+ stirrup/tools/code_backends/docker.py,sha256=6zeOL40peNGu3XGEzP6iwVIZmpr2TjENkllt5DZjHr4,30884
26
+ stirrup/tools/code_backends/e2b.py,sha256=hhaDVOYKxbYHIUhFw5kv-Pri0Sb3a-Weh5Zw5H0tyKM,15396
27
+ stirrup/tools/code_backends/local.py,sha256=6D6QnGYyspQbJ5HOaDke-asGc0-W55p-Nh5ABQkzjG0,20125
28
+ stirrup/tools/finish.py,sha256=xprl0z1N_e-8VddLyEIA9pLeMXjh5A9d4LpkKxhOh5A,1803
29
+ stirrup/tools/mcp.py,sha256=4wWYae95y8Bs7e36hHwnxRfVVj0PABrsRStw492lLaw,18749
30
+ stirrup/tools/user_input.py,sha256=XwK14FvRQly3vGwgNzPVGoSXfbco0WWaSVpTDyjV09E,4508
31
+ stirrup/tools/view_image.py,sha256=zazCpZMtLOD6lplLPYGNQ8JeYfc0oUDJoUUyVAp3AMU,3126
32
+ stirrup/tools/web.py,sha256=B-zp5i1WhjOOMAlYtnvU3N5hNYnJYm8qVXAtNx_ZaRw,12292
33
+ stirrup/utils/__init__.py,sha256=4kcuExrphSXqgxRgu1q8_Z6Rrb9aAZpIo4Xq4S9Twuk,230
34
+ stirrup/utils/logging.py,sha256=vZ7P7MotZnjbTyhMZ0p1YVTGhKQBAXBHOGbku3gEzuk,35166
35
+ stirrup/utils/text.py,sha256=3lGlcXFzQ-Mclsbu7wJciG3CcHvQ_Sk98tqOZxYLlGw,479
36
+ stirrup-0.1.4.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
37
+ stirrup-0.1.4.dist-info/METADATA,sha256=tJvHDTGB-Tfh4YqJVzyny9GAHqd6aZdqhpYG5apy3p8,13304
38
+ stirrup-0.1.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.18
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
@@ -1,34 +0,0 @@
1
- stirrup/__init__.py,sha256=Dol0Uui-gBslpplfEUrgxkqqJWr1ucZJej5finNvCeI,1869
2
- stirrup/clients/__init__.py,sha256=oO8VMHmbUhoxFyC0JLQs_kUFNSRlvTj5xz0FgzBb98E,405
3
- stirrup/clients/chat_completions_client.py,sha256=p_EeXqRuo6mWnzgMhy22SWdHE6_OXIRyABvCKgHdnu4,7586
4
- stirrup/clients/litellm_client.py,sha256=J-HDv7ZZTkNYC-aeSNyd7xTDd_5r8DEeXOPz9eQMC7A,4985
5
- stirrup/clients/utils.py,sha256=Yyeh6unQSvqgDTDhjpD5DoRu_wP_nWfsNv9DGXQwgo8,5452
6
- stirrup/constants.py,sha256=h3NzsePJ4FKpImTpV5xtFeJarKb67jR_6n89tNOkQYs,523
7
- stirrup/core/__init__.py,sha256=ReBVl7B9h_FNkZ77vCx2xlfuK1JuQ0yTSXrEgc4tONU,39
8
- stirrup/core/agent.py,sha256=tt1V564B6n_C0ffyH24LuC6PhE4EJA8NOolQCxDG9iw,50836
9
- stirrup/core/exceptions.py,sha256=CzLVAi7Ns-t9BWSkqQUCB7ypVHAesV2s4a09-i0NXyQ,213
10
- stirrup/core/models.py,sha256=KAyjJIoqbhgy_MN0sPwGI8XWxSiziPnyndMD05ylj_U,21121
11
- stirrup/prompts/__init__.py,sha256=e4bpTktBaFPuO_bIW5DelGNWtT6_NIUqnD2lRv8n89I,796
12
- stirrup/prompts/base_system_prompt.txt,sha256=KZ2_JhJ91u4oMqRZvhuAp99nb6ZkXkJdVbIRN6drVME,348
13
- stirrup/prompts/message_summarizer.txt,sha256=uQoTxreMuC42rTGSZmoH1Dnj06WrEQb0gLkDvVMhosQ,1173
14
- stirrup/prompts/message_summarizer_bridge.txt,sha256=sWbfnHtI6RWemBIyQsnqHMGpnU-E6FTbfUC6rvkEHLY,372
15
- stirrup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- stirrup/skills/__init__.py,sha256=BEcmdSskfBzx_QK4eKXECucndIKRjHXzzwwwsaez8k4,700
17
- stirrup/skills/skills.py,sha256=qhA3HI55kaRqLyvn_56Cs71833Xacg-8qP7muHrwruE,4282
18
- stirrup/tools/__init__.py,sha256=ohyeMvXb6oURiAyoHi0VmC9ksZSRyGleT341VNzHCy4,2714
19
- stirrup/tools/calculator.py,sha256=JkuGmGZJtaKbC4vHVrIph4aTjlGcFMhhv5MB1ntqgv4,1278
20
- stirrup/tools/code_backends/__init__.py,sha256=O3Rs76r0YcQ27voTrx_zuhIEFawK3b1TQdKi70MORG8,987
21
- stirrup/tools/code_backends/base.py,sha256=Nx0tTDX4GKoBWQK2F953vSsFgWCcOd_1WNtYCA4FG4o,17021
22
- stirrup/tools/code_backends/docker.py,sha256=Xx4aBZ1uXVznP0qV4tXL2PMMs-8QEPw1bIPvgPasEGk,30281
23
- stirrup/tools/code_backends/e2b.py,sha256=7wV1SOu4S5g5uCtnipC1xNg8kBzCrudyIEOIkf-JCkE,14072
24
- stirrup/tools/code_backends/local.py,sha256=WV-MMcPY5ooKPhOwd3JUUz718Ht8eRyYklGAZ0gkrx4,19598
25
- stirrup/tools/finish.py,sha256=K_NxwOwdvncT2QTua2A_8lZ9MwK4WQQ5FL2gdUrE29c,936
26
- stirrup/tools/mcp.py,sha256=4wWYae95y8Bs7e36hHwnxRfVVj0PABrsRStw492lLaw,18749
27
- stirrup/tools/view_image.py,sha256=zazCpZMtLOD6lplLPYGNQ8JeYfc0oUDJoUUyVAp3AMU,3126
28
- stirrup/tools/web.py,sha256=2yfBJsu8GgFI7Oh1dFlXwNaXth6WQfGpbJFU-rV-yuI,12261
29
- stirrup/utils/__init__.py,sha256=4kcuExrphSXqgxRgu1q8_Z6Rrb9aAZpIo4Xq4S9Twuk,230
30
- stirrup/utils/logging.py,sha256=3Li6MhjLJWwaXHDZ06EjgW42XDL6V3ZDt9rccV_ZYZ4,34292
31
- stirrup/utils/text.py,sha256=3lGlcXFzQ-Mclsbu7wJciG3CcHvQ_Sk98tqOZxYLlGw,479
32
- stirrup-0.1.2.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
33
- stirrup-0.1.2.dist-info/METADATA,sha256=eIK1F1yXhCgFspDO8q5J48B7P3Jt16QZez3ZBVFU5n8,12862
34
- stirrup-0.1.2.dist-info/RECORD,,