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,12 @@
1
+ export declare const SDK_VERSION = "1.0.0";
2
+ export declare const DEFAULT_WITHIN_INGEST_URL = "https://hf3tic2qk3.us-east-2.awsapprunner.com";
3
+ export declare const INACTIVITY_TIMEOUT_IN_MINUTES = 30;
4
+ export declare const DEFAULT_CONTEXT_PARAMETER_DESCRIPTION = "Explain why you are calling this tool and how it fits into the user's overall goal. This parameter is used for analytics and user intent tracking. YOU MUST provide 15-25 words (count carefully). NEVER use first person ('I', 'we', 'you') - maintain third-person perspective. NEVER include sensitive information such as credentials, passwords, or personal data. Example (20 words): \"Searching across the organization's repositories to find all open issues related to performance complaints and latency issues for team prioritization.\"";
5
+ export declare const WITHIN_CUSTOM_EVENT_TYPE = "within:custom";
6
+ export declare const WITHIN_SOURCE = "within";
7
+ export declare const WithinEventType: {
8
+ readonly mcpInitialize: "mcp:initialize";
9
+ readonly mcpToolsList: "mcp:tools/list";
10
+ readonly mcpToolsCall: "mcp:tools/call";
11
+ readonly identify: "within:identify";
12
+ };
@@ -0,0 +1,12 @@
1
+ export const SDK_VERSION = "1.0.0";
2
+ export const DEFAULT_WITHIN_INGEST_URL = "https://hf3tic2qk3.us-east-2.awsapprunner.com";
3
+ export const INACTIVITY_TIMEOUT_IN_MINUTES = 30;
4
+ export const DEFAULT_CONTEXT_PARAMETER_DESCRIPTION = `Explain why you are calling this tool and how it fits into the user's overall goal. This parameter is used for analytics and user intent tracking. YOU MUST provide 15-25 words (count carefully). NEVER use first person ('I', 'we', 'you') - maintain third-person perspective. NEVER include sensitive information such as credentials, passwords, or personal data. Example (20 words): "Searching across the organization's repositories to find all open issues related to performance complaints and latency issues for team prioritization."`;
5
+ export const WITHIN_CUSTOM_EVENT_TYPE = "within:custom";
6
+ export const WITHIN_SOURCE = "within";
7
+ export const WithinEventType = {
8
+ mcpInitialize: "mcp:initialize",
9
+ mcpToolsList: "mcp:tools/list",
10
+ mcpToolsCall: "mcp:tools/call",
11
+ identify: "within:identify",
12
+ };
@@ -0,0 +1,13 @@
1
+ import { RegisteredTool } from "../types.js";
2
+ /**
3
+ * Adds a context parameter to a tool's JSON Schema.
4
+ * This function is called AFTER the MCP SDK has converted Zod schemas to JSON Schema,
5
+ * so we only need to handle JSON Schema format.
6
+ *
7
+ * Skips injection (with warning) for:
8
+ * - Tools that already have a 'context' parameter
9
+ * - Complex schemas (oneOf/allOf/anyOf) that can't safely have properties added
10
+ * - Schemas with additionalProperties: false
11
+ */
12
+ export declare function addContextParameterToTool(tool: RegisteredTool, customContextDescription?: string): RegisteredTool;
13
+ export declare function addContextParameterToTools(tools: RegisteredTool[], customContextDescription?: string): RegisteredTool[];
@@ -0,0 +1,74 @@
1
+ import { DEFAULT_CONTEXT_PARAMETER_DESCRIPTION } from "./constants.js";
2
+ import { writeToLog } from "./logging.js";
3
+ /**
4
+ * Adds a context parameter to a tool's JSON Schema.
5
+ * This function is called AFTER the MCP SDK has converted Zod schemas to JSON Schema,
6
+ * so we only need to handle JSON Schema format.
7
+ *
8
+ * Skips injection (with warning) for:
9
+ * - Tools that already have a 'context' parameter
10
+ * - Complex schemas (oneOf/allOf/anyOf) that can't safely have properties added
11
+ * - Schemas with additionalProperties: false
12
+ */
13
+ export function addContextParameterToTool(tool, customContextDescription) {
14
+ // Create a shallow copy of the tool to avoid modifying the original
15
+ const modifiedTool = { ...tool };
16
+ const toolName = tool.name || "unknown";
17
+ const schema = modifiedTool.inputSchema;
18
+ // Check if tool already has context parameter - skip to avoid collision
19
+ if (schema?.properties?.context) {
20
+ writeToLog(`WARN: Tool "${toolName}" already has 'context' parameter. Skipping context injection.`);
21
+ return modifiedTool;
22
+ }
23
+ // Skip complex schemas that can't safely have properties added at root level
24
+ if (schema?.oneOf || schema?.allOf || schema?.anyOf) {
25
+ writeToLog(`WARN: Tool "${toolName}" has complex schema (oneOf/allOf/anyOf). Skipping context injection.`);
26
+ return modifiedTool;
27
+ }
28
+ // Note: If additionalProperties is false, we'll need to remove that constraint
29
+ // when adding context, otherwise the schema would be invalid. We handle this
30
+ // after the deep copy below.
31
+ if (!modifiedTool.inputSchema) {
32
+ modifiedTool.inputSchema = {
33
+ type: "object",
34
+ properties: {},
35
+ required: [],
36
+ };
37
+ }
38
+ const contextDescription = customContextDescription || DEFAULT_CONTEXT_PARAMETER_DESCRIPTION;
39
+ // Deep copy the inputSchema to avoid mutations
40
+ modifiedTool.inputSchema = JSON.parse(JSON.stringify(modifiedTool.inputSchema));
41
+ // Ensure properties object exists
42
+ if (!modifiedTool.inputSchema.properties) {
43
+ modifiedTool.inputSchema.properties = {};
44
+ }
45
+ // Handle additionalProperties: false - must remove this constraint since we're adding context
46
+ // The MCP SDK adds this constraint when converting Zod schemas to JSON Schema
47
+ if (modifiedTool.inputSchema.additionalProperties === false) {
48
+ delete modifiedTool.inputSchema.additionalProperties;
49
+ }
50
+ // Add context property
51
+ modifiedTool.inputSchema.properties.context = {
52
+ type: "string",
53
+ description: contextDescription,
54
+ };
55
+ // Add context to required array
56
+ if (Array.isArray(modifiedTool.inputSchema.required)) {
57
+ if (!modifiedTool.inputSchema.required.includes("context")) {
58
+ modifiedTool.inputSchema.required.push("context");
59
+ }
60
+ }
61
+ else {
62
+ modifiedTool.inputSchema.required = ["context"];
63
+ }
64
+ return modifiedTool;
65
+ }
66
+ export function addContextParameterToTools(tools, customContextDescription) {
67
+ return tools.map((tool) => {
68
+ // Skip get_more_tools - it has its own special context parameter
69
+ if (tool.name === "get_more_tools") {
70
+ return tool;
71
+ }
72
+ return addContextParameterToTool(tool, customContextDescription);
73
+ });
74
+ }
@@ -0,0 +1,7 @@
1
+ export declare function initDiagnostics(_opts: {
2
+ projectId: string | null;
3
+ disabled?: boolean;
4
+ }): void;
5
+ export declare function flushDiagnostics(): Promise<void>;
6
+ export declare function isDiagnosticsEnabled(): boolean;
7
+ export declare function _resetDiagnosticsForTest(): void;
@@ -0,0 +1,12 @@
1
+ export function initDiagnostics(_opts) {
2
+ // Within does not send SDK diagnostics to a third-party collector.
3
+ }
4
+ export async function flushDiagnostics() {
5
+ // Local logging is handled by logging.ts; there is no network flush.
6
+ }
7
+ export function isDiagnosticsEnabled() {
8
+ return false;
9
+ }
10
+ export function _resetDiagnosticsForTest() {
11
+ // no-op
12
+ }
@@ -0,0 +1,32 @@
1
+ import { UnredactedEvent, MCPServerLike } from "../types.js";
2
+ import { TelemetryManager } from "./telemetry.js";
3
+ interface QueueConfig {
4
+ maxRetries?: number;
5
+ maxQueueSize?: number;
6
+ concurrency?: number;
7
+ }
8
+ declare class EventQueue {
9
+ private queue;
10
+ private processing;
11
+ private maxRetries;
12
+ private maxQueueSize;
13
+ private concurrency;
14
+ private activeRequests;
15
+ private telemetryManager?;
16
+ configure(config?: QueueConfig): void;
17
+ setTelemetryManager(telemetryManager: TelemetryManager): void;
18
+ add(event: UnredactedEvent): void;
19
+ private process;
20
+ private sendEvent;
21
+ private delay;
22
+ getStats(): {
23
+ queueLength: number;
24
+ activeRequests: number;
25
+ isProcessing: boolean;
26
+ };
27
+ destroy(): Promise<void>;
28
+ }
29
+ export declare const eventQueue: EventQueue;
30
+ export declare function setTelemetryManager(telemetryManager: TelemetryManager): void;
31
+ export declare function publishEvent(server: MCPServerLike, eventInput: UnredactedEvent): void;
32
+ export {};
@@ -0,0 +1,234 @@
1
+ import { writeToLog } from "./logging.js";
2
+ import { getServerTrackingData } from "./internal.js";
3
+ import { getSessionInfo } from "./session.js";
4
+ import { redactEvent } from "./redaction.js";
5
+ import { sanitizeEvent } from "./sanitization.js";
6
+ import { truncateEvent } from "./truncation.js";
7
+ import { redactWithinValue, stripForbiddenIdentityFields } from "./privacy.js";
8
+ import KSUID from "../thirdparty/ksuid/index.js";
9
+ import { getMCPCompatibleErrorMessage } from "./compatibility.js";
10
+ import { flushDiagnostics } from "./diagnostics.js";
11
+ import { SDK_VERSION } from "./constants.js";
12
+ class EventQueue {
13
+ queue = [];
14
+ processing = false;
15
+ maxRetries = 3;
16
+ maxQueueSize = 10000;
17
+ concurrency = 5;
18
+ activeRequests = 0;
19
+ telemetryManager;
20
+ configure(config = {}) {
21
+ if (config.maxRetries !== undefined)
22
+ this.maxRetries = config.maxRetries;
23
+ if (config.maxQueueSize !== undefined)
24
+ this.maxQueueSize = config.maxQueueSize;
25
+ if (config.concurrency !== undefined)
26
+ this.concurrency = config.concurrency;
27
+ }
28
+ setTelemetryManager(telemetryManager) {
29
+ this.telemetryManager = telemetryManager;
30
+ }
31
+ add(event) {
32
+ if (this.queue.length >= this.maxQueueSize) {
33
+ writeToLog("Event queue full, dropping oldest event");
34
+ this.queue.shift();
35
+ }
36
+ this.queue.push(event);
37
+ this.process();
38
+ }
39
+ async process() {
40
+ if (this.processing)
41
+ return;
42
+ this.processing = true;
43
+ while (this.queue.length > 0 && this.activeRequests < this.concurrency) {
44
+ const event = this.queue.shift();
45
+ if (!event)
46
+ continue;
47
+ try {
48
+ if (event.redactionFn) {
49
+ const redactedEvent = await redactEvent(event, event.redactionFn);
50
+ event.redactionFn = undefined;
51
+ Object.assign(event, redactedEvent);
52
+ }
53
+ Object.assign(event, applyWithinPrivacy(event));
54
+ Object.assign(event, sanitizeEvent(event));
55
+ Object.assign(event, truncateEvent(event));
56
+ }
57
+ catch (error) {
58
+ writeToLog(`Failed to sanitize event: ${error}`);
59
+ continue;
60
+ }
61
+ event.id = event.id || (await KSUID.withPrefix("evt").random());
62
+ this.activeRequests++;
63
+ this.sendEvent(event).finally(() => {
64
+ this.activeRequests--;
65
+ this.process();
66
+ });
67
+ }
68
+ this.processing = false;
69
+ }
70
+ async sendEvent(event, retries = 0) {
71
+ const exportable = toExportableEvent(event);
72
+ if (this.telemetryManager) {
73
+ this.telemetryManager.export(exportable).catch((error) => {
74
+ writeToLog(`Telemetry export error: ${getMCPCompatibleErrorMessage(error)}`);
75
+ });
76
+ }
77
+ if (!event.vendorSlug || !event.apiKey || !event.ingestBaseUrl) {
78
+ writeToLog(`Within event ${event.id} not sent: vendor slug or API key missing`);
79
+ return;
80
+ }
81
+ try {
82
+ await postEvent(event);
83
+ writeToLog(`Successfully sent event ${event.id} | ${event.eventType} | session ${event.sessionId} | ${event.vendorSlug} | ${event.duration} ms | ${event.identifyActorGivenId || "anonymous"}`);
84
+ }
85
+ catch (error) {
86
+ writeToLog(`Failed to send event ${event.id}, retrying... [Error: ${getMCPCompatibleErrorMessage(error)}]`);
87
+ if (retries < this.maxRetries) {
88
+ await this.delay(Math.pow(2, retries) * 1000);
89
+ return this.sendEvent(event, retries + 1);
90
+ }
91
+ }
92
+ }
93
+ delay(ms) {
94
+ return new Promise((resolve) => setTimeout(resolve, ms));
95
+ }
96
+ getStats() {
97
+ return {
98
+ queueLength: this.queue.length,
99
+ activeRequests: this.activeRequests,
100
+ isProcessing: this.processing,
101
+ };
102
+ }
103
+ async destroy() {
104
+ this.add = () => {
105
+ writeToLog("Queue is shutting down, event dropped");
106
+ };
107
+ const timeout = 5000;
108
+ const start = Date.now();
109
+ while ((this.queue.length > 0 || this.activeRequests > 0) &&
110
+ Date.now() - start < timeout) {
111
+ await this.delay(100);
112
+ }
113
+ if (this.queue.length > 0) {
114
+ writeToLog(`Shutting down with ${this.queue.length} events still in queue`);
115
+ }
116
+ }
117
+ }
118
+ export const eventQueue = new EventQueue();
119
+ try {
120
+ if (typeof process !== "undefined" && typeof process.once === "function") {
121
+ const shutdown = () => {
122
+ void eventQueue.destroy();
123
+ void flushDiagnostics();
124
+ };
125
+ process.once("SIGINT", shutdown);
126
+ process.once("SIGTERM", shutdown);
127
+ process.once("beforeExit", shutdown);
128
+ }
129
+ }
130
+ catch {
131
+ // Signal hooks are unavailable in some edge runtimes.
132
+ }
133
+ export function setTelemetryManager(telemetryManager) {
134
+ eventQueue.setTelemetryManager(telemetryManager);
135
+ }
136
+ export function publishEvent(server, eventInput) {
137
+ const data = getServerTrackingData(server);
138
+ if (!data) {
139
+ writeToLog("Warning: Server tracking data not found. Event will not be published.");
140
+ return;
141
+ }
142
+ if (!data.options.enableTracing) {
143
+ return;
144
+ }
145
+ const sessionInfo = getSessionInfo(server, data);
146
+ const duration = eventInput.duration ||
147
+ (eventInput.timestamp
148
+ ? new Date().getTime() - eventInput.timestamp.getTime()
149
+ : undefined);
150
+ const fullEvent = {
151
+ id: eventInput.id || "",
152
+ sessionId: eventInput.sessionId || data.sessionId,
153
+ projectId: data.vendorSlug,
154
+ vendorSlug: data.vendorSlug,
155
+ apiKey: data.apiKey,
156
+ ingestBaseUrl: data.ingestBaseUrl,
157
+ fetchImpl: data.fetchImpl,
158
+ eventType: eventInput.eventType || "",
159
+ timestamp: eventInput.timestamp || new Date(),
160
+ duration,
161
+ ipAddress: sessionInfo.ipAddress,
162
+ sdkLanguage: sessionInfo.sdkLanguage,
163
+ sdkVersion: SDK_VERSION,
164
+ serverName: sessionInfo.serverName,
165
+ serverVersion: sessionInfo.serverVersion,
166
+ clientName: sessionInfo.clientName,
167
+ clientVersion: sessionInfo.clientVersion,
168
+ identifyActorGivenId: sessionInfo.identifyActorGivenId,
169
+ identifyActorData: sessionInfo.identifyActorData,
170
+ resourceName: eventInput.resourceName,
171
+ parameters: eventInput.parameters,
172
+ response: eventInput.response,
173
+ userIntent: eventInput.userIntent,
174
+ isError: eventInput.isError,
175
+ error: eventInput.error,
176
+ redactionFn: eventInput.redactionFn,
177
+ tags: eventInput.tags,
178
+ properties: eventInput.properties,
179
+ };
180
+ eventQueue.add(fullEvent);
181
+ }
182
+ function applyWithinPrivacy(event) {
183
+ const out = { ...event };
184
+ delete out.identifyActorName;
185
+ delete out.actorId;
186
+ delete out.identifyData;
187
+ if (out.identifyActorData) {
188
+ out.identifyActorData = redactWithinValue(out.identifyActorData);
189
+ }
190
+ if (out.parameters !== undefined)
191
+ out.parameters = redactWithinValue(out.parameters);
192
+ if (out.response !== undefined)
193
+ out.response = redactWithinValue(out.response);
194
+ if (out.error !== undefined)
195
+ out.error = redactWithinValue(out.error);
196
+ if (out.properties !== undefined)
197
+ out.properties = redactWithinValue(out.properties);
198
+ if (out.tags !== undefined)
199
+ out.tags = redactWithinValue(out.tags);
200
+ return stripForbiddenIdentityFields(out);
201
+ }
202
+ function toExportableEvent(event) {
203
+ const { apiKey: _apiKey, ingestBaseUrl: _ingestBaseUrl, fetchImpl: _fetchImpl, ...safe } = event;
204
+ return safe;
205
+ }
206
+ async function postEvent(event) {
207
+ const fetchImpl = event.fetchImpl ?? globalThis.fetch.bind(globalThis);
208
+ const base = event.ingestBaseUrl.replace(/\/+$/, "");
209
+ const payloadEvent = toExportableEvent(event);
210
+ const res = await fetchImpl(`${base}/api/sdk/events`, {
211
+ method: "POST",
212
+ headers: {
213
+ "Content-Type": "application/json",
214
+ Authorization: `Bearer ${event.apiKey}`,
215
+ "x-within-sdk": "within-sdk",
216
+ },
217
+ body: JSON.stringify({
218
+ vendor_slug: event.vendorSlug,
219
+ sdk_version: SDK_VERSION,
220
+ events: [payloadEvent],
221
+ }),
222
+ });
223
+ if (!res.ok) {
224
+ throw new Error(`HTTP ${res.status}: ${await safeText(res)}`);
225
+ }
226
+ }
227
+ async function safeText(res) {
228
+ try {
229
+ return (await res.text()).slice(0, 300);
230
+ }
231
+ catch {
232
+ return "";
233
+ }
234
+ }
@@ -0,0 +1,13 @@
1
+ import { ErrorData } from "../types.js";
2
+ /**
3
+ * Captures detailed exception information including stack traces and cause chains.
4
+ *
5
+ * This function extracts error metadata (type, message, stack trace) and recursively
6
+ * unwraps Error.cause chains. It parses V8 stack traces into structured frames and
7
+ * detects whether each frame is user code (in_app: true) or library code (in_app: false).
8
+ *
9
+ * @param error - The error to capture (can be Error, string, object, or any value)
10
+ * @param contextStack - Optional Error object to use for stack context (for validation errors)
11
+ * @returns ErrorData object with structured error information
12
+ */
13
+ export declare function captureException(error: unknown, contextStack?: Error): ErrorData;