From 0ca06555126ca2bfdb957a88e94e16b5e767b3c6 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 4 Feb 2026 22:13:18 -0600 Subject: [PATCH] Pass forward dangerouslyAllowBrowser to clean Client construction --- js/oai.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ js/oai.ts | 20 +++++++++++++++++--- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/js/oai.test.ts b/js/oai.test.ts index abf0d59..3252671 100644 --- a/js/oai.test.ts +++ b/js/oai.test.ts @@ -227,6 +227,45 @@ describe("OAI", () => { }); }); + test("wraps client with dangerouslyAllowBrowser", async () => { + server.use( + http.post("https://api.openai.com/v1/chat/completions", () => { + return HttpResponse.json(MOCK_OPENAI_COMPLETION_RESPONSE); + }), + ); + + // Create a subclass that throws if dangerouslyAllowBrowser is not passed + // This simulates browser environment behavior + class BrowserOpenAI extends OpenAI { + constructor(opts: ConstructorParameters[0]) { + if (!opts?.dangerouslyAllowBrowser) { + throw new Error( + "dangerouslyAllowBrowser must be set in browser environment", + ); + } + super(opts); + } + } + + await withMockWrapper(async ({ createSpy }) => { + const client = new BrowserOpenAI({ + apiKey: "test-api-key", + dangerouslyAllowBrowser: true, + }); + + // This would throw before the fix, because isWrapped() was not + // passing dangerouslyAllowBrowser when constructing the clean client + const builtClient = buildOpenAIClient({ client }); + + await builtClient.chat.completions.create({ + model: "gpt-4", + messages: [{ role: "user", content: "Hello" }], + }); + + expect(createSpy).toHaveBeenCalledTimes(1); + }); + }); + test("init sets client", async () => { server.use( http.post("https://api.openai.com/v1/chat/completions", () => { diff --git a/js/oai.ts b/js/oai.ts index 268908f..86d1e09 100644 --- a/js/oai.ts +++ b/js/oai.ts @@ -127,9 +127,15 @@ const resolveOpenAIClient = (options: OpenAIAuth): OpenAI => { }); }; -const isWrapped = (client: OpenAI): boolean => { +const isWrapped = ( + client: OpenAI, + dangerouslyAllowBrowser?: boolean, +): boolean => { const Constructor = Object.getPrototypeOf(client).constructor; - const clean = new Constructor({ apiKey: "dummy" }); + const clean = new Constructor({ + apiKey: "dummy", + dangerouslyAllowBrowser, + }); return ( String(client.chat.completions.create) !== String(clean.chat.completions.create) @@ -139,8 +145,16 @@ const isWrapped = (client: OpenAI): boolean => { export function buildOpenAIClient(options: OpenAIAuth): OpenAI { const client = resolveOpenAIClient(options); + // Extract from deprecated options or client instance + const dangerouslyAllowBrowser = + options.openAiDangerouslyAllowBrowser ?? + (client as any)._options?.dangerouslyAllowBrowser; + // avoid re-wrapping if the client is already wrapped (proxied) - if (globalThis.__inherited_braintrust_wrap_openai && !isWrapped(client)) { + if ( + globalThis.__inherited_braintrust_wrap_openai && + !isWrapped(client, dangerouslyAllowBrowser) + ) { return globalThis.__inherited_braintrust_wrap_openai(client); }