Skip to content

feat(discovery): per-provider admin API keys for external worker model discovery - #578

Merged
slin1237 merged 2 commits into
mainfrom
slin/worker-registry-external
Mar 3, 2026
Merged

slin1237 merged 2 commits into
mainfrom
slin/worker-registry-external

Conversation

@slin1237

@slin1237 slin1237 commented Mar 3, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Add per-provider admin API keys (via env vars) for external worker model discovery, so multiple providers can use different credentials
  • Add provider-aware auth header handling (Anthropic uses x-api-key instead of Bearer)
  • Fully backward compatible: --api-key without env vars works identically to before

Refs: external worker registration support

What changed

protocols/src/worker.rs — 3 new methods on ProviderType:

  • from_url(url) → Option<ProviderType> — detect provider from URL domain (openai.com, x.ai, anthropic, googleapis.com)
  • admin_key_env_var() → Option<&str> — maps provider to env var name (OPENAI_ADMIN_KEY, XAI_ADMIN_KEY, ANTHROPIC_ADMIN_KEY, GEMINI_ADMIN_KEY)
  • uses_x_api_key() → bool — Anthropic requires x-api-key header

model_gateway/src/core/steps/worker/external/discover_models.rs:

  • Add resolve_discovery_api_key() — resolves API key with priority: per-provider env var → --api-key → None (wildcard mode)
  • Update fetch_models() — accepts Option<&ProviderType>, sends x-api-key for Anthropic, Bearer for all others
  • Update DiscoverModelsStep::execute() — uses new resolution logic instead of checking config.api_key directly

Why

When registering multiple external backends (OpenAI, Anthropic, xAI, Gemini), model discovery requires calling /v1/models with a valid API key. The existing --api-key flag serves as a single key for all providers, which doesn't work when different providers need different credentials. Per-provider admin keys solve this by allowing operators to set OPENAI_ADMIN_KEY, XAI_ADMIN_KEY, etc. as environment variables, each used only for discovery on the matching provider.

How

API key resolution follows a clear priority chain: first check for a per-provider env var based on URL domain detection (ProviderType::from_url), then fall back to --api-key, then enter wildcard mode. The ProviderType enum in the protocols crate was extended (rather than creating a new enum) to keep provider logic centralized and reusable by both core and routers layers.

Usage examples

# Per-provider admin keys (multiple providers, IGW mode):
export OPENAI_ADMIN_KEY=sk-admin-openai
export XAI_ADMIN_KEY=xai-admin-key
smg --worker-url https://api.openai.com --worker-url https://api.x.ai --enable-igw

# Admin key for discovery + master key for inference:
export OPENAI_ADMIN_KEY=sk-admin-openai
smg --worker-url https://api.openai.com --api-key sk-inference

# Backward compatible (no env vars):
smg --worker-url https://api.openai.com --api-key sk-xxx

Test plan

  • cargo check -p smg — compiles
  • cargo clippy -p smg -- -D warnings — no warnings
  • cargo clippy -p openai-protocol -- -D warnings — no warnings
  • cargo test -p smg — all 413 tests pass
  • Manual: set OPENAI_ADMIN_KEY, start with --worker-url https://api.openai.com --enable-igw, verify models discovered
  • Manual: verify --api-key without env vars still works as before

Summary by CodeRabbit

  • Refactor
    • Improved model discovery with per-provider environment variable and configuration fallbacks for API keys.
    • Added provider detection from model URLs and provider-aware model fetching to select the correct authentication header format.
  • Chores
    • Added URL parsing dependency to support provider resolution.

…model discovery

When SMG registers multiple external backends (OpenAI, Anthropic, xAI,
Gemini), model discovery via /v1/models requires provider-specific API
keys. Previously, --api-key was used for both discovery and inference
across all workers, which doesn't work with multiple providers needing
different credentials.

Add per-provider "admin keys" resolved from environment variables, used
only for model discovery (not inference). Resolution priority:
  1. Per-provider env var (OPENAI_ADMIN_KEY, XAI_ADMIN_KEY, etc.)
  2. --api-key flag (backward-compatible fallback)
  3. No key → wildcard mode (unchanged)

Also add provider-aware auth header handling: Anthropic uses x-api-key
header instead of Authorization: Bearer.

What changed:
- protocols/src/worker.rs: Add from_url(), admin_key_env_var(), and
  uses_x_api_key() methods to ProviderType enum for URL-based provider
  detection and per-provider admin key env var mapping
- model_gateway/src/core/steps/worker/external/discover_models.rs: Add
  resolve_discovery_api_key() function, update fetch_models() to accept
  provider for correct auth headers, update DiscoverModelsStep::execute()
  to use the new key resolution logic

Fully backward compatible: --api-key without env vars works as before.

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the model gateway's external worker model discovery by introducing a more robust and flexible API key management system. It addresses the challenge of integrating multiple external providers, each potentially requiring unique credentials, by allowing per-provider admin API keys to be configured via environment variables. This change also incorporates provider-specific authentication header handling, such as for Anthropic, ensuring broader compatibility while maintaining full backward compatibility with existing --api-key configurations.

Highlights

  • Per-Provider Admin API Keys: Introduced the ability to use distinct admin API keys for external worker model discovery, configured via environment variables (e.g., OPENAI_ADMIN_KEY, XAI_ADMIN_KEY), allowing different providers to use different credentials.
  • Provider-Aware Authentication Headers: Implemented logic to handle provider-specific authentication headers, specifically using x-api-key for Anthropic instead of the default Bearer token.
  • Backward Compatibility: Ensured that the existing --api-key flag remains fully functional and works identically when per-provider environment variables are not set.
  • API Key Resolution Priority: Established a clear priority chain for API key resolution: per-provider environment variable > --api-key configuration > wildcard mode (no key).
  • Extended ProviderType: Added new methods to the ProviderType enum in protocols/src/worker.rs for detecting providers from URLs, mapping to admin key environment variables, and identifying providers that use x-api-key.
Changelog
  • model_gateway/src/core/steps/worker/external/discover_models.rs
    • Added resolve_discovery_api_key function to determine the API key for model discovery based on a priority order (per-provider env var, then global config API key).
    • Modified fetch_models function to accept an optional ProviderType and conditionally use the x-api-key header for Anthropic providers.
    • Updated DiscoverModelsStep::execute to integrate the new resolve_discovery_api_key logic for fetching models.
  • protocols/src/worker.rs
    • Added from_url method to ProviderType to detect the provider based on the URL domain.
    • Added admin_key_env_var method to ProviderType to map providers to their corresponding admin API key environment variable names.
    • Added uses_x_api_key method to ProviderType to indicate whether a provider requires the x-api-key header for authentication.
Activity
  • No activity to report yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@coderabbitai

coderabbitai Bot commented Mar 3, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds provider-aware model discovery: provider is inferred from config URL, discovery API key is resolved with per-provider env var → config.api_key → none, and fetch_models uses provider to choose x-api-key vs. bearer authentication.

Changes

Cohort / File(s) Summary
Provider Detection Utilities
protocols/src/worker.rs
Added ProviderType::from_url(&str) -> Option<Self>, admin_key_env_var(&self) -> Option<&'static str>, and uses_x_api_key(&self) -> bool to detect provider from a URL, map provider-specific env var names, and indicate Anthropic's x-api-key header requirement.
Discovery API Key Resolution & Fetch
model_gateway/src/core/steps/worker/external/discover_models.rs
Introduced resolve_discovery_api_key(provider, url, config_api_key) to prefer per-provider env var → config.api_key → None. Extended fetch_models(url, api_key, provider) to accept an optional provider and send x-api-key for providers that require it, otherwise use Bearer token. Updated DiscoverModelsStep::execute to derive provider from config URL, resolve discovery key, and adjust wildcard vs. keyed discovery logic.
Dependency
protocols/Cargo.toml
Added url = "2.5" dependency to support URL parsing used by ProviderType::from_url.

Sequence Diagram(s)

