openreward 0.1.159.dev1__tar.gz → 0.1.159.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 (123) hide show
  1. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/PKG-INFO +1 -1
  2. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/_sandbox_tools.py +4 -7
  3. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/gemini_cli.py +3 -16
  4. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/hermes.py +4 -17
  5. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/openclaw.py +4 -17
  6. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward.egg-info/PKG-INFO +1 -1
  7. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/pyproject.toml +1 -1
  8. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_cli_toolset_root_and_timeout.py +3 -0
  9. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_gemini_cli_toolset.py +3 -5
  10. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_sandbox_tool_shell_safety.py +5 -0
  11. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_sandbox_tools.py +3 -5
  12. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_session_toolset.py +3 -6
  13. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_toolsets.py +4 -7
  14. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/LICENSE +0 -0
  15. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/README.md +0 -0
  16. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/__init__.py +0 -0
  17. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/_update_check.py +0 -0
  18. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/_version.py +0 -0
  19. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/__init__.py +0 -0
  20. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/_session/__init__.py +0 -0
  21. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/_session/http.py +0 -0
  22. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/_session/ping.py +0 -0
  23. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/_session/session.py +0 -0
  24. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/environments/__init__.py +0 -0
  25. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/environments/client.py +0 -0
  26. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/environments/types.py +0 -0
  27. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/errors.py +0 -0
  28. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/__init__.py +0 -0
  29. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/background.py +0 -0
  30. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/rollout.py +0 -0
  31. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/__init__.py +0 -0
  32. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/ant.py +0 -0
  33. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/base.py +0 -0
  34. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/gdm.py +0 -0
  35. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/models.py +0 -0
  36. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/oai_completions.py +0 -0
  37. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/oai_responses.py +0 -0
  38. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/rollouts/serializers/utils.py +0 -0
  39. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/sandboxes/__init__.py +0 -0
  40. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/sandboxes/client.py +0 -0
  41. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/sandboxes/secrets.py +0 -0
  42. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/api/sandboxes/types.py +0 -0
  43. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/chat_backends/__init__.py +0 -0
  44. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/chat_backends/base.py +0 -0
  45. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/chat_backends/openai.py +0 -0
  46. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/cli.py +0 -0
  47. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/client.py +0 -0
  48. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/__init__.py +0 -0
  49. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/_metrics.py +0 -0
  50. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/environment.py +0 -0
  51. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/reconnect.py +0 -0
  52. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/server.py +0 -0
  53. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/session.py +0 -0
  54. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/toolset.py +0 -0
  55. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/types.py +0 -0
  56. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/environments/utils.py +0 -0
  57. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/http_client.py +0 -0
  58. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/judging/__init__.py +0 -0
  59. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/judging/_render.py +0 -0
  60. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/judging/group.py +0 -0
  61. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/judging/groupwise.py +0 -0
  62. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/judging/pairwise.py +0 -0
  63. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/judging/types.py +0 -0
  64. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/log_utils.py +0 -0
  65. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/models.py +0 -0
  66. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/search_backends/__init__.py +0 -0
  67. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/search_backends/backsearch.py +0 -0
  68. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/search_backends/base.py +0 -0
  69. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/search_backends/tavily.py +0 -0
  70. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/__init__.py +0 -0
  71. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/basic/Dockerfile +0 -0
  72. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/basic/__init__.py +0 -0
  73. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/basic/requirements.txt +0 -0
  74. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/basic/requirements.txt.tmpl +0 -0
  75. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/basic/server.py.tmpl +0 -0
  76. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/sandbox/Dockerfile +0 -0
  77. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/sandbox/__init__.py +0 -0
  78. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/sandbox/requirements.txt +0 -0
  79. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/sandbox/sandbox_env.py +0 -0
  80. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/templates/sandbox/server.py +0 -0
  81. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/tools/__init__.py +0 -0
  82. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/tools/search.py +0 -0
  83. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/tools/web.py +0 -0
  84. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/__init__.py +0 -0
  85. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/_web_common.py +0 -0
  86. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/backsearch.py +0 -0
  87. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/claude_code.py +0 -0
  88. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/cli.py +0 -0
  89. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/codex.py +0 -0
  90. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/excel.py +0 -0
  91. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/pdf.py +0 -0
  92. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/powerpoint.py +0 -0
  93. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/web.py +0 -0
  94. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/toolsets/word.py +0 -0
  95. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward/web_service.py +0 -0
  96. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward.egg-info/SOURCES.txt +0 -0
  97. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward.egg-info/dependency_links.txt +0 -0
  98. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward.egg-info/entry_points.txt +0 -0
  99. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward.egg-info/requires.txt +0 -0
  100. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/openreward.egg-info/top_level.txt +0 -0
  101. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/setup.cfg +0 -0
  102. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_chat_backends.py +0 -0
  103. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_cli_toolset_todo_write.py +0 -0
  104. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_env_server_retry.py +0 -0
  105. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_environment.py +0 -0
  106. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_errors.py +0 -0
  107. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_excel_toolset.py +0 -0
  108. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_judging.py +0 -0
  109. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_log_utils.py +0 -0
  110. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_messages.py +0 -0
  111. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_metrics.py +0 -0
  112. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_rich_messages.py +0 -0
  113. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_rollout_info.py +0 -0
  114. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_sandbox_secrets.py +0 -0
  115. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_sandboxes_client_paths.py +0 -0
  116. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_sanitise.py +0 -0
  117. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_score_group.py +0 -0
  118. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_search_backends.py +0 -0
  119. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_session.py +0 -0
  120. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_terminal_tool.py +0 -0
  121. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_tool_conversion.py +0 -0
  122. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_toolset_shell_safety.py +0 -0
  123. {openreward-0.1.159.dev1 → openreward-0.1.159.dev2}/tests/test_web_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openreward
3
- Version: 0.1.159.dev1
3
+ Version: 0.1.159.dev2
4
4
  Summary: Python SDK for the OpenReward platform.
5
5
  Author-email: GR Inc <hello@gr.inc>
6
6
  License-Expression: MIT
@@ -15,7 +15,6 @@ Public exports:
15
15
  """
16
16
  from __future__ import annotations
17
17
 
18
- import base64
19
18
  import os
20
19
  import shlex
21
20
  from typing import Any, Dict, List, Optional, overload
@@ -62,15 +61,13 @@ async def upload_text(
62
61
  ) -> None:
63
62
  """Write text content to a file in the sandbox.
64
63
 
65
- Uses ``check_run`` with a base64-encoded payload so binary-safe
66
- transfer works without depending on ``sandbox.upload`` (which expects
67
- a local-FS path, not in-memory bytes).
64
+ Goes through ``sandbox.upload_bytes`` (the exec-agent's ``/files``
65
+ endpoint), so no shell and no ``base64`` binary are involved and the
66
+ path needs no quoting. Parent directories are not created.
68
67
  """
69
68
  if ensure_trailing_newline and not content.endswith("\n"):
70
69
  content = content + "\n"
71
- encoded = base64.b64encode(content.encode("utf-8")).decode("ascii")
72
- # Quote the destination; the base64 payload is [A-Za-z0-9+/=] and needs none.
73
- await sandbox.check_run(f"echo '{encoded}' | base64 -d > {shlex.quote(path)}")
70
+ await sandbox.upload_bytes(content.encode("utf-8"), path)
74
71
 
75
72
 
76
73
  # ── Workspace rooting ───────────────────────────────────────────────────────
@@ -10,7 +10,6 @@ source (``google-gemini/gemini-cli/packages/core/src/tools``).
10
10
  """
11
11
  from __future__ import annotations
12
12
 
13
- import base64
14
13
  import os
15
14
  import shlex
16
15
  from typing import Any, Dict, List, Literal, Optional
@@ -18,6 +17,7 @@ from typing import Any, Dict, List, Literal, Optional
18
17
  from pydantic import BaseModel, Field
19
18
 
20
19
  from openreward.environments import TextBlock, Toolset, ToolOutput, tool
20
+ from openreward.environments._sandbox_tools import upload_text
21
21
 
22
22
 
23
23
  # ── Pydantic parameter models ──
@@ -87,19 +87,6 @@ async def _download_text(sandbox: Any, path: str) -> str:
87
87
  return data.decode("utf-8")
88
88
 
89
89
 
90
- async def _upload_text(
91
- sandbox: Any,
92
- path: str,
93
- content: str,
94
- ensure_trailing_newline: bool = True,
95
- ) -> None:
96
- if ensure_trailing_newline and not content.endswith("\n"):
97
- content = content + "\n"
98
- encoded = base64.b64encode(content.encode("utf-8")).decode("ascii")
99
- # Quote the destination; the base64 payload is [A-Za-z0-9+/=] and needs none.
100
- await sandbox.check_run(f"echo '{encoded}' | base64 -d > {shlex.quote(path)}")
101
-
102
-
103
90
  # ── Tool descriptions (verbatim from upstream Gemini CLI source) ──
