ory-agent-framework 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,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: ory-agent-framework
3
+ Version: 0.13.9
4
+ Summary: Ory Agent Security for the Microsoft Agent Framework — per-tool authorization, tracing, and identity propagation via function middleware. Built on ory-argus.
5
+ Author: Ory
6
+ License-Expression: Apache-2.0
7
+ Keywords: agent,agent-framework,ai,authorization,autogen,microsoft,ory
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: agent-framework>=0.0.0a1
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-agent-framework
17
+
18
+ Ory Agent Security for the [Microsoft Agent Framework](https://learn.microsoft.com/agent-framework/)
19
+ (the successor to AutoGen + Semantic Kernel).
20
+
21
+ Gates every function/tool call through Ory Permissions via **function middleware**, traces
22
+ invocations, and propagates the user → agent identity — built on
23
+ [`ory-argus`](https://pypi.org/project/ory-argus/).
24
+
25
+ ```bash
26
+ pip install ory-agent-framework
27
+ ```
28
+
29
+ ```python
30
+ from ory_agent_framework import ory_function_middleware
31
+
32
+ agent = chat_client.create_agent(
33
+ instructions="…",
34
+ tools=[search, send_email],
35
+ middleware=[ory_function_middleware()],
36
+ )
37
+ ```
38
+
39
+ In **enforce** mode (`ORY_PERMISSION_MODE=enforce`) a denied call sets the result to the
40
+ denial and terminates without invoking the function. In **observe** mode (default) the
41
+ function runs and a `permission.observe_deny` span is recorded.
42
+
43
+ Credentials come from the shared `~/.config/ory-agent-plugins/config.json`.
@@ -0,0 +1,28 @@
1
+ # ory-agent-framework
2
+
3
+ Ory Agent Security for the [Microsoft Agent Framework](https://learn.microsoft.com/agent-framework/)
4
+ (the successor to AutoGen + Semantic Kernel).
5
+
6
+ Gates every function/tool call through Ory Permissions via **function middleware**, traces
7
+ invocations, and propagates the user → agent identity — built on
8
+ [`ory-argus`](https://pypi.org/project/ory-argus/).
9
+
10
+ ```bash
11
+ pip install ory-agent-framework
12
+ ```
13
+
14
+ ```python
15
+ from ory_agent_framework import ory_function_middleware
16
+
17
+ agent = chat_client.create_agent(
18
+ instructions="…",
19
+ tools=[search, send_email],
20
+ middleware=[ory_function_middleware()],
21
+ )
22
+ ```
23
+
24
+ In **enforce** mode (`ORY_PERMISSION_MODE=enforce`) a denied call sets the result to the
25
+ denial and terminates without invoking the function. In **observe** mode (default) the
26
+ function runs and a `permission.observe_deny` span is recorded.
27
+
28
+ Credentials come from the shared `~/.config/ory-agent-plugins/config.json`.
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ory-agent-framework"
7
+ version = "0.13.9"
8
+ description = "Ory Agent Security for the Microsoft Agent Framework — per-tool authorization, tracing, and identity propagation via function middleware. 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", "microsoft", "agent-framework", "autogen", "authorization", "agent", "ai"]
14
+ dependencies = [
15
+ "ory-argus>=0.8,<1",
16
+ # The Microsoft Agent Framework currently ships pre-release builds on PyPI; the
17
+ # pre-release marker lets `pip install ory-agent-framework` resolve them.
18
+ "agent-framework>=0.0.0a1",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = ["pytest>=8", "ruff>=0.6"]
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["src/ory_agent_framework"]
@@ -0,0 +1,18 @@
1
+ """Ory Agent Security for the Microsoft Agent Framework.
2
+
3
+ from ory_agent_framework import ory_function_middleware
4
+
5
+ agent = chat_client.create_agent(
6
+ instructions="…",
7
+ tools=[search, send_email],
8
+ middleware=[ory_function_middleware()],
9
+ )
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from .middleware import guarded_process, ory_function_middleware
15
+
16
+ __version__ = "0.13.9"
17
+
18
+ __all__ = ["ory_function_middleware", "guarded_process", "__version__"]
@@ -0,0 +1,85 @@
1
+ """Ory Agent Security for the Microsoft Agent Framework.
2
+
3
+ The Agent Framework's per-tool interceptor is **function middleware** — ``process(context,
4
+ next)``. ``ory_function_middleware()`` authorizes each function/tool call against Ory
5
+ Permissions, traces it, and (on first call) runs the Ory session gates:
6
+
7
+ - allow / observe / fail-open / interactive → call ``next`` (the function runs) and record
8
+ ``tool.complete``.
9
+ - deny (enforce) → set ``context.result`` to the denial and ``context.terminate = True``
10
+ *without* calling ``next``, so the function never runs.
11
+
12
+ Gate logic lives in ``ory_argus``. ``agent_framework`` is imported lazily, so importing this
13
+ module never requires it.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any
19
+
20
+ from ory_argus import GateResult, OryAgentClient, complete, gate, session_start
21
+
22
+ HARNESS = "agent-framework"
23
+
24
+
25
+ def _extract(context: Any) -> tuple[str, Any]:
26
+ fn = getattr(context, "function", None)
27
+ name = (
28
+ getattr(fn, "name", None)
29
+ or getattr(context, "function_name", None)
30
+ or getattr(context, "name", None)
31
+ or "unknown"
32
+ )
33
+ args = getattr(context, "arguments", None) or getattr(context, "args", None)
34
+ return name, args
35
+
36
+
37
+ async def guarded_process(
38
+ client: OryAgentClient,
39
+ context: Any,
40
+ call_next: Any,
41
+ *,
42
+ can_block: bool = True,
43
+ project_url: str | None = None,
44
+ _session_state: dict | None = None,
45
+ ) -> Any:
46
+ """SDK-free guarded middleware body — testable with a fake context + next."""
47
+ state = _session_state if _session_state is not None else {}
48
+ if not state.get("started"):
49
+ state["started"] = True
50
+ try:
51
+ session_start(client, harness=HARNESS, project_url=project_url)
52
+ except Exception as err: # noqa: BLE001
53
+ client.logger.warn("session_start.failed", {"message": str(err)})
54
+
55
+ name, args = _extract(context)
56
+ result: GateResult = gate(client, harness=HARNESS, tool_name=name, tool_args=args, can_block=can_block)
57
+ if result.blocked:
58
+ # Short-circuit: set the result and terminate without calling next.
59
+ context.result = result.denial_message or "Ory: permission denied"
60
+ try:
61
+ context.terminate = True
62
+ except Exception: # noqa: BLE001 — some contexts may not expose terminate
63
+ pass
64
+ return None
65
+ out = await call_next(context)
66
+ complete(client, tool_name=name)
67
+ return out
68
+
69
+
70
+ def ory_function_middleware(
71
+ *, client: OryAgentClient | None = None, can_block: bool = True, project_url: str | None = None
72
+ ):
73
+ """Return a function-middleware callable ``async (context, next)`` gated through Ory."""
74
+ c = client or OryAgentClient.from_env(HARNESS)
75
+ state: dict = {}
76
+
77
+ async def _middleware(context: Any, call_next: Any) -> Any:
78
+ return await guarded_process(
79
+ c, context, call_next, can_block=can_block, project_url=project_url, _session_state=state
80
+ )
81
+
82
+ return _middleware
83
+
84
+
85
+ __all__ = ["HARNESS", "guarded_process", "ory_function_middleware"]
@@ -0,0 +1,95 @@
1
+ """Hermetic tests for the Microsoft Agent Framework Ory function middleware (SDK-free).
2
+
3
+ Covers the native veto shape (``context.terminate`` + ``context.result`` without calling
4
+ ``next``) and request-shape extraction (``function.name`` and the ``function_name``
5
+ fallback). The full permission decision matrix is core-owned
6
+ (``ory-argus/tests/test_adapters.py``).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ from types import SimpleNamespace
13
+
14
+ import pytest
15
+
16
+ from ory_agent_framework import guarded_process # 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
+
31
+
32
+ def client_with_user():
33
+ c = create_mock_client(harness="agent-framework")
34
+ c.set_user_principal(PrincipalIdentity(subject="user:alice"))
35
+ return c
36
+
37
+
38
+ def make_context(name="search", args=None):
39
+ return SimpleNamespace(function=SimpleNamespace(name=name), arguments=args or {}, result=None, terminate=False)
40
+
41
+
42
+ def run(coro):
43
+ return asyncio.run(coro)
44
+
45
+
46
+ def test_allow_calls_next_and_completes():
47
+ c = client_with_user()
48
+ stub_permission_allowed(c)
49
+ ctx = make_context("search")
50
+ ran = {}
51
+
52
+ async def call_next(context):
53
+ ran["yes"] = True
54
+ return "ok"
55
+
56
+ out = run(guarded_process(c, ctx, call_next))
57
+ assert out == "ok"
58
+ assert ran.get("yes") is True
59
+ invoke = get_trace_spans(c, "tool.invoke")[0]
60
+ assert invoke.attributes["allowed"] is True
61
+ # Tool name pulled from the context's ``function.name``.
62
+ assert invoke.attributes["toolName"] == "search"
63
+ assert get_trace_spans(c, "tool.complete")
64
+
65
+
66
+ def test_enforce_terminates_without_next(monkeypatch):
67
+ monkeypatch.setenv("ORY_PERMISSION_MODE", "enforce")
68
+ c = client_with_user()
69
+ stub_permission_denied(c)
70
+ ctx = make_context("rm")
71
+ ran = {}
72
+
73
+ async def call_next(context):
74
+ ran["yes"] = True
75
+ return "should-not-run"
76
+
77
+ out = run(guarded_process(c, ctx, call_next))
78
+ # Native veto shape: next() skipped, terminate flag set, denial in context.result.
79
+ assert out is None
80
+ assert "yes" not in ran # next never called
81
+ assert ctx.terminate is True
82
+ assert "permission denied" in ctx.result
83
+ assert get_trace_spans(c, "tool.block")[0].attributes["blocked"] is True
84
+
85
+
86
+ def test_extract_handles_function_name_field():
87
+ c = client_with_user()
88
+ stub_permission_allowed(c)
89
+ ctx = SimpleNamespace(function_name="edit", args={"a": 1}, result=None, terminate=False)
90
+
91
+ async def call_next(context):
92
+ return "ok"
93
+
94
+ run(guarded_process(c, ctx, call_next))
95
+ assert get_trace_spans(c, "tool.invoke")[0].attributes["toolName"] == "edit"
@@ -0,0 +1,48 @@
1
+ """Real-SDK test: the Microsoft Agent Framework middleware builds + vetoes.
2
+
3
+ Runs only when ``agent_framework`` is importable; the default workspace venv skips it. The
4
+ framework's veto is plain attribute setting (no SDK type), so the real-SDK value here is that
5
+ the module + middleware import and construct against the installed SDK. The permission
6
+ decision is stubbed (no backend).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ from types import SimpleNamespace
13
+
14
+ import pytest
15
+
16
+ pytest.importorskip("agent_framework")
17
+
18
+ from ory_agent_framework import guarded_process, ory_function_middleware # noqa: E402
19
+ from ory_agent_framework import middleware as m # noqa: E402
20
+ from ory_argus.adapters import GateResult # noqa: E402
21
+ from ory_argus.permissions import PermissionDecision # noqa: E402
22
+ from ory_argus.testing import create_mock_client # noqa: E402
23
+
24
+
25
+ def test_middleware_builds_against_real_sdk():
26
+ assert callable(ory_function_middleware(client=create_mock_client(harness="agent-framework")))
27
+
28
+
29
+ def test_deny_terminates_without_next(monkeypatch):
30
+ monkeypatch.setattr(m, "session_start", lambda *a, **k: None)
31
+ monkeypatch.setattr(
32
+ m, "gate",
33
+ lambda client, **kw: GateResult(
34
+ proceed=False, blocked=True, decision=PermissionDecision(kind="deny"),
35
+ subject="User:t", namespace="AgentTools", denial_message="Ory: permission denied",
36
+ ),
37
+ )
38
+ ctx = SimpleNamespace(function=SimpleNamespace(name="MyRealTool"), arguments={}, result=None, terminate=False)
39
+ ran = {}
40
+
41
+ async def call_next(_ctx):
42
+ ran["yes"] = True
43
+ return "should-not-run"
44
+
45
+ asyncio.run(guarded_process(create_mock_client(harness="agent-framework"), ctx, call_next))
46
+ assert "yes" not in ran
47
+ assert ctx.terminate is True
48
+ assert "permission denied" in str(ctx.result)