feat: add {{USER_GROUPS}} and {{USER_GROUP_IDS}} placeholders for custom forwarded headers (#27236)

Custom per-connection headers can now forward the user's groups to
upstream backends via two new template placeholders:

- {{USER_GROUPS}}: comma-separated group names
- {{USER_GROUP_IDS}}: comma-separated group ids

The group lookup is async, so get_custom_headers becomes an async
wrapper around the sync template substitution (parse_custom_headers)
and fetches groups lazily — only when a header value actually
references a groups placeholder. The external document loader path
runs in a worker thread without an event loop, so Loader.aload
prefetches the groups before offloading and passes them through to
ExternalDocumentLoader.


Claude-Session: https://claude.ai/code/session_01EbBEfTyu8fFJmC13rnQthT

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Classic298andClaude authored and GitHub committed 2026-07-26 23:21:26 -04:00
1 parent 3cd72ee6a8
commit f32b19c1f6
7 files changed
+52 -8

No files matched your search

+2 -2
View File
@@ -614,7 +614,7 @@ async def verify_tool_servers_config(request: Request, form_data: ToolServerConn
if form_data.headers and isinstance(form_data.headers, dict):
if headers is None:
headers = {}
custom_headers = get_custom_headers(form_data.headers, user)
custom_headers = await get_custom_headers(form_data.headers, user)
headers.update(custom_headers)
await client.connect(form_data.url, headers=headers)
@@ -659,7 +659,7 @@ async def verify_tool_servers_config(request: Request, form_data: ToolServerConn
if form_data.headers and isinstance(form_data.headers, dict):
if headers is None:
headers = {}
custom_headers = get_custom_headers(form_data.headers, user)
custom_headers = await get_custom_headers(form_data.headers, user)
headers.update(custom_headers)
url = get_tool_server_url(form_data.url, form_data.path)
+1 -1
View File
@@ -122,7 +122,7 @@ async def send_request(
# Custom per-connection headers last so admin-set headers take precedence.
if api_config and api_config.get('headers'):
headers.update(get_custom_headers(api_config['headers'], user, metadata, request=request))
headers.update(await get_custom_headers(api_config['headers'], user, metadata, request=request))
r = await session.request(
method,
+1 -1
View File
@@ -211,7 +211,7 @@ async def get_headers_and_cookies(
headers['Authorization'] = f'Bearer {token}'
if config.get('headers') and isinstance(config.get('headers'), dict):
custom_headers = get_custom_headers(config.get('headers'), user, metadata, request=request)
custom_headers = await get_custom_headers(config.get('headers'), user, metadata, request=request)
headers.update(custom_headers)
return headers, cookies