@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 +9 -5
- package/dist/index.cjs +17 -5
- package/dist/index.d.cts +11 -2
- package/dist/index.d.mts +11 -2
- package/dist/index.mjs +17 -5
- package/package.json +1 -1
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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.
|
|
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
|
-
/**
|
|
410
|
-
|
|
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(
|
|
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,
|
|
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.
|
|
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
|
-
/**
|
|
409
|
-
|
|
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(
|
|
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,
|
|
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) => {
|