stirrup 0.1.0__py3-none-any.whl → 0.1.2__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.
stirrup/core/agent.py CHANGED
@@ -36,6 +36,7 @@ from stirrup.core.models import (
36
36
  UserMessage,
37
37
  )
38
38
  from stirrup.prompts import MESSAGE_SUMMARIZER, MESSAGE_SUMMARIZER_BRIDGE_TEMPLATE
39
+ from stirrup.skills import SkillMetadata, format_skills_section, load_skills_metadata
39
40
  from stirrup.tools import DEFAULT_TOOLS
40
41
  from stirrup.tools.code_backends.base import CodeExecToolProvider
41
42
  from stirrup.tools.code_backends.local import LocalCodeExecToolProvider
@@ -70,6 +71,7 @@ class SessionState:
70
71
  parent_exec_env: CodeExecToolProvider | None = None
71
72
  depth: int = 0
72
73
  uploaded_file_paths: list[str] = field(default_factory=list) # Paths of files uploaded to exec_env
74
+ skills_metadata: list[SkillMetadata] = field(default_factory=list) # Loaded skills metadata
73
75
 
74
76
 
75
77
  _SESSION_STATE: contextvars.ContextVar[SessionState] = contextvars.ContextVar("session_state")
@@ -222,6 +224,7 @@ class Agent[FinishParams: BaseModel, FinishMeta]:
222
224
  # Session configuration (set during session(), used in __aenter__)
223
225
  self._pending_output_dir: Path | None = None
224
226
  self._pending_input_files: str | Path | list[str | Path] | None = None
227
+ self._pending_skills_dir: Path | None = None
225
228
 
226
229
  # Instance-scoped state (populated during __aenter__, isolated per agent instance)
227
230
  self._active_tools: dict[str, Tool] = {}
@@ -258,6 +261,7 @@ class Agent[FinishParams: BaseModel, FinishMeta]:
258
261
  self,
259
262
  output_dir: Path | str | None = None,
260
263
  input_files: str | Path | list[str | Path] | None = None,
264
+ skills_dir: Path | str | None = None,
261
265
  ) -> Self:
262
266
  """Configure a session and return self for use as async context manager.
263
267
 
@@ -270,6 +274,9 @@ class Agent[FinishParams: BaseModel, FinishMeta]:
270
274
  - Glob patterns (e.g., "data/*.csv", "**/*.py")
271
275
  Raises ValueError if no CodeExecToolProvider is configured
272
276
  or if a glob pattern matches no files.
277
+ skills_dir: Directory containing skill definitions to load and make available
278
+ to the agent. Skills are uploaded to the execution environment
279
+ and their metadata is included in the system prompt.
273
280
 
274
281
  Returns:
275
282
  Self, for use with `async with agent.session(...) as session:`
@@ -285,6 +292,7 @@ class Agent[FinishParams: BaseModel, FinishMeta]:
285
292
  """
286
293
  self._pending_output_dir = Path(output_dir) if output_dir else None
287
294
  self._pending_input_files = input_files
295
+ self._pending_skills_dir = Path(skills_dir) if skills_dir else None
288
296
  return self
289
297
 
290
298
  def _resolve_input_files(self, input_files: str | Path | list[str | Path]) -> list[Path]:
@@ -410,6 +418,12 @@ class Agent[FinishParams: BaseModel, FinishMeta]:
410
418
  files_section += f"\n- {file_path}"
411
419
  parts.append(files_section)
412
420
 
421
+ # Skills section (if skills were loaded)
422
+ if state and state.skills_metadata:
423
+ skills_section = format_skills_section(state.skills_metadata)
424
+ if skills_section:
425
+ parts.append(f"\n\n{skills_section}")
426
+
413
427
  # User's custom system prompt (if provided)
414
428
  if self._system_prompt:
415
429
  parts.append(f"\n\nFollow these instructions from the User:\n{self._system_prompt}")
@@ -588,6 +602,18 @@ class Agent[FinishParams: BaseModel, FinishMeta]:
588
602
  raise RuntimeError(f"Failed to upload files: {result.failed}")
589
603
  self._pending_input_files = None # Clear pending state
590
604
 
