Skip to content

Add LLM subscription client methods#178

Open
neubig wants to merge 2 commits into
mainfrom
add-llm-subscription-client
Open

Add LLM subscription client methods#178
neubig wants to merge 2 commits into
mainfrom
add-llm-subscription-client

Conversation

@neubig
Copy link
Copy Markdown
Member

@neubig neubig commented May 23, 2026

Summary

  • add typed response models for LLM subscription auth endpoints
  • add OpenAI subscription status, model list, device start/poll, and logout helpers to LLMMetadataClient
  • cover endpoint paths and token non-exposure in API client tests

Tests

  • npm test -- --runTestsByPath src/tests/api-clients.test.ts --runInBand
  • npm run build

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

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

@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.

🟡 Acceptable - Implementation is clean and follows existing patterns, but has missing test coverage and type inconsistencies that should be addressed.


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

);
});

it('LLMMetadataClient calls OpenAI subscription endpoints without exposing tokens', async () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Important: Missing test coverage for getOpenAISubscriptionModels().

The implementation adds 5 methods but this test only exercises 4. Add a test case for getOpenAISubscriptionModels() to verify it correctly calls GET /api/llm/subscription/openai/models and returns the models array.

Suggested change
it('LLMMetadataClient calls OpenAI subscription endpoints without exposing tokens', async () => {
it('LLMMetadataClient.getOpenAISubscriptionModels() returns models array', async () => {
global.fetch = jest.fn().mockResolvedValue(
new Response(JSON.stringify({ vendor: 'openai', models: ['gpt-4', 'gpt-3.5-turbo'] }), {
status: 200,
headers: { 'content-type': 'application/json' },
})
);
const client = new LLMMetadataClient({ host: 'http://example.com', apiKey: 'secret' });
const models = await client.getOpenAISubscriptionModels();
expect(models).toEqual(['gpt-4', 'gpt-3.5-turbo']);
expect(global.fetch).toHaveBeenCalledWith(
'http://example.com/api/llm/subscription/openai/models',
expect.objectContaining({ method: 'GET' })
);
});
it('LLMMetadataClient calls OpenAI subscription endpoints without exposing tokens', async () => {

Comment thread src/models/api.ts
Comment on lines +33 to +38
export interface LLMSubscriptionStatusResponse {
vendor: string;
connected: boolean;
account_email?: string | null;
expires_at?: number | string | null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: Type inconsistencies in optional fields.

  1. expires_at: number | string | null - Mixing number and string types creates ambiguity. Pick one:

    • If it's always a timestamp milliseconds: expires_at?: number | null
    • If it's always an ISO date string: expires_at?: string | null
  2. Optional + nullable redundancy - Using both ?: and | null is redundant:

    • account_email?: string | null means "optional, and when present can be null or string"
    • Better: account_email: string | null (always present but nullable) OR account_email?: string (optional but never explicitly null)

Check what the agent-server actually returns and match that exactly.

Comment thread src/models/api.ts
Comment on lines +40 to +47
export interface LLMSubscriptionDeviceStartResponse {
device_code: string;
user_code: string;
verification_uri: string;
verification_uri_complete?: string | null;
expires_at: number | string;
interval_seconds: number;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: Same type inconsistency as above.

  • verification_uri_complete?: string | null - Choose one: optional OR nullable, not both
  • expires_at: number | string - Pick a single type (number for timestamp milliseconds, or string for ISO date)

Consistency across these subscription response types will prevent runtime type confusion.

Copy link
Copy Markdown

@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.

🟡 Acceptable - Implementation is clean and follows existing patterns. Previous test coverage gap has been addressed. One minor type consistency issue remains.

[TESTING COVERAGE]
✅ All 5 methods now have test coverage (getOpenAISubscriptionModels test added)
✅ Token non-exposure verified
✅ Endpoint paths verified

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟢 LOW

Adding new client methods to existing LLMMetadataClient with comprehensive test coverage. No breaking changes to existing functionality. Standard CRUD operations following established patterns.

VERDICT:
Worth merging: Core implementation is solid, test coverage complete, only minor type style improvement suggested

KEY INSIGHT:
Previous review feedback has been addressed - missing test coverage added and type definitions improved from number | string | null to number | null.


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

Comment thread src/models/api.ts
device_code: string;
user_code: string;
verification_uri: string;
verification_uri_complete: string | null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: Use optional field syntax for idiomatic TypeScript.

Change:

verification_uri_complete: string | null;

To:

verification_uri_complete?: string;

Per OAuth 2.0 Device Authorization Grant (RFC 8628), verification_uri_complete is an optional field. TypeScript convention is to use ? for optional fields rather than | null. This makes the type more idiomatic and clearer to consumers.

Note: If the backend API explicitly returns null (rather than omitting the field), the current typing is technically correct but less idiomatic.

Comment thread src/models/api.ts
device_code: string;
user_code: string;
verification_uri: string;
verification_uri_complete: string | null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Suggestion: Use optional field syntax for idiomatic TypeScript.

Change:

verification_uri_complete: string | null;

To:

verification_uri_complete?: string;

Per OAuth 2.0 Device Authorization Grant (RFC 8628), verification_uri_complete is an optional field. TypeScript convention is to use ? for optional fields rather than | null. This makes the type more idiomatic and clearer to consumers.

Note: If the backend API explicitly returns null (rather than omitting the field), the current typing is technically correct but less idiomatic.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants