feat: add token vault support for auth0-ai-ms-agent SDK#60
feat: add token vault support for auth0-ai-ms-agent SDK#60adam-wang-okta-public merged 4 commits intomainfrom
Conversation
Quickly reviewed it with ClaudePR ReviewThe core logic is sound — framework kwargs stripping, context building, 🔴 Critical (blocking)
[tool.poetry.dependencies]
auth0-ai = { path = "../auth0-ai", develop = true } # won't resolve from PyPI
Anyone who runs pip install auth0-ai-ms-agent will get a broken package. The LangChain adapter handles this correctly — version pin in
main deps, path override in dev deps:
[tool.poetry.dependencies]
auth0-ai = "^1.0.x"
[tool.poetry.group.dev.dependencies]
auth0-ai = { path = "../auth0-ai", develop = true }
---
🟠 Major
Dead openfga-sdk dependency
openfga-sdk = "^0.9.5" is listed but there is no FGA code in this package. The other adapters include it because they ship FGA support —
this one is token-vault only. Remove it to avoid unnecessary transitive dependencies for all users.
---
TokenVaultAuthorizer incorrectly inherits from ABC
token_vault_authorizer.py:11 — TokenVaultAuthorizer is a concrete class, it's directly instantiated in auth0_ai.py. Declaring it abstract
is contradictory. TokenVaultAuthorizerBase is Generic, not ABC, so this isn't even inherited. The boilerplate __init__ that only calls
super() can also be removed entirely.
# current
class TokenVaultAuthorizer(TokenVaultAuthorizerBase, ABC):
def __init__(self, params, auth0=None):
super().__init__(params, auth0)
# suggested
class TokenVaultAuthorizer(TokenVaultAuthorizerBase):
pass # or just remove __init__ entirely
---
inspect._empty is a private API
tool_wrapper.py:45:
# current — uses private implementation detail
if value is not None or param.default is inspect._empty:
# fix — use the public API
if value is not None or param.default is inspect.Parameter.empty:
---
Non-serializable exception stored in session state
tool_wrapper.py:92:
session.state["pending_interrupt"] = e # stores a live Python exception object
If AgentSession.state is ever persisted between turns (Redis, DB, over the wire) — which is the point of a session in multi-turn
conversations — serializing a Python exception will fail. The data callers actually need (connection, scopes, required_scopes,
authorization_params) is all on the interrupt object; the exception wrapper is not necessary. The README's isinstance(interrupt,
TokenVaultInterrupt) check also couples callers to never persisting state. At minimum this constraint should be clearly documented;
ideally the stored value is a plain serializable dict.
---
pending_interrupt is never cleared on subsequent runs
After a TokenVaultInterrupt, session.state["pending_interrupt"] is written but never cleaned up. If the user completes authorization and
the agent runs successfully on the next turn, the stale interrupt is still present. Any caller checking
session.state.get("pending_interrupt") after a successful run will see a false positive.
Simple fix — clear it at the start of each invocation before calling protect_fn:
session.state.pop("pending_interrupt", None)
---
🟡 Minor
**params: TokenVaultAuthorizerParams type annotation is misleading
auth0_ai.py:27 — **params: T means "each kwarg value has type T", not "these kwargs together conform to T's shape".
TokenVaultAuthorizerParams is a regular class, not a TypedDict. The annotation doesn't give callers useful IDE autocomplete and is
semantically incorrect. Either annotate as **kwargs: Any or explicitly declare each parameter mirroring the
TokenVaultAuthorizerParams.__init__ signature.
---
_schema_supplied private attribute is fragile
tool_wrapper.py:96:
schema_or_model = tool.parameters() if getattr(tool, "_schema_supplied", False) else input_model
_schema_supplied is a private attribute on an RC-stage FunctionTool. The getattr default of False means if this attribute is renamed or
removed in a future framework version, this silently falls back to input_model with no error — just a quiet schema regression. Deserves a
comment explaining why it's needed and what the silent fallback means, so it's easy to audit on framework upgrades.
---
_FRAMEWORK_KWARGS risk going stale silently
The set is a deny-list of framework-injected kwargs derived from a specific line in the RC framework. When the framework graduates from RC
and adds/removes injected kwargs, this set won't update automatically. If a new framework kwarg isn't listed here it leaks into
original_func as an unexpected argument. The comment linking to the source line is good — worth also noting explicitly that this is a
maintenance point that needs revisiting on each framework version bump.
---
Missing pytest asyncio mode configuration
With pytest-asyncio = "^0.25.0", omitting asyncio_mode in pyproject.toml produces deprecation warnings. Add:
[tool.pytest.ini_options]
asyncio_mode = "auto"
---
Missing newline at end of token_vault_authorizer.py
---
🔵 Test coverage gaps
- No test for stale pending_interrupt being cleared on a successful retry — would directly catch the issue raised above.
- tool_call_id uniqueness is untested — the PR explicitly notes this is generated per-invocation as a design decision; worth asserting
that two successive invocations produce distinct tool_call_id values in their contexts.
- No test for the non-_schema_supplied path — test_returned_function_tool_preserves_schema_supplied_input_model covers the schema dict
path but not the pydantic model path. |
Comments were addressed with code update in a new commit. Complete replies to comments as below.#1 — PyPI path dependency (blocking) #2 — openfga-sdk dead dependency #3 — Incorrect ABC + redundant init #4 — inspect._empty private API #5 — AgentSession.state serialization #6 — Stale interrupt never cleared on retry #7 — #8 — Fragile _schema_supplied private attribute #9 — _FRAMEWORK_KWARGS silently going stale #10 — Missing pytest asyncio config #11 — Missing trailing newline Test gaps — tool_call_id uniqueness + non-_schema_supplied path Thanks for the review. @agupta-ghub |
|
Thanks for the thorough responses, most items are resolved. Just my 2 cents :
"Coming soon" is not a sufficient reason to ship a transitive dependency to all users today. Every
|
|
|
I mainly reviewed this update for consistency with the existing packages, w/o the example posted here. Here's what I'm seeing w/ Claude / Opus 4.6: ## PR Review: `auth0-ai-ms-agent` Package Consistency
I compared `packages/auth0-ai-ms-agent` against the three existing packages (`auth0-ai`, `auth0-ai-langchain`, `auth0-ai-llamaindex`) and found the following inconsistencies:
### 1. Missing `LICENSE` file
All three existing packages include an Apache 2.0 `LICENSE` file at the package root. The new `auth0-ai-ms-agent` package does **not** have one, even though its `pyproject.toml` declares `license = "apache-2.0"`.
### 2. Missing `tests/__init__.py`
All three existing packages have an empty `tests/__init__.py`. The new package is missing it. While this may work in practice, it's inconsistent and could cause issues with some test discovery configurations.
### 3. Missing README badges
The `auth0-ai-langchain` and `auth0-ai-llamaindex` packages include PyPI release, download, and license badges at the top of their READMEs. The `auth0-ai-ms-agent` README has none.
### 4. Python version constraint is different
| Package | Constraint |
|---|---|
| `auth0-ai`, `auth0-ai-langchain`, `auth0-ai-llamaindex` | `python = "^3.11"` (>=3.11, <4.0) |
| `auth0-ai-ms-agent` | `python = ">=3.11,<3.14"` |
The ms-agent constraint is tighter (capped at <3.14). This may be intentional due to `agent-framework` requirements, but worth confirming.
### 5. `TokenVaultAuthorizer` does not inherit `ABC` or override `_handle_authorization_interrupts`
In both langchain and llamaindex:
`class TokenVaultAuthorizer(TokenVaultAuthorizerBase, ABC):`
In ms-agent:
`class TokenVaultAuthorizer(TokenVaultAuthorizerBase):`
The `ABC` mixin is dropped. Additionally, the langchain authorizer overrides `_handle_authorization_interrupts` to convert to a LangGraph interrupt. The ms-agent version does **not** override it — it falls through to the base class default (`raise err`). This may be fine for the MS Agent Framework's interrupt model (which uses `session.state["pending_interrupt"]` instead), but the lack of `ABC` is a minor inconsistency.
### 6. Test dependency differences
- `auth0-ai-ms-agent` includes `pytest-cov` and `pytest-randomly` in its `[tool.poetry.group.test.dependencies]`, which the langchain and llamaindex packages do not.
- The base `auth0-ai` package puts pytest under `[tool.poetry.group.dev.dependencies]` rather than a separate `test` group.
Minor, but inconsistent across packages.
### 7. Top-level `__init__.py` exports are different in style
The ms-agent `__init__.py` exports `Auth0AI`, `TokenVaultAuthorizer`, `get_credentials_from_token_vault`, and `get_access_token_from_token_vault` at the top level. The langchain and llamaindex packages only export `FGARetriever` from their top-level `__init__.py`, with `Auth0AI` and token vault symbols accessed via submodules. This gives ms-agent a flatter public API surface.
### 8. No `async_authorization` or `fga` submodules
The langchain and llamaindex packages both have `async_authorization/` and `fga/` submodules. The ms-agent package has neither (the README notes these as "coming soon"). Not a bug — just a scope gap to be aware of for feature parity tracking.
---
### Summary
| Issue | Severity |
|---|---|
| Missing `LICENSE` file | **High** — should be added before merge |
| Missing `tests/__init__.py` | **Medium** — consistency + potential test discovery issue |
| Missing README badges | **Low** — cosmetic |
| Python version constraint difference | **Low** — confirm if intentional |
| Dropped `ABC` mixin on `TokenVaultAuthorizer` | **Low** — worth aligning |
| Test dependency inconsistencies | **Low** — cosmetic |
| Flatter `__init__.py` exports | **Low** — style preference |
| Missing `async_authorization`/`fga` modules | **Info** — known scope gap |From my view here, i'm really only concerned with: 1.) I think a LICENSE file would be nice here so when we go to publish to PyPI it's included. The other comments are largely unrelated to this PR, and would seemingly be addressed with future updates. |
|
Thanks for the check @priley86 .
Best practice is ^3.11 rather than hardcoding <3.14, because:
|
… update python version constraints
Description
Adds the auth0-ai-ms-agent package — an Auth0 AI SDK adapter for the https://github.com/microsoft/agent-framework.
This PR implements the Token Vault feature, which allows MS Agent tools to obtain access tokens for third-party APIs (Google, GitHub, Slack, etc.) on behalf of the user via Auth0's Token Vault.
Design doc
Public API example
Implementation details
Note
original_funcis called directly rather than through FunctionTool.call to avoid double-counting invocation metrics already tracked at the wrapped tool boundary.tool_call_idis generated per invocation (rather than forwarded from the framework) because FunctionTool.invoke() consumes it before it reaches the wrapped function.agent-frameworkto^1.0.0rc2Testing
Manual QA
Unit test
Unit tests are provided in tests/test_auth0_ai.py (15 tests, 100% coverage).
protect()logic runs for real, catching any interface mismatches between the adapter and the core SDK.Test coverage includes:
Decorator contract (returns callable, preserves tool name, description, schema, and all framework configuration)
Authorization pass path for both sync and async tool functions
Authorization fail path (TokenVaultInterrupt raised, session state set, original function not executed)
Framework kwargs stripping before calling the original function
Non-TokenVaultInterrupt exceptions propagate unchanged without setting session state
RuntimeError raised when no session is provided
To run tests:
cd packages/auth0-ai-ms-agent poetry install poetry run pytest tests/ --cov=auth0_ai_ms_agent --cov-report=term-missing -vChecklist
References