ory-adk 0.13.9__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.
@@ -0,0 +1,36 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+ .env
5
+ .env.local
6
+
7
+ # Python (uv workspace under python/)
8
+ .venv/
9
+ __pycache__/
10
+ *.egg-info/
11
+ .pytest_cache/
12
+ .ruff_cache/
13
+ build/
14
+ *.pyc
15
+
16
+ # Debug logs
17
+ *.log
18
+
19
+ # Harness sandbox dirs
20
+ .sandbox/
21
+
22
+ # Staged install-surface repo contents (see scripts/sync-install-surfaces.mjs)
23
+ .install-surfaces/
24
+
25
+ # Local dev environment (local Ory stack + Verdaccio registry)
26
+ .ory-dev/
27
+
28
+ # Worktrees for changes
29
+ .worktrees/
30
+ .claude/worktrees/
31
+
32
+ # Gemini CLI extension assets — materialized at install time from
33
+ # @ory/argus templates. The repo source is the canonical templates;
34
+ # these subdirs are generated wherever the install runs.
35
+ packages/gemini-cli/gemini-extension/skills/
36
+ packages/gemini-cli/gemini-extension/commands/
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: ory-adk
3
+ Version: 0.13.9
4
+ Summary: Ory Agent Security for the Google Agent Development Kit (ADK) — per-tool authorization, tracing, and identity propagation via before/after tool callbacks. Built on ory-argus.
5
+ Author: Ory
6
+ License-Expression: Apache-2.0
7
+ Keywords: adk,agent,agent-development-kit,ai,authorization,google,ory
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: google-adk>=1.0
10
+ Requires-Dist: ory-argus<1,>=0.8
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8; extra == 'dev'
13
+ Requires-Dist: ruff>=0.6; extra == 'dev'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # ory-adk
17
+
18
+ Ory Agent Security for the [Google Agent Development Kit (ADK)](https://google.github.io/adk-docs/).
19
+
20
+ Authorizes every tool call against Ory Permissions via ADK's before/after tool callbacks,
21
+ traces invocations, and propagates user → agent → sub-agent identity — built on
22
+ [`ory-argus`](https://pypi.org/project/ory-argus/).
23
+
24
+ ```bash
25
+ pip install ory-adk
26
+ ```
27
+
28
+ ```python
29
+ from google.adk.agents import Agent
30
+ from ory_adk import ory_before_tool_callback, ory_after_tool_callback
31
+
32
+ agent = Agent(
33
+ name="assistant",
34
+ model="gemini-2.5-flash",
35
+ tools=[search, send_email],
36
+ before_tool_callback=ory_before_tool_callback(), # gate every tool call
37
+ after_tool_callback=ory_after_tool_callback(), # trace completions
38
+ )
39
+ ```
40
+
41
+ In **enforce** mode (`ORY_PERMISSION_MODE=enforce`) a denied tool is skipped — ADK returns
42
+ the denial to the model and the tool never runs. In **observe** mode (default) the tool runs
43
+ and a `permission.observe_deny` span is recorded. When the model delegates to a sub-agent
44
+ (`transfer_to_agent`), a sub-agent identity + agent→subagent delegation are recorded.
45
+
46
+ Credentials come from the shared `~/.config/ory-agent-plugins/config.json`, so a login from
47
+ any Ory harness or `ory-argus login` is reused automatically. See
48
+ [docs/sdk-integrations.md](https://github.com/ory-corp/ory-agent-plugins/blob/main/docs/sdk-integrations.md).
@@ -0,0 +1,33 @@
1
+ # ory-adk
2
+
3
+ Ory Agent Security for the [Google Agent Development Kit (ADK)](https://google.github.io/adk-docs/).
4
+
5
+ Authorizes every tool call against Ory Permissions via ADK's before/after tool callbacks,
6
+ traces invocations, and propagates user → agent → sub-agent identity — built on
7
+ [`ory-argus`](https://pypi.org/project/ory-argus/).
8
+
9
+ ```bash
10
+ pip install ory-adk
11
+ ```
12
+
13
+ ```python
14
+ from google.adk.agents import Agent
15
+ from ory_adk import ory_before_tool_callback, ory_after_tool_callback
16
+
17
+ agent = Agent(
18
+ name="assistant",
19
+ model="gemini-2.5-flash",
20
+ tools=[search, send_email],
21
+ before_tool_callback=ory_before_tool_callback(), # gate every tool call
22
+ after_tool_callback=ory_after_tool_callback(), # trace completions
23
+ )
24
+ ```
25
+
26
+ In **enforce** mode (`ORY_PERMISSION_MODE=enforce`) a denied tool is skipped — ADK returns
27
+ the denial to the model and the tool never runs. In **observe** mode (default) the tool runs
28
+ and a `permission.observe_deny` span is recorded. When the model delegates to a sub-agent
29
+ (`transfer_to_agent`), a sub-agent identity + agent→subagent delegation are recorded.
30
+
31
+ Credentials come from the shared `~/.config/ory-agent-plugins/config.json`, so a login from
32
+ any Ory harness or `ory-argus login` is reused automatically. See
33
+ [docs/sdk-integrations.md](https://github.com/ory-corp/ory-agent-plugins/blob/main/docs/sdk-integrations.md).
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ory-adk"
7
+ version = "0.13.9"
8
+ description = "Ory Agent Security for the Google Agent Development Kit (ADK) — per-tool authorization, tracing, and identity propagation via before/after tool callbacks. Built on ory-argus."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "Ory" }]
13
+ keywords = ["ory", "google", "adk", "agent-development-kit", "authorization", "agent", "ai"]
14
+ dependencies = [
15
+ "ory-argus>=0.8,<1",
16
+ "google-adk>=1.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = ["pytest>=8", "ruff>=0.6"]
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/ory_adk"]
@@ -0,0 +1,19 @@
1
+ """Ory Agent Security for the Google Agent Development Kit (ADK).
2
+
3
+ from google.adk.agents import Agent
4
+ from ory_adk import ory_before_tool_callback, ory_after_tool_callback
5
+
6
+ agent = Agent(
7
+ name="assistant", model="gemini-2.5-flash", tools=[...],
8
+ before_tool_callback=ory_before_tool_callback(),
9
+ after_tool_callback=ory_after_tool_callback(),
10
+ )
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from .callbacks import ory_after_tool_callback, ory_before_tool_callback
16
+
17
+ __version__ = "0.13.9"
18
+
19
+ __all__ = ["ory_before_tool_callback", "ory_after_tool_callback", "__version__"]
@@ -0,0 +1,83 @@
1
+ """Ory Agent Security for the Google Agent Development Kit (ADK).
2
+
3
+ ADK exposes a first-class per-tool gate: ``before_tool_callback`` runs before every tool
4
+ call and **skips the tool when it returns a dict** (that dict becomes the tool response).
5
+ ``ory_before_tool_callback()`` authorizes each call against Ory Permissions:
6
+
7
+ - allow / observe / fail-open / interactive → return ``None`` (the tool runs).
8
+ - deny (enforce) → return ``{"error": "Ory: permission denied …"}`` so ADK skips the tool
9
+ and hands the model the denial.
10
+
11
+ ``ory_after_tool_callback()`` records the ``tool.complete`` span. Register both as
12
+ constructor kwargs:
13
+
14
+ from google.adk.agents import Agent
15
+ from ory_adk import ory_before_tool_callback, ory_after_tool_callback
16
+
17
+ agent = Agent(
18
+ name="assistant", model="gemini-2.5-flash", tools=[...],
19
+ before_tool_callback=ory_before_tool_callback(),
20
+ after_tool_callback=ory_after_tool_callback(),
21
+ )
22
+
23
+ All gate logic lives in ``ory_argus``; the callbacks only read ``tool.name`` + the ``args``
24
+ dict and return plain values, so this module imports without ``google-adk`` present.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from typing import Any
30
+
31
+ from ory_argus import OryAgentClient, complete, gate, register_subagent, session_start
32
+
33
+ HARNESS = "google-adk"
34
+ # ADK emits this as a tool call when the model delegates to a sub-agent.
35
+ _TRANSFER_TOOL = "transfer_to_agent"
36
+
37
+
38
+ def ory_before_tool_callback(
39
+ *, client: OryAgentClient | None = None, can_block: bool = True, project_url: str | None = None
40
+ ):
41
+ """Build an ADK ``before_tool_callback`` that gates each tool through Ory."""
42
+ c = client or OryAgentClient.from_env(HARNESS)
43
+ state: dict = {}
44
+
45
+ def before_tool_callback(tool: Any, args: dict, tool_context: Any) -> dict | None:
46
+ if not state.get("started"):
47
+ state["started"] = True
48
+ try:
49
+ session_start(c, harness=HARNESS, project_url=project_url)
50
+ except Exception as err: # noqa: BLE001 — session gate must never break the agent
51
+ c.logger.warn("session_start.failed", {"message": str(err)})
52
+
53
+ tool_name = getattr(tool, "name", None) or "unknown"
54
+
55
+ # Sub-agent delegation: ADK signals a handoff via a `transfer_to_agent` tool call.
56
+ if tool_name == _TRANSFER_TOOL:
57
+ target = (args or {}).get("agent_name")
58
+ if target:
59
+ try:
60
+ register_subagent(c, harness=HARNESS, sub_agent_type=target, project_url=project_url)
61
+ except Exception as err: # noqa: BLE001
62
+ c.logger.warn("subagent.register.failed", {"target": target, "message": str(err)})
63
+
64
+ result = gate(c, harness=HARNESS, tool_name=tool_name, tool_args=args, can_block=can_block)
65
+ if result.blocked:
66
+ return {"error": result.denial_message or "Ory: permission denied"}
67
+ return None # proceed (or let the next callback run)
68
+
69
+ return before_tool_callback
70
+
71
+
72
+ def ory_after_tool_callback(*, client: OryAgentClient | None = None):
73
+ """Build an ADK ``after_tool_callback`` that records a ``tool.complete`` span."""
74
+ c = client or OryAgentClient.from_env(HARNESS)
75
+
76
+ def after_tool_callback(tool: Any, args: dict, tool_context: Any, tool_response: Any) -> dict | None:
77
+ complete(c, tool_name=getattr(tool, "name", "unknown"), output=tool_response)
78
+ return None # keep the tool's response unchanged
79
+
80
+ return after_tool_callback
81
+
82
+
83
+ __all__ = ["HARNESS", "ory_before_tool_callback", "ory_after_tool_callback"]
@@ -0,0 +1,72 @@
1
+ """Hermetic tests for the Google ADK before/after tool callbacks (mocked Ory).
2
+
3
+ Covers the native veto shape (a denial dict returned from the before-callback, which ADK
4
+ substitutes for the tool result) and request-shape extraction (``tool.name``), plus the
5
+ after-callback ``tool.complete`` surface. The full permission decision matrix is core-owned
6
+ (``ory-argus/tests/test_adapters.py``).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from types import SimpleNamespace
12
+
13
+ import pytest
14
+
15
+ from ory_adk import callbacks as cb_mod # type: ignore
16
+ from ory_adk import ory_after_tool_callback, ory_before_tool_callback # type: ignore
17
+ from ory_argus.client import PrincipalIdentity
18
+ from ory_argus.testing import (
19
+ create_mock_client,
20
+ get_trace_spans,
21
+ stub_permission_allowed,
22
+ stub_permission_denied,
23
+ )
24
+
25
+
26
+ @pytest.fixture(autouse=True)
27
+ def _isolate(monkeypatch, tmp_path):
28
+ monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
29
+ monkeypatch.delenv("ORY_PERMISSION_MODE", raising=False)
30
+ # Avoid the session gate's network calls in unit tests.
31
+ monkeypatch.setattr(cb_mod, "session_start", lambda *a, **k: None)
32
+
33
+
34
+ def client_with_user():
35
+ c = create_mock_client(harness="google-adk")
36
+ c.set_user_principal(PrincipalIdentity(subject="user:alice"))
37
+ return c
38
+
39
+
40
+ def _tool(name="search"):
41
+ return SimpleNamespace(name=name, description="d")
42
+
43
+
44
+ def test_allow_returns_none():
45
+ c = client_with_user()
46
+ stub_permission_allowed(c)
47
+ cb = ory_before_tool_callback(client=c)
48
+ # Returning None tells ADK to run the tool normally.
49
+ assert cb(_tool("search"), {"q": 1}, None) is None
50
+ invoke = get_trace_spans(c, "tool.invoke")[0]
51
+ assert invoke.attributes["allowed"] is True
52
+ # Tool name pulled from the ADK tool object's ``name``.
53
+ assert invoke.attributes["toolName"] == "search"
54
+
55
+
56
+ def test_enforce_returns_denial_dict(monkeypatch):
57
+ monkeypatch.setenv("ORY_PERMISSION_MODE", "enforce")
58
+ c = client_with_user()
59
+ stub_permission_denied(c)
60
+ cb = ory_before_tool_callback(client=c)
61
+ out = cb(_tool("rm"), {}, None)
62
+ # Native veto shape: a non-None dict is substituted for the tool result by ADK,
63
+ # so the real tool never executes.
64
+ assert isinstance(out, dict) and "permission denied" in out["error"].lower()
65
+ assert get_trace_spans(c, "tool.block")[0].attributes["blocked"] is True
66
+
67
+
68
+ def test_after_callback_records_complete():
69
+ c = client_with_user()
70
+ after = ory_after_tool_callback(client=c)
71
+ assert after(_tool("search"), {}, None, {"result": "ok"}) is None
72
+ assert get_trace_spans(c, "tool.complete")[0].attributes["toolName"] == "search"
@@ -0,0 +1,55 @@
1
+ """Real-SDK test: attach the Ory callbacks to a real ADK ``Agent`` and gate.
2
+
3
+ Runs only when ``google.adk`` is importable; the default workspace venv skips it. Validates
4
+ that the real ``Agent`` constructor accepts our ``before_tool_callback`` / ``after_tool_callback``
5
+ (the registration shape) and that the callback denies a real ``BaseTool`` in enforce mode.
6
+ The permission decision is stubbed (no backend).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import pytest
12
+
13
+ pytest.importorskip("google.adk")
14
+
15
+ from google.adk.agents import Agent # noqa: E402
16
+
17
+ from ory_adk import callbacks as cb_mod # noqa: E402
18
+ from ory_adk import ory_after_tool_callback, ory_before_tool_callback # noqa: E402
19
+ from ory_argus.adapters import GateResult # noqa: E402
20
+ from ory_argus.permissions import PermissionDecision # noqa: E402
21
+ from ory_argus.testing import create_mock_client # noqa: E402
22
+
23
+
24
+ def test_callbacks_attach_to_real_agent():
25
+ agent = Agent(
26
+ name="ory_e2e_assistant",
27
+ model="gemini-2.5-flash",
28
+ before_tool_callback=ory_before_tool_callback(client=create_mock_client(harness="google-adk")),
29
+ after_tool_callback=ory_after_tool_callback(client=create_mock_client(harness="google-adk")),
30
+ )
31
+ assert agent.before_tool_callback is not None
32
+ assert agent.after_tool_callback is not None
33
+
34
+
35
+ def test_before_callback_denies_real_base_tool(monkeypatch):
36
+ monkeypatch.setattr(cb_mod, "session_start", lambda *a, **k: None)
37
+ monkeypatch.setattr(
38
+ cb_mod, "gate",
39
+ lambda client, **kw: GateResult(
40
+ proceed=False, blocked=True, decision=PermissionDecision(kind="deny"),
41
+ subject="User:t", namespace="AgentTools", denial_message="Ory: permission denied",
42
+ ),
43
+ )
44
+
45
+ # A real ADK FunctionTool wrapping a plain function.
46
+ from google.adk.tools import FunctionTool
47
+
48
+ def my_real_tool(query: str) -> str:
49
+ """A real tool."""
50
+ return query
51
+
52
+ tool = FunctionTool(func=my_real_tool)
53
+ cb = ory_before_tool_callback(client=create_mock_client(harness="google-adk"))
54
+ out = cb(tool, {"query": "x"}, None)
55
+ assert isinstance(out, dict) and "permission denied" in out["error"].lower()