605
+ # Upload skills directory if it exists and load metadata
606
+ if self._pending_skills_dir:
607
+ skills_path = self._pending_skills_dir
608
+ if skills_path.exists() and skills_path.is_dir():
609
+ if state.exec_env:
610
+ logger.debug("[%s __aenter__] Uploading skills directory: %s", self._name, skills_path)
611
+ await state.exec_env.upload_files(skills_path, dest_dir="skills")
612
+ # Load skills metadata (even if no exec_env, for system prompt)
613
+ state.skills_metadata = load_skills_metadata(skills_path)
614
+ logger.debug("[%s __aenter__] Loaded %d skills", self._name, len(state.skills_metadata))
615
+ self._pending_skills_dir = None # Clear pending state
616
+
591
617
  # Configure and enter logger context
592
618
  self._logger.name = self._name
593
619
  self._logger.model = self._client.model_slug
stirrup/core/models.py CHANGED
@@ -430,6 +430,7 @@ class Tool[P: BaseModel, M](BaseModel):
430
430
  (setup/teardown, resource pooling), use a ToolProvider instead.
431
431
 
432
432
  Example with parameters:
433
+ ```python
433
434
  class CalcParams(BaseModel):
434
435
  expression: str
435
436
 
@@ -439,13 +440,16 @@ class Tool[P: BaseModel, M](BaseModel):
439
440
  parameters=CalcParams,
440
441
  executor=lambda p: ToolResult(content=str(eval(p.expression))),
441
442
  )
443
+ ```
442
444
 
443
445
  Example without parameters:
446
+ ```python
444
447
  time_tool = Tool[None, None](
445
448
  name="time",
446
449
  description="Get current time",
447
450
  executor=lambda _: ToolResult(content=datetime.now().isoformat()),
448
451
  )
452
+ ```
449
453
  """
450
454
 
451
455
  name: str
@@ -0,0 +1,24 @@
1
+ """Skills module for agent capabilities.
2
+
3
+ This module provides functionality for loading and managing agent skills.
4
+ Skills are modular packages with instructions and resources that agents
5
+ can discover and use dynamically.
6
+
7
+ Example usage:
8
+ from stirrup.skills import load_skills_metadata, format_skills_section
9
+ from pathlib import Path
10
+
11
+ # Load skills from directory
12
+ skills = load_skills_metadata(Path("skills"))
13
+
14
+ # Format for system prompt
15
+ prompt_section = format_skills_section(skills)
16
+ """
17
+
18
+ from stirrup.skills.skills import SkillMetadata, format_skills_section, load_skills_metadata
19
+
20
+ __all__ = [
21
+ "SkillMetadata",
22
+ "format_skills_section",
23
+ "load_skills_metadata",
24
+ ]
@@ -0,0 +1,145 @@
1
+ """Skills loader for agent capabilities.
2
+
3
+ Skills are modular packages that extend agent capabilities with instructions,
4
+ scripts, and resources. Each skill is a directory containing a SKILL.md file
5
+ with YAML frontmatter (name, description) and detailed instructions.
6
+
7
+ Based on: https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills
8
+ """
9
+
10
+ import logging
11
+ import re
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class SkillMetadata:
20
+ """Metadata extracted from a skill's SKILL.md frontmatter."""
21
+
22
+ name: str
23
+ description: str
24
+ path: str # Relative path like "skills/data_analysis"
25
+
26
+
27
+ def parse_frontmatter(content: str) -> dict[str, str]:
28
+ """Parse YAML frontmatter from markdown content.
29
+
30
+ Extracts metadata between --- markers at the start of the file.
31
+
32
+ Args:
33
+ content: Markdown content with optional YAML frontmatter
34
+
35
+ Returns:
36
+ Dictionary of frontmatter key-value pairs, empty if no frontmatter found
37
+
38
+ """
39
+ # Match YAML frontmatter between --- markers
40
+ pattern = r"^---\s*\n(.*?)\n---"
41
+ match = re.match(pattern, content, re.DOTALL)
42
+
43
+ if not match:
44
+ return {}
45
+
46
+ frontmatter_text = match.group(1)
47
+ result: dict[str, str] = {}
48
+
49
+ # Simple YAML parsing for key: value pairs
50
+ for line in frontmatter_text.strip().split("\n"):
51
+ line = line.strip()
52
+ if ":" in line:
53
+ key, value = line.split(":", 1)
54
+ result[key.strip()] = value.strip()
55
+
56
+ return result
57
+
58
+
59
+ def load_skills_metadata(skills_dir: Path) -> list[SkillMetadata]:
60
+ """Scan skills directory for SKILL.md files and extract metadata.
61
+
62
+ Args:
63
+ skills_dir: Path to the skills directory
64
+
65
+ Returns:
66
+ List of SkillMetadata for each valid skill found.
67
+ Returns empty list if skills_dir doesn't exist or has no skills.
68
+
69
+ """
70
+ if not skills_dir.exists():
71
+ logger.debug("Skills directory does not exist: %s", skills_dir)
72
+ return []
73
+
74
+ if not skills_dir.is_dir():
75
+ logger.warning("Skills path is not a directory: %s", skills_dir)
76
+ return []
77
+
78
+ skills: list[SkillMetadata] = []
79
+
80
+ for skill_path in skills_dir.iterdir():
81
+ if not skill_path.is_dir():
82
+ continue
83
+
84
+ skill_md = skill_path / "SKILL.md"
85
+ if not skill_md.exists():
86
+ logger.debug("Skill directory missing SKILL.md: %s", skill_path)
87
+ continue
88
+
89
+ try:
90
+ content = skill_md.read_text(encoding="utf-8")
91
+ metadata = parse_frontmatter(content)
92
+
93
+ name = metadata.get("name")
94
+ description = metadata.get("description")
95
+
96
+ if not name or not description:
97
+ logger.warning(
98
+ "Skill %s missing required frontmatter (name, description)",
99
+ skill_path.name,
100
+ )
101
+ continue
102
+
103
+ skills.append(
104
+ SkillMetadata(
105
+ name=name,
106
+ description=description,
107
+ path=f"skills/{skill_path.name}",
108
+ )
109
+ )
110
+ logger.debug("Loaded skill: %s", name)
111
+
112
+ except Exception as e:
113
+ logger.warning("Failed to load skill %s: %s", skill_path.name, e)
114
+
115
+ return skills
116
+
117
+
118
+ def format_skills_section(skills: list[SkillMetadata]) -> str:
119
+ """Format skills metadata as a system prompt section.
120
+
121
+ Args:
122
+ skills: List of skill metadata to include
123
+
124
+ Returns:
125
+ Formatted string for inclusion in system prompt.
126
+ Returns empty string if no skills provided.
127
+
128
+ """
129
+ if not skills:
130
+ return ""
131
+
132
+ lines = [
133
+ "## Available Skills",
134
+ "",
135
+ "You have access to the following skills located in the `skills/` directory. "
136
+ "Each skill contains a SKILL.md file with detailed instructions and potentially bundled scripts.",
137
+ "",
138
+ "To use a skill:",
139
+ "1. Read the full instructions: `cat <skill_path>/SKILL.md`",
140
+ "2. Follow the instructions and use any bundled resources as described",
141
+ "",
142
+ ]
143
+ lines.extend([f"- **{skill.name}**: {skill.description} (`{skill.path}/SKILL.md`)" for skill in skills])
144
+
145
+ return "\n".join(lines)
@@ -330,7 +330,7 @@ class DockerCodeExecToolProvider(CodeExecToolProvider):
330
330
  context_path = self._dockerfile_context.resolve() if self._dockerfile_context else dockerfile_path.parent
331
331
 
332
332
  # Generate unique tag based on dockerfile path
333
- tag = f"agent001-exec-env-{hashlib.md5(str(dockerfile_path).encode()).hexdigest()[:8]}"
333
+ tag = f"stirrup-exec-env-{hashlib.md5(str(dockerfile_path).encode()).hexdigest()[:8]}"
334
334
 
335
335
  logger.info("Building image from %s with tag %s", dockerfile_path, tag)
336
336
 
@@ -710,8 +710,20 @@ class DockerCodeExecToolProvider(CodeExecToolProvider):
710
710
  logger.debug("Uploaded file: %s -> %s", source, container_path)
711
711
 
712
712
  elif source.is_dir():
713
- dest = dest_base / source.name
714
- shutil.copytree(source, dest, dirs_exist_ok=True)
713
+ # If dest_dir was explicitly provided, copy contents directly to dest_base
714
+ # Otherwise, create a subdirectory with the source's name
715
+ if dest_dir:
716
+ dest = dest_base
717
+ # Copy contents of source directory into dest_base
718
+ for item in source.iterdir():
719
+ item_dest = dest / item.name
720
+ if item.is_file():
721
+ shutil.copy2(item, item_dest)
722
+ else:
723
+ shutil.copytree(item, item_dest, dirs_exist_ok=True)
724
+ else:
725
+ dest = dest_base / source.name
726
+ shutil.copytree(source, dest, dirs_exist_ok=True)
715
727
  # Track all individual files uploaded
