-
Notifications
You must be signed in to change notification settings - Fork 3
Description
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.0openai: 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.