Skip to content

Cross-entry instanceof fails for @a2x/sdk error classes (independently bundled entry points) #227

Description

@longfin

Summary

packages/a2x/tsup.config.ts builds the root index entry and the subpath entries (client, x402, …) as two separate tsup configs. Each bundle therefore carries its own copy of shared classes: dist/index.js defines e.g. X402PaymentFailedError inline, while dist/x402/index.js imports it from a chunk shared only among the second config's entries.

As a result, an error thrown by A2XClient (imported from @a2x/sdk) is never instanceof the class a consumer imports from @a2x/sdk/x402:

import { A2XClient } from '@a2x/sdk';
import { X402PaymentFailedError } from '@a2x/sdk/x402';

try {
  await client.sendMessage(...);
} catch (err) {
  err instanceof X402PaymentFailedError; // always false
}

This bit the in-repo CLI: printX402Error matched only via instanceof, so every payment failure fell through to the generic connection-error path (exit 1, no x402 message). #226 works around it CLI-side by also matching on err.name, but the hazard remains for every external consumer and for all classes exported from more than one entry (X402Error subclasses, and any A2AError subclasses if they are reachable from multiple entries).

Possible fixes

  • Merge the two tsup configs into one so ESM entries share chunks (fixes ESM; CJS needs splitting enabled or the same class copied — CJS consumers requiring both entries would still be broken without cjs splitting).
  • And/or make instanceof non-load-bearing: give SDK error classes a stable discriminator (name is already set; document name-based narrowing, or export isX402PaymentFailedError()-style guards that match cross-realm).

Related

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