polyglav 0.37.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polyglav-0.37.0/LICENSE +21 -0
- polyglav-0.37.0/PKG-INFO +79 -0
- polyglav-0.37.0/README.md +64 -0
- polyglav-0.37.0/plugins/__init__.py +4 -0
- polyglav-0.37.0/plugins/polyglav-core-anthropic/manifest.json +13 -0
- polyglav-0.37.0/plugins/polyglav-core-anthropic/src/plugin.py +24 -0
- polyglav-0.37.0/plugins/polyglav-core-anthropic/tests/test_anthropic_provider.py +53 -0
- polyglav-0.37.0/plugins/polyglav-core-dev/manifest.json +17 -0
- polyglav-0.37.0/plugins/polyglav-core-dev/src/plugin.py +234 -0
- polyglav-0.37.0/plugins/polyglav-core-dev/tests/test_tools.py +200 -0
- polyglav-0.37.0/plugins/polyglav-core-edit/manifest.json +15 -0
- polyglav-0.37.0/plugins/polyglav-core-edit/src/plugin.py +103 -0
- polyglav-0.37.0/plugins/polyglav-core-edit/tests/test_tools.py +127 -0
- polyglav-0.37.0/plugins/polyglav-core-eval/manifest.json +20 -0
- polyglav-0.37.0/plugins/polyglav-core-eval/src/plugin.py +89 -0
- polyglav-0.37.0/plugins/polyglav-core-eval/tests/test_fixtures.py +46 -0
- polyglav-0.37.0/plugins/polyglav-core-exec/manifest.json +15 -0
- polyglav-0.37.0/plugins/polyglav-core-exec/src/plugin.py +127 -0
- polyglav-0.37.0/plugins/polyglav-core-exec/tests/test_tools.py +157 -0
- polyglav-0.37.0/plugins/polyglav-core-fs/manifest.json +19 -0
- polyglav-0.37.0/plugins/polyglav-core-fs/src/plugin.py +357 -0
- polyglav-0.37.0/plugins/polyglav-core-fs/tests/test_tools.py +335 -0
- polyglav-0.37.0/plugins/polyglav-core-git/manifest.json +15 -0
- polyglav-0.37.0/plugins/polyglav-core-git/src/plugin.py +203 -0
- polyglav-0.37.0/plugins/polyglav-core-git/tests/test_tools.py +220 -0
- polyglav-0.37.0/plugins/polyglav-core-groq/manifest.json +13 -0
- polyglav-0.37.0/plugins/polyglav-core-groq/src/plugin.py +11 -0
- polyglav-0.37.0/plugins/polyglav-core-groq/tests/test_groq_provider.py +36 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/manifest.json +19 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/src/client.py +260 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/src/client_http.py +141 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/src/client_stdio.py +176 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/src/jsonrpc.py +128 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/src/plugin.py +248 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/src/resources.py +31 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/src/server.py +184 -0
- polyglav-0.37.0/plugins/polyglav-core-mcp/tests/test_mcp.py +423 -0
- polyglav-0.37.0/plugins/polyglav-core-ollama/manifest.json +13 -0
- polyglav-0.37.0/plugins/polyglav-core-ollama/src/plugin.py +20 -0
- polyglav-0.37.0/plugins/polyglav-core-ollama/tests/test_ollama_provider.py +146 -0
- polyglav-0.37.0/plugins/polyglav-core-openai/manifest.json +13 -0
- polyglav-0.37.0/plugins/polyglav-core-openai/src/plugin.py +21 -0
- polyglav-0.37.0/plugins/polyglav-core-openai/tests/test_openai_provider.py +61 -0
- polyglav-0.37.0/plugins/polyglav-core-opencode/manifest.json +13 -0
- polyglav-0.37.0/plugins/polyglav-core-opencode/src/opencode.py +14 -0
- polyglav-0.37.0/plugins/polyglav-core-opencode/src/opencode_base.py +19 -0
- polyglav-0.37.0/plugins/polyglav-core-opencode/src/opencode_go.py +14 -0
- polyglav-0.37.0/plugins/polyglav-core-opencode/src/plugin.py +7 -0
- polyglav-0.37.0/plugins/polyglav-core-opencode/tests/test_opencode_providers.py +88 -0
- polyglav-0.37.0/plugins/polyglav-core-web/manifest.json +16 -0
- polyglav-0.37.0/plugins/polyglav-core-web/src/display.py +25 -0
- polyglav-0.37.0/plugins/polyglav-core-web/src/plugin.py +186 -0
- polyglav-0.37.0/plugins/polyglav-core-web/src/search.py +68 -0
- polyglav-0.37.0/plugins/polyglav-core-web/tests/test_tools.py +236 -0
- polyglav-0.37.0/plugins/polyglav-core-webhook/manifest.json +14 -0
- polyglav-0.37.0/plugins/polyglav-core-webhook/src/plugin.py +28 -0
- polyglav-0.37.0/plugins/polyglav-core-webhook/tests/test_webhook.py +67 -0
- polyglav-0.37.0/pyproject.toml +39 -0
- polyglav-0.37.0/setup.cfg +4 -0
- polyglav-0.37.0/src/polyglav/__init__.py +8 -0
- polyglav-0.37.0/src/polyglav/__main__.py +5 -0
- polyglav-0.37.0/src/polyglav/asks.py +141 -0
- polyglav-0.37.0/src/polyglav/bundled_roles.json +127 -0
- polyglav-0.37.0/src/polyglav/bundled_teams.json +54 -0
- polyglav-0.37.0/src/polyglav/chat.py +332 -0
- polyglav-0.37.0/src/polyglav/cli.py +796 -0
- polyglav-0.37.0/src/polyglav/commands/__init__.py +0 -0
- polyglav-0.37.0/src/polyglav/commands/builtins.py +1790 -0
- polyglav-0.37.0/src/polyglav/commands/registry.py +46 -0
- polyglav-0.37.0/src/polyglav/config.py +238 -0
- polyglav-0.37.0/src/polyglav/engine.py +1638 -0
- polyglav-0.37.0/src/polyglav/eval.py +303 -0
- polyglav-0.37.0/src/polyglav/fleet.py +597 -0
- polyglav-0.37.0/src/polyglav/focus.py +96 -0
- polyglav-0.37.0/src/polyglav/jobs.py +631 -0
- polyglav-0.37.0/src/polyglav/main.py +342 -0
- polyglav-0.37.0/src/polyglav/memory.py +86 -0
- polyglav-0.37.0/src/polyglav/models.py +96 -0
- polyglav-0.37.0/src/polyglav/modes.py +75 -0
- polyglav-0.37.0/src/polyglav/plugins/__init__.py +3 -0
- polyglav-0.37.0/src/polyglav/plugins/manager.py +477 -0
- polyglav-0.37.0/src/polyglav/providers/__init__.py +35 -0
- polyglav-0.37.0/src/polyglav/providers/base.py +237 -0
- polyglav-0.37.0/src/polyglav/providers/registry.py +129 -0
- polyglav-0.37.0/src/polyglav/roles.py +203 -0
- polyglav-0.37.0/src/polyglav/runs.py +114 -0
- polyglav-0.37.0/src/polyglav/scheduler.py +321 -0
- polyglav-0.37.0/src/polyglav/server.py +137 -0
- polyglav-0.37.0/src/polyglav/sessions/__init__.py +0 -0
- polyglav-0.37.0/src/polyglav/sessions/manager.py +277 -0
- polyglav-0.37.0/src/polyglav/sessions/render.py +329 -0
- polyglav-0.37.0/src/polyglav/sessions/turns.py +187 -0
- polyglav-0.37.0/src/polyglav/skills.py +149 -0
- polyglav-0.37.0/src/polyglav/teams.py +182 -0
- polyglav-0.37.0/src/polyglav/tools/__init__.py +0 -0
- polyglav-0.37.0/src/polyglav/tools/ask.py +236 -0
- polyglav-0.37.0/src/polyglav/tools/catalog.py +283 -0
- polyglav-0.37.0/src/polyglav/tools/delegate.py +182 -0
- polyglav-0.37.0/src/polyglav/tools/handoff.py +100 -0
- polyglav-0.37.0/src/polyglav/tools/policy.py +100 -0
- polyglav-0.37.0/src/polyglav/tools/registry.py +266 -0
- polyglav-0.37.0/src/polyglav/tools/team.py +154 -0
- polyglav-0.37.0/src/polyglav/ui.py +782 -0
- polyglav-0.37.0/src/polyglav/utils/__init__.py +0 -0
- polyglav-0.37.0/src/polyglav/utils/http.py +54 -0
- polyglav-0.37.0/src/polyglav.egg-info/PKG-INFO +79 -0
- polyglav-0.37.0/src/polyglav.egg-info/SOURCES.txt +161 -0
- polyglav-0.37.0/src/polyglav.egg-info/dependency_links.txt +1 -0
- polyglav-0.37.0/src/polyglav.egg-info/entry_points.txt +2 -0
- polyglav-0.37.0/src/polyglav.egg-info/top_level.txt +1 -0
- polyglav-0.37.0/tests/test_agent_loop.py +354 -0
- polyglav-0.37.0/tests/test_ask.py +149 -0
- polyglav-0.37.0/tests/test_asks.py +333 -0
- polyglav-0.37.0/tests/test_assistant.py +94 -0
- polyglav-0.37.0/tests/test_autonomous_supervisor.py +150 -0
- polyglav-0.37.0/tests/test_bundled_plugins.py +121 -0
- polyglav-0.37.0/tests/test_catalog.py +147 -0
- polyglav-0.37.0/tests/test_cli.py +585 -0
- polyglav-0.37.0/tests/test_commands.py +849 -0
- polyglav-0.37.0/tests/test_completion.py +78 -0
- polyglav-0.37.0/tests/test_composer.py +63 -0
- polyglav-0.37.0/tests/test_config.py +429 -0
- polyglav-0.37.0/tests/test_delegate.py +263 -0
- polyglav-0.37.0/tests/test_delegation_permissions.py +340 -0
- polyglav-0.37.0/tests/test_engine.py +808 -0
- polyglav-0.37.0/tests/test_eval.py +303 -0
- polyglav-0.37.0/tests/test_fleet.py +508 -0
- polyglav-0.37.0/tests/test_focus.py +251 -0
- polyglav-0.37.0/tests/test_focus_on_delegate.py +119 -0
- polyglav-0.37.0/tests/test_handoff.py +185 -0
- polyglav-0.37.0/tests/test_history.py +231 -0
- polyglav-0.37.0/tests/test_http.py +120 -0
- polyglav-0.37.0/tests/test_jobs.py +1071 -0
- polyglav-0.37.0/tests/test_memory.py +135 -0
- polyglav-0.37.0/tests/test_models.py +120 -0
- polyglav-0.37.0/tests/test_modes.py +205 -0
- polyglav-0.37.0/tests/test_plugin_suites.py +13 -0
- polyglav-0.37.0/tests/test_plugins.py +649 -0
- polyglav-0.37.0/tests/test_print.py +173 -0
- polyglav-0.37.0/tests/test_provider_session.py +54 -0
- polyglav-0.37.0/tests/test_providers.py +498 -0
- polyglav-0.37.0/tests/test_providers_registry.py +164 -0
- polyglav-0.37.0/tests/test_repl_input.py +160 -0
- polyglav-0.37.0/tests/test_roles.py +360 -0
- polyglav-0.37.0/tests/test_run_buffer.py +121 -0
- polyglav-0.37.0/tests/test_run_resume.py +173 -0
- polyglav-0.37.0/tests/test_runs.py +102 -0
- polyglav-0.37.0/tests/test_server.py +77 -0
- polyglav-0.37.0/tests/test_session_id.py +150 -0
- polyglav-0.37.0/tests/test_session_log.py +487 -0
- polyglav-0.37.0/tests/test_session_render.py +375 -0
- polyglav-0.37.0/tests/test_skills.py +259 -0
- polyglav-0.37.0/tests/test_subagent.py +242 -0
- polyglav-0.37.0/tests/test_team_loop.py +173 -0
- polyglav-0.37.0/tests/test_team_run.py +237 -0
- polyglav-0.37.0/tests/test_team_tool.py +184 -0
- polyglav-0.37.0/tests/test_teams.py +323 -0
- polyglav-0.37.0/tests/test_tool_calling.py +145 -0
- polyglav-0.37.0/tests/test_tool_policy.py +137 -0
- polyglav-0.37.0/tests/test_tool_registry.py +319 -0
- polyglav-0.37.0/tests/test_turns.py +197 -0
- polyglav-0.37.0/tests/test_ui.py +654 -0
- polyglav-0.37.0/tests/test_unattended.py +265 -0
polyglav-0.37.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Evgenij Myasnikov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
polyglav-0.37.0/PKG-INFO
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: polyglav
|
|
3
|
+
Version: 0.37.0
|
|
4
|
+
Summary: A lightweight agentic tooling core with API, CLI and REPL interfaces
|
|
5
|
+
Author: Evgenij Myasnikov
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/polyglav/polyglav
|
|
8
|
+
Project-URL: Issues, https://github.com/polyglav/polyglav/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/polyglav/polyglav/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: ai,ai-agents,api,assistant,cli,cloud,local,python,repl
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Polyglav
|
|
17
|
+
|
|
18
|
+
<p align="center"><img src="polyglav.svg" alt="Polyglav terminal session"></p>
|
|
19
|
+
|
|
20
|
+
**One agent, many heads**
|
|
21
|
+
|
|
22
|
+
<p>
|
|
23
|
+
<a href="https://pypi.org/project/polyglav/"><img src="https://img.shields.io/pypi/v/polyglav" alt="PyPI version"></a>
|
|
24
|
+
<a href="https://polyglav.github.io/polyglav/"><img src="https://img.shields.io/badge/docs-site-blue" alt="Site"></a>
|
|
25
|
+
<img src="https://img.shields.io/badge/python-%3E%3D3.10-blue" alt="Python >=3.10">
|
|
26
|
+
<img src="https://img.shields.io/github/actions/workflow/status/polyglav/polyglav/ci.yml?branch=main" alt="CI">
|
|
27
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
|
|
28
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
Polyglav is an AI agent that lives in your terminal. You describe an outcome once, it breaks the job into steps, brings in specialist agents for each one, and lets them work through it in order, handing results from one to the next as they finish. You stay at a single prompt the whole time. The name says it: poly means many, glav means heads.
|
|
32
|
+
|
|
33
|
+
## Why Polyglav
|
|
34
|
+
|
|
35
|
+
- **One prompt, many heads** - ask for an outcome, and Polyglav splits it across specialists that run one after another in the same process. As soon as one delivers, the next continues. No windows to switch and no babysitting.
|
|
36
|
+
- **It actually gets work done** - it reads and writes files, searches the web, runs commands, edits code, and commits, all behind permissions you control.
|
|
37
|
+
- **Grows with you** - reusable roles and skills, scheduled jobs that run unattended, and a supervisor for many agents at once.
|
|
38
|
+
- **Your machine, your keys** - config and complete session logs stay on your disk. Bring your own provider key, or run fully local.
|
|
39
|
+
- **Safe by default** - every tool is permission controlled. Anything outside your project asks first, and every action is on the record.
|
|
40
|
+
- **Zero dependencies** - pure Python standard library. Nothing to audit, no supply chain, no lockfile churn.
|
|
41
|
+
- **At home in the terminal** - a fast streaming REPL with dimmed thinking, markdown rendering, command completion, and history. No browser and no account.
|
|
42
|
+
- **Many providers, any model** - Ollama, OpenAI, Groq, Anthropic, OpenCode Zen/Go, or any OpenAI-compatible endpoint, picked automatically from the address.
|
|
43
|
+
|
|
44
|
+
## What you can do with it
|
|
45
|
+
|
|
46
|
+
- **Ship a change end to end** - ask for a feature and let a planner, a programmer, and a reviewer work through it in order.
|
|
47
|
+
- **Keep a project tidy** - schedule a job that refreshes docs, updates a changelog, or summarizes logs on a cron.
|
|
48
|
+
- **Research and write** - point it at a folder of notes or papers and ask for a summary with sources.
|
|
49
|
+
- **Run many agents** - give each folder its own agent and watch them all from one place.
|
|
50
|
+
- **Automate with the API** - drive the same loop from scripts, CI, or another program.
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
|
|
54
|
+
1. **Ask** - describe an outcome to the assistant in one sentence.
|
|
55
|
+
2. **Compose** - for bigger work it designs a small team: the specialists and the order they run in.
|
|
56
|
+
3. **Run** - stages run one after another, each with its own tools and permissions.
|
|
57
|
+
4. **Hand off** - as the work moves, each agent passes what it did to the next.
|
|
58
|
+
5. **Report** - watch progress, jump into any agent, or come back to a summary.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pipx install polyglav
|
|
64
|
+
polyglav
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For source installs, the REPL, the headless CLI, the HTTP API, Docker, plugins, and the full command reference, see [INSTALL.md](INSTALL.md).
|
|
68
|
+
|
|
69
|
+
## Learn more
|
|
70
|
+
|
|
71
|
+
- [AGENTS.md](AGENTS.md) - architecture and conventions
|
|
72
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) - the contribution workflow
|
|
73
|
+
- [INSTALL.md](INSTALL.md) - install, run, and command reference
|
|
74
|
+
- [VISION.md](VISION.md) - what Polyglav is and where it is going
|
|
75
|
+
- [docs/index.md](docs/index.md) - full documentation
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Polyglav
|
|
2
|
+
|
|
3
|
+
<p align="center"><img src="polyglav.svg" alt="Polyglav terminal session"></p>
|
|
4
|
+
|
|
5
|
+
**One agent, many heads**
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<a href="https://pypi.org/project/polyglav/"><img src="https://img.shields.io/pypi/v/polyglav" alt="PyPI version"></a>
|
|
9
|
+
<a href="https://polyglav.github.io/polyglav/"><img src="https://img.shields.io/badge/docs-site-blue" alt="Site"></a>
|
|
10
|
+
<img src="https://img.shields.io/badge/python-%3E%3D3.10-blue" alt="Python >=3.10">
|
|
11
|
+
<img src="https://img.shields.io/github/actions/workflow/status/polyglav/polyglav/ci.yml?branch=main" alt="CI">
|
|
12
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
|
|
13
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License">
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
Polyglav is an AI agent that lives in your terminal. You describe an outcome once, it breaks the job into steps, brings in specialist agents for each one, and lets them work through it in order, handing results from one to the next as they finish. You stay at a single prompt the whole time. The name says it: poly means many, glav means heads.
|
|
17
|
+
|
|
18
|
+
## Why Polyglav
|
|
19
|
+
|
|
20
|
+
- **One prompt, many heads** - ask for an outcome, and Polyglav splits it across specialists that run one after another in the same process. As soon as one delivers, the next continues. No windows to switch and no babysitting.
|
|
21
|
+
- **It actually gets work done** - it reads and writes files, searches the web, runs commands, edits code, and commits, all behind permissions you control.
|
|
22
|
+
- **Grows with you** - reusable roles and skills, scheduled jobs that run unattended, and a supervisor for many agents at once.
|
|
23
|
+
- **Your machine, your keys** - config and complete session logs stay on your disk. Bring your own provider key, or run fully local.
|
|
24
|
+
- **Safe by default** - every tool is permission controlled. Anything outside your project asks first, and every action is on the record.
|
|
25
|
+
- **Zero dependencies** - pure Python standard library. Nothing to audit, no supply chain, no lockfile churn.
|
|
26
|
+
- **At home in the terminal** - a fast streaming REPL with dimmed thinking, markdown rendering, command completion, and history. No browser and no account.
|
|
27
|
+
- **Many providers, any model** - Ollama, OpenAI, Groq, Anthropic, OpenCode Zen/Go, or any OpenAI-compatible endpoint, picked automatically from the address.
|
|
28
|
+
|
|
29
|
+
## What you can do with it
|
|
30
|
+
|
|
31
|
+
- **Ship a change end to end** - ask for a feature and let a planner, a programmer, and a reviewer work through it in order.
|
|
32
|
+
- **Keep a project tidy** - schedule a job that refreshes docs, updates a changelog, or summarizes logs on a cron.
|
|
33
|
+
- **Research and write** - point it at a folder of notes or papers and ask for a summary with sources.
|
|
34
|
+
- **Run many agents** - give each folder its own agent and watch them all from one place.
|
|
35
|
+
- **Automate with the API** - drive the same loop from scripts, CI, or another program.
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
1. **Ask** - describe an outcome to the assistant in one sentence.
|
|
40
|
+
2. **Compose** - for bigger work it designs a small team: the specialists and the order they run in.
|
|
41
|
+
3. **Run** - stages run one after another, each with its own tools and permissions.
|
|
42
|
+
4. **Hand off** - as the work moves, each agent passes what it did to the next.
|
|
43
|
+
5. **Report** - watch progress, jump into any agent, or come back to a summary.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pipx install polyglav
|
|
49
|
+
polyglav
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For source installs, the REPL, the headless CLI, the HTTP API, Docker, plugins, and the full command reference, see [INSTALL.md](INSTALL.md).
|
|
53
|
+
|
|
54
|
+
## Learn more
|
|
55
|
+
|
|
56
|
+
- [AGENTS.md](AGENTS.md) - architecture and conventions
|
|
57
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) - the contribution workflow
|
|
58
|
+
- [INSTALL.md](INSTALL.md) - install, run, and command reference
|
|
59
|
+
- [VISION.md](VISION.md) - what Polyglav is and where it is going
|
|
60
|
+
- [docs/index.md](docs/index.md) - full documentation
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "polyglav-core-anthropic",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Anthropic provider - bundled with polyglav",
|
|
5
|
+
"polyglav_version": ">=0.28.0,<1.0",
|
|
6
|
+
"python": ">=3.10",
|
|
7
|
+
"entry": "src/plugin.py",
|
|
8
|
+
"provides": {
|
|
9
|
+
"tools": [],
|
|
10
|
+
"providers": ["anthropic"],
|
|
11
|
+
"commands": []
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from polyglav.providers.base import OpenAICompatibleProvider
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AnthropicProvider(OpenAICompatibleProvider):
|
|
5
|
+
DEFAULT_BASE_URL = 'https://api.anthropic.com/v1'
|
|
6
|
+
DEFAULT_MODEL = 'claude-sonnet-4-20250514'
|
|
7
|
+
HOST_PATTERNS = ('anthropic.com',)
|
|
8
|
+
|
|
9
|
+
_BUDGET = {'low': 1024, 'medium': 2048, 'high': 4096}
|
|
10
|
+
|
|
11
|
+
def _reasoning_payload(self, payload: dict) -> dict:
|
|
12
|
+
if self._reasoning_off(self.reasoning):
|
|
13
|
+
payload['thinking'] = {'type': 'disabled'}
|
|
14
|
+
return payload
|
|
15
|
+
effort = self.reasoning
|
|
16
|
+
if effort is True or effort in ('on', 'auto'):
|
|
17
|
+
effort = 'medium'
|
|
18
|
+
budget = self._BUDGET.get(effort, 2048)
|
|
19
|
+
payload['thinking'] = {'type': 'enabled', 'budget_tokens': budget}
|
|
20
|
+
return payload
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def register_providers(providers):
|
|
24
|
+
providers['anthropic'] = AnthropicProvider
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import sys
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
SRC = Path(__file__).resolve().parents[1] / 'src'
|
|
7
|
+
if str(SRC) not in sys.path:
|
|
8
|
+
sys.path.insert(0, str(SRC))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _load_plugin():
|
|
12
|
+
name = 'polyglav_anthropic_provider_plugin'
|
|
13
|
+
spec = importlib.util.spec_from_file_location(name, str(SRC / 'plugin.py'))
|
|
14
|
+
module = importlib.util.module_from_spec(spec)
|
|
15
|
+
sys.modules[name] = module
|
|
16
|
+
spec.loader.exec_module(module)
|
|
17
|
+
return module
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
AnthropicProvider = _load_plugin().AnthropicProvider
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestAnthropicProvider(unittest.TestCase):
|
|
24
|
+
|
|
25
|
+
def test_defaults(self):
|
|
26
|
+
p = AnthropicProvider()
|
|
27
|
+
self.assertEqual(p.base_url, 'https://api.anthropic.com/v1')
|
|
28
|
+
self.assertEqual(p.model, 'claude-sonnet-4-20250514')
|
|
29
|
+
self.assertEqual(p._endpoint(), 'https://api.anthropic.com/v1/chat/completions')
|
|
30
|
+
|
|
31
|
+
def test_host_patterns(self):
|
|
32
|
+
self.assertIn('anthropic.com', AnthropicProvider.HOST_PATTERNS)
|
|
33
|
+
|
|
34
|
+
def test_reasoning_off(self):
|
|
35
|
+
p = AnthropicProvider(reasoning=False)
|
|
36
|
+
payload = p._payload([{'role': 'user', 'content': 'hi'}])
|
|
37
|
+
self.assertEqual(payload['thinking'], {'type': 'disabled'})
|
|
38
|
+
|
|
39
|
+
def test_reasoning_auto_maps_to_medium(self):
|
|
40
|
+
p = AnthropicProvider(reasoning='auto')
|
|
41
|
+
payload = p._payload([{'role': 'user', 'content': 'hi'}])
|
|
42
|
+
self.assertEqual(payload['thinking'],
|
|
43
|
+
{'type': 'enabled', 'budget_tokens': 2048})
|
|
44
|
+
|
|
45
|
+
def test_reasoning_high_budget(self):
|
|
46
|
+
p = AnthropicProvider(reasoning='high')
|
|
47
|
+
payload = p._payload([{'role': 'user', 'content': 'hi'}])
|
|
48
|
+
self.assertEqual(payload['thinking'],
|
|
49
|
+
{'type': 'enabled', 'budget_tokens': 4096})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == '__main__':
|
|
53
|
+
unittest.main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "polyglav-core-dev",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Developer workflow wrappers: code_test, code_lint, code_format - bundled with polyglav",
|
|
5
|
+
"polyglav_version": ">=0.13.0,<1.0",
|
|
6
|
+
"python": ">=3.10",
|
|
7
|
+
"entry": "src/plugin.py",
|
|
8
|
+
"provides": {
|
|
9
|
+
"tools": [
|
|
10
|
+
"code_test",
|
|
11
|
+
"code_lint",
|
|
12
|
+
"code_format"
|
|
13
|
+
],
|
|
14
|
+
"providers": [],
|
|
15
|
+
"commands": []
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
MAX_TIMEOUT = 600
|
|
7
|
+
DEFAULT_TIMEOUT = 300
|
|
8
|
+
|
|
9
|
+
DEFAULTS = {
|
|
10
|
+
'test': 'python -m unittest discover',
|
|
11
|
+
'lint': 'ruff check .',
|
|
12
|
+
'format': 'ruff format .',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _resolve_interpreter(argv: list[str]) -> list[str]:
|
|
17
|
+
if argv and argv[0] in ('python', 'python3'):
|
|
18
|
+
return [sys.executable] + argv[1:]
|
|
19
|
+
return argv
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _clamp_timeout(timeout) -> int:
|
|
23
|
+
try:
|
|
24
|
+
return max(1, min(int(timeout), MAX_TIMEOUT))
|
|
25
|
+
except (TypeError, ValueError):
|
|
26
|
+
return DEFAULT_TIMEOUT
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _cap(config=None) -> int:
|
|
30
|
+
if config is None:
|
|
31
|
+
return 0
|
|
32
|
+
try:
|
|
33
|
+
return max(0, int(config.get('tool_max_result_chars', 0)))
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _truncate(text: str, max_chars: int = 0) -> str:
|
|
39
|
+
if max_chars > 0 and len(text) > max_chars:
|
|
40
|
+
return text[:max_chars].rsplit('\n', 1)[0] + '\n... (truncated)'
|
|
41
|
+
return text
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _is_failure(result: str) -> bool:
|
|
45
|
+
for line in result.splitlines():
|
|
46
|
+
if line.startswith('exit '):
|
|
47
|
+
try:
|
|
48
|
+
return int(line[5:].strip()) != 0
|
|
49
|
+
except ValueError:
|
|
50
|
+
return False
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _cmd(config, key: str, default: str) -> list[str]:
|
|
55
|
+
raw = ''
|
|
56
|
+
if config is not None:
|
|
57
|
+
raw = str(config.get(key) or '')
|
|
58
|
+
if not raw.strip():
|
|
59
|
+
raw = default
|
|
60
|
+
try:
|
|
61
|
+
argv = shlex.split(raw)
|
|
62
|
+
except ValueError:
|
|
63
|
+
argv = raw.split()
|
|
64
|
+
argv = argv or [default]
|
|
65
|
+
return _resolve_interpreter(argv)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _is_discover(argv: list[str]) -> bool:
|
|
69
|
+
return (len(argv) >= 4 and argv[1] == '-m' and argv[2] == 'unittest'
|
|
70
|
+
and argv[3] == 'discover')
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _test_argv(config, target: str) -> list[str]:
|
|
74
|
+
argv = _cmd(config, 'dev.test_cmd', DEFAULTS['test'])
|
|
75
|
+
if not target:
|
|
76
|
+
return argv
|
|
77
|
+
if _is_discover(argv):
|
|
78
|
+
return [argv[0], '-m', 'unittest', target]
|
|
79
|
+
return argv + [target]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _run(argv: list[str], cwd: str | None, config,
|
|
83
|
+
timeout: int, target: str = '') -> str:
|
|
84
|
+
if cwd and not Path(cwd).is_dir():
|
|
85
|
+
return f'Error: cwd not found: {cwd}'
|
|
86
|
+
timeout = _clamp_timeout(timeout)
|
|
87
|
+
try:
|
|
88
|
+
proc = subprocess.run(
|
|
89
|
+
argv, capture_output=True, text=True, cwd=cwd, timeout=timeout,
|
|
90
|
+
)
|
|
91
|
+
except FileNotFoundError:
|
|
92
|
+
return f'Error: command not found: {argv[0]}'
|
|
93
|
+
except subprocess.TimeoutExpired as e:
|
|
94
|
+
out = e.stdout or ''
|
|
95
|
+
return f'Error: command timed out after {timeout}s' + (
|
|
96
|
+
f'\n{_truncate(out, _cap(config))}' if out else '')
|
|
97
|
+
except OSError as e:
|
|
98
|
+
return f'Error running command: {e}'
|
|
99
|
+
stdout = proc.stdout or ''
|
|
100
|
+
stderr = proc.stderr or ''
|
|
101
|
+
body = stdout if not stderr or stderr == stdout else f'{stdout}\n{stderr}'
|
|
102
|
+
lines = [f'$ {" ".join(argv)}']
|
|
103
|
+
if cwd:
|
|
104
|
+
lines.append(f'[cwd: {cwd}]')
|
|
105
|
+
lines.append(f'exit {proc.returncode}')
|
|
106
|
+
no_tests = ('NO TESTS RAN' in body or 'Ran 0 tests' in body
|
|
107
|
+
or proc.returncode == 5)
|
|
108
|
+
if target and no_tests:
|
|
109
|
+
lines.append(
|
|
110
|
+
f'Error: no tests matched "{target}" '
|
|
111
|
+
f'(unittest exit {proc.returncode})')
|
|
112
|
+
if body:
|
|
113
|
+
lines.append(_truncate(body, _cap(config)))
|
|
114
|
+
return '\n'.join(lines)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def register_tools(registry):
|
|
118
|
+
@registry.register(
|
|
119
|
+
name='code_test',
|
|
120
|
+
description='Run the project test suite. Uses the dev.test_cmd config key (default "python -m unittest discover"); pass target to run a specific test module or path. Reports the exit code and output. Use to verify code changes.',
|
|
121
|
+
parameters={
|
|
122
|
+
'type': 'object',
|
|
123
|
+
'properties': {
|
|
124
|
+
'target': {
|
|
125
|
+
'type': 'string',
|
|
126
|
+
'description': 'Optional test module, class, or path to run instead of the whole suite',
|
|
127
|
+
},
|
|
128
|
+
'cwd': {
|
|
129
|
+
'type': 'string',
|
|
130
|
+
'description': 'Working directory for the command (defaults to the current one)',
|
|
131
|
+
},
|
|
132
|
+
'timeout': {
|
|
133
|
+
'type': 'integer',
|
|
134
|
+
'description': 'Timeout in seconds before the command is killed (capped at 600)',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
'required': [],
|
|
138
|
+
},
|
|
139
|
+
category='exec',
|
|
140
|
+
permission='bash',
|
|
141
|
+
path_arg='cwd',
|
|
142
|
+
key_arg='target',
|
|
143
|
+
short='Run the project test suite',
|
|
144
|
+
echo=True,
|
|
145
|
+
glyph='$',
|
|
146
|
+
verb='Test',
|
|
147
|
+
error=_is_failure,
|
|
148
|
+
aliases=['run_tests', 'test_suite'],
|
|
149
|
+
param_aliases={'test': 'target', 'path': 'target', 'tests': 'target'},
|
|
150
|
+
)
|
|
151
|
+
def code_test(target: str = '', cwd: str | None = None,
|
|
152
|
+
timeout: int = DEFAULT_TIMEOUT, _config=None) -> str:
|
|
153
|
+
argv = _test_argv(_config, target)
|
|
154
|
+
return _run(argv, cwd, _config, timeout, target=target)
|
|
155
|
+
|
|
156
|
+
@registry.register(
|
|
157
|
+
name='code_lint',
|
|
158
|
+
description='Run the project linter. Uses the dev.lint_cmd config key (default "ruff check ."); pass target to lint a specific path. Reports the exit code and findings. Use to check code style before finishing.',
|
|
159
|
+
parameters={
|
|
160
|
+
'type': 'object',
|
|
161
|
+
'properties': {
|
|
162
|
+
'target': {
|
|
163
|
+
'type': 'string',
|
|
164
|
+
'description': 'Optional path to lint instead of the whole project',
|
|
165
|
+
},
|
|
166
|
+
'cwd': {
|
|
167
|
+
'type': 'string',
|
|
168
|
+
'description': 'Working directory for the command (defaults to the current one)',
|
|
169
|
+
},
|
|
170
|
+
'timeout': {
|
|
171
|
+
'type': 'integer',
|
|
172
|
+
'description': 'Timeout in seconds before the command is killed (capped at 600)',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
'required': [],
|
|
176
|
+
},
|
|
177
|
+
category='exec',
|
|
178
|
+
permission='bash',
|
|
179
|
+
path_arg='cwd',
|
|
180
|
+
key_arg='target',
|
|
181
|
+
short='Run the project linter',
|
|
182
|
+
echo=True,
|
|
183
|
+
glyph='$',
|
|
184
|
+
verb='Lint',
|
|
185
|
+
error=_is_failure,
|
|
186
|
+
aliases=['run_lint', 'lint_check'],
|
|
187
|
+
param_aliases={'path': 'target'},
|
|
188
|
+
)
|
|
189
|
+
def code_lint(target: str = '', cwd: str | None = None,
|
|
190
|
+
timeout: int = DEFAULT_TIMEOUT, _config=None) -> str:
|
|
191
|
+
argv = _cmd(_config, 'dev.lint_cmd', DEFAULTS['lint'])
|
|
192
|
+
if target:
|
|
193
|
+
argv = argv + [target]
|
|
194
|
+
return _run(argv, cwd, _config, timeout)
|
|
195
|
+
|
|
196
|
+
@registry.register(
|
|
197
|
+
name='code_format',
|
|
198
|
+
description='Run the project formatter. Uses the dev.format_cmd config key (default "ruff format ."); pass target to format a specific path. Reports the exit code and output. Use to format code before finishing.',
|
|
199
|
+
parameters={
|
|
200
|
+
'type': 'object',
|
|
201
|
+
'properties': {
|
|
202
|
+
'target': {
|
|
203
|
+
'type': 'string',
|
|
204
|
+
'description': 'Optional path to format instead of the whole project',
|
|
205
|
+
},
|
|
206
|
+
'cwd': {
|
|
207
|
+
'type': 'string',
|
|
208
|
+
'description': 'Working directory for the command (defaults to the current one)',
|
|
209
|
+
},
|
|
210
|
+
'timeout': {
|
|
211
|
+
'type': 'integer',
|
|
212
|
+
'description': 'Timeout in seconds before the command is killed (capped at 600)',
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
'required': [],
|
|
216
|
+
},
|
|
217
|
+
category='exec',
|
|
218
|
+
permission='bash',
|
|
219
|
+
path_arg='cwd',
|
|
220
|
+
key_arg='target',
|
|
221
|
+
short='Run the project formatter',
|
|
222
|
+
echo=True,
|
|
223
|
+
glyph='$',
|
|
224
|
+
verb='Format',
|
|
225
|
+
error=_is_failure,
|
|
226
|
+
aliases=['run_format', 'format_code'],
|
|
227
|
+
param_aliases={'path': 'target'},
|
|
228
|
+
)
|
|
229
|
+
def code_format(target: str = '', cwd: str | None = None,
|
|
230
|
+
timeout: int = DEFAULT_TIMEOUT, _config=None) -> str:
|
|
231
|
+
argv = _cmd(_config, 'dev.format_cmd', DEFAULTS['format'])
|
|
232
|
+
if target:
|
|
233
|
+
argv = argv + [target]
|
|
234
|
+
return _run(argv, cwd, _config, timeout)
|