python-agent-harness 1.5.5.8__tar.gz → 1.5.6.0__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.
- {python_agent_harness-1.5.5.8/python_agent_harness.egg-info → python_agent_harness-1.5.6.0}/PKG-INFO +102 -32
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/README.md +100 -30
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/pyproject.toml +7 -7
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/__init__.py +7 -4
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/__main__.py +1 -1
- python_agent_harness-1.5.6.0/python_agent_harness/core/__init__.py +1 -0
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/core}/agent.py +95 -8
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/core}/context_manager.py +3 -3
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/core}/models.py +75 -3
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/core}/planmode.py +1 -1
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/core}/subagent.py +15 -5
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/core}/token_estimator.py +34 -2
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/core}/tool_runner.py +2 -2
- python_agent_harness-1.5.6.0/python_agent_harness/entry/__init__.py +1 -0
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/entry}/cli.py +200 -21
- python_agent_harness-1.5.6.0/python_agent_harness/entry/controller.py +379 -0
- python_agent_harness-1.5.6.0/python_agent_harness/entry/headless.py +508 -0
- python_agent_harness-1.5.6.0/python_agent_harness/entry/server.py +534 -0
- python_agent_harness-1.5.6.0/python_agent_harness/entry/view.py +51 -0
- python_agent_harness-1.5.6.0/python_agent_harness/io/__init__.py +1 -0
- python_agent_harness-1.5.6.0/python_agent_harness/io/attachments.py +377 -0
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/io}/persistence.py +98 -1
- python_agent_harness-1.5.6.0/python_agent_harness/io/text_filter.py +84 -0
- python_agent_harness-1.5.6.0/python_agent_harness/llm/__init__.py +1 -0
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/llm}/client.py +105 -17
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/lsp/manager.py +1 -1
- python_agent_harness-1.5.6.0/python_agent_harness/prompts/__init__.py +41 -0
- python_agent_harness-1.5.6.0/python_agent_harness/prompts/agent.md +113 -0
- python_agent_harness-1.5.5.8/python_agent_harness/prompts.py → python_agent_harness-1.5.6.0/python_agent_harness/prompts/core.py +108 -10
- python_agent_harness-1.5.6.0/python_agent_harness/prompts/subagent.md +55 -0
- python_agent_harness-1.5.6.0/python_agent_harness/session/__init__.py +33 -0
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/session}/commands.py +3 -3
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/session}/config.py +51 -19
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/session}/session.py +107 -53
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/__init__.py +4 -16
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/agent_tool.py +45 -7
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/base.py +23 -5
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/bash.py +35 -5
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/diffapply.py +9 -3
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/edit.py +25 -4
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/edit_mac.py +1 -1
- python_agent_harness-1.5.6.0/python_agent_harness/tools/filesystem.py +367 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/glob.py +21 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/glob_mac.py +1 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/grep.py +22 -1
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/grep_mac.py +1 -1
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/insert.py +21 -4
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/lsp.py +23 -2
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/mkdir.py +1 -1
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/question.py +19 -1
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/read.py +21 -1
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/skill.py +1 -0
- python_agent_harness-1.5.6.0/python_agent_harness/tools/todo.py +110 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/write.py +33 -9
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tui/__init__.py +12 -4
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tui/commands.py +140 -146
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tui/core.py +95 -83
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tui/input.py +102 -39
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tui/render.py +64 -94
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0/python_agent_harness.egg-info}/PKG-INFO +102 -32
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness.egg-info/SOURCES.txt +28 -42
- python_agent_harness-1.5.6.0/python_agent_harness.egg-info/entry_points.txt +2 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness.egg-info/requires.txt +1 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/tests/test_invariants.py +19 -14
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/tests/test_scenarios.py +20 -19
- python_agent_harness-1.5.5.8/python_agent_harness/prompts/agent.md +0 -385
- python_agent_harness-1.5.5.8/python_agent_harness/prompts/subagent.md +0 -231
- python_agent_harness-1.5.5.8/python_agent_harness/tools/bash_win.py +0 -232
- python_agent_harness-1.5.5.8/python_agent_harness/tools/edit_win.py +0 -56
- python_agent_harness-1.5.5.8/python_agent_harness/tools/filesystem.py +0 -190
- python_agent_harness-1.5.5.8/python_agent_harness/tools/glob_win.py +0 -90
- python_agent_harness-1.5.5.8/python_agent_harness/tools/grep_win.py +0 -141
- python_agent_harness-1.5.5.8/python_agent_harness/tools/todo.py +0 -60
- python_agent_harness-1.5.5.8/python_agent_harness.egg-info/entry_points.txt +0 -2
- python_agent_harness-1.5.5.8/tests/test_agents_md.py +0 -260
- python_agent_harness-1.5.5.8/tests/test_cli.py +0 -483
- python_agent_harness-1.5.5.8/tests/test_client.py +0 -1952
- python_agent_harness-1.5.5.8/tests/test_commands.py +0 -119
- python_agent_harness-1.5.5.8/tests/test_concurrent_subagents.py +0 -384
- python_agent_harness-1.5.5.8/tests/test_config.py +0 -745
- python_agent_harness-1.5.5.8/tests/test_context_rules.py +0 -133
- python_agent_harness-1.5.5.8/tests/test_custom_agents.py +0 -629
- python_agent_harness-1.5.5.8/tests/test_diffrender.py +0 -68
- python_agent_harness-1.5.5.8/tests/test_filesystem.py +0 -2179
- python_agent_harness-1.5.5.8/tests/test_lsp.py +0 -984
- python_agent_harness-1.5.5.8/tests/test_mcp.py +0 -826
- python_agent_harness-1.5.5.8/tests/test_models.py +0 -130
- python_agent_harness-1.5.5.8/tests/test_persistence.py +0 -357
- python_agent_harness-1.5.5.8/tests/test_planmode.py +0 -243
- python_agent_harness-1.5.5.8/tests/test_prompts.py +0 -444
- python_agent_harness-1.5.5.8/tests/test_session.py +0 -1153
- python_agent_harness-1.5.5.8/tests/test_subagent.py +0 -175
- python_agent_harness-1.5.5.8/tests/test_subagent_isolation.py +0 -459
- python_agent_harness-1.5.5.8/tests/test_todos_scope.py +0 -117
- python_agent_harness-1.5.5.8/tests/test_token_estimator.py +0 -148
- python_agent_harness-1.5.5.8/tests/test_tools_misc.py +0 -563
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/LICENSE +0 -0
- {python_agent_harness-1.5.5.8/python_agent_harness → python_agent_harness-1.5.6.0/python_agent_harness/io}/diffrender.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/lsp/__init__.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/lsp/client.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/lsp/config.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/mcp/__init__.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/mcp/client.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/mcp/config.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/mcp/manager.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/agents/reviewer.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/build-switch.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/commands/explain.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/compact.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/initialize.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/plan-mode.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/plan.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/review.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/summary.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/task-completion-rules.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/prompts/title.md +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/mcp.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/tools/planexit.py +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness.egg-info/dependency_links.txt +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness.egg-info/top_level.txt +0 -0
- {python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/setup.cfg +0 -0
{python_agent_harness-1.5.5.8/python_agent_harness.egg-info → python_agent_harness-1.5.6.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-agent-harness
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.6.0
|
|
4
4
|
Summary: A lightweight, hackable mini-OpenCode written in Python.
|
|
5
5
|
Author-email: Huming Chen <chenhuming@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,7 +9,6 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
9
9
|
Classifier: Environment :: Console
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Operating System :: MacOS
|
|
12
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
13
12
|
Classifier: Operating System :: POSIX :: Linux
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -20,6 +19,7 @@ Description-Content-Type: text/markdown
|
|
|
20
19
|
License-File: LICENSE
|
|
21
20
|
Requires-Dist: rich>=13.0
|
|
22
21
|
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: certifi>=2024.0
|
|
23
23
|
Requires-Dist: prompt_toolkit>=3.0
|
|
24
24
|
Provides-Extra: mcp
|
|
25
25
|
Requires-Dist: mcp<3,>=2.0; extra == "mcp"
|
|
@@ -105,6 +105,7 @@ Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_
|
|
|
105
105
|
|
|
106
106
|
- **FSM-driven execution** — explicit `WAIT` / `TOOL` / `TRET` / `SUPERVISE` / `DONE` / `ERRS` / `ABRT` states. Completion supervision nudges the model when it stops early, while failed tool calls are sanitized so they never strand the agent. Transient API failures (`429` / `5xx`) retry with exponential backoff and jitter. Auth-expired status codes (configurable via `AUTH_REFRESH_STATUS_CODES` in `config.py`; defaults to `[401, 502]`) trigger automatic API key re-read from config/env — some API gateways return `502` instead of `401` when the backend auth token has expired. Note: codes in this list are treated as auth-expired exclusively and will not be retried with backoff, so only include codes that are unambiguously auth-related in your environment.
|
|
107
107
|
- **Context management** — CJK-aware token estimation, per-model context windows, and automatic compaction at 70% usage.
|
|
108
|
+
- **Image & text attachments** — attach an image or a text file with `@path` in your message (e.g. `@screenshot.png`, `@README.md`): images (PNG/JPEG/GIF/WebP, up to 20 MB, magic-byte validated) become multimodal `image_url` parts, text files are inlined as text. Only images and text files are supported as input. Path supports web-url for image as well.
|
|
108
109
|
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `Insert`, `Edit` (including unified diffs), `Write`, `Mkdir`, `Bash`, `Skill`, `Question`, `LSP`, and `PlanExit`. Synchronous tools execute sequentially, but a round made up entirely of read-only tools (`Read`, `Glob`, `Grep`, `Skill`, `LSP`) is dispatched concurrently via a bounded thread pool; asynchronous tools such as `Bash` and `Agent` can run concurrently as well. Results are always delivered in the model's emitted order.
|
|
109
110
|
- **Plan / Build modes** — plan mode is read-only except for the per-session plan file.
|
|
110
111
|
- **Persistent sessions** — sessions are automatically saved after every response to `~/.local/share/python-agent-harness/sessions/`, with LLM-generated titles and support for `/restore --latest` and `/sessions`.
|
|
@@ -112,6 +113,7 @@ Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_
|
|
|
112
113
|
- **MCP support** — optional MCP integration through the `[mcp]` extra. MCP tools become ordinary agent tools such as `mcp__<server>__<tool>`. Supports `stdio`, `streamable-http`, and `sse` transports.
|
|
113
114
|
- **Slash commands** — built-in `/init`, `/review`, `/explain`, and other commands, plus custom commands loaded from `prompts/commands/*.md`.
|
|
114
115
|
- **Custom agents** — switch the main agent's system prompt at runtime with `/agent`. Agent prompt files live in `prompts/agents/*.md`. Use `default_agent` in the config file to start sessions with a specific agent.
|
|
116
|
+
- **Embeddable runtime boundaries** — drive the agent from programs: `headless --json` for one-shot CI/scripting (write-only JSONL stream), `serve` for hosting apps (resident process, bidirectional protocol: multi-turn memory, mid-run Q&A, protocol-level cancel).
|
|
115
117
|
|
|
116
118
|
## Inspired by opencode
|
|
117
119
|
|
|
@@ -145,7 +147,8 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
145
147
|
"api_key": "sk-...",
|
|
146
148
|
"model": "gpt-5-mini",
|
|
147
149
|
"reasoning_effort": null,
|
|
148
|
-
"stream": true
|
|
150
|
+
"stream": true,
|
|
151
|
+
"supports_image_input": false
|
|
149
152
|
},
|
|
150
153
|
"models": {
|
|
151
154
|
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
|
|
@@ -171,7 +174,8 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
171
174
|
"max_tokens": null,
|
|
172
175
|
"timeout": null,
|
|
173
176
|
"reasoning_effort": null,
|
|
174
|
-
"stream": null
|
|
177
|
+
"stream": null,
|
|
178
|
+
"supports_image_input": null
|
|
175
179
|
},
|
|
176
180
|
"default_agent": null,
|
|
177
181
|
"paths": {
|
|
@@ -210,7 +214,7 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
210
214
|
|
|
211
215
|
### Configuration options
|
|
212
216
|
|
|
213
|
-
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, and `
|
|
217
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, `stream`, and `supports_image_input`. Values such as `reasoning_effort` are passed to the API as-is when set. `run --no-stream` overrides `stream`. `supports_image_input` (default `false`) controls whether `@path` image attachments are sent to the model or stripped with a warning.
|
|
214
218
|
- **`models`** — named LLM profiles for runtime switching with `/model`. A profile is a partial settings dictionary; unset keys inherit from the main `llm`. `default` restores the main LLM configuration.
|
|
215
219
|
- **`context_windows`** — optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g., `deepseek-v4`); matched in file order, first match wins. Overrides the built-in `CONTEXT_WINDOWS` table in `config.py`.
|
|
216
220
|
- **`subagent_llm`** — LLM configuration for `Agent` tool requests. Unset values inherit from the main `llm`. Set `profile` to reuse a profile from `models`. Precedence is: profile settings > explicit `subagent_llm` settings > main `llm` > environment variables.
|
|
@@ -225,10 +229,100 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
225
229
|
## Usage
|
|
226
230
|
|
|
227
231
|
```sh
|
|
228
|
-
python-agent-harness run [project
|
|
232
|
+
python-agent-harness run [--project DIR]
|
|
229
233
|
```
|
|
230
234
|
|
|
231
|
-
Launches the interactive TUI agent. If
|
|
235
|
+
Launches the interactive TUI agent. If `--project` is omitted, the current directory is used.
|
|
236
|
+
|
|
237
|
+
### Headless mode
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
python-agent-harness headless [prompt] [--project DIR] [--restore [SPEC]] [--model NAME]
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Runs a single prompt without the TUI — for CI, scripting, and piping.
|
|
244
|
+
The assistant's answer is written to stdout once the run completes;
|
|
245
|
+
tool/status events go to stderr, so the answer stream stays clean.
|
|
246
|
+
Interactive prompts are auto-answered (`confirm` → yes, `ask` →
|
|
247
|
+
"Unanswered"), so a run never blocks.
|
|
248
|
+
|
|
249
|
+
- The prompt is a positional argument; when omitted it is read from stdin:
|
|
250
|
+
|
|
251
|
+
```sh
|
|
252
|
+
python-agent-harness headless "fix the failing test"
|
|
253
|
+
echo "fix the failing test" | python-agent-harness headless
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- `--restore` continues a saved session (the conversation auto-saves after
|
|
257
|
+
each response). Bare `--restore` uses the most recent session; a SPEC may
|
|
258
|
+
be a file path or a title substring:
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
python-agent-harness headless "now add tests" --restore
|
|
262
|
+
python-agent-harness headless "next step" --restore parser-refactor
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- `--model NAME` selects the model: a profile from the `models` config
|
|
266
|
+
section (same as the TUI's `/model`), or a raw model name on the
|
|
267
|
+
configured endpoint when no such profile exists. An explicit `--model`
|
|
268
|
+
wins over the model restored by `--restore`.
|
|
269
|
+
|
|
270
|
+
- `--json` emits the run as JSON lines on stdout instead of plain text —
|
|
271
|
+
one `{"type": ...}` object per line: `start` (echoes the prompt and
|
|
272
|
+
submit warnings), `delta` (streamed text chunks), `notify` (tool and
|
|
273
|
+
status events, with `kind`/`data`), `log`, and a final `result` (the
|
|
274
|
+
filtered answer plus any `errors`). Diagnostics (restore/model notes,
|
|
275
|
+
a plain-text echo of error events) still go to stderr, so one pipe
|
|
276
|
+
carries the structured stream. Exit codes are unchanged.
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
python-agent-harness headless "fix it" --json | jq -c 'select(.type=="result")'
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- Exit code is 0 on success, 1 when the prompt was empty (only failed
|
|
283
|
+
`@file` references), the run raised an agent error, or the restore failed.
|
|
284
|
+
|
|
285
|
+
### Serve mode (resident JSONL server)
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
python-agent-harness serve [--project DIR] [--answer-timeout SECONDS]
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
A persistent, bidirectional runtime boundary for hosting applications
|
|
292
|
+
(a web backend, an IDE, a CI driver). Unlike `headless --json` (one
|
|
293
|
+
prompt per process, write-only stream), `serve` keeps the
|
|
294
|
+
`Controller`/`Session` resident and speaks a request/response protocol
|
|
295
|
+
over stdin/stdout — the same process boundary (containerizable), but
|
|
296
|
+
the host can:
|
|
297
|
+
|
|
298
|
+
- submit multiple prompts over the process's lifetime — no per-turn
|
|
299
|
+
interpreter spawn, and conversation history is retained between them
|
|
300
|
+
(multi-turn memory);
|
|
301
|
+
- answer the agent's mid-run questions (the `Question` tool and
|
|
302
|
+
plan-exit confirmation) via an `answer` op;
|
|
303
|
+
- cancel a run as a protocol message (no signal semantics).
|
|
304
|
+
|
|
305
|
+
Protocol (one JSON object per line):
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
host → agent: {"op": "submit", "prompt": ..., "run_id": ...}
|
|
309
|
+
{"op": "answer", "run_id": ..., "answers": [...]}
|
|
310
|
+
{"op": "cancel", "run_id": ...}
|
|
311
|
+
{"op": "ping"} | {"op": "shutdown"}
|
|
312
|
+
agent → host: {"type": "ready"} first line
|
|
313
|
+
{"seq": N, "type": "start"|"delta"|"notify"|"log", "run_id": ...}
|
|
314
|
+
{"seq": N, "type": "result", "run_id": ..., "answer": ...,
|
|
315
|
+
"errors": [...], "usage": {...}, "cancelled": bool}
|
|
316
|
+
{"type": "error", "error": ...} protocol failures
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
A mid-run question arrives as a `notify` with `kind: "ask"` (data has
|
|
320
|
+
`kind: "ask"|"confirm"`); reply with `answer`. One run at a time; a
|
|
321
|
+
`submit` while one is active is rejected with an `error` line.
|
|
322
|
+
`--answer-timeout SECONDS` bounds how long a pending question waits
|
|
323
|
+
for the host's answer (default: forever). The result line's shape is
|
|
324
|
+
identical to `headless --json`'s, so a driver can speak both
|
|
325
|
+
protocols with one parser.
|
|
232
326
|
|
|
233
327
|
### Slash commands
|
|
234
328
|
|
|
@@ -282,33 +376,9 @@ You are a personal assistant. You do NOT modify files or run shell commands.
|
|
|
282
376
|
| **Tools** | Can restrict (`allow_planexit=False`) | All tools, minus the agent's `exclude_tools` |
|
|
283
377
|
| **Ctrl-C** | Restores to default prompt | Stays on the custom agent |
|
|
284
378
|
|
|
285
|
-
## Project layout
|
|
286
|
-
|
|
287
|
-
```text
|
|
288
|
-
python_agent_harness/
|
|
289
|
-
├── agent.py # Agent FSM core: states, transitions, supervision
|
|
290
|
-
├── tool_runner.py # Tool-call execution/delivery + history salvage
|
|
291
|
-
├── context_manager.py # Context-ratio tracking + compaction
|
|
292
|
-
├── client.py # OpenAI-compatible streaming client (httpx)
|
|
293
|
-
├── models.py # Message / ToolCall / ToolSpec data classes
|
|
294
|
-
├── token_estimator.py # CJK-aware token estimation + calibration
|
|
295
|
-
├── planmode.py # Plan/build modes + plan-file lifecycle
|
|
296
|
-
├── prompts.py # Prompt loading + system-prompt assembly
|
|
297
|
-
├── persistence.py # Session persistence + titles
|
|
298
|
-
├── session.py # Session wiring hub + MCP lifecycle
|
|
299
|
-
├── subagent.py # Sub-agent runner + error containment
|
|
300
|
-
├── commands.py # Init/review/custom command definitions
|
|
301
|
-
├── cli.py # CLI entry points
|
|
302
|
-
├── tui/ # Rich + prompt_toolkit TUI (package)
|
|
303
|
-
├── diffrender.py # Unified diff generation + Rich rendering
|
|
304
|
-
├── lsp/ # Built-in LSP client
|
|
305
|
-
├── mcp/ # Optional MCP client
|
|
306
|
-
└── tools/ # Tool implementations + registry
|
|
307
|
-
```
|
|
308
|
-
|
|
309
379
|
## Development
|
|
310
380
|
|
|
311
|
-
Requires Python ≥ 3.11. CI runs against Python 3.11, 3.12, and 3.13 on Linux
|
|
381
|
+
Requires Python ≥ 3.11. CI runs against Python 3.11, 3.12, and 3.13 on Linux and MacOS.
|
|
312
382
|
|
|
313
383
|
```sh
|
|
314
384
|
make test # unit tests
|
|
@@ -71,6 +71,7 @@ Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_
|
|
|
71
71
|
|
|
72
72
|
- **FSM-driven execution** — explicit `WAIT` / `TOOL` / `TRET` / `SUPERVISE` / `DONE` / `ERRS` / `ABRT` states. Completion supervision nudges the model when it stops early, while failed tool calls are sanitized so they never strand the agent. Transient API failures (`429` / `5xx`) retry with exponential backoff and jitter. Auth-expired status codes (configurable via `AUTH_REFRESH_STATUS_CODES` in `config.py`; defaults to `[401, 502]`) trigger automatic API key re-read from config/env — some API gateways return `502` instead of `401` when the backend auth token has expired. Note: codes in this list are treated as auth-expired exclusively and will not be retried with backoff, so only include codes that are unambiguously auth-related in your environment.
|
|
73
73
|
- **Context management** — CJK-aware token estimation, per-model context windows, and automatic compaction at 70% usage.
|
|
74
|
+
- **Image & text attachments** — attach an image or a text file with `@path` in your message (e.g. `@screenshot.png`, `@README.md`): images (PNG/JPEG/GIF/WebP, up to 20 MB, magic-byte validated) become multimodal `image_url` parts, text files are inlined as text. Only images and text files are supported as input. Path supports web-url for image as well.
|
|
74
75
|
- **Coding tools** — `Agent`, `TodoWrite`, `Glob`, `Grep`, `Read`, `Insert`, `Edit` (including unified diffs), `Write`, `Mkdir`, `Bash`, `Skill`, `Question`, `LSP`, and `PlanExit`. Synchronous tools execute sequentially, but a round made up entirely of read-only tools (`Read`, `Glob`, `Grep`, `Skill`, `LSP`) is dispatched concurrently via a bounded thread pool; asynchronous tools such as `Bash` and `Agent` can run concurrently as well. Results are always delivered in the model's emitted order.
|
|
75
76
|
- **Plan / Build modes** — plan mode is read-only except for the per-session plan file.
|
|
76
77
|
- **Persistent sessions** — sessions are automatically saved after every response to `~/.local/share/python-agent-harness/sessions/`, with LLM-generated titles and support for `/restore --latest` and `/sessions`.
|
|
@@ -78,6 +79,7 @@ Edit `~/.config/python-agent-harness/config.json` and set your `base_url`, `api_
|
|
|
78
79
|
- **MCP support** — optional MCP integration through the `[mcp]` extra. MCP tools become ordinary agent tools such as `mcp__<server>__<tool>`. Supports `stdio`, `streamable-http`, and `sse` transports.
|
|
79
80
|
- **Slash commands** — built-in `/init`, `/review`, `/explain`, and other commands, plus custom commands loaded from `prompts/commands/*.md`.
|
|
80
81
|
- **Custom agents** — switch the main agent's system prompt at runtime with `/agent`. Agent prompt files live in `prompts/agents/*.md`. Use `default_agent` in the config file to start sessions with a specific agent.
|
|
82
|
+
- **Embeddable runtime boundaries** — drive the agent from programs: `headless --json` for one-shot CI/scripting (write-only JSONL stream), `serve` for hosting apps (resident process, bidirectional protocol: multi-turn memory, mid-run Q&A, protocol-level cancel).
|
|
81
83
|
|
|
82
84
|
## Inspired by opencode
|
|
83
85
|
|
|
@@ -111,7 +113,8 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
111
113
|
"api_key": "sk-...",
|
|
112
114
|
"model": "gpt-5-mini",
|
|
113
115
|
"reasoning_effort": null,
|
|
114
|
-
"stream": true
|
|
116
|
+
"stream": true,
|
|
117
|
+
"supports_image_input": false
|
|
115
118
|
},
|
|
116
119
|
"models": {
|
|
117
120
|
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
|
|
@@ -137,7 +140,8 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
137
140
|
"max_tokens": null,
|
|
138
141
|
"timeout": null,
|
|
139
142
|
"reasoning_effort": null,
|
|
140
|
-
"stream": null
|
|
143
|
+
"stream": null,
|
|
144
|
+
"supports_image_input": null
|
|
141
145
|
},
|
|
142
146
|
"default_agent": null,
|
|
143
147
|
"paths": {
|
|
@@ -176,7 +180,7 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
176
180
|
|
|
177
181
|
### Configuration options
|
|
178
182
|
|
|
179
|
-
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, and `
|
|
183
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, `stream`, and `supports_image_input`. Values such as `reasoning_effort` are passed to the API as-is when set. `run --no-stream` overrides `stream`. `supports_image_input` (default `false`) controls whether `@path` image attachments are sent to the model or stripped with a warning.
|
|
180
184
|
- **`models`** — named LLM profiles for runtime switching with `/model`. A profile is a partial settings dictionary; unset keys inherit from the main `llm`. `default` restores the main LLM configuration.
|
|
181
185
|
- **`context_windows`** — optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g., `deepseek-v4`); matched in file order, first match wins. Overrides the built-in `CONTEXT_WINDOWS` table in `config.py`.
|
|
182
186
|
- **`subagent_llm`** — LLM configuration for `Agent` tool requests. Unset values inherit from the main `llm`. Set `profile` to reuse a profile from `models`. Precedence is: profile settings > explicit `subagent_llm` settings > main `llm` > environment variables.
|
|
@@ -191,10 +195,100 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
191
195
|
## Usage
|
|
192
196
|
|
|
193
197
|
```sh
|
|
194
|
-
python-agent-harness run [project
|
|
198
|
+
python-agent-harness run [--project DIR]
|
|
195
199
|
```
|
|
196
200
|
|
|
197
|
-
Launches the interactive TUI agent. If
|
|
201
|
+
Launches the interactive TUI agent. If `--project` is omitted, the current directory is used.
|
|
202
|
+
|
|
203
|
+
### Headless mode
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
python-agent-harness headless [prompt] [--project DIR] [--restore [SPEC]] [--model NAME]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Runs a single prompt without the TUI — for CI, scripting, and piping.
|
|
210
|
+
The assistant's answer is written to stdout once the run completes;
|
|
211
|
+
tool/status events go to stderr, so the answer stream stays clean.
|
|
212
|
+
Interactive prompts are auto-answered (`confirm` → yes, `ask` →
|
|
213
|
+
"Unanswered"), so a run never blocks.
|
|
214
|
+
|
|
215
|
+
- The prompt is a positional argument; when omitted it is read from stdin:
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
python-agent-harness headless "fix the failing test"
|
|
219
|
+
echo "fix the failing test" | python-agent-harness headless
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- `--restore` continues a saved session (the conversation auto-saves after
|
|
223
|
+
each response). Bare `--restore` uses the most recent session; a SPEC may
|
|
224
|
+
be a file path or a title substring:
|
|
225
|
+
|
|
226
|
+
```sh
|
|
227
|
+
python-agent-harness headless "now add tests" --restore
|
|
228
|
+
python-agent-harness headless "next step" --restore parser-refactor
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- `--model NAME` selects the model: a profile from the `models` config
|
|
232
|
+
section (same as the TUI's `/model`), or a raw model name on the
|
|
233
|
+
configured endpoint when no such profile exists. An explicit `--model`
|
|
234
|
+
wins over the model restored by `--restore`.
|
|
235
|
+
|
|
236
|
+
- `--json` emits the run as JSON lines on stdout instead of plain text —
|
|
237
|
+
one `{"type": ...}` object per line: `start` (echoes the prompt and
|
|
238
|
+
submit warnings), `delta` (streamed text chunks), `notify` (tool and
|
|
239
|
+
status events, with `kind`/`data`), `log`, and a final `result` (the
|
|
240
|
+
filtered answer plus any `errors`). Diagnostics (restore/model notes,
|
|
241
|
+
a plain-text echo of error events) still go to stderr, so one pipe
|
|
242
|
+
carries the structured stream. Exit codes are unchanged.
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
python-agent-harness headless "fix it" --json | jq -c 'select(.type=="result")'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- Exit code is 0 on success, 1 when the prompt was empty (only failed
|
|
249
|
+
`@file` references), the run raised an agent error, or the restore failed.
|
|
250
|
+
|
|
251
|
+
### Serve mode (resident JSONL server)
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
python-agent-harness serve [--project DIR] [--answer-timeout SECONDS]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
A persistent, bidirectional runtime boundary for hosting applications
|
|
258
|
+
(a web backend, an IDE, a CI driver). Unlike `headless --json` (one
|
|
259
|
+
prompt per process, write-only stream), `serve` keeps the
|
|
260
|
+
`Controller`/`Session` resident and speaks a request/response protocol
|
|
261
|
+
over stdin/stdout — the same process boundary (containerizable), but
|
|
262
|
+
the host can:
|
|
263
|
+
|
|
264
|
+
- submit multiple prompts over the process's lifetime — no per-turn
|
|
265
|
+
interpreter spawn, and conversation history is retained between them
|
|
266
|
+
(multi-turn memory);
|
|
267
|
+
- answer the agent's mid-run questions (the `Question` tool and
|
|
268
|
+
plan-exit confirmation) via an `answer` op;
|
|
269
|
+
- cancel a run as a protocol message (no signal semantics).
|
|
270
|
+
|
|
271
|
+
Protocol (one JSON object per line):
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
host → agent: {"op": "submit", "prompt": ..., "run_id": ...}
|
|
275
|
+
{"op": "answer", "run_id": ..., "answers": [...]}
|
|
276
|
+
{"op": "cancel", "run_id": ...}
|
|
277
|
+
{"op": "ping"} | {"op": "shutdown"}
|
|
278
|
+
agent → host: {"type": "ready"} first line
|
|
279
|
+
{"seq": N, "type": "start"|"delta"|"notify"|"log", "run_id": ...}
|
|
280
|
+
{"seq": N, "type": "result", "run_id": ..., "answer": ...,
|
|
281
|
+
"errors": [...], "usage": {...}, "cancelled": bool}
|
|
282
|
+
{"type": "error", "error": ...} protocol failures
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
A mid-run question arrives as a `notify` with `kind: "ask"` (data has
|
|
286
|
+
`kind: "ask"|"confirm"`); reply with `answer`. One run at a time; a
|
|
287
|
+
`submit` while one is active is rejected with an `error` line.
|
|
288
|
+
`--answer-timeout SECONDS` bounds how long a pending question waits
|
|
289
|
+
for the host's answer (default: forever). The result line's shape is
|
|
290
|
+
identical to `headless --json`'s, so a driver can speak both
|
|
291
|
+
protocols with one parser.
|
|
198
292
|
|
|
199
293
|
### Slash commands
|
|
200
294
|
|
|
@@ -248,33 +342,9 @@ You are a personal assistant. You do NOT modify files or run shell commands.
|
|
|
248
342
|
| **Tools** | Can restrict (`allow_planexit=False`) | All tools, minus the agent's `exclude_tools` |
|
|
249
343
|
| **Ctrl-C** | Restores to default prompt | Stays on the custom agent |
|
|
250
344
|
|
|
251
|
-
## Project layout
|
|
252
|
-
|
|
253
|
-
```text
|
|
254
|
-
python_agent_harness/
|
|
255
|
-
├── agent.py # Agent FSM core: states, transitions, supervision
|
|
256
|
-
├── tool_runner.py # Tool-call execution/delivery + history salvage
|
|
257
|
-
├── context_manager.py # Context-ratio tracking + compaction
|
|
258
|
-
├── client.py # OpenAI-compatible streaming client (httpx)
|
|
259
|
-
├── models.py # Message / ToolCall / ToolSpec data classes
|
|
260
|
-
├── token_estimator.py # CJK-aware token estimation + calibration
|
|
261
|
-
├── planmode.py # Plan/build modes + plan-file lifecycle
|
|
262
|
-
├── prompts.py # Prompt loading + system-prompt assembly
|
|
263
|
-
├── persistence.py # Session persistence + titles
|
|
264
|
-
├── session.py # Session wiring hub + MCP lifecycle
|
|
265
|
-
├── subagent.py # Sub-agent runner + error containment
|
|
266
|
-
├── commands.py # Init/review/custom command definitions
|
|
267
|
-
├── cli.py # CLI entry points
|
|
268
|
-
├── tui/ # Rich + prompt_toolkit TUI (package)
|
|
269
|
-
├── diffrender.py # Unified diff generation + Rich rendering
|
|
270
|
-
├── lsp/ # Built-in LSP client
|
|
271
|
-
├── mcp/ # Optional MCP client
|
|
272
|
-
└── tools/ # Tool implementations + registry
|
|
273
|
-
```
|
|
274
|
-
|
|
275
345
|
## Development
|
|
276
346
|
|
|
277
|
-
Requires Python ≥ 3.11. CI runs against Python 3.11, 3.12, and 3.13 on Linux
|
|
347
|
+
Requires Python ≥ 3.11. CI runs against Python 3.11, 3.12, and 3.13 on Linux and MacOS.
|
|
278
348
|
|
|
279
349
|
```sh
|
|
280
350
|
make test # unit tests
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-agent-harness"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.6.0"
|
|
8
8
|
description = "A lightweight, hackable mini-OpenCode written in Python."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Huming Chen", email = "chenhuming@gmail.com"}
|
|
@@ -17,7 +17,6 @@ classifiers = [
|
|
|
17
17
|
"Environment :: Console",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Operating System :: MacOS",
|
|
20
|
-
"Operating System :: Microsoft :: Windows",
|
|
21
20
|
"Operating System :: POSIX :: Linux",
|
|
22
21
|
"Programming Language :: Python :: 3.11",
|
|
23
22
|
"Programming Language :: Python :: 3.12",
|
|
@@ -27,6 +26,7 @@ classifiers = [
|
|
|
27
26
|
dependencies = [
|
|
28
27
|
"rich>=13.0",
|
|
29
28
|
"httpx>=0.27",
|
|
29
|
+
"certifi>=2024.0",
|
|
30
30
|
"prompt_toolkit>=3.0",
|
|
31
31
|
]
|
|
32
32
|
|
|
@@ -57,7 +57,7 @@ dev = [
|
|
|
57
57
|
]
|
|
58
58
|
|
|
59
59
|
[project.scripts]
|
|
60
|
-
python-agent-harness = "python_agent_harness.cli:main"
|
|
60
|
+
python-agent-harness = "python_agent_harness.entry.cli:main"
|
|
61
61
|
|
|
62
62
|
[tool.setuptools.packages.find]
|
|
63
63
|
include = ["python_agent_harness*"]
|
|
@@ -90,10 +90,10 @@ source = ["python_agent_harness"]
|
|
|
90
90
|
branch = true
|
|
91
91
|
# The console/TUI entry points aren't exercised by unittest; omit the ones
|
|
92
92
|
# that would otherwise drag the number down without adding signal.
|
|
93
|
-
omit = [
|
|
93
|
+
omit = [
|
|
94
|
+
"python_agent_harness/__main__.py",
|
|
95
|
+
]
|
|
94
96
|
|
|
95
97
|
[tool.coverage.report]
|
|
96
98
|
show_missing = true
|
|
97
|
-
|
|
98
|
-
# small changes while still catching real regressions. Raise as coverage grows.
|
|
99
|
-
fail_under = 90
|
|
99
|
+
fail_under = 95
|
{python_agent_harness-1.5.5.8 → python_agent_harness-1.5.6.0}/python_agent_harness/__init__.py
RENAMED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""python-agent-harness: a Python port of the gptel-agent-harness."""
|
|
2
2
|
|
|
3
|
-
from .
|
|
3
|
+
from .core.models import AgentMode, ImagePart, Message, TextPart, ToolCall, ToolSpec
|
|
4
|
+
from .entry.cli import main # noqa: F401 (public lib surface: main entry point)
|
|
5
|
+
from .llm.client import Client, LLMClient
|
|
4
6
|
from .lsp.config import LSPConfig, LSPServerConfig
|
|
5
7
|
from .mcp.config import MCPConfig, MCPServerConfig
|
|
6
8
|
from .mcp.manager import MCPManager
|
|
7
|
-
from .
|
|
8
|
-
from .session import Session
|
|
9
|
+
from .session.session import Session
|
|
9
10
|
|
|
10
|
-
__version__ = "1.5.
|
|
11
|
+
__version__ = "1.5.6.0"
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
"Client",
|
|
@@ -20,6 +21,8 @@ __all__ = [
|
|
|
20
21
|
"MCPManager",
|
|
21
22
|
"MCPServerConfig",
|
|
22
23
|
"Message",
|
|
24
|
+
"TextPart",
|
|
25
|
+
"ImagePart",
|
|
23
26
|
"ToolCall",
|
|
24
27
|
"ToolSpec",
|
|
25
28
|
"__version__",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core engine: agent loop, tool dispatch, models, and context utilities."""
|