Skip to content

Add LLM subscription client methods#178

Open
neubig wants to merge 1 commit into
mainfrom
add-llm-subscription-client
Open

Add LLM subscription client methods#178
neubig wants to merge 1 commit 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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants