Skip to content

Commit 94cb577

Browse files
authored
Implement OIDC support for Gateway (#183)
* Implement OIDC support for Gateway Make vercel dep optional Add local middleware patch to fastapi vite Remove middleware hacks from the OIDC example * Clean up the rebase
1 parent ac4dcac commit 94cb577

13 files changed

Lines changed: 374 additions & 132 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ A toolkit for building LLM-powered applications and agent loops.
88
uv add ai
99
```
1010

11-
AI Gateway usage works with the base package. Direct providers that use an
12-
OpenAI-compatible or Anthropic-compatible adapter load the corresponding
13-
official SDK lazily and require optional extras:
11+
AI Gateway API-key usage works with the base package. Direct providers that
12+
use an OpenAI-compatible or Anthropic-compatible adapter load the corresponding
13+
official SDK lazily. Vercel OIDC for AI Gateway also uses an optional extra:
1414

1515
```bash
1616
uv add "ai[openai]" # OpenAI-compatible providers
1717
uv add "ai[anthropic]" # Anthropic-compatible providers
18+
uv add "ai[vercel]" # Vercel OIDC for AI Gateway
1819
```
1920

2021
```python

examples/fastapi-vite/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ data/
1717

1818
# Vercel
1919
.vercel
20+
.env*

examples/fastapi-vite/backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description = "Chat demo using Python Vercel AI SDK with FastAPI"
55
requires-python = ">=3.12"
66
dependencies = [
77
"fastapi[standard]>=0.128.1",
8-
"ai>=0.0.1.dev5",
8+
"ai[vercel]>=0.2.1",
99
]
1010

1111
[tool.uv]

examples/fastapi-vite/e2e-test/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ trap cleanup EXIT
3838
echo "Starting backend on :$BACKEND_PORT..."
3939
(
4040
cd "$ROOT/backend"
41-
uv run --frozen --with-editable ~/src/py-ai/ fastapi dev main.py --port "$BACKEND_PORT"
41+
uv run --frozen --with-editable "$ROOT/../.." fastapi dev main.py --port "$BACKEND_PORT"
4242
) > "$LOGS/backend.log" 2>&1 &
4343
BACKEND_PID=$!
4444

examples/fastapi-vite/frontend/pnpm-lock.yaml

Lines changed: 21 additions & 106 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ dependencies = [
3838
anthropic = ["anthropic>=0.83.0"]
3939
mcp = ["mcp>=1.18.0"]
4040
openai = ["openai>=2.14.0"]
41+
vercel = [
42+
"vercel>=0.5.9",
43+
]
4144

4245
[build-system]
4346
requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Async client for the AI Gateway provider protocol."""
22

33
from . import errors
4-
from ._client import GatewayClient, ModelType
4+
from ._client import AuthMethod, GatewayClient, ModelType
55

6-
__all__ = ["GatewayClient", "ModelType", "errors"]
6+
__all__ = ["AuthMethod", "GatewayClient", "ModelType", "errors"]

src/ai/providers/ai_gateway/client/_client.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
_PROTOCOL_VERSION = "0.0.1"
2020

2121
ModelType = Literal["language", "image", "video"]
22+
AuthMethod = Literal["api-key", "oidc"]
2223

2324

2425
class GatewayClient:
@@ -33,12 +34,14 @@ def __init__(
3334
self,
3435
*,
3536
base_url: str,
36-
api_key: str | None = None,
37+
auth_token: str | None = None,
38+
auth_method: AuthMethod | None = None,
3739
headers: Mapping[str, str] | None = None,
3840
client: httpx.AsyncClient | None = None,
3941
) -> None:
4042
self.base_url = base_url
41-
self.api_key = api_key
43+
self.auth_token = auth_token
44+
self.auth_method = auth_method
4245
self.headers = dict(headers or {})
4346
self._http = client or httpx.AsyncClient(
4447
timeout=httpx.Timeout(timeout=300.0, connect=10.0),
@@ -59,9 +62,9 @@ def origin_url(self, path: str) -> str:
5962
def protocol_headers(self) -> dict[str, str]:
6063
headers = dict(self.headers)
6164
headers["ai-gateway-protocol-version"] = _PROTOCOL_VERSION
62-
if self.api_key:
63-
headers["Authorization"] = f"Bearer {self.api_key}"
64-
headers["ai-gateway-auth-method"] = "api-key"
65+
if self.auth_token and self.auth_method:
66+
headers["Authorization"] = f"Bearer {self.auth_token}"
67+
headers["ai-gateway-auth-method"] = self.auth_method
6568
return headers
6669

6770
def model_headers(
@@ -165,7 +168,7 @@ async def probe_model(self, model_id: str) -> None:
165168
auth_resp = await self.get("v1/credits", origin=True)
166169
if auth_resp.status_code in {401, 403}:
167170
raise errors.GatewayAuthenticationError.create_contextual(
168-
api_key_provided=bool(self.api_key),
171+
auth_method=self.auth_method if self.auth_token else None,
169172
status_code=auth_resp.status_code,
170173
)
171174
if auth_resp.status_code != 200:
@@ -239,7 +242,7 @@ async def raise_for_error(self, response: httpx.Response) -> None:
239242
raise errors.create_gateway_error(
240243
response_body=response.text,
241244
status_code=response.status_code,
242-
api_key_provided=bool(self.api_key),
245+
auth_method=self.auth_method if self.auth_token else None,
243246
)
244247

245248
async def iter_sse(

0 commit comments

Comments
 (0)