Skip to content

Crash with Vercel AI SDK when used in ESM #16137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
3 tasks done
Jaakkonen opened this issue Apr 25, 2025 · 8 comments · Fixed by #16152
Closed
3 tasks done

Crash with Vercel AI SDK when used in ESM #16137

Jaakkonen opened this issue Apr 25, 2025 · 8 comments · Fixed by #16152
Assignees

Comments

@Jaakkonen
Copy link

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

9.14.0

Framework Version

No response

Link to Sentry event

No response

Reproduction Example/SDK Setup

https://github.com/Jaakkonen/sentry-spotlight-vercel-ai-bug

Steps to Reproduce

  1. Create a new Node.js project
  2. Install the required dependencies:
    npm install @sentry/node ai
    
  3. Create the following files:
// index.js
import * as Sentry from '@sentry/node'

Sentry.init({
  tracesSampler: (ctx) => {
    return 0.01
  },
  spotlight: true,
})

await import("./otherfile.js")
// otherfile.js
import { generateText } from "ai";

console.log(generateText)
  1. Run the application with:
    $ node index.js
    

Expected Result

Code to not crash. With spotlight disabled the code runs fine

[AsyncFunction: generateText]

Actual Result

❯ node index.js
TypeError: setters.get(...)[name] is not a function
    at Object.set (/home/jaakko/Code/opentelemetryairepro/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/register.js:13:37)
    at callHookFn (/home/jaakko/Code/opentelemetryairepro/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/index.js:31:23)
    at Hook._iitmHook (/home/jaakko/Code/opentelemetryairepro/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/index.js:150:11)
    at /home/jaakko/Code/opentelemetryairepro/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/register.js:42:31
    at Array.forEach (<anonymous>)
    at register (/home/jaakko/Code/opentelemetryairepro/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/register.js:42:15)
    at file:///home/jaakko/Code/opentelemetryairepro/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/ai/dist/index.mjs?iitm=true:1075:1
    at ModuleJob.run (node:internal/modules/esm/module_job:271:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:578:26)
    at async file:///home/jaakko/Code/opentelemetryairepro/index.js:10:1
@getsantry getsantry bot moved this to Waiting for: Product Owner in GitHub Issues with 👀 3 Apr 25, 2025
@linear linear bot added the Bug label Apr 25, 2025
@Lms24 Lms24 self-assigned this Apr 28, 2025
@Lms24
Copy link
Member

Lms24 commented Apr 28, 2025

Hey, thanks for writing in and for the reproduction! I'll take a look and might forward the issue to the Spotlight team, depending on results :)

@Lms24
Copy link
Member

Lms24 commented Apr 28, 2025

Some initial findings:

I was able to further narrow this down to the following SDK config:

import * as Sentry from '@sentry/node'

Sentry.init({
  dsn: '_any_valid_dsn_',
  defaultIntegrations: false,
  integrations: [
    Sentry.vercelAIIntegration(),
  ],
})

await import("./otherfile.js") // which imports 'ai'

which means that my suspicion as of this moment is that something breaks as soon as Sentry imports from node:http.

This happens

  • when you specify a dsn as then we initialize the Sentry transport which uses node:http to send events to Sentry
  • when you add spotlightIntegration which uses node:http to send events to Spotlight

I still have to find out why exactly this is happening but I'd bet on import-in-the-middle patching doing something weird with ai~ornode:http`~ .

UPDATE: node:http has nothing to do with this, see #16137 (comment)

Some more questions:

  • did this start happening after a specific change you made? Maybe you bumped a package version or the node runtime?

@Lms24
Copy link
Member

Lms24 commented Apr 28, 2025

I gotta stand corrected: The reason why both, setting dsn or adding spotlightIntegration triggers the bug is because in both cases, we call setupOnce of all integrations (so also of the vercelAi integration).

So my now changed slightly: I think patching the ai package in general throws this error in ESM. So neither node:http nor spotlightIntegration are factors in this.

@Lms24
Copy link
Member

Lms24 commented Apr 28, 2025

Okay, so I debugged this a bit further and created a new, reproduction. One that takes the general Sentry SDK out of the equation but only has 3 important components:

  • registering import-in-the-middle/hooks.mjs
  • Initializing Sentry's vercelAiIntegration
  • importing ai in a dynamic import

Here's a new reproduction (thanks @Jaakkonen for the original one! I based mine on yours): https://github.com/Lms24/gh-sentry-javascript-16137-vercel-ai-esm

I threw in a couple of logs in the IITM method where the error is thrown:

  set (target, name, value) {
    console.trace('set');
    console.log('> target', target);
    console.log('> name', name);
    console.log('> value', value);
    console.log('> setters', setters);
    console.log('> setters.get(target)', setters.get(target));
    console.log('> setters.get(target)[name]', setters.get(target)[name]);
    return setters.get(target)[name](value)
  },

and I'm getting the following output:

Log Output
Trace: set
    at Object.set (/Users/lukas/code/test-projects/gh-sentry-javascript-16137/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/register.js:13:13)
    at callHookFn (/Users/lukas/code/test-projects/gh-sentry-javascript-16137/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/index.js:31:23)
    at Hook._iitmHook (/Users/lukas/code/test-projects/gh-sentry-javascript-16137/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/index.js:150:11)
    at /Users/lukas/code/test-projects/gh-sentry-javascript-16137/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/register.js:50:31
    at Array.forEach (<anonymous>)
    at register (/Users/lukas/code/test-projects/gh-sentry-javascript-16137/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/register.js:50:15)
    at file:///Users/lukas/code/test-projects/gh-sentry-javascript-16137/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/ai/dist/index.mjs?iitm=true:655:1
    at ModuleJob.run (node:internal/modules/esm/module_job:274:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:644:26)
    at async file:///Users/lukas/code/test-projects/gh-sentry-javascript-16137/index.js:17:1
> target [Object: null prototype] [Module] {
  AISDKError: [class _AISDKError extends Error],
  APICallError: [class APICallError extends _AISDKError],
  AssistantResponse: [Function: AssistantResponse],
  DownloadError: [class DownloadError extends _AISDKError],
  EmptyResponseBodyError: [class EmptyResponseBodyError extends _AISDKError],
  InvalidArgumentError: [class InvalidArgumentError extends _AISDKError],
  InvalidDataContentError: [class InvalidDataContentError extends _AISDKError],
  InvalidMessageRoleError: [class InvalidMessageRoleError extends _AISDKError],
  InvalidPromptError: [class InvalidPromptError extends _AISDKError],
  InvalidResponseDataError: [class InvalidResponseDataError extends _AISDKError],
  InvalidToolArgumentsError: [class InvalidToolArgumentsError extends _AISDKError],
  JSONParseError: [class JSONParseError extends _AISDKError],
  LangChainAdapter: { toDataStream: [Getter], toDataStreamResponse: [Getter] },
  LlamaIndexAdapter: { toDataStream: [Getter], toDataStreamResponse: [Getter] },
  LoadAPIKeyError: [class LoadAPIKeyError extends _AISDKError],
  MessageConversionError: [class MessageConversionError extends _AISDKError],
  NoContentGeneratedError: [class NoContentGeneratedError extends _AISDKError],
  NoObjectGeneratedError: [class NoObjectGeneratedError extends _AISDKError],
  NoSuchModelError: [class NoSuchModelError extends _AISDKError],
  NoSuchProviderError: [class NoSuchProviderError extends NoSuchModelError],
  NoSuchToolError: [class NoSuchToolError extends _AISDKError],
  RetryError: [class RetryError extends _AISDKError],
  StreamData: [class StreamData],
  TypeValidationError: [class _TypeValidationError extends _AISDKError],
  UnsupportedFunctionalityError: [class UnsupportedFunctionalityError extends _AISDKError],
  convertToCoreMessages: [Function: convertToCoreMessages],
  cosineSimilarity: [Function: cosineSimilarity],
  createStreamDataTransformer: [Function: createStreamDataTransformer],
  embed: [AsyncFunction: embed],
  embedMany: [AsyncFunction: embedMany],
  experimental_createProviderRegistry: [Function: experimental_createProviderRegistry],
  experimental_customProvider: [Function: experimental_customProvider],
  experimental_wrapLanguageModel: [Function: experimental_wrapLanguageModel],
  formatAssistantStreamPart: [Function: formatAssistantStreamPart],
  formatDataStreamPart: [Function: formatDataStreamPart],
  generateId: [Function (anonymous)],
  generateObject: [AsyncFunction: generateObject],
  generateText: [AsyncFunction: generateText],
  jsonSchema: [Function: jsonSchema],
  parseAssistantStreamPart: [Function: parseAssistantStreamPart],
  parseDataStreamPart: [Function: parseDataStreamPart],
  processDataStream: [AsyncFunction: processDataStream],
  processTextStream: [AsyncFunction: processTextStream],
  streamObject: [Function: streamObject],
  streamText: [Function: streamText],
  tool: [Function: tool]
}
> name default
> value {
  AISDKError: [class _AISDKError extends Error],
  APICallError: [class APICallError extends _AISDKError],
  AssistantResponse: [Function: AssistantResponse],
  DownloadError: [class DownloadError extends _AISDKError],
  EmptyResponseBodyError: [class EmptyResponseBodyError extends _AISDKError],
  InvalidArgumentError: [class InvalidArgumentError extends _AISDKError],
  InvalidDataContentError: [class InvalidDataContentError extends _AISDKError],
  InvalidMessageRoleError: [class InvalidMessageRoleError extends _AISDKError],
  InvalidPromptError: [class InvalidPromptError extends _AISDKError],
  InvalidResponseDataError: [class InvalidResponseDataError extends _AISDKError],
  InvalidToolArgumentsError: [class InvalidToolArgumentsError extends _AISDKError],
  JSONParseError: [class JSONParseError extends _AISDKError],
  LangChainAdapter: { toDataStream: [Getter], toDataStreamResponse: [Getter] },
  LlamaIndexAdapter: { toDataStream: [Getter], toDataStreamResponse: [Getter] },
  LoadAPIKeyError: [class LoadAPIKeyError extends _AISDKError],
  MessageConversionError: [class MessageConversionError extends _AISDKError],
  NoContentGeneratedError: [class NoContentGeneratedError extends _AISDKError],
  NoObjectGeneratedError: [class NoObjectGeneratedError extends _AISDKError],
  NoSuchModelError: [class NoSuchModelError extends _AISDKError],
  NoSuchProviderError: [class NoSuchProviderError extends NoSuchModelError],
  NoSuchToolError: [class NoSuchToolError extends _AISDKError],
  RetryError: [class RetryError extends _AISDKError],
  StreamData: [class StreamData],
  TypeValidationError: [class _TypeValidationError extends _AISDKError],
  UnsupportedFunctionalityError: [class UnsupportedFunctionalityError extends _AISDKError],
  convertToCoreMessages: [Function: convertToCoreMessages],
  cosineSimilarity: [Function: cosineSimilarity],
  createStreamDataTransformer: [Function: createStreamDataTransformer],
  embed: [Function (anonymous)],
  embedMany: [Function (anonymous)],
  experimental_createProviderRegistry: [Function: experimental_createProviderRegistry],
  experimental_customProvider: [Function: experimental_customProvider],
  experimental_wrapLanguageModel: [Function: experimental_wrapLanguageModel],
  formatAssistantStreamPart: [Function: formatAssistantStreamPart],
  formatDataStreamPart: [Function: formatDataStreamPart],
  generateId: [Function (anonymous)],
  generateObject: [Function (anonymous)],
  generateText: [Function (anonymous)],
  jsonSchema: [Function: jsonSchema],
  parseAssistantStreamPart: [Function: parseAssistantStreamPart],
  parseDataStreamPart: [Function: parseDataStreamPart],
  processDataStream: [AsyncFunction: processDataStream],
  processTextStream: [AsyncFunction: processTextStream],
  streamObject: [Function (anonymous)],
  streamText: [Function (anonymous)],
  tool: [Function: tool]
}
> setters WeakMap { <items unknown> }
> setters.get(target) {
  AISDKError: [Function (anonymous)],
  APICallError: [Function (anonymous)],
  AssistantResponse: [Function (anonymous)],
  DownloadError: [Function (anonymous)],
  EmptyResponseBodyError: [Function (anonymous)],
  InvalidArgumentError: [Function (anonymous)],
  InvalidDataContentError: [Function (anonymous)],
  InvalidMessageRoleError: [Function (anonymous)],
  InvalidPromptError: [Function (anonymous)],
  InvalidResponseDataError: [Function (anonymous)],
  InvalidToolArgumentsError: [Function (anonymous)],
  JSONParseError: [Function (anonymous)],
  LangChainAdapter: [Function (anonymous)],
  LlamaIndexAdapter: [Function (anonymous)],
  LoadAPIKeyError: [Function (anonymous)],
  MessageConversionError: [Function (anonymous)],
  NoContentGeneratedError: [Function (anonymous)],
  NoObjectGeneratedError: [Function (anonymous)],
  NoSuchModelError: [Function (anonymous)],
  NoSuchProviderError: [Function (anonymous)],
  NoSuchToolError: [Function (anonymous)],
  RetryError: [Function (anonymous)],
  StreamData: [Function (anonymous)],
  TypeValidationError: [Function (anonymous)],
  UnsupportedFunctionalityError: [Function (anonymous)],
  convertToCoreMessages: [Function (anonymous)],
  cosineSimilarity: [Function (anonymous)],
  createStreamDataTransformer: [Function (anonymous)],
  embed: [Function (anonymous)],
  embedMany: [Function (anonymous)],
  experimental_createProviderRegistry: [Function (anonymous)],
  experimental_customProvider: [Function (anonymous)],
  experimental_wrapLanguageModel: [Function (anonymous)],
  formatAssistantStreamPart: [Function (anonymous)],
  formatDataStreamPart: [Function (anonymous)],
  generateId: [Function (anonymous)],
  generateObject: [Function (anonymous)],
  generateText: [Function (anonymous)],
  jsonSchema: [Function (anonymous)],
  parseAssistantStreamPart: [Function (anonymous)],
  parseDataStreamPart: [Function (anonymous)],
  processDataStream: [Function (anonymous)],
  processTextStream: [Function (anonymous)],
  streamObject: [Function (anonymous)],
  streamText: [Function (anonymous)],
  tool: [Function (anonymous)]
}
> setters.get(target)[name] undefined
/Users/lukas/code/test-projects/gh-sentry-javascript-16137/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/register.js:21
    return setters.get(target)[name](value)

So based on my limited understanding of IITM, it seems like for some reason it is trying to set a default export but the ai library doesn't export a default export.

I will ask @timfish and @AbhiPrasad - could you please take a look at this? I'm not sure if this is an IITM bug or our instrumentation is causing this.

@Lms24 Lms24 changed the title Crash with Vercel AI SDK when spotlight is enabled Crash with Vercel AI SDK when used in ESM Apr 28, 2025
@Lms24 Lms24 changed the title Crash with Vercel AI SDK when used in ESM Crash with Vercel AI SDK when spotlight is enabled Apr 28, 2025
@Lms24 Lms24 assigned timfish and unassigned Lms24 Apr 28, 2025
@Lms24 Lms24 changed the title Crash with Vercel AI SDK when spotlight is enabled Crash with Vercel AI SDK when used in ESM Apr 28, 2025
@timfish
Copy link
Collaborator

timfish commented Apr 28, 2025

I think it is caused by this code which reconstructs the module as an object:

const patchedModuleExports = INSTRUMENTED_METHODS.reduce((acc, curr) => {
acc[curr] = generatePatch(curr);
return acc;
}, {} as PatchedModuleExports);
return { ...moduleExports, ...patchedModuleExports };

I don't think a module can be represented with a pure object. Check out the craziness iitm does to "look" like a module in it's wrapping code:

// Mimic a Module object (https://tc39.es/ecma262/#sec-module-namespace-objects).
const _ = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } })

It doesn't look like there's a need to construct a new module, you can just overwrite the functions and return the original module:

for (const method of INSTRUMENTED_METHODS) {
  moduleExports[method] = generatePatch(method);
}

return moduleExports;

I'll test this and submit a PR!

@timfish
Copy link
Collaborator

timfish commented Apr 28, 2025

It doesn't look like there's a need to construct a new module

I was wrong!

So my changes fix ESM, but CJS is broken because the exports are only getters. This explains why the original code recreated the module as an object. Without setters, there's no way to replace them on the original object!

Looks like we need different hooking strategies for ESM/CJS...

chargome pushed a commit that referenced this issue Apr 29, 2025
- Closes #16137 

With ESM patching you need to retain the original module and just
overwrite the exports you want to wrap. This usually works for CJS too
but unfortunately, the CJS exports of `ai` only have getters and no
setters so this route is not possible.

This PR changes the patching so that it works slightly differently for
ESM and CJS. The original code that outputs a newly created object is
retained for CJS but for ESM we use the preferred route of replacing the
required exports.

To detect whether the module we're patching is an ES module we check
`Object.prototype.toString.call(moduleExports) === '[object Module]'`
which is documented
[here](https://tc39.es/ecma262/#sec-module-namespace-objects).

This PR also adds an ESM test for `ai`.
Copy link
Contributor

A PR closing this issue has just been released 🚀

This issue was referenced by PR #16152, which was included in the 9.15.0 release.

@i-tu
Copy link

i-tu commented Apr 30, 2025

Thank you @timfish , @Lms24 and @Jaakkonen ! Absolute legends 👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

4 participants