adk-code-mode 1.5.0__tar.gz → 1.6.1__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.
- adk_code_mode-1.6.1/.release-please-manifest.json +3 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/CHANGELOG.md +15 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/PKG-INFO +3 -3
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/README.md +1 -1
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/__about__.py +1 -1
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/_entry.py +0 -1
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/_http_server.py +15 -5
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/__about__.py +1 -1
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tool.py +9 -2
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tools/stubs.py +173 -17
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_integration.py +10 -1
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_remote_integration.py +35 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_stubs_json_schema.py +231 -0
- adk_code_mode-1.5.0/.release-please-manifest.json +0 -3
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.githooks/pre-commit +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.githooks/pre-push +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.github/workflows/ci.yml +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.github/workflows/release-please.yml +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.gitignore +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.prettierrc +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.vscode/settings.json +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/.zed/settings.json +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/LICENSE +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/Makefile +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/docker/Dockerfile +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/pyproject.toml +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/release-please-config.json +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/README.md +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/pyproject.toml +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/__init__.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/__main__.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/_rpc_client.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/protocol.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/scripts/install-git-hooks.sh +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/__init__.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/_artifact_tools.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/metadata.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/output.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/py.typed +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/runtime/__init__.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/runtime/base.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/runtime/docker.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/runtime/protocol.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/runtime/remote.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tool_result_artifacts.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tools/__init__.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tools/catalog.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tools/dispatcher.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tools/namespacing.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/tools/normaliser.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/workspace/__init__.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/src/adk_code_mode/workspace/files.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/__init__.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/_docker_helpers.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/_fake_runtime.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/conftest.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_agent_with_fake_llm.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_artifact_tools.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_catalog.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_dispatcher.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_docker_image.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_docker_integration.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_host_loop.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_metadata.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_namespacing.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_normaliser.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_output.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_protocol.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_remote_backend.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_runtime_docker.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_sandbox_entry.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_sandbox_rpc_client.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_stubs.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_tool.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_tool_result_artifacts.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_version.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/tests/test_workspace_files.py +0 -0
- {adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/uv.lock +0 -0
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.1](https://github.com/a2anet/adk-code-mode/compare/adk-code-mode-v1.6.0...adk-code-mode-v1.6.1) (2026-09-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* exit the sandbox when the host disconnects mid-block ([bd3fb0f](https://github.com/a2anet/adk-code-mode/commit/bd3fb0fb0d8a0cab9370f2ad6db56dfacf566cb4))
|
|
9
|
+
|
|
10
|
+
## [1.6.0](https://github.com/a2anet/adk-code-mode/compare/adk-code-mode-v1.5.0...adk-code-mode-v1.6.0) (2026-08-29)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* bound an execute_code block by default ([1609c0d](https://github.com/a2anet/adk-code-mode/commit/1609c0d24b8563cba51434cd1214b4299aeefc86))
|
|
16
|
+
* expand object oneOf/anyOf branches in tool stub docstrings ([#20](https://github.com/a2anet/adk-code-mode/issues/20)) ([7d42cb1](https://github.com/a2anet/adk-code-mode/commit/7d42cb1ee9d9e217d008776dc74a84034f4b67a9))
|
|
17
|
+
|
|
3
18
|
## [1.5.0](https://github.com/a2anet/adk-code-mode/compare/adk-code-mode-v1.4.0...adk-code-mode-v1.5.0) (2026-08-10)
|
|
4
19
|
|
|
5
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: adk-code-mode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.1
|
|
4
4
|
Summary: A "Code Mode" sandboxed code-execution tool for ADK agents to interact with tools, files, and custom packages with Python
|
|
5
5
|
Project-URL: Documentation, https://github.com/A2ANet/adk-code-mode#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/A2ANet/adk-code-mode/issues
|
|
@@ -300,7 +300,7 @@ All settings are `ExecuteCodeTool` constructor arguments:
|
|
|
300
300
|
| `max_code_mode_metadata_chars` | `50_000` | Budget for the whole rendered `<code-mode>` block, tags included. Oversized blocks degrade in tiers rather than disappearing — see [What the model sees](#what-the-model-sees). |
|
|
301
301
|
| `max_output_chars` | `50_000` | Caps stdout/stderr handed back to the model. Overflow is saved as a session artifact at `code_mode/stdout/<call-id>.txt` and the model sees a head-and-tail view pointing to it. |
|
|
302
302
|
| `max_code_chars` | `1_000_000` | Rejects oversized code payloads before starting a container. |
|
|
303
|
-
| `timeout_seconds` | `
|
|
303
|
+
| `timeout_seconds` | `60` | Caps overall execution time of one `execute_code` call, and with it every tool the block calls. `None` lifts the cap, leaving the platform request timeout (e.g. Cloud Run `--timeout`) as the only bound. |
|
|
304
304
|
| `per_tool_timeout_seconds` | `None` | Caps each individual tool call made from within the sandbox. |
|
|
305
305
|
| `session_idle_timeout_seconds` | `600` | Idle reaper: closes a turn's container once it goes untouched this long. Backstop for turns that never call `release_invocation`. |
|
|
306
306
|
|
|
@@ -273,7 +273,7 @@ All settings are `ExecuteCodeTool` constructor arguments:
|
|
|
273
273
|
| `max_code_mode_metadata_chars` | `50_000` | Budget for the whole rendered `<code-mode>` block, tags included. Oversized blocks degrade in tiers rather than disappearing — see [What the model sees](#what-the-model-sees). |
|
|
274
274
|
| `max_output_chars` | `50_000` | Caps stdout/stderr handed back to the model. Overflow is saved as a session artifact at `code_mode/stdout/<call-id>.txt` and the model sees a head-and-tail view pointing to it. |
|
|
275
275
|
| `max_code_chars` | `1_000_000` | Rejects oversized code payloads before starting a container. |
|
|
276
|
-
| `timeout_seconds` | `
|
|
276
|
+
| `timeout_seconds` | `60` | Caps overall execution time of one `execute_code` call, and with it every tool the block calls. `None` lifts the cap, leaving the platform request timeout (e.g. Cloud Run `--timeout`) as the only bound. |
|
|
277
277
|
| `per_tool_timeout_seconds` | `None` | Caps each individual tool call made from within the sandbox. |
|
|
278
278
|
| `session_idle_timeout_seconds` | `600` | Idle reaper: closes a turn's container once it goes untouched this long. Backstop for turns that never call `release_invocation`. |
|
|
279
279
|
|
{adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/_entry.py
RENAMED
|
@@ -228,7 +228,6 @@ def main() -> int:
|
|
|
228
228
|
port = int(os.environ.get("PORT", "8080"))
|
|
229
229
|
token = os.environ.get(AUTH_TOKEN_ENV)
|
|
230
230
|
asyncio.run(serve(port, token))
|
|
231
|
-
return 0
|
|
232
231
|
|
|
233
232
|
reader, writer = _open_control_streams()
|
|
234
233
|
client = RpcClient(reader=reader, writer=writer)
|
{adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/_http_server.py
RENAMED
|
@@ -38,7 +38,7 @@ import shutil
|
|
|
38
38
|
import tarfile
|
|
39
39
|
import tempfile
|
|
40
40
|
from http import HTTPStatus
|
|
41
|
-
from typing import Any
|
|
41
|
+
from typing import Any, NoReturn
|
|
42
42
|
|
|
43
43
|
import websockets
|
|
44
44
|
import websockets.asyncio.server
|
|
@@ -112,10 +112,11 @@ class _WsBridgeWriter:
|
|
|
112
112
|
pass
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
async def serve(port: int, token: str | None) ->
|
|
115
|
+
async def serve(port: int, token: str | None) -> NoReturn:
|
|
116
116
|
"""Start the WebSocket server on *port* with optional bearer *token* auth.
|
|
117
117
|
|
|
118
|
-
Accepts exactly one WebSocket connection, runs user code, then
|
|
118
|
+
Accepts exactly one WebSocket connection, runs user code, then exits the
|
|
119
|
+
process.
|
|
119
120
|
"""
|
|
120
121
|
stop = asyncio.Event()
|
|
121
122
|
accepted = False
|
|
@@ -136,9 +137,15 @@ async def serve(port: int, token: str | None) -> None:
|
|
|
136
137
|
return None
|
|
137
138
|
|
|
138
139
|
async def handler(ws: Any) -> None:
|
|
140
|
+
turn = asyncio.create_task(_handle_connection(ws))
|
|
141
|
+
# End the turn when the socket closes, even mid-block: user code that
|
|
142
|
+
# ignores the disconnect would otherwise keep this single-use container
|
|
143
|
+
# alive, answering every later connection with "Busy".
|
|
144
|
+
ws_closed = asyncio.create_task(ws.wait_closed())
|
|
145
|
+
ws_closed.add_done_callback(lambda _: turn.cancel())
|
|
139
146
|
try:
|
|
140
|
-
await
|
|
141
|
-
except websockets.ConnectionClosed:
|
|
147
|
+
await turn
|
|
148
|
+
except (websockets.ConnectionClosed, asyncio.CancelledError):
|
|
142
149
|
logger.debug("client disconnected")
|
|
143
150
|
except Exception:
|
|
144
151
|
logger.exception("error handling WebSocket connection")
|
|
@@ -154,6 +161,9 @@ async def serve(port: int, token: str | None) -> None:
|
|
|
154
161
|
):
|
|
155
162
|
logger.info("sandbox HTTP server listening on port %d", port)
|
|
156
163
|
await stop.wait()
|
|
164
|
+
# Don't let interpreter shutdown join a worker thread still running user code
|
|
165
|
+
# the host has given up on.
|
|
166
|
+
os._exit(0)
|
|
157
167
|
|
|
158
168
|
|
|
159
169
|
async def _handle_connection(ws: Any) -> None:
|
|
@@ -97,6 +97,11 @@ class _BlockConnectionLost(Exception):
|
|
|
97
97
|
|
|
98
98
|
logger = logging.getLogger("adk_code_mode.tool")
|
|
99
99
|
|
|
100
|
+
# A block is a model-authored program, so it is bounded by default: an unbounded one
|
|
101
|
+
# strands its container (the idle reaper skips turns still in use) and holds up
|
|
102
|
+
# whatever is waiting on the turn. Hosts that genuinely need longer pass their own.
|
|
103
|
+
DEFAULT_TIMEOUT_SECONDS = 60
|
|
104
|
+
|
|
100
105
|
_REAPER_MAX_POLL_SECONDS = 30.0
|
|
101
106
|
|
|
102
107
|
# One block runs at most twice: the original attempt plus a single reconnect, and
|
|
@@ -202,7 +207,7 @@ class ExecuteCodeTool(BaseTool):
|
|
|
202
207
|
max_code_mode_metadata_chars: int = 50_000,
|
|
203
208
|
max_output_chars: int = 50_000,
|
|
204
209
|
max_code_chars: int = 1_000_000,
|
|
205
|
-
timeout_seconds: int | None =
|
|
210
|
+
timeout_seconds: int | None = DEFAULT_TIMEOUT_SECONDS,
|
|
206
211
|
per_tool_timeout_seconds: float | None = None,
|
|
207
212
|
session_idle_timeout_seconds: float = 600,
|
|
208
213
|
on_artifacts_saved: ArtifactsSavedCallback | None = None,
|
|
@@ -241,7 +246,9 @@ class ExecuteCodeTool(BaseTool):
|
|
|
241
246
|
max_code_chars: Rejects oversized code payloads before starting a
|
|
242
247
|
container.
|
|
243
248
|
timeout_seconds: Caps overall execution time of one ``execute_code``
|
|
244
|
-
call.
|
|
249
|
+
call, and with it every tool the block calls. ``None`` lifts the cap,
|
|
250
|
+
which leaves a runaway block stranding its container until the idle
|
|
251
|
+
reaper takes it.
|
|
245
252
|
per_tool_timeout_seconds: Caps each individual tool call made from
|
|
246
253
|
within the sandbox.
|
|
247
254
|
session_idle_timeout_seconds: Idle reaper: closes a turn's container
|
|
@@ -151,10 +151,9 @@ def _effective_schema(schema: Any) -> dict[str, Any]:
|
|
|
151
151
|
if isinstance(variants, list):
|
|
152
152
|
merged: dict[str, Any] = {}
|
|
153
153
|
for part in variants:
|
|
154
|
-
merged
|
|
155
|
-
#
|
|
156
|
-
merged
|
|
157
|
-
normalised = merged
|
|
154
|
+
merged = _merge_schema(merged, _effective_schema(part))
|
|
155
|
+
# Wrapper keys (e.g. `nullable`) win; `properties` / `required` still merge.
|
|
156
|
+
normalised = _merge_schema(merged, normalised)
|
|
158
157
|
|
|
159
158
|
# A one-variant union expresses no choice, so fold it in rather than lose
|
|
160
159
|
# the description and properties hanging off the single branch.
|
|
@@ -163,12 +162,42 @@ def _effective_schema(schema: Any) -> dict[str, Any]:
|
|
|
163
162
|
if isinstance(branches, list) and len(branches) == 1:
|
|
164
163
|
folded = _effective_schema(branches[0])
|
|
165
164
|
normalised.pop(key)
|
|
166
|
-
folded
|
|
167
|
-
normalised = folded
|
|
165
|
+
normalised = _merge_schema(folded, normalised)
|
|
168
166
|
|
|
169
167
|
return normalised
|
|
170
168
|
|
|
171
169
|
|
|
170
|
+
def _merge_schema(base: dict[str, Any], extra: dict[str, Any]) -> dict[str, Any]:
|
|
171
|
+
"""Combine two schemas the way ``allOf`` means: properties accumulate.
|
|
172
|
+
|
|
173
|
+
A shallow ``update`` replaces the whole ``properties`` dict, so OpenAPI 3.0
|
|
174
|
+
``allOf: [$ref, {properties: extra}]`` would keep only ``extra``.
|
|
175
|
+
"""
|
|
176
|
+
out = dict(base)
|
|
177
|
+
extra_props = extra.get("properties")
|
|
178
|
+
if isinstance(extra_props, dict):
|
|
179
|
+
props = dict(out.get("properties") or {})
|
|
180
|
+
for name, schema in extra_props.items():
|
|
181
|
+
existing = props.get(name)
|
|
182
|
+
if isinstance(existing, dict) and isinstance(schema, dict):
|
|
183
|
+
props[name] = _merge_schema(existing, schema)
|
|
184
|
+
else:
|
|
185
|
+
props[name] = schema
|
|
186
|
+
out["properties"] = props
|
|
187
|
+
extra_required = extra.get("required")
|
|
188
|
+
if isinstance(extra_required, list):
|
|
189
|
+
required = [name for name in (out.get("required") or []) if isinstance(name, str)]
|
|
190
|
+
for name in extra_required:
|
|
191
|
+
if isinstance(name, str) and name not in required:
|
|
192
|
+
required.append(name)
|
|
193
|
+
out["required"] = required
|
|
194
|
+
for key, value in extra.items():
|
|
195
|
+
if key in ("properties", "required"):
|
|
196
|
+
continue
|
|
197
|
+
out[key] = value
|
|
198
|
+
return out
|
|
199
|
+
|
|
200
|
+
|
|
172
201
|
def _schema_to_type(schema: Any) -> str:
|
|
173
202
|
"""Convert a JSON-Schema fragment to a Python type expression (PEP 604).
|
|
174
203
|
|
|
@@ -322,23 +351,93 @@ def _prose(schema: dict[str, Any]) -> str:
|
|
|
322
351
|
return ""
|
|
323
352
|
|
|
324
353
|
|
|
325
|
-
def
|
|
326
|
-
"""
|
|
354
|
+
def _const_or_single_enum(schema: dict[str, Any]) -> str | None:
|
|
355
|
+
"""A branch tag value: ``const`` or a one-element ``enum``."""
|
|
356
|
+
if "const" in schema:
|
|
357
|
+
return _format_scalar(schema["const"])
|
|
358
|
+
enum = schema.get("enum")
|
|
359
|
+
if isinstance(enum, list) and len(enum) == 1:
|
|
360
|
+
return _format_scalar(enum[0])
|
|
361
|
+
return None
|
|
327
362
|
|
|
328
|
-
|
|
329
|
-
|
|
363
|
+
|
|
364
|
+
def _union_branch_tag(branch: dict[str, Any], *, discriminator: str | None) -> str | None:
|
|
365
|
+
"""Label a union branch without implying a nested object key.
|
|
366
|
+
|
|
367
|
+
OpenAPI discriminator unions (``type: openapi | mcp``) often lose
|
|
368
|
+
``discriminator`` once ADK inlines ``$ref`` into ``items``, leaving only a
|
|
369
|
+
single-value ``type`` enum on each object. Tag as ``(type=openapi)`` rather
|
|
370
|
+
than a fake ``openapi:`` key the model might nest under.
|
|
330
371
|
"""
|
|
331
|
-
|
|
332
|
-
|
|
372
|
+
props = branch.get("properties")
|
|
373
|
+
if not isinstance(props, dict):
|
|
374
|
+
return None
|
|
375
|
+
names: list[str] = []
|
|
376
|
+
if discriminator:
|
|
377
|
+
names.append(discriminator)
|
|
378
|
+
if "type" not in names:
|
|
379
|
+
names.append("type")
|
|
380
|
+
for name in names:
|
|
381
|
+
raw = props.get(name)
|
|
382
|
+
if not isinstance(raw, dict):
|
|
383
|
+
continue
|
|
384
|
+
value = _const_or_single_enum(_effective_schema(raw))
|
|
385
|
+
if value is not None:
|
|
386
|
+
return f"{name}={value}"
|
|
387
|
+
return None
|
|
333
388
|
|
|
389
|
+
|
|
390
|
+
def _describe_union(
|
|
391
|
+
schema: dict[str, Any], variants: list[Any], *, indent: str, depth: int
|
|
392
|
+
) -> list[str]:
|
|
393
|
+
"""Expand object ``oneOf``/``anyOf`` branches into the docstring.
|
|
394
|
+
|
|
395
|
+
The signature collapses every object variant to ``dict[str, Any]``, so
|
|
396
|
+
without this the keys on a discriminator union never reach the model.
|
|
397
|
+
Primitive-only unions stay in the type expression and are skipped here.
|
|
398
|
+
"""
|
|
399
|
+
disc = schema.get("discriminator")
|
|
400
|
+
discriminator = None
|
|
401
|
+
if isinstance(disc, dict):
|
|
402
|
+
name = disc.get("propertyName")
|
|
403
|
+
if isinstance(name, str) and name:
|
|
404
|
+
discriminator = name
|
|
405
|
+
|
|
406
|
+
parent_required = schema.get("required")
|
|
407
|
+
lines: list[str] = []
|
|
408
|
+
for raw in variants:
|
|
409
|
+
branch = _effective_schema(raw)
|
|
410
|
+
if isinstance(parent_required, list):
|
|
411
|
+
branch = _merge_schema(branch, {"required": parent_required})
|
|
412
|
+
nested = _describe_fields(branch, indent=indent, depth=depth + 1)
|
|
413
|
+
if not nested:
|
|
414
|
+
continue
|
|
415
|
+
if lines:
|
|
416
|
+
lines.append(f"{indent}-- or --")
|
|
417
|
+
tag = _union_branch_tag(branch, discriminator=discriminator)
|
|
418
|
+
description = _prose(branch).splitlines()
|
|
419
|
+
suffix = _constraint_suffix(branch)
|
|
420
|
+
if tag:
|
|
421
|
+
head = f"{indent}({tag})"
|
|
422
|
+
if description:
|
|
423
|
+
head += f" - {description[0].strip()}"
|
|
424
|
+
lines.append(head + suffix)
|
|
425
|
+
elif description:
|
|
426
|
+
lines.append(f"{indent}{description[0].strip()}{suffix}")
|
|
427
|
+
elif suffix:
|
|
428
|
+
lines.append(f"{indent}{suffix.strip()}")
|
|
429
|
+
for extra in description[1:]:
|
|
430
|
+
lines.append(f"{indent} {extra.strip()}")
|
|
431
|
+
lines.extend(nested)
|
|
432
|
+
return lines
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _describe_properties(schema: dict[str, Any], *, indent: str, depth: int) -> list[str]:
|
|
436
|
+
"""One line per object property, plus nested expansion."""
|
|
334
437
|
properties = schema.get("properties")
|
|
335
438
|
if not isinstance(properties, dict) or not properties:
|
|
336
|
-
items = schema.get("items")
|
|
337
|
-
if isinstance(items, dict):
|
|
338
|
-
return _describe_fields(_effective_schema(items), indent=indent, depth=depth + 1)
|
|
339
439
|
return []
|
|
340
|
-
|
|
341
|
-
required = set(schema.get("required") or [])
|
|
440
|
+
required = {name for name in (schema.get("required") or []) if isinstance(name, str)}
|
|
342
441
|
lines: list[str] = []
|
|
343
442
|
for name, raw in properties.items():
|
|
344
443
|
field = _effective_schema(raw)
|
|
@@ -355,6 +454,63 @@ def _describe_fields(schema: dict[str, Any], *, indent: str, depth: int = 0) ->
|
|
|
355
454
|
return lines
|
|
356
455
|
|
|
357
456
|
|
|
457
|
+
def _describe_prefix_items(schema: dict[str, Any], *, indent: str, depth: int) -> list[str]:
|
|
458
|
+
"""Expand object (or union) slots of a ``prefixItems`` tuple.
|
|
459
|
+
|
|
460
|
+
Primitive slots are already in the ``tuple[...]`` type expression.
|
|
461
|
+
"""
|
|
462
|
+
prefix_items = schema.get("prefixItems")
|
|
463
|
+
if not isinstance(prefix_items, list) or not prefix_items:
|
|
464
|
+
return []
|
|
465
|
+
lines: list[str] = []
|
|
466
|
+
for index, raw in enumerate(prefix_items):
|
|
467
|
+
field = _effective_schema(raw)
|
|
468
|
+
nested = _describe_fields(field, indent=indent + " ", depth=depth + 1)
|
|
469
|
+
if not nested:
|
|
470
|
+
continue
|
|
471
|
+
head = f"{indent}[{index}]: {_schema_to_type(field)}"
|
|
472
|
+
description = _prose(field).splitlines()
|
|
473
|
+
if description:
|
|
474
|
+
head += f" - {description[0].strip()}"
|
|
475
|
+
lines.append(head + _constraint_suffix(field, include_default=True))
|
|
476
|
+
for extra in description[1:]:
|
|
477
|
+
lines.append(f"{indent} {extra.strip()}")
|
|
478
|
+
lines.extend(nested)
|
|
479
|
+
return lines
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _describe_fields(schema: dict[str, Any], *, indent: str, depth: int = 0) -> list[str]:
|
|
483
|
+
"""Lines describing an object's properties, recursing into nested schemas.
|
|
484
|
+
|
|
485
|
+
The signature can only ever say ``dict[str, Any]`` for an object, so without
|
|
486
|
+
this the key names, their types and their meanings never reach the model.
|
|
487
|
+
Multi-branch ``oneOf``/``anyOf`` is walked the same way: a single-branch
|
|
488
|
+
union is folded in ``_effective_schema``, but two object variants have no
|
|
489
|
+
``properties`` at the union itself. Shared parent ``properties`` are listed
|
|
490
|
+
first, then the branches.
|
|
491
|
+
"""
|
|
492
|
+
if depth >= _MAX_SCHEMA_DEPTH:
|
|
493
|
+
return []
|
|
494
|
+
|
|
495
|
+
schema = _effective_schema(schema)
|
|
496
|
+
|
|
497
|
+
variants = schema.get("anyOf") or schema.get("oneOf")
|
|
498
|
+
lines = _describe_properties(schema, indent=indent, depth=depth)
|
|
499
|
+
if isinstance(variants, list) and len(variants) > 1:
|
|
500
|
+
lines.extend(_describe_union(schema, variants, indent=indent, depth=depth))
|
|
501
|
+
if lines:
|
|
502
|
+
return lines
|
|
503
|
+
|
|
504
|
+
prefix_lines = _describe_prefix_items(schema, indent=indent, depth=depth)
|
|
505
|
+
if prefix_lines:
|
|
506
|
+
return prefix_lines
|
|
507
|
+
|
|
508
|
+
items = schema.get("items")
|
|
509
|
+
if isinstance(items, dict):
|
|
510
|
+
return _describe_fields(items, indent=indent, depth=depth + 1)
|
|
511
|
+
return lines
|
|
512
|
+
|
|
513
|
+
|
|
358
514
|
def _format_docstring(
|
|
359
515
|
*, description: str, param_docs: list[tuple[str, dict[str, Any], str | None]]
|
|
360
516
|
) -> str:
|
|
@@ -24,7 +24,11 @@ from google.genai import types as genai_types
|
|
|
24
24
|
|
|
25
25
|
from adk_code_mode.runtime.base import SandboxConnectionError, SandboxResult, SandboxSession
|
|
26
26
|
from adk_code_mode.runtime.protocol import PROTOCOL_VERSION, DoneFrame, Frame, ReadyFrame
|
|
27
|
-
from adk_code_mode.tool import
|
|
27
|
+
from adk_code_mode.tool import (
|
|
28
|
+
DEFAULT_TIMEOUT_SECONDS,
|
|
29
|
+
ExecuteCodeTool,
|
|
30
|
+
ProtocolVersionMismatchError,
|
|
31
|
+
)
|
|
28
32
|
|
|
29
33
|
from ._fake_runtime import FakeRuntime
|
|
30
34
|
|
|
@@ -362,6 +366,11 @@ async def test_timeout_terminates_hung_sandbox() -> None:
|
|
|
362
366
|
assert "Execution exceeded timeout of 1s" in result["stderr"]
|
|
363
367
|
|
|
364
368
|
|
|
369
|
+
def test_a_block_is_bounded_without_the_host_saying_so() -> None:
|
|
370
|
+
tool = ExecuteCodeTool(tools=[], backend=FakeRuntime())
|
|
371
|
+
assert tool.timeout_seconds == DEFAULT_TIMEOUT_SECONDS
|
|
372
|
+
|
|
373
|
+
|
|
365
374
|
@pytest.mark.asyncio
|
|
366
375
|
async def test_timeout_does_not_wait_for_in_flight_tool_call() -> None:
|
|
367
376
|
artifact_service = InMemoryArtifactService()
|
|
@@ -340,3 +340,38 @@ async def test_remote_close_shuts_the_container_down(
|
|
|
340
340
|
break
|
|
341
341
|
await asyncio.sleep(0.02)
|
|
342
342
|
assert http_server.poll() is not None # container exited after ShutdownFrame
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@pytest.mark.asyncio
|
|
346
|
+
async def test_remote_timed_out_block_shuts_the_container_down(
|
|
347
|
+
http_server: subprocess.Popen[bytes],
|
|
348
|
+
) -> None:
|
|
349
|
+
"""A block still running when the host gives up doesn't keep the server alive."""
|
|
350
|
+
artifact_service = InMemoryArtifactService()
|
|
351
|
+
session = Session(
|
|
352
|
+
id="remote-timeout",
|
|
353
|
+
app_name="test-app",
|
|
354
|
+
user_id="u1",
|
|
355
|
+
state={},
|
|
356
|
+
events=[],
|
|
357
|
+
last_update_time=0.0,
|
|
358
|
+
)
|
|
359
|
+
ctx = _make_ctx(artifact_service, session)
|
|
360
|
+
|
|
361
|
+
tool = ExecuteCodeTool(
|
|
362
|
+
tools=[],
|
|
363
|
+
backend=RemoteBackend(url=_server_url(http_server)),
|
|
364
|
+
max_output_chars=10_000,
|
|
365
|
+
timeout_seconds=1,
|
|
366
|
+
)
|
|
367
|
+
result = await tool.run_async(
|
|
368
|
+
args={"code": "import time\ntime.sleep(3600)\n"},
|
|
369
|
+
tool_context=_tool_context(ctx, call_id="remote-timeout-1"),
|
|
370
|
+
)
|
|
371
|
+
assert "exceeded timeout" in result["stderr"]
|
|
372
|
+
|
|
373
|
+
for _ in range(200):
|
|
374
|
+
if http_server.poll() is not None:
|
|
375
|
+
break
|
|
376
|
+
await asyncio.sleep(0.02)
|
|
377
|
+
assert http_server.poll() is not None # container exited despite the running block
|
|
@@ -224,6 +224,35 @@ class TestApplicatorVocabulary:
|
|
|
224
224
|
required=["pair"],
|
|
225
225
|
)
|
|
226
226
|
assert "pair: tuple[str, int]" in source
|
|
227
|
+
# Primitive slots are already in the type; don't repeat them.
|
|
228
|
+
assert "[0]:" not in source
|
|
229
|
+
assert "[1]:" not in source
|
|
230
|
+
|
|
231
|
+
def test_prefix_items_object_slots_expand_their_properties(self) -> None:
|
|
232
|
+
source = _render(
|
|
233
|
+
{
|
|
234
|
+
"pair": {
|
|
235
|
+
"type": "array",
|
|
236
|
+
"prefixItems": [
|
|
237
|
+
{"type": "string"},
|
|
238
|
+
{
|
|
239
|
+
"type": "object",
|
|
240
|
+
"required": ["lat", "lng"],
|
|
241
|
+
"properties": {
|
|
242
|
+
"lat": {"type": "number"},
|
|
243
|
+
"lng": {"type": "number"},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
required=["pair"],
|
|
250
|
+
)
|
|
251
|
+
assert "pair: tuple[str, dict[str, Any]]" in source
|
|
252
|
+
assert "[1]: dict[str, Any]" in source
|
|
253
|
+
assert "lat: float (required)" in source
|
|
254
|
+
assert "lng: float (required)" in source
|
|
255
|
+
assert "[0]:" not in source
|
|
227
256
|
|
|
228
257
|
def test_additional_properties_schema_types_an_open_map(self) -> None:
|
|
229
258
|
source = _render(
|
|
@@ -268,6 +297,29 @@ class TestApplicatorVocabulary:
|
|
|
268
297
|
assert "Min/max capacity range." in source
|
|
269
298
|
assert "min: int" in source
|
|
270
299
|
|
|
300
|
+
def test_allof_merges_properties_from_each_branch(self) -> None:
|
|
301
|
+
"""OpenAPI 3.0 extends a `$ref` with extra fields via a second `allOf`
|
|
302
|
+
object; those keys must join the first object's, not replace them."""
|
|
303
|
+
source = _render(
|
|
304
|
+
{
|
|
305
|
+
"page": {
|
|
306
|
+
"allOf": [
|
|
307
|
+
{
|
|
308
|
+
"type": "object",
|
|
309
|
+
"required": ["limit"],
|
|
310
|
+
"properties": {"limit": {"type": "integer"}},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"required": ["cursor"],
|
|
314
|
+
"properties": {"cursor": {"type": "string"}},
|
|
315
|
+
},
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
assert "limit: int (required)" in source
|
|
321
|
+
assert "cursor: str (required)" in source
|
|
322
|
+
|
|
271
323
|
def test_anyof_becomes_a_union_and_a_lone_branch_is_folded(self) -> None:
|
|
272
324
|
source = _render(
|
|
273
325
|
{
|
|
@@ -279,6 +331,185 @@ class TestApplicatorVocabulary:
|
|
|
279
331
|
assert "many: str | int" in source
|
|
280
332
|
assert "one: str" in source
|
|
281
333
|
assert "Only branch." in source
|
|
334
|
+
# Primitive unions belong in the type expression, not a second listing.
|
|
335
|
+
assert "-- or --" not in source
|
|
336
|
+
|
|
337
|
+
def test_oneof_object_branches_expand_their_properties(self) -> None:
|
|
338
|
+
"""A discriminator union has no `properties` at the `oneOf` itself.
|
|
339
|
+
|
|
340
|
+
Folding only the single-branch case leaves `list[dict[str, Any]]` with
|
|
341
|
+
an empty docstring — the shape of `create_agent(tools=...)`.
|
|
342
|
+
"""
|
|
343
|
+
source = _render(
|
|
344
|
+
{
|
|
345
|
+
"tools": {
|
|
346
|
+
"type": "array",
|
|
347
|
+
"items": {
|
|
348
|
+
"oneOf": [
|
|
349
|
+
{
|
|
350
|
+
"type": "object",
|
|
351
|
+
"additionalProperties": False,
|
|
352
|
+
"required": ["type", "specUrl"],
|
|
353
|
+
"properties": {
|
|
354
|
+
"type": {"type": "string", "enum": ["openapi"]},
|
|
355
|
+
"specUrl": {"type": "string"},
|
|
356
|
+
"operationOverrides": {
|
|
357
|
+
"type": "array",
|
|
358
|
+
"items": {
|
|
359
|
+
"type": "object",
|
|
360
|
+
"required": ["sourceId", "enabled"],
|
|
361
|
+
"properties": {
|
|
362
|
+
"sourceId": {"type": "string"},
|
|
363
|
+
"enabled": {"type": "boolean"},
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"type": "object",
|
|
371
|
+
"additionalProperties": False,
|
|
372
|
+
"required": ["type", "url"],
|
|
373
|
+
"properties": {
|
|
374
|
+
"type": {"type": "string", "enum": ["mcp"]},
|
|
375
|
+
"url": {"type": "string"},
|
|
376
|
+
"toolOverrides": {
|
|
377
|
+
"type": "array",
|
|
378
|
+
"items": {
|
|
379
|
+
"type": "object",
|
|
380
|
+
"required": ["sourceId", "enabled"],
|
|
381
|
+
"properties": {
|
|
382
|
+
"sourceId": {"type": "string"},
|
|
383
|
+
"enabled": {"type": "boolean"},
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
]
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
required=["tools"],
|
|
394
|
+
)
|
|
395
|
+
assert "tools: list[dict[str, Any]]" in source
|
|
396
|
+
assert "(type=openapi) (no other keys)" in source
|
|
397
|
+
assert "specUrl: str (required)" in source
|
|
398
|
+
assert "operationOverrides: list[dict[str, Any]]" in source
|
|
399
|
+
assert "-- or --" in source
|
|
400
|
+
assert "(type=mcp) (no other keys)" in source
|
|
401
|
+
assert "url: str (required)" in source
|
|
402
|
+
assert "toolOverrides: list[dict[str, Any]]" in source
|
|
403
|
+
assert "sourceId: str (required)" in source
|
|
404
|
+
assert "enabled: bool (required)" in source
|
|
405
|
+
|
|
406
|
+
def test_oneof_uses_discriminator_property_for_the_branch_tag(self) -> None:
|
|
407
|
+
source = _render(
|
|
408
|
+
{
|
|
409
|
+
"tool": {
|
|
410
|
+
"discriminator": {"propertyName": "kind"},
|
|
411
|
+
"oneOf": [
|
|
412
|
+
{
|
|
413
|
+
"type": "object",
|
|
414
|
+
"properties": {
|
|
415
|
+
"kind": {"const": "http"},
|
|
416
|
+
"path": {"type": "string"},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"type": "object",
|
|
421
|
+
"properties": {
|
|
422
|
+
"kind": {"const": "stdio"},
|
|
423
|
+
"command": {"type": "string"},
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
assert "(kind=http)" in source
|
|
431
|
+
assert "(kind=stdio)" in source
|
|
432
|
+
assert "path: str" in source
|
|
433
|
+
assert "command: str" in source
|
|
434
|
+
|
|
435
|
+
def test_parent_required_applies_to_oneof_branch_properties(self) -> None:
|
|
436
|
+
source = _render(
|
|
437
|
+
{
|
|
438
|
+
"target": {
|
|
439
|
+
"required": ["value"],
|
|
440
|
+
"oneOf": [
|
|
441
|
+
{
|
|
442
|
+
"type": "object",
|
|
443
|
+
"properties": {"value": {"type": "string"}},
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"type": "object",
|
|
447
|
+
"properties": {"value": {"type": "integer"}},
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
assert "value: str (required)" in source
|
|
454
|
+
assert "value: int (required)" in source
|
|
455
|
+
|
|
456
|
+
def test_oneof_branch_prose_is_preserved(self) -> None:
|
|
457
|
+
source = _render(
|
|
458
|
+
{
|
|
459
|
+
"target": {
|
|
460
|
+
"oneOf": [
|
|
461
|
+
{
|
|
462
|
+
"type": "object",
|
|
463
|
+
"title": "Email target",
|
|
464
|
+
"properties": {"value": {"type": "string"}},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"type": "object",
|
|
468
|
+
"description": "Phone target.",
|
|
469
|
+
"properties": {"value": {"type": "string"}},
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
assert "Email target" in source
|
|
476
|
+
assert "Phone target." in source
|
|
477
|
+
|
|
478
|
+
def test_shared_properties_are_listed_before_oneof_branches(self) -> None:
|
|
479
|
+
source = _render(
|
|
480
|
+
{
|
|
481
|
+
"payment": {
|
|
482
|
+
"properties": {"id": {"type": "string"}},
|
|
483
|
+
"required": ["id"],
|
|
484
|
+
"oneOf": [
|
|
485
|
+
{
|
|
486
|
+
"type": "object",
|
|
487
|
+
"properties": {
|
|
488
|
+
"type": {"type": "string", "enum": ["card"]},
|
|
489
|
+
"cardNumber": {"type": "string"},
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
"type": "object",
|
|
494
|
+
"properties": {
|
|
495
|
+
"type": {"type": "string", "enum": ["bank"]},
|
|
496
|
+
"iban": {"type": "string"},
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
assert "id: str (required)" in source
|
|
504
|
+
assert "(type=card)" in source
|
|
505
|
+
assert "cardNumber: str" in source
|
|
506
|
+
assert "(type=bank)" in source
|
|
507
|
+
assert "iban: str" in source
|
|
508
|
+
id_at = source.index("id: str (required)")
|
|
509
|
+
card_at = source.index("(type=card)")
|
|
510
|
+
or_at = source.index("-- or --")
|
|
511
|
+
bank_at = source.index("(type=bank)")
|
|
512
|
+
assert id_at < card_at < or_at < bank_at
|
|
282
513
|
|
|
283
514
|
|
|
284
515
|
class TestMetaDataVocabulary:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/__init__.py
RENAMED
|
File without changes
|
{adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/__main__.py
RENAMED
|
File without changes
|
{adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/_rpc_client.py
RENAMED
|
File without changes
|
{adk_code_mode-1.5.0 → adk_code_mode-1.6.1}/sandbox-wheel/src/adk_code_mode_sandbox/protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|