UNPKG

@mastra/core

Version:
1 lines 18 kB
{"version":3,"file":"request-context-ByoZMp-j.cjs","names":[],"sources":["../../_internal-core/dist/request-context/index.js"],"sourcesContent":["//#region src/request-context/index.ts\n/**\n* Reserved key for setting resourceId from middleware.\n* When set in RequestContext, this takes precedence over client-provided values\n* for security (prevents attackers from hijacking another user's memory).\n*\n* @example\n* ```typescript\n* // In your auth middleware:\n* const requestContext = c.get('requestContext');\n* requestContext.set(MASTRA_RESOURCE_ID_KEY, authenticatedUser.id);\n* ```\n*/\nconst MASTRA_RESOURCE_ID_KEY = \"mastra__resourceId\";\n/**\n* Reserved key for setting threadId from middleware.\n* When set in RequestContext, this takes precedence over client-provided values\n* for security (prevents attackers from hijacking another user's memory).\n*\n* @example\n* ```typescript\n* // In your auth middleware:\n* const requestContext = c.get('requestContext');\n* requestContext.set(MASTRA_THREAD_ID_KEY, threadId);\n* ```\n*/\nconst MASTRA_THREAD_ID_KEY = \"mastra__threadId\";\n/**\n* Reserved key for storing version overrides on RequestContext.\n* When set, sub-agent delegation resolves versioned agents from these overrides.\n*\n* @example\n* ```typescript\n* requestContext.set(MASTRA_VERSIONS_KEY, {\n* agents: { 'researcher-agent': { versionId: '123' } },\n* });\n* ```\n*/\nconst MASTRA_VERSIONS_KEY = \"mastra__versions\";\n/**\n* Reserved key for storing the raw auth token from the incoming request.\n* Used by the editor to forward authentication when connecting to MCP servers\n* that require the same auth as the Mastra server itself.\n*/\nconst MASTRA_AUTH_TOKEN_KEY = \"mastra__authToken\";\nfunction mergeVersionOverrides(base, overrides) {\n\tif (!base && !overrides) return void 0;\n\treturn {\n\t\t...base,\n\t\t...overrides,\n\t\tagents: {\n\t\t\t...base?.agents,\n\t\t\t...overrides?.agents\n\t\t},\n\t\t...overrides?.defaultStatus ? { defaultStatus: overrides.defaultStatus } : base?.defaultStatus ? { defaultStatus: base.defaultStatus } : {}\n\t};\n}\n/**\n* Marker thrown by `RequestContext.toJSON()` when it detects cyclic re-entry.\n*\n* Cyclic re-entry happens when a stored value transitively references another\n* `RequestContext` whose `toJSON()` is already on the call stack. `JSON.stringify`\n* inside `isSerializable` then walks into that context, V8 invokes its\n* `toJSON()`, which iterates its registry and calls `JSON.stringify` on values\n* that may walk back through the first context — and so on. Each step is a\n* fresh `JSON.stringify` call with a fresh internal cycle stack, so V8's\n* built-in cycle detection never trips and the recursion would pin one CPU\n* core at 100% indefinitely.\n*\n* The fix: throw this marker on reentry. The marker propagates upward through\n* `isSerializable`'s nested catches (which re-throw it) until it reaches the\n* outermost `toJSON()`'s `isSerializable` — there it is swallowed and the\n* offending key is filtered, the same way in-value circular references are\n* filtered today.\n*/\nvar CyclicRequestContextToJSONError = class extends Error {\n\tconstructor(message) {\n\t\tsuper(message);\n\t\tthis.name = \"CyclicRequestContextToJSONError\";\n\t}\n};\n/**\n* Tracks `RequestContext` instances whose `toJSON()` is currently on the call\n* stack. Used to detect cyclic re-entry. Stored as a `WeakSet` so entries are\n* garbage-collected with their owning context.\n*/\nconst _toJSONInProgress = /* @__PURE__ */ new WeakSet();\n/**\n* Nesting depth of active `toJSON()` calls. The outermost call (depth === 1\n* after entry) catches the cyclic marker error and filters the offending\n* value; inner calls re-throw so the marker propagates to the outermost.\n*/\nlet _toJSONDepth = 0;\n/**\n* Maximum number of nodes the `isSerializable` probe lets `JSON.stringify`\n* visit for a single stored value.\n*\n* `JSON.stringify` expands shared (non-circular) references once per path,\n* not once per object: an acyclic graph where every level shares one child\n* (`{ a: n, b: n }` nested `d` times) holds `d + 1` heap objects but expands\n* to `2^d` visited nodes. Around `2^26` the output also exceeds V8's string\n* length cap and stringify throws `RangeError` — but only after doing the\n* traversal work, which keeps doubling past the cap. Unbounded, a ~30-object\n* value can block the event loop for minutes and then be silently filtered.\n*\n* The budget counts node *visits* (shared references count once per path)\n* because that is exactly the work any real downstream serialization of the\n* value would do — a value that fails the budget would also be pathological\n* to persist. 1M visits keeps the worst-case probe in the tens of\n* milliseconds while remaining far above any reasonable context value.\n*\n* The budget is shared across nested `RequestContext` probes within one\n* outermost probe (see `_probeBudgetRemaining`), so a shared-reference graph of\n* nested contexts is bounded too. One caveat remains: `Buffer.prototype.toJSON`\n* materializes a `{ type, data }` object before the replacer, so Buffers charge\n* one budget unit per byte rather than the arithmetic typed-array fast path — a\n* Buffer past the budget is filtered.\n*/\nconst SERIALIZATION_PROBE_BUDGET = 1e6;\n/**\n* The intrinsic `%TypedArray%.prototype.length` getter. Reads the element\n* count from internal slots, so it cannot be shadowed by an own `length`\n* property, works across realms, and throws `TypeError` for `DataView` —\n* which makes it double as the discriminator between typed arrays (intrinsic\n* indexed elements) and other `ArrayBuffer` views (plain-object semantics).\n*/\nconst _typedArrayLength = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Int8Array.prototype), \"length\").get;\n/**\n* Whether a value is a plain object (prototype `Object.prototype` or `null`)\n* or an array — i.e. structural data safe to hand to the span serializer to\n* walk. Class instances, functions, `Map`/`Set`, `Date`, etc. are excluded so\n* their internals aren't walked into traces.\n*/\nfunction isPlainObjectOrArray(value) {\n\ttry {\n\t\tif (Array.isArray(value)) return true;\n\t\tif (value === null || typeof value !== \"object\") return false;\n\t\tconst proto = Object.getPrototypeOf(value);\n\t\treturn proto === Object.prototype || proto === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n/**\n* Shared state for the serialization probe budget.\n*\n* The budget is drawn down by every `isSerializable` probe running within one\n* outermost probe — including the probes a nested `RequestContext.toJSON()`\n* runs, which `JSON.stringify` invokes *before* the outer replacer sees the\n* result. Sharing one budget across them keeps the probe's time bound holding\n* regardless of nesting; a fresh per-call budget (the earlier approach) let a\n* shared-reference graph of nested contexts re-run full-budget probes on every\n* visit and block for seconds. `_probeBudgetActive` marks that an outermost\n* probe owns the budget so nested probes draw down rather than reset it.\n*/\nlet _probeBudgetActive = false;\nlet _probeBudgetRemaining = 0;\nvar RequestContext = class {\n\tregistry = /* @__PURE__ */ new Map();\n\tconstructor(iterable) {\n\t\tif (iterable && typeof iterable === \"object\" && typeof iterable[Symbol.iterator] !== \"function\") this.registry = new Map(Object.entries(iterable));\n\t\telse this.registry = new Map(iterable);\n\t}\n\t/**\n\t* set a value with strict typing if `Values` is a Record and the key exists in it.\n\t*/\n\tset(key, value) {\n\t\tthis.registry.set(key, value);\n\t}\n\t/**\n\t* Get a value with its type\n\t*/\n\tget(key) {\n\t\treturn this.registry.get(key);\n\t}\n\t/**\n\t* Check if a key exists in the container\n\t*/\n\thas(key) {\n\t\treturn this.registry.has(key);\n\t}\n\t/**\n\t* Delete a value by key\n\t*/\n\tdelete(key) {\n\t\treturn this.registry.delete(key);\n\t}\n\t/**\n\t* Clear all values from the container\n\t*/\n\tclear() {\n\t\tthis.registry.clear();\n\t}\n\t/**\n\t* Get all keys in the container\n\t*/\n\tkeys() {\n\t\treturn this.registry.keys();\n\t}\n\t/**\n\t* Get all values in the container\n\t*/\n\tvalues() {\n\t\treturn this.registry.values();\n\t}\n\t/**\n\t* Get all entries in the container.\n\t* Returns a discriminated union of tuples for proper type narrowing when iterating.\n\t*/\n\tentries() {\n\t\treturn this.registry.entries();\n\t}\n\t/**\n\t* Get the size of the container\n\t*/\n\tsize() {\n\t\treturn this.registry.size;\n\t}\n\t/**\n\t* Execute a function for each entry in the container.\n\t* The callback receives properly typed key-value pairs.\n\t*/\n\tforEach(callbackfn) {\n\t\tthis.registry.forEach(callbackfn);\n\t}\n\t/**\n\t* Custom JSON serialization method.\n\t* Converts the internal Map to a plain object for proper JSON serialization.\n\t* Non-serializable values (functions, symbols, RPC proxies, in-value\n\t* circular references, and values whose serialization re-enters this\n\t* `toJSON` via cross-context back-references) are skipped to prevent\n\t* serialization errors when storing to database.\n\t*\n\t* Reentry safety: if a stored value's `isSerializable` probe re-enters\n\t* `toJSON()` on this same instance (through a chain of RequestContexts\n\t* holding references to each other), we throw `CyclicRequestContextToJSONError`.\n\t* Inner `isSerializable` calls re-throw the marker; the outermost\n\t* `isSerializable` swallows it and filters the offending key, the same\n\t* way it filters in-value circular references today.\n\t*/\n\ttoJSON() {\n\t\tif (_toJSONInProgress.has(this)) throw new CyclicRequestContextToJSONError(\"RequestContext.toJSON: detected cyclic re-entry (a stored value transitively references this context)\");\n\t\t_toJSONInProgress.add(this);\n\t\t_toJSONDepth++;\n\t\ttry {\n\t\t\tconst result = {};\n\t\t\tfor (const [key, value] of this.registry.entries()) if (this.isSerializable(value)) result[key] = value;\n\t\t\treturn result;\n\t\t} finally {\n\t\t\t_toJSONInProgress.delete(this);\n\t\t\t_toJSONDepth--;\n\t\t}\n\t}\n\t/**\n\t* Check if a value can be safely serialized to JSON.\n\t*\n\t* The probe is budgeted (see `SERIALIZATION_PROBE_BUDGET`): a value whose\n\t* serialization would visit an unbounded number of nodes — an acyclic\n\t* graph with layered shared references expands as 2^depth — is treated as\n\t* non-serializable and filtered instead of blocking the event loop for\n\t* the full expansion. The budget is shared across nested `RequestContext`\n\t* probes within one outermost probe (a nested `toJSON()` runs before the\n\t* replacer sees its result), so the bound holds even when the graph reaches\n\t* nested contexts through many shared paths.\n\t*\n\t* Re-throws `CyclicRequestContextToJSONError` when called from a nested\n\t* `toJSON()` (`_toJSONDepth > 1`), so the marker propagates up to the\n\t* outermost `toJSON()`'s `isSerializable`, which then swallows it and\n\t* filters the offending key. This is what lets the outermost call return\n\t* a clean JSON-safe dict for cross-context cycles.\n\t*/\n\tisSerializable(value) {\n\t\tif (value === null || value === void 0) return true;\n\t\tif (typeof value === \"function\") return false;\n\t\tif (typeof value === \"symbol\") return false;\n\t\tif (typeof value !== \"object\") return true;\n\t\tconst outermostProbe = !_probeBudgetActive;\n\t\tif (outermostProbe) {\n\t\t\t_probeBudgetActive = true;\n\t\t\t_probeBudgetRemaining = SERIALIZATION_PROBE_BUDGET;\n\t\t}\n\t\ttry {\n\t\t\tJSON.stringify(value, (_key, probed) => {\n\t\t\t\tif (--_probeBudgetRemaining < 0) throw new RangeError(\"RequestContext.isSerializable: value expands past the serialization probe budget\");\n\t\t\t\tif (ArrayBuffer.isView(probed)) {\n\t\t\t\t\tlet elementCount;\n\t\t\t\t\ttry {\n\t\t\t\t\t\telementCount = _typedArrayLength.call(probed);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn probed;\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof probed[0] === \"bigint\") return probed;\n\t\t\t\t\t_probeBudgetRemaining -= elementCount;\n\t\t\t\t\tif (_probeBudgetRemaining < 0) throw new RangeError(\"RequestContext.isSerializable: value expands past the serialization probe budget\");\n\t\t\t\t\tconst surrogate = Object.create(null);\n\t\t\t\t\tfor (const key of Object.keys(probed)) {\n\t\t\t\t\t\tconst index = Number(key);\n\t\t\t\t\t\tif (!(Number.isInteger(index) && index >= 0 && String(index) === key)) surrogate[key] = probed[key];\n\t\t\t\t\t}\n\t\t\t\t\treturn surrogate;\n\t\t\t\t}\n\t\t\t\treturn probed;\n\t\t\t});\n\t\t\treturn true;\n\t\t} catch (e) {\n\t\t\tif (e instanceof CyclicRequestContextToJSONError && _toJSONDepth > 1) throw e;\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tif (outermostProbe) _probeBudgetActive = false;\n\t\t}\n\t}\n\t/**\n\t* Custom span serialization. Exposes the registry *entries* (never the\n\t* instance's own private fields) so `deepClean` in `@mastra/observability`\n\t* doesn't walk the runtime-enumerable `registry` Map — which would\n\t* serialize its raw entries (including bearer tokens) into exported spans.\n\t*\n\t* Per stored value:\n\t* - The framework-managed auth token is redacted by key.\n\t* - Primitives are returned as-is.\n\t* - Plain objects and arrays are returned by reference so the downstream\n\t* `deepClean` walks and bounds them — this keeps nested request-context\n\t* data visible in traces instead of collapsing it to `[object]`.\n\t* - Every other type (class instances, functions, Map/Set, Date, etc.) is\n\t* collapsed to `[${typeof value}]` rather than walked, so a class's\n\t* internals never reach the trace serializer.\n\t*\n\t* The plain objects/arrays passed through here MUST still be bounded by a\n\t* downstream `deepClean` before export.\n\t*/\n\tserializeForSpan() {\n\t\tconst safe = {};\n\t\tfor (const [key, value] of this.registry.entries()) if (key === \"mastra__authToken\") safe[key] = \"[REDACTED]\";\n\t\telse if (value === null || value === void 0 || typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") safe[key] = value;\n\t\telse if (isPlainObjectOrArray(value)) safe[key] = value;\n\t\telse safe[key] = `[${typeof value}]`;\n\t\treturn safe;\n\t}\n\t/**\n\t* Get all values as a typed object for destructuring.\n\t* Returns Record<string, any> when untyped, or the Values type when typed.\n\t*\n\t* @example\n\t* ```typescript\n\t* const ctx = new RequestContext<{ userId: string; apiKey: string }>();\n\t* ctx.set('userId', 'user-123');\n\t* ctx.set('apiKey', 'key-456');\n\t* const { userId, apiKey } = ctx.all;\n\t* ```\n\t*/\n\tget all() {\n\t\treturn Object.fromEntries(this.registry);\n\t}\n};\n//#endregion\nexport { MASTRA_AUTH_TOKEN_KEY, MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY, MASTRA_VERSIONS_KEY, RequestContext, mergeVersionOverrides };\n\n//# sourceMappingURL=index.js.map"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,yBAAyB;;;;;;;;;;;;;AAa/B,MAAM,uBAAuB;;;;;;;;;;;;AAY7B,MAAM,sBAAsB;;;;;;AAM5B,MAAM,wBAAwB;AAC9B,SAAS,sBAAsB,MAAM,WAAW;CAC/C,IAAI,CAAC,QAAQ,CAAC,WAAW,OAAO,KAAK;CACrC,OAAO;EACN,GAAG;EACH,GAAG;EACH,QAAQ;GACP,GAAG,MAAM;GACT,GAAG,WAAW;EACf;EACA,GAAG,WAAW,gBAAgB,EAAE,eAAe,UAAU,cAAc,IAAI,MAAM,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;CAC3I;AACD;;;;;;;;;;;;;;;;;;;AAmBA,IAAI,kCAAkC,cAAc,MAAM;CACzD,YAAY,SAAS;EACpB,MAAM,OAAO;EACb,KAAK,OAAO;CACb;AACD;;;;;;AAMA,MAAM,oCAAoC,IAAI,QAAQ;;;;;;AAMtD,IAAI,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BnB,MAAM,6BAA6B;;;;;;;;AAQnC,MAAM,oBAAoB,OAAO,yBAAyB,OAAO,eAAe,UAAU,SAAS,GAAG,QAAQ,CAAC,CAAC;;;;;;;AAOhH,SAAS,qBAAqB,OAAO;CACpC,IAAI;EACH,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO;EACjC,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;EACxD,MAAM,QAAQ,OAAO,eAAe,KAAK;EACzC,OAAO,UAAU,OAAO,aAAa,UAAU;CAChD,QAAQ;EACP,OAAO;CACR;AACD;;;;;;;;;;;;;AAaA,IAAI,qBAAqB;AACzB,IAAI,wBAAwB;AAC5B,IAAI,iBAAiB,MAAM;CAC1B,2BAA2B,IAAI,IAAI;CACnC,YAAY,UAAU;EACrB,IAAI,YAAY,OAAO,aAAa,YAAY,OAAO,SAAS,OAAO,cAAc,YAAY,KAAK,WAAW,IAAI,IAAI,OAAO,QAAQ,QAAQ,CAAC;OAC5I,KAAK,WAAW,IAAI,IAAI,QAAQ;CACtC;;;;CAIA,IAAI,KAAK,OAAO;EACf,KAAK,SAAS,IAAI,KAAK,KAAK;CAC7B;;;;CAIA,IAAI,KAAK;EACR,OAAO,KAAK,SAAS,IAAI,GAAG;CAC7B;;;;CAIA,IAAI,KAAK;EACR,OAAO,KAAK,SAAS,IAAI,GAAG;CAC7B;;;;CAIA,OAAO,KAAK;EACX,OAAO,KAAK,SAAS,OAAO,GAAG;CAChC;;;;CAIA,QAAQ;EACP,KAAK,SAAS,MAAM;CACrB;;;;CAIA,OAAO;EACN,OAAO,KAAK,SAAS,KAAK;CAC3B;;;;CAIA,SAAS;EACR,OAAO,KAAK,SAAS,OAAO;CAC7B;;;;;CAKA,UAAU;EACT,OAAO,KAAK,SAAS,QAAQ;CAC9B;;;;CAIA,OAAO;EACN,OAAO,KAAK,SAAS;CACtB;;;;;CAKA,QAAQ,YAAY;EACnB,KAAK,SAAS,QAAQ,UAAU;CACjC;;;;;;;;;;;;;;;;CAgBA,SAAS;EACR,IAAI,kBAAkB,IAAI,IAAI,GAAG,MAAM,IAAI,gCAAgC,uGAAuG;EAClL,kBAAkB,IAAI,IAAI;EAC1B;EACA,IAAI;GACH,MAAM,SAAS,CAAC;GAChB,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,SAAS,QAAQ,GAAG,IAAI,KAAK,eAAe,KAAK,GAAG,OAAO,OAAO;GAClG,OAAO;EACR,UAAU;GACT,kBAAkB,OAAO,IAAI;GAC7B;EACD;CACD;;;;;;;;;;;;;;;;;;;CAmBA,eAAe,OAAO;EACrB,IAAI,UAAU,QAAQ,UAAU,KAAK,GAAG,OAAO;EAC/C,IAAI,OAAO,UAAU,YAAY,OAAO;EACxC,IAAI,OAAO,UAAU,UAAU,OAAO;EACtC,IAAI,OAAO,UAAU,UAAU,OAAO;EACtC,MAAM,iBAAiB,CAAC;EACxB,IAAI,gBAAgB;GACnB,qBAAqB;GACrB,wBAAwB;EACzB;EACA,IAAI;GACH,KAAK,UAAU,QAAQ,MAAM,WAAW;IACvC,IAAI,EAAE,wBAAwB,GAAG,MAAM,IAAI,WAAW,kFAAkF;IACxI,IAAI,YAAY,OAAO,MAAM,GAAG;KAC/B,IAAI;KACJ,IAAI;MACH,eAAe,kBAAkB,KAAK,MAAM;KAC7C,QAAQ;MACP,OAAO;KACR;KACA,IAAI,OAAO,OAAO,OAAO,UAAU,OAAO;KAC1C,yBAAyB;KACzB,IAAI,wBAAwB,GAAG,MAAM,IAAI,WAAW,kFAAkF;KACtI,MAAM,YAAY,OAAO,OAAO,IAAI;KACpC,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG;MACtC,MAAM,QAAQ,OAAO,GAAG;MACxB,IAAI,EAAE,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,OAAO,KAAK,MAAM,MAAM,UAAU,OAAO,OAAO;KAChG;KACA,OAAO;IACR;IACA,OAAO;GACR,CAAC;GACD,OAAO;EACR,SAAS,GAAG;GACX,IAAI,aAAa,mCAAmC,eAAe,GAAG,MAAM;GAC5E,OAAO;EACR,UAAU;GACT,IAAI,gBAAgB,qBAAqB;EAC1C;CACD;;;;;;;;;;;;;;;;;;;;CAoBA,mBAAmB;EAClB,MAAM,OAAO,CAAC;EACd,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,SAAS,QAAQ,GAAG,IAAI,QAAQ,qBAAqB,KAAK,OAAO;OAC5F,IAAI,UAAU,QAAQ,UAAU,KAAK,KAAK,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,KAAK,OAAO;OAC5I,IAAI,qBAAqB,KAAK,GAAG,KAAK,OAAO;OAC7C,KAAK,OAAO,IAAI,OAAO,MAAM;EAClC,OAAO;CACR;;;;;;;;;;;;;CAaA,IAAI,MAAM;EACT,OAAO,OAAO,YAAY,KAAK,QAAQ;CACxC;AACD"}