python-agent-harness 1.5.4.5__tar.gz → 1.5.4.6__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.5/python_agent_harness.egg-info → python_agent_harness-1.5.4.6}/PKG-INFO +10 -12
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/README.md +9 -11
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/pyproject.toml +1 -1
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/__init__.py +1 -1
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/cli.py +3 -9
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/client.py +18 -9
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/config.py +87 -58
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/session.py +6 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/token_estimator.py +3 -3
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6/python_agent_harness.egg-info}/PKG-INFO +10 -12
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_client.py +43 -52
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_config.py +138 -90
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_session.py +6 -2
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_token_estimator.py +6 -6
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/LICENSE +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/__main__.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/agent.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/commands.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/context_manager.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/diffrender.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/__init__.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/client.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/config.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/manager.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/models.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/persistence.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/planmode.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/agent.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/build-switch.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/commands/explain.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/compact.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/initialize.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/plan-mode.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/plan.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/review.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/subagent.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/summary.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/task-completion-rules.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/title.md +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/subagent.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tool_runner.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/__init__.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/agent_tool.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/base.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/bash.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/diffapply.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/edit.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/edit_mac.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/filesystem.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/glob.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/glob_mac.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/grep.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/grep_mac.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/insert.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/mcp.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/mkdir.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/planexit.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/question.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/read.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/skill.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/todo.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/write.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/__init__.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/commands.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/core.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/input.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/render.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/SOURCES.txt +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/dependency_links.txt +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/entry_points.txt +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/requires.txt +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/top_level.txt +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/setup.cfg +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_agents_md.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_cli.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_commands.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_concurrent_subagents.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_context_rules.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_diffrender.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_filesystem.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_invariants.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_mcp.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_models.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_persistence.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_planmode.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_prompts.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_scenarios.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_subagent.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_subagent_isolation.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_todos_scope.py +0 -0
- {python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/tests/test_tools_misc.py +0 -0
{python_agent_harness-1.5.4.5/python_agent_harness.egg-info → python_agent_harness-1.5.4.6}/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.6
|
|
4
4
|
Summary: A lightweight, hackable mini-OpenCode written in Python.
|
|
5
5
|
Author: huming
|
|
6
6
|
License-Expression: MIT
|
|
@@ -138,17 +138,15 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
138
138
|
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
|
|
139
139
|
"deepseek": {
|
|
140
140
|
"base_url": "https://api.deepseek.com/v1",
|
|
141
|
-
"model": "deepseek-chat"
|
|
141
|
+
"model": "deepseek-chat",
|
|
142
|
+
"context_window": 128000
|
|
142
143
|
},
|
|
143
144
|
"qwen": {
|
|
144
145
|
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
145
|
-
"model": "qwen3.5-coder"
|
|
146
|
+
"model": "qwen3.5-coder",
|
|
147
|
+
"context_window": 131072
|
|
146
148
|
}
|
|
147
149
|
},
|
|
148
|
-
"context_windows": {
|
|
149
|
-
"_comment": "Optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g. deepseek-v4 = 1000000); matched in file order, first match wins. Overrides the built-in CONTEXT_WINDOWS table in config.py.",
|
|
150
|
-
"deepseek-v4": 1000000
|
|
151
|
-
},
|
|
152
150
|
"subagent_llm": {
|
|
153
151
|
"profile": null,
|
|
154
152
|
"base_url": null,
|
|
@@ -158,7 +156,8 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
158
156
|
"max_tokens": null,
|
|
159
157
|
"timeout": null,
|
|
160
158
|
"reasoning_effort": null,
|
|
161
|
-
"stream": null
|
|
159
|
+
"stream": null,
|
|
160
|
+
"context_window": null
|
|
162
161
|
},
|
|
163
162
|
"paths": {
|
|
164
163
|
"context_path": null,
|
|
@@ -186,10 +185,9 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
186
185
|
|
|
187
186
|
### Configuration options
|
|
188
187
|
|
|
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
|
-
- **`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
|
-
- **`
|
|
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.
|
|
188
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, `context_window`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `context_window` (tokens) overrides the built-in table for the active model; when unset, well-known models are matched automatically. `run --no-stream` overrides `stream`.
|
|
189
|
+
- **`models`** — named LLM profiles for runtime switching with `/model`. A profile is a partial settings dictionary; unset keys inherit from the main `llm`. Each profile can include `context_window` to set the context window for that model. `default` restores the main LLM configuration.
|
|
190
|
+
- **`subagent_llm`** — LLM configuration for `Agent` tool requests. Unset values inherit from the main `llm`. Set `profile` to reuse a profile from `models`. `context_window` can also be set here to override the inherited value for sub-agents. Precedence is: profile settings > explicit `subagent_llm` settings > main `llm` > environment variables.
|
|
193
191
|
- **`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
192
|
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
195
193
|
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
@@ -115,17 +115,15 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
115
115
|
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
|
|
116
116
|
"deepseek": {
|
|
117
117
|
"base_url": "https://api.deepseek.com/v1",
|
|
118
|
-
"model": "deepseek-chat"
|
|
118
|
+
"model": "deepseek-chat",
|
|
119
|
+
"context_window": 128000
|
|
119
120
|
},
|
|
120
121
|
"qwen": {
|
|
121
122
|
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
122
|
-
"model": "qwen3.5-coder"
|
|
123
|
+
"model": "qwen3.5-coder",
|
|
124
|
+
"context_window": 131072
|
|
123
125
|
}
|
|
124
126
|
},
|
|
125
|
-
"context_windows": {
|
|
126
|
-
"_comment": "Optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g. deepseek-v4 = 1000000); matched in file order, first match wins. Overrides the built-in CONTEXT_WINDOWS table in config.py.",
|
|
127
|
-
"deepseek-v4": 1000000
|
|
128
|
-
},
|
|
129
127
|
"subagent_llm": {
|
|
130
128
|
"profile": null,
|
|
131
129
|
"base_url": null,
|
|
@@ -135,7 +133,8 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
135
133
|
"max_tokens": null,
|
|
136
134
|
"timeout": null,
|
|
137
135
|
"reasoning_effort": null,
|
|
138
|
-
"stream": null
|
|
136
|
+
"stream": null,
|
|
137
|
+
"context_window": null
|
|
139
138
|
},
|
|
140
139
|
"paths": {
|
|
141
140
|
"context_path": null,
|
|
@@ -163,10 +162,9 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
163
162
|
|
|
164
163
|
### Configuration options
|
|
165
164
|
|
|
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
|
-
- **`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
|
-
- **`
|
|
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.
|
|
165
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, `context_window`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `context_window` (tokens) overrides the built-in table for the active model; when unset, well-known models are matched automatically. `run --no-stream` overrides `stream`.
|
|
166
|
+
- **`models`** — named LLM profiles for runtime switching with `/model`. A profile is a partial settings dictionary; unset keys inherit from the main `llm`. Each profile can include `context_window` to set the context window for that model. `default` restores the main LLM configuration.
|
|
167
|
+
- **`subagent_llm`** — LLM configuration for `Agent` tool requests. Unset values inherit from the main `llm`. Set `profile` to reuse a profile from `models`. `context_window` can also be set here to override the inherited value for sub-agents. Precedence is: profile settings > explicit `subagent_llm` settings > main `llm` > environment variables.
|
|
170
168
|
- **`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
169
|
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
172
170
|
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
@@ -217,17 +217,11 @@ def cmd_config(args: argparse.Namespace) -> int:
|
|
|
217
217
|
for name, profile in sorted(model_profiles.items()):
|
|
218
218
|
model_name = profile.get("model", "(inherited)")
|
|
219
219
|
base_url = profile.get("base_url", "(inherited)")
|
|
220
|
-
|
|
220
|
+
cw = profile.get("context_window")
|
|
221
|
+
cw_str = f", context_window={cw}" if cw is not None else ""
|
|
222
|
+
print(f" {name}: model={model_name}, base_url={base_url}{cw_str}")
|
|
221
223
|
else:
|
|
222
224
|
print("models: (none configured — add a 'models' section to use /model)")
|
|
223
|
-
# Show context-window overrides
|
|
224
|
-
context_windows = config.load_context_windows_config(args.path)
|
|
225
|
-
if context_windows:
|
|
226
|
-
print("context_windows:")
|
|
227
|
-
for pattern, size in context_windows:
|
|
228
|
-
print(f" {pattern}: {size}")
|
|
229
|
-
else:
|
|
230
|
-
print("context_windows: (none configured — built-in table in config.py applies)")
|
|
231
225
|
return 0
|
|
232
226
|
|
|
233
227
|
|
{python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/client.py
RENAMED
|
@@ -217,6 +217,10 @@ class Client:
|
|
|
217
217
|
# the user asked to stop. Cleared at the start of each chat()
|
|
218
218
|
# so a fresh turn may retry normally.
|
|
219
219
|
self._aborted = False
|
|
220
|
+
# Per-model context-window override: set by the session when
|
|
221
|
+
# switching to a model profile that carries its own
|
|
222
|
+
# context_window. None means "resolve from config / table".
|
|
223
|
+
self._context_window: int | None = None
|
|
220
224
|
# an explicit log file is inherited by clones so every request
|
|
221
225
|
# of one session (main + all sub-agents) lands in a single log
|
|
222
226
|
self.log_path = (
|
|
@@ -229,18 +233,21 @@ class Client:
|
|
|
229
233
|
def context_window(self) -> int:
|
|
230
234
|
"""Get the context window for this model.
|
|
231
235
|
|
|
232
|
-
Resolution order:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
Resolution order: explicit ``_context_window`` override (set by
|
|
237
|
+
the session on model switch) → ``llm.context_window`` from the
|
|
238
|
+
config file → built-in CONTEXT_WINDOWS table (substring match)
|
|
239
|
+
→ DEFAULT_CONTEXT_WINDOW. Resolved on every access (no
|
|
240
|
+
caching), so a runtime model switch or config-file edit takes
|
|
241
|
+
effect immediately; a malformed config falls back to the default
|
|
242
|
+
for that access and recovers once the file is fixed.
|
|
238
243
|
"""
|
|
244
|
+
if self._context_window is not None:
|
|
245
|
+
return self._context_window
|
|
239
246
|
try:
|
|
240
247
|
return config.get_context_window_for_model(self.model, config_path=self._config_path)
|
|
241
248
|
except Exception:
|
|
242
|
-
# a malformed
|
|
243
|
-
#
|
|
249
|
+
# a malformed config must not break the loop: use the safe
|
|
250
|
+
# default, retry on the next access
|
|
244
251
|
return config.DEFAULT_CONTEXT_WINDOW
|
|
245
252
|
|
|
246
253
|
def close(self) -> None:
|
|
@@ -259,7 +266,7 @@ class Client:
|
|
|
259
266
|
flag strictly per-request. The log file is shared so one
|
|
260
267
|
session's LLM interactions stay in one log.
|
|
261
268
|
"""
|
|
262
|
-
|
|
269
|
+
c = Client(
|
|
263
270
|
base_url=self.base_url,
|
|
264
271
|
api_key=self.api_key,
|
|
265
272
|
model=self.model,
|
|
@@ -271,6 +278,8 @@ class Client:
|
|
|
271
278
|
config_path=self._config_path,
|
|
272
279
|
log_path=self.log_path,
|
|
273
280
|
)
|
|
281
|
+
c._context_window = self._context_window
|
|
282
|
+
return c
|
|
274
283
|
|
|
275
284
|
def abort(self) -> None:
|
|
276
285
|
"""Abort the in-flight request (called on cancel).
|
{python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/config.py
RENAMED
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
import os
|
|
10
|
+
import warnings
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
from .mcp.config import MCPConfig
|
|
@@ -173,6 +174,7 @@ DEFAULT_LLM: dict = {
|
|
|
173
174
|
"timeout": 600.0,
|
|
174
175
|
"reasoning_effort": None,
|
|
175
176
|
"stream": True,
|
|
177
|
+
"context_window": None,
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
DEFAULT_PATHS: dict = {
|
|
@@ -196,6 +198,7 @@ DEFAULT_SUBAGENT_LLM: dict = {
|
|
|
196
198
|
"timeout": None,
|
|
197
199
|
"reasoning_effort": None,
|
|
198
200
|
"stream": None,
|
|
201
|
+
"context_window": None,
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
CONFIG_TEMPLATE = """\
|
|
@@ -208,20 +211,18 @@ CONFIG_TEMPLATE = """\
|
|
|
208
211
|
"stream": true
|
|
209
212
|
}},
|
|
210
213
|
"models": {{
|
|
211
|
-
"_comment": "Named LLM profiles for /model switching. Each entry is a full set of LLM settings (base_url, api_key, model, etc.). Use /model in the TUI to switch at runtime.",
|
|
214
|
+
"_comment": "Named LLM profiles for /model switching. Each entry is a full set of LLM settings (base_url, api_key, model, context_window, etc.). Use /model in the TUI to switch at runtime. Unset keys inherit the main llm settings.",
|
|
212
215
|
"deepseek": {{
|
|
213
216
|
"base_url": "https://api.deepseek.com/v1",
|
|
214
|
-
"model": "deepseek-chat"
|
|
217
|
+
"model": "deepseek-chat",
|
|
218
|
+
"context_window": 128000
|
|
215
219
|
}},
|
|
216
220
|
"openai": {{
|
|
217
221
|
"base_url": "https://api.openai.com/v1",
|
|
218
|
-
"model": "gpt-5-mini"
|
|
222
|
+
"model": "gpt-5-mini",
|
|
223
|
+
"context_window": 128000
|
|
219
224
|
}}
|
|
220
225
|
}},
|
|
221
|
-
"context_windows": {{
|
|
222
|
-
"_comment": "Optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g. deepseek-v4 = 1000000); matched in file order, first match wins. Overrides the built-in CONTEXT_WINDOWS table in config.py. Remove this section to use the built-in table.",
|
|
223
|
-
"deepseek-v4": 1000000
|
|
224
|
-
}},
|
|
225
226
|
"subagent_llm": {{
|
|
226
227
|
"_comment": "Optional overrides for sub-agent (Agent tool) requests, e.g. a cheaper model. Every key is optional; unset keys inherit the main llm settings above. Set 'profile' to a name from the 'models' section to reuse a model profile (profile settings win over explicit keys below).",
|
|
227
228
|
"profile": null,
|
|
@@ -232,7 +233,8 @@ CONFIG_TEMPLATE = """\
|
|
|
232
233
|
"max_tokens": null,
|
|
233
234
|
"timeout": null,
|
|
234
235
|
"reasoning_effort": null,
|
|
235
|
-
"stream": null
|
|
236
|
+
"stream": null,
|
|
237
|
+
"context_window": null
|
|
236
238
|
}},
|
|
237
239
|
"paths": {{
|
|
238
240
|
"_comment": "Optional overrides for context and skill directories. Absolute paths or ~ expansion supported.",
|
|
@@ -270,6 +272,22 @@ _SUBAGENT_ENV_OVERRIDES = {
|
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
|
|
275
|
+
def _validate_context_window(
|
|
276
|
+
value: object, section: str, path: str | os.PathLike | None = None
|
|
277
|
+
) -> None:
|
|
278
|
+
"""Raise ValueError if *value* is set but not a positive integer."""
|
|
279
|
+
if value is None:
|
|
280
|
+
return
|
|
281
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
282
|
+
raise ValueError(
|
|
283
|
+
f"config file {_config_path(path)}: {section}.context_window must be an integer"
|
|
284
|
+
)
|
|
285
|
+
if value <= 0:
|
|
286
|
+
raise ValueError(
|
|
287
|
+
f"config file {_config_path(path)}: {section}.context_window must be positive"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
273
291
|
def _config_path(path: str | os.PathLike | None = None) -> Path:
|
|
274
292
|
"""Resolve the config file path: explicit arg > $PYTHON_AGENT_HARNESS_CONFIG > default."""
|
|
275
293
|
if path:
|
|
@@ -305,6 +323,9 @@ def load_llm_config(path: str | os.PathLike | None = None) -> dict:
|
|
|
305
323
|
|
|
306
324
|
The config file is JSON with an ``llm`` object (see `CONFIG_TEMPLATE`).
|
|
307
325
|
Environment variables still win if set, so existing setups keep working.
|
|
326
|
+
``context_window`` is an optional integer (tokens); when unset it
|
|
327
|
+
defaults to None, meaning "resolve from the built-in table at
|
|
328
|
+
runtime".
|
|
308
329
|
"""
|
|
309
330
|
settings = dict(DEFAULT_LLM)
|
|
310
331
|
data = _read_config(path)
|
|
@@ -320,9 +341,19 @@ def load_llm_config(path: str | os.PathLike | None = None) -> dict:
|
|
|
320
341
|
"timeout",
|
|
321
342
|
"reasoning_effort",
|
|
322
343
|
"stream",
|
|
344
|
+
"context_window",
|
|
323
345
|
):
|
|
324
346
|
if key in llm and llm[key] is not None:
|
|
325
347
|
settings[key] = llm[key]
|
|
348
|
+
_validate_context_window(settings.get("context_window"), "llm", path)
|
|
349
|
+
# Warn if the old top-level context_windows section is still present.
|
|
350
|
+
if data.get("context_windows"):
|
|
351
|
+
warnings.warn(
|
|
352
|
+
"The top-level 'context_windows' section in config.json is deprecated and ignored. "
|
|
353
|
+
"Set 'context_window' inside 'llm' or each model profile in 'models' instead.",
|
|
354
|
+
DeprecationWarning,
|
|
355
|
+
stacklevel=2,
|
|
356
|
+
)
|
|
326
357
|
for key, env in _ENV_OVERRIDES.items():
|
|
327
358
|
val = os.environ.get(env)
|
|
328
359
|
if val:
|
|
@@ -378,6 +409,7 @@ def load_subagent_llm_config(
|
|
|
378
409
|
"timeout",
|
|
379
410
|
"reasoning_effort",
|
|
380
411
|
"stream",
|
|
412
|
+
"context_window",
|
|
381
413
|
):
|
|
382
414
|
if key in profile and profile[key] is not None:
|
|
383
415
|
overrides[key] = profile[key]
|
|
@@ -473,65 +505,62 @@ def _match_context_window(model: str) -> int | None:
|
|
|
473
505
|
return None
|
|
474
506
|
|
|
475
507
|
|
|
476
|
-
def
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
508
|
+
def get_context_window_for_model(
|
|
509
|
+
model: str,
|
|
510
|
+
config_path: str | os.PathLike | None = None,
|
|
511
|
+
) -> int:
|
|
512
|
+
"""Get the context window for MODEL.
|
|
513
|
+
|
|
514
|
+
Resolution order:
|
|
515
|
+
1. ``llm.context_window`` from the config file (when the active
|
|
516
|
+
model matches — this is already baked into the settings dict
|
|
517
|
+
returned by ``load_llm_config``; callers that use a resolved
|
|
518
|
+
settings dict should prefer ``resolve_context_window`` instead).
|
|
519
|
+
2. Built-in CONTEXT_WINDOWS table (substring match, first wins).
|
|
520
|
+
3. DEFAULT_CONTEXT_WINDOW.
|
|
521
|
+
|
|
522
|
+
This function is the fallback for callers that only have a model
|
|
523
|
+
name and no resolved settings dict (e.g. ``token_estimator``
|
|
524
|
+
or ``FakeClient``).
|
|
487
525
|
"""
|
|
526
|
+
# Try the llm.context_window from the config file first.
|
|
488
527
|
try:
|
|
489
|
-
data = _read_config(
|
|
528
|
+
data = _read_config(config_path)
|
|
490
529
|
except ValueError:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
)
|
|
503
|
-
if size <= 0:
|
|
504
|
-
raise ValueError(
|
|
505
|
-
f"config file {_config_path(path)}: context_windows.{pattern} must be positive"
|
|
506
|
-
)
|
|
507
|
-
entries.append((pattern, size))
|
|
508
|
-
return entries
|
|
530
|
+
data = {}
|
|
531
|
+
llm = data.get("llm") or {}
|
|
532
|
+
if isinstance(llm, dict):
|
|
533
|
+
cw = llm.get("context_window")
|
|
534
|
+
if cw is not None and isinstance(cw, int) and not isinstance(cw, bool) and cw > 0:
|
|
535
|
+
return cw
|
|
536
|
+
# Fall back to the built-in table, then the default.
|
|
537
|
+
matched = _match_context_window(model)
|
|
538
|
+
if matched is not None:
|
|
539
|
+
return matched
|
|
540
|
+
return DEFAULT_CONTEXT_WINDOW
|
|
509
541
|
|
|
510
542
|
|
|
511
|
-
def
|
|
512
|
-
|
|
513
|
-
config_path: str | os.PathLike | None = None,
|
|
514
|
-
) -> int:
|
|
515
|
-
"""Get the context window for MODEL: config-file overrides, then
|
|
516
|
-
the built-in table, then the default.
|
|
543
|
+
def resolve_context_window(settings: dict, model: str | None = None) -> int:
|
|
544
|
+
"""Resolve the context window from a fully-merged settings dict.
|
|
517
545
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
546
|
+
Precedence:
|
|
547
|
+
1. ``settings["context_window"]`` — set by the active profile,
|
|
548
|
+
``llm`` section, or ``subagent_llm`` section (already merged
|
|
549
|
+
by the load functions).
|
|
550
|
+
2. Built-in CONTEXT_WINDOWS table (substring match on *model*).
|
|
551
|
+
3. DEFAULT_CONTEXT_WINDOW.
|
|
522
552
|
|
|
523
553
|
Args:
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
The context window size as an integer.
|
|
554
|
+
settings: A resolved LLM settings dict (from ``load_llm_config``,
|
|
555
|
+
``load_subagent_llm_config``, or a merged profile).
|
|
556
|
+
model: The model ID to match against the built-in table when
|
|
557
|
+
``context_window`` is not set. Defaults to
|
|
558
|
+
``settings["model"]`` when omitted.
|
|
530
559
|
"""
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
560
|
+
cw = settings.get("context_window")
|
|
561
|
+
if cw is not None and isinstance(cw, int) and not isinstance(cw, bool) and cw > 0:
|
|
562
|
+
return cw
|
|
563
|
+
model = model or settings.get("model") or ""
|
|
535
564
|
matched = _match_context_window(model)
|
|
536
565
|
if matched is not None:
|
|
537
566
|
return matched
|
{python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6}/python_agent_harness/session.py
RENAMED
|
@@ -661,6 +661,7 @@ class Session:
|
|
|
661
661
|
"timeout": self.client.timeout,
|
|
662
662
|
"reasoning_effort": self.reasoning_effort,
|
|
663
663
|
"stream": self.stream,
|
|
664
|
+
"context_window": getattr(self.client, "_context_window", None),
|
|
664
665
|
}
|
|
665
666
|
for key, val in current.items():
|
|
666
667
|
merged.setdefault(key, val)
|
|
@@ -674,6 +675,7 @@ class Session:
|
|
|
674
675
|
"timeout",
|
|
675
676
|
"reasoning_effort",
|
|
676
677
|
"stream",
|
|
678
|
+
"context_window",
|
|
677
679
|
):
|
|
678
680
|
if key in profile and profile[key] is not None:
|
|
679
681
|
merged[key] = profile[key]
|
|
@@ -693,6 +695,10 @@ class Session:
|
|
|
693
695
|
self.client.timeout = merged["timeout"]
|
|
694
696
|
self.reasoning_effort = merged["reasoning_effort"]
|
|
695
697
|
self.stream = merged["stream"]
|
|
698
|
+
# context_window: set the client override so subsequent
|
|
699
|
+
# client.context_window accesses use the profile's value
|
|
700
|
+
# (None means "resolve from config / built-in table").
|
|
701
|
+
self.client._context_window = merged.get("context_window")
|
|
696
702
|
return True, f"switched to {name} ({self.model})"
|
|
697
703
|
|
|
698
704
|
# ------------------------------------------------------------------
|
|
@@ -44,9 +44,9 @@ def context_window_for(model: str, config_path: str | os.PathLike | None = None)
|
|
|
44
44
|
"""Return the context window for MODEL, or a safe fallback.
|
|
45
45
|
|
|
46
46
|
Delegates to ``config.get_context_window_for_model``: config-file
|
|
47
|
-
``
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
``llm.context_window`` first, then substring match over
|
|
48
|
+
CONTEXT_WINDOWS (first match wins, case-insensitive), then
|
|
49
|
+
DEFAULT_CONTEXT_WINDOW.
|
|
50
50
|
|
|
51
51
|
Args:
|
|
52
52
|
model: The model ID to look up.
|
{python_agent_harness-1.5.4.5 → python_agent_harness-1.5.4.6/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.6
|
|
4
4
|
Summary: A lightweight, hackable mini-OpenCode written in Python.
|
|
5
5
|
Author: huming
|
|
6
6
|
License-Expression: MIT
|
|
@@ -138,17 +138,15 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
138
138
|
"_comment": "Named LLM profiles for /model switching. Partial settings; unset keys inherit the main llm.",
|
|
139
139
|
"deepseek": {
|
|
140
140
|
"base_url": "https://api.deepseek.com/v1",
|
|
141
|
-
"model": "deepseek-chat"
|
|
141
|
+
"model": "deepseek-chat",
|
|
142
|
+
"context_window": 128000
|
|
142
143
|
},
|
|
143
144
|
"qwen": {
|
|
144
145
|
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
145
|
-
"model": "qwen3.5-coder"
|
|
146
|
+
"model": "qwen3.5-coder",
|
|
147
|
+
"context_window": 131072
|
|
146
148
|
}
|
|
147
149
|
},
|
|
148
|
-
"context_windows": {
|
|
149
|
-
"_comment": "Optional per-model context-window overrides (tokens). Keys are model names or substrings (e.g. deepseek-v4 = 1000000); matched in file order, first match wins. Overrides the built-in CONTEXT_WINDOWS table in config.py.",
|
|
150
|
-
"deepseek-v4": 1000000
|
|
151
|
-
},
|
|
152
150
|
"subagent_llm": {
|
|
153
151
|
"profile": null,
|
|
154
152
|
"base_url": null,
|
|
@@ -158,7 +156,8 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
158
156
|
"max_tokens": null,
|
|
159
157
|
"timeout": null,
|
|
160
158
|
"reasoning_effort": null,
|
|
161
|
-
"stream": null
|
|
159
|
+
"stream": null,
|
|
160
|
+
"context_window": null
|
|
162
161
|
},
|
|
163
162
|
"paths": {
|
|
164
163
|
"context_path": null,
|
|
@@ -186,10 +185,9 @@ All LLM settings live in a single JSON configuration file. Environment variables
|
|
|
186
185
|
|
|
187
186
|
### Configuration options
|
|
188
187
|
|
|
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
|
-
- **`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
|
-
- **`
|
|
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.
|
|
188
|
+
- **`llm`** — main LLM configuration. Optional keys include `temperature`, `max_tokens`, `timeout`, `reasoning_effort`, `context_window`, and `stream`. Values such as `reasoning_effort` are passed to the API as-is when set. `context_window` (tokens) overrides the built-in table for the active model; when unset, well-known models are matched automatically. `run --no-stream` overrides `stream`.
|
|
189
|
+
- **`models`** — named LLM profiles for runtime switching with `/model`. A profile is a partial settings dictionary; unset keys inherit from the main `llm`. Each profile can include `context_window` to set the context window for that model. `default` restores the main LLM configuration.
|
|
190
|
+
- **`subagent_llm`** — LLM configuration for `Agent` tool requests. Unset values inherit from the main `llm`. Set `profile` to reuse a profile from `models`. `context_window` can also be set here to override the inherited value for sub-agents. Precedence is: profile settings > explicit `subagent_llm` settings > main `llm` > environment variables.
|
|
193
191
|
- **`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
192
|
- **`mcp.servers`** — MCP server configuration. Requires the `[mcp]` extra. Each server supports `transport`, `command`, `args`, `env`, `url`, `headers`, `parallel`, `timeout`, and `enabled`.
|
|
195
193
|
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|