@tt-a1i/mco 0.1.2
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.
- package/README.md +190 -0
- package/bin/mco.js +19 -0
- package/mco +16 -0
- package/package.json +21 -0
- package/runtime/__init__.py +8 -0
- package/runtime/adapters/__init__.py +7 -0
- package/runtime/adapters/claude.py +60 -0
- package/runtime/adapters/codex.py +84 -0
- package/runtime/adapters/gemini.py +48 -0
- package/runtime/adapters/opencode.py +38 -0
- package/runtime/adapters/parsing.py +305 -0
- package/runtime/adapters/qwen.py +38 -0
- package/runtime/adapters/shim.py +251 -0
- package/runtime/artifacts.py +40 -0
- package/runtime/cli.py +341 -0
- package/runtime/config.py +189 -0
- package/runtime/contracts.py +127 -0
- package/runtime/errors.py +43 -0
- package/runtime/orchestrator.py +241 -0
- package/runtime/retry.py +15 -0
- package/runtime/review_engine.py +806 -0
- package/runtime/schemas/review_findings.schema.json +94 -0
- package/runtime/types.py +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# MCO Docs Index
|
|
2
|
+
|
|
3
|
+
## Read First
|
|
4
|
+
1. [multi-cli-orchestrator-proposal.md](./multi-cli-orchestrator-proposal.md)
|
|
5
|
+
2. [capability-research.md](./capability-research.md)
|
|
6
|
+
3. [notes.md](./notes.md)
|
|
7
|
+
|
|
8
|
+
## Gate Artifacts
|
|
9
|
+
1. [capability-probe-spec.md](./capability-probe-spec.md)
|
|
10
|
+
2. [adapter-contract-tests.md](./adapter-contract-tests.md)
|
|
11
|
+
3. [dry-run-plan.md](./dry-run-plan.md)
|
|
12
|
+
4. [implementation-gate-checklist.md](./implementation-gate-checklist.md)
|
|
13
|
+
|
|
14
|
+
## Implementation Freeze
|
|
15
|
+
1. [docs/implementation/step0-interface-freeze.md](./docs/implementation/step0-interface-freeze.md)
|
|
16
|
+
2. [docs/contracts/cli-json-v0.1.x.md](./docs/contracts/cli-json-v0.1.x.md)
|
|
17
|
+
3. [docs/contracts/provider-permissions-v0.1.x.md](./docs/contracts/provider-permissions-v0.1.x.md)
|
|
18
|
+
|
|
19
|
+
## Planning and Tracking
|
|
20
|
+
1. [task_plan.md](./task_plan.md)
|
|
21
|
+
|
|
22
|
+
## Release Notes
|
|
23
|
+
1. [docs/releases/v0.1.2.md](./docs/releases/v0.1.2.md)
|
|
24
|
+
2. [docs/releases/v0.1.2.zh-CN.md](./docs/releases/v0.1.2.zh-CN.md)
|
|
25
|
+
3. [docs/releases/v0.1.1.md](./docs/releases/v0.1.1.md)
|
|
26
|
+
4. [docs/releases/v0.1.1.zh-CN.md](./docs/releases/v0.1.1.zh-CN.md)
|
|
27
|
+
5. [docs/releases/v0.1.0.md](./docs/releases/v0.1.0.md)
|
|
28
|
+
6. [docs/releases/v0.1.0.zh-CN.md](./docs/releases/v0.1.0.zh-CN.md)
|
|
29
|
+
|
|
30
|
+
## Unified CLI (Step 2)
|
|
31
|
+
`mco review` is the unified entrypoint for running a review task.
|
|
32
|
+
|
|
33
|
+
`mco run` is the generalized execution entrypoint for agent-style task orchestration (no forced findings schema).
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Python package (recommended):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pipx install mco
|
|
41
|
+
mco --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Install from source (editable):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/tt-a1i/mco.git
|
|
48
|
+
cd mco
|
|
49
|
+
python3 -m pip install -e .
|
|
50
|
+
mco --help
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
NPM wrapper (Python 3 required on PATH):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm i -g @tt-a1i/mco
|
|
57
|
+
mco --help
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Quick start:
|
|
61
|
+
```bash
|
|
62
|
+
./mco review \
|
|
63
|
+
--repo . \
|
|
64
|
+
--prompt "Review this repository for high-risk bugs and security issues." \
|
|
65
|
+
--providers claude,codex
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Machine-readable output:
|
|
69
|
+
```bash
|
|
70
|
+
./mco review --repo . --prompt "Review for bugs." --providers claude,codex --json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Stdout-only result mode (for caller rendering, no `summary.md/decision.md/findings.json/run.json` write):
|
|
74
|
+
```bash
|
|
75
|
+
./mco review --repo . --prompt "Review for bugs." --providers claude,codex --result-mode stdout --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
General run mode:
|
|
79
|
+
```bash
|
|
80
|
+
./mco run --repo . --prompt "Summarize the current repo architecture." --providers claude,codex --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Config file (JSON):
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"providers": ["claude", "codex"],
|
|
87
|
+
"artifact_base": "reports/review",
|
|
88
|
+
"state_file": ".mco/state.json",
|
|
89
|
+
"policy": {
|
|
90
|
+
"timeout_seconds": 180,
|
|
91
|
+
"stall_timeout_seconds": 900,
|
|
92
|
+
"poll_interval_seconds": 1.0,
|
|
93
|
+
"review_hard_timeout_seconds": 1800,
|
|
94
|
+
"enforce_findings_contract": false,
|
|
95
|
+
"max_retries": 1,
|
|
96
|
+
"high_escalation_threshold": 1,
|
|
97
|
+
"require_non_empty_findings": true,
|
|
98
|
+
"max_provider_parallelism": 0,
|
|
99
|
+
"allow_paths": [".", "runtime", "scripts"],
|
|
100
|
+
"enforcement_mode": "strict",
|
|
101
|
+
"provider_permissions": {
|
|
102
|
+
"claude": {
|
|
103
|
+
"permission_mode": "plan"
|
|
104
|
+
},
|
|
105
|
+
"codex": {
|
|
106
|
+
"sandbox": "workspace-write"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"provider_timeouts": {
|
|
110
|
+
"claude": 300,
|
|
111
|
+
"codex": 240,
|
|
112
|
+
"qwen": 240
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Run with config:
|
|
119
|
+
```bash
|
|
120
|
+
./mco review --config ./mco.example.json --repo . --prompt "Review for bugs and security issues."
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Override fan-out and per-provider timeout from CLI:
|
|
124
|
+
```bash
|
|
125
|
+
./mco review \
|
|
126
|
+
--repo . \
|
|
127
|
+
--prompt "Review for bugs and security issues." \
|
|
128
|
+
--providers claude,codex,gemini,opencode,qwen \
|
|
129
|
+
--strict-contract \
|
|
130
|
+
--max-provider-parallelism 2 \
|
|
131
|
+
--stall-timeout 900 \
|
|
132
|
+
--review-hard-timeout 1800 \
|
|
133
|
+
--provider-timeouts qwen=900,codex=900
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Run mode with hard path constraints:
|
|
137
|
+
```bash
|
|
138
|
+
./mco run \
|
|
139
|
+
--repo . \
|
|
140
|
+
--prompt "Compare adapter behaviors and return a short markdown summary." \
|
|
141
|
+
--providers claude,codex \
|
|
142
|
+
--allow-paths runtime,scripts \
|
|
143
|
+
--target-paths runtime/adapters,runtime/review_engine.py \
|
|
144
|
+
--enforcement-mode strict \
|
|
145
|
+
--provider-permissions-json '{"codex":{"sandbox":"workspace-write"},"claude":{"permission_mode":"plan"}}' \
|
|
146
|
+
--json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Artifacts are written to:
|
|
150
|
+
- `<artifact_base>/<task_id>/summary.md`
|
|
151
|
+
- `<artifact_base>/<task_id>/decision.md`
|
|
152
|
+
- `<artifact_base>/<task_id>/findings.json`
|
|
153
|
+
- `<artifact_base>/<task_id>/run.json`
|
|
154
|
+
- `<artifact_base>/<task_id>/providers/*.json`
|
|
155
|
+
- `<artifact_base>/<task_id>/raw/*.log`
|
|
156
|
+
|
|
157
|
+
`run.json` includes audit fields for reproducibility:
|
|
158
|
+
- `effective_cwd`
|
|
159
|
+
- `allow_paths_hash`
|
|
160
|
+
- `permissions_hash`
|
|
161
|
+
|
|
162
|
+
Notes:
|
|
163
|
+
- YAML config requires `pyyaml` installed; otherwise use JSON config.
|
|
164
|
+
- Review prompt is wrapped with a JSON finding contract, but strict parse enforcement is optional.
|
|
165
|
+
- Enable strict gate behavior with `--strict-contract` (or `policy.enforce_findings_contract=true` in config).
|
|
166
|
+
- `run` mode does not force findings schema; it focuses on execution aggregation and provider success.
|
|
167
|
+
- `result_mode=artifact` (default): write user-facing artifacts and print compact result.
|
|
168
|
+
- `result_mode=stdout`: print provider-level result payload to stdout, skip user-facing artifact files.
|
|
169
|
+
- `result_mode=both`: write artifacts and print provider-level payload.
|
|
170
|
+
- Execution model is `wait-all`: one provider timeout/failure does not stop others.
|
|
171
|
+
- Timeout behavior is progress-driven:
|
|
172
|
+
- `stall_timeout_seconds`: cancel only when output progress is idle beyond threshold.
|
|
173
|
+
- `review_hard_timeout_seconds`: hard deadline applied only in `review` mode.
|
|
174
|
+
- `max_provider_parallelism=0` (or omitted) means full parallelism across selected providers.
|
|
175
|
+
- `provider_timeouts` are provider-specific stall-timeout overrides.
|
|
176
|
+
- `allow_paths` and `target_paths` are validated against `repo_root`; path escape is rejected.
|
|
177
|
+
- `enforcement_mode=strict` (default) fails closed when provider permission requirements cannot be honored.
|
|
178
|
+
|
|
179
|
+
## Step5 Benchmark Script
|
|
180
|
+
Use this script to generate serial vs full-parallel evidence and write reports under `reports/adapter-contract/<date>/`:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
./scripts/run_step5_parallel_benchmark.sh
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The generated summary JSON includes separated parse-vs-findings metrics:
|
|
187
|
+
- `providers_total`
|
|
188
|
+
- `parse_success_rate`
|
|
189
|
+
- `effective_findings_count`
|
|
190
|
+
- `zero_finding_provider_count`
|
package/bin/mco.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
4
|
+
const { resolve } = require("node:path");
|
|
5
|
+
|
|
6
|
+
const scriptPath = resolve(__dirname, "..", "mco");
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
|
|
9
|
+
const result = spawnSync("python3", [scriptPath, ...args], {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (result.error) {
|
|
14
|
+
console.error(`Failed to run python3: ${result.error.message}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
19
|
+
|
package/mco
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
ROOT_DIR = Path(__file__).resolve().parent
|
|
8
|
+
if str(ROOT_DIR) not in sys.path:
|
|
9
|
+
sys.path.insert(0, str(ROOT_DIR))
|
|
10
|
+
|
|
11
|
+
from runtime.cli import main
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
raise SystemExit(main())
|
|
16
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tt-a1i/mco",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Node wrapper for the mco CLI (Python runtime required).",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mco": "bin/mco.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node -e \"console.log('No JS tests. Use Python test suite.')\""
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin/mco.js",
|
|
18
|
+
"mco",
|
|
19
|
+
"runtime"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Runtime package for orchestrator gate implementation."""
|
|
2
|
+
|
|
3
|
+
from .artifacts import ARTIFACT_LAYOUT_VERSION
|
|
4
|
+
from .types import RUN_RESULT_SCHEMA_VERSION
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.2"
|
|
7
|
+
|
|
8
|
+
__all__ = ["ARTIFACT_LAYOUT_VERSION", "RUN_RESULT_SCHEMA_VERSION", "__version__"]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .claude import ClaudeAdapter
|
|
2
|
+
from .codex import CodexAdapter
|
|
3
|
+
from .gemini import GeminiAdapter
|
|
4
|
+
from .opencode import OpenCodeAdapter
|
|
5
|
+
from .qwen import QwenAdapter
|
|
6
|
+
|
|
7
|
+
__all__ = ["ClaudeAdapter", "CodexAdapter", "GeminiAdapter", "OpenCodeAdapter", "QwenAdapter"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, List
|
|
4
|
+
|
|
5
|
+
from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
|
|
6
|
+
from .parsing import normalize_findings_from_text
|
|
7
|
+
from .shim import ShimAdapterBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ClaudeAdapter(ShimAdapterBase):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
super().__init__(
|
|
13
|
+
provider_id="claude",
|
|
14
|
+
binary_name="claude",
|
|
15
|
+
capability_set=CapabilitySet(
|
|
16
|
+
tiers=["C0", "C1", "C2", "C3", "C4", "C5", "C6"],
|
|
17
|
+
supports_native_async=False,
|
|
18
|
+
supports_poll_endpoint=False,
|
|
19
|
+
supports_resume_after_restart=True,
|
|
20
|
+
supports_schema_enforcement=True,
|
|
21
|
+
min_supported_version="2.1.59",
|
|
22
|
+
tested_os=["macos"],
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _auth_check_command(self, binary: str) -> List[str]:
|
|
27
|
+
return [binary, "auth", "status"]
|
|
28
|
+
|
|
29
|
+
def supported_permission_keys(self) -> List[str]:
|
|
30
|
+
return ["permission_mode"]
|
|
31
|
+
|
|
32
|
+
def _build_command(self, input_task: TaskInput) -> List[str]:
|
|
33
|
+
permission_mode = "plan"
|
|
34
|
+
raw_permissions = input_task.metadata.get("provider_permissions")
|
|
35
|
+
if isinstance(raw_permissions, dict):
|
|
36
|
+
value = raw_permissions.get("permission_mode")
|
|
37
|
+
if isinstance(value, str) and value.strip():
|
|
38
|
+
permission_mode = value.strip()
|
|
39
|
+
return [
|
|
40
|
+
"claude",
|
|
41
|
+
"-p",
|
|
42
|
+
"--permission-mode",
|
|
43
|
+
permission_mode,
|
|
44
|
+
"--output-format",
|
|
45
|
+
"text",
|
|
46
|
+
input_task.prompt,
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
def _build_command_for_record(self) -> List[str]:
|
|
50
|
+
return ["claude", "-p", "--permission-mode", "plan", "--output-format", "text", "<prompt>"]
|
|
51
|
+
|
|
52
|
+
def _is_success(self, return_code: int, stdout_text: str, stderr_text: str) -> bool:
|
|
53
|
+
if return_code != 0:
|
|
54
|
+
return False
|
|
55
|
+
text = f"{stdout_text}\n{stderr_text}".lower()
|
|
56
|
+
return "api error" not in text
|
|
57
|
+
|
|
58
|
+
def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
|
|
59
|
+
text = raw if isinstance(raw, str) else ""
|
|
60
|
+
return normalize_findings_from_text(text, ctx, "claude")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, List
|
|
4
|
+
|
|
5
|
+
from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
|
|
6
|
+
from .parsing import normalize_findings_from_text
|
|
7
|
+
from .shim import ShimAdapterBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CodexAdapter(ShimAdapterBase):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
super().__init__(
|
|
13
|
+
provider_id="codex",
|
|
14
|
+
binary_name="codex",
|
|
15
|
+
capability_set=CapabilitySet(
|
|
16
|
+
tiers=["C0", "C1", "C2", "C3", "C4", "C5"],
|
|
17
|
+
supports_native_async=False,
|
|
18
|
+
supports_poll_endpoint=False,
|
|
19
|
+
supports_resume_after_restart=True,
|
|
20
|
+
supports_schema_enforcement=True,
|
|
21
|
+
min_supported_version="0.46.0",
|
|
22
|
+
tested_os=["macos"],
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _auth_check_command(self, binary: str) -> List[str]:
|
|
27
|
+
return [binary, "login", "status"]
|
|
28
|
+
|
|
29
|
+
def supported_permission_keys(self) -> List[str]:
|
|
30
|
+
return ["sandbox"]
|
|
31
|
+
|
|
32
|
+
def _build_command(self, input_task: TaskInput) -> List[str]:
|
|
33
|
+
sandbox = "workspace-write"
|
|
34
|
+
raw_permissions = input_task.metadata.get("provider_permissions")
|
|
35
|
+
if isinstance(raw_permissions, dict):
|
|
36
|
+
value = raw_permissions.get("sandbox")
|
|
37
|
+
if isinstance(value, str) and value.strip():
|
|
38
|
+
sandbox = value.strip()
|
|
39
|
+
cmd = [
|
|
40
|
+
"codex",
|
|
41
|
+
"exec",
|
|
42
|
+
"--skip-git-repo-check",
|
|
43
|
+
"-C",
|
|
44
|
+
input_task.repo_root,
|
|
45
|
+
"--sandbox",
|
|
46
|
+
sandbox,
|
|
47
|
+
"--json",
|
|
48
|
+
]
|
|
49
|
+
output_schema_path = input_task.metadata.get("output_schema_path")
|
|
50
|
+
if isinstance(output_schema_path, str) and output_schema_path.strip():
|
|
51
|
+
cmd.extend(["--output-schema", output_schema_path.strip()])
|
|
52
|
+
cmd.append(input_task.prompt)
|
|
53
|
+
return cmd
|
|
54
|
+
|
|
55
|
+
def _build_command_for_record(self) -> List[str]:
|
|
56
|
+
return [
|
|
57
|
+
"codex",
|
|
58
|
+
"exec",
|
|
59
|
+
"--skip-git-repo-check",
|
|
60
|
+
"-C",
|
|
61
|
+
"<repo_root>",
|
|
62
|
+
"--sandbox",
|
|
63
|
+
"workspace-write",
|
|
64
|
+
"--json",
|
|
65
|
+
"--output-schema",
|
|
66
|
+
"<schema-path>",
|
|
67
|
+
"<prompt>",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
def _is_success(self, return_code: int, stdout_text: str, stderr_text: str) -> bool:
|
|
71
|
+
if return_code == 0:
|
|
72
|
+
return True
|
|
73
|
+
# Codex may emit MCP startup errors and still return useful JSON events.
|
|
74
|
+
if stdout_text.strip() and "\"type\":\"turn.completed\"" in stdout_text:
|
|
75
|
+
return True
|
|
76
|
+
if stdout_text.strip() and "\"ok\":true" in stdout_text:
|
|
77
|
+
return True
|
|
78
|
+
if "mcp client" in stderr_text.lower() and stdout_text.strip():
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
|
|
83
|
+
text = raw if isinstance(raw, str) else ""
|
|
84
|
+
return normalize_findings_from_text(text, ctx, "codex")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, List
|
|
4
|
+
|
|
5
|
+
from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
|
|
6
|
+
from .parsing import normalize_findings_from_text
|
|
7
|
+
from .shim import ShimAdapterBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GeminiAdapter(ShimAdapterBase):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
super().__init__(
|
|
13
|
+
provider_id="gemini",
|
|
14
|
+
binary_name="gemini",
|
|
15
|
+
capability_set=CapabilitySet(
|
|
16
|
+
tiers=["C0", "C1", "C2", "C3"],
|
|
17
|
+
supports_native_async=False,
|
|
18
|
+
supports_poll_endpoint=False,
|
|
19
|
+
supports_resume_after_restart=False,
|
|
20
|
+
supports_schema_enforcement=False,
|
|
21
|
+
min_supported_version="0.1.7",
|
|
22
|
+
tested_os=["macos"],
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _auth_check_command(self, binary: str) -> List[str]:
|
|
27
|
+
return [binary, "-p", "Reply with exactly OK"]
|
|
28
|
+
|
|
29
|
+
def _build_command(self, input_task: TaskInput) -> List[str]:
|
|
30
|
+
return ["gemini", "-p", input_task.prompt]
|
|
31
|
+
|
|
32
|
+
def _build_command_for_record(self) -> List[str]:
|
|
33
|
+
return ["gemini", "-p", "<prompt>"]
|
|
34
|
+
|
|
35
|
+
def _is_success(self, return_code: int, stdout_text: str, stderr_text: str) -> bool:
|
|
36
|
+
if return_code != 0:
|
|
37
|
+
return False
|
|
38
|
+
text = f"{stdout_text}\n{stderr_text}".lower()
|
|
39
|
+
if "unknown arguments" in text:
|
|
40
|
+
return False
|
|
41
|
+
if "api error" in text:
|
|
42
|
+
return False
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
|
|
46
|
+
text = raw if isinstance(raw, str) else ""
|
|
47
|
+
return normalize_findings_from_text(text, ctx, "gemini")
|
|
48
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, List
|
|
4
|
+
|
|
5
|
+
from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
|
|
6
|
+
from .parsing import normalize_findings_from_text
|
|
7
|
+
from .shim import ShimAdapterBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenCodeAdapter(ShimAdapterBase):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
super().__init__(
|
|
13
|
+
provider_id="opencode",
|
|
14
|
+
binary_name="opencode",
|
|
15
|
+
capability_set=CapabilitySet(
|
|
16
|
+
tiers=["C0", "C1", "C2", "C3", "C4"],
|
|
17
|
+
supports_native_async=True,
|
|
18
|
+
supports_poll_endpoint=True,
|
|
19
|
+
supports_resume_after_restart=True,
|
|
20
|
+
supports_schema_enforcement=False,
|
|
21
|
+
min_supported_version="1.2.11",
|
|
22
|
+
tested_os=["macos"],
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _auth_check_command(self, binary: str) -> List[str]:
|
|
27
|
+
return [binary, "auth", "list"]
|
|
28
|
+
|
|
29
|
+
def _build_command(self, input_task: TaskInput) -> List[str]:
|
|
30
|
+
return ["opencode", "run", input_task.prompt, "--format", "json"]
|
|
31
|
+
|
|
32
|
+
def _build_command_for_record(self) -> List[str]:
|
|
33
|
+
return ["opencode", "run", "<prompt>", "--format", "json"]
|
|
34
|
+
|
|
35
|
+
def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
|
|
36
|
+
text = raw if isinstance(raw, str) else ""
|
|
37
|
+
return normalize_findings_from_text(text, ctx, "opencode")
|
|
38
|
+
|