//#region src/media-shared/http/provider-http-errors.ts
/**
* Provider HTTP error formatting helpers.
*
* Ported from openclaw/src/agents/provider-http-errors.ts (commit baseline 2026-05-08).
*
* DECISION (per docs/voice-rearchitecture.md §6):
*  - Body read is hard-capped at 16 KiB to avoid memory blowups on misbehaving
*    providers that return huge HTML 5xx pages.
*  - Both `error.message` (OpenAI shape) and `error.detail` (DashScope shape) are
*    normalized into a single human-readable string for logs.
*  - Request id is taken from `x-request-id` first, then `request-id` (Alibaba uses
*    the latter on some endpoints).
*/
const DEFAULT_BODY_READ_LIMIT_BYTES = 16 * 1024;
const DEFAULT_DETAIL_TRUNCATION_LIMIT = 220;
function asObject(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
}
function trimToUndefined(value) {
	if (typeof value !== "string") return;
	const trimmed = value.trim();
	return trimmed.length > 0 ? trimmed : void 0;
}
function truncateErrorDetail(detail, limit = DEFAULT_DETAIL_TRUNCATION_LIMIT) {
	return detail.length <= limit ? detail : `${detail.slice(0, limit - 1)}…`;
}
async function readResponseTextLimited(response, limitBytes = DEFAULT_BODY_READ_LIMIT_BYTES) {
	if (limitBytes <= 0) return "";
	const reader = response.body?.getReader();
	if (!reader) return "";
	const decoder = new TextDecoder();
	let total = 0;
	let text = "";
	let reachedLimit = false;
	try {
		while (true) {
			const { value, done } = await reader.read();
			if (done) break;
			if (!value || value.byteLength === 0) continue;
			const remaining = limitBytes - total;
			if (remaining <= 0) {
				reachedLimit = true;
				break;
			}
			const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value;
			total += chunk.byteLength;
			text += decoder.decode(chunk, { stream: true });
			if (total >= limitBytes) {
				reachedLimit = true;
				break;
			}
		}
		text += decoder.decode();
	} finally {
		if (reachedLimit) await reader.cancel().catch(() => {});
	}
	return text;
}
/** Best-effort vendor `code` + human message from a JSON error body (image providers, failover). */
function extractVendorErrorFields(body) {
	if (!body) return {};
	let parsed;
	try {
		parsed = JSON.parse(body);
	} catch {
		return {};
	}
	if (!parsed || typeof parsed !== "object") return {};
	const o = parsed;
	if (o.error && typeof o.error === "object") {
		const e = o.error;
		const message = typeof e.message === "string" ? e.message : void 0;
		const code = typeof e.code === "string" ? e.code : typeof e.type === "string" ? e.type : void 0;
		if (message || code) return {
			message,
			code
		};
	}
	if (typeof o.code === "string" && typeof o.message === "string") return {
		code: o.code,
		message: o.message
	};
	if (o.base_resp && typeof o.base_resp === "object") {
		const br = o.base_resp;
		const sc = br.status_code;
		const sm = br.status_msg;
		if (typeof sm === "string") return {
			code: typeof sc === "number" || typeof sc === "string" ? String(sc) : void 0,
			message: sm
		};
	}
	if (typeof o.message === "string") return { message: o.message };
	return {};
}
function formatProviderErrorPayload(payload) {
	const root = asObject(payload);
	const detailObject = asObject(root?.detail);
	const subject = asObject(root?.error) ?? detailObject ?? root;
	if (!subject) return;
	const message = trimToUndefined(subject.message) ?? trimToUndefined(subject.detail) ?? trimToUndefined(root?.message) ?? trimToUndefined(root?.error) ?? trimToUndefined(root?.detail);
	const type = trimToUndefined(subject.type);
	const code = trimToUndefined(subject.code) ?? trimToUndefined(subject.status);
	const metadata = [type ? `type=${type}` : void 0, code ? `code=${code}` : void 0].filter((value) => Boolean(value)).join(", ");
	if (message && metadata) return `${truncateErrorDetail(message)} [${metadata}]`;
	if (message) return truncateErrorDetail(message);
	if (metadata) return `[${metadata}]`;
}
async function extractProviderErrorDetail(response) {
	const rawBody = trimToUndefined(await readResponseTextLimited(response));
	if (!rawBody) return;
	try {
		return formatProviderErrorPayload(JSON.parse(rawBody)) ?? truncateErrorDetail(rawBody);
	} catch {
		return truncateErrorDetail(rawBody);
	}
}
function extractProviderRequestId(response) {
	return trimToUndefined(response.headers.get("x-request-id")) ?? trimToUndefined(response.headers.get("request-id"));
}
function formatProviderHttpErrorMessage(parts) {
	if (parts.messageOverride) return parts.messageOverride;
	const { label, status, detail, requestId, statusPrefix = "", code } = parts;
	const codeSuffix = code ? ` [code=${code}]` : "";
	return `${label} (${statusPrefix}${status})` + (detail ? `: ${detail}` : "") + codeSuffix + (requestId ? ` [request_id=${requestId}]` : "");
}
/**
* ProviderHttpError carries the parsed status / detail / requestId so callers
* (e.g. STT runner, TTS fallback chain) can classify failures without re-parsing.
*/
var ProviderHttpError = class extends Error {
	status;
	detail;
	requestId;
	label;
	code;
	url;
	bodyPreview;
	constructor(parts) {
		super(formatProviderHttpErrorMessage(parts));
		this.name = "ProviderHttpError";
		this.status = parts.status;
		this.detail = parts.detail;
		this.requestId = parts.requestId;
		this.label = parts.label;
		this.code = parts.code;
		this.url = parts.url;
		this.bodyPreview = parts.bodyPreview;
	}
};
async function createProviderHttpError(response, label, options) {
	const rawBody = trimToUndefined(await readResponseTextLimited(response));
	const vendor = rawBody ? extractVendorErrorFields(rawBody) : {};
	let detail;
	if (rawBody) try {
		detail = formatProviderErrorPayload(JSON.parse(rawBody)) ?? truncateErrorDetail(rawBody);
	} catch {
		detail = truncateErrorDetail(rawBody);
	}
	const requestId = extractProviderRequestId(response);
	return new ProviderHttpError({
		label,
		status: response.status,
		detail,
		requestId,
		statusPrefix: options?.statusPrefix,
		code: vendor.code
	});
}
async function assertOkOrThrowProviderError(response, label) {
	if (response.ok) return;
	throw await createProviderHttpError(response, label);
}
async function assertOkOrThrowHttpError(response, label) {
	if (response.ok) return;
	throw await createProviderHttpError(response, label, { statusPrefix: "HTTP " });
}
/**
* Throws {@link ProviderHttpError} when the response is not OK; otherwise returns
* the response. Body is consumed on failure (do not re-read).
*
* Used by bundled image-generation HTTP helpers (vendor code extraction + body preview).
*/
async function assertOk(res, contextUrl) {
	if (res.ok) return res;
	const url = contextUrl ?? res.url;
	const bodyPreview = trimToUndefined(await readResponseTextLimited(res));
	const { code, message } = extractVendorErrorFields(bodyPreview ?? "");
	const finalMessage = `Provider HTTP ${res.status} ${res.statusText}: ${message || bodyPreview || "(empty body)"}`;
	throw new ProviderHttpError({
		label: url,
		status: res.status,
		code,
		url,
		bodyPreview,
		detail: message,
		requestId: extractProviderRequestId(res),
		messageOverride: finalMessage
	});
}
//#endregion
export { ProviderHttpError, assertOk, assertOkOrThrowHttpError, assertOkOrThrowProviderError, createProviderHttpError, extractProviderErrorDetail, extractProviderRequestId, extractVendorErrorFields, formatProviderErrorPayload, formatProviderHttpErrorMessage, readResponseTextLimited, truncateErrorDetail };

//# sourceMappingURL=provider-http-errors.js.map