within-sdk 1.0.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.
Files changed (69) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +77 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.js +243 -0
  5. package/dist/middleware.d.ts +40 -0
  6. package/dist/middleware.js +562 -0
  7. package/dist/modules/compatibility.d.ts +18 -0
  8. package/dist/modules/compatibility.js +99 -0
  9. package/dist/modules/constants.d.ts +12 -0
  10. package/dist/modules/constants.js +12 -0
  11. package/dist/modules/context-parameters.d.ts +13 -0
  12. package/dist/modules/context-parameters.js +74 -0
  13. package/dist/modules/diagnostics.d.ts +7 -0
  14. package/dist/modules/diagnostics.js +12 -0
  15. package/dist/modules/eventQueue.d.ts +32 -0
  16. package/dist/modules/eventQueue.js +234 -0
  17. package/dist/modules/exceptions.d.ts +13 -0
  18. package/dist/modules/exceptions.js +730 -0
  19. package/dist/modules/exporters/datadog.d.ts +17 -0
  20. package/dist/modules/exporters/datadog.js +166 -0
  21. package/dist/modules/exporters/otlp.d.ts +13 -0
  22. package/dist/modules/exporters/otlp.js +140 -0
  23. package/dist/modules/exporters/posthog.d.ts +32 -0
  24. package/dist/modules/exporters/posthog.js +272 -0
  25. package/dist/modules/exporters/sentry.d.ts +27 -0
  26. package/dist/modules/exporters/sentry.js +386 -0
  27. package/dist/modules/exporters/trace-context.d.ts +8 -0
  28. package/dist/modules/exporters/trace-context.js +27 -0
  29. package/dist/modules/index.d.ts +8 -0
  30. package/dist/modules/index.js +8 -0
  31. package/dist/modules/internal.d.ts +33 -0
  32. package/dist/modules/internal.js +195 -0
  33. package/dist/modules/logging.d.ts +2 -0
  34. package/dist/modules/logging.js +74 -0
  35. package/dist/modules/mcp-sdk-compat.d.ts +32 -0
  36. package/dist/modules/mcp-sdk-compat.js +111 -0
  37. package/dist/modules/privacy.d.ts +15 -0
  38. package/dist/modules/privacy.js +179 -0
  39. package/dist/modules/redaction.d.ts +11 -0
  40. package/dist/modules/redaction.js +81 -0
  41. package/dist/modules/sanitization.d.ts +9 -0
  42. package/dist/modules/sanitization.js +111 -0
  43. package/dist/modules/session.d.ts +22 -0
  44. package/dist/modules/session.js +109 -0
  45. package/dist/modules/telemetry.d.ts +8 -0
  46. package/dist/modules/telemetry.js +53 -0
  47. package/dist/modules/tools.d.ts +25 -0
  48. package/dist/modules/tools.js +119 -0
  49. package/dist/modules/tracing.d.ts +4 -0
  50. package/dist/modules/tracing.js +263 -0
  51. package/dist/modules/tracingV2.d.ts +2 -0
  52. package/dist/modules/tracingV2.js +300 -0
  53. package/dist/modules/truncation.d.ts +24 -0
  54. package/dist/modules/truncation.js +303 -0
  55. package/dist/modules/validation.d.ts +6 -0
  56. package/dist/modules/validation.js +51 -0
  57. package/dist/network.d.ts +83 -0
  58. package/dist/network.js +411 -0
  59. package/dist/proxy.d.ts +31 -0
  60. package/dist/proxy.js +158 -0
  61. package/dist/thirdparty/ksuid/base-convert-int-array.js +49 -0
  62. package/dist/thirdparty/ksuid/base62.js +24 -0
  63. package/dist/thirdparty/ksuid/index.d.ts +30 -0
  64. package/dist/thirdparty/ksuid/index.js +201 -0
  65. package/dist/types.d.ts +197 -0
  66. package/dist/types.js +5 -0
  67. package/dist/validate.d.ts +53 -0
  68. package/dist/validate.js +139 -0
  69. package/package.json +43 -0
@@ -0,0 +1,74 @@
1
+ import { createRequire } from "module";
2
+ // Lazy-loaded module references for Node.js file logging
3
+ // These are loaded dynamically to support edge environments (Cloudflare Workers, etc.)
4
+ let fsModule = null;
5
+ let logFilePath = null;
6
+ let initAttempted = false;
7
+ let useConsoleFallback = false;
8
+ let diagnosticsSink = null;
9
+ export function setDiagnosticsSink(fn) {
10
+ diagnosticsSink = fn;
11
+ }
12
+ /**
13
+ * Attempts to initialize Node.js file logging.
14
+ * Falls back to console.log in edge environments where fs/os modules are unavailable.
15
+ */
16
+ function tryInitSync() {
17
+ if (initAttempted)
18
+ return;
19
+ initAttempted = true;
20
+ try {
21
+ // Use createRequire for ESM compatibility
22
+ // Works in Node.js ESM/CJS, fails gracefully in Workers/edge environments
23
+ const require = createRequire(import.meta.url);
24
+ const fs = require("fs");
25
+ const os = require("os");
26
+ const path = require("path");
27
+ const home = os.homedir?.();
28
+ if (home) {
29
+ fsModule = fs;
30
+ logFilePath = path.join(home, "within-sdk.log");
31
+ }
32
+ else {
33
+ // homedir() returned null/undefined - use console fallback
34
+ useConsoleFallback = true;
35
+ }
36
+ }
37
+ catch {
38
+ // Module not available or homedir() not implemented - use console fallback
39
+ useConsoleFallback = true;
40
+ fsModule = null;
41
+ logFilePath = null;
42
+ }
43
+ }
44
+ export function writeToLog(message) {
45
+ tryInitSync();
46
+ const timestamp = new Date().toISOString();
47
+ const logEntry = `[${timestamp}] ${message}`;
48
+ // Tee to diagnostics if registered. Must never break logging.
49
+ try {
50
+ diagnosticsSink?.(logEntry);
51
+ }
52
+ catch {
53
+ // diagnostics must never break logging
54
+ }
55
+ if (useConsoleFallback) {
56
+ console.log(`[within-sdk] ${logEntry}`);
57
+ return;
58
+ }
59
+ // Node.js environment: write to file
60
+ if (!logFilePath || !fsModule) {
61
+ return;
62
+ }
63
+ try {
64
+ if (!fsModule.existsSync(logFilePath)) {
65
+ fsModule.writeFileSync(logFilePath, logEntry + "\n");
66
+ }
67
+ else {
68
+ fsModule.appendFileSync(logFilePath, logEntry + "\n");
69
+ }
70
+ }
71
+ catch {
72
+ // Silently fail to avoid breaking the server
73
+ }
74
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * MCP SDK Compatibility Helpers
3
+ *
4
+ * Internal utilities for handling differences between MCP SDK versions.
5
+ * These helpers abstract away SDK-internal details like:
6
+ * - Tool callback/handler property names (changed in SDK 1.24)
7
+ * - Zod schema internal structures (v3 vs v4)
8
+ */
9
+ import { RegisteredTool, ToolCallback } from "../types.js";
10
+ export type ToolFunctionKey = "callback" | "handler";
11
+ /**
12
+ * Returns the tool function (callback/handler) from a RegisteredTool.
13
+ * Supports both MCP SDK 1.23- (callback) and 1.24+ (handler).
14
+ */
15
+ export declare function getToolFunction(tool: RegisteredTool): ToolCallback;
16
+ /**
17
+ * Returns the property key name used for the tool function ("callback" or "handler").
18
+ * This preserves the original property name when wrapping tools.
19
+ */
20
+ export declare function getToolFunctionKey(tool: RegisteredTool): ToolFunctionKey;
21
+ /**
22
+ * Returns true if the tool has a callback or handler property.
23
+ */
24
+ export declare function hasToolFunction(tool: unknown): tool is RegisteredTool;
25
+ /**
26
+ * Creates a new tool object with the wrapped function, preserving the original property name.
27
+ * This ensures MCP SDK 1.24+ gets back a tool with "handler" and 1.23- gets "callback".
28
+ */
29
+ export declare function createWrappedTool(originalTool: RegisteredTool, wrappedFunction: ToolCallback): RegisteredTool;
30
+ export declare function isZ4Schema(schema: unknown): boolean;
31
+ export declare function getObjectShape(schema: unknown): Record<string, unknown> | undefined;
32
+ export declare function getLiteralValue(schema: unknown): unknown;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * MCP SDK Compatibility Helpers
3
+ *
4
+ * Internal utilities for handling differences between MCP SDK versions.
5
+ * These helpers abstract away SDK-internal details like:
6
+ * - Tool callback/handler property names (changed in SDK 1.24)
7
+ * - Zod schema internal structures (v3 vs v4)
8
+ */
9
+ /**
10
+ * Returns the tool function (callback/handler) from a RegisteredTool.
11
+ * Supports both MCP SDK 1.23- (callback) and 1.24+ (handler).
12
+ */
13
+ export function getToolFunction(tool) {
14
+ if ("handler" in tool && typeof tool.handler === "function") {
15
+ return tool.handler;
16
+ }
17
+ if ("callback" in tool && typeof tool.callback === "function") {
18
+ return tool.callback;
19
+ }
20
+ throw new Error("Tool has neither callback nor handler property");
21
+ }
22
+ /**
23
+ * Returns the property key name used for the tool function ("callback" or "handler").
24
+ * This preserves the original property name when wrapping tools.
25
+ */
26
+ export function getToolFunctionKey(tool) {
27
+ if ("handler" in tool && typeof tool.handler === "function") {
28
+ return "handler";
29
+ }
30
+ return "callback";
31
+ }
32
+ /**
33
+ * Returns true if the tool has a callback or handler property.
34
+ */
35
+ export function hasToolFunction(tool) {
36
+ if (!tool || typeof tool !== "object")
37
+ return false;
38
+ const t = tool;
39
+ return (("handler" in t && typeof t.handler === "function") ||
40
+ ("callback" in t && typeof t.callback === "function"));
41
+ }
42
+ /**
43
+ * Creates a new tool object with the wrapped function, preserving the original property name.
44
+ * This ensures MCP SDK 1.24+ gets back a tool with "handler" and 1.23- gets "callback".
45
+ */
46
+ export function createWrappedTool(originalTool, wrappedFunction) {
47
+ const key = getToolFunctionKey(originalTool);
48
+ return {
49
+ ...originalTool,
50
+ [key]: wrappedFunction,
51
+ };
52
+ }
53
+ export function isZ4Schema(schema) {
54
+ if (!schema || typeof schema !== "object")
55
+ return false;
56
+ return !!schema._zod;
57
+ }
58
+ export function getObjectShape(schema) {
59
+ if (!schema || typeof schema !== "object")
60
+ return undefined;
61
+ let rawShape;
62
+ if (isZ4Schema(schema)) {
63
+ const v4Schema = schema;
64
+ rawShape = v4Schema._zod?.def?.shape;
65
+ }
66
+ else {
67
+ const v3Schema = schema;
68
+ // Try .shape first, then fall back to _def.shape (some v3 schema types store it there)
69
+ rawShape = v3Schema.shape ?? v3Schema._def?.shape;
70
+ }
71
+ if (!rawShape)
72
+ return undefined;
73
+ if (typeof rawShape === "function") {
74
+ try {
75
+ return rawShape();
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ return rawShape;
82
+ }
83
+ export function getLiteralValue(schema) {
84
+ if (!schema || typeof schema !== "object")
85
+ return undefined;
86
+ if (isZ4Schema(schema)) {
87
+ const v4Schema = schema;
88
+ const def = v4Schema._zod?.def;
89
+ if (def?.value !== undefined)
90
+ return def.value;
91
+ // Fallback: values array (for enums)
92
+ if (Array.isArray(def?.values) && def.values.length > 0) {
93
+ return def.values[0];
94
+ }
95
+ }
96
+ else {
97
+ const v3Schema = schema;
98
+ const def = v3Schema._def;
99
+ if (def?.value !== undefined)
100
+ return def.value;
101
+ // Fallback: values array (for enums)
102
+ if (Array.isArray(def?.values) && def.values.length > 0) {
103
+ return def.values[0];
104
+ }
105
+ }
106
+ // Final fallback: direct .value property (some Zod versions)
107
+ const directValue = schema.value;
108
+ if (directValue !== undefined)
109
+ return directValue;
110
+ return undefined;
111
+ }
@@ -0,0 +1,15 @@
1
+ export interface RedactionOptions {
2
+ maxFieldBytes?: number;
3
+ maxEventBytes?: number;
4
+ redactKeys?: string[];
5
+ }
6
+ export interface UserIdentityLike {
7
+ userId: string;
8
+ userName?: string;
9
+ userData?: Record<string, unknown>;
10
+ }
11
+ export declare function hashWithinSubject(vendorSlug: string, subject: string): string;
12
+ export declare function hashWithinAnonymous(vendorSlug: string, value: string): string;
13
+ export declare function sanitizeIdentity(vendorSlug: string, identity: UserIdentityLike, options?: RedactionOptions): UserIdentityLike;
14
+ export declare function redactWithinValue(value: unknown, options?: RedactionOptions): unknown;
15
+ export declare function stripForbiddenIdentityFields(value: unknown): unknown;
@@ -0,0 +1,179 @@
1
+ import { createHash } from "node:crypto";
2
+ const DEFAULT_SECRET_KEY_PATTERNS = [
3
+ "auth",
4
+ "authorization",
5
+ "cookie",
6
+ "email",
7
+ "e_mail",
8
+ "full_name",
9
+ "identity",
10
+ "firstname",
11
+ "first_name",
12
+ "lastname",
13
+ "last_name",
14
+ "org_domain",
15
+ "orgdomain",
16
+ "phone",
17
+ "requester_name",
18
+ "author_name",
19
+ "customer_name",
20
+ "user_name",
21
+ "username",
22
+ "token",
23
+ "secret",
24
+ "password",
25
+ "api_key",
26
+ "apikey",
27
+ "access_token",
28
+ "refresh_token",
29
+ "ssn",
30
+ "card",
31
+ "cvv",
32
+ ];
33
+ const FORBIDDEN_IDENTITY_KEYS = new Set([
34
+ "identity",
35
+ "email",
36
+ "org_domain",
37
+ "orgdomain",
38
+ "full_name",
39
+ "firstname",
40
+ "first_name",
41
+ "lastname",
42
+ "last_name",
43
+ "phone",
44
+ "username",
45
+ "user_name",
46
+ ]);
47
+ export function hashWithinSubject(vendorSlug, subject) {
48
+ return createHash("sha256")
49
+ .update(`subject:${vendorSlug.trim().toLowerCase()}:${subject.trim()}`)
50
+ .digest("hex");
51
+ }
52
+ export function hashWithinAnonymous(vendorSlug, value) {
53
+ return createHash("sha256")
54
+ .update(`anonymous:${vendorSlug.trim().toLowerCase()}:${value.trim()}`)
55
+ .digest("hex");
56
+ }
57
+ export function sanitizeIdentity(vendorSlug, identity, options = {}) {
58
+ return {
59
+ userId: hashWithinSubject(vendorSlug, identity.userId),
60
+ userData: redactWithinValue(identity.userData ?? {}, options),
61
+ };
62
+ }
63
+ export function redactWithinValue(value, options = {}) {
64
+ const seen = new WeakSet();
65
+ const redacted = redactAny(stripForbiddenIdentityFields(value), {
66
+ maxFieldBytes: options.maxFieldBytes ?? 10_000,
67
+ maxEventBytes: options.maxEventBytes ?? 128_000,
68
+ redactKeys: options.redactKeys,
69
+ }, seen, "");
70
+ return enforceEventLimit(redacted, options.maxEventBytes ?? 128_000);
71
+ }
72
+ export function stripForbiddenIdentityFields(value) {
73
+ if (Array.isArray(value))
74
+ return value.map(stripForbiddenIdentityFields);
75
+ if (!value || typeof value !== "object")
76
+ return value;
77
+ const out = {};
78
+ for (const [key, child] of Object.entries(value)) {
79
+ if (FORBIDDEN_IDENTITY_KEYS.has(normalizeKey(key)))
80
+ continue;
81
+ out[key] = stripForbiddenIdentityFields(child);
82
+ }
83
+ return out;
84
+ }
85
+ function redactAny(value, options, seen, key) {
86
+ if (isSecretKey(key, options.redactKeys))
87
+ return "[redacted]";
88
+ if (value == null)
89
+ return value;
90
+ if (typeof value === "string")
91
+ return truncateString(redactSensitiveString(value), options.maxFieldBytes);
92
+ if (typeof value === "number" || typeof value === "boolean")
93
+ return value;
94
+ if (typeof value === "bigint")
95
+ return value.toString();
96
+ if (typeof value === "function" || typeof value === "symbol")
97
+ return undefined;
98
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value))
99
+ return "[binary omitted]";
100
+ if (value instanceof Error) {
101
+ return {
102
+ name: value.name,
103
+ message: truncateString(redactSensitiveString(value.message), options.maxFieldBytes),
104
+ };
105
+ }
106
+ if (Array.isArray(value)) {
107
+ if (seen.has(value))
108
+ return "[circular]";
109
+ seen.add(value);
110
+ return value.slice(0, 100).map((item) => redactAny(item, options, seen, key));
111
+ }
112
+ if (typeof value === "object") {
113
+ if (seen.has(value))
114
+ return "[circular]";
115
+ seen.add(value);
116
+ const out = {};
117
+ for (const [childKey, childValue] of Object.entries(value)) {
118
+ const redacted = redactAny(childValue, options, seen, childKey);
119
+ if (redacted !== undefined)
120
+ out[childKey] = redacted;
121
+ }
122
+ return out;
123
+ }
124
+ return String(value);
125
+ }
126
+ function isSecretKey(key, extra) {
127
+ const normalized = normalizeKey(key);
128
+ const patterns = extra && extra.length > 0
129
+ ? [...DEFAULT_SECRET_KEY_PATTERNS, ...extra.map((v) => normalizeKey(v))]
130
+ : DEFAULT_SECRET_KEY_PATTERNS;
131
+ return patterns.some((pattern) => normalized.includes(pattern));
132
+ }
133
+ function normalizeKey(key) {
134
+ return key.toLowerCase().replace(/[-\s]/g, "_");
135
+ }
136
+ function redactSensitiveString(value) {
137
+ if (isSensitiveString(value))
138
+ return "[redacted]";
139
+ return value
140
+ .replace(EMAIL_GLOBAL_RE, "[redacted]")
141
+ .replace(BEARER_GLOBAL_RE, "[redacted]")
142
+ .replace(API_KEY_GLOBAL_RE, "[redacted]");
143
+ }
144
+ function isSensitiveString(value) {
145
+ const trimmed = value.trim();
146
+ if (!trimmed)
147
+ return false;
148
+ return EMAIL_RE.test(trimmed)
149
+ || PHONE_RE.test(trimmed)
150
+ || SSN_RE.test(trimmed)
151
+ || CREDIT_CARD_RE.test(trimmed)
152
+ || BEARER_RE.test(trimmed)
153
+ || API_KEY_RE.test(trimmed);
154
+ }
155
+ function truncateString(value, maxBytes) {
156
+ if (Buffer.byteLength(value, "utf8") <= maxBytes)
157
+ return value;
158
+ return `${value.slice(0, maxBytes)}[truncated ${Buffer.byteLength(value, "utf8")} bytes]`;
159
+ }
160
+ function enforceEventLimit(value, maxBytes) {
161
+ try {
162
+ const json = JSON.stringify(value);
163
+ if (!json || Buffer.byteLength(json, "utf8") <= maxBytes)
164
+ return value;
165
+ return { omitted: true, reason: `payload exceeded ${maxBytes} bytes` };
166
+ }
167
+ catch {
168
+ return { omitted: true, reason: "payload was not serializable" };
169
+ }
170
+ }
171
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
172
+ const EMAIL_GLOBAL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
173
+ const PHONE_RE = /^\+?[\d().\-\s]{8,}$/;
174
+ const SSN_RE = /^\d{3}-?\d{2}-?\d{4}$/;
175
+ const CREDIT_CARD_RE = /^(?:\d[ -]*?){13,19}$/;
176
+ const BEARER_RE = /^bearer\s+[a-z0-9._~+/=-]+$/i;
177
+ const API_KEY_RE = /^(?:sk|pk|api|key)_[a-z0-9_\-]{16,}$/i;
178
+ const BEARER_GLOBAL_RE = /\bbearer\s+[a-z0-9._~+/=-]+/gi;
179
+ const API_KEY_GLOBAL_RE = /\b(?:sk|pk|api|key)_[a-z0-9_\-]{16,}\b/gi;
@@ -0,0 +1,11 @@
1
+ import { Event, RedactFunction, UnredactedEvent } from "../types.js";
2
+ /**
3
+ * Applies the customer's redaction function to all string fields in an Event object.
4
+ * This is the main entry point for redacting sensitive information from events
5
+ * before they are sent to the analytics service.
6
+ *
7
+ * @param event - The event to redact
8
+ * @param redactFn - The customer's redaction function
9
+ * @returns A new event object with all strings redacted
10
+ */
11
+ export declare function redactEvent(event: UnredactedEvent, redactFn: RedactFunction): Promise<Event>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Set of field names that should be protected from redaction.
3
+ * These fields contain system-level identifiers and metadata that
4
+ * need to be preserved for analytics tracking.
5
+ */
6
+ const PROTECTED_FIELDS = new Set([
7
+ "sessionId",
8
+ "id",
9
+ "projectId",
10
+ "server",
11
+ "identifyActorGivenId",
12
+ "identifyActorName",
13
+ "identifyData",
14
+ "resourceName",
15
+ "eventType",
16
+ "actorId",
17
+ "tags",
18
+ "properties",
19
+ ]);
20
+ /**
21
+ * Recursively applies a redaction function to all string values in an object.
22
+ * This ensures that sensitive information is removed from all string fields
23
+ * before events are sent to the analytics service.
24
+ *
25
+ * @param obj - The object to redact strings from
26
+ * @param redactFn - The redaction function to apply to each string
27
+ * @param path - The current path in the object tree (used to check protected fields)
28
+ * @param isProtected - Whether the current object/value is within a protected field
29
+ * @returns A new object with all strings redacted
30
+ */
31
+ async function redactStringsInObject(obj, redactFn, path = "", isProtected = false) {
32
+ if (obj === null || obj === undefined) {
33
+ return obj;
34
+ }
35
+ // Handle strings
36
+ if (typeof obj === "string") {
37
+ // Don't redact if this field or any parent field is protected
38
+ if (isProtected) {
39
+ return obj;
40
+ }
41
+ return await redactFn(obj);
42
+ }
43
+ // Handle arrays
44
+ if (Array.isArray(obj)) {
45
+ return Promise.all(obj.map((item, index) => redactStringsInObject(item, redactFn, `${path}[${index}]`, isProtected)));
46
+ }
47
+ // Handle dates (don't redact)
48
+ if (obj instanceof Date) {
49
+ return obj;
50
+ }
51
+ // Handle objects
52
+ if (typeof obj === "object") {
53
+ const redactedObj = {};
54
+ for (const [key, value] of Object.entries(obj)) {
55
+ // Skip functions and undefined values
56
+ if (typeof value === "function" || value === undefined) {
57
+ continue;
58
+ }
59
+ // Build the path for nested fields
60
+ const fieldPath = path ? `${path}.${key}` : key;
61
+ // Check if this field is protected (only check at top level)
62
+ const isFieldProtected = isProtected || (path === "" && PROTECTED_FIELDS.has(key));
63
+ redactedObj[key] = await redactStringsInObject(value, redactFn, fieldPath, isFieldProtected);
64
+ }
65
+ return redactedObj;
66
+ }
67
+ // For all other types (numbers, booleans, etc.), return as-is
68
+ return obj;
69
+ }
70
+ /**
71
+ * Applies the customer's redaction function to all string fields in an Event object.
72
+ * This is the main entry point for redacting sensitive information from events
73
+ * before they are sent to the analytics service.
74
+ *
75
+ * @param event - The event to redact
76
+ * @param redactFn - The customer's redaction function
77
+ * @returns A new event object with all strings redacted
78
+ */
79
+ export async function redactEvent(event, redactFn) {
80
+ return redactStringsInObject(event, redactFn, "", false);
81
+ }
@@ -0,0 +1,9 @@
1
+ import { Event, UnredactedEvent } from "../types.js";
2
+ /**
3
+ * Sanitizes an event by redacting non-text content blocks from responses
4
+ * and large base64-encoded strings from parameters.
5
+ *
6
+ * This is a synchronous operation that returns a new object without mutating the original.
7
+ * It should run after customer redaction in the event pipeline.
8
+ */
9
+ export declare function sanitizeEvent<T extends Event | UnredactedEvent>(event: T): T;
@@ -0,0 +1,111 @@
1
+ const BASE64_PATTERN = /^[A-Za-z0-9+/\n\r]+=*$/;
2
+ const SIZE_GATE = 10240; // 10KB - skip strings shorter than this
3
+ /**
4
+ * Sanitizes an event by redacting non-text content blocks from responses
5
+ * and large base64-encoded strings from parameters.
6
+ *
7
+ * This is a synchronous operation that returns a new object without mutating the original.
8
+ * It should run after customer redaction in the event pipeline.
9
+ */
10
+ export function sanitizeEvent(event) {
11
+ const result = { ...event };
12
+ if (result.response != null) {
13
+ result.response = sanitizeResponse(result.response);
14
+ }
15
+ if (result.parameters != null) {
16
+ result.parameters = sanitizeParameters(result.parameters);
17
+ }
18
+ return result;
19
+ }
20
+ /**
21
+ * Sanitizes response content blocks by replacing non-text content types
22
+ * with informative redaction messages.
23
+ */
24
+ function sanitizeResponse(response) {
25
+ if (response == null || typeof response !== "object") {
26
+ return response;
27
+ }
28
+ const result = { ...response };
29
+ if (Array.isArray(result.content)) {
30
+ result.content = result.content.map(sanitizeContentBlock);
31
+ }
32
+ if (result.structuredContent != null &&
33
+ typeof result.structuredContent === "object") {
34
+ result.structuredContent = sanitizeParameters(result.structuredContent);
35
+ }
36
+ return result;
37
+ }
38
+ /**
39
+ * Sanitizes a single content block based on its type discriminator.
40
+ */
41
+ function sanitizeContentBlock(block) {
42
+ if (block == null || typeof block !== "object") {
43
+ return block;
44
+ }
45
+ switch (block.type) {
46
+ case "text":
47
+ return block;
48
+ case "image":
49
+ return {
50
+ type: "text",
51
+ text: "[image content redacted - not supported by Within SDK]",
52
+ };
53
+ case "audio":
54
+ return {
55
+ type: "text",
56
+ text: "[audio content redacted - not supported by Within SDK]",
57
+ };
58
+ case "resource":
59
+ return sanitizeResourceBlock(block);
60
+ case "resource_link":
61
+ return block;
62
+ default:
63
+ return {
64
+ type: "text",
65
+ text: `[unsupported content type "${block.type}" redacted - not supported by Within SDK]`,
66
+ };
67
+ }
68
+ }
69
+ /**
70
+ * Sanitizes an embedded resource content block.
71
+ * BlobResourceContents (has `blob` field) are redacted.
72
+ * TextResourceContents (has `text` field) pass through.
73
+ */
74
+ function sanitizeResourceBlock(block) {
75
+ if (block.resource && block.resource.blob !== undefined) {
76
+ return {
77
+ type: "text",
78
+ text: "[binary resource content redacted - not supported by Within SDK]",
79
+ };
80
+ }
81
+ return block;
82
+ }
83
+ /**
84
+ * Recursively scans parameters for large base64-encoded strings and replaces them.
85
+ * Uses a size gate (10KB) to avoid regex testing on small strings.
86
+ */
87
+ function sanitizeParameters(obj) {
88
+ if (obj == null) {
89
+ return obj;
90
+ }
91
+ if (typeof obj === "string") {
92
+ if (obj.length >= SIZE_GATE && BASE64_PATTERN.test(obj)) {
93
+ return "[binary data redacted - not supported by Within SDK]";
94
+ }
95
+ return obj;
96
+ }
97
+ if (Array.isArray(obj)) {
98
+ return obj.map(sanitizeParameters);
99
+ }
100
+ if (obj instanceof Date) {
101
+ return obj;
102
+ }
103
+ if (typeof obj === "object") {
104
+ const result = {};
105
+ for (const [key, value] of Object.entries(obj)) {
106
+ result[key] = sanitizeParameters(value);
107
+ }
108
+ return result;
109
+ }
110
+ return obj;
111
+ }
@@ -0,0 +1,22 @@
1
+ import { WithinTrackingData, MCPServerLike, SessionInfo, CompatibleRequestHandlerExtra } from "../types.js";
2
+ export declare function newSessionId(): string;
3
+ /**
4
+ * Creates a deterministic KSUID session ID from an MCP sessionId and optional projectId.
5
+ * The same inputs will always produce the same session ID, enabling correlation across server restarts.
6
+ *
7
+ * @param mcpSessionId - The session ID from the MCP protocol
8
+ * @param projectId - Optional Within project ID to include in the hash
9
+ * @returns A KSUID with "ses" prefix derived deterministically from the inputs
10
+ */
11
+ export declare function deriveSessionIdFromMCPSession(mcpSessionId: string, projectId?: string): string;
12
+ /**
13
+ * Gets or generates a session ID for the server.
14
+ * Prioritizes MCP protocol sessionId over Within-generated sessionId.
15
+ *
16
+ * @param server - The MCP server instance
17
+ * @param extra - Optional extra data containing MCP sessionId
18
+ * @returns The session ID to use for events
19
+ */
20
+ export declare function getServerSessionId(server: MCPServerLike, extra?: CompatibleRequestHandlerExtra): string;
21
+ export declare function setLastActivity(server: MCPServerLike): void;
22
+ export declare function getSessionInfo(server: MCPServerLike, data: WithinTrackingData | undefined): SessionInfo;