python-agent-harness 1.5.4.3__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.3/python_agent_harness.egg-info → python_agent_harness-1.5.4.6}/PKG-INFO +11 -13
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/README.md +10 -12
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/pyproject.toml +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/__init__.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/agent.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/cli.py +6 -13
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/client.py +18 -9
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/config.py +89 -63
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/persistence.py +0 -3
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/session.py +7 -8
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/token_estimator.py +3 -3
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/__init__.py +13 -6
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/agent_tool.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/filesystem.py +4 -0
- python_agent_harness-1.5.4.6/python_agent_harness/tools/glob_mac.py +97 -0
- python_agent_harness-1.5.4.6/python_agent_harness/tools/grep_mac.py +92 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6/python_agent_harness.egg-info}/PKG-INFO +11 -13
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/SOURCES.txt +2 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_cli.py +1 -3
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_client.py +43 -52
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_config.py +141 -83
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_filesystem.py +257 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_persistence.py +12 -15
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_scenarios.py +0 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_session.py +6 -8
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_subagent.py +0 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_subagent_isolation.py +1 -1
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_token_estimator.py +6 -6
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/LICENSE +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/__main__.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/commands.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/context_manager.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/diffrender.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/__init__.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/client.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/config.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/mcp/manager.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/models.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/planmode.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/agent.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/build-switch.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/commands/explain.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/compact.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/initialize.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/plan-mode.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/plan.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/review.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/subagent.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/summary.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/task-completion-rules.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts/title.md +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/prompts.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/subagent.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tool_runner.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/base.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/bash.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/diffapply.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/edit.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/edit_mac.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/glob.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/grep.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/insert.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/mcp.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/mkdir.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/planexit.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/question.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/read.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/skill.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/todo.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tools/write.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/__init__.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/commands.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/core.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/input.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness/tui/render.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/dependency_links.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/entry_points.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/requires.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/python_agent_harness.egg-info/top_level.txt +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/setup.cfg +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_agents_md.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_commands.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_concurrent_subagents.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_context_rules.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_diffrender.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_invariants.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_mcp.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_models.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_planmode.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_prompts.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/tests/test_todos_scope.py +0 -0
- {python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/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.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,13 +185,12 @@ 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 `
|
|
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
|
-
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL
|
|
193
|
+
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
196
194
|
- **Custom config** — use `--config PATH` or `PYTHON_AGENT_HARNESS_CONFIG`.
|
|
197
195
|
- **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
196
|
|
|
@@ -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,13 +162,12 @@ 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 `
|
|
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
|
-
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL
|
|
170
|
+
- **Configuration precedence** — code defaults < config file < `OPENAI_*` environment variables. Sub-agent settings also support `OPENAI_SUBAGENT_*` (`_BASE_URL`, `_API_KEY`, `_MODEL`).
|
|
173
171
|
- **Custom config** — use `--config PATH` or `PYTHON_AGENT_HARNESS_CONFIG`.
|
|
174
172
|
- **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
173
|
|
|
@@ -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']}")
|
|
@@ -218,17 +217,11 @@ def cmd_config(args: argparse.Namespace) -> int:
|
|
|
218
217
|
for name, profile in sorted(model_profiles.items()):
|
|
219
218
|
model_name = profile.get("model", "(inherited)")
|
|
220
219
|
base_url = profile.get("base_url", "(inherited)")
|
|
221
|
-
|
|
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}")
|
|
222
223
|
else:
|
|
223
224
|
print("models: (none configured — add a 'models' section to use /model)")
|
|
224
|
-
# Show context-window overrides
|
|
225
|
-
context_windows = config.load_context_windows_config(args.path)
|
|
226
|
-
if context_windows:
|
|
227
|
-
print("context_windows:")
|
|
228
|
-
for pattern, size in context_windows:
|
|
229
|
-
print(f" {pattern}: {size}")
|
|
230
|
-
else:
|
|
231
|
-
print("context_windows: (none configured — built-in table in config.py applies)")
|
|
232
225
|
return 0
|
|
233
226
|
|
|
234
227
|
|
{python_agent_harness-1.5.4.3 → 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.3 → 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
|
|
@@ -168,12 +169,12 @@ DEFAULT_LLM: dict = {
|
|
|
168
169
|
"base_url": "https://api.openai.com/v1",
|
|
169
170
|
"api_key": None,
|
|
170
171
|
"model": "gpt-5-mini",
|
|
171
|
-
"backend": "OpenAI-compatible",
|
|
172
172
|
"temperature": TEMPERATURE,
|
|
173
173
|
"max_tokens": MAX_TOKENS,
|
|
174
174
|
"timeout": 600.0,
|
|
175
175
|
"reasoning_effort": None,
|
|
176
176
|
"stream": True,
|
|
177
|
+
"context_window": None,
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
DEFAULT_PATHS: dict = {
|
|
@@ -182,8 +183,7 @@ DEFAULT_PATHS: dict = {
|
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
# 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
|
|
186
|
+
# the main LLM setting" (mirrors gptel-agent-harness-subagent-model).
|
|
187
187
|
# agent's LLM. ``profile`` references a named profile from the
|
|
188
188
|
# ``models`` section: its settings are applied on top of any explicit
|
|
189
189
|
# subagent_llm keys (profile wins), and unset keys still inherit the
|
|
@@ -193,12 +193,12 @@ DEFAULT_SUBAGENT_LLM: dict = {
|
|
|
193
193
|
"base_url": None,
|
|
194
194
|
"api_key": None,
|
|
195
195
|
"model": None,
|
|
196
|
-
"backend": None,
|
|
197
196
|
"temperature": None,
|
|
198
197
|
"max_tokens": None,
|
|
199
198
|
"timeout": None,
|
|
200
199
|
"reasoning_effort": None,
|
|
201
200
|
"stream": None,
|
|
201
|
+
"context_window": None,
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
CONFIG_TEMPLATE = """\
|
|
@@ -211,20 +211,18 @@ CONFIG_TEMPLATE = """\
|
|
|
211
211
|
"stream": true
|
|
212
212
|
}},
|
|
213
213
|
"models": {{
|
|
214
|
-
"_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.",
|
|
215
215
|
"deepseek": {{
|
|
216
216
|
"base_url": "https://api.deepseek.com/v1",
|
|
217
|
-
"model": "deepseek-chat"
|
|
217
|
+
"model": "deepseek-chat",
|
|
218
|
+
"context_window": 128000
|
|
218
219
|
}},
|
|
219
220
|
"openai": {{
|
|
220
221
|
"base_url": "https://api.openai.com/v1",
|
|
221
|
-
"model": "gpt-5-mini"
|
|
222
|
+
"model": "gpt-5-mini",
|
|
223
|
+
"context_window": 128000
|
|
222
224
|
}}
|
|
223
225
|
}},
|
|
224
|
-
"context_windows": {{
|
|
225
|
-
"_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.",
|
|
226
|
-
"deepseek-v4": 1000000
|
|
227
|
-
}},
|
|
228
226
|
"subagent_llm": {{
|
|
229
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).",
|
|
230
228
|
"profile": null,
|
|
@@ -235,7 +233,8 @@ CONFIG_TEMPLATE = """\
|
|
|
235
233
|
"max_tokens": null,
|
|
236
234
|
"timeout": null,
|
|
237
235
|
"reasoning_effort": null,
|
|
238
|
-
"stream": null
|
|
236
|
+
"stream": null,
|
|
237
|
+
"context_window": null
|
|
239
238
|
}},
|
|
240
239
|
"paths": {{
|
|
241
240
|
"_comment": "Optional overrides for context and skill directories. Absolute paths or ~ expansion supported.",
|
|
@@ -264,17 +263,31 @@ _ENV_OVERRIDES = {
|
|
|
264
263
|
"base_url": "OPENAI_BASE_URL",
|
|
265
264
|
"api_key": "OPENAI_API_KEY",
|
|
266
265
|
"model": "OPENAI_MODEL",
|
|
267
|
-
"backend": "OPENAI_BACKEND",
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
_SUBAGENT_ENV_OVERRIDES = {
|
|
271
269
|
"base_url": "OPENAI_SUBAGENT_BASE_URL",
|
|
272
270
|
"api_key": "OPENAI_SUBAGENT_API_KEY",
|
|
273
271
|
"model": "OPENAI_SUBAGENT_MODEL",
|
|
274
|
-
"backend": "OPENAI_SUBAGENT_BACKEND",
|
|
275
272
|
}
|
|
276
273
|
|
|
277
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
|
+
|
|
278
291
|
def _config_path(path: str | os.PathLike | None = None) -> Path:
|
|
279
292
|
"""Resolve the config file path: explicit arg > $PYTHON_AGENT_HARNESS_CONFIG > default."""
|
|
280
293
|
if path:
|
|
@@ -310,6 +323,9 @@ def load_llm_config(path: str | os.PathLike | None = None) -> dict:
|
|
|
310
323
|
|
|
311
324
|
The config file is JSON with an ``llm`` object (see `CONFIG_TEMPLATE`).
|
|
312
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".
|
|
313
329
|
"""
|
|
314
330
|
settings = dict(DEFAULT_LLM)
|
|
315
331
|
data = _read_config(path)
|
|
@@ -320,15 +336,24 @@ def load_llm_config(path: str | os.PathLike | None = None) -> dict:
|
|
|
320
336
|
"base_url",
|
|
321
337
|
"api_key",
|
|
322
338
|
"model",
|
|
323
|
-
"backend",
|
|
324
339
|
"temperature",
|
|
325
340
|
"max_tokens",
|
|
326
341
|
"timeout",
|
|
327
342
|
"reasoning_effort",
|
|
328
343
|
"stream",
|
|
344
|
+
"context_window",
|
|
329
345
|
):
|
|
330
346
|
if key in llm and llm[key] is not None:
|
|
331
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
|
+
)
|
|
332
357
|
for key, env in _ENV_OVERRIDES.items():
|
|
333
358
|
val = os.environ.get(env)
|
|
334
359
|
if val:
|
|
@@ -342,7 +367,7 @@ def load_subagent_llm_config(
|
|
|
342
367
|
) -> dict:
|
|
343
368
|
"""Resolve sub-agent LLM settings; unset keys inherit ``main``.
|
|
344
369
|
|
|
345
|
-
Mirrors gptel-agent-harness-subagent-model
|
|
370
|
+
Mirrors gptel-agent-harness-subagent-model: sub-agents
|
|
346
371
|
(the Agent tool) use their own LLM when configured, otherwise the
|
|
347
372
|
main agent's. Precedence: ``main`` settings < config file
|
|
348
373
|
``subagent_llm`` object < referenced ``models`` profile (when
|
|
@@ -379,12 +404,12 @@ def load_subagent_llm_config(
|
|
|
379
404
|
"base_url",
|
|
380
405
|
"api_key",
|
|
381
406
|
"model",
|
|
382
|
-
"backend",
|
|
383
407
|
"temperature",
|
|
384
408
|
"max_tokens",
|
|
385
409
|
"timeout",
|
|
386
410
|
"reasoning_effort",
|
|
387
411
|
"stream",
|
|
412
|
+
"context_window",
|
|
388
413
|
):
|
|
389
414
|
if key in profile and profile[key] is not None:
|
|
390
415
|
overrides[key] = profile[key]
|
|
@@ -480,61 +505,62 @@ def _match_context_window(model: str) -> int | None:
|
|
|
480
505
|
return None
|
|
481
506
|
|
|
482
507
|
|
|
483
|
-
def
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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``).
|
|
494
525
|
"""
|
|
526
|
+
# Try the llm.context_window from the config file first.
|
|
495
527
|
try:
|
|
496
|
-
data = _read_config(
|
|
528
|
+
data = _read_config(config_path)
|
|
497
529
|
except ValueError:
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
)
|
|
510
|
-
entries.append((pattern, size))
|
|
511
|
-
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
|
|
512
541
|
|
|
513
542
|
|
|
514
|
-
def
|
|
515
|
-
|
|
516
|
-
config_path: str | os.PathLike | None = None,
|
|
517
|
-
) -> int:
|
|
518
|
-
"""Get the context window for MODEL: config-file overrides, then
|
|
519
|
-
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.
|
|
520
545
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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.
|
|
525
552
|
|
|
526
553
|
Args:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
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.
|
|
533
559
|
"""
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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 ""
|
|
538
564
|
matched = _match_context_window(model)
|
|
539
565
|
if matched is not None:
|
|
540
566
|
return matched
|
{python_agent_harness-1.5.4.3 → python_agent_harness-1.5.4.6}/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.6}/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,12 +656,12 @@ 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,
|
|
666
662
|
"reasoning_effort": self.reasoning_effort,
|
|
667
663
|
"stream": self.stream,
|
|
664
|
+
"context_window": getattr(self.client, "_context_window", None),
|
|
668
665
|
}
|
|
669
666
|
for key, val in current.items():
|
|
670
667
|
merged.setdefault(key, val)
|
|
@@ -673,12 +670,12 @@ class Session:
|
|
|
673
670
|
"base_url",
|
|
674
671
|
"api_key",
|
|
675
672
|
"model",
|
|
676
|
-
"backend",
|
|
677
673
|
"temperature",
|
|
678
674
|
"max_tokens",
|
|
679
675
|
"timeout",
|
|
680
676
|
"reasoning_effort",
|
|
681
677
|
"stream",
|
|
678
|
+
"context_window",
|
|
682
679
|
):
|
|
683
680
|
if key in profile and profile[key] is not None:
|
|
684
681
|
merged[key] = profile[key]
|
|
@@ -690,8 +687,6 @@ class Session:
|
|
|
690
687
|
# the calibration factor is tokenizer-specific: a factor tuned
|
|
691
688
|
# to the previous model must not skew estimates for the new one
|
|
692
689
|
self.calibrator.reset()
|
|
693
|
-
self.backend = merged["backend"]
|
|
694
|
-
self.store.backend = merged["backend"]
|
|
695
690
|
self.temperature = merged["temperature"]
|
|
696
691
|
self.max_tokens = merged["max_tokens"]
|
|
697
692
|
if hasattr(self.client, "set_timeout"):
|
|
@@ -700,6 +695,10 @@ class Session:
|
|
|
700
695
|
self.client.timeout = merged["timeout"]
|
|
701
696
|
self.reasoning_effort = merged["reasoning_effort"]
|
|
702
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")
|
|
703
702
|
return True, f"switched to {name} ({self.model})"
|
|
704
703
|
|
|
705
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.
|