Skip to content

Commit 1c0c792

Browse files
authored
chore: sync internal 0.5.10 to GitHub (#69)
2 parents 6395ebf + dbd6ef3 commit 1c0c792

5 files changed

Lines changed: 377 additions & 4 deletions

File tree

agentkit/apps/agent_server_app/agent_server_app.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@
4949
from agentkit.apps.agent_server_app.middleware import (
5050
AgentkitTelemetryHTTPMiddleware,
5151
)
52+
from agentkit.apps.agent_server_app.origin import (
53+
add_cors_compat_middleware,
54+
adk_supports_regex_origins,
55+
resolve_agentkit_allow_origins,
56+
split_allow_origins,
57+
supports_get_fast_api_kwarg,
58+
)
5259
from agentkit.apps.agent_server_app.telemetry import telemetry
5360
from agentkit.apps.base_app import BaseAgentkitApp
5461

@@ -101,6 +108,7 @@ def __init__(
101108
*,
102109
app: App | None = None,
103110
allow_origins: list[str] | None = None,
111+
allow_origin_regex: str | list[str] | None = None,
104112
) -> None:
105113
super().__init__()
106114

@@ -153,9 +161,36 @@ async def lifespan(app: FastAPI):
153161
await handler()
154162
yield
155163

156-
self.app = self.server.get_fast_api_app(
157-
lifespan=lifespan, allow_origins=allow_origins
164+
resolved_allow_origins = resolve_agentkit_allow_origins(
165+
allow_origins=allow_origins,
166+
allow_origin_regex=allow_origin_regex,
167+
)
168+
get_fast_api_app = self.server.get_fast_api_app
169+
get_fast_api_app_kwargs: dict[str, Any] = {"lifespan": lifespan}
170+
supports_allow_origins = supports_get_fast_api_kwarg(
171+
get_fast_api_app, "allow_origins"
158172
)
173+
supports_regex_origins = adk_supports_regex_origins()
174+
needs_cors_compat_middleware = False
175+
176+
if supports_allow_origins:
177+
if supports_regex_origins:
178+
get_fast_api_app_kwargs["allow_origins"] = (
179+
resolved_allow_origins or None
180+
)
181+
else:
182+
literal_origins, combined_regex = split_allow_origins(
183+
resolved_allow_origins
184+
)
185+
get_fast_api_app_kwargs["allow_origins"] = literal_origins or None
186+
needs_cors_compat_middleware = combined_regex is not None
187+
else:
188+
needs_cors_compat_middleware = bool(resolved_allow_origins)
189+
190+
self.app = get_fast_api_app(**get_fast_api_app_kwargs)
191+
192+
if needs_cors_compat_middleware:
193+
add_cors_compat_middleware(self.app, resolved_allow_origins)
159194

160195
@self.app.post("/run_sse")
161196
async def run_agent_sse(req: RunAgentRequest) -> StreamingResponse:
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import inspect
16+
import os
17+
import re
18+
from collections.abc import Callable
19+
from typing import Any
20+
21+
from fastapi import FastAPI
22+
from fastapi.middleware.cors import CORSMiddleware
23+
24+
_REGEX_PREFIX = "regex:"
25+
26+
DEFAULT_AGENTKIT_ALLOW_ORIGINS = ["*"]
27+
28+
STRICT_AGENTKIT_HOSTED_ORIGINS = [
29+
"https://console.volcengine.com",
30+
"https://console.byteplus.com",
31+
"regex:https://.*\\.volcengine\\.com",
32+
"regex:https://.*\\.volceapi\\.com",
33+
"regex:https://.*\\.byteplus\\.com",
34+
"regex:https://.*\\.byteplusapi\\.com",
35+
]
36+
37+
38+
def resolve_agentkit_allow_origins(
39+
*,
40+
allow_origins: list[str] | None,
41+
allow_origin_regex: str | list[str] | None = None,
42+
) -> list[str]:
43+
"""Resolve AgentKit CORS origins with SDK defaults and env overrides."""
44+
45+
env_origins = _get_env_list("AGENTKIT_ALLOW_ORIGINS", "ADK_ALLOW_ORIGINS")
46+
env_regexes = _get_env_list(
47+
"AGENTKIT_ALLOW_ORIGIN_REGEX",
48+
"ADK_ALLOW_ORIGIN_REGEX",
49+
)
50+
if allow_origins is not None:
51+
origins = list(allow_origins)
52+
elif env_origins is not None or env_regexes is not None:
53+
origins = env_origins or []
54+
elif _truthy_env("AGENTKIT_DISABLE_DEFAULT_ALLOW_ORIGINS"):
55+
origins = []
56+
else:
57+
origins = list(DEFAULT_AGENTKIT_ALLOW_ORIGINS)
58+
59+
if allow_origin_regex is None:
60+
regexes = env_regexes or []
61+
else:
62+
regexes = _as_list(allow_origin_regex)
63+
64+
resolved = [_normalize_origin(origin) for origin in origins]
65+
resolved.extend(_normalize_regex(pattern) for pattern in regexes)
66+
67+
deduped = _dedupe(resolved)
68+
_validate_regex_origins(deduped)
69+
return deduped
70+
71+
72+
def split_allow_origins(allow_origins: list[str]) -> tuple[list[str], str | None]:
73+
"""Split ADK-style origins into literal origins and a combined regex."""
74+
75+
literal_origins: list[str] = []
76+
regex_patterns: list[str] = []
77+
for origin in allow_origins:
78+
if origin.startswith(_REGEX_PREFIX):
79+
pattern = origin[len(_REGEX_PREFIX) :]
80+
if pattern:
81+
regex_patterns.append(pattern)
82+
else:
83+
literal_origins.append(origin)
84+
85+
return literal_origins, "|".join(regex_patterns) if regex_patterns else None
86+
87+
88+
def supports_get_fast_api_kwarg(func: Callable[..., Any], kwarg_name: str) -> bool:
89+
"""Return whether a callable accepts a given keyword argument."""
90+
91+
try:
92+
signature = inspect.signature(func)
93+
except (TypeError, ValueError):
94+
return True
95+
96+
for parameter in signature.parameters.values():
97+
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
98+
return True
99+
return kwarg_name in signature.parameters
100+
101+
102+
def adk_supports_regex_origins() -> bool:
103+
"""Return whether installed ADK understands `regex:` allow_origins entries."""
104+
105+
try:
106+
from google.adk.cli import adk_web_server
107+
108+
return hasattr(adk_web_server, "_parse_cors_origins")
109+
except Exception:
110+
return False
111+
112+
113+
def add_cors_compat_middleware(app: FastAPI, allow_origins: list[str]) -> None:
114+
"""Add CORS middleware for ADK versions that cannot parse regex origins."""
115+
116+
literal_origins, allow_origin_regex = split_allow_origins(allow_origins)
117+
app.add_middleware(
118+
CORSMiddleware,
119+
allow_origins=literal_origins,
120+
allow_origin_regex=allow_origin_regex,
121+
allow_credentials=True,
122+
allow_methods=["*"],
123+
allow_headers=["*"],
124+
)
125+
126+
127+
def _normalize_origin(origin: str) -> str:
128+
origin = origin.strip()
129+
if not origin or origin == "*" or origin.startswith(_REGEX_PREFIX):
130+
return origin
131+
if "*" in origin:
132+
return _normalize_regex(_glob_to_regex(origin))
133+
return origin
134+
135+
136+
def _normalize_regex(pattern: str) -> str:
137+
pattern = pattern.strip()
138+
if not pattern:
139+
return pattern
140+
if pattern.startswith(_REGEX_PREFIX):
141+
return pattern
142+
return f"{_REGEX_PREFIX}{pattern}"
143+
144+
145+
def _glob_to_regex(origin: str) -> str:
146+
return re.escape(origin).replace("\\*", ".*")
147+
148+
149+
def _validate_regex_origins(origins: list[str]) -> None:
150+
for origin in origins:
151+
if not origin.startswith(_REGEX_PREFIX):
152+
continue
153+
pattern = origin[len(_REGEX_PREFIX) :]
154+
try:
155+
re.compile(pattern)
156+
except re.error as e:
157+
raise ValueError(f"Invalid allow origin regex '{pattern}': {e}") from e
158+
159+
160+
def _get_env_list(*names: str) -> list[str] | None:
161+
for name in names:
162+
value = os.getenv(name)
163+
if value is None:
164+
continue
165+
return [item.strip() for item in value.split(",") if item.strip()]
166+
return None
167+
168+
169+
def _truthy_env(name: str) -> bool:
170+
value = os.getenv(name, "")
171+
return value.lower() in {"1", "true", "yes", "on"}
172+
173+
174+
def _as_list(value: str | list[str] | None) -> list[str]:
175+
if value is None:
176+
return []
177+
if isinstance(value, str):
178+
return [value]
179+
return list(value)
180+
181+
182+
def _dedupe(values: list[str]) -> list[str]:
183+
seen: set[str] = set()
184+
deduped: list[str] = []
185+
for value in values:
186+
if not value or value in seen:
187+
continue
188+
seen.add(value)
189+
deduped.append(value)
190+
return deduped

agentkit/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
VERSION = "0.5.9"
15+
VERSION = "0.5.10"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "agentkit-sdk-python"
3-
version = "0.5.9"
3+
version = "0.5.10"
44
description = "Python SDK for transforming any AI agent into a production-ready application. Framework-agnostic primitives for runtime, memory, authentication, and tools with volcengine-managed infrastructure."
55
readme = "README.md"
66
requires-python = ">=3.10"

0 commit comments

Comments
 (0)