Skip to content

Add LLM subscription auth endpoints#3367

Open
neubig wants to merge 12 commits into
mainfrom
add-llm-subscription-endpoints
Open

Add LLM subscription auth endpoints#3367
neubig wants to merge 12 commits into
mainfrom
add-llm-subscription-endpoints

Conversation

@neubig
Copy link
Copy Markdown
Member

@neubig neubig commented May 23, 2026

Summary

  • add safe OpenAI subscription status, device-code start/poll, logout, and model endpoints to agent-server
  • add serializable LLM auth mode fields and resolve subscription-backed LLM configs at runtime without exposing OAuth tokens
  • cover endpoint token-redaction behavior and subscription settings round-tripping in tests

Tests

  • uv run ruff check openhands-sdk/openhands/sdk/llm/llm.py openhands-sdk/openhands/sdk/llm/auth/openai.py openhands-sdk/openhands/sdk/llm/auth/init.py openhands-sdk/openhands/sdk/settings/model.py openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py openhands-agent-server/openhands/agent_server/llm_router.py tests/agent_server/test_llm_router.py tests/sdk/test_settings.py
  • uv run pytest tests/agent_server/test_llm_router.py tests/sdk/test_settings.py -q

This PR was created by an AI agent (OpenHands) on behalf of the user.


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:3ff4784-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-3ff4784-python \
  ghcr.io/openhands/agent-server:3ff4784-python

All tags pushed for this build

ghcr.io/openhands/agent-server:3ff4784-golang-amd64
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-golang-amd64
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-golang-amd64
ghcr.io/openhands/agent-server:3ff4784-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:3ff4784-golang-arm64
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-golang-arm64
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-golang-arm64
ghcr.io/openhands/agent-server:3ff4784-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:3ff4784-java-amd64
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-java-amd64
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-java-amd64
ghcr.io/openhands/agent-server:3ff4784-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:3ff4784-java-arm64
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-java-arm64
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-java-arm64
ghcr.io/openhands/agent-server:3ff4784-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:3ff4784-python-amd64
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-python-amd64
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-python-amd64
ghcr.io/openhands/agent-server:3ff4784-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:3ff4784-python-arm64
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-python-arm64
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-python-arm64
ghcr.io/openhands/agent-server:3ff4784-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:3ff4784-golang
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-golang
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-golang
ghcr.io/openhands/agent-server:3ff4784-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:3ff4784-java
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-java
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-java
ghcr.io/openhands/agent-server:3ff4784-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:3ff4784-python
ghcr.io/openhands/agent-server:3ff47842e8828e6e9fbd1f72e02c7dcceaad06d2-python
ghcr.io/openhands/agent-server:add-llm-subscription-endpoints-python
ghcr.io/openhands/agent-server:3ff4784-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 3ff4784-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 3ff4784-python-amd64) are also available if needed

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 23, 2026

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 23, 2026

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 23, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   llm_router.py1341688%97, 103, 111, 165, 177–178, 219–221, 230–235, 245
openhands-sdk/openhands/sdk/conversation/impl
   local_conversation.py6735591%90, 332, 337, 481, 527, 596, 612, 688, 918–919, 996–997, 1000, 1105, 1108–1109, 1133, 1166–1167, 1170, 1176, 1237, 1240, 1244–1245, 1249–1250, 1253, 1260, 1285, 1289, 1292, 1311, 1363, 1366, 1405, 1413, 1417–1419, 1426, 1534, 1539, 1649, 1651, 1655–1656, 1667–1668, 1693, 1888, 1892, 1962, 1969–1970
openhands-sdk/openhands/sdk/llm
   llm.py7248388%510, 534, 567, 852–853, 856–860, 862, 870–872, 876, 893–894, 898, 900–901, 903–905, 983, 1046, 1214–1216, 1301, 1342, 1354–1356, 1359–1362, 1368, 1411, 1454, 1467–1469, 1472–1475, 1481, 1551, 1553, 1574, 1576, 1680, 1694–1699, 1815–1816, 2048–2049, 2058, 2064, 2069, 2132, 2134, 2136–2139, 2141–2144, 2149–2152, 2167, 2228, 2230
openhands-sdk/openhands/sdk/llm/auth
   openai.py38112467%181–182, 186–188, 241–243, 267–268, 279–281, 303–304, 308, 318–319, 322, 353, 369, 374–375, 384–386, 391–392, 401–403, 516, 521–523, 555–556, 558–561, 564, 567, 569–570, 572–576, 581–586, 592–596, 602–603, 606–612, 618, 620–622, 624–629, 635, 637, 639–641, 643, 647–648, 651–652, 656, 659, 665–667, 670, 674, 683–686, 717–718, 799, 801, 810, 874–875, 879, 882–886, 889–891, 894–895, 907, 911, 916, 955, 994
openhands-sdk/openhands/sdk/settings
   model.py5655091%83, 108, 113, 352, 362–365, 368, 381, 385, 391, 401, 407, 412, 602, 615, 626, 636, 640, 642, 644, 646, 648, 650, 652, 930, 932, 1045, 1229, 1298, 1337, 1364, 1400–1403, 1429, 1553, 1598, 1630, 1640, 1642, 1647, 1665, 1678, 1680, 1682, 1684, 1691
