zag-agent 0.2.1__tar.gz

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.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: zag-agent
3
+ Version: 0.2.1
4
+ Summary: Python SDK for zag — a unified CLI for AI coding agents
5
+ Author: Niclas Lindstedt
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/niclaslindstedt/zag
8
+ Project-URL: Repository, https://github.com/niclaslindstedt/zag
9
+ Keywords: zag,ai,agent,claude,codex,gemini,copilot,ollama
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Zag Python Binding
22
+
23
+ Python binding for [zag](https://github.com/niclaslindstedt/zag) — a unified CLI for AI coding agents.
24
+
25
+ ## Prerequisites
26
+
27
+ - Python 3.10+
28
+ - The `zag` CLI binary installed and on your `PATH` (or set via `ZAG_BIN` env var)
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install zag-agent
34
+ ```
35
+
36
+ For development from source:
37
+
38
+ ```bash
39
+ cd bindings/python
40
+ pip install -e .
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ ```python
46
+ from zag import ZagBuilder
47
+
48
+ output = await ZagBuilder() \
49
+ .provider("claude") \
50
+ .model("sonnet") \
51
+ .auto_approve() \
52
+ .exec("write a hello world program")
53
+
54
+ print(output.result)
55
+ ```
56
+
57
+ ## Streaming
58
+
59
+ ```python
60
+ from zag import ZagBuilder
61
+
62
+ async for event in await ZagBuilder().provider("claude").stream("analyze code"):
63
+ print(event.type, event)
64
+ ```
65
+
66
+ ## Builder methods
67
+
68
+ | Method | Description |
69
+ |--------|-------------|
70
+ | `.provider(name)` | Set provider: `"claude"`, `"codex"`, `"gemini"`, `"copilot"`, `"ollama"` |
71
+ | `.model(name)` | Set model name or size alias (`"small"`, `"medium"`, `"large"`) |
72
+ | `.system_prompt(text)` | Set a system prompt |
73
+ | `.root(path)` | Set the working directory |
74
+ | `.auto_approve()` | Skip permission prompts |
75
+ | `.add_dir(path)` | Add an additional directory (chainable) |
76
+ | `.json_mode()` | Request JSON output |
77
+ | `.json_schema(schema)` | Validate output against a JSON schema (implies `.json_mode()`) |
78
+ | `.json_stream()` | Enable streaming NDJSON output |
79
+ | `.worktree(name=None)` | Run in an isolated git worktree |
80
+ | `.sandbox(name=None)` | Run in a Docker sandbox |
81
+ | `.session_id(uuid)` | Use a specific session ID |
82
+ | `.output_format(fmt)` | Set output format (`"text"`, `"json"`, `"json-pretty"`, `"stream-json"`) |
83
+ | `.input_format(fmt)` | Set input format (`"text"`, `"stream-json"` — Claude only) |
84
+ | `.replay_user_messages()` | Re-emit user messages on stdout (Claude only) |
85
+ | `.include_partial_messages()` | Include partial message chunks (Claude only) |
86
+ | `.max_turns(n)` | Set the maximum number of agentic turns |
87
+ | `.show_usage()` | Show token usage statistics (JSON output mode) |
88
+ | `.size(size)` | Set Ollama model parameter size (e.g., `"2b"`, `"9b"`, `"35b"`) |
89
+ | `.verbose()` | Enable verbose output |
90
+ | `.quiet()` | Suppress non-essential output |
91
+ | `.debug()` | Enable debug logging |
92
+ | `.bin(path)` | Override the `zag` binary path |
93
+
94
+ ## Terminal methods
95
+
96
+ | Method | Returns | Description |
97
+ |--------|---------|-------------|
98
+ | `.exec(prompt)` | `AgentOutput` | Run non-interactively, return structured output |
99
+ | `.stream(prompt)` | `AsyncGenerator[Event]` | Stream NDJSON events |
100
+ | `.exec_streaming(prompt)` | `StreamingSession` | Bidirectional streaming (Claude only) |
101
+ | `.run(prompt=None)` | `None` | Start an interactive session (inherits stdio) |
102
+ | `.resume(session_id)` | `None` | Resume a previous session by ID |
103
+ | `.continue_last()` | `None` | Resume the most recent session |
104
+
105
+ ## How it works
106
+
107
+ The SDK spawns the `zag` CLI as a subprocess (`zag exec -o json` or `-o stream-json`) and parses the JSON/NDJSON output into typed dataclasses. Zero external dependencies — only the Python standard library.
108
+
109
+ ## Testing
110
+
111
+ ```bash
112
+ pip install pytest pytest-asyncio
113
+ pytest
114
+ ```
115
+
116
+ ## See also
117
+
118
+ - [TypeScript SDK](../typescript/README.md)
119
+ - [C# SDK](../csharp/README.md)
120
+ - [Rust API (zag-agent)](../../zag-agent/README.md)
121
+ - [All bindings](../README.md)
122
+
123
+ ## License
124
+
125
+ [MIT](../../LICENSE)
@@ -0,0 +1,105 @@
1
+ # Zag Python Binding
2
+
3
+ Python binding for [zag](https://github.com/niclaslindstedt/zag) — a unified CLI for AI coding agents.
4
+
5
+ ## Prerequisites
6
+
7
+ - Python 3.10+
8
+ - The `zag` CLI binary installed and on your `PATH` (or set via `ZAG_BIN` env var)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install zag-agent
14
+ ```
15
+
16
+ For development from source:
17
+
18
+ ```bash
19
+ cd bindings/python
20
+ pip install -e .
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ```python
26
+ from zag import ZagBuilder
27
+
28
+ output = await ZagBuilder() \
29
+ .provider("claude") \
30
+ .model("sonnet") \
31
+ .auto_approve() \
32
+ .exec("write a hello world program")
33
+
34
+ print(output.result)
35
+ ```
36
+
37
+ ## Streaming
38
+
39
+ ```python
40
+ from zag import ZagBuilder
41
+
42
+ async for event in await ZagBuilder().provider("claude").stream("analyze code"):
43
+ print(event.type, event)
44
+ ```
45
+
46
+ ## Builder methods
47
+
48
+ | Method | Description |
49
+ |--------|-------------|
50
+ | `.provider(name)` | Set provider: `"claude"`, `"codex"`, `"gemini"`, `"copilot"`, `"ollama"` |
51
+ | `.model(name)` | Set model name or size alias (`"small"`, `"medium"`, `"large"`) |
52
+ | `.system_prompt(text)` | Set a system prompt |
53
+ | `.root(path)` | Set the working directory |
54
+ | `.auto_approve()` | Skip permission prompts |
55
+ | `.add_dir(path)` | Add an additional directory (chainable) |
56
+ | `.json_mode()` | Request JSON output |
57
+ | `.json_schema(schema)` | Validate output against a JSON schema (implies `.json_mode()`) |
58
+ | `.json_stream()` | Enable streaming NDJSON output |
59
+ | `.worktree(name=None)` | Run in an isolated git worktree |
60
+ | `.sandbox(name=None)` | Run in a Docker sandbox |
61
+ | `.session_id(uuid)` | Use a specific session ID |
62
+ | `.output_format(fmt)` | Set output format (`"text"`, `"json"`, `"json-pretty"`, `"stream-json"`) |
63
+ | `.input_format(fmt)` | Set input format (`"text"`, `"stream-json"` — Claude only) |
64
+ | `.replay_user_messages()` | Re-emit user messages on stdout (Claude only) |
65
+ | `.include_partial_messages()` | Include partial message chunks (Claude only) |
66
+ | `.max_turns(n)` | Set the maximum number of agentic turns |
67
+ | `.show_usage()` | Show token usage statistics (JSON output mode) |
68
+ | `.size(size)` | Set Ollama model parameter size (e.g., `"2b"`, `"9b"`, `"35b"`) |
69
+ | `.verbose()` | Enable verbose output |
70
+ | `.quiet()` | Suppress non-essential output |
71
+ | `.debug()` | Enable debug logging |
72
+ | `.bin(path)` | Override the `zag` binary path |
73
+
74
+ ## Terminal methods
75
+
76
+ | Method | Returns | Description |
77
+ |--------|---------|-------------|
78
+ | `.exec(prompt)` | `AgentOutput` | Run non-interactively, return structured output |
79
+ | `.stream(prompt)` | `AsyncGenerator[Event]` | Stream NDJSON events |
80
+ | `.exec_streaming(prompt)` | `StreamingSession` | Bidirectional streaming (Claude only) |
81
+ | `.run(prompt=None)` | `None` | Start an interactive session (inherits stdio) |
82
+ | `.resume(session_id)` | `None` | Resume a previous session by ID |
83
+ | `.continue_last()` | `None` | Resume the most recent session |
84
+
85
+ ## How it works
86
+
87
+ The SDK spawns the `zag` CLI as a subprocess (`zag exec -o json` or `-o stream-json`) and parses the JSON/NDJSON output into typed dataclasses. Zero external dependencies — only the Python standard library.
88
+
89
+ ## Testing
90
+
91
+ ```bash
92
+ pip install pytest pytest-asyncio
93
+ pytest
94
+ ```
95
+
96
+ ## See also
97
+
98
+ - [TypeScript SDK](../typescript/README.md)
99
+ - [C# SDK](../csharp/README.md)
100
+ - [Rust API (zag-agent)](../../zag-agent/README.md)
101
+ - [All bindings](../README.md)
102
+
103
+ ## License
104
+
105
+ [MIT](../../LICENSE)
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zag-agent"
7
+ version = "0.2.1"
8
+ description = "Python SDK for zag — a unified CLI for AI coding agents"
9
+ license = "MIT"
10
+ requires-python = ">=3.10"
11
+ readme = "README.md"
12
+ keywords = ["zag", "ai", "agent", "claude", "codex", "gemini", "copilot", "ollama"]
13
+ authors = [
14
+ { name = "Niclas Lindstedt" },
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Libraries",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/niclaslindstedt/zag"
29
+ Repository = "https://github.com/niclaslindstedt/zag"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.pytest.ini_options]
35
+ asyncio_mode = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ """Python SDK for zag — a unified CLI for AI coding agents."""
2
+
3
+ from .builder import ZagBuilder
4
+ from .types import (
5
+ AgentOutput,
6
+ AssistantMessageEvent,
7
+ ContentBlock,
8
+ ErrorEvent,
9
+ Event,
10
+ InitEvent,
11
+ PermissionRequestEvent,
12
+ ResultEvent,
13
+ TextBlock,
14
+ ToolExecutionEvent,
15
+ ToolResult,
16
+ ToolUseBlock,
17
+ Usage,
18
+ ZagError,
19
+ )
20
+
21
+ __all__ = [
22
+ "ZagBuilder",
23
+ "AgentOutput",
24
+ "Usage",
25
+ "Event",
26
+ "InitEvent",
27
+ "AssistantMessageEvent",
28
+ "ToolExecutionEvent",
29
+ "ResultEvent",
30
+ "ErrorEvent",
31
+ "PermissionRequestEvent",
32
+ "ContentBlock",
33
+ "TextBlock",
34
+ "ToolUseBlock",
35
+ "ToolResult",
36
+ "ZagError",
37
+ ]
@@ -0,0 +1,306 @@
1
+ """Fluent builder for configuring and running zag agent sessions.
2
+
3
+ Example::
4
+
5
+ from zag import ZagBuilder
6
+
7
+ output = await ZagBuilder() \\
8
+ .provider("claude") \\
9
+ .model("sonnet") \\
10
+ .auto_approve() \\
11
+ .exec("write a hello world program")
12
+
13
+ print(output.result)
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from collections.abc import AsyncGenerator
20
+
21
+ from .process import default_bin, exec_zag, run_zag, stream_zag, stream_with_input
22
+ from .types import AgentOutput, Event
23
+
24
+
25
+ class ZagBuilder:
26
+ """Fluent builder for configuring and running zag agent sessions."""
27
+
28
+ def __init__(self) -> None:
29
+ self._bin: str = default_bin()
30
+ self._provider: str | None = None
31
+ self._model: str | None = None
32
+ self._system_prompt: str | None = None
33
+ self._root: str | None = None
34
+ self._auto_approve: bool = False
35
+ self._add_dirs: list[str] = []
36
+ self._json: bool = False
37
+ self._json_schema: dict | None = None
38
+ self._json_stream: bool = False
39
+ self._worktree: str | bool | None = None
40
+ self._sandbox: str | bool | None = None
41
+ self._verbose: bool = False
42
+ self._quiet: bool = False
43
+ self._debug: bool = False
44
+ self._session_id: str | None = None
45
+ self._output_format: str | None = None
46
+ self._input_format: str | None = None
47
+ self._replay_user_messages: bool = False
48
+ self._include_partial_messages: bool = False
49
+ self._max_turns: int | None = None
50
+ self._show_usage: bool = False
51
+ self._size: str | None = None
52
+
53
+ # -- Configuration methods -----------------------------------------------
54
+
55
+ def bin(self, path: str) -> ZagBuilder:
56
+ """Override the zag binary path (default: ``ZAG_BIN`` env or ``"zag"``)."""
57
+ self._bin = path
58
+ return self
59
+
60
+ def provider(self, p: str) -> ZagBuilder:
61
+ """Set the provider (e.g., ``"claude"``, ``"codex"``, ``"gemini"``)."""
62
+ self._provider = p
63
+ return self
64
+
65
+ def model(self, m: str) -> ZagBuilder:
66
+ """Set the model (e.g., ``"sonnet"``, ``"opus"``, ``"small"``)."""
67
+ self._model = m
68
+ return self
69
+
70
+ def system_prompt(self, p: str) -> ZagBuilder:
71
+ """Set a system prompt to configure agent behavior."""
72
+ self._system_prompt = p
73
+ return self
74
+
75
+ def root(self, r: str) -> ZagBuilder:
76
+ """Set the root directory for the agent to operate in."""
77
+ self._root = r
78
+ return self
79
+
80
+ def auto_approve(self, a: bool = True) -> ZagBuilder:
81
+ """Enable auto-approve mode (skip permission prompts)."""
82
+ self._auto_approve = a
83
+ return self
84
+
85
+ def add_dir(self, d: str) -> ZagBuilder:
86
+ """Add an additional directory for the agent to include."""
87
+ self._add_dirs.append(d)
88
+ return self
89
+
90
+ def json_mode(self) -> ZagBuilder:
91
+ """Request JSON output from the agent."""
92
+ self._json = True
93
+ return self
94
+
95
+ def json_schema(self, s: dict) -> ZagBuilder:
96
+ """Set a JSON schema for structured output validation. Implies ``json_mode()``."""
97
+ self._json_schema = s
98
+ self._json = True
99
+ return self
100
+
101
+ def json_stream(self) -> ZagBuilder:
102
+ """Enable streaming JSON output (NDJSON format)."""
103
+ self._json_stream = True
104
+ return self
105
+
106
+ def worktree(self, name: str | None = None) -> ZagBuilder:
107
+ """Enable worktree mode with an optional name."""
108
+ self._worktree = name if name is not None else True
109
+ return self
110
+
111
+ def sandbox(self, name: str | None = None) -> ZagBuilder:
112
+ """Enable sandbox mode with an optional name."""
113
+ self._sandbox = name if name is not None else True
114
+ return self
115
+
116
+ def verbose(self, v: bool = True) -> ZagBuilder:
117
+ """Enable verbose output."""
118
+ self._verbose = v
119
+ return self
120
+
121
+ def quiet(self, q: bool = True) -> ZagBuilder:
122
+ """Enable quiet mode."""
123
+ self._quiet = q
124
+ return self
125
+
126
+ def debug(self, d: bool = True) -> ZagBuilder:
127
+ """Enable debug logging."""
128
+ self._debug = d
129
+ return self
130
+
131
+ def session_id(self, id: str) -> ZagBuilder:
132
+ """Pre-set a session ID (UUID)."""
133
+ self._session_id = id
134
+ return self
135
+
136
+ def output_format(self, f: str) -> ZagBuilder:
137
+ """Set the output format (e.g., ``"text"``, ``"json"``, ``"stream-json"``)."""
138
+ self._output_format = f
139
+ return self
140
+
141
+ def input_format(self, f: str) -> ZagBuilder:
142
+ """Set the input format (Claude only)."""
143
+ self._input_format = f
144
+ return self
145
+
146
+ def replay_user_messages(self, r: bool = True) -> ZagBuilder:
147
+ """Re-emit user messages from stdin on stdout (Claude only)."""
148
+ self._replay_user_messages = r
149
+ return self
150
+
151
+ def include_partial_messages(self, i: bool = True) -> ZagBuilder:
152
+ """Include partial message chunks in streaming output (Claude only)."""
153
+ self._include_partial_messages = i
154
+ return self
155
+
156
+ def max_turns(self, n: int) -> ZagBuilder:
157
+ """Set the maximum number of agentic turns."""
158
+ self._max_turns = n
159
+ return self
160
+
161
+ def show_usage(self, s: bool = True) -> ZagBuilder:
162
+ """Show token usage statistics (only applies to JSON output mode)."""
163
+ self._show_usage = s
164
+ return self
165
+
166
+ def size(self, s: str) -> ZagBuilder:
167
+ """Set the Ollama model parameter size (e.g., ``"2b"``, ``"9b"``, ``"35b"``)."""
168
+ self._size = s
169
+ return self
170
+
171
+ # -- Arg building --------------------------------------------------------
172
+
173
+ def _global_args(self) -> list[str]:
174
+ args: list[str] = []
175
+ if self._provider:
176
+ args.extend(["-p", self._provider])
177
+ if self._model:
178
+ args.extend(["--model", self._model])
179
+ if self._system_prompt:
180
+ args.extend(["--system-prompt", self._system_prompt])
181
+ if self._root:
182
+ args.extend(["--root", self._root])
183
+ if self._auto_approve:
184
+ args.append("--auto-approve")
185
+ for d in self._add_dirs:
186
+ args.extend(["--add-dir", d])
187
+ if self._worktree is True:
188
+ args.append("-w")
189
+ elif isinstance(self._worktree, str):
190
+ args.extend(["-w", self._worktree])
191
+ if self._sandbox is True:
192
+ args.append("--sandbox")
193
+ elif isinstance(self._sandbox, str):
194
+ args.extend(["--sandbox", self._sandbox])
195
+ if self._verbose:
196
+ args.append("--verbose")
197
+ if self._quiet:
198
+ args.append("--quiet")
199
+ if self._debug:
200
+ args.append("--debug")
201
+ if self._session_id:
202
+ args.extend(["--session", self._session_id])
203
+ if self._max_turns is not None:
204
+ args.extend(["--max-turns", str(self._max_turns)])
205
+ if self._show_usage:
206
+ args.append("--show-usage")
207
+ if self._size:
208
+ args.extend(["--size", self._size])
209
+ return args
210
+
211
+ def _exec_args(self, prompt: str, *, streaming: bool = False) -> list[str]:
212
+ args = self._global_args()
213
+ args.append("exec")
214
+ if self._json:
215
+ args.append("--json")
216
+ if self._json_schema:
217
+ args.extend(["--json-schema", json.dumps(self._json_schema)])
218
+ if self._json_stream or streaming:
219
+ args.append("--json-stream")
220
+ if self._output_format:
221
+ args.extend(["-o", self._output_format])
222
+ if self._input_format:
223
+ args.extend(["-i", self._input_format])
224
+ if self._replay_user_messages:
225
+ args.append("--replay-user-messages")
226
+ if self._include_partial_messages:
227
+ args.append("--include-partial-messages")
228
+ # Default to json output for structured parsing
229
+ if not streaming and not self._output_format and not self._json_stream:
230
+ args.extend(["-o", "json"])
231
+ args.append(prompt)
232
+ return args
233
+
234
+ # -- Terminal methods ----------------------------------------------------
235
+
236
+ async def exec(self, prompt: str) -> AgentOutput:
237
+ """Run the agent non-interactively and return structured output.
238
+
239
+ Example::
240
+
241
+ output = await ZagBuilder().provider("claude").exec("say hello")
242
+ print(output.result)
243
+ """
244
+ args = self._exec_args(prompt)
245
+ return await exec_zag(self._bin, args)
246
+
247
+ async def exec_streaming(self, prompt: str) -> "StreamingSession":
248
+ """Run the agent with streaming input and output (Claude only).
249
+
250
+ Returns a StreamingSession for bidirectional communication.
251
+
252
+ Example::
253
+
254
+ session = await ZagBuilder().provider("claude").exec_streaming("hello")
255
+ await session.send_user_message("do something")
256
+ async for event in session.events():
257
+ print(event.type)
258
+ await session.wait()
259
+ """
260
+ from .process import StreamingSession as _StreamingSession
261
+
262
+ args = self._global_args()
263
+ args.append("exec")
264
+ args.extend(["-i", "stream-json"])
265
+ args.extend(["-o", "stream-json"])
266
+ args.append("--replay-user-messages")
267
+ if self._include_partial_messages:
268
+ args.append("--include-partial-messages")
269
+ args.append(prompt)
270
+ return await _StreamingSession.create(self._bin, args)
271
+
272
+ async def stream(self, prompt: str) -> AsyncGenerator[Event, None]:
273
+ """Run the agent in streaming mode, yielding events as they arrive.
274
+
275
+ Example::
276
+
277
+ async for event in await ZagBuilder().provider("claude").stream("analyze"):
278
+ print(event.type)
279
+ """
280
+ args = self._exec_args(prompt, streaming=True)
281
+ async for event in stream_zag(self._bin, args):
282
+ yield event
283
+
284
+ async def run(self, prompt: str | None = None) -> None:
285
+ """Start an interactive agent session (inherits stdio)."""
286
+ args = self._global_args()
287
+ args.append("run")
288
+ if self._json:
289
+ args.append("--json")
290
+ if self._json_schema:
291
+ args.extend(["--json-schema", json.dumps(self._json_schema)])
292
+ if prompt:
293
+ args.append(prompt)
294
+ await run_zag(self._bin, args)
295
+
296
+ async def resume(self, session_id: str) -> None:
297
+ """Resume a previous session by ID."""
298
+ args = self._global_args()
299
+ args.extend(["run", "--resume", session_id])
300
+ await run_zag(self._bin, args)
301
+
302
+ async def continue_last(self) -> None:
303
+ """Resume the most recent session."""
304
+ args = self._global_args()
305
+ args.extend(["run", "--continue"])
306
+ await run_zag(self._bin, args)