sequenceDiagram
  participant WorkerStep as DiscoverModelsStep
  participant ProviderUtil as ProviderType::from_url
  participant Env as Environment
  participant Fetch as fetch_models
  participant Remote as Discovery API

  Note over WorkerStep,ProviderUtil: DiscoverModelsStep.execute flow
  WorkerStep->>ProviderUtil: parse config.url -> provider?
  ProviderUtil-->>WorkerStep: provider or None
  WorkerStep->>Env: check provider-specific env var (if provider)
  Env-->>WorkerStep: env value or None
  WorkerStep->>WorkerStep: resolve_discovery_api_key(env_var, config.api_key)
  WorkerStep->>Fetch: fetch_models(url, discovery_key, provider)
  Fetch->>Remote: HTTP request with header:
  Note right of Fetch: if provider.uses_x_api_key -> "x-api-key: <key>"\nelse if key -> "Authorization: Bearer <key>"\nelse -> no auth (wildcard)
  Remote-->>Fetch: models / error
  Fetch-->>WorkerStep: models or error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I sniffed the URL, then found the trail,
Env vars and configs tell the tale.
x-api-key or Bearer, I choose with cheer,
Models discovered, hopping near—
A tiny rabbit, keys kept clear. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding per-provider admin API keys for external worker model discovery, which is the core objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch slin/worker-registry-external

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added protocols Protocols crate changes model-gateway Model gateway crate changes labels Mar 3, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a valuable feature for using per-provider admin API keys for model discovery. However, the current implementation of provider detection from URLs is flawed, using a simple string containment check (url.contains(...)) that can be bypassed to leak sensitive administrative API keys. Suggestions have been provided to enhance the robustness of provider detection and optimize the code by removing a redundant function call.