TOTAL29377660577% 

Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

🟡 QA Report: PARTIAL

The new subscription API endpoints and SDK settings path worked up to the point that does not require a human OpenAI subscription login; completed OAuth/login-backed LLM use could not be verified without credentials.

Does this PR achieve its stated goal?

Partially. I verified with a real running agent-server that the PR adds the previously-missing OpenAI subscription endpoints: models/status/device-start/device-poll/logout all returned the expected HTTP statuses and safe response shapes, and the device-start call reached OpenAI and produced a real browser sign-in challenge. I also verified the SDK now preserves auth_type="subscription" / subscription_vendor="openai" in serialized settings and no longer silently creates a normal API-key LLM from that config. I could not verify the final connected status or an actual subscription-backed LLM call because that requires completing the OpenAI device-code flow with a ChatGPT subscription account.

Phase Result
Environment Setup make build completed and the agent-server launched locally
CI Status ⏳ Most checks are green; several build/QA/review checks were still in progress when checked
Functional Verification 🟡 New endpoints/settings behavior verified; completed OAuth + real subscription LLM use not verified
Functional Verification

Test 1: Subscription endpoints on the running agent-server

Step 1 — Reproduce / establish baseline without the PR:

Checked out main at 3d9fc105856acd1d8786b8ba76ea2f3dc8be2fc8, launched the real server:

OPENHANDS_SUPPRESS_BANNER=1 uv run python -m openhands.agent_server --host 127.0.0.1 --port 8010

Then called existing and new API routes:

GET /api/llm/providers -> HTTP 200
GET /api/llm/subscription/openai/status -> {"detail":"Not Found"} HTTP 404
GET /api/llm/subscription/openai/models -> {"detail":"Not Found"} HTTP 404

This confirms the base branch served the existing LLM API but did not expose the subscription endpoints.

Step 2 — Apply the PR's changes:

Checked out add-llm-subscription-endpoints at c47c75573cf1d1b614458207ae34cc9387517867 and launched the real server on port 8011.

Step 3 — Re-run with the PR in place:

GET /api/llm/providers -> HTTP 200
GET /api/llm/subscription/openai/models -> HTTP 200
{"vendor":"openai","models":["gpt-5.1-codex-max","gpt-5.1-codex-mini","gpt-5.2","gpt-5.2-codex","gpt-5.3-codex"]}

GET /api/llm/subscription/openai/status -> HTTP 200
{"vendor":"openai","connected":false,"account_email":null,"expires_at":null}

POST /api/llm/subscription/openai/device/start -> HTTP 200
{"device_code":"<43-char opaque server token>","user_code":"<redacted live OpenAI user code>","verification_uri":"https://auth.openai.com/codex/device","verification_uri_complete":null,"expires_at":1779551456489,"interval_seconds":5}

POST /api/llm/subscription/openai/device/poll with the returned opaque token -> HTTP 200
{"vendor":"openai","connected":false,"account_email":null,"expires_at":null}

POST /api/llm/subscription/openai/device/poll with an invalid token -> HTTP 404
{"detail":"Subscription device login not found or expired"}

POST /api/llm/subscription/openai/logout -> HTTP 200
{"vendor":"openai","connected":false,"account_email":null,"expires_at":null}

This shows the PR adds functional HTTP endpoints, preserves the existing providers endpoint, returns an OpenAI device-code challenge through the real API path, keeps polling server-side via an opaque token, handles pending/invalid poll states, and returns safe status/logout payloads without OAuth access or refresh tokens.

Test 2: SDK subscription settings round-trip and runtime behavior

Step 1 — Reproduce / establish baseline without the PR:

On main, ran a small user-style SDK script that constructs an LLM with subscription fields, serializes OpenHandsAgentSettings, and calls create_agent():

LLM_CREATED {'model': 'gpt-5.2-codex', ...}
SETTINGS_DUMP {'model': 'gpt-5.2-codex', ...}
CREATE_AGENT_OK

The subscription fields were absent from the dump and the agent was created as a normal LLM configuration, confirming the base branch did not round-trip subscription auth settings.

Step 2 — Apply the PR's changes:

Checked out c47c75573cf1d1b614458207ae34cc9387517867 and ran the same script.

Step 3 — Re-run with the PR in place:

LLM_CREATED {'model': 'gpt-5.2-codex', 'auth_type': 'subscription', 'subscription_vendor': 'openai', ...}
SETTINGS_DUMP {'model': 'gpt-5.2-codex', 'auth_type': 'subscription', 'subscription_vendor': 'openai', ...}
CREATE_AGENT_ERROR ValueError OpenAI subscription login is required

This shows the PR preserves the subscription auth fields in serialized settings and changes runtime behavior from silently creating a regular API-key LLM to requiring an OpenAI subscription login before building the agent.

Unable to Verify

