-
Notifications
You must be signed in to change notification settings - Fork 45
Add ChatGPT subscription LLM support #744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
06f18ec
Add ChatGPT subscription LLM support
openhands-agent 83e343c
Use typed LLM subscription client
openhands-agent b89360b
Fix subscription model list and device code UX
openhands-agent 25a3db6
Add gpt-5.5 (and gpt-5.4/5.4-pro) to subscription model fallback list
openhands-agent 47bbca5
Simplify subscription model source to chatgpt/ provider only
openhands-agent 1e94188
Merge main and address subscription review feedback
openhands-agent 5780fb7
test: link tracking issue #917 to typescript-client git-pin exemption
openhands-agent 3da19b0
Address subscription settings review feedback
openhands-agent 65e1712
Add workspace mock handlers for snapshots
openhands-agent e5e5bff
Mock MCP test endpoint in snapshot tests
openhands-agent 13ede8a
Stabilize home snapshot waits
openhands-agent 97a8d32
Fix subscription settings dark theme contrast
openhands-agent 6a5799a
Merge remote-tracking branch 'origin/main' into add-llm-subscription-…
openhands-agent 5ca3e55
Merge remote-tracking branch 'origin/main' into add-llm-subscription-…
openhands-agent d7ce01d
Merge main into subscription LLM PR
neubig 581fc5c
Format subscription service
neubig 254aaba
Merge branch 'main' into add-llm-subscription-support
neubig 1cdaf9f
Fix subscription settings CI coverage
neubig 9a20a11
Merge branch 'main' into add-llm-subscription-support
neubig 909082f
Guard subscription model loading
neubig 358faba
Auto-poll subscription device login
neubig f96869d
Format subscription auth auto-poll changes
neubig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import { http, HttpResponse } from "msw"; | ||
| import { beforeEach, describe, expect, it } from "vitest"; | ||
| import LLMSubscriptionService from "#/api/llm-subscription-service"; | ||
| import { | ||
| OPENAI_SUBSCRIPTION_DEVICE_START_PATH, | ||
| OPENAI_SUBSCRIPTION_STATUS_PATH, | ||
| } from "#/constants/llm-subscription"; | ||
| import { server } from "#/mocks/node"; | ||
| import { resetTestHandlersMockSettings } from "#/mocks/settings-handlers"; | ||
|
|
||
| describe("LLMSubscriptionService", () => { | ||
| beforeEach(() => { | ||
| resetTestHandlersMockSettings(); | ||
| }); | ||
|
|
||
| it("fetches OpenAI subscription models from the agent-server endpoint", async () => { | ||
| await expect(LLMSubscriptionService.getOpenAIModels()).resolves.toEqual([ | ||
| "gpt-5.2", | ||
| "gpt-5.3-codex", | ||
| ]); | ||
| }); | ||
|
|
||
| it("normalizes OpenAI subscription status from MSW handlers", async () => { | ||
| await expect(LLMSubscriptionService.getOpenAIStatus()).resolves.toEqual({ | ||
| vendor: "openai", | ||
| connected: false, | ||
| accountEmail: null, | ||
| expiresAt: null, | ||
| }); | ||
| }); | ||
|
|
||
| it("normalizes device login challenge responses", async () => { | ||
| await expect( | ||
| LLMSubscriptionService.startOpenAIDeviceLogin(), | ||
| ).resolves.toEqual({ | ||
| deviceCode: "mock-device-code", | ||
| userCode: "MOCK-CODE", | ||
| verificationUri: "https://auth.openai.com/activate", | ||
| verificationUriComplete: | ||
| "https://auth.openai.com/activate?user_code=MOCK-CODE", | ||
| expiresAt: 900, | ||
| intervalSeconds: 1, | ||
| }); | ||
| }); | ||
|
|
||
| it("posts the device code when polling login", async () => { | ||
| await expect( | ||
| LLMSubscriptionService.pollOpenAIDeviceLogin("mock-device-code"), | ||
| ).resolves.toMatchObject({ connected: true }); | ||
|
|
||
| await expect( | ||
| LLMSubscriptionService.getOpenAIStatus(), | ||
| ).resolves.toMatchObject({ | ||
| connected: true, | ||
| accountEmail: "mock-chatgpt@example.com", | ||
| }); | ||
| }); | ||
|
|
||
| it("calls the logout endpoint", async () => { | ||
| await LLMSubscriptionService.pollOpenAIDeviceLogin("mock-device-code"); | ||
|
|
||
| await expect(LLMSubscriptionService.logoutOpenAI()).resolves.toMatchObject({ | ||
| connected: false, | ||
| }); | ||
| await expect( | ||
| LLMSubscriptionService.getOpenAIStatus(), | ||
| ).resolves.toMatchObject({ connected: false }); | ||
| }); | ||
|
|
||
| it("rejects incomplete device challenges with blank required fields", async () => { | ||
| server.use( | ||
| http.post(`*${OPENAI_SUBSCRIPTION_DEVICE_START_PATH}`, () => | ||
| HttpResponse.json({ | ||
| device_code: " ", | ||
| user_code: "MOCK-CODE", | ||
| verification_uri: "https://auth.openai.com/activate", | ||
| }), | ||
| ), | ||
| ); | ||
|
|
||
| await expect( | ||
| LLMSubscriptionService.startOpenAIDeviceLogin(), | ||
| ).rejects.toThrow("Subscription device login response is incomplete"); | ||
| }); | ||
|
|
||
| it("surfaces agent-server errors", async () => { | ||
| server.use( | ||
| http.get(`*${OPENAI_SUBSCRIPTION_STATUS_PATH}`, () => | ||
| HttpResponse.json({ detail: "unauthorized" }, { status: 401 }), | ||
| ), | ||
| ); | ||
|
|
||
| await expect(LLMSubscriptionService.getOpenAIStatus()).rejects.toThrow(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.