Skip to content

wrapOpenAI Proxy breaks OpenAI SDK methods that use native private fields (buildURL, buildRequest) #1693

@tomquist

Description

@tomquist

Summary

wrapOpenAI returns a Proxy whose get trap passes receiver (the Proxy itself) through to Reflect.get. When the OpenAI SDK's buildURL or buildRequest methods are called on the wrapped client, they throw:

TypeError: Cannot read private member from an object whose class did not declare it

This happens because the OpenAI SDK (v6.x) uses native ES private class fields internally in these methods. Private fields perform a brand check on this, and when this is the Proxy rather than the original OpenAI instance, the check fails.

Reproduction

import OpenAI from "openai";
import { wrapOpenAI } from "braintrust";

const client = new OpenAI({ apiKey: "test-key" });
const wrapped = wrapOpenAI(client);

// These both throw: "Cannot read private member from an object whose class did not declare it"
wrapped.buildURL("/files", null);
await wrapped.buildRequest({ method: "post", path: "/files" }, { retryCount: 0 });

// This works fine (underscore-prefixed, not a real ES private field):
await wrapped._callApiKey();

Root cause

In wrapOpenAIv4, the top-level Proxy's get trap is:

return new Proxy(typedOpenai, {
  get(target, name, receiver) {
    switch (name) {
      case "chat": return chatProxy;
      case "embeddings": return embeddingProxy;
      // ...
    }
    return Reflect.get(target, name, receiver);
    //                                ^^^^^^^^ should be `target`
  }
});

For non-intercepted property names, Reflect.get(target, name, receiver) sets this to receiver (the Proxy) when evaluating getters or calling methods. Since buildURL and buildRequest access native private fields (#field syntax), and the Proxy is not the original class instance, the private field brand check fails.

Suggested fix

For non-intercepted properties, use target instead of receiver:

return Reflect.get(target, name, target);

Or alternatively, bind methods to the original target when returning them.

Versions

  • braintrust: 3.5.0
  • openai: 6.27.0
  • Node.js: v24.x

Impact

Any code that calls buildURL, buildRequest, or other SDK methods using native private fields on a wrapOpenAI-wrapped client will throw at runtime. We hit this when extracting auth headers and the API URL from the client for a custom streaming upload that bypasses the SDK's buffering behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions