Skip to content

Commit c0f0375

Browse files
authored
fix(node): Fix this context for vercel AI instrumentation (#17681)
Noticed while working on #17679, we incorrectly changed the `this` context here (which is also why TS complained). I rewrote this to a proxy which should keep the context properly.
1 parent 4756112 commit c0f0375

File tree

3 files changed

+60
-59
lines changed

3 files changed

+60
-59
lines changed

packages/node/src/integrations/tracing/anthropic-ai/instrumentation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
InstrumentationNodeModuleDefinition,
66
} from '@opentelemetry/instrumentation';
77
import type { AnthropicAiClient, AnthropicAiOptions, Integration } from '@sentry/core';
8-
import { ANTHROPIC_AI_INTEGRATION_NAME, getCurrentScope, instrumentAnthropicAiClient, SDK_VERSION } from '@sentry/core';
8+
import { ANTHROPIC_AI_INTEGRATION_NAME, getClient, instrumentAnthropicAiClient, SDK_VERSION } from '@sentry/core';
99

1010
const supportedVersions = ['>=0.19.2 <1.0.0'];
1111

@@ -61,10 +61,10 @@ export class SentryAnthropicAiInstrumentation extends InstrumentationBase<Instru
6161

6262
const WrappedAnthropic = function (this: unknown, ...args: unknown[]) {
6363
const instance = Reflect.construct(Original, args);
64-
const scopeClient = getCurrentScope().getClient();
65-
const integration = scopeClient?.getIntegrationByName<AnthropicAiIntegration>(ANTHROPIC_AI_INTEGRATION_NAME);
64+
const client = getClient();
65+
const integration = client?.getIntegrationByName<AnthropicAiIntegration>(ANTHROPIC_AI_INTEGRATION_NAME);
6666
const integrationOpts = integration?.options;
67-
const defaultPii = Boolean(scopeClient?.getOptions().sendDefaultPii);
67+
const defaultPii = Boolean(client?.getOptions().sendDefaultPii);
6868

6969
const { recordInputs, recordOutputs } = determineRecordingSettings(integrationOpts, defaultPii);
7070

packages/node/src/integrations/tracing/openai/instrumentation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
InstrumentationNodeModuleDefinition,
66
} from '@opentelemetry/instrumentation';
77
import type { Integration, OpenAiClient, OpenAiOptions } from '@sentry/core';
8-
import { getCurrentScope, instrumentOpenAiClient, OPENAI_INTEGRATION_NAME, SDK_VERSION } from '@sentry/core';
8+
import { getClient, instrumentOpenAiClient, OPENAI_INTEGRATION_NAME, SDK_VERSION } from '@sentry/core';
99

1010
const supportedVersions = ['>=4.0.0 <6'];
1111

@@ -57,10 +57,10 @@ export class SentryOpenAiInstrumentation extends InstrumentationBase<Instrumenta
5757

