agent-shell-py 0.2.2__tar.gz → 0.2.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.
Files changed (113) hide show
  1. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/PKG-INFO +1 -1
  2. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/docs/development/agent_parameter_comparison.md +25 -16
  3. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/_version.py +2 -2
  4. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/copilot_cli_adapter.py +16 -0
  5. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_stream.py +52 -0
  6. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/.github/workflows/build.yml +0 -0
  7. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/.github/workflows/ci.yml +0 -0
  8. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/.github/workflows/publish.yml +0 -0
  9. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/.gitignore +0 -0
  10. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/.python-version +0 -0
  11. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/AGENTS.md +0 -0
  12. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/LICENSE +0 -0
  13. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/README.md +0 -0
  14. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/docs/assets/skill_banner.png +0 -0
  15. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/docs/development/disabled_tools.md +0 -0
  16. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/docs/development/info.md +0 -0
  17. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/docs/development/total_token_count.md +0 -0
  18. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/pyproject.toml +0 -0
  19. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/skills/delegating-code-review/SKILL.md +0 -0
  20. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/skills/invoking-cli-agents/SKILL.md +0 -0
  21. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/skills/invoking-cli-agents/api-reference.md +0 -0
  22. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/__init__.py +0 -0
  23. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/__init__.py +0 -0
  24. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/agent_adapter_protocol.py +0 -0
  25. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/claude_code_adapter.py +0 -0
  26. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/codex_adapter.py +0 -0
  27. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/cursor_adapter.py +0 -0
  28. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/health.py +0 -0
  29. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/model_discovery.py +0 -0
  30. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/opencode_adapter.py +0 -0
  31. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/outcome.py +0 -0
  32. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/pi_adapter.py +0 -0
  33. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/response.py +0 -0
  34. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/stderr_format.py +0 -0
  35. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/adapters/tool_denial.py +0 -0
  36. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/models/__init__.py +0 -0
  37. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/models/agent.py +0 -0
  38. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/process_cleanup.py +0 -0
  39. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/src/agent_shell/shell.py +0 -0
  40. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/__init__.py +0 -0
  41. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/conftest.py +0 -0
  42. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/__init__.py +0 -0
  43. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_claude_code_e2e.py +0 -0
  44. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_codex_e2e.py +0 -0
  45. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_copilot_cli_e2e.py +0 -0
  46. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_cursor_e2e.py +0 -0
  47. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_health_check_e2e.py +0 -0
  48. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_model_discovery_e2e.py +0 -0
  49. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_opencode_e2e.py +0 -0
  50. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/e2e/test_pi_e2e.py +0 -0
  51. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/__init__.py +0 -0
  52. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_claude_code_integration.py +0 -0
  53. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_claude_code_mcp_integration.py +0 -0
  54. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_codex_integration.py +0 -0
  55. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_codex_mcp_integration.py +0 -0
  56. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_copilot_cli_integration.py +0 -0
  57. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_copilot_cli_mcp_integration.py +0 -0
  58. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_cursor_integration.py +0 -0
  59. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_cursor_mcp_integration.py +0 -0
  60. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_health_check_integration.py +0 -0
  61. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_model_discovery_integration.py +0 -0
  62. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_opencode_integration.py +0 -0
  63. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_opencode_mcp_integration.py +0 -0
  64. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_pi_integration.py +0 -0
  65. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_pi_mcp_integration.py +0 -0
  66. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/integration/test_process_lifecycle.py +0 -0
  67. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/__init__.py +0 -0
  68. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/adapter_matrix.py +0 -0
  69. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/codex_fixtures.py +0 -0
  70. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/copilot_fixtures.py +0 -0
  71. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/cursor_fixtures.py +0 -0
  72. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/fixtures.py +0 -0
  73. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/opencode_fixtures.py +0 -0
  74. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/pi_fixtures.py +0 -0
  75. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_adapter_transport.py +0 -0
  76. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_cancel.py +0 -0
  77. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_codex_cancel.py +0 -0
  78. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_codex_execute.py +0 -0
  79. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_codex_parse_event.py +0 -0
  80. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_codex_warnings.py +0 -0
  81. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_cancel.py +0 -0
  82. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_execute.py +0 -0
  83. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_copilot_cli_parse_event.py +0 -0
  84. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_cursor_cancel.py +0 -0
  85. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_cursor_execute.py +0 -0
  86. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_cursor_parse_event.py +0 -0
  87. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_cursor_warnings.py +0 -0
  88. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_execute.py +0 -0
  89. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_execute_outcome.py +0 -0
  90. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_health_probe.py +0 -0
  91. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_mcp_server_spec.py +0 -0
  92. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_model_discovery.py +0 -0
  93. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_models.py +0 -0
  94. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_opencode_cancel.py +0 -0
  95. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_opencode_execute.py +0 -0
  96. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_opencode_parse_event.py +0 -0
  97. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_opencode_spawn.py +0 -0
  98. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_opencode_stream.py +0 -0
  99. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_parse_event.py +0 -0
  100. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_pi_cancel.py +0 -0
  101. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_pi_execute.py +0 -0
  102. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_pi_parse_event.py +0 -0
  103. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_pi_warnings.py +0 -0
  104. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_process_cleanup.py +0 -0
  105. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_process_group_registration.py +0 -0
  106. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_response_aggregation.py +0 -0
  107. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_shell.py +0 -0
  108. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_shell_cancellation.py +0 -0
  109. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_shell_mcp.py +0 -0
  110. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_stderr_format.py +0 -0
  111. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_stream.py +0 -0
  112. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/tests/unit/test_tool_denial.py +0 -0
  113. {agent_shell_py-0.2.2 → agent_shell_py-0.2.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-shell-py
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A lightweight abstraction for executing CLI coding agents headlessly
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,7 +1,7 @@
1
1
  # Agent CLI Parameter Comparison
2
2
 
3
3
  Comparison of headless/non-interactive configuration across supported CLI coding agents.
4
- Last updated: 2026-08-02
4
+ Last updated: 2026-08-05
5
5
 
6
6
  > The summary matrix below has no Pi or Cursor column (it predates both adapters); see the
7
7
  > per-agent detail sections, the model-discovery section, and the `disallowed_tools` table.
@@ -20,9 +20,9 @@ Last updated: 2026-08-02
20
20
  | **Allowed tools** | `--allowed-tools` | No direct flag | `--allow-tool`, `--available-tools` | `tools` in config |
21
21
  | **Disallowed tools** | `--disallowed-tools` | `web_search` config only | `--deny-tool` | `OPENCODE_PERMISSION` env / `permission` config |
22
22
  | **Stream output** | `--output-format stream-json` | `--json` (NDJSON) | `--output-format=json` (JSONL) | `--format json` |
23
- | **Working dir** | cwd + `--add-dir` | `--cd` / `-C` | cwd (no flag) | cwd (no flag) |
23
+ | **Working dir** | cwd + `--add-dir` | `--cd` / `-C` | cwd / `-C` | cwd |
24
24
  | **System prompt** | `--system-prompt` / `--append-system-prompt` | No flag (files only) | No flag (files only) | `instructions` in config |
25
- | **Budget** | `--max-budget-usd` | No direct flag | No direct flag | No direct flag |
25
+ | **Budget** | `--max-budget-usd` | No direct flag | `--max-ai-credits` | No direct flag |
26
26
  | **Auto-approve** | `--dangerously-skip-permissions` | `--yolo` | `--yolo` / `--allow-all` | Auto in `run` mode |
27
27
  | **Session resume** | `--resume` | `exec resume <id>` | `--resume` | `-s <id>` |
28
28
 
@@ -123,23 +123,32 @@ real discovery commands.
123
123
 
124
124
  ## Copilot CLI (GitHub)
125
125
 
126
- - **Headless mode**: `-p` / `--prompt` for one-shot, `--acp --stdio` for programmatic JSON-RPC
127
- - **Model**: `--model` (default `claude-sonnet-4.5`)
128
- - **Effort**: `--effort low|medium|high` or `--reasoning-effort low|medium|high`
129
- - **Allowed tools**: `--allow-tool`, `--deny-tool`, `--available-tools`, `--excluded-tools`, `--allow-all-tools`
130
- - **Auto-approve**: `--allow-all` / `--yolo`, `--autopilot`
126
+ - **Headless mode**: `-p` / `--prompt` for one-shot; `--acp` for Agent Client Protocol
127
+ - **Model**: `--model <model>`; use `auto` to let Copilot choose
128
+ - **Effort**: `--effort` / `--reasoning-effort` with choices
129
+ `none|minimal|low|medium|high|xhigh|max`. AgentShell accepts effort values
130
+ case-insensitively, validates them against these choices, and passes the normalized lowercase
131
+ value to Copilot via `--effort`.
132
+ - **Allowed tools**: `--allow-tool`, `--deny-tool`, `--available-tools`,
133
+ `--excluded-tools`, `--allow-all-tools`
134
+ - **Auto-approve**: `--allow-all-tools`; `--allow-all` / `--yolo` also grant path and URL
135
+ permissions
136
+ - **Agent mode**: `--mode interactive|plan|autopilot`; `--autopilot` is the shortcut
131
137
  - **Output format**: `--output-format=json` (JSONL)
132
138
  - **Silent mode**: `--silent` suppresses stats, prints only response
133
- - **Working directory**: Uses cwd (no flag), ACP mode uses `newSession` parameter
134
- - **System prompt**: No CLI flag, uses `.github/copilot-instructions.md` and `AGENTS.md` files
135
- - **Budget**: No per-run flag, auto-compacts at 95% token limit
139
+ - **Working directory**: CLI supports `-C <directory>`; AgentShell sets subprocess `cwd`
140
+ - **System prompt**: No CLI flag; uses `.github/copilot-instructions.md` and `AGENTS.md` files
141
+ - **AI credit budget**: `--max-ai-credits <credits>` limits credits for the session
142
+ - **Token budget**: No per-run flag; auto-compacts at 95% of the token limit
136
143
  - **Path permissions**: `--allow-all-paths`, `--disallow-temp-dir`
137
144
  - **URL permissions**: `--allow-all-urls`, `--allow-url`, `--deny-url`
138
- - **Session**: `--resume`, `--continue`, plus `--session-id=<uuid>` to start a NEW session under
139
- a chosen id. The adapter resumes with `--resume <id>`. The resumed run reports the SAME
140
- `sessionId`, and an unknown id is rejected ("No session, task, or name matched"), so id
141
- identity is real evidence the CLI continued that session (measured 2026-07-26)
142
- - **ACP mode**: `--acp --stdio` or `--acp --port 3000` for JSON-RPC integration
145
+ - **Session**: `--resume`, `--continue`, and `--session-id <id>`. The latter resumes an existing
146
+ session or task, or assigns a UUID to a new session. AgentShell resumes with `--resume <id>`.
147
+ The resumed run reports the SAME `sessionId`, and an unknown id is rejected
148
+ ("No session, task, or name matched"), so id identity is real evidence the CLI continued that
149
+ session (measured 2026-07-26).
150
+ - **ACP mode**: `--acp` uses stdio by default. Copilot CLI 1.0.78 also accepts hidden
151
+ `--stdio` and `--port <port>` transport options; they are not shown by `copilot --help`.
143
152
 
144
153
  ## OpenCode
145
154
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.2.3'
22
+ __version_tuple__ = version_tuple = (0, 2, 3)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -44,6 +44,21 @@ _DISALLOWED_TOOL_MAP = {
44
44
  "edit": ["write"],
45
45
  }
46
46
 
47
+ _COPILOT_EFFORTS = ("none", "minimal", "low", "medium", "high", "xhigh", "max")
48
+
49
+
50
+ def _normalize_effort(effort: str | None) -> str | None:
51
+ if effort is None:
52
+ return None
53
+
54
+ normalized = effort.lower() if isinstance(effort, str) else None
55
+ if normalized not in _COPILOT_EFFORTS:
56
+ choices = ", ".join(_COPILOT_EFFORTS)
57
+ raise ValueError(
58
+ f"Unsupported Copilot effort {effort!r}; accepted choices: {choices}"
59
+ )
60
+ return normalized
61
+
47
62
 
48
63
  def _json_rpc_frame(message: dict) -> bytes:
49
64
  payload = json.dumps(message, separators=(",", ":")).encode("utf-8")
@@ -160,6 +175,7 @@ class CopilotCLIAdapter:
160
175
  session_id: str | None = None,
161
176
  disallowed_tools: list[str] | None = None,
162
177
  ) -> AsyncIterator[StreamEvent]:
178
+ effort = _normalize_effort(effort)
163
179
  cmd = [
164
180
  "copilot", "-p", prompt,
165
181
  "--output-format", "json",
@@ -115,6 +115,58 @@ class TestStream:
115
115
  assert "--effort" in cmd_args
116
116
  assert cmd_args[cmd_args.index("--effort") + 1] == "high"
117
117
 
118
+ @pytest.mark.parametrize(
119
+ ("effort", "expected"),
120
+ [
121
+ ("none", "none"),
122
+ ("minimal", "minimal"),
123
+ ("low", "low"),
124
+ ("medium", "medium"),
125
+ ("high", "high"),
126
+ ("xhigh", "xhigh"),
127
+ ("max", "max"),
128
+ ("NONE", "none"),
129
+ ("Minimal", "minimal"),
130
+ ("HIGH", "high"),
131
+ ("xHiGh", "xhigh"),
132
+ ("MAX", "max"),
133
+ ("Max", "max"),
134
+ ],
135
+ )
136
+ async def test_accepts_and_normalizes_supported_effort_values(
137
+ self, effort: str, expected: str
138
+ ):
139
+ # Arrange
140
+ adapter = CopilotCLIAdapter()
141
+ ndjson = [MESSAGE_DELTA_EVENT, RESULT_EVENT_SUCCESS]
142
+ mock_process = _make_mock_process(ndjson)
143
+
144
+ # Act
145
+ with patch("asyncio.create_subprocess_exec", return_value=mock_process) as mock_exec:
146
+ async for _ in adapter.stream(cwd="/tmp", prompt="test", effort=effort):
147
+ pass
148
+
149
+ # Assert
150
+ cmd_args = mock_exec.call_args[0]
151
+ assert cmd_args[cmd_args.index("--effort") + 1] == expected
152
+
153
+ async def test_rejects_unsupported_effort_before_spawning(self):
154
+ # Arrange
155
+ adapter = CopilotCLIAdapter()
156
+
157
+ # Act
158
+ with patch("asyncio.create_subprocess_exec") as mock_exec:
159
+ with pytest.raises(ValueError) as error:
160
+ async for _ in adapter.stream(cwd="/tmp", prompt="test", effort="turbo"):
161
+ pass
162
+
163
+ # Assert
164
+ assert str(error.value) == (
165
+ "Unsupported Copilot effort 'turbo'; accepted choices: "
166
+ "none, minimal, low, medium, high, xhigh, max"
167
+ )
168
+ mock_exec.assert_not_called()
169
+
118
170
  async def test_omits_effort_flag_when_none(self):
119
171
  # Arrange
120
172
  adapter = CopilotCLIAdapter()
File without changes
File without changes
File without changes
File without changes