adk-code-mode 1.4.0__tar.gz → 1.5.0__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.5.0/.release-please-manifest.json +3 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/CHANGELOG.md +7 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/PKG-INFO +11 -35
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/README.md +10 -34
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/__about__.py +1 -1
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/__about__.py +1 -1
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tools/namespacing.py +20 -23
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_catalog.py +12 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_namespacing.py +48 -2
- adk_code_mode-1.4.0/.release-please-manifest.json +0 -3
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.githooks/pre-commit +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.githooks/pre-push +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.github/workflows/ci.yml +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.github/workflows/release-please.yml +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.gitignore +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.prettierrc +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.vscode/settings.json +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/.zed/settings.json +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/LICENSE +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/Makefile +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/docker/Dockerfile +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/pyproject.toml +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/release-please-config.json +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/README.md +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/pyproject.toml +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/__init__.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/__main__.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/_entry.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/_http_server.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/_rpc_client.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/protocol.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/scripts/install-git-hooks.sh +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/__init__.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/_artifact_tools.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/metadata.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/output.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/py.typed +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/runtime/__init__.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/runtime/base.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/runtime/docker.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/runtime/protocol.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/runtime/remote.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tool.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tool_result_artifacts.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tools/__init__.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tools/catalog.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tools/dispatcher.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tools/normaliser.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/tools/stubs.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/workspace/__init__.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/src/adk_code_mode/workspace/files.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/__init__.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/_docker_helpers.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/_fake_runtime.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/conftest.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_agent_with_fake_llm.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_artifact_tools.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_dispatcher.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_docker_image.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_docker_integration.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_host_loop.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_integration.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_metadata.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_normaliser.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_output.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_protocol.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_remote_backend.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_remote_integration.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_runtime_docker.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_sandbox_entry.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_sandbox_rpc_client.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_stubs.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_stubs_json_schema.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_tool.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_tool_result_artifacts.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_version.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/tests/test_workspace_files.py +0 -0
- {adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/uv.lock +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [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
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* scope tool-name uniqueness to each namespace ([#17](https://github.com/a2anet/adk-code-mode/issues/17)) ([437bc0d](https://github.com/a2anet/adk-code-mode/commit/437bc0dc930521a933e5c25b4bd6103e9434fc27))
|
|
9
|
+
|
|
3
10
|
## [1.4.0](https://github.com/a2anet/adk-code-mode/compare/adk-code-mode-v1.3.0...adk-code-mode-v1.4.0) (2026-07-28)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adk-code-mode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
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
|
|
@@ -85,13 +85,17 @@ Build an `ExecuteCodeTool`, give it the tools the sandboxed code may call, and a
|
|
|
85
85
|
|
|
86
86
|
```python
|
|
87
87
|
from google.adk.agents import LlmAgent
|
|
88
|
+
from google.adk.features import FeatureName, override_feature_enabled
|
|
88
89
|
from adk_code_mode import ExecuteCodeTool, RemoteBackend
|
|
89
90
|
|
|
91
|
+
# Required for docstrings to show full tool schemas
|
|
92
|
+
override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, True)
|
|
93
|
+
|
|
90
94
|
tool = ExecuteCodeTool(
|
|
91
95
|
tools=[my_fn_tool, McpToolset(...), OpenAPIToolset(...)],
|
|
92
96
|
backend=RemoteBackend(
|
|
93
|
-
url="https://sandbox-xyz.run.app",
|
|
94
|
-
token="your-secret-token",
|
|
97
|
+
url="https://sandbox-xyz.run.app",
|
|
98
|
+
token="your-secret-token",
|
|
95
99
|
),
|
|
96
100
|
)
|
|
97
101
|
|
|
@@ -112,8 +116,12 @@ root_agent = LlmAgent(
|
|
|
112
116
|
> **`UnsafeLocalDockerBackend` is not safe for production or multi-tenant use.** See [Safety](#-safety).
|
|
113
117
|
|
|
114
118
|
```python
|
|
119
|
+
from google.adk.features import FeatureName, override_feature_enabled
|
|
115
120
|
from adk_code_mode import ExecuteCodeTool, UnsafeLocalDockerBackend
|
|
116
121
|
|
|
122
|
+
# Required for docstrings to show full tool schemas
|
|
123
|
+
override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, True)
|
|
124
|
+
|
|
117
125
|
tool = ExecuteCodeTool(
|
|
118
126
|
tools=[my_fn_tool, McpToolset(...), OpenAPIToolset(...)],
|
|
119
127
|
backend=UnsafeLocalDockerBackend(image="ghcr.io/a2anet/adk-code-mode:latest"),
|
|
@@ -377,38 +385,6 @@ Tags are indented; their content is not. The `<tools-package>` body is Python so
|
|
|
377
385
|
|
|
378
386
|
With `save_tool_results_as_artifacts` enabled (the default), each non-artifact tool above — e.g. `list_channels` and `send_message` — also carries two optional `artifact_name: str | None = ...` / `artifact_description: str | None = ...` parameters for naming its saved result.
|
|
379
387
|
|
|
380
|
-
#### Schema fidelity
|
|
381
|
-
|
|
382
|
-
Stubs are rendered from each tool's declaration, so whatever the declaration drops can never reach the model. Nested object properties, their descriptions and JSON Schema's validation keywords all land in the docstring, because no Python type expresses them:
|
|
383
|
-
|
|
384
|
-
~~~python
|
|
385
|
-
def create_minimum_spend_rule(*, space_id: int, conditions: dict[str, Any] | None = ..., label: str | None = ...) -> Any:
|
|
386
|
-
"""Creates a new minimum spend rule under the MinimumSpend policy.
|
|
387
|
-
|
|
388
|
-
Args:
|
|
389
|
-
space_id: The unique identifier of the venue space. (format=int32)
|
|
390
|
-
conditions: Conditions that must all be satisfied for the rule to apply (AND logic).
|
|
391
|
-
daysOfWeek: list[int] | None - Days of the week (1=Monday, 7=Sunday). (items: minimum=1, maximum=7)
|
|
392
|
-
timeRange: dict[str, Any] | None - Time window of the booking start time.
|
|
393
|
-
from: str (pattern=^([01][0-9]|2[0-3]):[0-5][0-9]$, example=18:00)
|
|
394
|
-
to: str (example=23:59)
|
|
395
|
-
label: (maxLength=255)
|
|
396
|
-
"""
|
|
397
|
-
~~~
|
|
398
|
-
|
|
399
|
-
Every JSON Schema 2020-12 Validation and Meta-Data keyword is covered; `tests/test_stubs_json_schema.py` reads the keyword set from the meta-schemas and fails if one goes unclassified.
|
|
400
|
-
|
|
401
|
-
> [!IMPORTANT]
|
|
402
|
-
> **Enable ADK's `JSON_SCHEMA_FOR_FUNC_DECL` feature to get the full picture.** It is off by default, and without it ADK builds declarations through a Gemini `types.Schema`, which reduces an `allOf`-wrapped `$ref` — how OpenAPI 3.0 spells a nullable reference — to a bare `{"type": "OBJECT"}`. The referenced properties and description are gone before this package sees them, and the field renders as an empty `dict[str, Any]`.
|
|
403
|
-
>
|
|
404
|
-
> ```python
|
|
405
|
-
> from google.adk.features import FeatureName, override_feature_enabled
|
|
406
|
-
>
|
|
407
|
-
> override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, True)
|
|
408
|
-
> ```
|
|
409
|
-
>
|
|
410
|
-
> Or set `ADK_ENABLE_JSON_SCHEMA_FOR_FUNC_DECL=1` in the environment. Do this before building any tool. It changes how declarations are built for every tool in the process, so if the same process also exposes tools to the model directly, check the model layer accepts `parameters_json_schema` — ADK's LiteLLM integration does, and `ExecuteCodeTool` declares itself that way regardless.
|
|
411
|
-
|
|
412
388
|
#### Fitting the budget
|
|
413
389
|
|
|
414
390
|
A block over `max_code_mode_metadata_chars` degrades in tiers instead of vanishing. The Python version and package list survive every tier — they're small, and the model cannot discover them any other way.
|
|
@@ -58,13 +58,17 @@ Build an `ExecuteCodeTool`, give it the tools the sandboxed code may call, and a
|
|
|
58
58
|
|
|
59
59
|
```python
|
|
60
60
|
from google.adk.agents import LlmAgent
|
|
61
|
+
from google.adk.features import FeatureName, override_feature_enabled
|
|
61
62
|
from adk_code_mode import ExecuteCodeTool, RemoteBackend
|
|
62
63
|
|
|
64
|
+
# Required for docstrings to show full tool schemas
|
|
65
|
+
override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, True)
|
|
66
|
+
|
|
63
67
|
tool = ExecuteCodeTool(
|
|
64
68
|
tools=[my_fn_tool, McpToolset(...), OpenAPIToolset(...)],
|
|
65
69
|
backend=RemoteBackend(
|
|
66
|
-
url="https://sandbox-xyz.run.app",
|
|
67
|
-
token="your-secret-token",
|
|
70
|
+
url="https://sandbox-xyz.run.app",
|
|
71
|
+
token="your-secret-token",
|
|
68
72
|
),
|
|
69
73
|
)
|
|
70
74
|
|
|
@@ -85,8 +89,12 @@ root_agent = LlmAgent(
|
|
|
85
89
|
> **`UnsafeLocalDockerBackend` is not safe for production or multi-tenant use.** See [Safety](#-safety).
|
|
86
90
|
|
|
87
91
|
```python
|
|
92
|
+
from google.adk.features import FeatureName, override_feature_enabled
|
|
88
93
|
from adk_code_mode import ExecuteCodeTool, UnsafeLocalDockerBackend
|
|
89
94
|
|
|
95
|
+
# Required for docstrings to show full tool schemas
|
|
96
|
+
override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, True)
|
|
97
|
+
|
|
90
98
|
tool = ExecuteCodeTool(
|
|
91
99
|
tools=[my_fn_tool, McpToolset(...), OpenAPIToolset(...)],
|
|
92
100
|
backend=UnsafeLocalDockerBackend(image="ghcr.io/a2anet/adk-code-mode:latest"),
|
|
@@ -350,38 +358,6 @@ Tags are indented; their content is not. The `<tools-package>` body is Python so
|
|
|
350
358
|
|
|
351
359
|
With `save_tool_results_as_artifacts` enabled (the default), each non-artifact tool above — e.g. `list_channels` and `send_message` — also carries two optional `artifact_name: str | None = ...` / `artifact_description: str | None = ...` parameters for naming its saved result.
|
|
352
360
|
|
|
353
|
-
#### Schema fidelity
|
|
354
|
-
|
|
355
|
-
Stubs are rendered from each tool's declaration, so whatever the declaration drops can never reach the model. Nested object properties, their descriptions and JSON Schema's validation keywords all land in the docstring, because no Python type expresses them:
|
|
356
|
-
|
|
357
|
-
~~~python
|
|
358
|
-
def create_minimum_spend_rule(*, space_id: int, conditions: dict[str, Any] | None = ..., label: str | None = ...) -> Any:
|
|
359
|
-
"""Creates a new minimum spend rule under the MinimumSpend policy.
|
|
360
|
-
|
|
361
|
-
Args:
|
|
362
|
-
space_id: The unique identifier of the venue space. (format=int32)
|
|
363
|
-
conditions: Conditions that must all be satisfied for the rule to apply (AND logic).
|
|
364
|
-
daysOfWeek: list[int] | None - Days of the week (1=Monday, 7=Sunday). (items: minimum=1, maximum=7)
|
|
365
|
-
timeRange: dict[str, Any] | None - Time window of the booking start time.
|
|
366
|
-
from: str (pattern=^([01][0-9]|2[0-3]):[0-5][0-9]$, example=18:00)
|
|
367
|
-
to: str (example=23:59)
|
|
368
|
-
label: (maxLength=255)
|
|
369
|
-
"""
|
|
370
|
-
~~~
|
|
371
|
-
|
|
372
|
-
Every JSON Schema 2020-12 Validation and Meta-Data keyword is covered; `tests/test_stubs_json_schema.py` reads the keyword set from the meta-schemas and fails if one goes unclassified.
|
|
373
|
-
|
|
374
|
-
> [!IMPORTANT]
|
|
375
|
-
> **Enable ADK's `JSON_SCHEMA_FOR_FUNC_DECL` feature to get the full picture.** It is off by default, and without it ADK builds declarations through a Gemini `types.Schema`, which reduces an `allOf`-wrapped `$ref` — how OpenAPI 3.0 spells a nullable reference — to a bare `{"type": "OBJECT"}`. The referenced properties and description are gone before this package sees them, and the field renders as an empty `dict[str, Any]`.
|
|
376
|
-
>
|
|
377
|
-
> ```python
|
|
378
|
-
> from google.adk.features import FeatureName, override_feature_enabled
|
|
379
|
-
>
|
|
380
|
-
> override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, True)
|
|
381
|
-
> ```
|
|
382
|
-
>
|
|
383
|
-
> Or set `ADK_ENABLE_JSON_SCHEMA_FOR_FUNC_DECL=1` in the environment. Do this before building any tool. It changes how declarations are built for every tool in the process, so if the same process also exposes tools to the model directly, check the model layer accepts `parameters_json_schema` — ADK's LiteLLM integration does, and `ExecuteCodeTool` declares itself that way regardless.
|
|
384
|
-
|
|
385
361
|
#### Fitting the budget
|
|
386
362
|
|
|
387
363
|
A block over `max_code_mode_metadata_chars` degrades in tiers instead of vanishing. The Python version and package list survive every tier — they're small, and the model cannot discover them any other way.
|
|
@@ -26,7 +26,7 @@ class ToolSurfaceError(ValueError):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class DuplicateToolNameError(ToolSurfaceError):
|
|
29
|
-
"""Raised when two
|
|
29
|
+
"""Raised when two tools in the same namespace share a raw ADK tool name."""
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class NamespaceCollisionError(ToolSurfaceError):
|
|
@@ -91,23 +91,22 @@ def build(tools: list[ResolvedTool]) -> list[NamespacedTool]:
|
|
|
91
91
|
"""
|
|
92
92
|
out: list[NamespacedTool] = []
|
|
93
93
|
seen_attributes: set[tuple[str | None, str]] = set()
|
|
94
|
-
seen_raw_names: dict[str, NamespacedTool] = {}
|
|
94
|
+
seen_raw_names: dict[tuple[str | None, str], NamespacedTool] = {}
|
|
95
95
|
namespace_sources: dict[str, BaseToolset] = {}
|
|
96
96
|
bare_attributes: dict[str, NamespacedTool] = {}
|
|
97
97
|
for resolved in tools:
|
|
98
98
|
namespace = _toolset_namespace(resolved.toolset) if resolved.toolset else None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
else _sanitise_identifier(resolved.tool.name, fallback="tool")
|
|
105
|
-
)
|
|
99
|
+
# Raw names only have to be unique *within* a namespace: dispatch goes
|
|
100
|
+
# through the dotted path, so two toolsets may each expose ``list_items``.
|
|
101
|
+
raw_key = (namespace, resolved.tool.name)
|
|
102
|
+
if raw_key in seen_raw_names:
|
|
103
|
+
other = seen_raw_names[raw_key]
|
|
106
104
|
raise DuplicateToolNameError(
|
|
107
|
-
f"Duplicate ADK tool name {resolved.tool.name!r}
|
|
108
|
-
f"
|
|
109
|
-
f"{
|
|
110
|
-
"Rename one of the tools or apply distinct
|
|
105
|
+
f"Duplicate ADK tool name {resolved.tool.name!r} within namespace "
|
|
106
|
+
f"{namespace or 'tools'!r} is not supported; this would make host dispatch "
|
|
107
|
+
f"ambiguous for {other.dotted_path!r} (from {_origin(other.resolved)} and "
|
|
108
|
+
f"{_origin(resolved)}). Rename one of the tools or apply distinct "
|
|
109
|
+
"``tool_name_prefix`` values."
|
|
111
110
|
)
|
|
112
111
|
if namespace is not None and resolved.toolset is not None:
|
|
113
112
|
existing_source = namespace_sources.get(namespace)
|
|
@@ -138,7 +137,7 @@ def build(tools: list[ResolvedTool]) -> list[NamespacedTool]:
|
|
|
138
137
|
)
|
|
139
138
|
seen_attributes.add(key)
|
|
140
139
|
nt = NamespacedTool(resolved=resolved, namespace=namespace, attribute=attribute)
|
|
141
|
-
seen_raw_names[
|
|
140
|
+
seen_raw_names[raw_key] = nt
|
|
142
141
|
if namespace is None:
|
|
143
142
|
bare_attributes[attribute] = nt
|
|
144
143
|
out.append(nt)
|
|
@@ -167,15 +166,12 @@ class Registry:
|
|
|
167
166
|
|
|
168
167
|
def __init__(self, tools: list[NamespacedTool]) -> None:
|
|
169
168
|
self._by_path: dict[str, NamespacedTool] = {t.dotted_path: t for t in tools}
|
|
170
|
-
|
|
169
|
+
# Generated stubs call ``_call(<dotted path>, ...)``, so this map is only a
|
|
170
|
+
# fallback for callers that know the raw ADK name. A name shared by two
|
|
171
|
+
# namespaces is ambiguous and simply doesn't resolve through it.
|
|
172
|
+
by_tool_name: dict[str, NamespacedTool | None] = {}
|
|
171
173
|
for tool in tools:
|
|
172
|
-
|
|
173
|
-
if existing is not None:
|
|
174
|
-
raise DuplicateToolNameError(
|
|
175
|
-
"Duplicate ADK tool name "
|
|
176
|
-
f"{tool.tool_name!r} is not supported; use unique tool names."
|
|
177
|
-
)
|
|
178
|
-
by_tool_name[tool.tool_name] = tool
|
|
174
|
+
by_tool_name[tool.tool_name] = None if tool.tool_name in by_tool_name else tool
|
|
179
175
|
self._by_tool_name = by_tool_name
|
|
180
176
|
self._tools = list(tools)
|
|
181
177
|
|
|
@@ -187,10 +183,11 @@ class Registry:
|
|
|
187
183
|
return self._by_path.get(path)
|
|
188
184
|
|
|
189
185
|
def by_tool_name(self, tool_name: str) -> NamespacedTool | None:
|
|
186
|
+
"""Resolve a raw ADK name, or ``None`` if unknown or shared by two namespaces."""
|
|
190
187
|
return self._by_tool_name.get(tool_name)
|
|
191
188
|
|
|
192
189
|
def resolve_call(self, name: str) -> NamespacedTool:
|
|
193
|
-
"""Accept either the dotted path or
|
|
190
|
+
"""Accept either the dotted path or an unambiguous original ``BaseTool.name``."""
|
|
194
191
|
hit = self._by_path.get(name) or self._by_tool_name.get(name)
|
|
195
192
|
if hit is None:
|
|
196
193
|
raise KeyError(f"no tool registered for {name!r}")
|
|
@@ -83,6 +83,18 @@ def test_render_catalog_includes_top_level_tools_section() -> None:
|
|
|
83
83
|
assert "def echo(*, message: str) -> Any:" in catalog
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def test_render_catalog_keeps_a_shared_tool_name_in_both_namespaces() -> None:
|
|
87
|
+
bookings = _FakeToolset("bookings")
|
|
88
|
+
inventory = _FakeToolset("inventory")
|
|
89
|
+
a = _SchemaTool("list_items", description="List bookings.", schema={"type": "object"})
|
|
90
|
+
b = _SchemaTool("list_items", description="List inventory.", schema={"type": "object"})
|
|
91
|
+
catalog = render_catalog(_build([(a, bookings), (b, inventory)]))
|
|
92
|
+
assert "from tools.bookings import list_items" in catalog
|
|
93
|
+
assert "from tools.inventory import list_items" in catalog
|
|
94
|
+
assert '"""List bookings.' in catalog
|
|
95
|
+
assert '"""List inventory.' in catalog
|
|
96
|
+
|
|
97
|
+
|
|
86
98
|
def test_render_catalog_groups_tools_by_namespace_and_sorts() -> None:
|
|
87
99
|
slack = _FakeToolset("slack")
|
|
88
100
|
gmail = _FakeToolset("gmail")
|
|
@@ -54,15 +54,46 @@ def test_toolset_tools_share_namespace() -> None:
|
|
|
54
54
|
assert ns[1].dotted_path == "slack.list_channels"
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def
|
|
57
|
+
def test_duplicate_raw_tool_names_in_one_namespace_raise() -> None:
|
|
58
|
+
slack = _NamedToolset("Slack")
|
|
59
|
+
tools = [
|
|
60
|
+
ResolvedTool(tool=_Tool("ping"), toolset=slack),
|
|
61
|
+
ResolvedTool(tool=_Tool("ping"), toolset=slack),
|
|
62
|
+
]
|
|
63
|
+
with pytest.raises(namespacing.DuplicateToolNameError):
|
|
64
|
+
namespacing.build(tools)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_duplicate_bare_tool_names_raise() -> None:
|
|
58
68
|
tools = [
|
|
59
69
|
ResolvedTool(tool=_Tool("ping"), toolset=None),
|
|
60
|
-
ResolvedTool(tool=_Tool("ping"), toolset=
|
|
70
|
+
ResolvedTool(tool=_Tool("ping"), toolset=None),
|
|
61
71
|
]
|
|
62
72
|
with pytest.raises(namespacing.DuplicateToolNameError):
|
|
63
73
|
namespacing.build(tools)
|
|
64
74
|
|
|
65
75
|
|
|
76
|
+
def test_same_tool_name_in_two_namespaces_is_allowed() -> None:
|
|
77
|
+
tools = [
|
|
78
|
+
ResolvedTool(tool=_Tool("list_items"), toolset=_NamedToolset("Bookings")),
|
|
79
|
+
ResolvedTool(tool=_Tool("list_items"), toolset=_NamedToolset("Inventory")),
|
|
80
|
+
]
|
|
81
|
+
ns = namespacing.build(tools)
|
|
82
|
+
assert [t.dotted_path for t in ns] == ["bookings.list_items", "inventory.list_items"]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_bare_tool_may_share_a_name_with_a_namespaced_tool() -> None:
|
|
86
|
+
tools = [
|
|
87
|
+
ResolvedTool(tool=_Tool("ping"), toolset=None),
|
|
88
|
+
ResolvedTool(tool=_Tool("ping"), toolset=_NamedToolset("Slack")),
|
|
89
|
+
]
|
|
90
|
+
ns = namespacing.build(tools)
|
|
91
|
+
assert [t.dotted_path for t in ns] == ["ping", "slack.ping"]
|
|
92
|
+
reg = namespacing.Registry(ns)
|
|
93
|
+
assert reg.resolve_call("ping") is ns[0]
|
|
94
|
+
assert reg.resolve_call("slack.ping") is ns[1]
|
|
95
|
+
|
|
96
|
+
|
|
66
97
|
def test_python_identifier_collisions_raise() -> None:
|
|
67
98
|
tools = [
|
|
68
99
|
ResolvedTool(tool=_Tool("ping"), toolset=None),
|
|
@@ -98,3 +129,18 @@ def test_registry_bidirectional_lookup() -> None:
|
|
|
98
129
|
assert reg.resolve_call("send_message") is ns[0]
|
|
99
130
|
with pytest.raises(KeyError):
|
|
100
131
|
reg.resolve_call("nope")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_registry_dotted_paths_still_resolve_when_a_name_is_shared() -> None:
|
|
135
|
+
ns = namespacing.build(
|
|
136
|
+
[
|
|
137
|
+
ResolvedTool(tool=_Tool("list_items"), toolset=_NamedToolset("Bookings")),
|
|
138
|
+
ResolvedTool(tool=_Tool("list_items"), toolset=_NamedToolset("Inventory")),
|
|
139
|
+
]
|
|
140
|
+
)
|
|
141
|
+
reg = namespacing.Registry(ns)
|
|
142
|
+
assert reg.resolve_call("bookings.list_items") is ns[0]
|
|
143
|
+
assert reg.resolve_call("inventory.list_items") is ns[1]
|
|
144
|
+
assert reg.by_tool_name("list_items") is None
|
|
145
|
+
with pytest.raises(KeyError):
|
|
146
|
+
reg.resolve_call("list_items")
|
|
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.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/__init__.py
RENAMED
|
File without changes
|
{adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/__main__.py
RENAMED
|
File without changes
|
{adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/_entry.py
RENAMED
|
File without changes
|
{adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/_http_server.py
RENAMED
|
File without changes
|
{adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/sandbox-wheel/src/adk_code_mode_sandbox/_rpc_client.py
RENAMED
|
File without changes
|
{adk_code_mode-1.4.0 → adk_code_mode-1.5.0}/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
|
|
File without changes
|
|
File without changes
|