openshell-agent-runner 0.0.1__tar.gz → 0.0.3__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.
- openshell_agent_runner-0.0.3/PKG-INFO +138 -0
- openshell_agent_runner-0.0.3/README.md +115 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/pyproject.toml +2 -1
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/cli.py +46 -8
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/config.py +107 -24
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/harnesses/pi/resources.py +32 -4
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/harnesses/pi/runtime/extensions/validate-tools.ts +42 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/harnesses/pi/runtime/image/exec.sh +1 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/openshell.py +36 -4
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/profile_init.py +1 -1
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer/profile.yaml +21 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer/prompt-repository.md +15 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer/schemas/review.json +159 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer/skills/review-code/SKILL.md +111 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer/skills/review-code/agents/openai.yaml +4 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/models.json +19 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/policy.yaml +15 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/profile.yaml +21 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/prompt-document.md +15 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/schemas/review.json +160 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/settings.json +5 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/skills/review-technical-writing/SKILL.md +120 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/technical-writing-reviewer/skills/review-technical-writing/agents/openai.yaml +4 -0
- openshell_agent_runner-0.0.3/src/openshell_agent_runner/prompt_templates.py +48 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/runner.py +144 -26
- openshell_agent_runner-0.0.1/PKG-INFO +0 -404
- openshell_agent_runner-0.0.1/README.md +0 -381
- openshell_agent_runner-0.0.1/src/openshell_agent_runner/profiles/reviewer/profile.yaml +0 -12
- openshell_agent_runner-0.0.1/src/openshell_agent_runner/profiles/reviewer/prompt.md +0 -5
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/.gitignore +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/LICENSE +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/__init__.py +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/artifacts.py +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/errors.py +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/harnesses/__init__.py +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/harnesses/pi/__init__.py +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/harnesses/pi/runtime/extensions/submit-result.ts +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/harnesses/pi/runtime/image/Dockerfile +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/harnesses/resources.py +0 -0
- {openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/profiles/__init__.py +0 -0
- {openshell_agent_runner-0.0.1/src/openshell_agent_runner/profiles/reviewer → openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer}/models.json +0 -0
- {openshell_agent_runner-0.0.1/src/openshell_agent_runner/profiles/reviewer → openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer}/policy.yaml +0 -0
- {openshell_agent_runner-0.0.1/src/openshell_agent_runner/profiles/reviewer → openshell_agent_runner-0.0.3/src/openshell_agent_runner/profiles/code-reviewer}/settings.json +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: openshell-agent-runner
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Launch ephemeral agents for single tasks in OpenShell sandboxes.
|
|
5
|
+
Project-URL: Repository, https://github.com/NVIDIA/OpenShell-Research
|
|
6
|
+
Project-URL: Documentation, https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/
|
|
7
|
+
Author: NVIDIA CORPORATION & AFFILIATES
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Software Development
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: jsonschema<5,>=4.25
|
|
19
|
+
Requires-Dist: pydantic<3,>=2.11
|
|
20
|
+
Requires-Dist: pyyaml<7,>=6
|
|
21
|
+
Requires-Dist: typer<1,>=0.16
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# OpenShell Agent Runner
|
|
25
|
+
|
|
26
|
+
OpenShell Agent Runner (OAR) runs an agent task in an isolated OpenShell sandbox
|
|
27
|
+
and saves the result to a file. Use it to review code, review technical writing,
|
|
28
|
+
or run your own tasks from a terminal or CI job.
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
Profile + input → OAR → Agent in a temporary sandbox → Result file
|
|
32
|
+
sandbox removed when the run ends
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A **profile** packages prompts, reusable skills, and settings. A **task** is one
|
|
36
|
+
job in that profile; it selects the prompt and skills to use. OAR includes two
|
|
37
|
+
ready-to-use reviewers:
|
|
38
|
+
|
|
39
|
+
| Profile | Task | Input |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `code-reviewer` | `review-repository` | A local project directory |
|
|
42
|
+
| `technical-writing-reviewer` | `review-document` | A text file, such as Markdown or `.txt` |
|
|
43
|
+
|
|
44
|
+
## Get started
|
|
45
|
+
|
|
46
|
+
You need [uv](https://docs.astral.sh/uv/getting-started/installation/) and
|
|
47
|
+
OpenShell 0.0.111 or newer, with a running gateway and configured inference.
|
|
48
|
+
If OpenShell is not ready, follow its
|
|
49
|
+
[quickstart](https://docs.nvidia.com/openshell/latest/get-started/quickstart).
|
|
50
|
+
OAR uses that setup to create sandboxes and reach your model.
|
|
51
|
+
|
|
52
|
+
**1. Install OAR and check your connection.**
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv tool install openshell-agent-runner
|
|
56
|
+
oar doctor
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Alternatively, use `uvx --from openshell-agent-runner oar` in place of `oar`.
|
|
60
|
+
|
|
61
|
+
These commands use your selected OpenShell gateway and its `default` workspace.
|
|
62
|
+
For another target, add `--gateway NAME --workspace NAME` to `doctor` and `run`.
|
|
63
|
+
|
|
64
|
+
**2. Create your profiles.** Replace `YOUR_MODEL_ID` with the model ID shown in
|
|
65
|
+
the inference output from `oar doctor`.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
oar init ./profiles --model YOUR_MODEL_ID
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This creates editable copies of both reviewers. For a model without reasoning
|
|
72
|
+
support, add `--thinking off`.
|
|
73
|
+
|
|
74
|
+
**3. Review a document.** Replace `./README.md` with an existing text file.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
oar run ./profiles/technical-writing-reviewer \
|
|
78
|
+
--task review-document \
|
|
79
|
+
--input ./README.md \
|
|
80
|
+
--output ./review.json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Open `review.json` for the verdict, a score from 0 to 100, and specific findings.
|
|
84
|
+
OAR saves the validated result and removes the sandbox. Add `--dry-run` to
|
|
85
|
+
preview the operation without launching an agent.
|
|
86
|
+
|
|
87
|
+
## Review code
|
|
88
|
+
|
|
89
|
+
Pass a local project directory. Optional `focus` and `context` values help the
|
|
90
|
+
reviewer understand what matters for this run:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
oar run ./profiles/code-reviewer \
|
|
94
|
+
--task review-repository \
|
|
95
|
+
--input ./my-project \
|
|
96
|
+
--prompt-var focus="src/auth and tests/auth" \
|
|
97
|
+
--prompt-var context="A small internal tool; keep recommendations proportionate." \
|
|
98
|
+
--output ./code-review.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The agent works on an uploaded copy. Changes in the sandbox stay there; only the
|
|
102
|
+
result is downloaded. A `focus` value guides attention but does not limit which
|
|
103
|
+
files are uploaded.
|
|
104
|
+
|
|
105
|
+
## Learn more
|
|
106
|
+
|
|
107
|
+
- [Get started](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/): setup and your first review.
|
|
108
|
+
- [Run reviews](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/reviews/): inputs, focus, context, and scores.
|
|
109
|
+
- [Customize profiles](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/profiles/): prompts, variables, skills, and output formats.
|
|
110
|
+
- [Command reference](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/reference/): options, CI behavior, and troubleshooting.
|
|
111
|
+
|
|
112
|
+
For help with a specific task:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
oar run ./profiles/code-reviewer --task review-repository --help
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Develop OAR
|
|
119
|
+
|
|
120
|
+
From `projects/openshell-agent-runner`, run:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
make check
|
|
124
|
+
make build
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`make check` includes CLI workflows against a simulated OpenShell. With Docker,
|
|
128
|
+
run `make test-runtime` to exercise the real Pi harness without external inference.
|
|
129
|
+
For runtime changes, one live CI task checks input transfer, prompt variables,
|
|
130
|
+
structured output, and sandbox cleanup. It does not grade reviewer opinions.
|
|
131
|
+
|
|
132
|
+
Use `uv run --frozen oar` in this directory to run the checked-out code instead
|
|
133
|
+
of the installed release. Run a focused test with
|
|
134
|
+
`make test PYTEST_ARGS="tests/test_config.py"`. See
|
|
135
|
+
[RELEASING.md](https://github.com/NVIDIA/OpenShell-Research/blob/main/projects/openshell-agent-runner/RELEASING.md)
|
|
136
|
+
for publishing, and the
|
|
137
|
+
[repository CI guide](https://github.com/NVIDIA/OpenShell-Research/blob/main/docs/development/ci.md)
|
|
138
|
+
for automated project reviews and the live OAR smoke test.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# OpenShell Agent Runner
|
|
2
|
+
|
|
3
|
+
OpenShell Agent Runner (OAR) runs an agent task in an isolated OpenShell sandbox
|
|
4
|
+
and saves the result to a file. Use it to review code, review technical writing,
|
|
5
|
+
or run your own tasks from a terminal or CI job.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Profile + input → OAR → Agent in a temporary sandbox → Result file
|
|
9
|
+
sandbox removed when the run ends
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
A **profile** packages prompts, reusable skills, and settings. A **task** is one
|
|
13
|
+
job in that profile; it selects the prompt and skills to use. OAR includes two
|
|
14
|
+
ready-to-use reviewers:
|
|
15
|
+
|
|
16
|
+
| Profile | Task | Input |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `code-reviewer` | `review-repository` | A local project directory |
|
|
19
|
+
| `technical-writing-reviewer` | `review-document` | A text file, such as Markdown or `.txt` |
|
|
20
|
+
|
|
21
|
+
## Get started
|
|
22
|
+
|
|
23
|
+
You need [uv](https://docs.astral.sh/uv/getting-started/installation/) and
|
|
24
|
+
OpenShell 0.0.111 or newer, with a running gateway and configured inference.
|
|
25
|
+
If OpenShell is not ready, follow its
|
|
26
|
+
[quickstart](https://docs.nvidia.com/openshell/latest/get-started/quickstart).
|
|
27
|
+
OAR uses that setup to create sandboxes and reach your model.
|
|
28
|
+
|
|
29
|
+
**1. Install OAR and check your connection.**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv tool install openshell-agent-runner
|
|
33
|
+
oar doctor
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Alternatively, use `uvx --from openshell-agent-runner oar` in place of `oar`.
|
|
37
|
+
|
|
38
|
+
These commands use your selected OpenShell gateway and its `default` workspace.
|
|
39
|
+
For another target, add `--gateway NAME --workspace NAME` to `doctor` and `run`.
|
|
40
|
+
|
|
41
|
+
**2. Create your profiles.** Replace `YOUR_MODEL_ID` with the model ID shown in
|
|
42
|
+
the inference output from `oar doctor`.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
oar init ./profiles --model YOUR_MODEL_ID
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This creates editable copies of both reviewers. For a model without reasoning
|
|
49
|
+
support, add `--thinking off`.
|
|
50
|
+
|
|
51
|
+
**3. Review a document.** Replace `./README.md` with an existing text file.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
oar run ./profiles/technical-writing-reviewer \
|
|
55
|
+
--task review-document \
|
|
56
|
+
--input ./README.md \
|
|
57
|
+
--output ./review.json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Open `review.json` for the verdict, a score from 0 to 100, and specific findings.
|
|
61
|
+
OAR saves the validated result and removes the sandbox. Add `--dry-run` to
|
|
62
|
+
preview the operation without launching an agent.
|
|
63
|
+
|
|
64
|
+
## Review code
|
|
65
|
+
|
|
66
|
+
Pass a local project directory. Optional `focus` and `context` values help the
|
|
67
|
+
reviewer understand what matters for this run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
oar run ./profiles/code-reviewer \
|
|
71
|
+
--task review-repository \
|
|
72
|
+
--input ./my-project \
|
|
73
|
+
--prompt-var focus="src/auth and tests/auth" \
|
|
74
|
+
--prompt-var context="A small internal tool; keep recommendations proportionate." \
|
|
75
|
+
--output ./code-review.json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The agent works on an uploaded copy. Changes in the sandbox stay there; only the
|
|
79
|
+
result is downloaded. A `focus` value guides attention but does not limit which
|
|
80
|
+
files are uploaded.
|
|
81
|
+
|
|
82
|
+
## Learn more
|
|
83
|
+
|
|
84
|
+
- [Get started](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/): setup and your first review.
|
|
85
|
+
- [Run reviews](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/reviews/): inputs, focus, context, and scores.
|
|
86
|
+
- [Customize profiles](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/profiles/): prompts, variables, skills, and output formats.
|
|
87
|
+
- [Command reference](https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/reference/): options, CI behavior, and troubleshooting.
|
|
88
|
+
|
|
89
|
+
For help with a specific task:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
oar run ./profiles/code-reviewer --task review-repository --help
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Develop OAR
|
|
96
|
+
|
|
97
|
+
From `projects/openshell-agent-runner`, run:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
make check
|
|
101
|
+
make build
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`make check` includes CLI workflows against a simulated OpenShell. With Docker,
|
|
105
|
+
run `make test-runtime` to exercise the real Pi harness without external inference.
|
|
106
|
+
For runtime changes, one live CI task checks input transfer, prompt variables,
|
|
107
|
+
structured output, and sandbox cleanup. It does not grade reviewer opinions.
|
|
108
|
+
|
|
109
|
+
Use `uv run --frozen oar` in this directory to run the checked-out code instead
|
|
110
|
+
of the installed release. Run a focused test with
|
|
111
|
+
`make test PYTEST_ARGS="tests/test_config.py"`. See
|
|
112
|
+
[RELEASING.md](https://github.com/NVIDIA/OpenShell-Research/blob/main/projects/openshell-agent-runner/RELEASING.md)
|
|
113
|
+
for publishing, and the
|
|
114
|
+
[repository CI guide](https://github.com/NVIDIA/OpenShell-Research/blob/main/docs/development/ci.md)
|
|
115
|
+
for automated project reviews and the live OAR smoke test.
|
|
@@ -31,7 +31,7 @@ openshell-agent-runner = "openshell_agent_runner.cli:app"
|
|
|
31
31
|
|
|
32
32
|
[project.urls]
|
|
33
33
|
Repository = "https://github.com/NVIDIA/OpenShell-Research"
|
|
34
|
-
Documentation = "https://github.
|
|
34
|
+
Documentation = "https://nvidia.github.io/OpenShell-Research/documentation/openshell-agent-runner/"
|
|
35
35
|
|
|
36
36
|
[dependency-groups]
|
|
37
37
|
dev = [
|
|
@@ -51,6 +51,7 @@ source = "uv-dynamic-versioning"
|
|
|
51
51
|
[tool.uv-dynamic-versioning]
|
|
52
52
|
vcs = "git"
|
|
53
53
|
style = "pep440"
|
|
54
|
+
pattern-prefix = "oar-"
|
|
54
55
|
bump = true
|
|
55
56
|
|
|
56
57
|
[tool.hatch.build.targets.wheel]
|
{openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/cli.py
RENAMED
|
@@ -120,9 +120,16 @@ def run(
|
|
|
120
120
|
output: Annotated[
|
|
121
121
|
Path, typer.Option("--output", help="Host path for the agent result.")
|
|
122
122
|
],
|
|
123
|
-
|
|
123
|
+
input_path: Annotated[
|
|
124
124
|
Path | None,
|
|
125
|
-
typer.Option("--input", help="Host
|
|
125
|
+
typer.Option("--input", help="Host input required by the selected task."),
|
|
126
|
+
] = None,
|
|
127
|
+
prompt_variable: Annotated[
|
|
128
|
+
list[str] | None,
|
|
129
|
+
typer.Option(
|
|
130
|
+
"--prompt-var",
|
|
131
|
+
help="Non-secret NAME=VALUE prompt variable. Repeat for several.",
|
|
132
|
+
),
|
|
126
133
|
] = None,
|
|
127
134
|
upload: Annotated[
|
|
128
135
|
list[str] | None,
|
|
@@ -159,7 +166,8 @@ def run(
|
|
|
159
166
|
profile_directory=profile,
|
|
160
167
|
task_id=task,
|
|
161
168
|
output=output,
|
|
162
|
-
|
|
169
|
+
input_path=input_path,
|
|
170
|
+
prompt_variables=prompt_variable or (),
|
|
163
171
|
uploads=upload or (),
|
|
164
172
|
environments=environment or (),
|
|
165
173
|
gateway=gateway,
|
|
@@ -185,12 +193,17 @@ def doctor(
|
|
|
185
193
|
str, typer.Option("--workspace", help="OpenShell workspace name.")
|
|
186
194
|
] = "default",
|
|
187
195
|
) -> None:
|
|
188
|
-
"""
|
|
196
|
+
"""Inspect OpenShell connection and configuration without changing its state."""
|
|
189
197
|
try:
|
|
190
198
|
checks = run_doctor(NativeTarget(gateway=gateway, workspace=workspace))
|
|
191
199
|
except OarError as error:
|
|
192
200
|
_fail(error)
|
|
193
201
|
typer.echo("\n\n".join(result for _, result in checks))
|
|
202
|
+
typer.echo(
|
|
203
|
+
"\nThese are configuration checks, not an inference request. "
|
|
204
|
+
"Before running a task, confirm that an inference provider and model "
|
|
205
|
+
"are listed above."
|
|
206
|
+
)
|
|
194
207
|
|
|
195
208
|
|
|
196
209
|
def _fail(error: OarError) -> NoReturn:
|
|
@@ -227,8 +240,10 @@ def _render_task_help(
|
|
|
227
240
|
_help_command(f" oar run {shlex.quote(str(profile_directory))} \\"),
|
|
228
241
|
_help_command(f" --task {shlex.quote(task_id)} \\"),
|
|
229
242
|
]
|
|
230
|
-
if task.required_input
|
|
231
|
-
usage_lines.append(
|
|
243
|
+
if task.required_input is not None:
|
|
244
|
+
usage_lines.append(
|
|
245
|
+
_help_command(f" --input {task.required_input.upper()} \\")
|
|
246
|
+
)
|
|
232
247
|
usage_lines.append(_help_command(" --output OUTPUT"))
|
|
233
248
|
|
|
234
249
|
upload_lines = [_help_heading("Additional configured uploads:")]
|
|
@@ -244,6 +259,22 @@ def _render_task_help(
|
|
|
244
259
|
environment_lines.append(" None. Add values with --env KEY=VALUE.")
|
|
245
260
|
|
|
246
261
|
input_lines = _required_input_help(task.required_input)
|
|
262
|
+
prompt_variable_lines = [_help_heading("Prompt variables:")]
|
|
263
|
+
if task.prompt_variables:
|
|
264
|
+
for name, variable in task.prompt_variables.items():
|
|
265
|
+
requirement = (
|
|
266
|
+
f"Default: {variable.default}"
|
|
267
|
+
if variable.default is not None
|
|
268
|
+
else "Required."
|
|
269
|
+
)
|
|
270
|
+
prompt_variable_lines.extend(
|
|
271
|
+
[
|
|
272
|
+
_help_command(f" --prompt-var {name}=VALUE"),
|
|
273
|
+
f" {variable.description} {requirement}",
|
|
274
|
+
]
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
prompt_variable_lines.append(" None.")
|
|
247
278
|
|
|
248
279
|
output_description = (
|
|
249
280
|
f"JSON validated against {task.output_schema}."
|
|
@@ -260,6 +291,8 @@ def _render_task_help(
|
|
|
260
291
|
"",
|
|
261
292
|
*input_lines,
|
|
262
293
|
"",
|
|
294
|
+
*prompt_variable_lines,
|
|
295
|
+
"",
|
|
263
296
|
*upload_lines,
|
|
264
297
|
"",
|
|
265
298
|
*environment_lines,
|
|
@@ -274,10 +307,15 @@ def _render_task_help(
|
|
|
274
307
|
def _required_input_help(required_input: str | None) -> list[str]:
|
|
275
308
|
if required_input is None:
|
|
276
309
|
return [_help_heading("Required input:"), " None."]
|
|
310
|
+
description = (
|
|
311
|
+
"Host document to review."
|
|
312
|
+
if required_input == "document"
|
|
313
|
+
else "Host code repository to review."
|
|
314
|
+
)
|
|
277
315
|
return [
|
|
278
316
|
_help_heading("Required argument:"),
|
|
279
|
-
_help_command(" --input
|
|
280
|
-
"
|
|
317
|
+
_help_command(f" --input {required_input.upper()}"),
|
|
318
|
+
f" {description}",
|
|
281
319
|
]
|
|
282
320
|
|
|
283
321
|
|
{openshell_agent_runner-0.0.1 → openshell_agent_runner-0.0.3}/src/openshell_agent_runner/config.py
RENAMED
|
@@ -19,9 +19,15 @@ from pydantic import (
|
|
|
19
19
|
Field,
|
|
20
20
|
ValidationError,
|
|
21
21
|
field_validator,
|
|
22
|
+
model_validator,
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
from openshell_agent_runner.errors import ConfigurationError
|
|
26
|
+
from openshell_agent_runner.prompt_templates import (
|
|
27
|
+
BUILTIN_PROMPT_VARIABLES,
|
|
28
|
+
PROMPT_VARIABLE_NAME_PATTERN,
|
|
29
|
+
validate_prompt_template,
|
|
30
|
+
)
|
|
25
31
|
|
|
26
32
|
IDENTIFIER_PATTERN = r"^[a-z][a-z0-9-]{0,62}$"
|
|
27
33
|
RESOURCE_IDENTIFIER_PATTERN = r"^[a-z][a-z0-9_-]{0,62}$"
|
|
@@ -29,6 +35,8 @@ MODEL_IDENTIFIER_PATTERN = r"^[A-Za-z0-9._:/-]{1,256}$"
|
|
|
29
35
|
MODELS_FILENAME = "models.json"
|
|
30
36
|
PROFILE_FILENAME = "profile.yaml"
|
|
31
37
|
SETTINGS_FILENAME = "settings.json"
|
|
38
|
+
BUILTIN_PI_TOOLS = frozenset({"bash", "edit", "find", "grep", "ls", "read", "write"})
|
|
39
|
+
SUBMIT_RESULT_TOOL = "submit_result"
|
|
32
40
|
_PI_RUNTIME_SETTING_KEYS = {
|
|
33
41
|
"defaultProvider",
|
|
34
42
|
"defaultModel",
|
|
@@ -58,24 +66,86 @@ class SandboxConfig(StrictModel):
|
|
|
58
66
|
return values
|
|
59
67
|
|
|
60
68
|
|
|
69
|
+
ToolName = Annotated[str, Field(pattern=RESOURCE_IDENTIFIER_PATTERN)]
|
|
70
|
+
PromptVariableName = Annotated[str, Field(pattern=PROMPT_VARIABLE_NAME_PATTERN)]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ExtensionConfig(StrictModel):
|
|
74
|
+
path: Path
|
|
75
|
+
tools: list[ToolName] = Field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
@field_validator("tools")
|
|
78
|
+
@classmethod
|
|
79
|
+
def require_unique_tools(cls, values: list[str]) -> list[str]:
|
|
80
|
+
if len(values) != len(set(values)):
|
|
81
|
+
raise ValueError("extension tool entries must be unique")
|
|
82
|
+
return values
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class PromptVariableConfig(StrictModel):
|
|
86
|
+
description: str = Field(min_length=1, max_length=1000)
|
|
87
|
+
default: str | None = Field(default=None, min_length=1)
|
|
88
|
+
|
|
89
|
+
|
|
61
90
|
class TaskConfig(StrictModel):
|
|
62
91
|
description: str | None = Field(default=None, min_length=1, max_length=1000)
|
|
63
|
-
required_input: Literal["document"] | None = None
|
|
92
|
+
required_input: Literal["document", "repository"] | None = None
|
|
64
93
|
prompt: Path
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
default_factory=list
|
|
94
|
+
prompt_variables: dict[PromptVariableName, PromptVariableConfig] = Field(
|
|
95
|
+
default_factory=dict
|
|
68
96
|
)
|
|
97
|
+
output_schema: Path | None = None
|
|
98
|
+
tools: list[ToolName] = Field(default_factory=list)
|
|
69
99
|
skills: list[Path] = Field(default_factory=list)
|
|
70
|
-
extensions: list[
|
|
100
|
+
extensions: list[ExtensionConfig] = Field(default_factory=list)
|
|
71
101
|
|
|
72
|
-
@field_validator("tools", "skills"
|
|
102
|
+
@field_validator("tools", "skills")
|
|
73
103
|
@classmethod
|
|
74
104
|
def require_unique_resources(cls, values: list[object]) -> list[object]:
|
|
75
105
|
if len(values) != len(set(values)):
|
|
76
106
|
raise ValueError("resource entries must be unique")
|
|
77
107
|
return values
|
|
78
108
|
|
|
109
|
+
@field_validator("extensions")
|
|
110
|
+
@classmethod
|
|
111
|
+
def require_unique_extensions(
|
|
112
|
+
cls, values: list[ExtensionConfig]
|
|
113
|
+
) -> list[ExtensionConfig]:
|
|
114
|
+
paths = [extension.path for extension in values]
|
|
115
|
+
if len(paths) != len(set(paths)):
|
|
116
|
+
raise ValueError("extension paths must be unique")
|
|
117
|
+
return values
|
|
118
|
+
|
|
119
|
+
@model_validator(mode="after")
|
|
120
|
+
def require_known_tools(self) -> TaskConfig:
|
|
121
|
+
declared_custom_tools: set[str] = set()
|
|
122
|
+
for extension in self.extensions:
|
|
123
|
+
for tool in extension.tools:
|
|
124
|
+
if tool in BUILTIN_PI_TOOLS or tool == SUBMIT_RESULT_TOOL:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"extension tool {tool!r} conflicts with a reserved tool"
|
|
127
|
+
)
|
|
128
|
+
if tool in declared_custom_tools:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"custom tool {tool!r} is declared by multiple extensions"
|
|
131
|
+
)
|
|
132
|
+
declared_custom_tools.add(tool)
|
|
133
|
+
|
|
134
|
+
available_tools = BUILTIN_PI_TOOLS | declared_custom_tools
|
|
135
|
+
unknown_tools = sorted(set(self.tools) - available_tools)
|
|
136
|
+
if unknown_tools:
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"unknown tools {unknown_tools}; Pi built-ins are "
|
|
139
|
+
f"{sorted(BUILTIN_PI_TOOLS)}; declare each custom tool under a "
|
|
140
|
+
"referenced extension"
|
|
141
|
+
)
|
|
142
|
+
if self.skills and "read" not in self.tools:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
"tasks with skills must include 'read' in tools so Pi can "
|
|
145
|
+
"discover and load the skills"
|
|
146
|
+
)
|
|
147
|
+
return self
|
|
148
|
+
|
|
79
149
|
|
|
80
150
|
class ProfileConfig(StrictModel):
|
|
81
151
|
id: Annotated[str, Field(pattern=IDENTIFIER_PATTERN)]
|
|
@@ -155,7 +225,6 @@ def resolve_task(profile_directory: Path, task_id: str) -> ResolvedProfile:
|
|
|
155
225
|
def validate_upload_mappings(values: Sequence[str]) -> tuple[str, ...]:
|
|
156
226
|
if len(values) != len(set(values)):
|
|
157
227
|
raise ValueError("duplicate upload mapping")
|
|
158
|
-
destinations: dict[str, str] = {}
|
|
159
228
|
for value in values:
|
|
160
229
|
source, separator, destination = value.rpartition(":")
|
|
161
230
|
if not separator or not source or not destination.startswith("/"):
|
|
@@ -169,16 +238,11 @@ def validate_upload_mappings(values: Sequence[str]) -> tuple[str, ...]:
|
|
|
169
238
|
PurePosixPath("/sandbox/artifacts"),
|
|
170
239
|
PurePosixPath("/sandbox/oar-runtime"),
|
|
171
240
|
):
|
|
172
|
-
if path
|
|
241
|
+
if path.is_relative_to(reserved) or reserved.is_relative_to(path):
|
|
173
242
|
raise ValueError(
|
|
174
243
|
"upload destination is reserved for runner resources: "
|
|
175
244
|
f"{destination}"
|
|
176
245
|
)
|
|
177
|
-
normalized = str(path)
|
|
178
|
-
previous = destinations.get(normalized)
|
|
179
|
-
if previous is not None and previous != source:
|
|
180
|
-
raise ValueError(f"conflicting upload destination: {destination}")
|
|
181
|
-
destinations[normalized] = source
|
|
182
246
|
return tuple(values)
|
|
183
247
|
|
|
184
248
|
|
|
@@ -300,7 +364,23 @@ def _validate_profile_resources(resolved: ResolvedProfile) -> None:
|
|
|
300
364
|
directory = resolved.profile_dir
|
|
301
365
|
_inside(directory, directory / resolved.profile.sandbox.policy, "sandbox policy")
|
|
302
366
|
for task_id, task in resolved.profile.tasks.items():
|
|
303
|
-
|
|
367
|
+
prompt = _inside(
|
|
368
|
+
directory, directory / task.prompt, f"prompt for task {task_id}"
|
|
369
|
+
)
|
|
370
|
+
available_builtins = (
|
|
371
|
+
BUILTIN_PROMPT_VARIABLES if task.required_input is not None else frozenset()
|
|
372
|
+
)
|
|
373
|
+
try:
|
|
374
|
+
template = prompt.read_text(encoding="utf-8")
|
|
375
|
+
validate_prompt_template(
|
|
376
|
+
template,
|
|
377
|
+
task.prompt_variables.keys(),
|
|
378
|
+
available_builtins,
|
|
379
|
+
)
|
|
380
|
+
except (OSError, UnicodeError, ValueError) as error:
|
|
381
|
+
raise ConfigurationError(
|
|
382
|
+
f"invalid prompt template for task {task_id}: {error}"
|
|
383
|
+
) from error
|
|
304
384
|
if task.output_schema is not None:
|
|
305
385
|
schema = _inside(
|
|
306
386
|
directory,
|
|
@@ -326,7 +406,11 @@ def _validate_profile_resources(resolved: ResolvedProfile) -> None:
|
|
|
326
406
|
f"skill for task {task_id} contains a symlink: {descendant}"
|
|
327
407
|
)
|
|
328
408
|
for extension in task.extensions:
|
|
329
|
-
_inside(
|
|
409
|
+
_inside(
|
|
410
|
+
directory,
|
|
411
|
+
directory / extension.path,
|
|
412
|
+
f"extension for task {task_id}",
|
|
413
|
+
)
|
|
330
414
|
|
|
331
415
|
|
|
332
416
|
def _validate_output_schema(path: Path) -> None:
|
|
@@ -335,10 +419,10 @@ def _validate_output_schema(path: Path) -> None:
|
|
|
335
419
|
Draft202012Validator.check_schema(document)
|
|
336
420
|
except (OSError, UnicodeError, json.JSONDecodeError, SchemaError) as error:
|
|
337
421
|
raise ConfigurationError(f"invalid output schema {path}: {error}") from error
|
|
338
|
-
_validate_schema_references(document
|
|
422
|
+
_validate_schema_references(document)
|
|
339
423
|
|
|
340
424
|
|
|
341
|
-
def _validate_schema_references(document: Any
|
|
425
|
+
def _validate_schema_references(document: Any) -> None:
|
|
342
426
|
if not isinstance(document, dict):
|
|
343
427
|
return
|
|
344
428
|
|
|
@@ -349,23 +433,22 @@ def _validate_schema_references(document: Any, path: Path) -> None:
|
|
|
349
433
|
f"({key}) because host and sandbox engines use different dialects"
|
|
350
434
|
)
|
|
351
435
|
for key in {"$ref", "$dynamicRef", "$recursiveRef"}:
|
|
352
|
-
if key in document
|
|
353
|
-
not isinstance(document[key], str) or not document[key].startswith("#")
|
|
354
|
-
):
|
|
436
|
+
if key in document:
|
|
355
437
|
raise ConfigurationError(
|
|
356
|
-
|
|
438
|
+
"output schemas do not support reference keywords "
|
|
439
|
+
f"({key}) because the submission tool nests the schema"
|
|
357
440
|
)
|
|
358
441
|
|
|
359
442
|
for key in {"$defs", "definitions", "properties", "dependentSchemas"}:
|
|
360
443
|
value = document.get(key)
|
|
361
444
|
if isinstance(value, dict):
|
|
362
445
|
for schema in value.values():
|
|
363
|
-
_validate_schema_references(schema
|
|
446
|
+
_validate_schema_references(schema)
|
|
364
447
|
for key in {"allOf", "anyOf", "oneOf", "prefixItems"}:
|
|
365
448
|
value = document.get(key)
|
|
366
449
|
if isinstance(value, list):
|
|
367
450
|
for schema in value:
|
|
368
|
-
_validate_schema_references(schema
|
|
451
|
+
_validate_schema_references(schema)
|
|
369
452
|
for key in {
|
|
370
453
|
"additionalProperties",
|
|
371
454
|
"contains",
|
|
@@ -379,4 +462,4 @@ def _validate_schema_references(document: Any, path: Path) -> None:
|
|
|
379
462
|
"unevaluatedItems",
|
|
380
463
|
"unevaluatedProperties",
|
|
381
464
|
}:
|
|
382
|
-
_validate_schema_references(document.get(key)
|
|
465
|
+
_validate_schema_references(document.get(key))
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
"""Materialize the explicit native-upload runtime bundle for Pi."""
|
|
5
5
|
|
|
6
|
+
import json
|
|
6
7
|
import shutil
|
|
7
8
|
import tempfile
|
|
9
|
+
from collections.abc import Mapping
|
|
8
10
|
from importlib.resources import files
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
|
|
@@ -14,6 +16,7 @@ from openshell_agent_runner.config import (
|
|
|
14
16
|
ResolvedProfile,
|
|
15
17
|
)
|
|
16
18
|
from openshell_agent_runner.harnesses.resources import PreparedResources
|
|
19
|
+
from openshell_agent_runner.prompt_templates import render_prompt_template
|
|
17
20
|
|
|
18
21
|
SANDBOX_RUNTIME_ROOT = "/sandbox/oar-runtime"
|
|
19
22
|
|
|
@@ -22,13 +25,19 @@ def image_directory() -> Path:
|
|
|
22
25
|
return Path(str(files("openshell_agent_runner.harnesses.pi") / "runtime" / "image"))
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
def prepare_resources(
|
|
28
|
+
def prepare_resources(
|
|
29
|
+
resolved: ResolvedProfile,
|
|
30
|
+
task_id: str,
|
|
31
|
+
prompt_variables: Mapping[str, str] | None = None,
|
|
32
|
+
) -> PreparedResources:
|
|
26
33
|
temporary = tempfile.TemporaryDirectory(prefix="oar-pi-")
|
|
27
34
|
runtime = Path(temporary.name) / "runtime"
|
|
28
35
|
(runtime / "skills").mkdir(parents=True, exist_ok=True)
|
|
29
36
|
(runtime / "extensions").mkdir(parents=True, exist_ok=True)
|
|
30
37
|
task = resolved.profile.tasks[task_id]
|
|
31
|
-
|
|
38
|
+
template = (resolved.profile_dir / task.prompt).read_text(encoding="utf-8")
|
|
39
|
+
rendered_prompt = render_prompt_template(template, prompt_variables or {})
|
|
40
|
+
(runtime / "prompt.md").write_text(rendered_prompt, encoding="utf-8")
|
|
32
41
|
shutil.copy2(resolved.profile_dir / MODELS_FILENAME, runtime / MODELS_FILENAME)
|
|
33
42
|
shutil.copy2(resolved.profile_dir / SETTINGS_FILENAME, runtime / SETTINGS_FILENAME)
|
|
34
43
|
arguments = [
|
|
@@ -66,15 +75,34 @@ def prepare_resources(resolved: ResolvedProfile, task_id: str) -> PreparedResour
|
|
|
66
75
|
shutil.copytree(resolved.profile_dir / skill, target)
|
|
67
76
|
arguments.extend(["--skill", f"{SANDBOX_RUNTIME_ROOT}/skills/{target.name}"])
|
|
68
77
|
for index, extension in enumerate(task.extensions):
|
|
69
|
-
target = runtime / "extensions" / f"{index:02d}-{extension.name}"
|
|
70
|
-
shutil.copy2(resolved.profile_dir / extension, target)
|
|
78
|
+
target = runtime / "extensions" / f"{index:02d}-{extension.path.name}"
|
|
79
|
+
shutil.copy2(resolved.profile_dir / extension.path, target)
|
|
71
80
|
arguments.extend(
|
|
72
81
|
["--extension", f"{SANDBOX_RUNTIME_ROOT}/extensions/{target.name}"]
|
|
73
82
|
)
|
|
83
|
+
expected_tools = runtime / "tools.json"
|
|
84
|
+
expected_tools.write_text(f"{json.dumps(tools)}\n", encoding="utf-8")
|
|
85
|
+
validate_tools = Path(
|
|
86
|
+
str(
|
|
87
|
+
files("openshell_agent_runner.harnesses.pi")
|
|
88
|
+
/ "runtime"
|
|
89
|
+
/ "extensions"
|
|
90
|
+
/ "validate-tools.ts"
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
validator_target = runtime / "extensions" / "oar-validate-tools.ts"
|
|
94
|
+
shutil.copy2(validate_tools, validator_target)
|
|
95
|
+
arguments.extend(
|
|
96
|
+
[
|
|
97
|
+
"--extension",
|
|
98
|
+
f"{SANDBOX_RUNTIME_ROOT}/extensions/{validator_target.name}",
|
|
99
|
+
]
|
|
100
|
+
)
|
|
74
101
|
uploads = [
|
|
75
102
|
f"{runtime / 'prompt.md'}:{SANDBOX_RUNTIME_ROOT}/prompt.md",
|
|
76
103
|
f"{runtime / 'models.json'}:{SANDBOX_RUNTIME_ROOT}/models.json",
|
|
77
104
|
f"{runtime / 'settings.json'}:{SANDBOX_RUNTIME_ROOT}/settings.json",
|
|
105
|
+
f"{expected_tools}:{SANDBOX_RUNTIME_ROOT}/tools.json",
|
|
78
106
|
]
|
|
79
107
|
if task.output_schema is not None:
|
|
80
108
|
uploads.append(
|