Comment thread protocols/src/worker.rs
Comment thread model_gateway/src/core/steps/worker/external/discover_models.rs Outdated
Comment thread model_gateway/src/core/steps/worker/external/discover_models.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@protocols/src/worker.rs`:
- Around line 202-213: The provider detection in from_url is unsafe because it
uses url.contains; parse the input with url::Url::parse(url), get host_str(),
and match against canonical hostnames using exact-equals or suffix-with-dot
checks (e.g., host == "openai.com" || host.ends_with(".openai.com")) to prevent
matches like "openai.com.evil.example"; update the matching branches for OpenAI,
XAI, Anthropic, and Gemini in from_url to use the parsed host checks and return
None on parse failure.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6db7d7a and a9a72a1.

📒 Files selected for processing (2)
  • model_gateway/src/core/steps/worker/external/discover_models.rs
  • protocols/src/worker.rs

Comment thread protocols/src/worker.rs
…tion

Address code review feedback on PR #578:

- Security: Parse URL host with url::Url instead of string contains()
  to prevent credential leakage via crafted URLs like
  http://attacker.com/openai.com/. Now checks host suffix only
  (ends_with on parsed host). Also tighten "anthropic" to
  "anthropic.com".
- Simplify nested env var check using .ok().filter() idiom
- Remove redundant ProviderType::from_url() call by passing the
  already-resolved provider into resolve_discovery_api_key()
- Add url crate dependency to openai-protocol

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@github-actions github-actions Bot added the dependencies Dependency updates label Mar 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
protocols/src/worker.rs (1)

202-216: ⚠️ Potential issue | 🔴 Critical

Security: ends_with() hostname check still allows credential leakage to lookalike domains.

The current ends_with("openai.com") check matches malicious domains like evilopenai.com, which would cause admin credentials to be sent to attacker-controlled servers. The same vulnerability exists for all provider checks.

The fix requires checking for exact match OR dot-prefixed subdomain:

🔐 Proposed fix for secure hostname matching
     pub fn from_url(url: &str) -> Option<Self> {
         let host = url::Url::parse(url).ok()?.host_str()?.to_lowercase();

-        if host.ends_with("openai.com") {
+        if host == "openai.com" || host.ends_with(".openai.com") {
             Some(Self::OpenAI)
-        } else if host.ends_with("x.ai") {
+        } else if host == "x.ai" || host.ends_with(".x.ai") {
             Some(Self::XAI)
-        } else if host.ends_with("anthropic.com") {
+        } else if host == "anthropic.com" || host.ends_with(".anthropic.com") {
             Some(Self::Anthropic)
-        } else if host.ends_with("googleapis.com") {
+        } else if host == "googleapis.com" || host.ends_with(".googleapis.com") {
             Some(Self::Gemini)
         } else {
             None
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@protocols/src/worker.rs` around lines 202 - 216, The hostname matching in
from_url currently uses ends_with("openai.com") (and similar checks) which
allows lookalike domains; change the checks in the from_url function to accept
either an exact match OR a dot-prefixed subdomain (e.g., host == "openai.com" ||
host.ends_with(".openai.com")) for each provider (OpenAI, XAI, Anthropic,
Gemini) so credentials are only sent to the real domains or their subdomains;
keep the existing host normalization (to_lowercase) and apply the same pattern
for "x.ai", "anthropic.com", and "googleapis.com".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@protocols/src/worker.rs`:
- Around line 202-216: The hostname matching in from_url currently uses
ends_with("openai.com") (and similar checks) which allows lookalike domains;
change the checks in the from_url function to accept either an exact match OR a
dot-prefixed subdomain (e.g., host == "openai.com" ||
host.ends_with(".openai.com")) for each provider (OpenAI, XAI, Anthropic,
Gemini) so credentials are only sent to the real domains or their subdomains;
keep the existing host normalization (to_lowercase) and apply the same pattern
for "x.ai", "anthropic.com", and "googleapis.com".

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9a72a1 and da38b6c.

📒 Files selected for processing (3)
  • model_gateway/src/core/steps/worker/external/discover_models.rs
  • protocols/Cargo.toml
  • protocols/src/worker.rs

@slin1237
slin1237 merged commit 5432755 into main Mar 3, 2026
23 of 24 checks passed
@slin1237
slin1237 deleted the slin/worker-registry-external branch March 3, 2026 04:11
key4ng pushed a commit that referenced this pull request May 1, 2026
…ixes

The previous pin (fd080fc7) is the commit immediately before
lightseekorg/tokenspeed#578, which adds defensive Finished-state
handlers to the scheduler FSM. Without #578 the engine crashes under
retract pressure with:

    RuntimeError: FSM transition invalid:
      event=tokenspeed::fsm::ExtendResultEvent;
      state=tokenspeed::fsm::Finished

Reproduced on the nightly Qwen3-30B-A3B bench: when the host KV cache
fills up and a retract fails, AbortEvent terminalizes the request →
Finished, but overlap scheduling has already dispatched a forward
batch including it, and the late ExtendResultEvent commit hits a
strict FSM handler that throws and kills the scheduler event loop.

Bump to current lightseekorg/tokenspeed main (eabeb106) so we also
pick up #602 (release scheduler slot + cancel non-stream handlers on
client disconnect), which removes the long pre-crash stream of
``Received output for rid=... but the state was deleted in AsyncLLM``
warnings caused by aborted requests still occupying engine slots.

Signed-off-by: Chang Su <chang.s.su@oracle.com>
key4ng added a commit that referenced this pull request May 1, 2026
…ixes

The previous pin (fd080fc7) is the commit immediately before
lightseekorg/tokenspeed#578, which adds defensive Finished-state
handlers to the scheduler FSM. Without #578 the engine crashes under
retract pressure with:

    RuntimeError: FSM transition invalid:
      event=tokenspeed::fsm::ExtendResultEvent;
      state=tokenspeed::fsm::Finished

Reproduced on the nightly Qwen3-30B-A3B bench: when the host KV cache
fills up and a retract fails, AbortEvent terminalizes the request →
Finished, but overlap scheduling has already dispatched a forward
batch including it, and the late ExtendResultEvent commit hits a
strict FSM handler that throws and kills the scheduler event loop.

Bump to current lightseekorg/tokenspeed main (eabeb106) so we also
pick up #602 (release scheduler slot + cancel non-stream handlers on
client disconnect), which removes the long pre-crash stream of
``Received output for rid=... but the state was deleted in AsyncLLM``
warnings caused by aborted requests still occupying engine slots.

Signed-off-by: key4ng <rukeyang@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependency updates model-gateway Model gateway crate changes protocols Protocols crate changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant