python-agent-harness 1.5.4.3__tar.gz → 1.5.4.5__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.4.3/python_agent_harness.egg-info → python_agent_harness-1.5.4.5}/PKG-INFO +3 -3
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/README.md +2 -2
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/pyproject.toml +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/__init__.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/agent.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/cli.py +3 -4
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/config.py +6 -9
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/persistence.py +0 -3
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/session.py +1 -8
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/__init__.py +13 -6
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/agent_tool.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/filesystem.py +4 -0
- python_agent_harness-1.5.4.5/python_agent_harness/tools/glob_mac.py +97 -0
- python_agent_harness-1.5.4.5/python_agent_harness/tools/grep_mac.py +92 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5/python_agent_harness.egg-info}/PKG-INFO +3 -3
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness.egg-info/SOURCES.txt +2 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_cli.py +1 -3
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_config.py +16 -6
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_filesystem.py +257 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_persistence.py +12 -15
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_scenarios.py +0 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_session.py +0 -6
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_subagent.py +0 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_subagent_isolation.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/LICENSE +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/__main__.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/client.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/commands.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/context_manager.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/diffrender.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/mcp/__init__.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/mcp/client.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/mcp/config.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/mcp/manager.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/models.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/planmode.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/agent.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/build-switch.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/commands/explain.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/compact.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/initialize.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/plan-mode.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/plan.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/review.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/subagent.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/summary.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/task-completion-rules.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts/title.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/prompts.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/subagent.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/token_estimator.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tool_runner.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/base.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/bash.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/diffapply.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/edit.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/edit_mac.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/glob.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/grep.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/insert.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/mcp.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/mkdir.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/planexit.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/question.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/read.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/skill.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/todo.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/write.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tui/__init__.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tui/commands.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tui/core.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tui/input.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tui/render.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness.egg-info/dependency_links.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness.egg-info/entry_points.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness.egg-info/requires.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness.egg-info/top_level.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/setup.cfg +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_agents_md.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_client.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_commands.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_concurrent_subagents.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_context_rules.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_diffrender.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_invariants.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_mcp.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_models.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_planmode.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_prompts.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_todos_scope.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_token_estimator.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/tests/test_tools_misc.py +0 -0
{python_agent_harness-1.5.4.3/python_agent_harness.egg-info → python_agent_harness-1.5.4.5}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-agent-harness
|
|
3
|
-
Version: 1.5.4.
|
|
3
|
+
Version: 1.5.4.5
|
|
4
4
|
Summary: A lightweight, hackable mini-OpenCode written in Python.
|
|
5
5
|
Author: huming
|
|
6
6
|
License-Expression: MIT
|
|
@@ -186,13 +186,13 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
186
186
|
|
|
187
187
|
### Configuration options
|
|
188
188
|
|
|
189
|
-
- **`llm`** — main LLM configuration. Optional keys include `
|
|
189
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `run --no-stream` overrides `stream`.
|
|
190
190
|
- **`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.
|
|
191
191
|
- **`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`.
|
|
192
192
|
- **`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.
|
|
193
193
|
- **`paths.context_path` / `paths.skill_path`** — locations from which to load context files and skills. When unset, the project-local `<project>/contexts` and `<project>/skills` directories are used.
|
|
194
194
|
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
195
|
-
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL
|
|
195
|
+
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
196
196
|
- **Custom config** — use `--config PATH` or `PYTHON_AGENT_HARNESS_CONFIG`.
|
|
197
197
|
- **LLM logging** — request and response bodies are logged as JSON to `/tmp/python-agent-harness-<date>-<id>.json`. Set `LLM_LOG_DIR` to change the directory. The log path is printed at startup.
|
|
198
198
|
|
|
@@ -163,13 +163,13 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
163
163
|
|
|
164
164
|
### Configuration options
|
|
165
165
|
|
|
166
|
-
- **`llm`** — main LLM configuration. Optional keys include `
|
|
166
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `run --no-stream` overrides `stream`.
|
|
167
167
|
- **`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.
|
|
168
168
|
- **`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`.
|
|
169
169
|
- **`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.
|
|
170
170
|
- **`paths.context_path` / `paths.skill_path`** — locations from which to load context files and skills. When unset, the project-local `<project>/contexts` and `<project>/skills` directories are used.
|
|
171
171
|
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
172
|
-
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL
|
|
172
|
+
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
173
173
|
- **Custom config** — use `--config PATH` or `PYTHON_AGENT_HARNESS_CONFIG`.
|
|
174
174
|
- **LLM logging** — request and response bodies are logged as JSON to `/tmp/python-agent-harness-<date>-<id>.json`. Set `LLM_LOG_DIR` to change the directory. The log path is printed at startup.
|
|
175
175
|
|
|
@@ -452,7 +452,7 @@ class AgentLoop:
|
|
|
452
452
|
)
|
|
453
453
|
# sub-agent runs use their own LLM when one is configured
|
|
454
454
|
# (a per-invocation clone of session.subagent_client,
|
|
455
|
-
# mirroring gptel-agent-harness-subagent-model
|
|
455
|
+
# mirroring gptel-agent-harness-subagent-model);
|
|
456
456
|
# everything unset inherits the main agent's settings, so
|
|
457
457
|
# the sub-agent path is identical when no separate LLM is
|
|
458
458
|
# configured
|
|
@@ -54,8 +54,8 @@ def make_session(
|
|
|
54
54
|
config_path=config_path,
|
|
55
55
|
)
|
|
56
56
|
# A separate client for sub-agent requests only when a different
|
|
57
|
-
# LLM is configured (mirrors gptel-agent-harness-subagent-model
|
|
58
|
-
#
|
|
57
|
+
# LLM is configured (mirrors gptel-agent-harness-subagent-model);
|
|
58
|
+
# otherwise the sub-agent shares the main client.
|
|
59
59
|
subagent_client = None
|
|
60
60
|
if any(
|
|
61
61
|
subagent_settings[k] != settings[k] for k in ("base_url", "api_key", "model", "timeout")
|
|
@@ -101,7 +101,6 @@ def make_session(
|
|
|
101
101
|
project_dir=abs_project,
|
|
102
102
|
client=client,
|
|
103
103
|
model=model,
|
|
104
|
-
backend=settings["backend"],
|
|
105
104
|
system_prompt=system_prompt,
|
|
106
105
|
subagent_system_prompt=subagent_system_prompt,
|
|
107
106
|
temperature=settings["temperature"],
|
|
@@ -176,7 +175,7 @@ def cmd_config(args: argparse.Namespace) -> int:
|
|
|
176
175
|
print(f"config file: {path}")
|
|
177
176
|
if not path.exists():
|
|
178
177
|
print("(file does not exist yet — run `python-agent-harness config --init` to create it)")
|
|
179
|
-
for key in ("base_url", "model"
|
|
178
|
+
for key in ("base_url", "model"):
|
|
180
179
|
print(f"{key}: {settings[key]}")
|
|
181
180
|
print(f"api_key: {config.mask_secret(settings['api_key'])}")
|
|
182
181
|
print(f"temperature: {settings['temperature']}")
|
{python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/config.py
RENAMED
|
@@ -168,7 +168,6 @@ DEFAULT_LLM: dict = {
|
|
|
168
168
|
"base_url": "https://api.openai.com/v1",
|
|
169
169
|
"api_key": None,
|
|
170
170
|
"model": "gpt-5-mini",
|
|
171
|
-
"backend": "OpenAI-compatible",
|
|
172
171
|
"temperature": TEMPERATURE,
|
|
173
172
|
"max_tokens": MAX_TOKENS,
|
|
174
173
|
"timeout": 600.0,
|
|
@@ -182,8 +181,7 @@ DEFAULT_PATHS: dict = {
|
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
# Sub-agent LLM overrides: every key defaults to None, meaning "inherit
|
|
185
|
-
# the main LLM setting" (mirrors gptel-agent-harness-subagent-model
|
|
186
|
-
# -backend). Only the keys the user actually sets differ from the main
|
|
184
|
+
# the main LLM setting" (mirrors gptel-agent-harness-subagent-model).
|
|
187
185
|
# agent's LLM. ``profile`` references a named profile from the
|
|
188
186
|
# ``models`` section: its settings are applied on top of any explicit
|
|
189
187
|
# subagent_llm keys (profile wins), and unset keys still inherit the
|
|
@@ -193,7 +191,6 @@ DEFAULT_SUBAGENT_LLM: dict = {
|
|
|
193
191
|
"base_url": None,
|
|
194
192
|
"api_key": None,
|
|
195
193
|
"model": None,
|
|
196
|
-
"backend": None,
|
|
197
194
|
"temperature": None,
|
|
198
195
|
"max_tokens": None,
|
|
199
196
|
"timeout": None,
|
|
@@ -264,14 +261,12 @@ _ENV_OVERRIDES = {
|
|
|
264
261
|
"base_url": "OPENAI_BASE_URL",
|
|
265
262
|
"api_key": "OPENAI_API_KEY",
|
|
266
263
|
"model": "OPENAI_MODEL",
|
|
267
|
-
"backend": "OPENAI_BACKEND",
|
|
268
264
|
}
|
|
269
265
|
|
|
270
266
|
_SUBAGENT_ENV_OVERRIDES = {
|
|
271
267
|
"base_url": "OPENAI_SUBAGENT_BASE_URL",
|
|
272
268
|
"api_key": "OPENAI_SUBAGENT_API_KEY",
|
|
273
269
|
"model": "OPENAI_SUBAGENT_MODEL",
|
|
274
|
-
"backend": "OPENAI_SUBAGENT_BACKEND",
|
|
275
270
|
}
|
|
276
271
|
|
|
277
272
|
|
|
@@ -320,7 +315,6 @@ def load_llm_config(path: str | os.PathLike | None = None) -> dict:
|
|
|
320
315
|
"base_url",
|
|
321
316
|
"api_key",
|
|
322
317
|
"model",
|
|
323
|
-
"backend",
|
|
324
318
|
"temperature",
|
|
325
319
|
"max_tokens",
|
|
326
320
|
"timeout",
|
|
@@ -342,7 +336,7 @@ def load_subagent_llm_config(
|
|
|
342
336
|
) -> dict:
|
|
343
337
|
"""Resolve sub-agent LLM settings; unset keys inherit ``main``.
|
|
344
338
|
|
|
345
|
-
Mirrors gptel-agent-harness-subagent-model
|
|
339
|
+
Mirrors gptel-agent-harness-subagent-model: sub-agents
|
|
346
340
|
(the Agent tool) use their own LLM when configured, otherwise the
|
|
347
341
|
main agent's. Precedence: ``main`` settings < config file
|
|
348
342
|
``subagent_llm`` object < referenced ``models`` profile (when
|
|
@@ -379,7 +373,6 @@ def load_subagent_llm_config(
|
|
|
379
373
|
"base_url",
|
|
380
374
|
"api_key",
|
|
381
375
|
"model",
|
|
382
|
-
"backend",
|
|
383
376
|
"temperature",
|
|
384
377
|
"max_tokens",
|
|
385
378
|
"timeout",
|
|
@@ -507,6 +500,10 @@ def load_context_windows_config(
|
|
|
507
500
|
raise ValueError(
|
|
508
501
|
f"config file {_config_path(path)}: context_windows.{pattern} must be an integer"
|
|
509
502
|
)
|
|
503
|
+
if size <= 0:
|
|
504
|
+
raise ValueError(
|
|
505
|
+
f"config file {_config_path(path)}: context_windows.{pattern} must be positive"
|
|
506
|
+
)
|
|
510
507
|
entries.append((pattern, size))
|
|
511
508
|
return entries
|
|
512
509
|
|
{python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/persistence.py
RENAMED
|
@@ -107,7 +107,6 @@ class SessionPersistence:
|
|
|
107
107
|
self,
|
|
108
108
|
project_dir: str,
|
|
109
109
|
model: str,
|
|
110
|
-
backend: str,
|
|
111
110
|
system_prompt: str | None = None,
|
|
112
111
|
temperature: float | None = None,
|
|
113
112
|
max_tokens: int | None = None,
|
|
@@ -116,7 +115,6 @@ class SessionPersistence:
|
|
|
116
115
|
) -> None:
|
|
117
116
|
self.project_dir = project_dir
|
|
118
117
|
self.model = model
|
|
119
|
-
self.backend = backend
|
|
120
118
|
self.system_prompt = system_prompt
|
|
121
119
|
self.temperature = temperature
|
|
122
120
|
self.max_tokens = max_tokens
|
|
@@ -173,7 +171,6 @@ class SessionPersistence:
|
|
|
173
171
|
pairs = [
|
|
174
172
|
("python-agent-harness--project-dir", self.project_dir),
|
|
175
173
|
("gptel-model", self.model),
|
|
176
|
-
("gptel--backend-name", self.backend),
|
|
177
174
|
("gptel-system-prompt", self.system_prompt),
|
|
178
175
|
("gptel-temperature", self.temperature),
|
|
179
176
|
("gptel-max-tokens", self.max_tokens),
|
{python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/session.py
RENAMED
|
@@ -71,7 +71,6 @@ class Session:
|
|
|
71
71
|
project_dir: str,
|
|
72
72
|
client: Client,
|
|
73
73
|
model: str,
|
|
74
|
-
backend: str = "OpenAI-compatible",
|
|
75
74
|
system_prompt: str | None = None,
|
|
76
75
|
subagent_system_prompt: str | None = None,
|
|
77
76
|
temperature: float = config.TEMPERATURE,
|
|
@@ -96,7 +95,6 @@ class Session:
|
|
|
96
95
|
self.project_dir = project_dir
|
|
97
96
|
self.client = client
|
|
98
97
|
self.model = model
|
|
99
|
-
self.backend = backend
|
|
100
98
|
self.system_prompt = system_prompt
|
|
101
99
|
self.subagent_system_prompt = subagent_system_prompt
|
|
102
100
|
self.temperature = temperature
|
|
@@ -113,7 +111,7 @@ class Session:
|
|
|
113
111
|
# Sub-agent LLM: a dedicated client (base_url/api_key/model/
|
|
114
112
|
# timeout) and per-request options when a different LLM is
|
|
115
113
|
# configured for sub-agents (mirrors gptel-agent-harness-
|
|
116
|
-
# subagent-model
|
|
114
|
+
# subagent-model); every unset option inherits the
|
|
117
115
|
# main agent's value. The sub-agent loop never uses this
|
|
118
116
|
# client directly — each Agent tool invocation clones it
|
|
119
117
|
# (see run_subagent) so concurrent sub-agents never share a
|
|
@@ -161,7 +159,6 @@ class Session:
|
|
|
161
159
|
self.store = SessionPersistence(
|
|
162
160
|
project_dir=project_dir,
|
|
163
161
|
model=model,
|
|
164
|
-
backend=backend,
|
|
165
162
|
system_prompt=system_prompt,
|
|
166
163
|
temperature=temperature,
|
|
167
164
|
max_tokens=max_tokens,
|
|
@@ -659,7 +656,6 @@ class Session:
|
|
|
659
656
|
"base_url": self.client.base_url,
|
|
660
657
|
"api_key": self.client.api_key,
|
|
661
658
|
"model": self.model,
|
|
662
|
-
"backend": self.backend,
|
|
663
659
|
"temperature": self.temperature,
|
|
664
660
|
"max_tokens": self.max_tokens,
|
|
665
661
|
"timeout": self.client.timeout,
|
|
@@ -673,7 +669,6 @@ class Session:
|
|
|
673
669
|
"base_url",
|
|
674
670
|
"api_key",
|
|
675
671
|
"model",
|
|
676
|
-
"backend",
|
|
677
672
|
"temperature",
|
|
678
673
|
"max_tokens",
|
|
679
674
|
"timeout",
|
|
@@ -690,8 +685,6 @@ class Session:
|
|
|
690
685
|
# the calibration factor is tokenizer-specific: a factor tuned
|
|
691
686
|
# to the previous model must not skew estimates for the new one
|
|
692
687
|
self.calibrator.reset()
|
|
693
|
-
self.backend = merged["backend"]
|
|
694
|
-
self.store.backend = merged["backend"]
|
|
695
688
|
self.temperature = merged["temperature"]
|
|
696
689
|
self.max_tokens = merged["max_tokens"]
|
|
697
690
|
if hasattr(self.client, "set_timeout"):
|
{python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5}/python_agent_harness/tools/__init__.py
RENAMED
|
@@ -10,6 +10,8 @@ from .bash import Bash
|
|
|
10
10
|
from .edit import Edit
|
|
11
11
|
from .edit_mac import EditMac
|
|
12
12
|
from .filesystem import GlobTool, Grep, Insert, Mkdir, Read, Write
|
|
13
|
+
from .glob_mac import GlobMac
|
|
14
|
+
from .grep_mac import GrepMac
|
|
13
15
|
from .mcp import MCPTool, mcp_tools_from_manager, normalize_mcp_result
|
|
14
16
|
from .planexit import PlanExit
|
|
15
17
|
from .question import Question
|
|
@@ -25,8 +27,10 @@ __all__ = [
|
|
|
25
27
|
"Bash",
|
|
26
28
|
"Edit",
|
|
27
29
|
"EditMac",
|
|
30
|
+
"GlobMac",
|
|
28
31
|
"GlobTool",
|
|
29
32
|
"Grep",
|
|
33
|
+
"GrepMac",
|
|
30
34
|
"Insert",
|
|
31
35
|
"MCPTool",
|
|
32
36
|
"Mkdir",
|
|
@@ -43,15 +47,18 @@ __all__ = [
|
|
|
43
47
|
|
|
44
48
|
def default_registry() -> Registry:
|
|
45
49
|
reg = Registry()
|
|
46
|
-
# macOS
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
+
# macOS lacks GNU `patch`, `tree`, and PCRE-enabled git — use
|
|
51
|
+
# pure-Python / POSIX-compatible Mac variants that register under
|
|
52
|
+
# the same tool names so callers are platform-independent.
|
|
53
|
+
_mac = sys.platform == "darwin"
|
|
54
|
+
edit_tool = EditMac() if _mac else Edit()
|
|
55
|
+
glob_tool = GlobMac() if _mac else GlobTool()
|
|
56
|
+
grep_tool = GrepMac() if _mac else Grep()
|
|
50
57
|
for tool in (
|
|
51
58
|
AgentTool(),
|
|
52
59
|
TodoWrite(),
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
glob_tool,
|
|
61
|
+
grep_tool,
|
|
55
62
|
Read(),
|
|
56
63
|
Insert(),
|
|
57
64
|
edit_tool,
|
|
@@ -6,7 +6,7 @@ sub-agent loop, delivering the result string when it finishes — a
|
|
|
6
6
|
long-running sub-agent never blocks the parent's sequential tool loop.
|
|
7
7
|
|
|
8
8
|
Sub-agents run the same agent loop with a fresh loop instance; their
|
|
9
|
-
|
|
9
|
+
model can be overridden (see config). Results flow back to the
|
|
10
10
|
parent as a single tool result string. Errors are contained: an
|
|
11
11
|
unexpected sub-agent response becomes an error string fed to the parent,
|
|
12
12
|
never a crash.
|
|
@@ -148,7 +148,9 @@ def _git_root(path: str) -> str | None:
|
|
|
148
148
|
# isort: off
|
|
149
149
|
from .edit import Edit, _fix_patch_headers, _strip_diff_fence # noqa: E402
|
|
150
150
|
from .glob import GlobTool, _git_glob_results # noqa: E402
|
|
151
|
+
from .glob_mac import GlobMac # noqa: E402
|
|
151
152
|
from .grep import Grep, _grep_out # noqa: E402
|
|
153
|
+
from .grep_mac import GrepMac # noqa: E402
|
|
152
154
|
from .insert import Insert # noqa: E402
|
|
153
155
|
from .mkdir import Mkdir # noqa: E402
|
|
154
156
|
from .read import Read # noqa: E402
|
|
@@ -157,8 +159,10 @@ from .write import Write # noqa: E402
|
|
|
157
159
|
|
|
158
160
|
__all__ = [
|
|
159
161
|
"Edit",
|
|
162
|
+
"GlobMac",
|
|
160
163
|
"GlobTool",
|
|
161
164
|
"Grep",
|
|
165
|
+
"GrepMac",
|
|
162
166
|
"Insert",
|
|
163
167
|
"Mkdir",
|
|
164
168
|
"Read",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""macOS Glob tool: ``git ls-files`` inside git repos, ``pathlib`` outside.
|
|
2
|
+
|
|
3
|
+
Apple ships neither GNU ``tree`` nor a compatible equivalent, so the
|
|
4
|
+
non-git fallback in :class:`GlobTool` (which shells out to ``tree``)
|
|
5
|
+
fails on a stock macOS install. ``GlobMac`` replaces that fallback
|
|
6
|
+
with :meth:`pathlib.Path.rglob`, which is pure-Python, requires no
|
|
7
|
+
external binary, and produces identical results.
|
|
8
|
+
|
|
9
|
+
Only the non-git fallback differs; the git path, the tool name, and
|
|
10
|
+
the result format are inherited unchanged so callers, the tool
|
|
11
|
+
registry, and the plan-mode write guard are platform-independent.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import fnmatch
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from .base import ToolContext
|
|
21
|
+
from .filesystem import _natnump, _spool
|
|
22
|
+
from .glob import GlobTool
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GlobMac(GlobTool):
|
|
26
|
+
"""Glob with a pure-Python non-git fallback for macOS."""
|
|
27
|
+
|
|
28
|
+
def _tree_fallback(self, pattern: str, base: str, depth: object) -> str:
|
|
29
|
+
"""Walk *base* with :func:`pathlib.Path.rglob` instead of ``tree``.
|
|
30
|
+
|
|
31
|
+
Results are absolute paths sorted by modification time (newest
|
|
32
|
+
first), matching the ``tree --sort=mtime`` order of the Linux
|
|
33
|
+
fallback. Hidden directories (``.git``, etc.) are skipped.
|
|
34
|
+
"""
|
|
35
|
+
root = Path(base)
|
|
36
|
+
matches: list[tuple[float, str]] = []
|
|
37
|
+
if _natnump(depth):
|
|
38
|
+
# Depth-limited: walk manually so we can count levels.
|
|
39
|
+
for dirpath, dirnames, filenames in os.walk(base):
|
|
40
|
+
# Skip hidden directories (mirrors tree's -I .git and
|
|
41
|
+
# the general expectation that dotfiles are excluded).
|
|
42
|
+
dirnames[:] = [d for d in dirnames if not d.startswith(".")]
|
|
43
|
+
rel = os.path.relpath(dirpath, base)
|
|
44
|
+
level = 0 if rel == "." else rel.count(os.sep) + 1
|
|
45
|
+
if level >= depth:
|
|
46
|
+
dirnames.clear()
|
|
47
|
+
continue
|
|
48
|
+
for name in filenames:
|
|
49
|
+
if fnmatch.fnmatch(name.lower(), pattern.lower()):
|
|
50
|
+
full = os.path.join(dirpath, name)
|
|
51
|
+
try:
|
|
52
|
+
mtime = os.path.getmtime(full)
|
|
53
|
+
except OSError:
|
|
54
|
+
mtime = 0.0
|
|
55
|
+
matches.append((mtime, full))
|
|
56
|
+
else:
|
|
57
|
+
# Unlimited depth: rglob is simpler.
|
|
58
|
+
for p in root.rglob("*"):
|
|
59
|
+
if any(part.startswith(".") for part in p.relative_to(root).parts):
|
|
60
|
+
continue
|
|
61
|
+
if p.is_file() and fnmatch.fnmatch(p.name.lower(), pattern.lower()):
|
|
62
|
+
try:
|
|
63
|
+
mtime = p.stat().st_mtime
|
|
64
|
+
except OSError:
|
|
65
|
+
mtime = 0.0
|
|
66
|
+
matches.append((mtime, str(p)))
|
|
67
|
+
|
|
68
|
+
# Sort newest-first (like tree --sort=mtime).
|
|
69
|
+
matches.sort(key=lambda t: t[0], reverse=True)
|
|
70
|
+
out = "\n".join(path for _, path in matches)
|
|
71
|
+
if not out:
|
|
72
|
+
return ""
|
|
73
|
+
return _spool(out + "\n", "glob")
|
|
74
|
+
|
|
75
|
+
def run(self, args: dict, ctx: ToolContext) -> str:
|
|
76
|
+
pattern = args.get("pattern") or ""
|
|
77
|
+
if not pattern:
|
|
78
|
+
return "Error: pattern must not be empty"
|
|
79
|
+
path = args.get("path")
|
|
80
|
+
if path:
|
|
81
|
+
if not (os.path.isdir(path) and os.access(path, os.R_OK)):
|
|
82
|
+
return f"Error: path {path} is not readable"
|
|
83
|
+
else:
|
|
84
|
+
path = ctx.cwd
|
|
85
|
+
base = os.path.realpath(path)
|
|
86
|
+
depth = args.get("depth")
|
|
87
|
+
|
|
88
|
+
from .filesystem import _git_root
|
|
89
|
+
|
|
90
|
+
git_root = _git_root(base)
|
|
91
|
+
|
|
92
|
+
if git_root:
|
|
93
|
+
# The git path is identical to the parent — delegate.
|
|
94
|
+
return super().run(args, ctx)
|
|
95
|
+
|
|
96
|
+
# Non-git: pure-Python fallback instead of `tree`.
|
|
97
|
+
return self._tree_fallback(pattern, base, depth)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""macOS Grep tool: ``git grep -E`` instead of ``git grep -P``.
|
|
2
|
+
|
|
3
|
+
Apple's default Git (installed via Xcode Command Line Tools) is built
|
|
4
|
+
without PCRE support, so ``git grep -P`` (Perl-compatible regex) fails
|
|
5
|
+
with::
|
|
6
|
+
|
|
7
|
+
fatal: cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE
|
|
8
|
+
|
|
9
|
+
The Linux :class:`Grep` tool uses ``-P`` as the first-choice backend;
|
|
10
|
+
when that fails on macOS the fallback chain (``rg`` → ``grep``) still
|
|
11
|
+
works, but:
|
|
12
|
+
|
|
13
|
+
- ``rg`` (ripgrep) is not installed by default on macOS.
|
|
14
|
+
- BSD ``grep`` supports only POSIX ERE, losing Perl features
|
|
15
|
+
(``\\d``, ``\\w``, lookaheads).
|
|
16
|
+
- Two unnecessary failed attempts add latency before reaching a
|
|
17
|
+
working backend.
|
|
18
|
+
|
|
19
|
+
``GrepMac`` replaces ``-P`` with ``-E`` (POSIX extended regex) in the
|
|
20
|
+
``git grep`` command, so the git path — the fastest and most common
|
|
21
|
+
backend — works on stock macOS. The rest of the fallback chain (rg,
|
|
22
|
+
grep) is inherited unchanged.
|
|
23
|
+
|
|
24
|
+
Only the ``git grep`` regex flag differs; the tool name and result
|
|
25
|
+
format are inherited so callers, the tool registry, and the plan-mode
|
|
26
|
+
write guard are platform-independent.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
import subprocess
|
|
33
|
+
|
|
34
|
+
from .base import ToolContext
|
|
35
|
+
from .filesystem import _git_root
|
|
36
|
+
from .grep import Grep, _grep_out
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class GrepMac(Grep):
|
|
40
|
+
"""Grep with ``git grep -E`` for macOS (no PCRE dependency)."""
|
|
41
|
+
|
|
42
|
+
def run(self, args: dict, ctx: ToolContext) -> str:
|
|
43
|
+
regex = args["regex"]
|
|
44
|
+
path = os.path.realpath(args["path"])
|
|
45
|
+
if not os.path.isdir(path) and not os.path.isfile(path):
|
|
46
|
+
return f"Error: path {args['path']} is not readable"
|
|
47
|
+
glob = args.get("glob")
|
|
48
|
+
context = args.get("context_lines")
|
|
49
|
+
if context is not None:
|
|
50
|
+
context = max(0, min(15, int(context)))
|
|
51
|
+
|
|
52
|
+
git_root = _git_root(path) if os.path.isdir(path) else None
|
|
53
|
+
if git_root:
|
|
54
|
+
rel = os.path.relpath(path, git_root)
|
|
55
|
+
pathspec = rel
|
|
56
|
+
if glob and os.path.isdir(path):
|
|
57
|
+
pathspec = os.path.join(rel, glob).replace(os.sep, "/")
|
|
58
|
+
# -E (extended regex) instead of -P (Perl regex): Apple's
|
|
59
|
+
# Git is built without PCRE, so -P fails on stock macOS.
|
|
60
|
+
cmd = [
|
|
61
|
+
"git",
|
|
62
|
+
"grep",
|
|
63
|
+
"--line-number",
|
|
64
|
+
"--no-color",
|
|
65
|
+
"--max-count=1000",
|
|
66
|
+
"--untracked",
|
|
67
|
+
"-E",
|
|
68
|
+
"-e",
|
|
69
|
+
regex,
|
|
70
|
+
"--",
|
|
71
|
+
pathspec,
|
|
72
|
+
]
|
|
73
|
+
if context:
|
|
74
|
+
cmd = cmd[:3] + [f"-C{context}"] + cmd[3:]
|
|
75
|
+
try:
|
|
76
|
+
proc = subprocess.run(
|
|
77
|
+
cmd,
|
|
78
|
+
cwd=git_root,
|
|
79
|
+
capture_output=True,
|
|
80
|
+
text=True,
|
|
81
|
+
encoding="utf-8",
|
|
82
|
+
errors="replace",
|
|
83
|
+
timeout=60,
|
|
84
|
+
)
|
|
85
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
86
|
+
proc = None
|
|
87
|
+
if proc is not None and proc.returncode in (0, 1):
|
|
88
|
+
return _grep_out(proc, "git")
|
|
89
|
+
|
|
90
|
+
# Non-git or git grep failed: delegate to the parent's rg/grep
|
|
91
|
+
# fallback chain (identical on both platforms).
|
|
92
|
+
return super().run(args, ctx)
|
{python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.5/python_agent_harness.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-agent-harness
|
|
3
|
-
Version: 1.5.4.
|
|
3
|
+
Version: 1.5.4.5
|
|
4
4
|
Summary: A lightweight, hackable mini-OpenCode written in Python.
|
|
5
5
|
Author: huming
|
|
6
6
|
License-Expression: MIT
|
|
@@ -186,13 +186,13 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
186
186
|
|
|
187
187
|
### Configuration options
|
|
188
188
|
|
|
189
|
-
- **`llm`** — main LLM configuration. Optional keys include `
|
|
189
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `run --no-stream` overrides `stream`.
|
|
190
190
|
- **`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.
|
|
191
191
|
- **`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`.
|
|
192
192
|
- **`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.
|
|
193
193
|
- **`paths.context_path` / `paths.skill_path`** — locations from which to load context files and skills. When unset, the project-local `<project>/contexts` and `<project>/skills` directories are used.
|
|
194
194
|
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
195
|
-
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL
|
|
195
|
+
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
196
196
|
- **Custom config** — use `--config PATH` or `PYTHON_AGENT_HARNESS_CONFIG`.
|
|
197
197
|
- **LLM logging** — request and response bodies are logged as JSON to `/tmp/python-agent-harness-<date>-<id>.json`. Set `LLM_LOG_DIR` to change the directory. The log path is printed at startup.
|
|
198
198
|
|
|
@@ -49,7 +49,9 @@ python_agent_harness/tools/edit.py
|
|
|
49
49
|
python_agent_harness/tools/edit_mac.py
|
|
50
50
|
python_agent_harness/tools/filesystem.py
|
|
51
51
|
python_agent_harness/tools/glob.py
|
|
52
|
+
python_agent_harness/tools/glob_mac.py
|
|
52
53
|
python_agent_harness/tools/grep.py
|
|
54
|
+
python_agent_harness/tools/grep_mac.py
|
|
53
55
|
python_agent_harness/tools/insert.py
|
|
54
56
|
python_agent_harness/tools/mcp.py
|
|
55
57
|
python_agent_harness/tools/mkdir.py
|
|
@@ -20,11 +20,9 @@ class TestMakeSessionPromptDefaults(unittest.TestCase):
|
|
|
20
20
|
"OPENAI_BASE_URL",
|
|
21
21
|
"OPENAI_API_KEY",
|
|
22
22
|
"OPENAI_MODEL",
|
|
23
|
-
"OPENAI_BACKEND",
|
|
24
23
|
"OPENAI_SUBAGENT_BASE_URL",
|
|
25
24
|
"OPENAI_SUBAGENT_API_KEY",
|
|
26
25
|
"OPENAI_SUBAGENT_MODEL",
|
|
27
|
-
"OPENAI_SUBAGENT_BACKEND",
|
|
28
26
|
]
|
|
29
27
|
|
|
30
28
|
def setUp(self):
|
|
@@ -178,7 +176,7 @@ class TestMakeSessionPromptDefaults(unittest.TestCase):
|
|
|
178
176
|
|
|
179
177
|
def test_subagent_llm_inherits_main_client_by_default(self):
|
|
180
178
|
"""Without subagent_llm overrides, the sub-agent shares the
|
|
181
|
-
main client (mirrors gptel-agent-harness: nil
|
|
179
|
+
main client (mirrors gptel-agent-harness: nil model overrides
|
|
182
180
|
inherit the main agent's)."""
|
|
183
181
|
session = cli.make_session(self._tmp.name, config_path=self._config_path)
|
|
184
182
|
try:
|
|
@@ -11,12 +11,10 @@ ENV_KEYS = [
|
|
|
11
11
|
"OPENAI_BASE_URL",
|
|
12
12
|
"OPENAI_API_KEY",
|
|
13
13
|
"OPENAI_MODEL",
|
|
14
|
-
"OPENAI_BACKEND",
|
|
15
14
|
"PYTHON_AGENT_HARNESS_CONFIG",
|
|
16
15
|
"OPENAI_SUBAGENT_BASE_URL",
|
|
17
16
|
"OPENAI_SUBAGENT_API_KEY",
|
|
18
17
|
"OPENAI_SUBAGENT_MODEL",
|
|
19
|
-
"OPENAI_SUBAGENT_BACKEND",
|
|
20
18
|
]
|
|
21
19
|
|
|
22
20
|
|
|
@@ -49,7 +47,6 @@ class TestConfigFile(unittest.TestCase):
|
|
|
49
47
|
'"base_url": "https://api.example.com/v1", '
|
|
50
48
|
'"api_key": "sk-test", '
|
|
51
49
|
'"model": "custom-model", '
|
|
52
|
-
'"backend": "Example", '
|
|
53
50
|
'"temperature": 0.2, '
|
|
54
51
|
'"max_tokens": 2048, '
|
|
55
52
|
'"timeout": 30.0, '
|
|
@@ -60,7 +57,6 @@ class TestConfigFile(unittest.TestCase):
|
|
|
60
57
|
self.assertEqual(settings["base_url"], "https://api.example.com/v1")
|
|
61
58
|
self.assertEqual(settings["api_key"], "sk-test")
|
|
62
59
|
self.assertEqual(settings["model"], "custom-model")
|
|
63
|
-
self.assertEqual(settings["backend"], "Example")
|
|
64
60
|
self.assertEqual(settings["temperature"], 0.2)
|
|
65
61
|
self.assertEqual(settings["max_tokens"], 2048)
|
|
66
62
|
self.assertEqual(settings["timeout"], 30.0)
|
|
@@ -257,6 +253,21 @@ class TestContextWindowsConfig(unittest.TestCase):
|
|
|
257
253
|
with self.assertRaises(ValueError):
|
|
258
254
|
config.load_context_windows_config(p)
|
|
259
255
|
|
|
256
|
+
def test_context_windows_zero_rejected(self):
|
|
257
|
+
"""A zero context window would cause a division-by-zero at runtime."""
|
|
258
|
+
with tempfile.TemporaryDirectory() as d:
|
|
259
|
+
p = Path(d) / "config.json"
|
|
260
|
+
p.write_text('{"context_windows": {"m": 0}}', encoding="utf-8")
|
|
261
|
+
with self.assertRaises(ValueError):
|
|
262
|
+
config.load_context_windows_config(p)
|
|
263
|
+
|
|
264
|
+
def test_context_windows_negative_rejected(self):
|
|
265
|
+
with tempfile.TemporaryDirectory() as d:
|
|
266
|
+
p = Path(d) / "config.json"
|
|
267
|
+
p.write_text('{"context_windows": {"m": -1}}', encoding="utf-8")
|
|
268
|
+
with self.assertRaises(ValueError):
|
|
269
|
+
config.load_context_windows_config(p)
|
|
270
|
+
|
|
260
271
|
def test_get_context_window_precedence(self):
|
|
261
272
|
"""Config-file override -> CONTEXT_WINDOWS -> default."""
|
|
262
273
|
with tempfile.TemporaryDirectory() as d:
|
|
@@ -327,7 +338,7 @@ class TestContextWindowsConfig(unittest.TestCase):
|
|
|
327
338
|
|
|
328
339
|
class TestSubagentLlmConfig(unittest.TestCase):
|
|
329
340
|
"""Sub-agent LLM settings: unset keys inherit the main settings
|
|
330
|
-
(mirrors gptel-agent-harness-subagent-model
|
|
341
|
+
(mirrors gptel-agent-harness-subagent-model)."""
|
|
331
342
|
|
|
332
343
|
def setUp(self):
|
|
333
344
|
self._saved = {k: os.environ.get(k) for k in ENV_KEYS}
|
|
@@ -345,7 +356,6 @@ class TestSubagentLlmConfig(unittest.TestCase):
|
|
|
345
356
|
"base_url": "https://api.main.example/v1",
|
|
346
357
|
"api_key": "sk-main",
|
|
347
358
|
"model": "big-model",
|
|
348
|
-
"backend": "Main",
|
|
349
359
|
"temperature": 0.0,
|
|
350
360
|
"max_tokens": None,
|
|
351
361
|
"timeout": 600.0,
|