I could not complete the OpenAI browser device-code flow, verify connected: true, verify token refresh behavior, or make an actual subscription-backed LLM completion because this environment does not have a ChatGPT subscription account to authorize the live device challenge. Future QA would benefit from AGENTS.md guidance documenting whether a safe non-production OpenAI subscription test account or approved OAuth stub is available for end-to-end subscription login verification.

Issues Found

None from functional QA. The only gap is credential-bound verification of the completed OpenAI login and actual subscription LLM use.

This review was created by an AI agent (OpenHands) on behalf of the user.

Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

🟡 Taste Rating: Acceptable, but I found a few correctness issues around subscription runtime resolution and device-login lifecycle. Inline comments have the actionable details.

Risk: 🟡 Medium — this touches auth/session state and LLM runtime wiring for long-running agents.
Verdict: COMMENT pending fixes.

This review was generated by an AI agent (OpenHands) on behalf of the user.


Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26336675207

Comment thread openhands-agent-server/openhands/agent_server/llm_router.py Outdated
Comment thread openhands-agent-server/openhands/agent_server/llm_router.py Outdated
Comment thread openhands-sdk/openhands/sdk/llm/auth/openai.py Outdated
Comment thread openhands-sdk/openhands/sdk/llm/auth/openai.py Outdated
Comment thread openhands-sdk/openhands/sdk/llm/auth/openai.py Outdated
Comment thread openhands-sdk/openhands/sdk/settings/model.py Outdated
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig neubig added the review-this This label triggers a PR review by OpenHands label May 23, 2026 — with OpenHands AI
Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

🟡 Taste Rating: Acceptable, but I found a few correctness issues around OAuth token handling and device-login lifecycle.

Risk: 🟡 Medium — this touches auth/session state and LLM runtime wiring for long-running agents.
Verdict: COMMENT pending fixes.

This review was generated by an AI agent (OpenHands) on behalf of the user.


Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26337336844

Comment thread openhands-sdk/openhands/sdk/llm/auth/openai.py
Comment thread openhands-sdk/openhands/sdk/llm/auth/openai.py
Comment thread openhands-agent-server/openhands/agent_server/llm_router.py Outdated
Comment thread openhands-agent-server/openhands/agent_server/llm_router.py Outdated
Comment thread openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig neubig removed the review-this This label triggers a PR review by OpenHands label May 23, 2026
@neubig neubig added the review-this This label triggers a PR review by OpenHands label May 23, 2026 — with OpenHands AI
Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

🟡 Taste Rating: Acceptable, but I found a few correctness issues around subscription runtime state, device-login lifecycle, and profile switching.

Risk: 🟡 Medium — this touches auth/session state and long-running LLM/condenser behavior.
Verdict: COMMENT pending fixes.

This review was generated by an AI agent (OpenHands) on behalf of the user.


Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26338423527

Comment thread openhands-agent-server/openhands/agent_server/llm_router.py Outdated
Comment thread openhands-sdk/openhands/sdk/llm/llm.py Outdated
Comment thread openhands-sdk/openhands/sdk/llm/llm.py
Comment thread openhands-sdk/openhands/sdk/conversation/impl/local_conversation.py
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig neubig removed the review-this This label triggers a PR review by OpenHands label May 23, 2026
@neubig neubig added the review-this This label triggers a PR review by OpenHands label May 23, 2026 — with OpenHands AI
@neubig neubig removed the review-this This label triggers a PR review by OpenHands label May 23, 2026
@neubig neubig added the review-this This label triggers a PR review by OpenHands label May 23, 2026 — with OpenHands AI
@neubig neubig removed the review-this This label triggers a PR review by OpenHands label May 23, 2026
@neubig neubig added the review-this This label triggers a PR review by OpenHands label May 25, 2026
Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

🟡 Taste Rating: Acceptable, but I found a few correctness issues around subscription runtime resolution and credential refresh.

Risk: 🟡 Medium — this touches auth/session state and async LLM runtime behavior.
Verdict: COMMENT pending fixes.

This review was generated by an AI agent (OpenHands) on behalf of the user.


Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26411115492

Comment thread openhands-sdk/openhands/sdk/llm/auth/openai.py
Comment thread openhands-sdk/openhands/sdk/llm/llm.py
Comment thread openhands-sdk/openhands/sdk/llm/llm.py
Comment thread openhands-agent-server/openhands/agent_server/llm_router.py Outdated
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Copy link
Copy Markdown
Member Author

neubig commented May 28, 2026

Updated the SDK side through 3ff4784: merged current main, fixed the subscription runtime resolver/env loader/async refresh/status locking feedback, resolved review threads, and verified targeted tests locally. CI is running on the latest SHA.\n\nThis comment was generated by an AI agent (OpenHands) on behalf of neubig.

Copy link
Copy Markdown
Member Author

neubig commented May 28, 2026

CI is green on 3ff4784, all review threads are resolved, and this PR is ready for another review/merge gate pass.\n\nThis comment was generated by an AI agent (OpenHands) on behalf of neubig.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-this This label triggers a PR review by OpenHands

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants