const require_errors = require('./errors-BtJEw2Z-.cjs');
const require_url_utils = require('./url-utils-Bn5EoScr.cjs');
const require_logging = require('./logging-CkAJKyq2.cjs');
const require_schema = require('./schema-DoWJbcHt.cjs');
const require_errors$1 = require('./errors-Bmqdg5Q6.cjs');

//#region src/api-client/client.ts
/**
* Mapping of common network error codes to user-friendly messages.
* These help users understand and resolve connection issues.
*/
const NETWORK_ERROR_MESSAGES = {
	EAI_AGAIN: "DNS temporarily unavailable. Check your internet connection.",
	ENOTFOUND: "Hostname not found. Verify the URL is correct.",
	ECONNREFUSED: "Connection refused. Ensure the service is accessible.",
	ETIMEDOUT: "Connection timed out. Check network connectivity.",
	ECONNRESET: "Connection reset. Try again in a moment."
};
/**
* Sentry API client service for interacting with Sentry's REST API.
*
* This service provides a comprehensive interface to Sentry's API endpoints,
* handling authentication, error processing, multi-region support, and
* response validation through Zod schemas.
*
* Key Features:
* - Multi-region support for Sentry SaaS and self-hosted instances
* - Automatic schema validation with Zod
* - Enhanced error handling with LLM-friendly messages
* - URL generation for Sentry resources (issues, traces)
* - Bearer token authentication
* - Always uses HTTPS for secure connections
*
* @example Basic Usage
* ```typescript
* const apiService = new SentryApiService({
*   accessToken: "your-token",
*   host: "sentry.io"
* });
*
* const orgs = await apiService.listOrganizations();
* const issues = await apiService.listIssues({
*   organizationSlug: "my-org",
*   query: "is:unresolved"
* });
* ```
*
* @example Multi-Region Support
* ```typescript
* // Self-hosted instance with hostname
* const selfHosted = new SentryApiService({
*   accessToken: "token",
*   host: "sentry.company.com"
* });
*
* // Regional endpoint override
* const issues = await apiService.listIssues(
*   { organizationSlug: "org" },
*   { host: "eu.sentry.io" }
* );
* ```
*/
var SentryApiService = class {
	accessToken;
	host;
	apiPrefix;
	/**
	* Creates a new Sentry API service instance.
	*
	* Always uses HTTPS for secure connections.
	*
	* @param config Configuration object
	* @param config.accessToken OAuth access token for authentication (optional for some endpoints)
	* @param config.host Sentry hostname (e.g. "sentry.io", "sentry.example.com")
	*/
	constructor({ accessToken = null, host = "sentry.io" }) {
		this.accessToken = accessToken;
		this.host = host;
		this.apiPrefix = `https://${host}/api/0`;
	}
	/**
	* Updates the host for API requests.
	*
	* Used for multi-region support or switching between Sentry instances.
	* Always uses HTTPS protocol.
	*
	* @param host New hostname to use for API requests
	*/
	setHost(host) {
		this.host = host;
		this.apiPrefix = `https://${this.host}/api/0`;
	}
	/**
	* Checks if the current host is Sentry SaaS (sentry.io).
	*
	* Used to determine API endpoint availability and URL formats.
	* Self-hosted instances may not have all endpoints available.
	*
	* @returns True if using Sentry SaaS, false for self-hosted instances
	*/
	isSaas() {
		return require_url_utils.isSentryHost(this.host);
	}
	/**
	* Internal method for making authenticated requests to Sentry API.
	*
	* Handles:
	* - Bearer token authentication
	* - Error response parsing and enhancement
	* - Multi-region host overrides
	* - Fetch availability validation
	*
	* @param path API endpoint path (without /api/0 prefix)
	* @param options Fetch options
	* @param requestOptions Additional request configuration
	* @returns Promise resolving to Response object
	* @throws {ApiError} Enhanced API errors with user-friendly messages
	* @throws {Error} Network or parsing errors
	*/
	async request(path, options = {}, { host } = {}) {
		const url = host ? `https://${host}/api/0${path}` : `${this.apiPrefix}${path}`;
		const headers = {
			"Content-Type": "application/json",
			"User-Agent": "Sentry MCP Server"
		};
		if (this.accessToken) headers.Authorization = `Bearer ${this.accessToken}`;
		if (typeof globalThis.fetch === "undefined") throw new require_errors.ConfigurationError("fetch is not available. Please use Node.js >= 18 or ensure fetch is available in your environment.");
		let response;
		try {
			response = await fetch(url, {
				...options,
				headers
			});
		} catch (error) {
			let rootCause = error;
			while (rootCause instanceof Error && rootCause.cause) rootCause = rootCause.cause;
			const errorMessage = rootCause instanceof Error ? rootCause.message : String(rootCause);
			let friendlyMessage = `Unable to connect to ${url}`;
			const errorCode = Object.keys(NETWORK_ERROR_MESSAGES).find((code) => errorMessage.includes(code));
			if (errorCode) friendlyMessage += ` - ${NETWORK_ERROR_MESSAGES[errorCode]}`;
			else friendlyMessage += ` - ${errorMessage}`;
			if (errorCode === "ENOTFOUND" || errorCode === "EAI_AGAIN" || errorCode === "ECONNREFUSED" || errorCode === "ETIMEDOUT" || errorMessage.includes("Connect Timeout Error")) throw new require_errors.ConfigurationError(friendlyMessage, { cause: error });
			throw new Error(friendlyMessage, { cause: error });
		}
		if (!response.ok) {
			const errorText = await response.text();
			let parsed;
			try {
				parsed = JSON.parse(errorText);
			} catch (error) {
				if (errorText.includes("<!DOCTYPE") || errorText.includes("<html")) {
					require_logging.logWarn("Received HTML error page instead of JSON", {
						loggerScope: ["api", "client"],
						extra: {
							status: response.status,
							statusText: response.statusText,
							host: this.host,
							path,
							parseErrorMessage: error instanceof Error ? error.message : String(error)
						}
					});
					throw require_errors$1.createApiError(`Server error: Received HTML instead of JSON (${response.status} ${response.statusText}). This may indicate an invalid URL or server issue.`, response.status, errorText, void 0);
				}
				require_logging.logWarn("Failed to parse JSON error response", {
					loggerScope: ["api", "client"],
					extra: {
						status: response.status,
						statusText: response.statusText,
						host: this.host,
						path,
						bodyPreview: errorText.length > 256 ? `${errorText.slice(0, 253)}…` : errorText,
						parseErrorMessage: error instanceof Error ? error.message : String(error)
					}
				});
			}
			if (parsed) {
				const { data, success, error } = require_schema.ApiErrorSchema.safeParse(parsed);
				if (success) throw require_errors$1.createApiError(data.detail, response.status, data.detail, parsed);
				require_logging.logWarn("Failed to parse validated API error response", {
					loggerScope: ["api", "client"],
					extra: {
						status: response.status,
						statusText: response.statusText,
						host: this.host,
						path,
						bodyPreview: errorText.length > 256 ? `${errorText.slice(0, 253)}…` : errorText,
						validationErrorMessage: error instanceof Error ? error.message : String(error)
					}
				});
			}
			throw require_errors$1.createApiError(`API request failed: ${response.statusText}\n${errorText}`, response.status, errorText, void 0);
		}
		return response;
	}
	/**
	* Safely parses a JSON response, checking Content-Type header first.
	*
	* @param response The Response object from fetch
	* @returns Promise resolving to the parsed JSON object
	* @throws {Error} If response is not JSON or parsing fails
	*/
	async parseJsonResponse(response) {
		if (!response.headers?.get) return response.json();
		const contentType = response.headers.get("content-type");
		if (!contentType || !contentType.includes("application/json")) {
			const responseText = await response.text();
			if (contentType?.includes("text/html") || responseText.includes("<!DOCTYPE") || responseText.includes("<html")) throw new Error(`Expected JSON response but received HTML (${response.status} ${response.statusText}). This may indicate you're not authenticated, the URL is incorrect, or there's a server issue.`);
			throw new Error(`Expected JSON response but received ${contentType || "unknown content type"} (${response.status} ${response.statusText})`);
		}
		try {
			return await response.json();
		} catch (error) {
			throw new Error(`Failed to parse JSON response: ${error instanceof Error ? error.message : String(error)}`);
		}
	}
	/**
	* Makes a request to the Sentry API and parses the JSON response.
	*
	* This is the primary method for API calls that expect JSON responses.
	* It automatically validates Content-Type and provides helpful error messages
	* for common issues like authentication failures or server errors.
	*
	* @param path API endpoint path (without /api/0 prefix)
	* @param options Fetch options
	* @param requestOptions Additional request configuration
	* @returns Promise resolving to the parsed JSON response
	* @throws {ApiError} Enhanced API errors with user-friendly messages
	* @throws {Error} Network, parsing, or validation errors
	*/
	async requestJSON(path, options = {}, requestOptions) {
		const response = await this.request(path, options, requestOptions);
		return this.parseJsonResponse(response);
	}
	/**
	* Generates a Sentry issue URL for browser navigation.
	*
	* Handles both SaaS (subdomain-based) and self-hosted URL formats.
	* Always uses HTTPS protocol.
	*
	* @param organizationSlug Organization identifier
	* @param issueId Issue identifier (short ID or numeric ID)
	* @returns Full URL to the issue in Sentry UI
	*
	* @example
	* ```typescript
	* // SaaS: https://my-org.sentry.io/issues/PROJ-123
	* apiService.getIssueUrl("my-org", "PROJ-123")
	*
	* // Self-hosted: https://sentry.company.com/organizations/my-org/issues/PROJ-123
	* apiService.getIssueUrl("my-org", "PROJ-123")
	* ```
	*/
	getIssueUrl(organizationSlug, issueId) {
		return require_url_utils.getIssueUrl(this.host, organizationSlug, issueId);
	}
	/**
	* Generates a Sentry trace URL for performance investigation.
	*
	* Always uses HTTPS protocol.
	*
	* @param organizationSlug Organization identifier
	* @param traceId Trace identifier (hex string)
	* @returns Full HTTPS URL to the trace in Sentry UI
	*
	* @example
	* ```typescript
	* const traceUrl = apiService.getTraceUrl("my-org", "6a477f5b0f31ef7b6b9b5e1dea66c91d");
	* // https://my-org.sentry.io/explore/traces/trace/6a477f5b0f31ef7b6b9b5e1dea66c91d
	* ```
	*/
	getTraceUrl(organizationSlug, traceId) {
		return require_url_utils.getTraceUrl(this.host, organizationSlug, traceId);
	}
	/**
	* Builds a URL for the legacy Discover API (used by errors dataset).
	*
	* The Discover API is the older query interface that includes aggregate
	* functions directly in the field list.
	*
	* @example
	* // URL format: /explore/discover/homepage/?field=title&field=count_unique(user)
	* buildDiscoverUrl("my-org", "level:error", "123", ["title", "count_unique(user)"], "-timestamp")
	*/
	buildDiscoverUrl(params) {
		const { organizationSlug, query, projectId, fields, sort, statsPeriod, start, end, aggregateFunctions, groupByFields } = params;
		const urlParams = new URLSearchParams();
		urlParams.set("dataset", "errors");
		urlParams.set("queryDataset", "error-events");
		urlParams.set("query", query);
		if (projectId) urlParams.set("project", projectId);
		if (fields && fields.length > 0) for (const field of fields) urlParams.append("field", field);
		else {
			urlParams.append("field", "title");
			urlParams.append("field", "project");
			urlParams.append("field", "user.display");
			urlParams.append("field", "timestamp");
		}
		urlParams.set("sort", sort || "-timestamp");
		if (start && end) {
			urlParams.set("start", start);
			urlParams.set("end", end);
		} else urlParams.set("statsPeriod", statsPeriod || "24h");
		const isAggregate = (aggregateFunctions?.length ?? 0) > 0;
		if (isAggregate) {
			urlParams.set("mode", "aggregate");
			if (aggregateFunctions && aggregateFunctions.length > 0) urlParams.set("yAxis", aggregateFunctions[0]);
		} else urlParams.set("yAxis", "count()");
		const webHost = this.isSaas() ? "sentry.io" : this.host;
		const path = this.isSaas() ? `https://${organizationSlug}.${webHost}/explore/discover/homepage/` : `https://${this.host}/organizations/${organizationSlug}/explore/discover/homepage/`;
		return `${path}?${urlParams.toString()}`;
	}
	/**
	* Builds a URL for the modern EAP (Event Analytics Platform) API used by spans/logs.
	*
	* The EAP API uses structured aggregate queries with separate aggregateField
	* parameters containing JSON objects for groupBy and yAxes.
	*
	* @example
	* // URL format: /explore/traces/?aggregateField={"groupBy":"span.op"}&aggregateField={"yAxes":["count()"]}
	* buildEapUrl("my-org", "span.op:db", "123", ["span.op", "count()"], "-count()", ["count()"], ["span.op"])
	*/
	buildEapUrl(params) {
		const { organizationSlug, query, dataset, projectId, fields, sort, statsPeriod, start, end, aggregateFunctions, groupByFields } = params;
		const urlParams = new URLSearchParams();
		urlParams.set("query", query);
		if (projectId) urlParams.set("project", projectId);
		const isAggregateQuery = (aggregateFunctions?.length ?? 0) > 0 || fields?.some((field) => field.includes("(") && field.includes(")")) || false;
		if (isAggregateQuery) {
			if ((aggregateFunctions?.length ?? 0) > 0 || (groupByFields?.length ?? 0) > 0) {
				if (groupByFields && groupByFields.length > 0) for (const field of groupByFields) urlParams.append("aggregateField", JSON.stringify({ groupBy: field }));
				if (aggregateFunctions && aggregateFunctions.length > 0) urlParams.append("aggregateField", JSON.stringify({ yAxes: aggregateFunctions }));
			} else {
				const parsedGroupByFields = fields?.filter((field) => !field.includes("(") && !field.includes(")")) || [];
				const parsedAggregateFunctions = fields?.filter((field) => field.includes("(") && field.includes(")")) || [];
				for (const field of parsedGroupByFields) urlParams.append("aggregateField", JSON.stringify({ groupBy: field }));
				if (parsedAggregateFunctions.length > 0) urlParams.append("aggregateField", JSON.stringify({ yAxes: parsedAggregateFunctions }));
			}
			urlParams.set("mode", "aggregate");
		} else if (fields && fields.length > 0) for (const field of fields) urlParams.append("field", field);
		if (sort) urlParams.set("sort", sort);
		if (start && end) {
			urlParams.set("start", start);
			urlParams.set("end", end);
		} else urlParams.set("statsPeriod", statsPeriod || "24h");
		if (dataset === "spans") urlParams.set("table", "span");
		const basePath = dataset === "logs" ? "logs" : "traces";
		const webHost = this.isSaas() ? "sentry.io" : this.host;
		const path = this.isSaas() ? `https://${organizationSlug}.${webHost}/explore/${basePath}/` : `https://${this.host}/organizations/${organizationSlug}/explore/${basePath}/`;
		return `${path}?${urlParams.toString()}`;
	}
	/**
	* Generates a Sentry events explorer URL for viewing search results.
	*
	* Routes to the appropriate API based on dataset:
	* - Errors: Uses legacy Discover API
	* - Spans/Logs: Uses modern EAP (Event Analytics Platform) API
	*
	* @param organizationSlug Organization identifier
	* @param query Sentry search query
	* @param projectId Optional project filter
	* @param dataset Dataset type (spans, errors, or logs)
	* @param fields Array of fields to include in results
	* @param sort Sort parameter (e.g., "-timestamp", "-count()")
	* @param aggregateFunctions Array of aggregate functions (only used for EAP datasets)
	* @param groupByFields Array of fields to group by (only used for EAP datasets)
	* @param statsPeriod Relative time period (e.g., "24h", "7d")
	* @param start Absolute start time (ISO 8601)
	* @param end Absolute end time (ISO 8601)
	* @returns Full HTTPS URL to the events explorer in Sentry UI
	*/
	getEventsExplorerUrl(organizationSlug, query, projectId, dataset = "spans", fields, sort, aggregateFunctions, groupByFields, statsPeriod, start, end) {
		if (dataset === "errors") return this.buildDiscoverUrl({
			organizationSlug,
			query,
			projectId,
			fields,
			sort,
			statsPeriod,
			start,
			end,
			aggregateFunctions,
			groupByFields
		});
		return this.buildEapUrl({
			organizationSlug,
			query,
			dataset,
			projectId,
			fields,
			sort,
			statsPeriod,
			start,
			end,
			aggregateFunctions,
			groupByFields
		});
	}
	/**
	* Retrieves the authenticated user's profile information.
	*
	* @param opts Request options including host override
	* @returns User profile data
	* @throws {ApiError} If authentication fails or user not found
	*/
	async getAuthenticatedUser(opts) {
		let authHost;
		if (this.isSaas()) authHost = "sentry.io";
		const body = await this.requestJSON("/auth/", void 0, {
			...opts,
			host: authHost
		});
		return require_schema.UserSchema.parse(body);
	}
	/**
	* Lists all organizations accessible to the authenticated user.
	*
	* Automatically handles multi-region queries by fetching from all
	* available regions and combining results.
	*
	* @param params Query parameters
	* @param params.query Search query to filter organizations by name/slug
	* @param opts Request options
	* @returns Array of organizations across all accessible regions (limited to 25 results)
	*
	* @example
	* ```typescript
	* const orgs = await apiService.listOrganizations();
	* orgs.forEach(org => {
	*   // regionUrl present for Cloud Service, empty for self-hosted
	*   console.log(`${org.name} (${org.slug}) - ${org.links?.regionUrl || 'No region URL'}`);
	* });
	* ```
	*/
	async listOrganizations(params, opts) {
		const queryParams = new URLSearchParams();
		queryParams.set("per_page", "25");
		if (params?.query) queryParams.set("query", params.query);
		const queryString = queryParams.toString();
		const path = `/organizations/?${queryString}`;
		if (!this.isSaas()) {
			const body = await this.requestJSON(path, void 0, opts);
			return require_schema.OrganizationListSchema.parse(body);
		}
		try {
			const regionsBody = await this.requestJSON("/users/me/regions/", void 0, {});
			const regionData = require_schema.UserRegionsSchema.parse(regionsBody);
			const allOrganizations = (await Promise.all(regionData.regions.map(async (region) => this.requestJSON(path, void 0, {
				...opts,
				host: new URL(region.url).host
			})))).map((data) => require_schema.OrganizationListSchema.parse(data)).reduce((acc, curr) => acc.concat(curr), []);
			return allOrganizations.slice(0, 25);
		} catch (error) {
			if (error instanceof require_errors$1.ApiNotFoundError) {
				const body = await this.requestJSON(path, void 0, opts);
				return require_schema.OrganizationListSchema.parse(body);
			}
			throw error;
		}
	}
	/**
	* Gets a single organization by slug.
	*
	* @param organizationSlug Organization identifier
	* @param opts Request options including host override
	* @returns Organization data
	*/
	async getOrganization(organizationSlug, opts) {
		const body = await this.requestJSON(`/organizations/${organizationSlug}/`, void 0, opts);
		return require_schema.OrganizationSchema.parse(body);
	}
	/**
	* Lists teams within an organization.
	*
	* @param organizationSlug Organization identifier
	* @param params Query parameters
	* @param params.query Search query to filter teams by name/slug
	* @param opts Request options including host override
	* @returns Array of teams in the organization (limited to 25 results)
	*/
	async listTeams(organizationSlug, params, opts) {
		const queryParams = new URLSearchParams();
		queryParams.set("per_page", "25");
		if (params?.query) queryParams.set("query", params.query);
		const queryString = queryParams.toString();
		const path = `/organizations/${organizationSlug}/teams/?${queryString}`;
		const body = await this.requestJSON(path, void 0, opts);
		return require_schema.TeamListSchema.parse(body);
	}
	/**
	* Creates a new team within an organization.
	*
	* @param params Team creation parameters
	* @param params.organizationSlug Organization identifier
	* @param params.name Team name
	* @param opts Request options
	* @returns Created team data
	* @throws {ApiError} If team creation fails (e.g., name conflicts)
	*/
	async createTeam({ organizationSlug, name }, opts) {
		const body = await this.requestJSON(`/organizations/${organizationSlug}/teams/`, {
			method: "POST",
			body: JSON.stringify({ name })
		}, opts);
		return require_schema.TeamSchema.parse(body);
	}
	/**
	* Lists projects within an organization.
	*
	* @param organizationSlug Organization identifier
	* @param params Query parameters
	* @param params.query Search query to filter projects by name/slug
	* @param opts Request options
	* @returns Array of projects in the organization (limited to 25 results)
	*/
	async listProjects(organizationSlug, params, opts) {
		const queryParams = new URLSearchParams();
		queryParams.set("per_page", "25");
		if (params?.query) queryParams.set("query", params.query);
		const queryString = queryParams.toString();
		const path = `/organizations/${organizationSlug}/projects/?${queryString}`;
		const body = await this.requestJSON(path, void 0, opts);
		return require_schema.ProjectListSchema.parse(body);
	}
	/**
	* Gets a single project by slug or ID.
	*
	* @param params Project fetch parameters
	* @param params.organizationSlug Organization identifier
	* @param params.projectSlugOrId Project slug or numeric ID
	* @param opts Request options
	* @returns Project data
	*/
	async getProject({ organizationSlug, projectSlugOrId }, opts) {
		const body = await this.requestJSON(`/projects/${organizationSlug}/${projectSlugOrId}/`, void 0, opts);
		return require_schema.ProjectSchema.parse(body);
	}
	/**
	* Creates a new project within a team.
	*
	* @param params Project creation parameters
	* @param params.organizationSlug Organization identifier
	* @param params.teamSlug Team identifier
	* @param params.name Project name
	* @param params.platform Platform identifier (e.g., "javascript", "python")
	* @param opts Request options
	* @returns Created project data
	*/
	async createProject({ organizationSlug, teamSlug, name, platform }, opts) {
		const body = await this.requestJSON(`/teams/${organizationSlug}/${teamSlug}/projects/`, {
			method: "POST",
			body: JSON.stringify({
				name,
				platform
			})
		}, opts);
		return require_schema.ProjectSchema.parse(body);
	}
	/**
	* Updates an existing project's configuration.
	*
	* @param params Project update parameters
	* @param params.organizationSlug Organization identifier
	* @param params.projectSlug Current project identifier
	* @param params.name New project name (optional)
	* @param params.slug New project slug (optional)
	* @param params.platform New platform identifier (optional)
	* @param opts Request options
	* @returns Updated project data
	*/
	async updateProject({ organizationSlug, projectSlug, name, slug, platform }, opts) {
		const updateData = {};
		if (name !== void 0) updateData.name = name;
		if (slug !== void 0) updateData.slug = slug;
		if (platform !== void 0) updateData.platform = platform;
		const body = await this.requestJSON(`/projects/${organizationSlug}/${projectSlug}/`, {
			method: "PUT",
			body: JSON.stringify(updateData)
		}, opts);
		return require_schema.ProjectSchema.parse(body);
	}
	/**
	* Assigns a team to a project.
	*
	* @param params Assignment parameters
	* @param params.organizationSlug Organization identifier
	* @param params.projectSlug Project identifier
	* @param params.teamSlug Team identifier to assign
	* @param opts Request options
	*/
	async addTeamToProject({ organizationSlug, projectSlug, teamSlug }, opts) {
		await this.request(`/projects/${organizationSlug}/${projectSlug}/teams/${teamSlug}/`, {
			method: "POST",
			body: JSON.stringify({})
		}, opts);
	}
	/**
	* Creates a new client key (DSN) for a project.
	*
	* Client keys are used to identify and authenticate SDK requests to Sentry.
	*
	* @param params Key creation parameters
	* @param params.organizationSlug Organization identifier
	* @param params.projectSlug Project identifier
	* @param params.name Human-readable name for the key (optional)
	* @param opts Request options
	* @returns Created client key with DSN information
	*
	* @example
	* ```typescript
	* const key = await apiService.createClientKey({
	*   organizationSlug: "my-org",
	*   projectSlug: "my-project",
	*   name: "Production"
	* });
	* console.log(`DSN: ${key.dsn.public}`);
	* ```
	*/
	async createClientKey({ organizationSlug, projectSlug, name }, opts) {
		const body = await this.requestJSON(`/projects/${organizationSlug}/${projectSlug}/keys/`, {
			method: "POST",
			body: JSON.stringify({ name })
		}, opts);
		return require_schema.ClientKeySchema.parse(body);
	}
	/**
	* Lists all client keys (DSNs) for a project.
	*
	* @param params Query parameters
	* @param params.organizationSlug Organization identifier
	* @param params.projectSlug Project identifier
	* @param opts Request options
	* @returns Array of client keys with DSN information
	*/
	async listClientKeys({ organizationSlug, projectSlug }, opts) {
		const body = await this.requestJSON(`/projects/${organizationSlug}/${projectSlug}/keys/`, void 0, opts);
		return require_schema.ClientKeyListSchema.parse(body);
	}
	/**
	* Lists releases for an organization or specific project.
	*
	* @param params Query parameters
	* @param params.organizationSlug Organization identifier
	* @param params.projectSlug Project identifier (optional, scopes to specific project)
	* @param params.query Search query for filtering releases
	* @param opts Request options
	* @returns Array of releases with deployment and commit information
	*
	* @example
	* ```typescript
	* // All releases for organization
	* const releases = await apiService.listReleases({
	*   organizationSlug: "my-org"
	* });
	*
	* // Search for specific version
	* const filtered = await apiService.listReleases({
	*   organizationSlug: "my-org",
	*   query: "v1.2.3"
	* });
	* ```
	*/
	async listReleases({ organizationSlug, projectSlug, query }, opts) {
		const searchQuery = new URLSearchParams();
		if (query) searchQuery.set("query", query);
		const path = projectSlug ? `/projects/${organizationSlug}/${projectSlug}/releases/` : `/organizations/${organizationSlug}/releases/`;
		const body = await this.requestJSON(searchQuery.toString() ? `${path}?${searchQuery.toString()}` : path, void 0, opts);
		return require_schema.ReleaseListSchema.parse(body);
	}
	/**
	* Lists available tags for search queries.
	*
	* Tags represent indexed fields that can be used in Sentry search queries.
	*
	* @param params Query parameters
	* @param params.organizationSlug Organization identifier
	* @param params.dataset Dataset to query tags for ("events", "errors" or "search_issues")
	* @param params.project Numeric project ID to filter tags
	* @param params.statsPeriod Time range for tag statistics (e.g., "24h", "7d")
	* @param params.useCache Whether to use cached results
	* @param params.useFlagsBackend Whether to use flags backend features
	* @param opts Request options
	* @returns Array of available tags with metadata
	*
	* @example
	* ```typescript
	* const tags = await apiService.listTags({
	*   organizationSlug: "my-org",
	*   dataset: "events",
	*   project: "123456",
	*   statsPeriod: "24h",
	*   useCache: true
	* });
	* tags.forEach(tag => console.log(`${tag.key}: ${tag.name}`));
	* ```
	*/
	async listTags({ organizationSlug, dataset, project, statsPeriod, start, end, useCache, useFlagsBackend }, opts) {
		const searchQuery = new URLSearchParams();
		if (dataset) searchQuery.set("dataset", dataset);
		if (project) searchQuery.set("project", project);
		if (statsPeriod && (start || end)) throw new require_errors$1.ApiValidationError("Cannot use both statsPeriod and start/end parameters. Use either statsPeriod for relative time or start/end for absolute time.");
		if (start && !end || !start && end) throw new require_errors$1.ApiValidationError("Both start and end parameters must be provided together for absolute time ranges.");
		if (statsPeriod) searchQuery.set("statsPeriod", statsPeriod);
		else if (start && end) {
			searchQuery.set("start", start);
			searchQuery.set("end", end);
		}
		if (useCache !== void 0) searchQuery.set("useCache", useCache ? "1" : "0");
		if (useFlagsBackend !== void 0) searchQuery.set("useFlagsBackend", useFlagsBackend ? "1" : "0");
		const body = await this.requestJSON(searchQuery.toString() ? `/organizations/${organizationSlug}/tags/?${searchQuery.toString()}` : `/organizations/${organizationSlug}/tags/`, void 0, opts);
		return require_schema.TagListSchema.parse(body);
	}
	/**
	* Lists trace item attributes available for search queries.
	*
	* Returns all available fields/attributes that can be used in event searches,
	* including both built-in fields and custom tags.
	*
	* @param params Query parameters
	* @param params.organizationSlug Organization identifier
	* @param params.itemType Item type to query attributes for ("spans" or "logs")
	* @param params.project Numeric project ID to filter attributes
	* @param params.statsPeriod Time range for attribute statistics (e.g., "24h", "7d")
	* @param opts Request options
	* @returns Array of available attributes with metadata including type
	*/
	async listTraceItemAttributes({ organizationSlug, itemType = "spans", project, statsPeriod, start, end }, opts) {
		const [stringAttributes, numberAttributes] = await Promise.all([this.fetchTraceItemAttributesByType(organizationSlug, itemType, "string", project, statsPeriod, start, end, opts), this.fetchTraceItemAttributesByType(organizationSlug, itemType, "number", project, statsPeriod, start, end, opts)]);
		const allAttributes = [];
		for (const attr of stringAttributes) allAttributes.push({
			key: attr.key,
			name: attr.name || attr.key,
			type: "string"
		});
		for (const attr of numberAttributes) allAttributes.push({
			key: attr.key,
			name: attr.name || attr.key,
			type: "number"
		});
		return allAttributes;
	}
	async fetchTraceItemAttributesByType(organizationSlug, itemType, attributeType, project, statsPeriod, start, end, opts) {
		const queryParams = new URLSearchParams();
		queryParams.set("itemType", itemType);
		queryParams.set("attributeType", attributeType);
		if (project) queryParams.set("project", project);
		if (statsPeriod && (start || end)) throw new require_errors$1.ApiValidationError("Cannot use both statsPeriod and start/end parameters. Use either statsPeriod for relative time or start/end for absolute time.");
		if (start && !end || !start && end) throw new require_errors$1.ApiValidationError("Both start and end parameters must be provided together for absolute time ranges.");
		if (statsPeriod) queryParams.set("statsPeriod", statsPeriod);
		else if (start && end) {
			queryParams.set("start", start);
			queryParams.set("end", end);
		}
		const url = `/organizations/${organizationSlug}/trace-items/attributes/?${queryParams.toString()}`;
		const body = await this.requestJSON(url, void 0, opts);
		return Array.isArray(body) ? body : [];
	}
	/**
	* Lists issues within an organization or project.
	*
	* Issues represent groups of similar errors or problems in your application.
	* Supports Sentry's powerful query syntax for filtering and sorting.
	*
	* @param params Query parameters
	* @param params.organizationSlug Organization identifier
	* @param params.projectSlug Project identifier (optional, scopes to specific project)
	* @param params.query Sentry search query (e.g., "is:unresolved browser:chrome")
	* @param params.sortBy Sort order ("user", "freq", "date", "new")
	* @param opts Request options
	* @returns Array of issues with metadata and statistics
	*
	* @example
	* ```typescript
	* // Recent unresolved issues
	* const issues = await apiService.listIssues({
	*   organizationSlug: "my-org",
	*   query: "is:unresolved",
	*   sortBy: "date"
	* });
	*
	* // High-frequency errors in specific project
	* const critical = await apiService.listIssues({
	*   organizationSlug: "my-org",
	*   projectSlug: "backend",
	*   query: "level:error",
	*   sortBy: "freq"
	* });
	* ```
	*/
	async listIssues({ organizationSlug, projectSlug, query, sortBy, limit = 10 }, opts) {
		const sentryQuery = [];
		if (query) sentryQuery.push(query);
		const queryParams = new URLSearchParams();
		queryParams.set("per_page", String(limit));
		if (sortBy) queryParams.set("sort", sortBy);
		queryParams.set("statsPeriod", "24h");
		queryParams.set("query", sentryQuery.join(" "));
		queryParams.append("collapse", "unhandled");
		const apiUrl = projectSlug ? `/projects/${organizationSlug}/${projectSlug}/issues/?${queryParams.toString()}` : `/organizations/${organizationSlug}/issues/?${queryParams.toString()}`;
		const body = await this.requestJSON(apiUrl, void 0, opts);
		return require_schema.IssueListSchema.parse(body);
	}
	async getIssue({ organizationSlug, issueId }, opts) {
		const body = await this.requestJSON(`/organizations/${organizationSlug}/issues/${issueId}/`, void 0, opts);
		return require_schema.IssueSchema.parse(body);
	}
	async getEventForIssue({ organizationSlug, issueId, eventId }, opts) {
		const body = await this.requestJSON(`/organizations/${organizationSlug}/issues/${issueId}/events/${eventId}/`, void 0, opts);
		const rawEvent = require_schema.EventSchema.parse(body);
		if (rawEvent.type === "error" || rawEvent.type === "default") return rawEvent;
		if (rawEvent.type === "transaction") return rawEvent;
		const eventType = typeof rawEvent.type === "string" ? rawEvent.type : String(rawEvent.type);
		throw new require_errors$1.ApiValidationError(`Unknown event type: ${eventType}`, 400, `Only error, default, and transaction events are supported, got: ${eventType}`, body);
	}
	async getLatestEventForIssue({ organizationSlug, issueId }, opts) {
		return this.getEventForIssue({
			organizationSlug,
			issueId,
			eventId: "latest"
		}, opts);
	}
	async listEventAttachments({ organizationSlug, projectSlug, eventId }, opts) {
		const body = await this.requestJSON(`/projects/${organizationSlug}/${projectSlug}/events/${eventId}/attachments/`, void 0, opts);
		return require_schema.EventAttachmentListSchema.parse(body);
	}
	async getEventAttachment({ organizationSlug, projectSlug, eventId, attachmentId }, opts) {
		const attachmentsData = await this.requestJSON(`/projects/${organizationSlug}/${projectSlug}/events/${eventId}/attachments/`, void 0, opts);
		const attachments = require_schema.EventAttachmentListSchema.parse(attachmentsData);
		const attachment = attachments.find((att) => att.id === attachmentId);
		if (!attachment) throw new require_errors$1.ApiNotFoundError(`Attachment with ID ${attachmentId} not found for event ${eventId}`);
		const downloadUrl = `/projects/${organizationSlug}/${projectSlug}/events/${eventId}/attachments/${attachmentId}/?download=1`;
		const downloadResponse = await this.request(downloadUrl, {
			method: "GET",
			headers: { Accept: "application/octet-stream" }
		}, opts);
		return {
			attachment,
			downloadUrl: downloadResponse.url,
			filename: attachment.name,
			blob: await downloadResponse.blob()
		};
	}
	async updateIssue({ organizationSlug, issueId, status, assignedTo }, opts) {
		const updateData = {};
		if (status !== void 0) updateData.status = status;
		if (assignedTo !== void 0) updateData.assignedTo = assignedTo;
		const body = await this.requestJSON(`/organizations/${organizationSlug}/issues/${issueId}/`, {
			method: "PUT",
			body: JSON.stringify(updateData)
		}, opts);
		return require_schema.IssueSchema.parse(body);
	}
	async searchErrors({ organizationSlug, projectSlug, filename, transaction, query, sortBy = "last_seen" }, opts) {
		const sentryQuery = [];
		if (filename) sentryQuery.push(`stack.filename:"*${filename.replace(/"/g, "\\\"")}"`);
		if (transaction) sentryQuery.push(`transaction:"${transaction.replace(/"/g, "\\\"")}"`);
		if (query) sentryQuery.push(query);
		if (projectSlug) sentryQuery.push(`project:${projectSlug}`);
		const queryParams = new URLSearchParams();
		queryParams.set("dataset", "errors");
		queryParams.set("per_page", "10");
		queryParams.set("sort", `-${sortBy === "last_seen" ? "last_seen" : "count"}`);
		queryParams.set("statsPeriod", "24h");
		queryParams.append("field", "issue");
		queryParams.append("field", "title");
		queryParams.append("field", "project");
		queryParams.append("field", "last_seen()");
		queryParams.append("field", "count()");
		queryParams.set("query", sentryQuery.join(" "));
		const apiUrl = `/organizations/${organizationSlug}/events/?${queryParams.toString()}`;
		const body = await this.requestJSON(apiUrl, void 0, opts);
		return require_schema.ErrorsSearchResponseSchema.parse(body).data;
	}
	async searchSpans({ organizationSlug, projectSlug, transaction, query, sortBy = "timestamp" }, opts) {
		const sentryQuery = ["is_transaction:true"];
		if (transaction) sentryQuery.push(`transaction:"${transaction.replace(/"/g, "\\\"")}"`);
		if (query) sentryQuery.push(query);
		if (projectSlug) sentryQuery.push(`project:${projectSlug}`);
		const queryParams = new URLSearchParams();
		queryParams.set("dataset", "spans");
		queryParams.set("per_page", "10");
		queryParams.set("sort", `-${sortBy === "timestamp" ? "timestamp" : "span.duration"}`);
		queryParams.set("allowAggregateConditions", "0");
		queryParams.set("useRpc", "1");
		queryParams.append("field", "id");
		queryParams.append("field", "trace");
		queryParams.append("field", "span.op");
		queryParams.append("field", "span.description");
		queryParams.append("field", "span.duration");
		queryParams.append("field", "transaction");
		queryParams.append("field", "project");
		queryParams.append("field", "timestamp");
		queryParams.set("query", sentryQuery.join(" "));
		const apiUrl = `/organizations/${organizationSlug}/events/?${queryParams.toString()}`;
		const body = await this.requestJSON(apiUrl, void 0, opts);
		return require_schema.SpansSearchResponseSchema.parse(body).data;
	}
	/**
	* Builds query parameters for the legacy Discover API (primarily used by errors dataset).
	*
	* Note: While the API endpoint is the same for all datasets, we maintain separate
	* builders to make future divergence easier and to keep the code organized.
	*/
	buildDiscoverApiQuery(params) {
		const queryParams = new URLSearchParams();
		queryParams.set("per_page", params.limit.toString());
		queryParams.set("query", params.query);
		queryParams.set("dataset", "errors");
		if (params.statsPeriod && (params.start || params.end)) throw new require_errors$1.ApiValidationError("Cannot use both statsPeriod and start/end parameters. Use either statsPeriod for relative time or start/end for absolute time.");
		if (params.start && !params.end || !params.start && params.end) throw new require_errors$1.ApiValidationError("Both start and end parameters must be provided together for absolute time ranges.");
		if (params.statsPeriod) queryParams.set("statsPeriod", params.statsPeriod);
		else if (params.start && params.end) {
			queryParams.set("start", params.start);
			queryParams.set("end", params.end);
		}
		if (params.projectId) queryParams.set("project", params.projectId);
		let apiSort = params.sort;
		if (params.sort?.includes("(") && !params.sort?.includes("equation|")) {
			const parenStart = params.sort.indexOf("(");
			const parenEnd = params.sort.indexOf(")", parenStart);
			if (parenStart !== -1 && parenEnd !== -1) {
				const beforeParen = params.sort.substring(0, parenStart);
				const insideParen = params.sort.substring(parenStart + 1, parenEnd);
				const afterParen = params.sort.substring(parenEnd + 1);
				const transformedInside = insideParen ? `_${insideParen.replace(/\./g, "_")}` : "";
				apiSort = beforeParen + transformedInside + afterParen;
			}
		}
		queryParams.set("sort", apiSort);
		for (const field of params.fields) queryParams.append("field", field);
		return queryParams;
	}
	/**
	* Builds query parameters for the modern EAP API (used by spans/logs datasets).
	*
	* Includes dataset-specific parameters like sampling for spans.
	*/
	buildEapApiQuery(params) {
		const queryParams = new URLSearchParams();
		queryParams.set("per_page", params.limit.toString());
		queryParams.set("query", params.query);
		queryParams.set("dataset", params.dataset);
		if (params.statsPeriod && (params.start || params.end)) throw new require_errors$1.ApiValidationError("Cannot use both statsPeriod and start/end parameters. Use either statsPeriod for relative time or start/end for absolute time.");
		if (params.start && !params.end || !params.start && params.end) throw new require_errors$1.ApiValidationError("Both start and end parameters must be provided together for absolute time ranges.");
		if (params.statsPeriod) queryParams.set("statsPeriod", params.statsPeriod);
		else if (params.start && params.end) {
			queryParams.set("start", params.start);
			queryParams.set("end", params.end);
		}
		if (params.projectId) queryParams.set("project", params.projectId);
		if (params.dataset === "spans") queryParams.set("sampling", "NORMAL");
		let apiSort = params.sort;
		if (params.sort?.includes("(") && !params.sort?.includes("equation|")) {
			const parenStart = params.sort.indexOf("(");
			const parenEnd = params.sort.indexOf(")", parenStart);
			if (parenStart !== -1 && parenEnd !== -1) {
				const beforeParen = params.sort.substring(0, parenStart);
				const insideParen = params.sort.substring(parenStart + 1, parenEnd);
				const afterParen = params.sort.substring(parenEnd + 1);
				const transformedInside = insideParen ? `_${insideParen.replace(/\./g, "_")}` : "";
				apiSort = beforeParen + transformedInside + afterParen;
			}
		}
		queryParams.set("sort", apiSort);
		for (const field of params.fields) queryParams.append("field", field);
		return queryParams;
	}
	/**
	* Searches for events in Sentry using the unified events API.
	* This method is used by the search_events tool for semantic search.
	*
	* Routes to the appropriate query builder based on dataset, even though
	* the underlying API endpoint is the same. This separation makes the code
	* cleaner and allows for future API divergence.
	*/
	async searchEvents({ organizationSlug, query, fields, limit = 10, projectId, dataset = "spans", statsPeriod, start, end, sort = "-timestamp" }, opts) {
		let queryParams;
		if (dataset === "errors") queryParams = this.buildDiscoverApiQuery({
			query,
			fields,
			limit,
			projectId,
			statsPeriod,
			start,
			end,
			sort
		});
		else queryParams = this.buildEapApiQuery({
			query,
			fields,
			limit,
			projectId,
			dataset,
			statsPeriod,
			start,
			end,
			sort
		});
		const apiUrl = `/organizations/${organizationSlug}/events/?${queryParams.toString()}`;
		return await this.requestJSON(apiUrl, void 0, opts);
	}
	async startAutofix({ organizationSlug, issueId, eventId, instruction = "" }, opts) {
		const body = await this.requestJSON(`/organizations/${organizationSlug}/issues/${issueId}/autofix/`, {
			method: "POST",
			body: JSON.stringify({
				event_id: eventId,
				instruction
			})
		}, opts);
		return require_schema.AutofixRunSchema.parse(body);
	}
	async getAutofixState({ organizationSlug, issueId }, opts) {
		const body = await this.requestJSON(`/organizations/${organizationSlug}/issues/${issueId}/autofix/`, void 0, opts);
		return require_schema.AutofixRunStateSchema.parse(body);
	}
	/**
	* Retrieves high-level metadata about a trace.
	*
	* Returns statistics including span counts, error counts, transaction
	* breakdown, and operation type distribution for the specified trace.
	*
	* @param params Query parameters
	* @param params.organizationSlug Organization identifier
	* @param params.traceId Trace identifier (32-character hex string)
	* @param params.statsPeriod Optional stats period (e.g., "14d", "7d")
	* @param opts Request options
	* @returns Trace metadata with statistics
	*
	* @example
	* ```typescript
	* const traceMeta = await apiService.getTraceMeta({
	*   organizationSlug: "my-org",
	*   traceId: "a4d1aae7216b47ff8117cf4e09ce9d0a"
	* });
	* console.log(`Trace has ${traceMeta.span_count} spans`);
	* ```
	*/
	async getTraceMeta({ organizationSlug, traceId, statsPeriod = "14d" }, opts) {
		const queryParams = new URLSearchParams();
		queryParams.set("statsPeriod", statsPeriod);
		const body = await this.requestJSON(`/organizations/${organizationSlug}/trace-meta/${traceId}/?${queryParams.toString()}`, void 0, opts);
		return require_schema.TraceMetaSchema.parse(body);
	}
	/**
	* Retrieves the complete trace structure with all spans.
	*
	* Returns the hierarchical trace data including all spans, their timing
	* information, operation details, and nested relationships.
	*
	* @param params Query parameters
	* @param params.organizationSlug Organization identifier
	* @param params.traceId Trace identifier (32-character hex string)
	* @param params.limit Maximum number of spans to return (default: 1000)
	* @param params.project Project filter (-1 for all projects)
	* @param params.statsPeriod Optional stats period (e.g., "14d", "7d")
	* @param opts Request options
	* @returns Complete trace tree structure
	*
	* @example
	* ```typescript
	* const trace = await apiService.getTrace({
	*   organizationSlug: "my-org",
	*   traceId: "a4d1aae7216b47ff8117cf4e09ce9d0a",
	*   limit: 1000
	* });
	* console.log(`Root spans: ${trace.length}`);
	* ```
	*/
	async getTrace({ organizationSlug, traceId, limit = 1e3, project = "-1", statsPeriod = "14d" }, opts) {
		const queryParams = new URLSearchParams();
		queryParams.set("limit", String(limit));
		queryParams.set("project", project);
		queryParams.set("statsPeriod", statsPeriod);
		const body = await this.requestJSON(`/organizations/${organizationSlug}/trace/${traceId}/?${queryParams.toString()}`, void 0, opts);
		return require_schema.TraceSchema.parse(body);
	}
};

//#endregion
Object.defineProperty(exports, 'SentryApiService', {
  enumerable: true,
  get: function () {
    return SentryApiService;
  }
});
//# sourceMappingURL=client-C2TwbSm-.cjs.map