stirrup 0.1.3__py3-none-any.whl → 0.1.5__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.
@@ -228,6 +228,56 @@ class CodeExecToolProvider(ToolProvider, ABC):
228
228
  """
229
229
  ...
230
230
 
231
+ @abstractmethod
232
+ async def file_exists(self, path: str) -> bool:
233
+ """Check if a file exists in this execution environment.
234
+
235
+ Args:
236
+ path: File path within this execution environment (relative or absolute
237
+ within the env's working directory).
238
+
239
+ Returns:
240
+ True if the file exists, False otherwise.
241
+
242
+ Raises:
243
+ RuntimeError: If execution environment not started.
244
+
245
+ """
246
+ ...
247
+
248
+ @abstractmethod
249
+ async def is_directory(self, path: str) -> bool:
250
+ """Check if a path is a directory in this execution environment.
251
+
252
+ Args:
253
+ path: Path within this execution environment.
254
+
255
+ Returns:
256
+ True if the path exists and is a directory, False otherwise.
257
+
258
+ Raises:
259
+ RuntimeError: If execution environment not started.
260
+
261
+ """
262
+ ...
263
+
264
+ @abstractmethod
265
+ async def list_files(self, path: str) -> list[str]:
266
+ """List all files recursively in a directory within this execution environment.
267
+
268
+ Args:
269
+ path: Directory path within this execution environment.
270
+
271
+ Returns:
272
+ List of file paths (relative to the given path) for all files in the directory.
273
+ Returns an empty list if the path is a file or doesn't exist.
274
+
275
+ Raises:
276
+ RuntimeError: If execution environment not started.
277
+
278
+ """
279
+ ...
280
+
231
281
  async def save_output_files(
232
282
  self,
233
283
  paths: list[str],
@@ -317,18 +367,44 @@ class CodeExecToolProvider(ToolProvider, ABC):
317
367
  try:
318
368
  if source_env:
319
369
  # Cross-environment transfer: read from source_env
320
- content = await source_env.read_file_bytes(path_str)
321
- filename = Path(path_str).name
322
- dest_path = f"{dest_dir_str}/{filename}" if dest_dir_str else filename
323
- logger.debug(
324
- "UPLOAD CROSS-ENV: %s (%d bytes) from %s -> %s",
325
- path_str,
326
- len(content),
327
- type(source_env).__name__,
328
- dest_path,
329
- )
330
- await self.write_file_bytes(dest_path, content)
331
- result.uploaded.append(UploadedFile(Path(path_str), dest_path, len(content)))
370
+ # Check if it's a directory first
371
+ if await source_env.is_directory(path_str):
372
+ # Handle directory recursively
373
+ # Preserve directory name when dest_dir not specified
374
+ dir_name = Path(path_str).name
375
+ files = await source_env.list_files(path_str)
376
+ for rel_file_path in files:
377
+ src_file_path = f"{path_str}/{rel_file_path}"
378
+ # If dest_dir specified, put files directly there
379
+ # Otherwise, preserve the source directory name
380
+ if dest_dir_str:
381
+ dest_path = f"{dest_dir_str}/{rel_file_path}"
382
+ else:
383
+ dest_path = f"{dir_name}/{rel_file_path}"
384
+ content = await source_env.read_file_bytes(src_file_path)
385
+ logger.debug(
386
+ "UPLOAD CROSS-ENV (dir): %s (%d bytes) from %s -> %s",
387
+ src_file_path,
388
+ len(content),
389
+ type(source_env).__name__,
390
+ dest_path,
391
+ )
392
+ await self.write_file_bytes(dest_path, content)
393
+ result.uploaded.append(UploadedFile(Path(src_file_path), dest_path, len(content)))
394
+ else:
395
+ # Single file transfer
396
+ content = await source_env.read_file_bytes(path_str)
397
+ filename = Path(path_str).name
398
+ dest_path = f"{dest_dir_str}/{filename}" if dest_dir_str else filename
399
+ logger.debug(
400
+ "UPLOAD CROSS-ENV: %s (%d bytes) from %s -> %s",
401
+ path_str,
402
+ len(content),
403
+ type(source_env).__name__,
404
+ dest_path,
405
+ )
406
+ await self.write_file_bytes(dest_path, content)
407
+ result.uploaded.append(UploadedFile(Path(path_str), dest_path, len(content)))
332
408
  else:
333
409
  # Local filesystem upload - must be handled by subclass
334
410
  # This is a fallback that reads from local fs and writes to env
@@ -486,6 +486,72 @@ class DockerCodeExecToolProvider(CodeExecToolProvider):
486
486
  host_path.parent.mkdir(parents=True, exist_ok=True)
487
487
  host_path.write_bytes(content)
488
488
 
489
+ async def file_exists(self, path: str) -> bool:
490
+ """Check if a file exists in the container.
491
+
492
+ Since files are volume-mounted, checks directly on the host temp directory.
493
+
494
+ Args:
495
+ path: File path (relative or absolute container path).
496
+
497
+ Returns:
498
+ True if the file exists, False otherwise.
499
+
500
+ Raises:
501
+ RuntimeError: If environment not started.
502
+ ValueError: If path is outside mounted directory.
503
+
504
+ """
505
+ host_path = self._container_path_to_host(path)
506
+ return host_path.exists() and host_path.is_file()
507
+
508
+ async def is_directory(self, path: str) -> bool:
509
+ """Check if a path is a directory in the container.
510
+
511
+ Since files are volume-mounted, checks directly on the host temp directory.
512
+
513
+ Args:
514
+ path: Path (relative or absolute container path).
515
+
516
+ Returns:
517
+ True if the path exists and is a directory, False otherwise.
518
+
519
+ Raises:
520
+ RuntimeError: If environment not started.
521
+ ValueError: If path is outside mounted directory.
522
+
523
+ """
524
+ host_path = self._container_path_to_host(path)
525
+ return host_path.exists() and host_path.is_dir()
526
+
527
+ async def list_files(self, path: str) -> list[str]:
528
+ """List all files recursively in a directory within the container.
529
+
530
+ Since files are volume-mounted, lists directly from the host temp directory.
531
+
532
+ Args:
533
+ path: Directory path (relative or absolute container path).
534
+
535
+ Returns:
536
+ List of file paths (relative to the given path) for all files in the directory.
537
+ Returns an empty list if the path is a file or doesn't exist.
538
+
539
+ Raises:
540
+ RuntimeError: If environment not started.
541
+ ValueError: If path is outside mounted directory.
542
+
543
+ """
544
+ host_path = self._container_path_to_host(path)
545
+ if not host_path.exists() or not host_path.is_dir():
546
+ return []
547
+
548
+ files = []
549
+ for file_path in host_path.rglob("*"):
550
+ if file_path.is_file():
551
+ rel_path = file_path.relative_to(host_path)
552
+ files.append(str(rel_path))
553
+ return files
554
+
489
555
  async def run_command(self, cmd: str, *, timeout: int = SHELL_TIMEOUT) -> CommandResult:
490
556
  """Execute a shell command in the Docker container.
491
557
 
@@ -132,6 +132,86 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
132
132
 
133
133
  await self._sbx.files.write(path, content, request_timeout=self._request_timeout)
134
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)
152
+
153
+ async def is_directory(self, path: str) -> bool:
154
+ """Check if a path is a directory in the E2B sandbox.
155
+
156
+ Args:
157
+ path: Path within the sandbox.
158
+
159
+ Returns:
160
+ True if the path exists and is a directory, False otherwise.
161
+
162
+ Raises:
163
+ RuntimeError: If environment not started.
164
+
165
+ """
166
+ if self._sbx is None:
167
+ raise RuntimeError("ExecutionEnvironment not started.")
168
+
169
+ if not await self._sbx.files.exists(path):
170
+ return False
171
+
172
+ info = await self._sbx.files.get_info(path)
173
+ return info.type == FileType.DIR
174
+
175
+ async def list_files(self, path: str) -> list[str]:
176
+ """List all files recursively in a directory within the E2B sandbox.
177
+
178
+ Args:
179
+ path: Directory path within the sandbox.
180
+
181
+ Returns:
182
+ List of file paths (relative to the given path) for all files in the directory.
183
+ Returns an empty list if the path is a file or doesn't exist.
184
+
185
+ Raises:
186
+ RuntimeError: If environment not started.
187
+
188
+ """
189
+ if self._sbx is None:
190
+ raise RuntimeError("ExecutionEnvironment not started.")
191
+
192
+ if not await self._sbx.files.exists(path):
193
+ return []
194
+
195
+ info = await self._sbx.files.get_info(path)
196
+ if info.type != FileType.DIR:
197
+ return []
198
+
199
+ # Use find command to list all files recursively
200
+ result = await self.run_command(f"find {path} -type f")
201
+ if result.exit_code != 0:
202
+ return []
203
+
204
+ files = []
205
+ for line in result.stdout.strip().split("\n"):
206
+ if line:
207
+ # Convert absolute path to relative path
208
+ rel_path = line.removeprefix(f"{path}/").removeprefix(path)
209
+ if rel_path.startswith("/"):
210
+ rel_path = rel_path[1:]
211
+ if rel_path:
212
+ files.append(rel_path)
213
+ return files
214
+
135
215
  async def run_command(self, cmd: str, *, timeout: int = SHELL_TIMEOUT) -> CommandResult:
136
216
  """Execute command in E2B execution environment, returning raw CommandResult."""
137
217
  if self._sbx is None:
@@ -205,6 +205,66 @@ 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
+
225
+ async def is_directory(self, path: str) -> bool:
226
+ """Check if a path is a directory in the temp directory.
227
+
228
+ Args:
229
+ path: Path (relative or absolute within the temp dir).
230
+
231
+ Returns:
232
+ True if the path exists and is a directory, False otherwise.
233
+
234
+ Raises:
235
+ RuntimeError: If environment not started.
236
+ ValueError: If path is outside temp directory.
237
+
238
+ """
239
+ resolved = self._resolve_and_validate_path(path)
240
+ return resolved.exists() and resolved.is_dir()
241
+
242
+ async def list_files(self, path: str) -> list[str]:
243
+ """List all files recursively in a directory within the temp directory.
244
+
245
+ Args:
246
+ path: Directory path (relative or absolute within the temp dir).
247
+
248
+ Returns:
249
+ List of file paths (relative to the given path) for all files in the directory.
250
+ Returns an empty list if the path is a file or doesn't exist.
251
+
252
+ Raises:
253
+ RuntimeError: If environment not started.
254
+ ValueError: If path is outside temp directory.
255
+
256
+ """
257
+ resolved = self._resolve_and_validate_path(path)
258
+ if not resolved.exists() or not resolved.is_dir():
259
+ return []
260
+
261
+ files = []
262
+ for file_path in resolved.rglob("*"):
263
+ if file_path.is_file():
264
+ rel_path = file_path.relative_to(resolved)
265
+ files.append(str(rel_path))
266
+ return files
267
+
208
268
  async def run_command(self, cmd: str, *, timeout: int = SHELL_TIMEOUT) -> CommandResult:
209
269
  """Execute command in the temp directory.
210
270
 
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(), success=True),
48
+ executor=_validating_finish_executor,
23
49
  )
stirrup/utils/logging.py CHANGED
@@ -23,6 +23,7 @@ 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",
@@ -769,11 +770,12 @@ class AgentLogger(AgentLoggerBase):
769
770
  content.append("\n\n")
770
771
  content.append("Tool Calls:\n", style="bold magenta")
771
772
  for tc in assistant_message.tool_calls:
772
- args_parsed = json.loads(tc.arguments)
773
- args_formatted = json.dumps(args_parsed, indent=2, ensure_ascii=False)
774
- args_preview = args_formatted[:1000] + "..." if len(args_formatted) > 1000 else args_formatted
775
773
  content.append(f" 🔧 {tc.name}", style="magenta")
776
- 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")
777
779
 
778
780
  # Create and print panel with agent name in title
779
781
  title = f"[bold]AssistantMessage[/bold] │ {self.name} │ Turn {turn}/{max_turns}"
@@ -875,9 +877,8 @@ class AgentLogger(AgentLoggerBase):
875
877
  # Unescape HTML entities (e.g., &lt; -> <, &gt; -> >, &amp; -> &)
876
878
  result_text = html.unescape(result_text)
877
879
 
878
- # Truncate long results
879
- if len(result_text) > 1000:
880
- result_text = result_text[:1000] + "..."
880
+ # Truncate long results (keeps start and end, removes middle)
881
+ result_text = truncate_msg(result_text, 1000)
881
882
 
882
883
  # Format as XML with syntax highlighting
883
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.3
3
+ Version: 0.1.5
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
@@ -75,32 +77,32 @@ Description-Content-Type: text/markdown
75
77
  <br>
76
78
  </div>
77
79
 
78
-
79
80
  <p align="center">
80
81
  <a href="https://pypi.python.org/pypi/stirrup"><img src="https://img.shields.io/pypi/v/stirrup" alt="PyPI version" /></a>&nbsp;<!--
81
82
  --><a href="https://github.com/ArtificialAnalysis/Stirrup/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ArtificialAnalysis/Stirrup" alt="License" /></a>&nbsp;<!--
82
83
  --><a href="https://stirrup.artificialanalysis.ai"><img src="https://img.shields.io/badge/MkDocs-4F46E5?logo=materialformkdocs&logoColor=fff" alt="MkDocs" /></a>
83
84
  </p>
84
85
 
85
-
86
86
  Stirrup is a lightweight framework, or starting point template, for building agents. It differs from other agent frameworks by:
87
87
 
88
88
  - **Working with the model, not against it:** Stirrup gets out of the way and lets the model choose its own approach to completing tasks (similar to Claude Code). Many frameworks impose rigid workflows that can degrade results.
89
89
  - **Best practices and tools built-in:** We analyzed the leading agents (Claude Code, Codex, and others) to understand and incorporate best practices relating to topics like context management and foundational tools (e.g., code execution).
90
90
  - **Fully customizable:** Use Stirrup as a package or as a starting template to build your own fully customized agents.
91
91
 
92
+ > **Note:** This is the Python implementation, [StirrupJS](https://github.com/ArtificialAnalysis/StirrupJS) is the Typescript implementation.
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
@@ -1,14 +1,15 @@
1
1
  stirrup/__init__.py,sha256=4p5Rw7f_wdxVu1FJTgJROe0aTlnc8tOsainBEzDRGEY,1905
2
- stirrup/clients/__init__.py,sha256=oO8VMHmbUhoxFyC0JLQs_kUFNSRlvTj5xz0FgzBb98E,405
2
+ stirrup/clients/__init__.py,sha256=vHtR1rqK7w9LMWCBJXJde_R1pwdbuj9f6AbAJ4ib3xk,622
3
3
  stirrup/clients/chat_completions_client.py,sha256=P0VOGFQcfLIVf7zCGYbopQDjNVEJlpeLBeGvB87sQQg,7390
4
4
  stirrup/clients/litellm_client.py,sha256=2ZrZKKAEV2dEFs6ze4qBl3it5VwiGH8s7wHVuHdw-uY,5507
5
+ stirrup/clients/open_responses_client.py,sha256=AEM2z_LZhT4hW7VUSFb4mqzV5q_mavnLCwiBTUmt340,15303
5
6
  stirrup/clients/utils.py,sha256=Z_8KiENDZVD9fChUm7PA-RLhvoChswHVQsjrHXlIfkg,5684
6
7
  stirrup/constants.py,sha256=WpVPm2jRN2AqYMyoMYeJimiggoquP7M3IrcHNpduFF4,644
7
8
  stirrup/core/__init__.py,sha256=ReBVl7B9h_FNkZ77vCx2xlfuK1JuQ0yTSXrEgc4tONU,39
8
- stirrup/core/agent.py,sha256=2KxjuCy3scLRc59habPSZyYDCHL9a9--UcdFKYV-86w,56907
9
+ stirrup/core/agent.py,sha256=I-SDjXxUOYSVxowy7SHXP_bjEEZD2JIouNnS7VarmxI,58120
9
10
  stirrup/core/cache.py,sha256=lAbbBJzgYInewoBBPMzNooroU2Q7JbF21riggfdIDa8,16697
10
11
  stirrup/core/exceptions.py,sha256=CzLVAi7Ns-t9BWSkqQUCB7ypVHAesV2s4a09-i0NXyQ,213
11
- stirrup/core/models.py,sha256=qMmpecRIl8sKdUePtoVChKA3rBHxzG8lI4ZvuLFHNzo,22553
12
+ stirrup/core/models.py,sha256=hAwquChulASzwf0yirmsWkfgBEnLTxX9pTAkJ7CwVvM,22591
12
13
  stirrup/prompts/__init__.py,sha256=e4bpTktBaFPuO_bIW5DelGNWtT6_NIUqnD2lRv8n89I,796
13
14
  stirrup/prompts/base_system_prompt.txt,sha256=D_UlDWEnG2yaPCMFrE7IqMHI8VCzi4BZ-GnuL3qs5q4,288
14
15
  stirrup/prompts/message_summarizer.txt,sha256=uQoTxreMuC42rTGSZmoH1Dnj06WrEQb0gLkDvVMhosQ,1173
@@ -16,21 +17,22 @@ stirrup/prompts/message_summarizer_bridge.txt,sha256=sWbfnHtI6RWemBIyQsnqHMGpnU-
16
17
  stirrup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
18
  stirrup/skills/__init__.py,sha256=BEcmdSskfBzx_QK4eKXECucndIKRjHXzzwwwsaez8k4,700
18
19
  stirrup/skills/skills.py,sha256=qhA3HI55kaRqLyvn_56Cs71833Xacg-8qP7muHrwruE,4282
19
- stirrup/tools/__init__.py,sha256=l8bKHZwPBZfBPgxQfLzOKyyzdfoF86NrgdmxhjYW09s,2790
20
+ stirrup/tools/__init__.py,sha256=TJ3AqPhAkfVPboyMGHscIfq-oUc8brPPVnKtUrf9ICU,2879
21
+ stirrup/tools/browser_use.py,sha256=m7y5F9mO_Qhitmpu6NFh9-wYAxqgXb4_T3VuZdgGInM,21111
20
22
  stirrup/tools/calculator.py,sha256=Cckt-8TtltxtuyY_Hh0wOr8efUzBZzg7rG4dBpvpuRM,1293
21
23
  stirrup/tools/code_backends/__init__.py,sha256=O3Rs76r0YcQ27voTrx_zuhIEFawK3b1TQdKi70MORG8,987
22
- stirrup/tools/code_backends/base.py,sha256=5mQxbHoOvtTdiF1y1aVe4csodaCOGvHaVN5viRnUlUE,17250
23
- stirrup/tools/code_backends/docker.py,sha256=Xx4aBZ1uXVznP0qV4tXL2PMMs-8QEPw1bIPvgPasEGk,30281
24
- stirrup/tools/code_backends/e2b.py,sha256=4I5Aqas-4PVhYgMhIAQh3xcwl_0kGiUpo64UU9-awfE,14921
25
- stirrup/tools/code_backends/local.py,sha256=snzbWHnZkVu1ibLLRyRVmi6U9-8407_A0sKbdDNyTe4,19600
26
- stirrup/tools/finish.py,sha256=zdljSs77zMVkA2AGUfV05QmeLxz6JrwJF_ar02yvsVA,950
24
+ stirrup/tools/code_backends/base.py,sha256=5kWr_sIcg5gchJUue-8olrj-cgD81gdgHZgGexGVYfs,20310
25
+ stirrup/tools/code_backends/docker.py,sha256=M6UeypwMqEb_em_oul_53UyWG8zWzkAb7NzgEt27SGc,32510
26
+ stirrup/tools/code_backends/e2b.py,sha256=Jgv1p1kWlTDHCQmJsX2tFqG2lSKtdRsZ_553juo4HGE,17358
27
+ stirrup/tools/code_backends/local.py,sha256=2GP0jo0-rDYr-KDTDCESA0_HIBWi8iuebUWdtitrogs,21592
28
+ stirrup/tools/finish.py,sha256=xprl0z1N_e-8VddLyEIA9pLeMXjh5A9d4LpkKxhOh5A,1803
27
29
  stirrup/tools/mcp.py,sha256=4wWYae95y8Bs7e36hHwnxRfVVj0PABrsRStw492lLaw,18749
28
30
  stirrup/tools/user_input.py,sha256=XwK14FvRQly3vGwgNzPVGoSXfbco0WWaSVpTDyjV09E,4508
29
31
  stirrup/tools/view_image.py,sha256=zazCpZMtLOD6lplLPYGNQ8JeYfc0oUDJoUUyVAp3AMU,3126
30
32
  stirrup/tools/web.py,sha256=B-zp5i1WhjOOMAlYtnvU3N5hNYnJYm8qVXAtNx_ZaRw,12292
31
33
  stirrup/utils/__init__.py,sha256=4kcuExrphSXqgxRgu1q8_Z6Rrb9aAZpIo4Xq4S9Twuk,230
32
- stirrup/utils/logging.py,sha256=48rCzv7B15jiVM1JlKzwTSnGodbqGSKLlsZi9xRMmVM,35045
34
+ stirrup/utils/logging.py,sha256=vZ7P7MotZnjbTyhMZ0p1YVTGhKQBAXBHOGbku3gEzuk,35166
33
35
  stirrup/utils/text.py,sha256=3lGlcXFzQ-Mclsbu7wJciG3CcHvQ_Sk98tqOZxYLlGw,479
34
- stirrup-0.1.3.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
35
- stirrup-0.1.3.dist-info/METADATA,sha256=EyJEJTRFzLoSXnkBPauFvskBgMwJDJ8O2Vm8HdJPdsI,12862
36
- stirrup-0.1.3.dist-info/RECORD,,
36
+ stirrup-0.1.5.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
37
+ stirrup-0.1.5.dist-info/METADATA,sha256=8QNBKGrsYwWO4eJVKUSdZVPkyjodn5L96MtE0Ns8rc4,13445
38
+ stirrup-0.1.5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.22
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any