import { ConfigurationError } from "./exceptions.js";
import { getLogger } from "./utils/logger.js";
import { trackAiMessage, trackEmbedding, trackScore, trackSessionEnd, trackSessionEnrichment, trackSpan, trackToolCall, trackUserMessage } from "./core/tracking.js";
import { isServerless } from "./serverless.js";
import { BoundAgent } from "./bound-agent.js";
import { AIConfig } from "./config.js";
import { calculateCost } from "./utils/costs.js";
import { setDefaultPropagateContext } from "./propagation.js";
import { isBundlerEnvironment, tryRequire } from "./utils/resolve-module.js";
import { patchedProviders } from "./patching.js";
import { TenantHandle } from "./tenant.js";
import { formatDebugLine, formatDryRunLine } from "./utils/debug.js";
import { setupOtel } from "./otel/setup.js";
import { createRequire } from "node:module";

//#region src/client.ts
const _require = createRequire(import.meta.url);
const _MAX_SESSION_TURN_COUNTERS = 1e4;
const _MIN_ID_LENGTH = 5;
const _shortIdWarned = /* @__PURE__ */ new Set();
function _warnShortId(event, logger) {
	for (const field of ["user_id", "device_id"]) {
		const val = event[field];
		if (typeof val === "string" && val.length > 0 && val.length < _MIN_ID_LENGTH) {
			const key = `${field}:${val}`;
			if (!_shortIdWarned.has(key)) {
				_shortIdWarned.add(key);
				logger.warn(`AmplitudeAI: ${field}="${val}" is shorter than ${_MIN_ID_LENGTH} characters. Amplitude's server will reject this event with HTTP 400 ("Invalid id length"). Use a longer identifier.`);
			}
		}
	}
}
/** @internal Exposed for testing only. */
function _resetShortIdWarned() {
	_shortIdWarned.clear();
}
/**
* Thin mutable wrapper around a potentially-frozen Amplitude client.
*
* ES module namespaces (`import * as mod`) are frozen objects whose
* export bindings cannot be reassigned.  Instead of monkey-patching
* `track` on the real client we keep the replacement on this proxy,
* which is always a plain mutable object.
*/
var TrackingProxy = class {
	_original;
	track;
	constructor(original) {
		this._original = original;
		this.track = original.track.bind(original);
	}
	flush() {
		return this._original.flush();
	}
	shutdown() {
		this._original.shutdown?.();
	}
	get configuration() {
		return this._original.configuration;
	}
};
let _globalUnflushedCount = 0;
let _exitHookRegistered = false;
/** @internal Exposed for testing only. */
function _getGlobalUnflushedCount() {
	return _globalUnflushedCount;
}
function _registerExitHook() {
	if (_exitHookRegistered) return;
	_exitHookRegistered = true;
	process.on("beforeExit", () => {
		if (!isServerless()) return;
		if (_globalUnflushedCount > 0) console.warn(`⚠️  AmplitudeAI: ${_globalUnflushedCount} event(s) were tracked but never flushed. In serverless environments, call \`await ai.flush()\` before your handler returns, or use session.run() which auto-flushes by default.`);
	});
}
/**
* Main Amplitude AI client for tracking LLM interactions.
*
* Create an instance with an API key or an existing Amplitude client,
* then use `.agent()` to create bound agents and `.session()` to
* manage session context.
*
* @example
* ```typescript
* const ai = new AmplitudeAI({ apiKey: 'YOUR_KEY' });
* const agent = ai.agent('my-chatbot', { userId: 'user-1' });
* const session = agent.session();
* await session.run(async (s) => {
*   s.trackUserMessage('Hello');
*   s.trackAiMessage('Hi there!', 'gpt-4', 'openai', 150);
* });
* ```
*/
var AmplitudeAI = class {
	_amplitude;
	_ownsClient;
	_config;
	_privacyConfig;
	_sessionTurnCounters = /* @__PURE__ */ new Map();
	/** @internal Tracks events since last flush() — used by the exit warning. */
	_trackCountSinceFlush = 0;
	_otelEnabled = false;
	_otelTracerProvider = null;
	_otelMapper = null;
	_otelProcessor = null;
	constructor(options) {
		let rawAmplitude;
		if (options.amplitude != null) {
			rawAmplitude = options.amplitude;
			this._ownsClient = false;
		} else if (options.apiKey != null) {
			const amplitudeNode = tryRequire("@amplitude/analytics-node");
			if (amplitudeNode == null || typeof amplitudeNode.init !== "function") {
				if (isBundlerEnvironment) throw new ConfigurationError("Could not resolve @amplitude/analytics-node (likely a bundler environment such as Turbopack or Webpack). Pass a pre-initialized Amplitude client via the 'amplitude' option instead. Example: new AmplitudeAI({ amplitude: amplitudeNodeClient, apiKey: undefined })");
				throw new ConfigurationError("@amplitude/analytics-node is required. Install it as a dependency: npm install @amplitude/analytics-node");
			}
			amplitudeNode.init(options.apiKey);
			rawAmplitude = amplitudeNode;
			this._ownsClient = true;
		} else throw new ConfigurationError("Provide either an existing Amplitude instance via 'amplitude' or an API key via 'apiKey'.");
		this._amplitude = new TrackingProxy(rawAmplitude);
		this._config = options.config ?? new AIConfig();
		this._privacyConfig = this._config.toPrivacyConfig();
		setDefaultPropagateContext(this._config.propagateContext);
		this._installTrackHook();
		this._installTrackCounter();
		_registerExitHook();
	}
	_installTrackCounter() {
		const originalTrack = this._amplitude.track.bind(this._amplitude);
		this._amplitude.track = (event) => {
			this._trackCountSinceFlush++;
			_globalUnflushedCount++;
			return originalTrack(event);
		};
	}
	_installTrackHook() {
		const originalTrack = this._amplitude.track.bind(this._amplitude);
		const debug = this._config.debug;
		const dryRun = this._config.dryRun;
		const onEvent = this._config.onEventCallback;
		const logger = getLogger();
		const clientWithConfig = this._amplitude;
		const existingCallback = clientWithConfig.configuration?.callback;
		if (clientWithConfig.configuration == null && onEvent != null) logger.warn("AmplitudeAI: onEventCallback is set but the Amplitude client has no \"configuration\" property. The callback will not fire. If you passed an external client, ensure it exposes a configuration object.");
		if (clientWithConfig.configuration != null) clientWithConfig.configuration.callback = (...args) => {
			const event = args[0];
			const statusCode = typeof args[1] === "number" ? args[1] : 0;
			const message = args[2] == null ? null : String(args[2]);
			if (typeof existingCallback === "function") try {
				existingCallback(...args);
			} catch (e) {
				logger.debug(`Existing delivery callback raised: ${e}`);
			}
			if (onEvent != null) try {
				onEvent(event, statusCode, message);
			} catch (e) {
				logger.debug(`AI SDK onEventCallback raised: ${e}`);
			}
			if (statusCode >= 400) {
				const eventType = event?.event_type ?? "unknown";
				const userId = event?.user_id ?? "";
				logger.warn(`AmplitudeAI: event delivery failed — HTTP ${statusCode} for event=${String(eventType)} user_id=${String(userId)}: ${message ?? ""}`);
			}
		};
		this._amplitude.track = (event) => {
			_warnShortId(event, logger);
			if (debug) console.warn(formatDebugLine(event));
			if (dryRun) console.warn(formatDryRunLine(event));
			if (!dryRun) originalTrack(event);
			if (onEvent != null && dryRun) try {
				onEvent(event, -1, "dry-run");
			} catch (e) {
				logger.debug(`AI SDK onEventCallback raised in dry-run: ${e}`);
			}
		};
	}
	get amplitude() {
		return this._amplitude;
	}
	get config() {
		return this._config;
	}
	_nextTurnId(sessionId) {
		if (!this._sessionTurnCounters.has(sessionId) && this._sessionTurnCounters.size >= _MAX_SESSION_TURN_COUNTERS) {
			const lruKey = this._sessionTurnCounters.keys().next().value;
			if (lruKey != null) this._sessionTurnCounters.delete(lruKey);
		}
		const next = (this._sessionTurnCounters.get(sessionId) ?? 0) + 1;
		this._sessionTurnCounters.delete(sessionId);
		this._sessionTurnCounters.set(sessionId, next);
		return next;
	}
	trackUserMessage(opts) {
		const effectiveTurnId = opts.turnId ?? this._nextTurnId(opts.sessionId);
		return trackUserMessage({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			messageContent: opts.content,
			sessionId: opts.sessionId,
			traceId: opts.traceId,
			turnId: effectiveTurnId,
			messageId: opts.messageId,
			messageSource: opts.messageSource ?? "user",
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			isRegeneration: opts.isRegeneration,
			isEdit: opts.isEdit,
			editedMessageId: opts.editedMessageId,
			attachments: opts.attachments,
			labels: opts.labels,
			idleTimeoutMinutes: opts.idleTimeoutMinutes,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
	}
	trackAiMessage(opts) {
		const effectiveTurnId = opts.turnId ?? this._nextTurnId(opts.sessionId);
		let effectiveCost = opts.totalCostUsd ?? null;
		if (effectiveCost == null && opts.inputTokens != null && opts.outputTokens != null) try {
			effectiveCost = calculateCost({
				modelName: opts.model,
				inputTokens: opts.inputTokens,
				outputTokens: opts.outputTokens,
				reasoningTokens: opts.reasoningTokens ?? 0,
				cacheReadInputTokens: opts.cacheReadTokens ?? 0,
				cacheCreationInputTokens: opts.cacheCreationTokens ?? 0,
				defaultProvider: opts.provider || void 0
			});
		} catch {}
		return trackAiMessage({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			modelName: opts.model,
			provider: opts.provider,
			responseContent: opts.content,
			latencyMs: opts.latencyMs,
			sessionId: opts.sessionId,
			traceId: opts.traceId,
			turnId: effectiveTurnId,
			messageId: opts.messageId,
			inputTokens: opts.inputTokens,
			outputTokens: opts.outputTokens,
			reasoningTokens: opts.reasoningTokens,
			cacheReadInputTokens: opts.cacheReadTokens,
			cacheCreationInputTokens: opts.cacheCreationTokens,
			totalTokens: opts.totalTokens,
			totalCostUsd: effectiveCost,
			finishReason: opts.finishReason,
			toolCalls: opts.toolCalls,
			reasoningContent: opts.reasoningContent,
			toolDefinitions: opts.toolDefinitions,
			systemPrompt: opts.systemPrompt,
			temperature: opts.temperature,
			maxOutputTokens: opts.maxOutputTokens,
			topP: opts.topP,
			isStreaming: opts.isStreaming,
			promptId: opts.promptId,
			wasCopied: opts.wasCopied,
			wasCached: opts.wasCached,
			modelTier: opts.modelTier,
			attachments: opts.attachments,
			labels: opts.labels,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			isError: opts.isError,
			errorMessage: opts.errorMessage,
			errorType: opts.errorType,
			providerTtfbMs: opts.ttfbMs,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: opts.privacyConfig ?? this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
	}
	trackToolCall(opts) {
		return trackToolCall({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			toolName: opts.toolName,
			toolType: opts.toolType,
			toolOwner: opts.toolOwner,
			success: opts.success,
			latencyMs: opts.latencyMs,
			sessionId: opts.sessionId,
			traceId: opts.traceId,
			turnId: opts.turnId ?? void 0,
			invocationId: opts.invocationId,
			toolInput: opts.input,
			toolOutput: opts.output,
			parentMessageId: opts.parentMessageId,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			errorMessage: opts.errorMessage,
			errorType: opts.errorType,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
	}
	trackEmbedding(opts) {
		let effectiveCost = opts.totalCostUsd ?? null;
		if (effectiveCost == null && opts.inputTokens != null) try {
			effectiveCost = calculateCost({
				modelName: opts.model,
				inputTokens: opts.inputTokens,
				outputTokens: 0,
				defaultProvider: opts.provider || void 0
			});
		} catch {}
		return trackEmbedding({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			model: opts.model,
			provider: opts.provider,
			latencyMs: opts.latencyMs,
			inputTokens: opts.inputTokens,
			dimensions: opts.dimensions,
			totalCostUsd: effectiveCost,
			sessionId: opts.sessionId,
			traceId: opts.traceId,
			turnId: opts.turnId,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
	}
	trackSpan(opts) {
		return trackSpan({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			spanName: opts.spanName,
			traceId: opts.traceId,
			latencyMs: opts.latencyMs,
			inputState: opts.inputState,
			outputState: opts.outputState,
			parentSpanId: opts.parentSpanId,
			isError: opts.isError,
			errorMessage: opts.errorMessage,
			errorType: opts.errorType,
			sessionId: opts.sessionId,
			turnId: opts.turnId,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
	}
	trackSessionEnd(opts) {
		trackSessionEnd({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			sessionId: opts.sessionId,
			enrichments: opts.enrichments,
			traceId: opts.traceId,
			turnId: opts.turnId,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			abandonmentTurn: opts.abandonmentTurn,
			idleTimeoutMinutes: opts.idleTimeoutMinutes,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
		this._sessionTurnCounters.delete(opts.sessionId);
	}
	trackSessionEnrichment(opts) {
		trackSessionEnrichment({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			sessionId: opts.sessionId,
			enrichments: opts.enrichments,
			traceId: opts.traceId,
			turnId: opts.turnId,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
	}
	score(opts) {
		trackScore({
			amplitude: this._amplitude,
			userId: opts.userId,
			deviceId: opts.deviceId,
			name: opts.name,
			value: opts.value,
			targetId: opts.targetId,
			targetType: opts.targetType,
			source: opts.source,
			comment: opts.comment,
			sessionId: opts.sessionId,
			traceId: opts.traceId,
			turnId: opts.turnId,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			eventProperties: opts.eventProperties,
			groups: opts.groups,
			privacyConfig: this._privacyConfig,
			browserSessionId: opts.browserSessionId
		});
	}
	agent(agentId, opts = {}) {
		return new BoundAgent(this, {
			agentId,
			...opts
		});
	}
	tenant(customerOrgId, opts = {}) {
		return new TenantHandle(this, {
			customerOrgId,
			...opts
		});
	}
	enableOtel(options) {
		if (this._otelEnabled) {
			getLogger().debug("enableOtel() called more than once — skipping");
			return this;
		}
		const { provider, mapper, processor } = setupOtel({
			amplitude: this._amplitude,
			defaultUserId: options?.defaultUserId,
			defaultDeviceId: options?.defaultDeviceId,
			otelEndpoint: options?.otelEndpoint,
			privacyConfig: this._privacyConfig
		});
		this._otelTracerProvider = provider;
		this._otelMapper = mapper;
		this._otelProcessor = processor;
		this._otelEnabled = true;
		getLogger().info("OTEL span-first instrumentation enabled");
		return this;
	}
	get otelEnabled() {
		return this._otelEnabled;
	}
	usingAttributes(attrs, fn) {
		if (this._otelEnabled) try {
			const span = _require("@opentelemetry/api").trace.getActiveSpan();
			if (span && span.isRecording()) for (const [key, value] of Object.entries(attrs)) span.setAttribute(key, value);
		} catch {
			getLogger().debug("opentelemetry not available for usingAttributes");
		}
		return fn();
	}
	updateCurrentSpan(attrs) {
		if (!this._otelEnabled) {
			getLogger().debug("updateCurrentSpan called but OTEL is not enabled");
			return;
		}
		try {
			const span = _require("@opentelemetry/api").trace.getActiveSpan();
			if (span && span.isRecording()) for (const [key, value] of Object.entries(attrs)) span.setAttribute(key, value);
		} catch {
			getLogger().debug("opentelemetry not available for updateCurrentSpan");
		}
	}
	updateCurrentTrace(attrs) {
		if (!this._otelEnabled) {
			getLogger().debug("updateCurrentTrace called but OTEL is not enabled");
			return;
		}
		try {
			const span = _require("@opentelemetry/api").trace.getActiveSpan();
			if (span && span.isRecording()) for (const [key, value] of Object.entries(attrs)) span.setAttribute(`amplitude.trace.${key}`, value);
		} catch {
			getLogger().debug("opentelemetry not available for updateCurrentTrace");
		}
	}
	status() {
		const availableProviders = [];
		for (const mod of [
			"openai",
			"@anthropic-ai/sdk",
			"@google/generative-ai",
			"@aws-sdk/client-bedrock-runtime",
			"@mistralai/mistralai"
		]) if (tryRequire(mod) != null) {
			const name = mod === "@anthropic-ai/sdk" ? "anthropic" : mod === "@google/generative-ai" ? "gemini" : mod === "@aws-sdk/client-bedrock-runtime" ? "bedrock" : mod === "@mistralai/mistralai" ? "mistral" : mod;
			availableProviders.push(name);
			if (mod === "openai") availableProviders.push("azure-openai");
		}
		return {
			content_mode: this._config.contentMode,
			debug: this._config.debug,
			dry_run: this._config.dryRun,
			redact_pii: this._config.redactPii,
			otel_enabled: this._otelEnabled,
			providers_available: availableProviders,
			patched_providers: patchedProviders()
		};
	}
	flush() {
		_globalUnflushedCount = Math.max(0, _globalUnflushedCount - this._trackCountSinceFlush);
		this._trackCountSinceFlush = 0;
		return this._amplitude.flush();
	}
	shutdown() {
		_globalUnflushedCount = Math.max(0, _globalUnflushedCount - this._trackCountSinceFlush);
		this._trackCountSinceFlush = 0;
		if (this._ownsClient) this._amplitude.shutdown?.();
	}
};

//#endregion
export { AmplitudeAI, _getGlobalUnflushedCount, _resetShortIdWarned };
//# sourceMappingURL=client.js.map