openreward 0.1.96.dev1__tar.gz → 0.1.96.dev2__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 (76) hide show
  1. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/PKG-INFO +1 -1
  2. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/environments/client.py +2 -2
  3. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/__init__.py +6 -6
  4. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/hermes.py +20 -48
  5. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/openclaw.py +19 -32
  6. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward.egg-info/PKG-INFO +1 -1
  7. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/pyproject.toml +1 -1
  8. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_session_toolset.py +0 -136
  9. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/README.md +0 -0
  10. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/__init__.py +0 -0
  11. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/_version.py +0 -0
  12. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/__init__.py +0 -0
  13. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/_session/__init__.py +0 -0
  14. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/_session/http.py +0 -0
  15. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/_session/ping.py +0 -0
  16. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/_session/session.py +0 -0
  17. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/environments/__init__.py +0 -0
  18. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/environments/types.py +0 -0
  19. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/__init__.py +0 -0
  20. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/background.py +0 -0
  21. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/rollout.py +0 -0
  22. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/__init__.py +0 -0
  23. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/ant.py +0 -0
  24. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/base.py +0 -0
  25. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/gdm.py +0 -0
  26. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/models.py +0 -0
  27. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/oai_completions.py +0 -0
  28. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/oai_responses.py +0 -0
  29. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/rollouts/serializers/utils.py +0 -0
  30. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/sandboxes/__init__.py +0 -0
  31. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/sandboxes/client.py +0 -0
  32. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/sandboxes/secrets.py +0 -0
  33. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/api/sandboxes/types.py +0 -0
  34. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/cli.py +0 -0
  35. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/client.py +0 -0
  36. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/__init__.py +0 -0
  37. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/environment.py +0 -0
  38. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/reconnect.py +0 -0
  39. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/server.py +0 -0
  40. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/session.py +0 -0
  41. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/toolset.py +0 -0
  42. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/types.py +0 -0
  43. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/environments/utils.py +0 -0
  44. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/http_client.py +0 -0
  45. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/log_utils.py +0 -0
  46. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/models.py +0 -0
  47. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/__init__.py +0 -0
  48. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/basic/Dockerfile +0 -0
  49. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/basic/__init__.py +0 -0
  50. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/basic/requirements.txt +0 -0
  51. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/basic/requirements.txt.tmpl +0 -0
  52. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/basic/server.py.tmpl +0 -0
  53. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/sandbox/Dockerfile +0 -0
  54. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/sandbox/__init__.py +0 -0
  55. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/sandbox/requirements.txt +0 -0
  56. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/sandbox/sandbox_env.py +0 -0
  57. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/templates/sandbox/server.py +0 -0
  58. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/claude_code.py +0 -0
  59. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/codex.py +0 -0
  60. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/excel.py +0 -0
  61. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/pdf.py +0 -0
  62. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/powerpoint.py +0 -0
  63. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward/toolsets/word.py +0 -0
  64. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward.egg-info/SOURCES.txt +0 -0
  65. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward.egg-info/dependency_links.txt +0 -0
  66. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward.egg-info/entry_points.txt +0 -0
  67. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward.egg-info/requires.txt +0 -0
  68. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/openreward.egg-info/top_level.txt +0 -0
  69. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/setup.cfg +0 -0
  70. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_environment.py +0 -0
  71. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_errors.py +0 -0
  72. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_rollout_info.py +0 -0
  73. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_sanitise.py +0 -0
  74. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_session.py +0 -0
  75. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_tool_conversion.py +0 -0
  76. {openreward-0.1.96.dev1 → openreward-0.1.96.dev2}/tests/test_toolsets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openreward
3
- Version: 0.1.96.dev1
3
+ Version: 0.1.96.dev2
4
4
  Summary: Python SDK for the OpenReward platform.
5
5
  Author-email: GR Inc <hello@gr.inc>
6
6
  Requires-Python: >=3.11
@@ -12,8 +12,8 @@ from openreward.api._session.http import (
12
12
  )
13
13
  from openreward.api._session.session import BaseAsyncSession, SessionTerminatedError
14
14
 
15
- BuiltinToolset = Literal["claude-code", "codex", "openclaw-sandboxed", "hermes-sandboxed"]
16
- _VALID_BUILTIN_TOOLSETS = {"claude-code", "codex", "openclaw-sandboxed", "hermes-sandboxed"}
15
+ BuiltinToolset = Literal["claude-code", "codex"]
16
+ _VALID_BUILTIN_TOOLSETS = {"claude-code", "codex"}
17
17
  from .types import (
18
18
  ImageBlock,
19
19
  JSONObject,
@@ -5,8 +5,8 @@ from openreward.environments.toolset import Toolset
5
5
  from .claude_code import ClaudeCodeToolset
6
6
  from .codex import CodexToolset
7
7
  from .excel import ExcelToolset
8
- from .hermes import HermesSandboxedToolset
9
- from .openclaw import OpenClawSandboxedToolset
8
+ from .hermes import HermesToolset
9
+ from .openclaw import OpenClawToolset
10
10
  from .pdf import PDFToolset
11
11
  from .powerpoint import PowerPointToolset
12
12
  from .word import WordToolset
@@ -18,8 +18,8 @@ from .word import WordToolset
18
18
  BUILTIN_TOOLSETS: dict[str, type[Toolset]] = {
19
19
  ClaudeCodeToolset.name(): ClaudeCodeToolset,
20
20
  CodexToolset.name(): CodexToolset,
21
- OpenClawSandboxedToolset.name(): OpenClawSandboxedToolset,
22
- HermesSandboxedToolset.name(): HermesSandboxedToolset,
21
+ HermesToolset.name(): HermesToolset,
22
+ OpenClawToolset.name(): OpenClawToolset,
23
23
  }
24
24
 
25
25
  __all__ = [
@@ -27,8 +27,8 @@ __all__ = [
27
27
  "ClaudeCodeToolset",
28
28
  "CodexToolset",
29
29
  "ExcelToolset",
30
- "HermesSandboxedToolset",
31
- "OpenClawSandboxedToolset",
30
+ "HermesToolset",
31
+ "OpenClawToolset",
32
32
  "PowerPointToolset",
33
33
  "WordToolset",
34
34
  "PDFToolset",
@@ -1,23 +1,15 @@
1
- """Hermes Agent sandboxed session toolset.
1
+ """Hermes Agent session toolset.
2
2
 
3
- Provides the five built-in tools that Hermes Agent exposes for coding tasks
4
- (``terminal``, ``read_file``, ``write_file``, ``search_files``, ``patch``),
5
- each backed by ``self.sandbox`` from the bound environment. All file and
6
- shell operations are routed through the sandbox so the agent runs in an
7
- isolated environment.
8
-
9
- For the non-sandboxed variant (``hermes``), Hermes uses its own native tools
10
- and only environment-specific tools (e.g. ``submit_answer``) are exposed via
11
- MCP — no SDK toolset is needed.
12
-
13
- Tool names, parameter schemas, and descriptions match Hermes Agent's upstream
14
- registry definitions (``nousresearch/hermes-agent``).
3
+ Provides the five built-in coding tools Hermes Agent exposes
4
+ (``terminal``, ``read_file``, ``write_file``, ``search_files``, ``patch``).
5
+ Tool names, parameter schemas, and descriptions match Hermes Agent's
6
+ upstream registry definitions (``nousresearch/hermes-agent``).
15
7
  """
16
8
  from __future__ import annotations
17
9
 
18
10
  import base64
19
11
  import os
20
- from typing import Any, List, Optional
12
+ from typing import Any, Optional
21
13
 
22
14
  from pydantic import BaseModel
23
15
 
@@ -26,8 +18,6 @@ from openreward.environments.toolset import Toolset
26
18
  from openreward.environments.types import TextBlock, ToolOutput
27
19
 
28
20
 
29
- # ── Sandbox text helpers (inlined; same as claude_code.py) ──
30
-
31
21
  async def _download_text(sandbox: Any, path: str) -> str:
32
22
  data = await sandbox.download(path)
33
23
  return data.decode("utf-8")
@@ -130,12 +120,14 @@ Include enough surrounding context to ensure uniqueness."""
130
120
 
131
121
  # ── Toolset ──
132
122
 
133
- class HermesSandboxedToolset(Toolset):
134
- """Sandboxed session toolset exposing the Hermes Agent five-tool coding surface.
123
+ class HermesToolset(Toolset):
124
+ """Session toolset exposing the Hermes Agent five-tool coding surface.
135
125
 
136
126
  The toolset is bound to a session by passing it to ``env.session(...)``::
137
127
 
138
- with env.session(task=task, toolset="hermes-sandboxed") as session:
128
+ from openreward.toolsets import HermesToolset
129
+
130
+ with env.session(task=task, toolset="hermes") as session:
139
131
  session.call_tool("terminal", {"command": "ls"})
140
132
 
141
133
  Requires the bound environment to define ``self.sandbox``.
@@ -143,7 +135,7 @@ class HermesSandboxedToolset(Toolset):
143
135
 
144
136
  @classmethod
145
137
  def name(cls) -> str:
146
- return "hermes-sandboxed"
138
+ return "hermes"
147
139
 
148
140
  @tool
149
141
  async def terminal(self, params: TerminalParams) -> ToolOutput:
@@ -168,15 +160,11 @@ class HermesSandboxedToolset(Toolset):
168
160
  content = await _download_text(self.sandbox, params.path)
169
161
  lines = content.splitlines()
170
162
 
171
- # Apply offset (1-indexed) and limit
172
163
  start = max(0, params.offset - 1)
173
164
  end = start + params.limit
174
165
  selected_lines = lines[start:end]
175
166
 
176
- # Format as LINE_NUM|CONTENT (Hermes native format)
177
- output_lines = []
178
- for i, line in enumerate(selected_lines, start=start + 1):
179
- output_lines.append(f"{i}|{line}")
167
+ output_lines = [f"{i}|{line}" for i, line in enumerate(selected_lines, start=start + 1)]
180
168
  output = "\n".join(output_lines)
181
169
 
182
170
  return ToolOutput(
@@ -221,7 +209,6 @@ class HermesSandboxedToolset(Toolset):
221
209
  async def search_files(self, params: SearchFilesParams) -> ToolOutput:
222
210
  try:
223
211
  if params.target == "files":
224
- # File name search using find
225
212
  cmd = f"find {params.path} -type f -name '{params.pattern}'"
226
213
  output, code = await self.sandbox.run(cmd)
227
214
  if code != 0:
@@ -230,7 +217,6 @@ class HermesSandboxedToolset(Toolset):
230
217
  blocks=[TextBlock(text=f"search_files failed (exit {code}):\n{output}")],
231
218
  finished=False,
232
219
  )
233
- # Apply offset/limit to results
234
220
  lines = [l for l in output.splitlines() if l.strip()]
235
221
  lines = lines[params.offset:params.offset + params.limit]
236
222
  result = "\n".join(lines)
@@ -241,14 +227,8 @@ class HermesSandboxedToolset(Toolset):
241
227
  finished=False,
242
228
  )
243
229
  else:
244
- # Content search using grep
245
- glob_flag = ""
246
- if params.file_glob:
247
- glob_flag = f" --include='{params.file_glob}'"
248
-
249
- context_flag = ""
250
- if params.context > 0:
251
- context_flag = f" -C {params.context}"
230
+ glob_flag = f" --include='{params.file_glob}'" if params.file_glob else ""
231
+ context_flag = f" -C {params.context}" if params.context > 0 else ""
252
232
 
253
233
  if params.output_mode == "files_only":
254
234
  mode_flag = " -l"
@@ -268,7 +248,6 @@ class HermesSandboxedToolset(Toolset):
268
248
  finished=False,
269
249
  )
270
250
 
271
- # Apply offset/limit to results
272
251
  lines = output.splitlines()
273
252
  lines = lines[params.offset:params.offset + params.limit]
274
253
  result = "\n".join(lines)
@@ -300,7 +279,6 @@ class HermesSandboxedToolset(Toolset):
300
279
  )
301
280
 
302
281
  async def _patch_replace(self, params: PatchParams) -> ToolOutput:
303
- """Replace mode: find a unique string and replace it."""
304
282
  try:
305
283
  if not params.path or params.old_string is None or params.new_string is None:
306
284
  return ToolOutput(
@@ -346,7 +324,6 @@ class HermesSandboxedToolset(Toolset):
346
324
  )
347
325
 
348
326
  async def _patch_v4a(self, params: PatchParams) -> ToolOutput:
349
- """Patch mode: apply V4A multi-file patch content."""
350
327
  try:
351
328
  if not params.patch:
352
329
  return ToolOutput(
@@ -355,14 +332,11 @@ class HermesSandboxedToolset(Toolset):
355
332
  finished=False,
356
333
  )
357
334
 
358
- # Upload patch content to a temp file and apply via patch command
359
335
  patch_tmp = "/tmp/_hermes_patch.diff"
360
336
  await _upload_text(self.sandbox, patch_tmp, params.patch, ensure_trailing_newline=True)
361
337
 
362
- # Try applying as a unified diff first
363
338
  output, code = await self.sandbox.run(f"patch -p1 < {patch_tmp}")
364
339
  if code != 0:
365
- # Clean up and report
366
340
  await self.sandbox.run(f"rm -f {patch_tmp}")
367
341
  return ToolOutput(
368
342
  metadata={"error": output, "exit_code": code},
@@ -385,10 +359,8 @@ class HermesSandboxedToolset(Toolset):
385
359
  )
386
360
 
387
361
 
388
- # Assign descriptions onto each tool method's __doc__ so the framework's
389
- # introspection picks them up.
390
- HermesSandboxedToolset.terminal.__doc__ = TERMINAL_DESCRIPTION
391
- HermesSandboxedToolset.read_file.__doc__ = READ_FILE_DESCRIPTION
392
- HermesSandboxedToolset.write_file.__doc__ = WRITE_FILE_DESCRIPTION
393
- HermesSandboxedToolset.search_files.__doc__ = SEARCH_FILES_DESCRIPTION
394
- HermesSandboxedToolset.patch.__doc__ = PATCH_DESCRIPTION
362
+ HermesToolset.terminal.__doc__ = TERMINAL_DESCRIPTION
363
+ HermesToolset.read_file.__doc__ = READ_FILE_DESCRIPTION
364
+ HermesToolset.write_file.__doc__ = WRITE_FILE_DESCRIPTION
365
+ HermesToolset.search_files.__doc__ = SEARCH_FILES_DESCRIPTION
366
+ HermesToolset.patch.__doc__ = PATCH_DESCRIPTION
@@ -1,17 +1,9 @@
1
- """OpenClaw sandboxed session toolset.
1
+ """OpenClaw session toolset.
2
2
 
3
- Provides the built-in tools that OpenClaw exposes for coding tasks
4
- (``exec``, ``process``, ``read``, ``write``, ``edit``, ``apply_patch``),
5
- each backed by
6
- ``self.sandbox`` from the bound environment. All file and shell operations
7
- are routed through the sandbox so the agent runs in an isolated environment.
8
-
9
- For the non-sandboxed variant (``openclaw``), OpenClaw uses its own native
10
- tools and only environment-specific tools (e.g. ``submit_answer``) are
11
- exposed via MCP — no SDK toolset is needed.
12
-
13
- Tool names, parameter schemas, and descriptions match OpenClaw's upstream
14
- definitions.
3
+ Provides the six built-in coding tools OpenClaw exposes
4
+ (``exec``, ``process``, ``read``, ``write``, ``edit``, ``apply_patch``).
5
+ Tool names, parameter schemas, and descriptions match OpenClaw's
6
+ upstream definitions.
15
7
  """
16
8
  from __future__ import annotations
17
9
 
@@ -19,15 +11,13 @@ import base64
19
11
  import os
20
12
  from typing import Any, List, Optional
21
13
 
22
- from pydantic import BaseModel, Field
14
+ from pydantic import BaseModel
23
15
 
24
16
  from openreward.environments.environment import tool
25
17
  from openreward.environments.toolset import Toolset
26
18
  from openreward.environments.types import TextBlock, ToolOutput
27
19
 
28
20
 
29
- # ── Sandbox text helpers (inlined; same as claude_code.py) ──
30
-
31
21
  async def _download_text(sandbox: Any, path: str) -> str:
32
22
  data = await sandbox.download(path)
33
23
  return data.decode("utf-8")
@@ -132,12 +122,14 @@ operations: '*** Add File:', '*** Update File:' (with optional '*** Move to:'),
132
122
 
133
123
  # ── Toolset ──
134
124
 
135
- class OpenClawSandboxedToolset(Toolset):
136
- """Sandboxed session toolset exposing the OpenClaw six-tool coding surface.
125
+ class OpenClawToolset(Toolset):
126
+ """Session toolset exposing the OpenClaw six-tool coding surface.
137
127
 
138
128
  The toolset is bound to a session by passing it to ``env.session(...)``::
139
129
 
140
- with env.session(task=task, toolset="openclaw-sandboxed") as session:
130
+ from openreward.toolsets import OpenClawToolset
131
+
132
+ with env.session(task=task, toolset="openclaw") as session:
141
133
  session.call_tool("exec", {"command": "ls"})
142
134
 
143
135
  Requires the bound environment to define ``self.sandbox``.
@@ -145,7 +137,7 @@ class OpenClawSandboxedToolset(Toolset):
145
137
 
146
138
  @classmethod
147
139
  def name(cls) -> str:
148
- return "openclaw-sandboxed"
140
+ return "openclaw"
149
141
 
150
142
  @tool
151
143
  async def exec(self, params: ExecParams) -> ToolOutput:
@@ -187,7 +179,6 @@ class OpenClawSandboxedToolset(Toolset):
187
179
  )
188
180
 
189
181
  if action in ("poll", "log"):
190
- # Read output from a background process log file
191
182
  tail_n = params.limit or 200
192
183
  cmd = f"cat /tmp/_oc_proc_{sid}.log 2>/dev/null || echo 'No output available for session {sid}'"
193
184
  if params.offset is not None:
@@ -204,7 +195,6 @@ class OpenClawSandboxedToolset(Toolset):
204
195
 
205
196
  if action == "write":
206
197
  data = params.data or ""
207
- # Write data to the process's stdin via a named pipe or file
208
198
  output, code = await self.sandbox.run(
209
199
  f"echo '{data}' >> /tmp/_oc_proc_{sid}.stdin 2>/dev/null"
210
200
  )
@@ -247,7 +237,7 @@ class OpenClawSandboxedToolset(Toolset):
247
237
  lines = content.splitlines()
248
238
 
249
239
  if params.offset is not None or params.limit is not None:
250
- start = (params.offset or 1) - 1 # Convert 1-indexed to 0-indexed
240
+ start = (params.offset or 1) - 1
251
241
  if params.limit is not None:
252
242
  lines = lines[start:start + params.limit]
253
243
  else:
@@ -331,7 +321,6 @@ class OpenClawSandboxedToolset(Toolset):
331
321
  @tool
332
322
  async def apply_patch(self, params: ApplyPatchParams) -> ToolOutput:
333
323
  try:
334
- # Upload patch content to a temp file and apply via patch command
335
324
  patch_tmp = "/tmp/_openclaw_patch.diff"
336
325
  await _upload_text(self.sandbox, patch_tmp, params.input, ensure_trailing_newline=True)
337
326
 
@@ -359,11 +348,9 @@ class OpenClawSandboxedToolset(Toolset):
359
348
  )
360
349
 
361
350
 
362
- # Assign descriptions onto each tool method's __doc__ so the framework's
363
- # introspection picks them up.
364
- OpenClawSandboxedToolset.exec.__doc__ = EXEC_DESCRIPTION
365
- OpenClawSandboxedToolset.process.__doc__ = PROCESS_DESCRIPTION
366
- OpenClawSandboxedToolset.read.__doc__ = READ_DESCRIPTION
367
- OpenClawSandboxedToolset.write.__doc__ = WRITE_DESCRIPTION
368
- OpenClawSandboxedToolset.edit.__doc__ = EDIT_DESCRIPTION
369
- OpenClawSandboxedToolset.apply_patch.__doc__ = APPLY_PATCH_DESCRIPTION
351
+ OpenClawToolset.exec.__doc__ = EXEC_DESCRIPTION
352
+ OpenClawToolset.process.__doc__ = PROCESS_DESCRIPTION
353
+ OpenClawToolset.read.__doc__ = READ_DESCRIPTION
354
+ OpenClawToolset.write.__doc__ = WRITE_DESCRIPTION
355
+ OpenClawToolset.edit.__doc__ = EDIT_DESCRIPTION
356
+ OpenClawToolset.apply_patch.__doc__ = APPLY_PATCH_DESCRIPTION
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openreward
3
- Version: 0.1.96.dev1
3
+ Version: 0.1.96.dev2
4
4
  Summary: Python SDK for the OpenReward platform.
5
5
  Author-email: GR Inc <hello@gr.inc>
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openreward"
3
- version = "0.1.96.dev1"
3
+ version = "0.1.96.dev2"
4
4
  description = "Python SDK for the OpenReward platform."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -283,139 +283,3 @@ async def test_session_toolset_warns_on_shadow(monkeypatch):
283
283
  call_warnings = [e for e in captured if e[0] == "session_toolset_shadows_env_tool"]
284
284
  assert len(call_warnings) == 1
285
285
  assert call_warnings[0][1]["tool"] == "bash"
286
-
287
-
288
- # ── OpenClaw toolset ──
289
-
290
- OPENCLAW_TOOLS = {"exec", "process", "read", "write", "edit", "apply_patch"}
291
-
292
-
293
- @pytest.mark.asyncio
294
- async def test_openclaw_toolset_tools(client: AsyncOpenReward, server: str):
295
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
296
- tasks = await env.list_tasks(split="train")
297
- async with env.session(tasks[0], toolset="openclaw-sandboxed") as session:
298
- tools = await session.list_tools()
299
- toolset_names = {t.name for t in tools}
300
- for name in OPENCLAW_TOOLS:
301
- assert name in toolset_names, f"missing tool {name}"
302
- # Env's submit tool is preserved.
303
- assert "submit" in toolset_names
304
- # exec description matches OpenClaw style.
305
- exec_spec = next(t for t in tools if t.name == "exec")
306
- assert exec_spec.description.startswith("Execute a shell command")
307
-
308
-
309
- @pytest.mark.asyncio
310
- async def test_openclaw_exec_routes_to_sandbox(client: AsyncOpenReward, server: str):
311
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
312
- tasks = await env.list_tasks(split="train")
313
- async with env.session(tasks[0], toolset="openclaw-sandboxed") as session:
314
- result = await session.call_tool("exec", {"command": "echo hi"})
315
- assert "ran: echo hi" in result.blocks[0].text
316
-
317
-
318
- @pytest.mark.asyncio
319
- async def test_openclaw_write_then_read_roundtrip(client: AsyncOpenReward, server: str):
320
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
321
- tasks = await env.list_tasks(split="train")
322
- async with env.session(tasks[0], toolset="openclaw-sandboxed") as session:
323
- await session.call_tool("write", {"path": "/tmp/oc.txt", "content": "hello openclaw"})
324
- result = await session.call_tool("read", {"path": "/tmp/oc.txt"})
325
- assert "hello openclaw" in result.blocks[0].text
326
-
327
-
328
- @pytest.mark.asyncio
329
- async def test_openclaw_edit_with_edits_array(client: AsyncOpenReward, server: str):
330
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
331
- tasks = await env.list_tasks(split="train")
332
- async with env.session(tasks[0], toolset="openclaw-sandboxed") as session:
333
- await session.call_tool("write", {"path": "/tmp/oc_edit.txt", "content": "foo bar baz"})
334
- result = await session.call_tool("edit", {
335
- "path": "/tmp/oc_edit.txt",
336
- "edits": [{"oldText": "bar", "newText": "qux"}],
337
- })
338
- assert "Successfully edited" in result.blocks[0].text
339
- read_result = await session.call_tool("read", {"path": "/tmp/oc_edit.txt"})
340
- assert "qux" in read_result.blocks[0].text
341
- assert "bar" not in read_result.blocks[0].text
342
-
343
-
344
- @pytest.mark.asyncio
345
- async def test_openclaw_apply_patch(client: AsyncOpenReward, server: str):
346
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
347
- tasks = await env.list_tasks(split="train")
348
- async with env.session(tasks[0], toolset="openclaw-sandboxed") as session:
349
- result = await session.call_tool("apply_patch", {
350
- "input": "*** Begin Patch\n*** Update File: /tmp/test.txt\n--- old\n+++ new\n*** End Patch",
351
- })
352
- # apply_patch runs patch under the hood; mock sandbox returns "ran: ..."
353
- assert result.blocks[0].text is not None
354
-
355
-
356
- # ── Hermes toolset ──
357
-
358
- HERMES_TOOLS = {"terminal", "read_file", "write_file", "search_files", "patch"}
359
-
360
-
361
- @pytest.mark.asyncio
362
- async def test_hermes_toolset_tools(client: AsyncOpenReward, server: str):
363
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
364
- tasks = await env.list_tasks(split="train")
365
- async with env.session(tasks[0], toolset="hermes-sandboxed") as session:
366
- tools = await session.list_tools()
367
- toolset_names = {t.name for t in tools}
368
- for name in HERMES_TOOLS:
369
- assert name in toolset_names, f"missing tool {name}"
370
- assert "submit" in toolset_names
371
- terminal_spec = next(t for t in tools if t.name == "terminal")
372
- assert terminal_spec.description.startswith("Execute shell commands")
373
-
374
-
375
- @pytest.mark.asyncio
376
- async def test_hermes_terminal_routes_to_sandbox(client: AsyncOpenReward, server: str):
377
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
378
- tasks = await env.list_tasks(split="train")
379
- async with env.session(tasks[0], toolset="hermes-sandboxed") as session:
380
- result = await session.call_tool("terminal", {"command": "echo hi"})
381
- assert "ran: echo hi" in result.blocks[0].text
382
-
383
-
384
- @pytest.mark.asyncio
385
- async def test_hermes_write_then_read_roundtrip(client: AsyncOpenReward, server: str):
386
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
387
- tasks = await env.list_tasks(split="train")
388
- async with env.session(tasks[0], toolset="hermes-sandboxed") as session:
389
- await session.call_tool("write_file", {"path": "/tmp/hm.txt", "content": "hello hermes"})
390
- result = await session.call_tool("read_file", {"path": "/tmp/hm.txt"})
391
- # Hermes read_file uses LINE_NUM|CONTENT format
392
- assert "hello hermes" in result.blocks[0].text
393
- assert "1|" in result.blocks[0].text
394
-
395
-
396
- @pytest.mark.asyncio
397
- async def test_hermes_search_files_content(client: AsyncOpenReward, server: str):
398
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
399
- tasks = await env.list_tasks(split="train")
400
- async with env.session(tasks[0], toolset="hermes-sandboxed") as session:
401
- result = await session.call_tool("search_files", {"pattern": "hello", "path": "/tmp"})
402
- # search_files runs grep under the hood; mock sandbox returns "ran: ..."
403
- assert result.blocks[0].text is not None
404
-
405
-
406
- @pytest.mark.asyncio
407
- async def test_hermes_patch_replace_mode(client: AsyncOpenReward, server: str):
408
- env = client.environments.get("envwithsandbox", variant="envwithsandbox", base_url=server)
409
- tasks = await env.list_tasks(split="train")
410
- async with env.session(tasks[0], toolset="hermes-sandboxed") as session:
411
- await session.call_tool("write_file", {"path": "/tmp/hm_patch.txt", "content": "foo bar baz"})
412
- result = await session.call_tool("patch", {
413
- "mode": "replace",
414
- "path": "/tmp/hm_patch.txt",
415
- "old_string": "bar",
416
- "new_string": "qux",
417
- })
418
- assert "Successfully patched" in result.blocks[0].text
419
- read_result = await session.call_tool("read_file", {"path": "/tmp/hm_patch.txt"})
420
- assert "qux" in read_result.blocks[0].text
421
- assert "bar" not in read_result.blocks[0].text