py-opencode-wrapper 0.2.0__tar.gz → 0.2.2__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 (25) hide show
  1. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/PKG-INFO +25 -1
  2. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/README.md +24 -0
  3. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/opencode_wrapper/client.py +17 -4
  4. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/opencode_wrapper/config.py +4 -0
  5. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/py_opencode_wrapper.egg-info/PKG-INFO +25 -1
  6. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/pyproject.toml +1 -1
  7. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_client_async.py +30 -0
  8. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/opencode_wrapper/__init__.py +0 -0
  9. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/opencode_wrapper/errors.py +0 -0
  10. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/opencode_wrapper/events.py +0 -0
  11. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/py_opencode_wrapper.egg-info/SOURCES.txt +0 -0
  12. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/py_opencode_wrapper.egg-info/dependency_links.txt +0 -0
  13. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/py_opencode_wrapper.egg-info/requires.txt +0 -0
  14. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/py_opencode_wrapper.egg-info/top_level.txt +0 -0
  15. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/setup.cfg +0 -0
  16. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_config_instructions.py +0 -0
  17. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_config_permission.py +0 -0
  18. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_event_parser.py +0 -0
  19. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_integration_external_directory.py +0 -0
  20. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_integration_instructions.py +0 -0
  21. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_integration_multi_agent_weather.py +0 -0
  22. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_integration_opencode.py +0 -0
  23. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_integration_parallel.py +0 -0
  24. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_run_result_fuzzy_text.py +0 -0
  25. {py_opencode_wrapper-0.2.0 → py_opencode_wrapper-0.2.2}/tests/test_user_config_isolation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-opencode-wrapper
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Async Python wrapper for OpenCode CLI (opencode run --format json)
5
5
  Project-URL: Homepage, https://github.com/idailylife/oc_py_wrapper
6
6
  Project-URL: Repository, https://github.com/idailylife/oc_py_wrapper
@@ -61,6 +61,12 @@ async def main():
61
61
  asyncio.run(main())
62
62
  ```
63
63
 
64
+ Set `RunConfig(record_thinking=True)` when you want OpenCode reasoning/thinking
65
+ parts included in `result.events` and `log_file` JSON lines. This only maps to
66
+ OpenCode's display/output flag `--thinking`; it does not change model reasoning
67
+ effort. Use `variant` separately if you intentionally want a provider-specific
68
+ reasoning effort.
69
+
64
70
  ### Stream structured JSON events
65
71
 
66
72
  ```python
@@ -104,11 +110,29 @@ Per-call JSON is merged and passed as `OPENCODE_CONFIG_CONTENT` (see [OpenCode c
104
110
  | `permission` | `permission` map (`allow` / `deny`, patterns) |
105
111
  | `mcp` | MCP server definitions |
106
112
  | `tools` | Enable/disable tools (including MCP globs) |
113
+ | `instructions` | Instruction file paths / glob patterns to inject |
107
114
  | `config_overrides` | Any extra top-level config keys to deep-merge |
108
115
 
109
116
  Optional env tuning: `disable_autoupdate=True` sets `OPENCODE_DISABLE_AUTOUPDATE=1`.
110
117
  Note: `ask` is intentionally rejected in subprocess mode (no interactive terminal); use `allow` or `deny`.
111
118
 
119
+ ### User config isolation
120
+
121
+ By default, `RunConfig.inherit_user_config=False` makes each child `opencode`
122
+ process see a sanitized copy of the host's global OpenCode config. The wrapper
123
+ keeps only provider-selection keys (`$schema`, `provider`,
124
+ `disabled_providers`, `enabled_providers`) and drops capability/configuration
125
+ keys such as `mcp`, `agent`, `command`, `tools`, `plugin`, `skills`,
126
+ `instructions`, `permission`, and `model`.
127
+
128
+ This keeps benchmark and orchestration runs reproducible while still allowing
129
+ provider configuration and `opencode auth` credentials to work. Project-level
130
+ config discovered from the workspace is not suppressed.
131
+
132
+ Set `inherit_user_config=True` to restore the legacy behavior of inheriting the
133
+ host OpenCode config as-is. For reproducible runs, pass `model`, `permission`,
134
+ `mcp`, `tools`, and `instructions` explicitly through `RunConfig`.
135
+
112
136
  ## CLI arguments
113
137
 
114
138
  `RunConfig` maps to flags such as `--agent`, `-m`, `-f`, `--attach`, `--title`, etc. Prompt text is appended as the final `opencode run` message argument.
@@ -48,6 +48,12 @@ async def main():
48
48
  asyncio.run(main())
49
49
  ```
50
50
 
51
+ Set `RunConfig(record_thinking=True)` when you want OpenCode reasoning/thinking
52
+ parts included in `result.events` and `log_file` JSON lines. This only maps to
53
+ OpenCode's display/output flag `--thinking`; it does not change model reasoning
54
+ effort. Use `variant` separately if you intentionally want a provider-specific
55
+ reasoning effort.
56
+
51
57
  ### Stream structured JSON events
52
58
 
53
59
  ```python
@@ -91,11 +97,29 @@ Per-call JSON is merged and passed as `OPENCODE_CONFIG_CONTENT` (see [OpenCode c
91
97
  | `permission` | `permission` map (`allow` / `deny`, patterns) |
92
98
  | `mcp` | MCP server definitions |
93
99
  | `tools` | Enable/disable tools (including MCP globs) |
100
+ | `instructions` | Instruction file paths / glob patterns to inject |
94
101
  | `config_overrides` | Any extra top-level config keys to deep-merge |
95
102
 
96
103
  Optional env tuning: `disable_autoupdate=True` sets `OPENCODE_DISABLE_AUTOUPDATE=1`.
97
104
  Note: `ask` is intentionally rejected in subprocess mode (no interactive terminal); use `allow` or `deny`.
98
105
 
106
+ ### User config isolation
107
+
108
+ By default, `RunConfig.inherit_user_config=False` makes each child `opencode`
109
+ process see a sanitized copy of the host's global OpenCode config. The wrapper
110
+ keeps only provider-selection keys (`$schema`, `provider`,
111
+ `disabled_providers`, `enabled_providers`) and drops capability/configuration
112
+ keys such as `mcp`, `agent`, `command`, `tools`, `plugin`, `skills`,
113
+ `instructions`, `permission`, and `model`.
114
+
115
+ This keeps benchmark and orchestration runs reproducible while still allowing
116
+ provider configuration and `opencode auth` credentials to work. Project-level
117
+ config discovered from the workspace is not suppressed.
118
+
119
+ Set `inherit_user_config=True` to restore the legacy behavior of inheriting the
120
+ host OpenCode config as-is. For reproducible runs, pass `model`, `permission`,
121
+ `mcp`, `tools`, and `instructions` explicitly through `RunConfig`.
122
+
99
123
  ## CLI arguments
100
124
 
101
125
  `RunConfig` maps to flags such as `--agent`, `-m`, `-f`, `--attach`, `--title`, etc. Prompt text is appended as the final `opencode run` message argument.
@@ -73,7 +73,7 @@ def build_argv(
73
73
  cmd.extend(["--port", str(run_cfg.port)])
74
74
  if run_cfg.variant:
75
75
  cmd.extend(["--variant", run_cfg.variant])
76
- if run_cfg.thinking is True:
76
+ if run_cfg.record_thinking is True or run_cfg.thinking is True:
77
77
  cmd.append("--thinking")
78
78
 
79
79
  if prompt:
@@ -81,7 +81,12 @@ def build_argv(
81
81
  return cmd
82
82
 
83
83
 
84
- def build_env(run_cfg: RunConfig, base: Mapping[str, str] | None = None) -> dict[str, str]:
84
+ def build_env(
85
+ run_cfg: RunConfig,
86
+ base: Mapping[str, str] | None = None,
87
+ *,
88
+ cwd: str | Path | None = None,
89
+ ) -> dict[str, str]:
85
90
  env = dict(base if base is not None else os.environ)
86
91
  if run_cfg.extra_env:
87
92
  env.update(dict(run_cfg.extra_env))
@@ -90,6 +95,14 @@ def build_env(run_cfg: RunConfig, base: Mapping[str, str] | None = None) -> dict
90
95
  env["OPENCODE_CONFIG_CONTENT"] = content
91
96
  if run_cfg.disable_autoupdate:
92
97
  env["OPENCODE_DISABLE_AUTOUPDATE"] = "1"
98
+ # opencode's `run` cmd resolves the project root as
99
+ # `process.env.PWD ?? process.cwd()` (run.ts:276), and the bash builtin
100
+ # `pwd` reads $PWD too. asyncio.create_subprocess_exec(cwd=...) only
101
+ # chdirs the child; it leaves PWD inherited from the parent shell, which
102
+ # makes opencode operate against the wrong directory. Pin PWD to the
103
+ # resolved workspace so the child sees a consistent cwd.
104
+ if cwd is not None:
105
+ env["PWD"] = str(cwd)
93
106
  return env
94
107
 
95
108
 
@@ -380,8 +393,8 @@ class AsyncOpenCodeClient:
380
393
  validate_config_for_run(run_cfg)
381
394
  bin_path = self.resolved_binary()
382
395
  argv = build_argv(bin_path, prompt, run_cfg)
383
- env = build_env(run_cfg)
384
396
  cwd = str(Path(workspace).expanduser().resolve())
397
+ env = build_env(run_cfg, cwd=cwd)
385
398
 
386
399
  events_acc: list[dict[str, Any]] = []
387
400
  raw_acc: list[str] = []
@@ -436,8 +449,8 @@ class AsyncOpenCodeClient:
436
449
  validate_config_for_run(run_cfg)
437
450
  bin_path = self.resolved_binary()
438
451
  argv = build_argv(bin_path, prompt, run_cfg)
439
- env = build_env(run_cfg)
440
452
  cwd = str(Path(workspace).expanduser().resolve())
453
+ env = build_env(run_cfg, cwd=cwd)
441
454
 
442
455
  events_acc: list[dict[str, Any]] = []
443
456
  raw_acc: list[str] = []
@@ -147,6 +147,10 @@ class RunConfig:
147
147
  remote_dir: str | None = None
148
148
  port: int | None = None
149
149
  variant: str | None = None
150
+ # Include OpenCode reasoning/thinking parts in the JSON event stream.
151
+ # This maps to `opencode run --thinking`; it does not set model reasoning effort.
152
+ record_thinking: bool | None = None
153
+ # Backward-compatible alias for record_thinking.
150
154
  thinking: bool | None = None
151
155
  print_logs: bool | None = None
152
156
  log_level: str | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-opencode-wrapper
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Async Python wrapper for OpenCode CLI (opencode run --format json)
5
5
  Project-URL: Homepage, https://github.com/idailylife/oc_py_wrapper
6
6
  Project-URL: Repository, https://github.com/idailylife/oc_py_wrapper
@@ -61,6 +61,12 @@ async def main():
61
61
  asyncio.run(main())
62
62
  ```
63
63
 
64
+ Set `RunConfig(record_thinking=True)` when you want OpenCode reasoning/thinking
65
+ parts included in `result.events` and `log_file` JSON lines. This only maps to
66
+ OpenCode's display/output flag `--thinking`; it does not change model reasoning
67
+ effort. Use `variant` separately if you intentionally want a provider-specific
68
+ reasoning effort.
69
+
64
70
  ### Stream structured JSON events
65
71
 
66
72
  ```python
@@ -104,11 +110,29 @@ Per-call JSON is merged and passed as `OPENCODE_CONFIG_CONTENT` (see [OpenCode c
104
110
  | `permission` | `permission` map (`allow` / `deny`, patterns) |
105
111
  | `mcp` | MCP server definitions |
106
112
  | `tools` | Enable/disable tools (including MCP globs) |
113
+ | `instructions` | Instruction file paths / glob patterns to inject |
107
114
  | `config_overrides` | Any extra top-level config keys to deep-merge |
108
115
 
109
116
  Optional env tuning: `disable_autoupdate=True` sets `OPENCODE_DISABLE_AUTOUPDATE=1`.
110
117
  Note: `ask` is intentionally rejected in subprocess mode (no interactive terminal); use `allow` or `deny`.
111
118
 
119
+ ### User config isolation
120
+
121
+ By default, `RunConfig.inherit_user_config=False` makes each child `opencode`
122
+ process see a sanitized copy of the host's global OpenCode config. The wrapper
123
+ keeps only provider-selection keys (`$schema`, `provider`,
124
+ `disabled_providers`, `enabled_providers`) and drops capability/configuration
125
+ keys such as `mcp`, `agent`, `command`, `tools`, `plugin`, `skills`,
126
+ `instructions`, `permission`, and `model`.
127
+
128
+ This keeps benchmark and orchestration runs reproducible while still allowing
129
+ provider configuration and `opencode auth` credentials to work. Project-level
130
+ config discovered from the workspace is not suppressed.
131
+
132
+ Set `inherit_user_config=True` to restore the legacy behavior of inheriting the
133
+ host OpenCode config as-is. For reproducible runs, pass `model`, `permission`,
134
+ `mcp`, `tools`, and `instructions` explicitly through `RunConfig`.
135
+
112
136
  ## CLI arguments
113
137
 
114
138
  `RunConfig` maps to flags such as `--agent`, `-m`, `-f`, `--attach`, `--title`, etc. Prompt text is appended as the final `opencode run` message argument.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "py-opencode-wrapper"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Async Python wrapper for OpenCode CLI (opencode run --format json)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
@@ -49,6 +49,19 @@ def test_build_argv_with_agent_model_files() -> None:
49
49
  assert "-f" in argv
50
50
 
51
51
 
52
+ def test_build_argv_record_thinking_adds_display_flag_only() -> None:
53
+ cfg = RunConfig(record_thinking=True)
54
+ argv = build_argv("/x/opencode", "p", cfg)
55
+ assert "--thinking" in argv
56
+ assert "--variant" not in argv
57
+
58
+
59
+ def test_build_argv_thinking_alias_still_adds_display_flag() -> None:
60
+ cfg = RunConfig(thinking=True)
61
+ argv = build_argv("/x/opencode", "p", cfg)
62
+ assert "--thinking" in argv
63
+
64
+
52
65
  def test_build_env_config_content_and_autoupdate() -> None:
53
66
  cfg = RunConfig(permission={"bash": "deny"}, disable_autoupdate=True)
54
67
  env = build_env(cfg, base={"HOME": "/tmp"})
@@ -57,6 +70,23 @@ def test_build_env_config_content_and_autoupdate() -> None:
57
70
  assert env.get("OPENCODE_DISABLE_AUTOUPDATE") == "1"
58
71
 
59
72
 
73
+ def test_build_env_sets_pwd_to_cwd() -> None:
74
+ """opencode's run.ts:276 reads `process.env.PWD ?? process.cwd()` to
75
+ resolve the project root, and the bash builtin pwd reads $PWD.
76
+ asyncio.create_subprocess_exec(cwd=...) only chdirs the child; PWD stays
77
+ inherited from the parent shell. build_env must pin PWD to the workspace
78
+ so opencode and bash both see a consistent cwd."""
79
+ cfg = RunConfig()
80
+ env = build_env(cfg, base={"HOME": "/tmp", "PWD": "/some/other/dir"}, cwd="/ws/x")
81
+ assert env["PWD"] == "/ws/x"
82
+
83
+
84
+ def test_build_env_without_cwd_leaves_pwd_untouched() -> None:
85
+ cfg = RunConfig()
86
+ env = build_env(cfg, base={"HOME": "/tmp", "PWD": "/parent"})
87
+ assert env["PWD"] == "/parent"
88
+
89
+
60
90
  @pytest.mark.asyncio
61
91
  async def test_readline_unlimited_normal_line() -> None:
62
92
  """Lines within the default 64 KiB limit are returned as-is."""