104
91
  # Source: packages/core/src/tools/definitions/model-family-sets/default-legacy.ts
105
92
  # Shell description: packages/core/src/tools/definitions/dynamic-declaration-helpers.ts
@@ -272,7 +259,7 @@ class GeminiCliToolset(Toolset):
272
259
  dir_name = os.path.dirname(params.file_path)
273
260
  if dir_name:
274
261
  await self.sandbox.run(f"mkdir -p {shlex.quote(dir_name)}")
275
- await _upload_text(
262
+ await upload_text(
276
263
  self.sandbox,
277
264
  params.file_path,
278
265
  params.content,
@@ -329,7 +316,7 @@ class GeminiCliToolset(Toolset):
329
316
  else:
330
317
  new_content = content.replace(params.old_string, params.new_string, 1)
331
318
 
332
- await _upload_text(
319
+ await upload_text(
333
320
  self.sandbox, params.file_path, new_content, ensure_trailing_newline=True
334
321
  )
335
322
 
@@ -7,7 +7,6 @@ upstream registry definitions (``nousresearch/hermes-agent``).
7
7
  """
8
8
  from __future__ import annotations
9
9
 
10
- import base64
11
10
  import os
12
11
  import shlex
13
12
  from typing import Any, Optional
@@ -17,6 +16,7 @@ from pydantic import BaseModel
17
16
  from openreward.environments.environment import tool
18
17
  from openreward.environments.toolset import Toolset
19
18
  from openreward.environments.types import TextBlock, ToolOutput
19
+ from openreward.environments._sandbox_tools import upload_text
20
20
 
21
21
 
22
22
  async def _download_text(sandbox: Any, path: str) -> str:
@@ -24,19 +24,6 @@ async def _download_text(sandbox: Any, path: str) -> str:
24
24
  return data.decode("utf-8")
25
25
 
26
26
 
27
- async def _upload_text(
28
- sandbox: Any,
29
- path: str,
30
- content: str,
31
- ensure_trailing_newline: bool = True,
32
- ) -> None:
33
- if ensure_trailing_newline and not content.endswith("\n"):
34
- content = content + "\n"
35
- encoded = base64.b64encode(content.encode("utf-8")).decode("ascii")
36
- # Quote the destination; the base64 payload is [A-Za-z0-9+/=] and needs none.
37
- await sandbox.check_run(f"echo '{encoded}' | base64 -d > {shlex.quote(path)}")
38
-
39
-
40
27
  # ── Pydantic parameter models ──
41
28
 
42
29
  class TerminalParams(BaseModel, extra="forbid"):
@@ -188,7 +175,7 @@ class HermesToolset(Toolset):
188
175
  dir_name = os.path.dirname(params.path)
189
176
  if dir_name:
190
177
  await self.sandbox.run(f"mkdir -p {shlex.quote(dir_name)}")
191
- await _upload_text(
178
+ await upload_text(
192
179
  self.sandbox,
193
180
  params.path,
194
181
  params.content,
@@ -319,7 +306,7 @@ class HermesToolset(Toolset):
319
306
  else:
320
307
  new_content = content.replace(params.old_string, params.new_string, 1)
321
308
 
322
- await _upload_text(self.sandbox, params.path, new_content, ensure_trailing_newline=True)
309
+ await upload_text(self.sandbox, params.path, new_content, ensure_trailing_newline=True)
323
310
 
324
311
  return ToolOutput(
325
312
  metadata={"output": "", "exit_code": 0},
@@ -344,7 +331,7 @@ class HermesToolset(Toolset):
344
331
  )
345
332
 
346
333
  patch_tmp = "/tmp/_hermes_patch.diff"
347
- await _upload_text(self.sandbox, patch_tmp, params.patch, ensure_trailing_newline=True)
334
+ await upload_text(self.sandbox, patch_tmp, params.patch, ensure_trailing_newline=True)
348
335
 
349
336
  output, code = await self.sandbox.run(f"patch -p1 < {shlex.quote(patch_tmp)}")
350
337
  if code != 0:
@@ -7,7 +7,6 @@ upstream definitions.
7
7
  """
8
8
  from __future__ import annotations
9
9
 
10
- import base64
11
10
  import os
12
11
  import shlex
13
12
  from typing import Any, List, Optional
@@ -17,6 +16,7 @@ from pydantic import BaseModel
17
16
  from openreward.environments.environment import tool
18
17
  from openreward.environments.toolset import Toolset
19
18
  from openreward.environments.types import TextBlock, ToolOutput
19
+ from openreward.environments._sandbox_tools import upload_text
20
20
 
21
21
 
22
22
  def _proc_path(sid: str, ext: str) -> str:
@@ -29,19 +29,6 @@ async def _download_text(sandbox: Any, path: str) -> str:
29
29
  return data.decode("utf-8")
30
30
 
31
31
 
32
- async def _upload_text(
33
- sandbox: Any,
34
- path: str,
35
- content: str,
36
- ensure_trailing_newline: bool = True,
37
- ) -> None:
38
- if ensure_trailing_newline and not content.endswith("\n"):
39
- content = content + "\n"
40
- encoded = base64.b64encode(content.encode("utf-8")).decode("ascii")
41
- # Quote the destination; the base64 payload is [A-Za-z0-9+/=] and needs none.
42
- await sandbox.check_run(f"echo '{encoded}' | base64 -d > {shlex.quote(path)}")
43
-
44
-
45
32
  # ── Pydantic parameter models ──
46
33
 
47
34
  class ExecParams(BaseModel, extra="forbid"):
@@ -277,7 +264,7 @@ class OpenClawToolset(Toolset):
277
264
  dir_name = os.path.dirname(params.path)
278
265
  if dir_name:
279
266
  await self.sandbox.run(f"mkdir -p {shlex.quote(dir_name)}")
280
- await _upload_text(
267
+ await upload_text(
281
268
  self.sandbox,
282
269
  params.path,
283
270
  params.content,
@@ -317,7 +304,7 @@ class OpenClawToolset(Toolset):
317
304
  )
318
305
  content = content.replace(item.oldText, item.newText, 1)
319
306
 
320
- await _upload_text(self.sandbox, params.path, content, ensure_trailing_newline=True)
307
+ await upload_text(self.sandbox, params.path, content, ensure_trailing_newline=True)
321
308
 
322
309
  return ToolOutput(
323
310
  metadata={"output": "", "exit_code": 0},
@@ -336,7 +323,7 @@ class OpenClawToolset(Toolset):
336
323
  async def apply_patch(self, params: ApplyPatchParams) -> ToolOutput:
337
324
  try:
338
325
  patch_tmp = "/tmp/_openclaw_patch.diff"
339
- await _upload_text(self.sandbox, patch_tmp, params.input, ensure_trailing_newline=True)
326
+ await upload_text(self.sandbox, patch_tmp, params.input, ensure_trailing_newline=True)
340
327
 
341
328
  output, code = await self.sandbox.run(f"patch -p1 < {shlex.quote(patch_tmp)}")
342
329
  if code != 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openreward
3
- Version: 0.1.159.dev1
3
+ Version: 0.1.159.dev2
4
4
  Summary: Python SDK for the OpenReward platform.
5
5
  Author-email: GR Inc <hello@gr.inc>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openreward"
3
- version = "0.1.159.dev1"
3
+ version = "0.1.159.dev2"
4
4
  description = "Python SDK for the OpenReward platform."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -24,6 +24,9 @@ class RecordingSandbox:
24
24
  self.uploads.append(cmd)
25
25
  return RunResult(output="", return_code=0)
26
26
 
27
+ async def upload_bytes(self, data: bytes, path: str) -> None:
28
+ self.uploads.append(f"upload:{path}")
29
+
27
30
  async def download(self, path: str) -> bytes:
28
31
  self.uploads.append(f"download:{path}")
29
32
  return self.file_content.encode()
@@ -6,7 +6,6 @@ FastAPI server to verify end-to-end HTTP routing through the session layer.
6
6
  """
7
7
 
8
8
  import asyncio
9
- import base64
10
9
  from threading import Thread
11
10
  from typing import Generator
12
11
 
@@ -37,12 +36,11 @@ class _MockSandbox:
37
36
 
38
37
  async def check_run(self, cmd: str, **kwargs) -> str:
39
38
  self.commands.append(cmd)
40
- if " | base64 -d > " in cmd:
41
- encoded = cmd.split("'", 2)[1]
42
- path = cmd.split(" > ", 1)[1]
43
- self.files[path] = base64.b64decode(encoded)
44
39
  return ""
45
40
 
41
+ async def upload_bytes(self, data: bytes, path: str) -> None:
42
+ self.files[path] = data
43
+
46
44
  async def download(self, path: str) -> bytes:
47
45
  return self.files.get(path, b"")
48
46
 
@@ -69,6 +69,11 @@ class RealShellSandbox:
69
69
  async def download(self, path: str) -> bytes:
70
70
  return Path(path).read_bytes()
71
71
 
72
+ async def upload_bytes(self, data: bytes, path: str) -> None:
73
+ # The agent writes the path verbatim: no shell, so no quoting to test here,
74
+ # but a missing parent directory still fails like the agent's 404 would.
75
+ Path(path).write_bytes(data)
76
+
72
77
 
73
78
  class RaisingSandbox:
74
79
  """Every sandbox call raises the given exception."""
@@ -1,7 +1,5 @@
1
1
  """``_sandbox_tools`` outputs carry no reward."""
2
2
 
3
- import base64
4
- import re
5
3
  from functools import partial
6
4
 
7
5
  import pytest
@@ -39,11 +37,11 @@ class FakeSandbox:
39
37
  return (f"ran: {cmd}", 0)
40
38
 
41
39
  async def check_run(self, cmd: str, **kwargs) -> str:
42
- m = re.fullmatch(r"echo '([A-Za-z0-9+/=]+)' \| base64 -d > (.+)", cmd)
43
- if m:
44
- self.files[m.group(2)] = base64.b64decode(m.group(1).encode("ascii"))
45
40
  return ""
46
41
 
42
+ async def upload_bytes(self, data: bytes, path: str) -> None:
43
+ self.files[path] = data
44
+
47
45
  async def download(self, path: str) -> bytes:
48
46
  if path not in self.files:
49
47
  raise FileNotFoundError(path)
@@ -6,7 +6,6 @@ through the HTTP layer (POST /create, GET /task_tools, POST /call).
6
6
  """
7
7
 
8
8
  import asyncio
9
- import base64
10
9
  from threading import Thread
11
10
  from typing import Generator
12
11
 
@@ -37,13 +36,11 @@ class _MockSandbox:
37
36
 
38
37
  async def check_run(self, cmd: str, **kwargs) -> str:
39
38
  self.commands.append(cmd)
40
- # Emulate "echo BASE64 | base64 -d > path" used by _upload_text.
41
- if " | base64 -d > " in cmd:
42
- encoded = cmd.split("'", 2)[1]
43
- path = cmd.split(" > ", 1)[1]
44
- self.files[path] = base64.b64decode(encoded)
45
39
  return ""
46
40
 
41
+ async def upload_bytes(self, data: bytes, path: str) -> None:
42
+ self.files[path] = data
43
+
47
44
  async def download(self, path: str) -> bytes:
48
45
  return self.files.get(path, b"")
49
46
 
@@ -532,7 +532,6 @@ def test_list_task_tools_override():
532
532
  # A FakeFsSandbox in-memory file dict is enough — none of the tools talk
533
533
  # to a real container.
534
534
 
535
- import base64
536
535
  import re
537
536
 
538
537
  from openreward.toolsets import CLIToolset
@@ -555,7 +554,7 @@ class FakeFsSandbox:
555
554
  Supports the three calls the SDK helpers make:
556
555
  * ``await sandbox.download(path)`` -> bytes (from ``self.files``)
557
556
  * ``await sandbox.check_run(cmd)`` -> recognises the base64 upload
558
- pattern emitted by ``upload_text`` and updates ``self.files``;
557
+ ``upload_bytes`` call made by ``upload_text`` and updates ``self.files``;
559
558
  otherwise echoes the command.
560
559
  * ``await sandbox.run(cmd)`` -> echoes ``(cmd, return_code)`` with a
561
560
  configurable default return code.
@@ -582,13 +581,11 @@ class FakeFsSandbox:
582
581
 
583
582
  async def check_run(self, cmd: str):
584
583
  self.check_run_log.append(cmd)
585
- m = re.fullmatch(r"echo '([A-Za-z0-9+/=]+)' \| base64 -d > (.+)", cmd)
586
- if m:
587
- payload = base64.b64decode(m.group(1).encode("ascii"))
588
- self.files[m.group(2)] = payload
589
- return ("", 0)
590
584
  return ("", 0)
591
585
 
586
+ async def upload_bytes(self, data: bytes, path: str) -> None:
587
+ self.files[path] = data
588
+
592
589
  async def run(self, cmd: str, **kwargs):
593
590
  self.run_log.append(cmd)
594
591
  for pat, (output, code) in self.run_responses: