Skip to content

Conversation

deyaaeldeen
Copy link
Contributor

  • I understand that this repository is auto-generated and my pull request may not be merged

Changes being requested

This PR introduces a new tokenProvider option to the OpenAI client, enabling dynamic token retrieval for authentication scenarios where API keys need to be refreshed or obtained at runtime.

Additional context & links

Changes

New Feature: tokenProvider Option

  • Added TokenProvider type definition: () => Promise<AccessToken>
  • Added AccessToken interface with token: string property
  • Added tokenProvider option to ClientOptions interface
  • Implemented token provider logic in the _setToken() method

Key Benefits

  1. Dynamic Authentication: Supports scenarios where tokens expire and need to be refreshed
  2. Enhanced Security: Tokens can be retrieved just-in-time rather than stored statically
  3. Enterprise Integration: Enables integration with corporate identity providers and token management systems
  4. Flexible Authentication: Provides an alternative to static API keys for environments requiring dynamic credentials

Implementation Details

Client Construction:

  • tokenProvider and apiKey are mutually exclusive - only one can be provided
  • If neither is provided, an error is thrown requiring one of the two authentication methods
  • Browser usage automatically allowed when using tokenProvider (sets dangerouslyAllowBrowser: true)

Token Management:

  • _setToken() method now returns a boolean indicating if a token was successfully retrieved
  • Token provider is called on every request preparation via prepareOptions()

Usage Example

const client = new OpenAI({
  tokenProvider: async () => {
    // Fetch token from your auth service
    const response = await fetch('/api/auth/token');
    const data = await response.json();
    return { token: data.access_token };
  }
});

Backward Compatibility

This change is fully backward compatible. Existing code using apiKey continues to work unchanged. The new tokenProvider option is purely additive.


@deyaaeldeen deyaaeldeen force-pushed the feat/token-provider branch 2 times, most recently from b4dd476 to f514473 Compare July 16, 2025 23:40
src/client.ts Outdated
@@ -438,6 +457,31 @@ export class OpenAI {
return Errors.APIError.generate(status, error, message, headers);
}

async _setToken(): Promise<boolean> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I think "refresh" is a better verb than "set" here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The decision to refresh is taken by the input function and not by this method, i.e. it is not guaranteed that the token will be refreshed if this method is called. However, let me know if you feel strongly about it and I can update the name.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I do dislike _set but don't feel strongly about it being called _refresh - I find it weird that a method called _setX() doesn't actually take any arguments.

fwiw I was also framing "refresh" as in, refresh the apiKey property - not necessarily do a full token refresh.

wdyt about _callApiKey()? I don't think that's great either... my main fud with _setApiKey() is that the return value is not intuitive imo and it'd be great if we could find a name that makes that work

@deyaaeldeen deyaaeldeen force-pushed the feat/token-provider branch 2 times, most recently from 1453757 to b623760 Compare August 22, 2025 18:09
export interface ClientOptions {
/**
* Defaults to process.env['OPENAI_API_KEY'].
*/
apiKey?: string | undefined;

apiKey?: string | ApiKeySetter | undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

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

The docstring should mention the new function behaviour

@@ -385,7 +386,7 @@ export class OpenAI {

this._options = options;

this.apiKey = apiKey;
this.apiKey = typeof apiKey === 'string' ? apiKey : 'Missing Key';
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggestion: should probably be something like <not set yet>?

Copy link
Collaborator

Choose a reason for hiding this comment

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

actually, we should move API_KEY_SENTINEL our of the azure specific file and use that

let fail = true;
const testFetch = async (url: RequestInfo, req: RequestInit | undefined): Promise<Response> => {
const testFetch = async (url: any, { headers }: RequestInit = {}): Promise<Response> => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

q: why did you have to use any?

src/client.ts Outdated
@@ -438,6 +457,31 @@ export class OpenAI {
return Errors.APIError.generate(status, error, message, headers);
}

async _setToken(): Promise<boolean> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do dislike _set but don't feel strongly about it being called _refresh - I find it weird that a method called _setX() doesn't actually take any arguments.

fwiw I was also framing "refresh" as in, refresh the apiKey property - not necessarily do a full token refresh.

wdyt about _callApiKey()? I don't think that's great either... my main fud with _setApiKey() is that the return value is not intuitive imo and it'd be great if we could find a name that makes that work

@@ -49,6 +50,10 @@ export class OpenAIRealtimeWebSocket extends OpenAIRealtimeEmitter {

client ??= new OpenAI({ dangerouslyAllowBrowser });

if (typeof (client as any)?._options?.apiKey !== 'string') {
throw new Error('Call the create method instead to construct the client');
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: this error message could be more helpful

Copy link
Collaborator

Choose a reason for hiding this comment

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

also, wait does this work? because _setApiKey() doesn't mutate options, won't this check always return true if you pass in a function?

should we instead define the client.apiKey fallback string as a constant and check that? e.g. if (client.apiKey === API_KEY_MISSING) { ...

Comment on lines +460 to +478
try {
const token = await apiKey();
if (!token || typeof token !== 'string') {
throw new Errors.OpenAIError(
`Expected 'apiKey' function argument to return a string but it returned ${token}`,
);
}
this.apiKey = token;
return true;
} catch (err: any) {
if (err instanceof Errors.OpenAIError) {
throw err;
}
throw new Errors.OpenAIError(
`Failed to get token from 'apiKey' function: ${err.message}`,
// @ts-ignore
{ cause: err },
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

having a try catch around the whole block and then checking inside the catch if we threw isn't great, could we instead just either put the try catch just around the await apiKey()? or await apiKey().catch(...)?

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