langchain-agentcore-codeinterpreter 0.0.2__tar.gz → 0.0.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 (20) hide show
  1. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/PKG-INFO +1 -1
  2. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/langchain_agentcore_codeinterpreter/sandbox.py +23 -6
  3. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/pyproject.toml +1 -1
  4. langchain_agentcore_codeinterpreter-0.0.3/tests/unit_tests/__init__.py +0 -0
  5. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/tests/unit_tests/test_sandbox.py +37 -0
  6. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/tests/unit_tests/test_stream_parsing.py +48 -0
  7. langchain_agentcore_codeinterpreter-0.0.3/uv.lock +2571 -0
  8. langchain_agentcore_codeinterpreter-0.0.2/uv.lock +0 -2470
  9. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/.gitignore +0 -0
  10. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/LICENSE +0 -0
  11. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/Makefile +0 -0
  12. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/README.md +0 -0
  13. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/langchain_agentcore_codeinterpreter/__init__.py +0 -0
  14. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/scripts/check_imports.py +0 -0
  15. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/tests/__init__.py +0 -0
  16. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/tests/integration_tests/__init__.py +0 -0
  17. {langchain_agentcore_codeinterpreter-0.0.2/tests/unit_tests → langchain_agentcore_codeinterpreter-0.0.3/tests/integration_tests/sandbox}/__init__.py +0 -0
  18. /langchain_agentcore_codeinterpreter-0.0.2/tests/integration_tests/test_integration.py → /langchain_agentcore_codeinterpreter-0.0.3/tests/integration_tests/sandbox/test_sandbox.py +0 -0
  19. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/tests/integration_tests/test_compile.py +0 -0
  20. {langchain_agentcore_codeinterpreter-0.0.2 → langchain_agentcore_codeinterpreter-0.0.3}/tests/unit_tests/test_imports.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-agentcore-codeinterpreter
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Amazon Bedrock AgentCore Code Interpreter sandbox integration for Deep Agents
5
5
  Project-URL: Source Code, https://github.com/langchain-ai/langchain-aws/tree/main/libs/agentcore-codeinterpreter
6
6
  Project-URL: Repository, https://github.com/langchain-ai/langchain-aws
@@ -32,6 +32,21 @@ _AGENTCORE_EXECUTOR = ThreadPoolExecutor(
32
32
  )
33
33
 
34
34
 
35
+ def _normalize_relative_path(path: str) -> str:
36
+ """Strip leading slashes and ``./`` prefixes to a canonical relative path.
37
+
38
+ Args:
39
+ path: File path (absolute or relative).
40
+
41
+ Returns:
42
+ Canonical relative path string with no leading ``/`` or ``./``.
43
+ """
44
+ path = path.lstrip("/")
45
+ while path.startswith("./"):
46
+ path = path[2:]
47
+ return path
48
+
49
+
35
50
  class SessionExpiredError(Exception):
36
51
  """Raised when the AgentCore session has expired or been terminated."""
37
52
 
@@ -104,8 +119,7 @@ def _extract_files_from_stream(
104
119
  """
105
120
  path_lookup: dict[str, str] = {}
106
121
  for path in requested_paths:
107
- stripped = path.lstrip("/")
108
- path_lookup[stripped] = path
122
+ path_lookup[_normalize_relative_path(path)] = path
109
123
 
110
124
  files: dict[str, bytes] = {}
111
125
 
@@ -117,13 +131,16 @@ def _extract_files_from_stream(
117
131
  continue
118
132
  resource = item.get("resource", {})
119
133
  uri = resource.get("uri", "")
120
- file_path = uri.replace("file://", "").lstrip("/")
134
+ file_path = _normalize_relative_path(uri.replace("file://", ""))
121
135
 
122
136
  content: bytes | None = None
123
137
  if "text" in resource:
124
138
  content = resource["text"].encode("utf-8")
125
139
  elif "blob" in resource:
126
- content = base64.b64decode(resource["blob"])
140
+ blob = resource["blob"]
141
+ # The AgentCore stream may deliver blob as already-decoded bytes.
142
+ # Only base64-decode when it arrives as encoded text.
143
+ content = blob if isinstance(blob, bytes) else base64.b64decode(blob)
127
144
 
128
145
  if content is not None:
129
146
  original_path = path_lookup.get(file_path, file_path)
@@ -178,7 +195,7 @@ class AgentCoreSandbox(BaseSandbox):
178
195
 
179
196
  @staticmethod
180
197
  def _to_relative_path(path: str) -> str:
181
- """Strip leading slashes so paths are relative for AgentCore APIs.
198
+ """Strip leading slashes and ``./`` prefixes for AgentCore APIs.
182
199
 
183
200
  Args:
184
201
  path: File path (absolute or relative).
@@ -186,7 +203,7 @@ class AgentCoreSandbox(BaseSandbox):
186
203
  Returns:
187
204
  Relative path string.
188
205
  """
189
- return path.lstrip("/")
206
+ return _normalize_relative_path(path)
190
207
 
191
208
  def _invoke(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
192
209
  """Invoke the interpreter and eagerly consume the response stream.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langchain-agentcore-codeinterpreter"
7
- version = "0.0.2"
7
+ version = "0.0.3"
8
8
  description = "Amazon Bedrock AgentCore Code Interpreter sandbox integration for Deep Agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -228,6 +228,36 @@ def test_download_files_handles_session_expiry() -> None:
228
228
  assert results[0].error == "permission_denied"
229
229
 
230
230
 
231
+ def test_download_files_dot_slash_path() -> None:
232
+ """./-prefixed paths must round-trip through readFiles and lookup."""
233
+ fake_png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100
234
+ sandbox, mock = _make_sandbox(
235
+ {
236
+ "stream": [
237
+ {
238
+ "result": {
239
+ "content": [
240
+ {
241
+ "type": "resource",
242
+ "resource": {
243
+ "uri": "file:///data/foo.png",
244
+ "blob": fake_png,
245
+ },
246
+ }
247
+ ]
248
+ }
249
+ }
250
+ ]
251
+ }
252
+ )
253
+ results = sandbox.download_files(["./data/foo.png"])
254
+ mock.invoke.assert_called_once_with(
255
+ method="readFiles", params={"paths": ["data/foo.png"]}
256
+ )
257
+ assert results[0].error is None
258
+ assert results[0].content == fake_png
259
+
260
+
231
261
  # ------------------------------------------------------------------
232
262
  # _to_relative_path()
233
263
  # ------------------------------------------------------------------
@@ -240,6 +270,13 @@ def test_relative_path_stripping() -> None:
240
270
  assert AgentCoreSandbox._to_relative_path("///triple.txt") == "triple.txt"
241
271
 
242
272
 
273
+ def test_relative_path_strips_dot_slash() -> None:
274
+ """./ and repeated ././ prefixes should be stripped."""
275
+ assert AgentCoreSandbox._to_relative_path("./data/foo.png") == "data/foo.png"
276
+ assert AgentCoreSandbox._to_relative_path("././foo.png") == "foo.png"
277
+ assert AgentCoreSandbox._to_relative_path("/./data/foo.png") == "data/foo.png"
278
+
279
+
243
280
  # ------------------------------------------------------------------
244
281
  # Constructor
245
282
  # ------------------------------------------------------------------
@@ -182,6 +182,54 @@ def test_extract_files_blob_resource() -> None:
182
182
  assert files["/data.bin"] == b"binary data"
183
183
 
184
184
 
185
+ def test_extract_files_blob_already_bytes() -> None:
186
+ """Blob delivered as raw bytes must not be base64-decoded again."""
187
+ png_magic = b"\x89PNG\r\n\x1a\n"
188
+ fake_png = png_magic + b"\x00" * 100
189
+ response: dict[str, Any] = {
190
+ "stream": [
191
+ {
192
+ "result": {
193
+ "content": [
194
+ {
195
+ "type": "resource",
196
+ "resource": {
197
+ "uri": "file:///test.png",
198
+ "blob": fake_png,
199
+ },
200
+ }
201
+ ]
202
+ }
203
+ }
204
+ ]
205
+ }
206
+ files = _extract_files_from_stream(response, ["test.png"])
207
+ assert files["test.png"] == fake_png
208
+
209
+
210
+ def test_extract_files_dot_slash_path() -> None:
211
+ """./-prefixed requested paths should match URIs without the prefix."""
212
+ response: dict[str, Any] = {
213
+ "stream": [
214
+ {
215
+ "result": {
216
+ "content": [
217
+ {
218
+ "type": "resource",
219
+ "resource": {
220
+ "uri": "file:///data/foo.png",
221
+ "text": "x",
222
+ },
223
+ }
224
+ ]
225
+ }
226
+ }
227
+ ]
228
+ }
229
+ files = _extract_files_from_stream(response, ["./data/foo.png"])
230
+ assert files["./data/foo.png"] == b"x"
231
+
232
+
185
233
  def test_extract_files_path_normalization() -> None:
186
234
  """Relative requested paths should still match file:/// URIs."""
187
235
  response: dict[str, Any] = {