adk-code-mode 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present A2A Net <hello@a2anet.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ __version__ = "0.1.0" # x-release-please-version
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: 2025-present A2A Net <hello@a2anet.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from adk_code_mode.__about__ import __version__
5
+ from adk_code_mode.callback import code_mode_before_model_callback
6
+ from adk_code_mode.executor import (
7
+ CODE_MODE_SYSTEM_INSTRUCTION,
8
+ ArtifactsSavedCallback,
9
+ CodeModeCodeExecutor,
10
+ )
11
+ from adk_code_mode.runtime import DockerRuntime, SandboxHandle, SandboxRuntime
12
+
13
+ __all__ = [
14
+ "ArtifactsSavedCallback",
15
+ "CODE_MODE_SYSTEM_INSTRUCTION",
16
+ "CodeModeCodeExecutor",
17
+ "DockerRuntime",
18
+ "SandboxHandle",
19
+ "SandboxRuntime",
20
+ "__version__",
21
+ "code_mode_before_model_callback",
22
+ ]
@@ -0,0 +1,152 @@
1
+ # SPDX-FileCopyrightText: 2025-present A2A Net <hello@a2anet.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ """Built-in ``save_artifact`` / ``load_artifact`` / ``list_artifacts`` tools.
5
+
6
+ These are regular ``FunctionTool`` instances injected at the front of
7
+ ``CodeModeCodeExecutor.tools`` (set ``include_artifact_tools=False`` to opt out).
8
+ They appear in the rendered catalog as top-level tools; the model writes
9
+ ``from tools import save_artifact, load_artifact, list_artifacts``.
10
+
11
+ The wire format is JSON. Binary content is base64 over the wire — the model
12
+ encodes it before calling ``save_artifact`` and decodes it after
13
+ ``load_artifact``. Loaded artifacts return a flat ``{"kind", "data",
14
+ "mime_type"}`` dict so the model dispatches on ``kind`` without importing
15
+ host-side types.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import base64
21
+ from typing import Any
22
+
23
+ from google.adk.tools.function_tool import FunctionTool
24
+ from google.adk.tools.tool_context import ToolContext
25
+ from google.genai import types as genai_types
26
+
27
+ _TEXT_MIME_PREFIXES = ("text/",)
28
+ _TEXT_MIME_TYPES = {"application/json", "application/xml", "application/javascript"}
29
+
30
+
31
+ def _is_text_mime(mime_type: str | None) -> bool:
32
+ if not mime_type:
33
+ return False
34
+ if mime_type.startswith(_TEXT_MIME_PREFIXES):
35
+ return True
36
+ if mime_type in _TEXT_MIME_TYPES:
37
+ return True
38
+ return mime_type.endswith("+json") or mime_type.endswith("+xml")
39
+
40
+
41
+ async def save_artifact(
42
+ *,
43
+ filename: str,
44
+ content: str,
45
+ mime_type: str | None = None,
46
+ tool_context: ToolContext,
47
+ ) -> int:
48
+ """Save an artifact to the session. Returns the new version number.
49
+
50
+ For text or JSON, pass ``content`` as a string and set ``mime_type``:
51
+
52
+ save_artifact(
53
+ filename="report.json",
54
+ content=json.dumps({"status": "ok"}),
55
+ mime_type="application/json",
56
+ )
57
+
58
+ For binary, base64-encode the bytes and set the binary mime type:
59
+
60
+ save_artifact(
61
+ filename="image.png",
62
+ content=base64.b64encode(image_bytes).decode("ascii"),
63
+ mime_type="image/png",
64
+ )
65
+ """
66
+ if _is_text_mime(mime_type):
67
+ part = genai_types.Part(
68
+ inline_data=genai_types.Blob(
69
+ data=content.encode("utf-8"),
70
+ mime_type=mime_type or "text/plain",
71
+ )
72
+ )
73
+ else:
74
+ part = genai_types.Part(
75
+ inline_data=genai_types.Blob(
76
+ data=base64.b64decode(content),
77
+ mime_type=mime_type or "application/octet-stream",
78
+ )
79
+ )
80
+ return await tool_context.save_artifact(filename, part)
81
+
82
+
83
+ async def load_artifact(
84
+ *,
85
+ filename: str,
86
+ version: int | None = None,
87
+ tool_context: ToolContext,
88
+ ) -> dict[str, Any] | None:
89
+ """Load an artifact by filename. Returns ``None`` if not found.
90
+
91
+ Returned dict shape:
92
+
93
+ {
94
+ "kind": "text" | "bytes",
95
+ "data": <str>, # text: the string; bytes: base64-encoded
96
+ "mime_type": <str | None>,
97
+ }
98
+
99
+ Example:
100
+
101
+ result = load_artifact(filename="report.json")
102
+ if result is None:
103
+ return
104
+ if result["kind"] == "text" and result["mime_type"] == "application/json":
105
+ payload = json.loads(result["data"])
106
+ elif result["kind"] == "bytes":
107
+ blob = base64.b64decode(result["data"])
108
+ """
109
+ part = await tool_context.load_artifact(filename, version=version)
110
+ if part is None:
111
+ return None
112
+ inline = part.inline_data
113
+ if inline is not None:
114
+ mime_type = inline.mime_type
115
+ data = inline.data or b""
116
+ if isinstance(data, str):
117
+ data = data.encode("utf-8")
118
+ if _is_text_mime(mime_type):
119
+ return {
120
+ "kind": "text",
121
+ "data": data.decode("utf-8"),
122
+ "mime_type": mime_type,
123
+ }
124
+ return {
125
+ "kind": "bytes",
126
+ "data": base64.b64encode(data).decode("ascii"),
127
+ "mime_type": mime_type,
128
+ }
129
+ if part.text is not None:
130
+ return {"kind": "text", "data": part.text, "mime_type": None}
131
+ return {"kind": "text", "data": "", "mime_type": None}
132
+
133
+
134
+ async def list_artifacts(*, tool_context: ToolContext) -> list[str]:
135
+ """Return the filenames of artifacts visible to this session.
136
+
137
+ Example:
138
+
139
+ for filename in list_artifacts():
140
+ print(filename)
141
+ """
142
+ return await tool_context.list_artifacts()
143
+
144
+
145
+ ARTIFACT_TOOLS: tuple[FunctionTool, ...] = (
146
+ FunctionTool(save_artifact),
147
+ FunctionTool(load_artifact),
148
+ FunctionTool(list_artifacts),
149
+ )
150
+
151
+
152
+ __all__ = ["ARTIFACT_TOOLS", "list_artifacts", "load_artifact", "save_artifact"]
@@ -0,0 +1,81 @@
1
+ # SPDX-FileCopyrightText: 2025-present A2A Net <hello@a2anet.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ """``code_mode_before_model_callback``: inject the tool catalog into the prompt.
5
+
6
+ Wire the result of :func:`code_mode_before_model_callback` as
7
+ ``before_model_callback`` on an ``LlmAgent``. On every model turn, the
8
+ callback:
9
+
10
+ 1. Resolves the tools (including any ``BaseToolset`` instances) for the
11
+ current invocation, using the live ``CallbackContext``.
12
+ 2. Renders the catalog — signatures + docstrings for every tool —
13
+ falling back to a short overflow message when the rendered catalog
14
+ would exceed ``max_catalog_chars``.
15
+ 3. Appends ``\\n\\n<tools>\\n…\\n</tools>`` to
16
+ ``llm_request.config.system_instruction``.
17
+ 4. Caches the resolved tools on the executor so the follow-up
18
+ ``execute_code`` call reuses them instead of re-resolving toolsets.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable
25
+
26
+ from adk_code_mode.tools import namespacing, normaliser
27
+ from adk_code_mode.tools.catalog import render_catalog, render_overflow_catalog
28
+
29
+ if TYPE_CHECKING:
30
+ from adk_code_mode.executor import CodeModeCodeExecutor
31
+
32
+ logger = logging.getLogger("adk_code_mode.callback")
33
+
34
+ _TOOLS_OPEN = "<tools>"
35
+ _TOOLS_CLOSE = "</tools>"
36
+
37
+
38
+ def code_mode_before_model_callback(
39
+ executor: "CodeModeCodeExecutor",
40
+ ) -> Callable[..., Awaitable[None]]:
41
+ """Build a ``before_model_callback`` bound to ``executor``."""
42
+
43
+ async def _callback(
44
+ callback_context: Any,
45
+ llm_request: Any,
46
+ **_unused: Any,
47
+ ) -> None:
48
+ resolved = await normaliser.resolve(list(executor.tools), callback_context)
49
+
50
+ executor._record_resolved_tools(callback_context.invocation_id, resolved)
51
+
52
+ ns_tools = namespacing.build(resolved)
53
+ catalog = render_catalog(ns_tools)
54
+ if len(catalog) > executor.max_catalog_chars:
55
+ catalog = render_overflow_catalog()
56
+ block = f"{_TOOLS_OPEN}\n{catalog}\n{_TOOLS_CLOSE}"
57
+
58
+ _append_system_instruction(llm_request.config, block)
59
+
60
+ return _callback
61
+
62
+
63
+ def _append_system_instruction(config: Any, block: str) -> None:
64
+ """Append ``block`` to ``config.system_instruction`` in place.
65
+
66
+ Handles the two shapes ADK exposes: ``None`` or ``str``, and a list of
67
+ items (typically strings or ``Part`` objects). The new block is always
68
+ appended as a final string; non-string items in a list are preserved
69
+ as-is.
70
+ """
71
+ existing = config.system_instruction
72
+ if existing is None:
73
+ config.system_instruction = block
74
+ return
75
+ if isinstance(existing, str):
76
+ config.system_instruction = f"{existing}\n\n{block}"
77
+ return
78
+ config.system_instruction = [*existing, block]
79
+
80
+
81
+ __all__ = ["code_mode_before_model_callback"]