@hashspan/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Transaction lifecycle tracing for the on-chain actions of AI agents, built on Op
9
9
  revert reason. It carries a span link to its `send` span.
10
10
 
11
11
  The core is library-agnostic and read-only: it never signs, sends or fetches anything. Adapters such as
12
- [`@hashspan/viem`](https://github.com/selimaytac/hashspan/tree/main/packages/viem) call it for you. Use the core directly to instrument any other send path.
12
+ [`@hashspan/viem`](https://github.com/selimaytac/hashspan/tree/@hashspan/core@0.3.0/packages/viem) call it for you. Use the core directly to instrument any other send path.
13
13
 
14
14
  ## Install
15
15
 
@@ -57,9 +57,13 @@ confirm.end({
57
57
  share one confirm span. A receipt from any of them ends it; a timeout or failure ends it once every caller gave up.
58
58
  End every handle you start, since an open handle keeps the shared span open.
59
59
 
60
+ `send.fail(error, endTime, { errorType })` records a library's machine-readable error code as `error.type` instead
61
+ of the error's class name, if it is a short identifier (`[A-Za-z0-9_.-]`, at most 64 characters); `exception.type`
62
+ stays the class name.
63
+
60
64
  Both calls accept an explicit parent `Context` as a second argument. An integration that learns about a call only
61
65
  after it started can record it after the fact: pass `startTime` in the input and the end time as the last argument
62
- of the handle method, e.g. `send.end(hash, endTime)` ([ADR 0009](https://github.com/selimaytac/hashspan/blob/main/docs/adr/0009-telemetry-off-the-call-path.md)). Every method is safe to call: failures inside
66
+ of the handle method, e.g. `send.end(hash, endTime)` ([ADR 0009](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.3.0/docs/adr/0009-telemetry-off-the-call-path.md)). Every method is safe to call: failures inside
63
67
  the instrumentation are reported through `diag` and never thrown into your code.
64
68
 
65
69
  ## Options
@@ -68,7 +72,7 @@ the instrumentation are reported through `diag` and never thrown into your code.
68
72
  |---|---|---|
69
73
  | `tracerProvider` | global provider | Tracer provider to use |
70
74
  | `address` | `'raw'` | `'raw'`, `'hashed'`, `'off'`, or `{ mode: 'hashed', hash: (address) => string }` |
71
- | `errorMessages` | `'off'` | What failed spans record about the error: `'off'` (type only), `'sanitized'` (first line, addresses per `address` mode, calldata removed; in `hashed` and `off` mode any hex longer than an address) or `'raw'` (full message and stack trace). `'raw'` can record RPC URLs that include API keys, as some libraries put the request URL in the message; `'sanitized'` keeps only the first line (viem puts the URL on a later line), which is best effort. See [ADR 0006](https://github.com/selimaytac/hashspan/blob/main/docs/adr/0006-error-privacy.md) |
75
+ | `errorMessages` | `'off'` | What failed spans record about the error: `'off'` (type only), `'sanitized'` (first line, addresses per `address` mode, calldata removed; in `hashed` and `off` mode any hex longer than an address) or `'raw'` (full message and stack trace). `'raw'` can record RPC URLs that include API keys, as some libraries put the request URL in the message; `'sanitized'` keeps only the first line (viem puts the URL on a later line), which is best effort. See [ADR 0006](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.3.0/docs/adr/0006-error-privacy.md) |
72
76
  | `recordFunctionArguments` | `false` | Record `functionArguments` as a JSON array in `blockchain.contract.function.arguments`: bigints as decimal strings, addresses per `address` mode (longer hex values become `<hex>` in `hashed` and `off` mode), at most 4096 characters. Reads only own enumerable data properties: `toJSON()` and getters are never called, so a `Date` records as `{}`; a Proxy's traps still run |
73
77
  | `agent` | none | Agent `{ id, name }`; a field set here always wins, unset fields come from the Baggage entries `gen_ai.agent.id` / `gen_ai.agent.name` |
74
78
  | `agentFromBaggage` | `true` | Read agent identity fields that `agent` leaves unset from Baggage; set to `false` in services that accept requests from outside their trust boundary |
@@ -82,7 +86,7 @@ Chain id, transaction hash, sender/recipient (per `address` mode), value, nonce,
82
86
  on confirmation, status, block number, gas used, effective gas price, L1 fee, total fee and revert reason. Decoded
83
87
  call arguments are recorded only with `recordFunctionArguments`, and error messages only with `errorMessages`.
84
88
  Attribute definitions:
85
- [docs/semconv.md](https://github.com/selimaytac/hashspan/blob/main/docs/semconv.md).
89
+ [docs/semconv.md](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.3.0/docs/semconv.md).
86
90
 
87
91
  ## Privacy notes
88
92
 
@@ -97,7 +101,7 @@ Attribute definitions:
97
101
  propagated, or strip the entries before outbound calls.
98
102
  - **Inbound Baggage can claim an identity.** A caller can send Baggage entries with any agent id. A field set in the
99
103
  `agent` option cannot be overridden that way; to ignore identity from Baggage entirely, set `agentFromBaggage: false`
100
- ([ADR 0011](https://github.com/selimaytac/hashspan/blob/main/docs/adr/0011-agent-identity-precedence.md)).
104
+ ([ADR 0011](https://github.com/selimaytac/hashspan/blob/@hashspan/core@0.3.0/docs/adr/0011-agent-identity-precedence.md)).
101
105
  - The redaction hook (`redact`) runs last on every attribute set and on exception attributes; use it for anything
102
106
  else your policy forbids.
103
107
 
package/dist/index.cjs CHANGED
@@ -302,7 +302,7 @@ function resolveErrorMessageMode(mode) {
302
302
  }
303
303
  //#endregion
304
304
  //#region src/version.ts
305
- const VERSION = "0.2.0";
305
+ const VERSION = "0.3.0";
306
306
  //#endregion
307
307
  //#region src/tracker.ts
308
308
  const INSTRUMENTATION_NAME = "@hashspan/core";
@@ -357,6 +357,15 @@ function toInt(value) {
357
357
  function errorType(error) {
358
358
  return error instanceof Error && error.name ? error.name : ERROR_TYPE_VALUE_OTHER;
359
359
  }
360
+ const ERROR_TYPE_OVERRIDE = /^[A-Za-z0-9_.-]{1,64}$/;
361
+ /** The `error.type` for a failure: an adapter's override when it is a short identifier, else the class name. */
362
+ function reportedErrorType(error, options) {
363
+ const override = options?.errorType;
364
+ if (override === void 0) return errorType(error);
365
+ if (typeof override === "string" && ERROR_TYPE_OVERRIDE.test(override)) return override;
366
+ _opentelemetry_api.diag.debug("hashspan: ignoring an error type that is not a short identifier");
367
+ return errorType(error);
368
+ }
360
369
  function createTxTracker(options = {}) {
361
370
  const links = new LinkStore({
362
371
  ttlMs: options.linkTtlMs ?? DEFAULT_LINK_TTL_MS,
@@ -406,12 +415,15 @@ function createTxTracker(options = {}) {
406
415
  if (error instanceof Error && error.stack) attributes[ATTR_EXCEPTION_STACKTRACE] = error.stack;
407
416
  return attributes;
408
417
  };
409
- /** Error names are free text too: they follow the address mode and pass through the redaction hook. */
410
- const markError = (span, errorName, error) => {
418
+ /**
419
+ * Error names are free text too: they follow the address mode and pass through the redaction hook.
420
+ * `exceptionName` is the class name for `exception.type` when `errorName` is an adapter's error type.
421
+ */
422
+ const markError = (span, errorName, error, exceptionName = errorName) => {
411
423
  const type = formatAddressesIn(errorName, formatAddress);
412
424
  let message;
413
425
  if (error !== void 0) {
414
- const exception = redact(exceptionAttributes(type, error));
426
+ const exception = redact(exceptionAttributes(formatAddressesIn(exceptionName, formatAddress), error));
415
427
  span.addEvent(EXCEPTION_EVENT, exception);
416
428
  const recorded = exception[ATTR_EXCEPTION_MESSAGE];
417
429
  if (typeof recorded === "string") message = recorded;
@@ -476,7 +488,7 @@ function createTxTracker(options = {}) {
476
488
  });
477
489
  span.setAttributes(redact({ [ATTR_BLOCKCHAIN_TX_HASH]: hash }));
478
490
  }, endTime),
479
- fail: (error, endTime) => finish("record send failure", () => markError(span, errorType(error), error), endTime)
491
+ fail: (error, endTime, options) => finish("record send failure", () => markError(span, reportedErrorType(error, options), error, errorType(error)), endTime)
480
492
  };
481
493
  };
482
494
  const receiptAttributes = (receipt) => {
package/dist/index.d.cts CHANGED
@@ -82,7 +82,16 @@ interface SendHandle {
82
82
  /** Ends the send span successfully once the transaction hash is known; `endTime` defaults to now. */
83
83
  end(hash: string, endTime?: TimeInput): void;
84
84
  /** Ends the send span with an error (signing, simulation or broadcast failure); `endTime` defaults to now. */
85
- fail(error: unknown, endTime?: TimeInput): void;
85
+ fail(error: unknown, endTime?: TimeInput, options?: FailOptions): void;
86
+ }
87
+ interface FailOptions {
88
+ /**
89
+ * `error.type` to record instead of the error's class name, for adapters whose library reports a stable,
90
+ * machine-readable error code (for example a wallet API's error type). Recorded only if it matches
91
+ * `/^[A-Za-z0-9_.-]{1,64}$/`, so that the attribute keeps a bounded set of values; otherwise the class name is
92
+ * recorded. `exception.type` is always the class name.
93
+ */
94
+ errorType?: string | undefined;
86
95
  }
87
96
  interface ConfirmInput {
88
97
  chainId: number;
@@ -196,4 +205,4 @@ export declare function createTxTracker(options?: TxTrackerOptions): TxTracker;
196
205
  //#region src/version.d.ts
197
206
  export declare const VERSION: string;
198
207
  //#endregion
199
- export type { AddressMode, AddressOptions, AgentIdentity, ConfirmHandle, ConfirmInput, ErrorMessageMode, ReceiptLike, ReplacementReason, SendHandle, SendInput, TxTracker, TxTrackerOptions };
208
+ export type { AddressMode, AddressOptions, AgentIdentity, ConfirmHandle, ConfirmInput, ErrorMessageMode, FailOptions, ReceiptLike, ReplacementReason, SendHandle, SendInput, TxTracker, TxTrackerOptions };
package/dist/index.d.mts CHANGED
@@ -82,7 +82,16 @@ interface SendHandle {
82
82
  /** Ends the send span successfully once the transaction hash is known; `endTime` defaults to now. */
83
83
  end(hash: string, endTime?: TimeInput): void;
84
84
  /** Ends the send span with an error (signing, simulation or broadcast failure); `endTime` defaults to now. */
85
- fail(error: unknown, endTime?: TimeInput): void;
85
+ fail(error: unknown, endTime?: TimeInput, options?: FailOptions): void;
86
+ }
87
+ interface FailOptions {
88
+ /**
89
+ * `error.type` to record instead of the error's class name, for adapters whose library reports a stable,
90
+ * machine-readable error code (for example a wallet API's error type). Recorded only if it matches
91
+ * `/^[A-Za-z0-9_.-]{1,64}$/`, so that the attribute keeps a bounded set of values; otherwise the class name is
92
+ * recorded. `exception.type` is always the class name.
93
+ */
94
+ errorType?: string | undefined;
86
95
  }
87
96
  interface ConfirmInput {
88
97
  chainId: number;
@@ -196,4 +205,4 @@ export declare function createTxTracker(options?: TxTrackerOptions): TxTracker;
196
205
  //#region src/version.d.ts
197
206
  export declare const VERSION: string;
198
207
  //#endregion
199
- export type { AddressMode, AddressOptions, AgentIdentity, ConfirmHandle, ConfirmInput, ErrorMessageMode, ReceiptLike, ReplacementReason, SendHandle, SendInput, TxTracker, TxTrackerOptions };
208
+ export type { AddressMode, AddressOptions, AgentIdentity, ConfirmHandle, ConfirmInput, ErrorMessageMode, FailOptions, ReceiptLike, ReplacementReason, SendHandle, SendInput, TxTracker, TxTrackerOptions };
package/dist/index.mjs CHANGED
@@ -301,7 +301,7 @@ function resolveErrorMessageMode(mode) {
301
301
  }
302
302
  //#endregion
303
303
  //#region src/version.ts
304
- const VERSION = "0.2.0";
304
+ const VERSION = "0.3.0";
305
305
  //#endregion
306
306
  //#region src/tracker.ts
307
307
  const INSTRUMENTATION_NAME = "@hashspan/core";
@@ -356,6 +356,15 @@ function toInt(value) {
356
356
  function errorType(error) {
357
357
  return error instanceof Error && error.name ? error.name : ERROR_TYPE_VALUE_OTHER;
358
358
  }
359
+ const ERROR_TYPE_OVERRIDE = /^[A-Za-z0-9_.-]{1,64}$/;
360
+ /** The `error.type` for a failure: an adapter's override when it is a short identifier, else the class name. */
361
+ function reportedErrorType(error, options) {
362
+ const override = options?.errorType;
363
+ if (override === void 0) return errorType(error);
364
+ if (typeof override === "string" && ERROR_TYPE_OVERRIDE.test(override)) return override;
365
+ diag.debug("hashspan: ignoring an error type that is not a short identifier");
366
+ return errorType(error);
367
+ }
359
368
  function createTxTracker(options = {}) {
360
369
  const links = new LinkStore({
361
370
  ttlMs: options.linkTtlMs ?? DEFAULT_LINK_TTL_MS,
@@ -405,12 +414,15 @@ function createTxTracker(options = {}) {
405
414
  if (error instanceof Error && error.stack) attributes[ATTR_EXCEPTION_STACKTRACE] = error.stack;
406
415
  return attributes;
407
416
  };
408
- /** Error names are free text too: they follow the address mode and pass through the redaction hook. */
409
- const markError = (span, errorName, error) => {
417
+ /**
418
+ * Error names are free text too: they follow the address mode and pass through the redaction hook.
419
+ * `exceptionName` is the class name for `exception.type` when `errorName` is an adapter's error type.
420
+ */
421
+ const markError = (span, errorName, error, exceptionName = errorName) => {
410
422
  const type = formatAddressesIn(errorName, formatAddress);
411
423
  let message;
412
424
  if (error !== void 0) {
413
- const exception = redact(exceptionAttributes(type, error));
425
+ const exception = redact(exceptionAttributes(formatAddressesIn(exceptionName, formatAddress), error));
414
426
  span.addEvent(EXCEPTION_EVENT, exception);
415
427
  const recorded = exception[ATTR_EXCEPTION_MESSAGE];
416
428
  if (typeof recorded === "string") message = recorded;
@@ -475,7 +487,7 @@ function createTxTracker(options = {}) {
475
487
  });
476
488
  span.setAttributes(redact({ [ATTR_BLOCKCHAIN_TX_HASH]: hash }));
477
489
  }, endTime),
478
- fail: (error, endTime) => finish("record send failure", () => markError(span, errorType(error), error), endTime)
490
+ fail: (error, endTime, options) => finish("record send failure", () => markError(span, reportedErrorType(error, options), error, errorType(error)), endTime)
479
491
  };
480
492
  };
481
493
  const receiptAttributes = (receipt) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashspan/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Transaction lifecycle tracing for on-chain actions of AI agents, built on OpenTelemetry.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Selim Aytac",