UNPKG

@composio/core

Version:

![Composio Banner](https://github.com/user-attachments/assets/9ba0e9c1-85a4-4b51-ae60-f9fe7992e819)

335 lines (330 loc) 11.4 kB
const require_chunk = require('./chunk-CQwRTUmo.cjs'); const require_ComposioError = require('./ComposioError-CYaI68Mo.cjs'); const require_buffer = require('./buffer-BjWptbNH.cjs'); let _composio_client = require("@composio/client"); //#region src/types/telemetry.types.ts const TELEMETRY_EVENTS = { SDK_INITIALIZED: "SDK_INITIALIZED", SDK_METHOD_INVOKED: "SDK_METHOD_INVOKED", SDK_METHOD_ERROR: "SDK_METHOD_ERROR", CLI_INVOKED: "CLI_INVOKED" }; //#endregion //#region src/telemetry/BatchProcessor.ts var BatchProcessor = class { batch = []; time; batchSize; processBatchCallback; timer = null; pendingBatches = /* @__PURE__ */ new Set(); constructor(time = 2e3, batchSize = 100, processBatchCallback) { this.batch = []; this.time = time; this.batchSize = batchSize; this.processBatchCallback = processBatchCallback; } pushItem(item) { this.batch.push(item); if (this.batch.length >= this.batchSize) this.processBatch(); else if (!this.timer) this.timer = setTimeout(() => this.processBatch(), this.time); } processBatch() { if (this.batch.length > 0) { const batchToProcess = this.batch; this.batch = []; const pending = this.processBatchCallback(batchToProcess).catch(() => {}).finally(() => { this.pendingBatches.delete(pending); }); this.pendingBatches.add(pending); } if (this.timer) { clearTimeout(this.timer); this.timer = null; } } /** * Flush any pending batches and wait for all of them to complete. * Useful for ensuring telemetry is sent before process exit. */ async flush() { this.processBatch(); if (this.pendingBatches.size > 0) await Promise.all(this.pendingBatches); } }; //#endregion //#region src/services/telemetry/TelemetryService.ts const TELEMETRY_URL = "https://telemetry.composio.dev/v1"; var TelemetryService = class { /** * Sends a metric to the Telemetry API. * @param payload - The payload to send to the Telemetry API. * @returns The response from the Telemetry API. */ static async sendMetric(payload) { try { return await fetch(`${TELEMETRY_URL}/metrics/invocations`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); } catch (error) { require_ComposioError.logger_default.debug("Error sending metric telemetry", error); } } /** * Sends an error log to the Telemetry API. * @param payload - The payload to send to the Telemetry API. * @returns The response from the Telemetry API. */ static async sendErrorLog(payload) { try { return await fetch(`${TELEMETRY_URL}/errors`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); } catch (error) { require_ComposioError.logger_default.debug("Error sending error telemetry", error); } } }; //#endregion //#region src/telemetry/Telemetry.ts /** * The Telemetry class is used to log the telemetry for any given instance which extends InstrumentedInstance. * * This class is used to instrument the telemetry for the given instance and send the telemetry to the server. * This class is also used to create a global error handler for the given instance. * * @example * * const telemetry = new Telemetry({...}); * const composio = new Composio({...}) * * telemetry.instrument(composio); * telemetry.instrument(composio.tools); * telemetry.instrument(composio.toolkits); * telemetry.instrument(composio.triggers); * */ var TelemetryTransport = class { telemetryMetadata; isTelemetryDisabled = true; telemetrySource; telemetrySourceName = "typescript-sdk"; telemetryServiceName = "sdk"; telemetryLanguage = "typescript"; exitHandlersRegistered = false; batchProcessor = new BatchProcessor(200, 10, async (data) => { require_ComposioError.logger_default.debug("Sending batch of telemetry metrics", data); await TelemetryService.sendMetric(data); }); setup(metadata) { this.telemetryMetadata = metadata; this.isTelemetryDisabled = false; this.telemetrySource = { host: this.telemetryMetadata?.host ?? this.telemetrySourceName, service: this.telemetryServiceName, language: this.telemetryLanguage, version: this.telemetryMetadata?.version, platform: this.telemetryMetadata?.isBrowser ? "browser" : "node", environment: require_ComposioError.getEnvVariable("NODE_ENV", "production") }; this.registerExitHandlers(); this.sendMetric([{ functionName: TELEMETRY_EVENTS.SDK_INITIALIZED, durationMs: 0, timestamp: Date.now() / 1e3, props: {}, source: this.telemetrySource, metadata: { provider: this.telemetryMetadata?.provider ?? "openai" }, error: void 0 }]); } /** * Instrument the telemetry for the given instance. * * You can pass the instance and the file name of the instance to instrument the telemetry. * This will instrument all the methods of the instance and log the telemetry for each method call. * @param instance - any instance that extends InstrumentedInstance * @param fileName - the file name of the instance * @returns */ instrument(instance, fileName) { const proto = Object.getPrototypeOf(instance); const methodNames = Object.getOwnPropertyNames(proto).filter((key) => { const descriptor = Object.getOwnPropertyDescriptor(proto, key); return key !== "constructor" && descriptor && typeof descriptor.value === "function" && descriptor.value.constructor.name === "AsyncFunction"; }); const instrumentedClassName = fileName ?? instance.constructor?.name ?? "unknown"; for (const name of methodNames) { const originalMethod = instance[name]; instance[name] = async (...args) => { const telemetryEnabled = this.shouldSendTelemetry(); const startTime = telemetryEnabled ? Date.now() : void 0; try { const result = await originalMethod.apply(instance, args); if (telemetryEnabled && startTime !== void 0) { const durationMs = Date.now() - startTime; const telemetryPayload = { functionName: `${instrumentedClassName}.${name}`, durationMs, timestamp: startTime / 1e3, props: { fileName: instrumentedClassName, method: name }, metadata: { provider: this.telemetryMetadata?.provider ?? "openai" }, error: void 0, source: this.telemetrySource }; this.batchProcessor.pushItem(telemetryPayload); } return result; } catch (error) { if (error instanceof Error) { if (!error.errorId) { error.errorId = require_buffer.getRandomUUID(); if (telemetryEnabled && startTime !== void 0) { const durationMs = Date.now() - startTime; await this.prepareAndSendErrorTelemetry(error, instrumentedClassName, name, startTime, durationMs); } } } throw error; } }; } return instance; } /** * Check if the telemetry should be sent. * @returns true if the telemetry should be sent, false otherwise */ shouldSendTelemetry() { const telemetryDisabledEnvironments = ["test", "ci"]; const nodeEnv = (require_ComposioError.getEnvVariable("NODE_ENV", "development") || "").toLowerCase(); const isDisabledEnvironment = telemetryDisabledEnvironments.includes(nodeEnv); const isTelemetryDisabledByEnv = require_ComposioError.getEnvVariable("TELEMETRY_DISABLED", "false") === "true"; return !this.isTelemetryDisabled && !isTelemetryDisabledByEnv && !isDisabledEnvironment; } /** * Prepare and send the error telemetry. * * @TODO This currently blocks the thread and sends the telemetry to the server. * * @param {unknown} error - The error to send. * @param {string} instrumentedClassName - The class name of the instrumented class. * @param {string} name - The name of the method that threw the error. * @param {number} startTime - The start time of the method invocation in milliseconds. * @param {number} durationMs - The duration of the method invocation in milliseconds. */ async prepareAndSendErrorTelemetry(error, instrumentedClassName, name, startTime, durationMs) { const telemetryPayload = { functionName: `${instrumentedClassName}.${name}`, durationMs, timestamp: startTime / 1e3, props: { fileName: instrumentedClassName, method: name }, metadata: { provider: this.telemetryMetadata?.provider ?? "openai" }, source: this.telemetrySource }; if (error instanceof _composio_client.ComposioError) telemetryPayload.error = { errorId: error.errorId, name: error.name, message: error.message, stack: error.stack }; else if (error instanceof require_ComposioError.ComposioError) telemetryPayload.error = { errorId: error.errorId, name: error.name, code: error.code, message: error.message, stack: error.stack }; else if (error instanceof Error) telemetryPayload.error = { errorId: error.errorId, name: error.name ?? "Unknown error", message: error.message, stack: error.stack }; await this.sendErrorTelemetry(telemetryPayload); } /** * Send the telemetry payload to the server. * @param payload - the telemetry payload to send * @returns */ async sendMetric(payload) { if (!this.shouldSendTelemetry()) { require_ComposioError.logger_default.debug("Telemetry is disabled, skipping metric telemetry", payload); return; } try { require_ComposioError.logger_default.debug("SDK Metric", payload); await TelemetryService.sendMetric(payload); } catch (error) { require_ComposioError.logger_default.error("Error sending metric telemetry", error); } } async sendErrorTelemetry(payload) { if (!this.shouldSendTelemetry()) { require_ComposioError.logger_default.debug("Telemetry is disabled, skipping metric telemetry", payload); return; } try { require_ComposioError.logger_default.debug("SDK Error Telemetry", payload); await TelemetryService.sendErrorLog(payload); } catch (error) { require_ComposioError.logger_default.error("Error sending error telemetry", error); } } /** * Flush any pending telemetry and wait for it to complete. * This is automatically called on process exit in Node.js environments. */ async flush() { await this.batchProcessor.flush(); } /** * Register process exit handlers to automatically flush telemetry. * Only registers handlers in Node.js environments (not in browsers). */ registerExitHandlers() { if (this.exitHandlersRegistered || typeof process === "undefined" || !process.on) return; this.exitHandlersRegistered = true; const flushSync = () => { this.flush().catch((error) => { require_ComposioError.logger_default.debug("Error flushing telemetry on exit", error); }); }; process.on("beforeExit", () => { flushSync(); }); const createSignalHandler = (signal) => { const handler = () => { require_ComposioError.logger_default.debug(`Received ${signal}, flushing telemetry...`); this.flush().catch((error) => { require_ComposioError.logger_default.debug("Error flushing telemetry on signal", error); }).finally(() => { process.removeListener(signal, handler); process.kill(process.pid, signal); }); }; return handler; }; process.on("SIGINT", createSignalHandler("SIGINT")); process.on("SIGTERM", createSignalHandler("SIGTERM")); } }; const telemetry = new TelemetryTransport(); //#endregion Object.defineProperty(exports, 'telemetry', { enumerable: true, get: function () { return telemetry; } });