agentshim 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
agentshim/usage.py ADDED
@@ -0,0 +1,63 @@
1
+ """Token/turn usage value objects for CLI-based coding agents.
2
+
3
+ Duplicated in shape (not in import) from ``libs/pydantic_agent/_usage.py``
4
+ so ``agentshim`` does not depend on the pydantic-ai stack. The
5
+ ``to_dict()`` output shape is the shared contract between the two copies
6
+ and is pinned by ``tests/unit/test_usage_schema_parity.py``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class TokenUsage:
17
+ """Token counts for a CLI-agent session.
18
+
19
+ Invariant: ``cached_input_tokens`` is a subset of ``input_tokens``.
20
+ """
21
+
22
+ input_tokens: int = 0
23
+ output_tokens: int = 0
24
+ cached_input_tokens: int = 0
25
+ turns: int = 0
26
+
27
+ def __add__(self, other: TokenUsage) -> TokenUsage:
28
+ return TokenUsage(
29
+ input_tokens=self.input_tokens + other.input_tokens,
30
+ output_tokens=self.output_tokens + other.output_tokens,
31
+ cached_input_tokens=self.cached_input_tokens + other.cached_input_tokens,
32
+ turns=self.turns + other.turns,
33
+ )
34
+
35
+ def to_dict(self) -> dict[str, int]:
36
+ return {
37
+ "input_tokens": self.input_tokens,
38
+ "output_tokens": self.output_tokens,
39
+ "cached_input_tokens": self.cached_input_tokens,
40
+ "turns": self.turns,
41
+ }
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class ProviderUsage:
46
+ """Per-session usage bundle: common token counts + provider extras.
47
+
48
+ ``provider`` is the registered provider name ("claude", "codex",
49
+ "gemini", "opencode"). ``total_cost_usd`` is ``None`` when the
50
+ provider does not report cost in its stream (codex, gemini, opencode
51
+ depending on config).
52
+ """
53
+
54
+ tokens: TokenUsage = field(default_factory=TokenUsage)
55
+ total_cost_usd: float | None = None
56
+ provider: str = ""
57
+
58
+ def to_dict(self) -> dict[str, Any]:
59
+ return {
60
+ **self.tokens.to_dict(),
61
+ "total_cost_usd": self.total_cost_usd,
62
+ "provider": self.provider,
63
+ }
agentshim/utils.py ADDED
@@ -0,0 +1,120 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from loguru import logger
8
+
9
+
10
+ def get_interactive_env() -> dict[str, str]:
11
+ """Capture environment variables from an interactive shell."""
12
+ try:
13
+ # Run env in an interactive shell to get the full user environment
14
+ # Use start_new_session=True (setsid) to detach from TTY and avoid
15
+ # SIGTTOU/SIGTTIN signals when bash -i tries to set process group
16
+ result = subprocess.run(
17
+ ["/bin/bash", "-i", "-c", "env"], capture_output=True, text=True, check=False, start_new_session=True
18
+ )
19
+ if result.returncode != 0:
20
+ return os.environ.copy()
21
+
22
+ env: dict[str, str] = {}
23
+ for line in result.stdout.splitlines():
24
+ if "=" in line:
25
+ key, value = line.split("=", 1)
26
+ env[key] = value
27
+ return env
28
+ except Exception:
29
+ return os.environ.copy()
30
+
31
+
32
+ # -- File generation system prompt (shared by RLMCodingAgent & SubagentCodingAgent) --
33
+
34
+
35
+ FILE_GEN_SYSTEM_PROMPT = (
36
+ "You are a deployment assistant. The user will ask you to generate "
37
+ "one or more files. For EACH file, output a section in this exact format:\n\n"
38
+ "FILE: .sds/<filename>\n"
39
+ "```\n"
40
+ "<file content here>\n"
41
+ "```\n\n"
42
+ "Output ONLY these sections. Do not add explanations outside the sections."
43
+ )
44
+
45
+
46
+ def generate_and_write_files(
47
+ raw: str,
48
+ prompt: str,
49
+ repo_path: Path,
50
+ log_label: str,
51
+ ) -> list[str]:
52
+ """Parse ``FILE: .sds/<name>`` sections from *raw* LLM output and write them.
53
+
54
+ Returns a list of relative paths that were written. If no ``FILE:`` sections
55
+ are found but the prompt references exactly one ``.sds/`` file, the entire
56
+ *raw* response (with markdown fences stripped) is written as that file
57
+ (fallback mode).
58
+
59
+ Args:
60
+ raw: Raw LLM response text.
61
+ prompt: The original prompt (used to detect expected output files).
62
+ repo_path: Root of the repository.
63
+ log_label: Label for log messages (e.g. ``"[RLM]"``, ``"[Subagent]"``).
64
+ """
65
+ expected_files = re.findall(r"\.sds/[\w._-]+", prompt)
66
+
67
+ written: list[str] = []
68
+ file_sections = re.findall(
69
+ r"(?:^|\n)\s*(?:#+\s*)?FILE:\s*(\.sds/[\w./-]+)\s*\n```[^\n]*\n(.*?)```",
70
+ raw,
71
+ re.DOTALL | re.IGNORECASE,
72
+ )
73
+ for rel_path, content in file_sections:
74
+ out_path = repo_path / rel_path
75
+ out_path.parent.mkdir(parents=True, exist_ok=True)
76
+ out_path.write_text(content)
77
+ logger.info(f"{log_label} Wrote {out_path}")
78
+ written.append(rel_path)
79
+
80
+ if not written and len(expected_files) == 1:
81
+ out_path = repo_path / expected_files[0]
82
+ content = re.sub(r"^```[^\n]*\n|```$", "", raw.strip(), flags=re.MULTILINE)
83
+ out_path.parent.mkdir(parents=True, exist_ok=True)
84
+ out_path.write_text(content)
85
+ logger.info(f"{log_label} Wrote {out_path} (fallback)")
86
+ written.append(expected_files[0])
87
+
88
+ return written
89
+
90
+
91
+ DEFAULT_TOOL_PARAM_MAX_LEN = 200
92
+ MEMORY_TOOL_PARAM_MAX_LEN = 10000
93
+ _VERBOSE_TOOL_NAMES = frozenset({"recall_incident", "store_incident"})
94
+
95
+
96
+ def truncate_params(params: Any, max_len: int = DEFAULT_TOOL_PARAM_MAX_LEN) -> str:
97
+ """Truncate a parameter representation to *max_len* characters."""
98
+ s = str(params)
99
+ if len(s) > max_len:
100
+ return s[:max_len] + "..."
101
+ return s
102
+
103
+
104
+ def truncate_tool_params(tool_name: str, params: Any) -> str:
105
+ """Render tool parameters with per-tool truncation rules.
106
+
107
+ Most tools keep the historical compact preview so CLI logs stay readable.
108
+ Incident-memory tools get a much larger budget because their payloads are
109
+ often the exact debugging context we need to inspect.
110
+ """
111
+ max_len = MEMORY_TOOL_PARAM_MAX_LEN if tool_name in _VERBOSE_TOOL_NAMES else DEFAULT_TOOL_PARAM_MAX_LEN
112
+ return truncate_params(params, max_len=max_len)
113
+
114
+
115
+ def truncate_content(content: str, max_lines: int = 10) -> str:
116
+ """Truncate *content* keeping the first and last *max_lines* lines."""
117
+ lines = content.splitlines()
118
+ if len(lines) > max_lines * 2:
119
+ return "\n".join(lines[:max_lines] + ["... (truncated) ..."] + lines[-max_lines:])
120
+ return content
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentshim
3
+ Version: 0.1.0
4
+ Summary: Provider-agnostic coding-agent CLI shims
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: litellm>=1.0.0
7
+ Requires-Dist: loguru>=0.7.2
8
+ Provides-Extra: test
9
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
10
+ Description-Content-Type: text/markdown
11
+
12
+ # agentshim
13
+
14
+ `agentshim` is a small Python package that wraps coding-agent CLIs behind a
15
+ stable interface for higher-level runtimes.
16
+
17
+ It currently includes:
18
+
19
+ - a `CodingAgent` base class and provider registry
20
+ - CLI adapters for Claude Code, Codex, Gemini, and Opencode
21
+ - MCP server config dataclasses
22
+ - sandbox settings helpers for Claude Code
23
+ - a lightweight litellm client and isolated subagent helper
24
+ - a trajectory protocol with a no-op implementation
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install agentshim
30
+ ```
31
+
32
+ For development:
33
+
34
+ ```bash
35
+ uv sync --dev
36
+ uv run pytest
37
+ ```
38
+
39
+ ## Quick Example
40
+
41
+ ```python
42
+ from agentshim.claude import ClaudeCodeCodingAgent
43
+
44
+ agent = ClaudeCodeCodingAgent(model="sonnet")
45
+ result = agent.generate("Summarize the repository layout.", cwd=".")
46
+ print(result)
47
+ ```
48
+
49
+ ## Publishing
50
+
51
+ Build locally with:
52
+
53
+ ```bash
54
+ uv build
55
+ ```
56
+
57
+ Publish with:
58
+
59
+ ```bash
60
+ uv publish
61
+ ```
@@ -0,0 +1,33 @@
1
+ agentshim/__init__.py,sha256=6jsY9Z3wuEl_OZcyrLJWmSzl6tyAR24oUW99UMXbpZc,692
2
+ agentshim/base.py,sha256=Z23Ca3ufd9qGDBK9ER4OeCGEpgdlNgX8t1aSpWhv1UQ,1425
3
+ agentshim/claude_events.py,sha256=U8A1IH4dsVHxfHrq28ghAUv1m6P2H_1ZHTp6SnJ3irw,375
4
+ agentshim/cli_agent.py,sha256=INdN67V10KOvS9v0miImx3YjRZz6PJscw34WQ3SnaHM,15623
5
+ agentshim/codex_events.py,sha256=GrUlbtvE_xh00tLWpMqBOnoKjEw4R31jubKbSctjwIQ,441
6
+ agentshim/events.py,sha256=meWWo8MX-25ByDtYqmYCzZ93KyYMWgKYpXJcT8dT8aw,622
7
+ agentshim/gemini_events.py,sha256=6mlITPfx_WjX-4mDZbt5FVw4iJCcuSAnjxdeP9Rm4ow,282
8
+ agentshim/llm_client.py,sha256=sOXrvp6qJiFWx6VAy-szNkapli55qRjIK7_yed0fHvQ,2245
9
+ agentshim/mcp_config.py,sha256=q_gJY0k89a0AHPUe_9KNNIHyb1xr5SAHMEke_XMrCmc,710
10
+ agentshim/opencode_events.py,sha256=S54kA-3olnkqm1sHD_CKRt_KnrRzEp6Nhdu2E08ppao,294
11
+ agentshim/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ agentshim/sandbox.py,sha256=pNEZM85cXq3Swh9S_Vkb2V-02QzEF5RH2MKWwWaUk14,6071
13
+ agentshim/subagent.py,sha256=S_mfykQ9envLQ1BshbXEg4e8sTxI3Db-mQgcQ7W3hzg,3264
14
+ agentshim/trajectory.py,sha256=xjLWv-EXc6_U3ubHg8SATvfgTMn4AEnMxiqG4Oc79wY,5064
15
+ agentshim/usage.py,sha256=yf_M9PkDyDZnz9pv8Z8oISeHSRqp0N62NeemFQ1tWS0,2015
16
+ agentshim/utils.py,sha256=qXNOma48LaiTeEKQr8cbNejA8qtErZMXX1JldyuxUb8,4253
17
+ agentshim/claude/__init__.py,sha256=7-enwzQK90mpHy_Z-IKkkoErwCL8aIfalo3SV24l7f0,130
18
+ agentshim/claude/agent.py,sha256=BVbiW7M9cpQpSBEE9mr13BxZArrK6ki8ywNcmHl_gGE,10310
19
+ agentshim/claude/events.py,sha256=90O7QFre0mNKTn7BX8MaSWewg1tpywDLxdiZ7cwHVwI,5075
20
+ agentshim/claude/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ agentshim/claude/hooks/confine_reads.py,sha256=kWDxAi1MyPtDNnGmuD0ZNrnIkzDdkjctr2oaBwiDIPc,2237
22
+ agentshim/codex/__init__.py,sha256=bgHz_1JcfcEeUwJwCBqWjauiIvhun7HoJkzdhCBTPQE,118
23
+ agentshim/codex/agent.py,sha256=ttZEbiLja5IAjPi1vxp6_X1zzvAEa-m-pFKLjF26aR4,8500
24
+ agentshim/codex/events.py,sha256=I4u5Uug1ku12a44gouBNJgZGVJdCNx2o8xCQFUZi3Rg,7602
25
+ agentshim/gemini/__init__.py,sha256=HYgiOZwekbPGaTn6aQURuLa7ZIoUmienU_Tr4apDdsI,122
26
+ agentshim/gemini/agent.py,sha256=DlJn2YdlLfkRbZhyjyhURJOhzOWE0v1SWlMgqhG434o,8500
27
+ agentshim/gemini/events.py,sha256=Mj_poV6MzCAWRjWLD9-GJ_U3VbZGdzdEPD7jsC5IsnY,2727
28
+ agentshim/opencode/__init__.py,sha256=mQPn-hR4lIIxjKlrdc0byhdqlD9f0ST-UkKK_1vedHk,130
29
+ agentshim/opencode/agent.py,sha256=I9-vqXxgi6eUNOxgFsBsTa7jEW7_r6gfpu4kTVlV35w,7103
30
+ agentshim/opencode/events.py,sha256=svYi3zP9cziaUPnKlxCH3pdj41qUcvOP2FXb0qOhvks,3027
31
+ agentshim-0.1.0.dist-info/METADATA,sha256=FgjWfvVSOLZrUoU3OtDEj-f0vFJzxEp0RIj8JlLJYrE,1167
32
+ agentshim-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
33
+ agentshim-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any