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.
- ory_adk-0.13.9/.gitignore +36 -0
- ory_adk-0.13.9/PKG-INFO +48 -0
- ory_adk-0.13.9/README.md +33 -0
- ory_adk-0.13.9/pyproject.toml +23 -0
- ory_adk-0.13.9/src/ory_adk/__init__.py +19 -0
- ory_adk-0.13.9/src/ory_adk/callbacks.py +83 -0
- ory_adk-0.13.9/tests/test_callbacks.py +72 -0
- ory_adk-0.13.9/tests/test_realsdk.py +55 -0
|
@@ -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/
|
ory_adk-0.13.9/PKG-INFO
ADDED
|
@@ -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).
|
ory_adk-0.13.9/README.md
ADDED
|
@@ -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()
|