agents-gl 0.0.1__py3-none-any.whl
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.
- agents_gl-0.0.1.dist-info/METADATA +53 -0
- agents_gl-0.0.1.dist-info/RECORD +115 -0
- agents_gl-0.0.1.dist-info/WHEEL +4 -0
- agents_gl-0.0.1.dist-info/entry_points.txt +6 -0
- agents_gl-0.0.1.dist-info/licenses/LICENSE +21 -0
- agl/__init__.py +0 -0
- agl/adapters/__init__.py +0 -0
- agl/adapters/claude_code/__init__.py +0 -0
- agl/adapters/claude_code/_session.py +127 -0
- agl/adapters/claude_code/_tools.py +72 -0
- agl/adapters/claude_code/fake.py +191 -0
- agl/adapters/claude_code/runner.py +160 -0
- agl/adapters/claude_code/translate.py +241 -0
- agl/adapters/filesystem/__init__.py +0 -0
- agl/adapters/filesystem/_documents.py +35 -0
- agl/adapters/filesystem/memory_store.py +67 -0
- agl/adapters/filesystem/store.py +140 -0
- agl/adapters/git/__init__.py +0 -0
- agl/adapters/git/_changes.py +53 -0
- agl/adapters/git/_conflicts.py +61 -0
- agl/adapters/git/_merging.py +144 -0
- agl/adapters/git/_patches.py +69 -0
- agl/adapters/git/_runner.py +192 -0
- agl/adapters/git/_snapshots.py +178 -0
- agl/adapters/git/_trees.py +139 -0
- agl/adapters/git/_working.py +82 -0
- agl/adapters/git/fake.py +306 -0
- agl/adapters/git/history.py +125 -0
- agl/adapters/git/integrator.py +98 -0
- agl/adapters/git/workspace.py +175 -0
- agl/adapters/openai/__init__.py +0 -0
- agl/adapters/openai/_http.py +137 -0
- agl/adapters/openai/_session.py +220 -0
- agl/adapters/openai/_tools.py +153 -0
- agl/adapters/openai/fake.py +183 -0
- agl/adapters/openai/runner.py +177 -0
- agl/adapters/openai/translate.py +247 -0
- agl/adapters/rich_terminal/__init__.py +0 -0
- agl/adapters/rich_terminal/_display.py +81 -0
- agl/adapters/rich_terminal/_render.py +49 -0
- agl/adapters/rich_terminal/headless.py +65 -0
- agl/adapters/rich_terminal/queues.py +117 -0
- agl/adapters/rich_terminal/scripted.py +95 -0
- agl/adapters/rich_terminal/terminal.py +235 -0
- agl/adapters/routing.py +55 -0
- agl/adapters/shell/__init__.py +0 -0
- agl/adapters/shell/fake.py +25 -0
- agl/adapters/shell/verifier.py +102 -0
- agl/adapters/system_clock.py +39 -0
- agl/api.py +204 -0
- agl/cli/__init__.py +0 -0
- agl/cli/commands/__init__.py +19 -0
- agl/cli/commands/clear.py +50 -0
- agl/cli/commands/init.py +35 -0
- agl/cli/commands/resume.py +51 -0
- agl/cli/commands/run.py +88 -0
- agl/cli/commands/workflows.py +68 -0
- agl/cli/exit_codes.py +21 -0
- agl/cli/main.py +173 -0
- agl/config/__init__.py +0 -0
- agl/config/container.py +198 -0
- agl/config/registry.py +69 -0
- agl/config/schema.py +81 -0
- agl/config/sources.py +202 -0
- agl/config/toml_file.py +347 -0
- agl/ports/__init__.py +0 -0
- agl/ports/agent.py +183 -0
- agl/ports/clock.py +13 -0
- agl/ports/errors.py +73 -0
- agl/ports/history.py +118 -0
- agl/ports/home_layout.py +169 -0
- agl/ports/ids.py +136 -0
- agl/ports/integration.py +82 -0
- agl/ports/run.py +253 -0
- agl/ports/store.py +69 -0
- agl/ports/terminal.py +105 -0
- agl/ports/tree_layout.py +90 -0
- agl/ports/verifier.py +24 -0
- agl/ports/workspace.py +91 -0
- agl/sdk/__init__.py +99 -0
- agl/sdk/_declarations.py +19 -0
- agl/sdk/_engine/__init__.py +0 -0
- agl/sdk/_engine/integration.py +251 -0
- agl/sdk/_engine/journal.py +376 -0
- agl/sdk/_engine/preflight.py +127 -0
- agl/sdk/_engine/services.py +31 -0
- agl/sdk/_engine/steps.py +188 -0
- agl/sdk/_engine/worktrees.py +48 -0
- agl/sdk/errors.py +23 -0
- agl/sdk/params.py +235 -0
- agl/sdk/roles.py +187 -0
- agl/sdk/terminal.py +23 -0
- agl/sdk/testing.py +45 -0
- agl/sdk/tools.py +333 -0
- agl/sdk/workflow.py +249 -0
- agl/testing.py +237 -0
- agl/workflows/__init__.py +0 -0
- agl/workflows/fix/__init__.py +20 -0
- agl/workflows/fix/asking.py +60 -0
- agl/workflows/fix/findings.py +51 -0
- agl/workflows/fix/prompts/implement.md +112 -0
- agl/workflows/fix/prompts/review.md +79 -0
- agl/workflows/fix/questions.py +33 -0
- agl/workflows/fix/roles.py +24 -0
- agl/workflows/fix/views/__init__.py +4 -0
- agl/workflows/fix/views/board.py +6 -0
- agl/workflows/fix/views/question.py +15 -0
- agl/workflows/split/__init__.py +35 -0
- agl/workflows/split/chunks.py +63 -0
- agl/workflows/split/prompts/implement.md +105 -0
- agl/workflows/split/prompts/plan.md +120 -0
- agl/workflows/split/roles.py +23 -0
- agl/workflows/split/views/__init__.py +4 -0
- agl/workflows/split/views/board.py +11 -0
- agl/workflows/split/views/conflict.py +24 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agents-gl
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A framework for running AI agent workflows against code repositories.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jashioq/AGL
|
|
6
|
+
Project-URL: Repository, https://github.com/jashioq/AGL
|
|
7
|
+
Author-email: Jan <janwylegalaandrzej@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,git,llm,orchestration,workflows
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
15
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Provides-Extra: all
|
|
18
|
+
Requires-Dist: agl[claude,terminal]; extra == 'all'
|
|
19
|
+
Provides-Extra: claude
|
|
20
|
+
Requires-Dist: claude-agent-sdk>=0.2.140; extra == 'claude'
|
|
21
|
+
Provides-Extra: terminal
|
|
22
|
+
Requires-Dist: rich>=15.0.0; extra == 'terminal'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# AGL
|
|
26
|
+
|
|
27
|
+
AGL runs AI agent workflows against a code repository. A workflow is ordinary Python that asks for
|
|
28
|
+
a worktree, hands roles to agents and lands what comes back: `fix` runs one worktree sequentially
|
|
29
|
+
with Claude implementing and OpenAI reviewing, `split` runs N chunks concurrently.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool install "agl[all]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `[all]` extra pulls in the Claude adapter (`claude-agent-sdk`) and the rich terminal. Without
|
|
38
|
+
it you get the core, which is stdlib-only.
|
|
39
|
+
|
|
40
|
+
## Python 3.14 or newer
|
|
41
|
+
|
|
42
|
+
This is the one thing most likely to trip you up. AGL declares `requires-python = ">=3.14"`, so a
|
|
43
|
+
plain `pip install agl` on an older interpreter refuses before it downloads anything, and it
|
|
44
|
+
reports that as not finding a version rather than as your interpreter being too old. `uv tool
|
|
45
|
+
install` sidesteps it by fetching a matching interpreter itself.
|
|
46
|
+
|
|
47
|
+
## Status
|
|
48
|
+
|
|
49
|
+
Early. 0.0.1 is a name claim rather than a release, and the surface may still change.
|
|
50
|
+
|
|
51
|
+
## Licence
|
|
52
|
+
|
|
53
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
agl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
agl/api.py,sha256=SFxNudzyd_wjNxKoHizXvCbYArbUBbjrDPy_8OGC8bo,8106
|
|
3
|
+
agl/testing.py,sha256=4NhJy2i4fZ4bP6LO3J6bYtrsRVDVV982AyLjolannXM,7816
|
|
4
|
+
agl/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
agl/adapters/routing.py,sha256=mWKR6RoMyoGR5hVXhSpb6xc0AASpmLvEV6-ahHIyPNk,2310
|
|
6
|
+
agl/adapters/system_clock.py,sha256=yChkyzdKS-Cjir1TOhgC9asp0-YSnOoE_z1yD9kzBSQ,1489
|
|
7
|
+
agl/adapters/claude_code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
agl/adapters/claude_code/_session.py,sha256=8jFXjge0B3bZD9vTtXZ7xcd4ezyKCzI6SRMqoBoIAHQ,4239
|
|
9
|
+
agl/adapters/claude_code/_tools.py,sha256=VN6ylJPs7kPIejlDaZc4Mt1J3DVGo-lfiXWvvai4JK0,2740
|
|
10
|
+
agl/adapters/claude_code/fake.py,sha256=qqteEYq37EDa_8RwnjgdX2iL4GtcVKSQi1BpKpXpwhk,6753
|
|
11
|
+
agl/adapters/claude_code/runner.py,sha256=WNjduqsa_xXs50_HysSgfkQmKe25l0OR7bZXUaCZ15o,6354
|
|
12
|
+
agl/adapters/claude_code/translate.py,sha256=lRLt43SGKuOen0ZE-hPQ1-fPH7RrVYyBubfp6yYL4bY,9138
|
|
13
|
+
agl/adapters/filesystem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
agl/adapters/filesystem/_documents.py,sha256=DiGjpQaIDYftnmoxaoqH7p4OGtcX_YBcZlyiCYL_CTg,1654
|
|
15
|
+
agl/adapters/filesystem/memory_store.py,sha256=d-O6ajH3GmsL4mS-X1viKK4dguY_ktzyNA439rofE28,2436
|
|
16
|
+
agl/adapters/filesystem/store.py,sha256=MooAKDsbK8VkYfnO4Fuflt0wMFtBDIWU8sXx4wipz2A,5488
|
|
17
|
+
agl/adapters/git/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
agl/adapters/git/_changes.py,sha256=NBIUYCzMHTFqp-ntZvsLxPtLAwKh09R0kOI7TK4Gki0,1735
|
|
19
|
+
agl/adapters/git/_conflicts.py,sha256=wn5UMzxc6mResM-lkKHE4lvt-_RAeIg0sIMZ1X6qZ6k,2385
|
|
20
|
+
agl/adapters/git/_merging.py,sha256=-7PtPk72xC19V13AmyO_oX2oWOGBLSrMSa2XQb20Tvs,5058
|
|
21
|
+
agl/adapters/git/_patches.py,sha256=bDQW-0pi3YKKAFX3BtP_U5ZW9di1TRmOVBsW5osRfvU,2902
|
|
22
|
+
agl/adapters/git/_runner.py,sha256=PxY5O6sNFz4nuBr0z-cnCyQPAqvduwk7zbxnyZ7Mg2I,7319
|
|
23
|
+
agl/adapters/git/_snapshots.py,sha256=vueFtdkqM5KyyRqy0wHS6OoM9PK5nBZESS50aZzvaMg,5761
|
|
24
|
+
agl/adapters/git/_trees.py,sha256=VNXsSrFaZeso1iQdDhr-RF4wSFUMT7x08urjqiwW8eE,4959
|
|
25
|
+
agl/adapters/git/_working.py,sha256=3aKCo6dOEiqtenwwos74cmUHahMaa5Ec_UaFunm4T7w,2726
|
|
26
|
+
agl/adapters/git/fake.py,sha256=sRrvZlYAr-I4U8CZNplqcnTAEdRoBHaH6jKKVXI1Mso,13357
|
|
27
|
+
agl/adapters/git/history.py,sha256=pSAG26us5UKH0elnKhwb7fYNS1S7C1jTOG5CWiKbIjQ,4710
|
|
28
|
+
agl/adapters/git/integrator.py,sha256=zFUkSqX3j-exHgK4LRVPbGypVB-2LsRvhbq-ocF07Bk,4052
|
|
29
|
+
agl/adapters/git/workspace.py,sha256=wOvcrNub5gSsm6HAx-X92FGjMbj4pfGQU4CmgMcrQPM,6565
|
|
30
|
+
agl/adapters/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
agl/adapters/openai/_http.py,sha256=nJeqkJjZ7atq2kFdNGMCTCtTZl7wLnknRRRXOGHhOEI,5278
|
|
32
|
+
agl/adapters/openai/_session.py,sha256=HAmv7ckAW7fdBE4_awmVy9hXbyZiYGhrIXfrJYnDtAI,7165
|
|
33
|
+
agl/adapters/openai/_tools.py,sha256=RQ5_-XxVpO32zi4MShwpKiz7A-rEDuPUpLmjvY7QbaI,5889
|
|
34
|
+
agl/adapters/openai/fake.py,sha256=xabiQ-VD2NapWIvIPGt8xGF7oOLVb6s5mDKiY9FvL_Q,6299
|
|
35
|
+
agl/adapters/openai/runner.py,sha256=zzrwfixRUgcjd2Jx8gT4vBmNPXuX8Get4nDWHxyUpco,5733
|
|
36
|
+
agl/adapters/openai/translate.py,sha256=CH-nrNrvRqYxL04RkRe2M2dyzh_mGnkQ8zG3YX0wfXE,10799
|
|
37
|
+
agl/adapters/rich_terminal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
agl/adapters/rich_terminal/_display.py,sha256=othKSbWO8cPUcL2OGy4y77XMKWU7C5pZG35yO8SFku0,2029
|
|
39
|
+
agl/adapters/rich_terminal/_render.py,sha256=-qjleot6ZA-Ev2-jHVNdRcHOy6oQSI1rQ9TsCt3d5LU,1605
|
|
40
|
+
agl/adapters/rich_terminal/headless.py,sha256=dMxyHZJphVuZR65uygueqIyFV1s0Jna-GNgJi2P0eeQ,2649
|
|
41
|
+
agl/adapters/rich_terminal/queues.py,sha256=Cj_vz2komrVE5Jx1ZdV1ikOit04GQyJcT4UZap3HLfA,4334
|
|
42
|
+
agl/adapters/rich_terminal/scripted.py,sha256=RABIosqrKD3MsrNZQcm3vSt0mngu997_XhK8Xj1cSA8,3418
|
|
43
|
+
agl/adapters/rich_terminal/terminal.py,sha256=y5US69pALhCaoSbHYG_3Ii076P25DCcQiBixYUCvHME,7828
|
|
44
|
+
agl/adapters/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
+
agl/adapters/shell/fake.py,sha256=jY2L5lAW1i-h_4QQ0WjLr6YTVe9583WEyEyOWlibVQw,1066
|
|
46
|
+
agl/adapters/shell/verifier.py,sha256=XsC_a9T5Dcku4dYRzYoZcV_YbLWIi2cFlfmQsf-D6Yw,3928
|
|
47
|
+
agl/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
agl/cli/exit_codes.py,sha256=X-6j0vMJu2x7yQJOnxX6JhEZIAwXlK_BOe_aW_oU6wg,816
|
|
49
|
+
agl/cli/main.py,sha256=rhkEQRNDd-ld4ZYQLAjnBprwjwWKF76uCPSDjr2n_Lo,7750
|
|
50
|
+
agl/cli/commands/__init__.py,sha256=3DOW8vPNqQbIz2D1dlhuhMv0DmYVYnJiMedo5bM1tkA,716
|
|
51
|
+
agl/cli/commands/clear.py,sha256=-rkOj__Ep0bbdJv63AsNkIWpfuN8BQzK0_W4FMNcdoM,1815
|
|
52
|
+
agl/cli/commands/init.py,sha256=KRyDdBrLhgca1UNJHeP-r-VlLva3DvR2d7MsvP126CU,1273
|
|
53
|
+
agl/cli/commands/resume.py,sha256=r6qzSal3n7d6KCVSZYp-a_HMI4xc6aQ3oCuvOko00mE,1647
|
|
54
|
+
agl/cli/commands/run.py,sha256=xKLFg_rwN1c5H9tR8cpmzvsVMNy4PjAV6IOIMrt0m74,2790
|
|
55
|
+
agl/cli/commands/workflows.py,sha256=AGTPAf10apekPHK1JUSnmHkqOPNoP7SDbAjSlJfwoXk,2590
|
|
56
|
+
agl/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
+
agl/config/container.py,sha256=CVrgFUmexwBFsf_CkZ664EHjZ9LrtFixEF4pxABs-mk,7392
|
|
58
|
+
agl/config/registry.py,sha256=jgVfqZZokO67yj5EFwC5ltrcVOSiOQOnwtelcIkHlw4,3100
|
|
59
|
+
agl/config/schema.py,sha256=S5KwGzSWTg_VNvQrBGhUrVFAq4NXZHhU-4cW9eOMEUc,2921
|
|
60
|
+
agl/config/sources.py,sha256=sInZSBtssUvrtoj0-6BBAHUn_1d35ZT1ItcE6cwX47s,7127
|
|
61
|
+
agl/config/toml_file.py,sha256=dSthg2qCYaAR_lMK3tPQTD9S1eP0BcSJMl92wPdkA10,12916
|
|
62
|
+
agl/ports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
|
+
agl/ports/agent.py,sha256=TvuVyEI0iTPTLi93JUU7bPfkYsm0E0CGjpJ7ysxRNww,5939
|
|
64
|
+
agl/ports/clock.py,sha256=10lDkNWHrVxKrZl43r2lH1f4It8lMTq95p7oovjKots,359
|
|
65
|
+
agl/ports/errors.py,sha256=vx3u2kUdLNYO9dp-F4htm-7xg0cAbQubou6tckMeA7E,1583
|
|
66
|
+
agl/ports/history.py,sha256=4yn0Zv3i-CVD-78HweKd4s67KfatO2eVhWljUHpIh8E,4690
|
|
67
|
+
agl/ports/home_layout.py,sha256=zZT7EgzoIDGk6Pa0M9pFHCIJrYLIVdCIDiXXn8LurtM,7094
|
|
68
|
+
agl/ports/ids.py,sha256=USiWbbp2w8WCLNARWHHiRaLoRqjcETZQaKTXSOLmadg,5427
|
|
69
|
+
agl/ports/integration.py,sha256=ATUWAzuNnAsE0HurhKZxSXJzp_LBW0eFw-iRG9-fobc,3419
|
|
70
|
+
agl/ports/run.py,sha256=88ez5yijeUlIVkIhVMOYG63f4Ai3WvvMtvRjDX1n9mA,10511
|
|
71
|
+
agl/ports/store.py,sha256=qNqVXxVzKguMQExn6awOsk8oBOb7gzDU_BB6HJj8iyY,2990
|
|
72
|
+
agl/ports/terminal.py,sha256=wwNFehOh0yB7v_J1vq1xor8Ht6BjHcWIuN2juFVi-sc,2862
|
|
73
|
+
agl/ports/tree_layout.py,sha256=h2OJ2dtzQ9aYj_QO4voW4qUUQF8zW4qa417L3qB8SrE,3945
|
|
74
|
+
agl/ports/verifier.py,sha256=rahPVsMmvwnTK6CNp64UKkheo5u2bTHKbFtMQGCyhD8,768
|
|
75
|
+
agl/ports/workspace.py,sha256=s4TLa6lrie0SInEATgtwMrf8T5LNAduykPWdMMSUqrw,3678
|
|
76
|
+
agl/sdk/__init__.py,sha256=k0qguxsWLdtQ8GV_IZd7NLaIp3F_RIVFNI3nwCkVV00,1564
|
|
77
|
+
agl/sdk/_declarations.py,sha256=PC6honf_xuEsEW6QPvGnkLw7LW093WxV5Gl-d644pWM,688
|
|
78
|
+
agl/sdk/errors.py,sha256=whKE5BVkDNFY8VuzyWWzh5cyHTezewWrip4Hdld9o-o,410
|
|
79
|
+
agl/sdk/params.py,sha256=PhFYxl5a3NPO8iBdAOItoM2wqgjoy6VtO54HAi7tHbs,11771
|
|
80
|
+
agl/sdk/roles.py,sha256=4zYWQlDpLFhydu2jUcI43VtQHVPcOL4R3xAh2MSifZc,8244
|
|
81
|
+
agl/sdk/terminal.py,sha256=2vNWjAammPjue8eq8y06p3s7js2kYWzcW-oZuA92bzY,290
|
|
82
|
+
agl/sdk/testing.py,sha256=si70wjDrTr0Wn_pj3BN_qltXmrk9N5IuKXGwWbPU4FA,1663
|
|
83
|
+
agl/sdk/tools.py,sha256=M5OWJgrrPGjeDvJKP7JszaRNyC4rN8l6jj-4KToQ3SQ,13960
|
|
84
|
+
agl/sdk/workflow.py,sha256=irvX5ZKPUg_Ub7TZ3Hhbo_5aHLJiu4sqBvYt2jELP7g,11322
|
|
85
|
+
agl/sdk/_engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
|
+
agl/sdk/_engine/integration.py,sha256=GzHLB3GbXiUDzBXfnXyDJ5mjUQH4huOi54epMJ7_H0Y,8958
|
|
87
|
+
agl/sdk/_engine/journal.py,sha256=YQh26G5YHjpgOCqL9qa5dBl-9Xd7l1JjipYHmOCutXY,14784
|
|
88
|
+
agl/sdk/_engine/preflight.py,sha256=aYyQirjUL250QyX1hnPF76LjGo6FJa4E9y3HQvnc7-g,6719
|
|
89
|
+
agl/sdk/_engine/services.py,sha256=lw8q-ze_OBP_7qu31-8GnW84ihCq4Tv_qJTWt56H6fU,641
|
|
90
|
+
agl/sdk/_engine/steps.py,sha256=qZDFU1_CmRSbQDpXl4jtN_xg-FmR-H3q2GssmGH6Ags,6966
|
|
91
|
+
agl/sdk/_engine/worktrees.py,sha256=JS3RMp6Q8b7Dw8jNXb2Fh7KxCGVueTT5IKroQq7lJlk,1824
|
|
92
|
+
agl/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
|
+
agl/workflows/fix/__init__.py,sha256=ZgDikYDZ7bWsfqBWucNLJsBnh6JE6bz5jIlIhg_aYGY,808
|
|
94
|
+
agl/workflows/fix/asking.py,sha256=y9_ssjrDj5qsYw6ib2OWn2ilysqA-1XhrMtJEKTyBQE,2298
|
|
95
|
+
agl/workflows/fix/findings.py,sha256=uraLQwRoVrXjRAwyb9g-QhrXztilFnRI48e6obX6M2M,2146
|
|
96
|
+
agl/workflows/fix/questions.py,sha256=hMKAvAVv_TiLjXQnB4wTxyG4oYZ2u28CjDvnY0Cpapc,1259
|
|
97
|
+
agl/workflows/fix/roles.py,sha256=jgHk-IHSG9_AHBWm_MWueo1ylnypAU3XfPONRaYUX-o,868
|
|
98
|
+
agl/workflows/fix/prompts/implement.md,sha256=3SUIEowwNdAbXDm2ca7PdFqtR5qZSkyMa9-QT9Fd2ig,7191
|
|
99
|
+
agl/workflows/fix/prompts/review.md,sha256=U_jwbqD472KRIVrJlYdc3a7ydN4Wpsc4hYGxxZO52eo,4739
|
|
100
|
+
agl/workflows/fix/views/__init__.py,sha256=wR9rlSLnOxQc2PCKyXfGMaCLDJDdjRpjFAlvzd5s1ao,147
|
|
101
|
+
agl/workflows/fix/views/board.py,sha256=nAG8j95Z8qXhL9gGcUstZXAkLiJhBbAvh8wXM06ZLBw,195
|
|
102
|
+
agl/workflows/fix/views/question.py,sha256=rT51_hkIDT2uJuhJ_b8XKMPq-MRXQ956xnW0GOxuCK4,550
|
|
103
|
+
agl/workflows/split/__init__.py,sha256=mkmLbFtaXMWnsSRfD_Ml_8iohKl46kZvMiB7TUMipas,1429
|
|
104
|
+
agl/workflows/split/chunks.py,sha256=-80WnCAV3Qk8EfPuB8eIbj_dEASSCmrbZtMt20MjnVI,2834
|
|
105
|
+
agl/workflows/split/roles.py,sha256=18gW7fkMEAP9m2l-VQzMnrBS2yO_gTbjQJZr3W7qih0,770
|
|
106
|
+
agl/workflows/split/prompts/implement.md,sha256=Zuo1Cb3FcPUecVQhqk69-ZWzvbi_6JDGFJAeMK7fne4,6679
|
|
107
|
+
agl/workflows/split/prompts/plan.md,sha256=RMfISttfjImUmBouSzk_bASV0KcQv4EClO9ajJLC-x4,6837
|
|
108
|
+
agl/workflows/split/views/__init__.py,sha256=Z_EP5drTjGryofFdaIsAkFYeKmNT0dxQpC6Hg9s94iY,139
|
|
109
|
+
agl/workflows/split/views/board.py,sha256=naUg6fHTLTF8NTdY3zwSbGwNj09di5TBCgF7nE_SUwk,404
|
|
110
|
+
agl/workflows/split/views/conflict.py,sha256=1IfFCufSiRH0rHzQX_wx8TMA6nhx-ScJzr2Lyr-zjBc,912
|
|
111
|
+
agents_gl-0.0.1.dist-info/METADATA,sha256=TO8zX1swferkXiTwb7dpAafpSW82sPLxkuKKFJzKzKI,1948
|
|
112
|
+
agents_gl-0.0.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
113
|
+
agents_gl-0.0.1.dist-info/entry_points.txt,sha256=QuUi5XKEQYmJArCNJe4X3BYD2uKttDoYJLXbvk5AiN8,121
|
|
114
|
+
agents_gl-0.0.1.dist-info/licenses/LICENSE,sha256=Z_632xNvXXjmrt3pUfa1CTphR5q4ieR8Wve0XAoflkk,1060
|
|
115
|
+
agents_gl-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jan
|
|
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.
|
agl/__init__.py
ADDED
|
File without changes
|
agl/adapters/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from typing import Final
|
|
3
|
+
from claude_agent_sdk import (
|
|
4
|
+
AssistantMessage,
|
|
5
|
+
ClaudeAgentOptions,
|
|
6
|
+
ClaudeSDKError,
|
|
7
|
+
ResultMessage,
|
|
8
|
+
TextBlock,
|
|
9
|
+
ToolUseBlock,
|
|
10
|
+
query,
|
|
11
|
+
)
|
|
12
|
+
from agl.adapters.claude_code._tools import Caller
|
|
13
|
+
from agl.adapters.claude_code.translate import activity, translated
|
|
14
|
+
from agl.ports.agent import ActivityReporter, AgentOutcome, AgentTask, StopReason
|
|
15
|
+
from agl.ports.errors import UpstreamUnavailable, UpstreamUnexpected
|
|
16
|
+
|
|
17
|
+
__all__ = ["Stderr", "outcome_of"]
|
|
18
|
+
|
|
19
|
+
_TERMINAL_REASONS: Final[dict[str, StopReason | None]] = {
|
|
20
|
+
"completed": StopReason.COMPLETED,
|
|
21
|
+
"max_turns": StopReason.LIMIT,
|
|
22
|
+
"aborted_streaming": None,
|
|
23
|
+
"aborted_tools": None,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_SUBTYPES: Final[dict[str, StopReason | None]] = {
|
|
27
|
+
"error_max_turns": StopReason.LIMIT,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_STOP_REASONS: Final[dict[str, StopReason | None]] = {
|
|
31
|
+
"end_turn": StopReason.COMPLETED,
|
|
32
|
+
"stop_sequence": StopReason.COMPLETED,
|
|
33
|
+
"tool_use": StopReason.COMPLETED,
|
|
34
|
+
"max_tokens": StopReason.LIMIT,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_STDERR_LINES: Final = 50
|
|
38
|
+
|
|
39
|
+
class Stderr:
|
|
40
|
+
def __init__(self) -> None:
|
|
41
|
+
self._lines: deque[str] = deque(maxlen=_STDERR_LINES)
|
|
42
|
+
|
|
43
|
+
def __call__(self, line: str) -> None:
|
|
44
|
+
self._lines.append(line.rstrip("\n"))
|
|
45
|
+
|
|
46
|
+
def tail(self) -> str:
|
|
47
|
+
return "\n".join(self._lines)
|
|
48
|
+
|
|
49
|
+
async def outcome_of(
|
|
50
|
+
task: AgentTask,
|
|
51
|
+
prompt: str,
|
|
52
|
+
options: ClaudeAgentOptions,
|
|
53
|
+
*,
|
|
54
|
+
caller: Caller,
|
|
55
|
+
on_activity: ActivityReporter | None,
|
|
56
|
+
stderr: Stderr,
|
|
57
|
+
) -> AgentOutcome:
|
|
58
|
+
said = ""
|
|
59
|
+
reported: ResultMessage | None = None
|
|
60
|
+
try:
|
|
61
|
+
async for message in query(prompt=prompt, options=options):
|
|
62
|
+
if isinstance(message, AssistantMessage):
|
|
63
|
+
said = _read(message, task, said, on_activity)
|
|
64
|
+
elif isinstance(message, ResultMessage):
|
|
65
|
+
reported = message
|
|
66
|
+
if caller.failure is not None:
|
|
67
|
+
break
|
|
68
|
+
except ClaudeSDKError as error:
|
|
69
|
+
limited = _limited(reported, said)
|
|
70
|
+
if limited is not None:
|
|
71
|
+
return limited
|
|
72
|
+
raise translated(error) from error
|
|
73
|
+
if caller.failure is not None:
|
|
74
|
+
raise caller.failure
|
|
75
|
+
if reported is None:
|
|
76
|
+
raise UpstreamUnexpected(
|
|
77
|
+
f"the Claude Code CLI ran and never said how the run ended, so AGL has no outcome to "
|
|
78
|
+
f"report for it. The same call would answer the same way - this is a version mismatch "
|
|
79
|
+
f"or an AGL bug, not a busy backend. The CLI's last words were: "
|
|
80
|
+
f"{stderr.tail() or '(it printed nothing)'}"
|
|
81
|
+
)
|
|
82
|
+
limited = _limited(reported, said)
|
|
83
|
+
if limited is not None:
|
|
84
|
+
return limited
|
|
85
|
+
if reported.is_error:
|
|
86
|
+
raise UpstreamUnavailable(
|
|
87
|
+
f"the Claude Code CLI ended the run with an error instead of a result: "
|
|
88
|
+
f"{reported.result or reported.subtype}. Nothing usable came back from the far side, "
|
|
89
|
+
f"so the same call may well succeed once whatever stopped it is fixed"
|
|
90
|
+
)
|
|
91
|
+
return _answered(reported, said)
|
|
92
|
+
|
|
93
|
+
def _read(
|
|
94
|
+
message: AssistantMessage,
|
|
95
|
+
task: AgentTask,
|
|
96
|
+
said: str,
|
|
97
|
+
on_activity: ActivityReporter | None,
|
|
98
|
+
) -> str:
|
|
99
|
+
for block in message.content:
|
|
100
|
+
if isinstance(block, ToolUseBlock):
|
|
101
|
+
if on_activity is not None:
|
|
102
|
+
on_activity(activity(block, task.workspace))
|
|
103
|
+
elif (
|
|
104
|
+
isinstance(block, TextBlock)
|
|
105
|
+
and block.text.strip()
|
|
106
|
+
and message.parent_tool_use_id is None
|
|
107
|
+
):
|
|
108
|
+
said = block.text
|
|
109
|
+
return said
|
|
110
|
+
|
|
111
|
+
def _limited(reported: ResultMessage | None, said: str) -> AgentOutcome | None:
|
|
112
|
+
if reported is None or _stopped(reported) is not StopReason.LIMIT:
|
|
113
|
+
return None
|
|
114
|
+
return _answered(reported, said)
|
|
115
|
+
|
|
116
|
+
def _answered(reported: ResultMessage, said: str) -> AgentOutcome:
|
|
117
|
+
return AgentOutcome(stop_reason=_stopped(reported), text=reported.result or said)
|
|
118
|
+
|
|
119
|
+
def _stopped(reported: ResultMessage) -> StopReason | None:
|
|
120
|
+
for value, table in (
|
|
121
|
+
(reported.terminal_reason, _TERMINAL_REASONS),
|
|
122
|
+
(reported.subtype, _SUBTYPES),
|
|
123
|
+
(reported.stop_reason, _STOP_REASONS),
|
|
124
|
+
):
|
|
125
|
+
if value is not None and value in table:
|
|
126
|
+
return table[value]
|
|
127
|
+
return None
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Any, Final
|
|
3
|
+
from claude_agent_sdk import McpServerConfig, SdkMcpTool, create_sdk_mcp_server
|
|
4
|
+
from agl.ports.agent import Tool, ToolResult
|
|
5
|
+
from agl.ports.run import JsonValue
|
|
6
|
+
|
|
7
|
+
__all__ = ["ASKING_MECHANISMS_DENIED", "Caller", "servers"]
|
|
8
|
+
|
|
9
|
+
# Absent from a probed session even on a machine carrying
|
|
10
|
+
# `CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL`, and denied anyway: a rule naming a tool that is not
|
|
11
|
+
# there costs a startup warning, and a tool that is there and unnamed costs the restriction.
|
|
12
|
+
ASKING_MECHANISMS_DENIED: Final = ("AskUserQuestion",)
|
|
13
|
+
|
|
14
|
+
_SUPPLIED: Final = "agl"
|
|
15
|
+
|
|
16
|
+
_FAILED: Final = (
|
|
17
|
+
"{name} could not do what it was asked: {raised}. This task is being stopped because of it. "
|
|
18
|
+
"Nothing you do from here is kept, so do not call it again and do not work around it."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_STOPPING: Final = (
|
|
22
|
+
"{name} was not called. This task is already being stopped because a tool could not do what "
|
|
23
|
+
"it was asked: {raised}. Nothing you do from here is kept."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def servers(tools: tuple[Tool, ...], caller: Caller) -> dict[str, McpServerConfig]:
|
|
27
|
+
return {
|
|
28
|
+
_SUPPLIED: create_sdk_mcp_server(
|
|
29
|
+
name=_SUPPLIED, tools=[_wrapped(declared, caller) for declared in tools]
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class Caller:
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self.failure: Exception | None = None
|
|
36
|
+
|
|
37
|
+
def fail(self, raised: Exception) -> None:
|
|
38
|
+
if self.failure is None:
|
|
39
|
+
self.failure = raised
|
|
40
|
+
|
|
41
|
+
async def handled(self, tool: Tool, payload: Mapping[str, JsonValue]) -> ToolResult:
|
|
42
|
+
if self.failure is not None:
|
|
43
|
+
return ToolResult(
|
|
44
|
+
text=_STOPPING.format(name=tool.name, raised=self.failure), rejected=True
|
|
45
|
+
)
|
|
46
|
+
try:
|
|
47
|
+
return await tool.handler(payload)
|
|
48
|
+
except Exception as raised:
|
|
49
|
+
self.fail(raised)
|
|
50
|
+
return ToolResult(text=_FAILED.format(name=tool.name, raised=raised), rejected=True)
|
|
51
|
+
|
|
52
|
+
def _wrapped(declared: Tool, caller: Caller) -> SdkMcpTool[Any]:
|
|
53
|
+
|
|
54
|
+
async def invoked(payload: dict[str, Any]) -> dict[str, Any]:
|
|
55
|
+
outcome = await caller.handled(declared, payload)
|
|
56
|
+
return _result(outcome.text, rejected=outcome.rejected)
|
|
57
|
+
|
|
58
|
+
return SdkMcpTool(
|
|
59
|
+
name=declared.name,
|
|
60
|
+
description=declared.description,
|
|
61
|
+
input_schema=_schema(declared.payload_schema),
|
|
62
|
+
handler=invoked,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def _schema(payload_schema: Mapping[str, JsonValue]) -> dict[str, Any]:
|
|
66
|
+
schema = dict(payload_schema)
|
|
67
|
+
schema.setdefault("type", "object")
|
|
68
|
+
schema.setdefault("properties", {})
|
|
69
|
+
return schema
|
|
70
|
+
|
|
71
|
+
def _result(text: str, *, rejected: bool = False) -> dict[str, Any]:
|
|
72
|
+
return {"content": [{"type": "text", "text": text}], "is_error": rejected}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
3
|
+
from typing import Final, cast
|
|
4
|
+
from agl.ports.agent import (
|
|
5
|
+
ActivityReporter,
|
|
6
|
+
AgentOutcome,
|
|
7
|
+
AgentRunner,
|
|
8
|
+
AgentTask,
|
|
9
|
+
Capability,
|
|
10
|
+
Claude,
|
|
11
|
+
ModelId,
|
|
12
|
+
StopReason,
|
|
13
|
+
Tool,
|
|
14
|
+
ToolResult,
|
|
15
|
+
)
|
|
16
|
+
from agl.ports.errors import InputError
|
|
17
|
+
from agl.ports.run import JsonValue
|
|
18
|
+
|
|
19
|
+
__all__ = ["Conversation", "FakeAgentRunner", "Script", "unscripted"]
|
|
20
|
+
|
|
21
|
+
_CAPABILITIES: Final = frozenset(
|
|
22
|
+
{
|
|
23
|
+
Capability.FILE_EDIT,
|
|
24
|
+
Capability.SHELL,
|
|
25
|
+
Capability.TOOL_CALLING,
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
_ROUNDS: Final = 8
|
|
30
|
+
|
|
31
|
+
_OPENING: Final = "Read: the task AGL's Claude Code fake was given"
|
|
32
|
+
|
|
33
|
+
_PLACEHOLDER: Final = "AGL's Claude Code fake produced this: no model was involved."
|
|
34
|
+
|
|
35
|
+
_CLOSING: Final = (
|
|
36
|
+
"AGL's Claude Code fake ran this task with a script where the model would be. It read nothing "
|
|
37
|
+
"in the workspace and changed nothing there."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_FAILED: Final = (
|
|
41
|
+
"{name} could not do what it was asked: {raised}. This task is being stopped because of it. "
|
|
42
|
+
"Nothing you do from here is kept, so do not call it again and do not work around it."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_STOPPING: Final = (
|
|
46
|
+
"{name} was not called. This task is already being stopped because a tool could not do what "
|
|
47
|
+
"it was asked: {raised}. Nothing you do from here is kept."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
class Conversation:
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
task: AgentTask,
|
|
54
|
+
*,
|
|
55
|
+
on_activity: ActivityReporter | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
self.task = task
|
|
58
|
+
|
|
59
|
+
self.failure: Exception | None = None
|
|
60
|
+
|
|
61
|
+
self._on_activity = on_activity
|
|
62
|
+
self._tools: dict[str, Tool] = {declared.name: declared for declared in task.tools}
|
|
63
|
+
|
|
64
|
+
async def call(self, tool: str, payload: Mapping[str, JsonValue]) -> ToolResult:
|
|
65
|
+
declared = self._tools.get(tool)
|
|
66
|
+
if declared is None:
|
|
67
|
+
known = sorted(self._tools)
|
|
68
|
+
raise InputError(
|
|
69
|
+
f"this task declares no tool called {tool!r}: it declares {known}. A session "
|
|
70
|
+
f"registers exactly the tools the task carries, so a call to anything else is not "
|
|
71
|
+
f"a move any agent could have made, and a script that can make it is a script that "
|
|
72
|
+
f"proves a workflow works with a tool the workflow never declared"
|
|
73
|
+
)
|
|
74
|
+
arguments = _as_json(payload, tool)
|
|
75
|
+
if self.failure is not None:
|
|
76
|
+
return ToolResult(
|
|
77
|
+
text=_STOPPING.format(name=declared.name, raised=self.failure), rejected=True
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
return await declared.handler(arguments)
|
|
81
|
+
except Exception as raised:
|
|
82
|
+
self.failure = raised
|
|
83
|
+
return ToolResult(
|
|
84
|
+
text=_FAILED.format(name=declared.name, raised=raised), rejected=True
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def report(self, line: str) -> None:
|
|
88
|
+
if self._on_activity is not None:
|
|
89
|
+
self._on_activity(line)
|
|
90
|
+
|
|
91
|
+
type Script = Callable[[Conversation], Awaitable[AgentOutcome]]
|
|
92
|
+
|
|
93
|
+
async def unscripted(conversation: Conversation) -> AgentOutcome:
|
|
94
|
+
conversation.report(_OPENING)
|
|
95
|
+
|
|
96
|
+
called: list[str] = []
|
|
97
|
+
for declared in conversation.task.tools:
|
|
98
|
+
if conversation.failure is not None:
|
|
99
|
+
break
|
|
100
|
+
said = _PLACEHOLDER
|
|
101
|
+
for _ in range(_ROUNDS):
|
|
102
|
+
conversation.report(f"{declared.name}: {said}")
|
|
103
|
+
result = await conversation.call(declared.name, _payload(declared.payload_schema, said))
|
|
104
|
+
called.append(declared.name)
|
|
105
|
+
if not result.rejected or conversation.failure is not None:
|
|
106
|
+
break
|
|
107
|
+
said = result.text
|
|
108
|
+
|
|
109
|
+
return AgentOutcome(stop_reason=StopReason.COMPLETED, text=_said(called))
|
|
110
|
+
|
|
111
|
+
class FakeAgentRunner(AgentRunner):
|
|
112
|
+
def __init__(self, script: Script | None = None) -> None:
|
|
113
|
+
self._script: Final = script if script is not None else unscripted
|
|
114
|
+
|
|
115
|
+
async def capabilities(self, model: ModelId) -> frozenset[Capability]:
|
|
116
|
+
_check_model(model)
|
|
117
|
+
return _CAPABILITIES
|
|
118
|
+
|
|
119
|
+
async def check_ready(self, model: ModelId) -> None:
|
|
120
|
+
_check_model(model)
|
|
121
|
+
|
|
122
|
+
async def run(
|
|
123
|
+
self,
|
|
124
|
+
task: AgentTask,
|
|
125
|
+
*,
|
|
126
|
+
on_activity: ActivityReporter | None = None,
|
|
127
|
+
) -> AgentOutcome:
|
|
128
|
+
_check_model(task.model)
|
|
129
|
+
conversation = Conversation(task, on_activity=on_activity)
|
|
130
|
+
outcome = await self._script(conversation)
|
|
131
|
+
if conversation.failure is not None:
|
|
132
|
+
raise conversation.failure
|
|
133
|
+
return outcome
|
|
134
|
+
|
|
135
|
+
def _check_model(model: ModelId) -> None:
|
|
136
|
+
if not isinstance(model, Claude):
|
|
137
|
+
served = sorted(str(member) for member in Claude)
|
|
138
|
+
raise InputError(
|
|
139
|
+
f"the Claude Code fake cannot run {str(model)!r}: it serves {served} and nothing "
|
|
140
|
+
f"else. It will not stand in another model for this one - the model was named beside "
|
|
141
|
+
f"the prompt because the choice was semantic, and substituting answers a different "
|
|
142
|
+
f"question than the one the workflow asked"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _as_json(payload: Mapping[str, JsonValue], tool: str) -> dict[str, JsonValue]:
|
|
146
|
+
try:
|
|
147
|
+
return cast(dict[str, JsonValue], json.loads(json.dumps(payload, allow_nan=False)))
|
|
148
|
+
except (TypeError, ValueError) as unwritable:
|
|
149
|
+
raise InputError(
|
|
150
|
+
f"the payload for tool {tool!r} is not JSON: {unwritable}. A tool call reaches a real "
|
|
151
|
+
f"handler as JSON a model produced, and a step's result is written down as JSON, so a "
|
|
152
|
+
f"payload that cannot be one is a call no run could have made"
|
|
153
|
+
) from unwritable
|
|
154
|
+
|
|
155
|
+
def _payload(schema: Mapping[str, JsonValue], said: str) -> dict[str, JsonValue]:
|
|
156
|
+
properties = schema.get("properties")
|
|
157
|
+
required = schema.get("required")
|
|
158
|
+
if not isinstance(properties, dict) or not isinstance(required, list):
|
|
159
|
+
return {}
|
|
160
|
+
return {
|
|
161
|
+
name: _value(properties[name], said)
|
|
162
|
+
for name in required
|
|
163
|
+
if isinstance(name, str) and name in properties
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
def _value(described: JsonValue, said: str) -> JsonValue:
|
|
167
|
+
if not isinstance(described, dict):
|
|
168
|
+
return said
|
|
169
|
+
choices = described.get("enum")
|
|
170
|
+
if isinstance(choices, list) and choices:
|
|
171
|
+
return choices[0]
|
|
172
|
+
kind = described.get("type")
|
|
173
|
+
if isinstance(kind, list):
|
|
174
|
+
kind = next((one for one in kind if isinstance(one, str)), None)
|
|
175
|
+
if kind == "object":
|
|
176
|
+
return _payload(described, said)
|
|
177
|
+
if kind == "array":
|
|
178
|
+
return []
|
|
179
|
+
if kind in ("integer", "number"):
|
|
180
|
+
return 0
|
|
181
|
+
if kind == "boolean":
|
|
182
|
+
return False
|
|
183
|
+
if kind == "null":
|
|
184
|
+
return None
|
|
185
|
+
return said
|
|
186
|
+
|
|
187
|
+
def _said(called: list[str]) -> str:
|
|
188
|
+
parts = [_CLOSING]
|
|
189
|
+
if called:
|
|
190
|
+
parts.append(f"It called: {', '.join(called)}.")
|
|
191
|
+
return " ".join(parts)
|