5858
const WrappedOpenAI = function (this: unknown, ...args: unknown[]) {
5959
const instance = Reflect.construct(Original, args);
60-
const scopeClient = getCurrentScope().getClient();
61-
const integration = scopeClient?.getIntegrationByName<OpenAiIntegration>(OPENAI_INTEGRATION_NAME);
60+
const client = getClient();
61+
const integration = client?.getIntegrationByName<OpenAiIntegration>(OPENAI_INTEGRATION_NAME);
6262
const integrationOpts = integration?.options;
63-
const defaultPii = Boolean(scopeClient?.getOptions().sendDefaultPii);
63+
const defaultPii = Boolean(client?.getOptions().sendDefaultPii);
6464

6565
const { recordInputs, recordOutputs } = determineRecordingSettings(integrationOpts, defaultPii);
6666

packages/node/src/integrations/tracing/vercelai/instrumentation.ts

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
addNonEnumerableProperty,
88
captureException,
99
getActiveSpan,
10-
getCurrentScope,
10+
getClient,
1111
handleCallbackErrors,
1212
isThenable,
1313
SDK_VERSION,
@@ -212,57 +212,58 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase {
212212
this._callbacks.forEach(callback => callback());
213213
this._callbacks = [];
214214

215-
function generatePatch(originalMethod: (...args: MethodArgs) => unknown) {
216-
return (...args: MethodArgs) => {
217-
const existingExperimentalTelemetry = args[0].experimental_telemetry || {};
218-
const isEnabled = existingExperimentalTelemetry.isEnabled;
219-
220-
const client = getCurrentScope().getClient();
221-
const integration = client?.getIntegrationByName<VercelAiIntegration>(INTEGRATION_NAME);
222-
const integrationOptions = integration?.options;
223-
const shouldRecordInputsAndOutputs = integration ? Boolean(client?.getOptions().sendDefaultPii) : false;
224-
225-
const { recordInputs, recordOutputs } = determineRecordingSettings(
226-
integrationOptions,
227-
existingExperimentalTelemetry,
228-
isEnabled,
229-
shouldRecordInputsAndOutputs,
230-
);
231-
232-
args[0].experimental_telemetry = {
233-
...existingExperimentalTelemetry,
234-
isEnabled: isEnabled !== undefined ? isEnabled : true,
235-
recordInputs,
236-
recordOutputs,
237-
};
238-
239-
return handleCallbackErrors(
240-
() => {
241-
// @ts-expect-error we know that the method exists
242-
const result = originalMethod.apply(this, args);
243-
244-
if (isThenable(result)) {
245-
// check for tool errors when the promise resolves, keep the original promise identity
246-
result.then(checkResultForToolErrors, () => {});
215+
const generatePatch = <T extends (...args: MethodArgs) => unknown>(originalMethod: T): T => {
216+
return new Proxy(originalMethod, {
217+
apply: (target, thisArg, args: MethodArgs) => {
218+
const existingExperimentalTelemetry = args[0].experimental_telemetry || {};
219+
const isEnabled = existingExperimentalTelemetry.isEnabled;
220+
221+
const client = getClient();
222+
const integration = client?.getIntegrationByName<VercelAiIntegration>(INTEGRATION_NAME);
223+
const integrationOptions = integration?.options;
224+
const shouldRecordInputsAndOutputs = integration ? Boolean(client?.getOptions().sendDefaultPii) : false;
225+
226+
const { recordInputs, recordOutputs } = determineRecordingSettings(
227+
integrationOptions,
228+
existingExperimentalTelemetry,
229+
isEnabled,
230+
shouldRecordInputsAndOutputs,
231+
);
232+
233+
args[0].experimental_telemetry = {
234+
...existingExperimentalTelemetry,
235+
isEnabled: isEnabled !== undefined ? isEnabled : true,
236+
recordInputs,
237+
recordOutputs,
238+
};
239+
240+
return handleCallbackErrors(
241+
() => {
242+
const result = Reflect.apply(target, thisArg, args);
243+
244+
if (isThenable(result)) {
245+
// check for tool errors when the promise resolves, keep the original promise identity
246+
result.then(checkResultForToolErrors, () => {});
247+
return result;
248+
}
249+
250+
// check for tool errors when the result is synchronous
251+
checkResultForToolErrors(result);
247252
return result;
248-
}
249-
250-
// check for tool errors when the result is synchronous
251-
checkResultForToolErrors(result);
252-
return result;
253-
},
254-
error => {
255-
// This error bubbles up to unhandledrejection handler (if not handled before),
256-
// where we do not know the active span anymore
257-
// So to circumvent this, we set the active span on the error object
258-
// which is picked up by the unhandledrejection handler
259-
if (error && typeof error === 'object') {
260-
addNonEnumerableProperty(error, '_sentry_active_span', getActiveSpan());
261-
}
262-
},
263-
);
264-
};
265-
}
253+
},
254+
error => {
255+
// This error bubbles up to unhandledrejection handler (if not handled before),
256+
// where we do not know the active span anymore
257+
// So to circumvent this, we set the active span on the error object
258+
// which is picked up by the unhandledrejection handler
259+
if (error && typeof error === 'object') {
260+
addNonEnumerableProperty(error, '_sentry_active_span', getActiveSpan());
261+
}
262+
},
263+
);
264+
},
265+
});
266+
};
266267

267268
// Is this an ESM module?
268269
// https://tc39.es/ecma262/#sec-module-namespace-objects

0 commit comments

Comments
 (0)