716
728
  for file_path in source.rglob("*"):
717
729
  if file_path.is_file():
@@ -725,7 +737,7 @@ class DockerCodeExecToolProvider(CodeExecToolProvider):
725
737
  size=file_path.stat().st_size,
726
738
  ),
727
739
  )
728
- logger.debug("Uploaded directory: %s -> %s/%s", source, self._working_dir, source.name)
740
+ logger.debug("Uploaded directory: %s -> %s", source, dest)
729
741
 
730
742
  except Exception as exc:
731
743
  result.failed[str(source)] = str(exc)
@@ -320,10 +320,12 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
320
320
 
321
321
  elif source.is_dir():
322
322
  # Upload all files in directory recursively
323
+ # If dest_dir was explicitly provided, copy contents directly to dest_base
324
+ # Otherwise, create a subdirectory with the source's name
323
325
  for file_path in source.rglob("*"):
324
326
  if file_path.is_file():
325
327
  relative = file_path.relative_to(source)
326
- dest = f"{dest_base}/{source.name}/{relative}"
328
+ dest = f"{dest_base}/{relative}" if dest_dir else f"{dest_base}/{source.name}/{relative}"
327
329
  content = file_path.read_bytes()
328
330
  await self._sbx.files.write(dest, content)
329
331
  result.uploaded.append(
@@ -333,7 +335,10 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
333
335
  size=len(content),
334
336
  ),
335
337
  )
336
- logger.debug("Uploaded directory: %s -> %s/%s", source, dest_base, source.name)
338
+ if dest_dir:
339
+ logger.debug("Uploaded directory contents: %s -> %s", source, dest_base)
340
+ else:
341
+ logger.debug("Uploaded directory: %s -> %s/%s", source, dest_base, source.name)
337
342
 
338
343
  except Exception as exc:
339
344
  result.failed[str(source)] = str(exc)
@@ -440,8 +440,20 @@ class LocalCodeExecToolProvider(CodeExecToolProvider):
440
440
  logger.debug("Uploaded file: %s -> %s", source, dest)
441
441
 
442
442
  elif source.is_dir():
443
- dest = dest_base / source.name
444
- shutil.copytree(source, dest, dirs_exist_ok=True)
443
+ # If dest_dir was explicitly provided, copy contents directly to dest_base
444
+ # Otherwise, create a subdirectory with the source's name
445
+ if dest_dir:
446
+ dest = dest_base
447
+ # Copy contents of source directory into dest_base
448
+ for item in source.iterdir():
449
+ item_dest = dest / item.name
450
+ if item.is_file():
451
+ shutil.copy2(item, item_dest)
452
+ else:
453
+ shutil.copytree(item, item_dest, dirs_exist_ok=True)
454
+ else:
455
+ dest = dest_base / source.name
456
+ shutil.copytree(source, dest, dirs_exist_ok=True)
445
457
  # Track all individual files uploaded
446
458
  for file_path in source.rglob("*"):
447
459
  if file_path.is_file():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stirrup
3
- Version: 0.1.0
3
+ Version: 0.1.2
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.
@@ -44,6 +44,7 @@ Requires-Dist: moviepy>=2.0.0
44
44
  Requires-Dist: openai>=1.0.0
45
45
  Requires-Dist: pillow>=10.4.0
46
46
  Requires-Dist: pydantic>=2.0.0
47
+ Requires-Dist: rich>=13.0.0
47
48
  Requires-Dist: tenacity>=5.0.0
48
49
  Requires-Dist: trafilatura>=1.9.0
49
50
  Requires-Dist: stirrup[litellm,e2b,docker,mcp] ; extra == 'all'
@@ -53,7 +54,7 @@ Requires-Dist: e2b-code-interpreter>=2.3.0 ; extra == 'e2b'
53
54
  Requires-Dist: litellm>=1.79.3 ; extra == 'litellm'
54
55
  Requires-Dist: mcp>=1.9.0 ; extra == 'mcp'
55
56
  Requires-Python: >=3.12
56
- Project-URL: Documentation, https://artificialanalysis.github.io/Stirrup
57
+ Project-URL: Documentation, https://stirrup.artificialanalysis.ai
57
58
  Project-URL: Homepage, https://github.com/ArtificialAnalysis/Stirrup
58
59
  Project-URL: Repository, https://github.com/ArtificialAnalysis/Stirrup
59
60
  Provides-Extra: all
@@ -64,9 +65,9 @@ Provides-Extra: mcp
64
65
  Description-Content-Type: text/markdown
65
66
 
66
67
  <div align="center">
67
- <a href="">
68
+ <a href="https://stirrup.artificialanalysis.ai">
68
69
  <picture>
69
- <img alt="Stirrup" src="assets/stirrup-banner.png" width="700">
70
+ <img alt="Stirrup" src="https://github.com/ArtificialAnalysis/Stirrup/blob/048653717d8662b0b81d152a037995af1c926afc/assets/stirrup-banner.png?raw=true" width="700">
70
71
  </picture>
71
72
  </a>
72
73
  <br></br>
@@ -78,7 +79,7 @@ Description-Content-Type: text/markdown
78
79
  <p align="center">
79
80
  <a href="https://pypi.python.org/pypi/stirrup"><img src="https://img.shields.io/pypi/v/stirrup" alt="PyPI version" /></a>&nbsp;<!--
80
81
  --><a href="https://github.com/ArtificialAnalysis/Stirrup/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ArtificialAnalysis/Stirrup" alt="License" /></a>&nbsp;<!--
81
- --><a href="https://artificialanalysis.github.io/Stirrup"><img src="https://img.shields.io/badge/MkDocs-4F46E5?logo=materialformkdocs&logoColor=fff" alt="MkDocs" /></a>
82
+ --><a href="https://stirrup.artificialanalysis.ai"><img src="https://img.shields.io/badge/MkDocs-4F46E5?logo=materialformkdocs&logoColor=fff" alt="MkDocs" /></a>
82
83
  </p>
83
84
 
84
85
 
@@ -95,6 +96,7 @@ Stirrup is a lightweight framework, or starting point template, for building age
95
96
  - Code execution (local, Docker container, E2B sandbox)
96
97
  - MCP client
97
98
  - Document input and output
99
+ - **Skills system:** Extend agent capabilities with modular, domain-specific instruction packages
98
100
  - **Flexible tool execution:** A generic `Tool` class allows easy tool definition and extension
99
101
  - **Context management:** Automatically summarizes conversation history when approaching context limits
100
102
  - **Flexible provider support:** Pre-built support for OpenAI-compatible APIs and LiteLLM, or bring your own client
@@ -104,16 +106,16 @@ Stirrup is a lightweight framework, or starting point template, for building age
104
106
 
105
107
  ```bash
106
108
  # Core framework
107
- uv pip install stirrup # or: uv add stirrup
109
+ pip install stirrup # or: uv add stirrup
108
110
 
109
111
  # With all optional components
110
- uv pip install stirrup[all] # or: uv add stirrup[all]
112
+ pip install 'stirrup[all]' # or: uv add 'stirrup[all]'
111
113
 
112
114
  # Individual extras
113
- uv pip install stirrup[litellm] # or: uv add stirrup[litellm]
114
- uv pip install stirrup[docker] # or: uv add stirrup[docker]
115
- uv pip install stirrup[e2b] # or: uv add stirrup[e2b]
116
- uv pip install stirrup[mcp] # or: uv add stirrup[mcp]
115
+ pip install 'stirrup[litellm]' # or: uv add 'stirrup[litellm]'
116
+ pip install 'stirrup[docker]' # or: uv add 'stirrup[docker]'
117
+ pip install 'stirrup[e2b]' # or: uv add 'stirrup[e2b]'
118
+ pip install 'stirrup[mcp]' # or: uv add 'stirrup[mcp]'
117
119
  ```
118
120
 
119
121
  ## Quick Start
@@ -159,6 +161,24 @@ if __name__ == "__main__":
159
161
 
160
162
  > **Note:** This example uses OpenRouter. Set `OPENROUTER_API_KEY` in your environment before running. Web search requires a `BRAVE_API_KEY`. The agent will still work without it, but web search will be unavailable.
161
163
 
164
+ ## Full Customization
165
+
166
+ For using Stirrup as a foundation for your own fully customized agent, you can clone and import Stirrup locally:
167
+
168
+ ```bash
169
+ # Clone the repository
170
+ git clone https://github.com/ArtificialAnalysis/Stirrup.git
171
+ cd stirrup
172
+
173
+ # Install in editable mode
174
+ pip install -e . # or: uv venv && uv pip install -e .
175
+
176
+ # Or with all optional dependencies
177
+ pip install -e '.[all]' # or: uv venv && uv pip install -e '.[all]'
178
+ ```
179
+
180
+ See the [Full Customization guide](https://stirrup.artificialanalysis.ai/extending/full-customization/) for more details.
181
+
162
182
  ## How It Works
163
183
 
164
184
  - **`Agent`** - Configures and runs the agent loop until a finish tool is called or max turns reached
@@ -187,6 +207,7 @@ agent = Agent(client=client, name="deepseek_agent")
187
207
  ### LiteLLM (Anthropic, Google, etc.)
188
208
 
189
209
  ```python
210
+ # Ensure LiteLLM is added with: pip install 'stirrup[litellm]' # or: uv add 'stirrup[litellm]'
190
211
  # Create LiteLLM client for Anthropic Claude
191
212
  # See https://docs.litellm.ai/docs/providers for all supported providers
192
213
  client = LiteLLMClient(
@@ -201,7 +222,7 @@ agent = Agent(
201
222
  )
202
223
  ```
203
224
 
204
- See [LiteLLM Example](https://artificialanalysis.github.io/Stirrup/examples/#litellm-multi-provider-support) or [Deepseek Example](https://artificialanalysis.github.io/Stirrup/examples/#openai-compatible-apis-deepseek-vllm-ollama) for complete examples.
225
+ See [LiteLLM Example](https://stirrup.artificialanalysis.ai/examples/#litellm-multi-provider-support) or [Deepseek Example](https://stirrup.artificialanalysis.ai/examples/#openai-compatible-apis-deepseek-vllm-ollama) for complete examples.
205
226
 
206
227
  ## Default Tools
207
228
 
@@ -284,14 +305,14 @@ agent = Agent(
284
305
 
285
306
  ## Next Steps
286
307
 
287
- - [Getting Started](https://artificialanalysis.github.io/Stirrup/getting-started/) - Installation and first agent tutorial
288
- - [Core Concepts](https://artificialanalysis.github.io/Stirrup/concepts/) - Understand Agent, Tools, and Sessions
289
- - [Examples](https://artificialanalysis.github.io/Stirrup/examples/) - Working examples for common patterns
290
- - [Creating Tools](https://artificialanalysis.github.io/Stirrup/guides/tools/) - Build your own tools
308
+ - [Getting Started](https://stirrup.artificialanalysis.ai/getting-started/) - Installation and first agent tutorial
309
+ - [Core Concepts](https://stirrup.artificialanalysis.ai/concepts/) - Understand Agent, Tools, and Sessions
310
+ - [Examples](https://stirrup.artificialanalysis.ai/examples/) - Working examples for common patterns
311
+ - [Creating Tools](https://stirrup.artificialanalysis.ai/guides/tools/) - Build your own tools
291
312
 
292
313
  ## Documentation
293
314
 
294
- Full documentation: [artificialanalysis.github.io/Stirrup](https://artificialanalysis.github.io/Stirrup)
315
+ Full documentation: [artificialanalysis.github.io/Stirrup](https://stirrup.artificialanalysis.ai)
295
316
 
296
317
  Build and serve locally:
297
318
 
@@ -5,21 +5,23 @@ stirrup/clients/litellm_client.py,sha256=J-HDv7ZZTkNYC-aeSNyd7xTDd_5r8DEeXOPz9eQ
5
5
  stirrup/clients/utils.py,sha256=Yyeh6unQSvqgDTDhjpD5DoRu_wP_nWfsNv9DGXQwgo8,5452
6
6
  stirrup/constants.py,sha256=h3NzsePJ4FKpImTpV5xtFeJarKb67jR_6n89tNOkQYs,523
7
7
  stirrup/core/__init__.py,sha256=ReBVl7B9h_FNkZ77vCx2xlfuK1JuQ0yTSXrEgc4tONU,39
8
- stirrup/core/agent.py,sha256=8nHlcyh5Ji3_vXSH6dPbKvFCV7Q7cmnJcSaj0Sy-zto,49158
8
+ stirrup/core/agent.py,sha256=tt1V564B6n_C0ffyH24LuC6PhE4EJA8NOolQCxDG9iw,50836
9
9
  stirrup/core/exceptions.py,sha256=CzLVAi7Ns-t9BWSkqQUCB7ypVHAesV2s4a09-i0NXyQ,213
10
- stirrup/core/models.py,sha256=ONiCFC9NcoeArX4629n9s5FNLi02ynqZRiCxJbzhr1Q,21061
10
+ stirrup/core/models.py,sha256=KAyjJIoqbhgy_MN0sPwGI8XWxSiziPnyndMD05ylj_U,21121
11
11
  stirrup/prompts/__init__.py,sha256=e4bpTktBaFPuO_bIW5DelGNWtT6_NIUqnD2lRv8n89I,796
12
12
  stirrup/prompts/base_system_prompt.txt,sha256=KZ2_JhJ91u4oMqRZvhuAp99nb6ZkXkJdVbIRN6drVME,348
13
13
  stirrup/prompts/message_summarizer.txt,sha256=uQoTxreMuC42rTGSZmoH1Dnj06WrEQb0gLkDvVMhosQ,1173
14
14
  stirrup/prompts/message_summarizer_bridge.txt,sha256=sWbfnHtI6RWemBIyQsnqHMGpnU-E6FTbfUC6rvkEHLY,372
15
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
16
18
  stirrup/tools/__init__.py,sha256=ohyeMvXb6oURiAyoHi0VmC9ksZSRyGleT341VNzHCy4,2714
17
19
  stirrup/tools/calculator.py,sha256=JkuGmGZJtaKbC4vHVrIph4aTjlGcFMhhv5MB1ntqgv4,1278
18
20
  stirrup/tools/code_backends/__init__.py,sha256=O3Rs76r0YcQ27voTrx_zuhIEFawK3b1TQdKi70MORG8,987
19
21
  stirrup/tools/code_backends/base.py,sha256=Nx0tTDX4GKoBWQK2F953vSsFgWCcOd_1WNtYCA4FG4o,17021
20
- stirrup/tools/code_backends/docker.py,sha256=xtQmAwGkXc9jKlrN2ydYn66HpQ8cQW1b-4bTmN87S4U,29616
21
- stirrup/tools/code_backends/e2b.py,sha256=1V52m1pQ4uBjGRL9iBeJik2beWURLupODCFtAtng9sE,13696
22
- stirrup/tools/code_backends/local.py,sha256=6KOn-4aoySHVElitqWX5yN_WDrpJkQlsgSif2q0hYi8,18903
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
23
25
  stirrup/tools/finish.py,sha256=K_NxwOwdvncT2QTua2A_8lZ9MwK4WQQ5FL2gdUrE29c,936
24
26
  stirrup/tools/mcp.py,sha256=4wWYae95y8Bs7e36hHwnxRfVVj0PABrsRStw492lLaw,18749
25
27
  stirrup/tools/view_image.py,sha256=zazCpZMtLOD6lplLPYGNQ8JeYfc0oUDJoUUyVAp3AMU,3126
@@ -27,6 +29,6 @@ stirrup/tools/web.py,sha256=2yfBJsu8GgFI7Oh1dFlXwNaXth6WQfGpbJFU-rV-yuI,12261
27
29
  stirrup/utils/__init__.py,sha256=4kcuExrphSXqgxRgu1q8_Z6Rrb9aAZpIo4Xq4S9Twuk,230
28
30
  stirrup/utils/logging.py,sha256=3Li6MhjLJWwaXHDZ06EjgW42XDL6V3ZDt9rccV_ZYZ4,34292
29
31
  stirrup/utils/text.py,sha256=3lGlcXFzQ-Mclsbu7wJciG3CcHvQ_Sk98tqOZxYLlGw,479
30
- stirrup-0.1.0.dist-info/WHEEL,sha256=xDCZ-UyfvkGuEHPeI7BcJzYKIZzdqN8A8o1M5Om8IyA,79
31
- stirrup-0.1.0.dist-info/METADATA,sha256=dUTUx3W620GhVbZBqXuaI6nwR-4Q6vLmfiE_4BE1S6A,12003
32
- stirrup-0.1.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.17
2
+ Generator: uv 0.9.18
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any