import { a as llmProvider, c as neutralizeUntrusted, l as maskPii, o as localLlmConfig, s as guardrailsEnabled, u as piiMaskingEnabled } from "./compliance-Bc12Hn9a.js";
import Anthropic from "@anthropic-ai/sdk";
//#region src/core/resilience.ts
var CircuitBreaker = class {
	opts;
	failures = 0;
	openedAt = null;
	_state = "closed";
	constructor(opts) {
		this.opts = opts;
	}
	get state() {
		return this._state;
	}
	async call(fn) {
		if (this._state === "open") if (Date.now() - (this.openedAt ?? 0) >= this.opts.halfOpenAfter) this._state = "half-open";
		else throw new Error("Circuit open");
		try {
			const result = await fn();
			this.failures = 0;
			this._state = "closed";
			this.openedAt = null;
			return result;
		} catch (err) {
			this.failures++;
			if (this._state === "half-open" || this.failures >= this.opts.threshold) {
				this._state = "open";
				this.openedAt = Date.now();
				this.failures = 0;
			}
			throw err;
		}
	}
};
//#endregion
//#region src/core/input-guard.ts
function guardIsoDate(val, field) {
	if (typeof val !== "string" || !val) throw new Error(`${field}: invalid date`);
	const d = new Date(val);
	if (isNaN(d.getTime())) throw new Error(`${field}: invalid date`);
	if (/^\d{4}-\d{2}-\d{2}/.test(val)) {
		const [year, month, day] = val.slice(0, 10).split("-").map(Number);
		if (month < 1 || month > 12 || day < 1 || day > 31) throw new Error(`${field}: invalid date`);
		const reparse = /* @__PURE__ */ new Date(`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`);
		if (isNaN(reparse.getTime()) || reparse.getMonth() + 1 !== month) throw new Error(`${field}: invalid date`);
	}
	return val;
}
const DEFAULT_LLM_MAX_BYTES = 512 * 1024;
function guardLlmResponse(response, maxBytes = DEFAULT_LLM_MAX_BYTES) {
	if (typeof response !== "string") throw new Error("LLM response: expected string");
	const byteLen = Buffer.byteLength(response, "utf-8");
	if (byteLen > maxBytes) throw new Error(`LLM response exceeds ${maxBytes} bytes (got ${byteLen})`);
	return response;
}
//#endregion
//#region src/core/llm.ts
const MODEL = "claude-haiku-4-5-20251001";
let _client = null;
let llmCircuit = new CircuitBreaker({
	threshold: 3,
	timeoutMs: 3e4,
	halfOpenAfter: 3e4
});
function getClient() {
	if (!process.env["ANTHROPIC_API_KEY"]) return null;
	if (!_client) _client = new Anthropic();
	return _client;
}
function emailFallback(snippet) {
	return {
		summary: snippet.slice(0, 300),
		sentiment: "neutral",
		nextSteps: []
	};
}
async function summarizeEmail(subject, snippet, from) {
	const client = getClient();
	if (!client) return emailFallback(snippet);
	try {
		const textBlock = (await client.messages.create({
			model: MODEL,
			max_tokens: 200,
			system: [{
				type: "text",
				text: "You are a CRM assistant. Extract structured information from email metadata.\nReturn ONLY valid JSON matching: { \"summary\": string (2 sentences, German), \"sentiment\": \"positive\"|\"neutral\"|\"negative\"|\"urgent\", \"nextSteps\": string[] }",
				cache_control: { type: "ephemeral" }
			}],
			messages: [{
				role: "user",
				content: `Subject: ${subject}\nFrom: ${from}\nContent: ${snippet}`
			}]
		})).content.find((b) => b.type === "text");
		if (!textBlock || textBlock.type !== "text") return emailFallback(snippet);
		try {
			return JSON.parse(textBlock.text);
		} catch {
			return emailFallback(snippet);
		}
	} catch {
		return emailFallback(snippet);
	}
}
async function recognizeCustomer(transcriptContent, candidates) {
	if (candidates.length === 0) return {
		slug: null,
		confidence: "low"
	};
	const client = getClient();
	if (!client) return {
		slug: null,
		confidence: "low"
	};
	try {
		const textBlock = (await client.messages.create({
			model: MODEL,
			max_tokens: 100,
			system: [{
				type: "text",
				text: "You are a CRM assistant. Match a meeting transcript to the most likely customer.\nReturn ONLY valid JSON: { \"slug\": string|null, \"confidence\": \"high\"|\"medium\"|\"low\" }\nslug must be one of the provided candidates or null if no match.",
				cache_control: { type: "ephemeral" }
			}],
			messages: [{
				role: "user",
				content: `Available customers: ${candidates.map((c) => `${c.slug} (${c.name})`).join(", ")}\nTranscript (first 1000 chars): ${transcriptContent.slice(0, 1e3)}`
			}]
		})).content.find((b) => b.type === "text");
		if (!textBlock || textBlock.type !== "text") return {
			slug: null,
			confidence: "low"
		};
		try {
			return JSON.parse(textBlock.text);
		} catch {
			return {
				slug: null,
				confidence: "low"
			};
		}
	} catch {
		return {
			slug: null,
			confidence: "low"
		};
	}
}
function recordCall(model, inputTokens, outputTokens, ctx) {
	const dataDir = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
	import("./usage-BVlFlKW_.js").then(({ recordUsage }) => recordUsage(dataDir, {
		...ctx?.slug ? { slug: ctx.slug } : {},
		...ctx?.tool ? { tool: ctx.tool } : {},
		model,
		inputTokens,
		outputTokens
	}));
}
/**
* Local-LLM path (D17): call an OpenAI-compatible endpoint (Ollama/local) via
* fetch — no extra dependency — so customer data can stay on-machine. Usage is
* still recorded for cost/observability parity with the Anthropic path.
*/
async function callLocalLlm(masked, ctx) {
	const { baseUrl, model } = localLlmConfig();
	const res = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({
			model,
			max_tokens: 500,
			messages: [{
				role: "user",
				content: masked
			}]
		})
	});
	if (!res.ok) throw new Error(`Local LLM error ${res.status}`);
	const data = await res.json();
	const text = data.choices?.[0]?.message?.content;
	if (!text) throw new Error("No text response from local LLM");
	if (data.usage) recordCall(model, data.usage.prompt_tokens ?? 0, data.usage.completion_tokens ?? 0, ctx);
	return text;
}
async function callLlm(prompt, ctx) {
	const provider = llmProvider();
	const client = provider === "anthropic" ? getClient() : null;
	if (provider === "anthropic" && !client) throw new Error("ANTHROPIC_API_KEY not set");
	const guarded = guardrailsEnabled() ? neutralizeUntrusted(prompt) : prompt;
	const { masked, unmask } = piiMaskingEnabled() ? maskPii(guarded) : {
		masked: guarded,
		unmask: (t) => t
	};
	if (provider !== "anthropic") return llmCircuit.call(async () => unmask(guardLlmResponse(await callLocalLlm(masked, ctx))));
	return llmCircuit.call(async () => {
		const response = await client.messages.create({
			model: MODEL,
			max_tokens: 500,
			messages: [{
				role: "user",
				content: masked
			}]
		});
		const usage = response.usage;
		if (usage) recordCall(MODEL, usage.input_tokens, usage.output_tokens, ctx);
		const textBlock = response.content.find((b) => b.type === "text");
		if (!textBlock || textBlock.type !== "text") throw new Error("No text response from LLM");
		return unmask(guardLlmResponse(textBlock.text));
	});
}
const FIELD_ALIASES = {
	name: [
		"company name",
		"company",
		"organization",
		"organisation",
		"account name",
		"name",
		"firma"
	],
	email: [
		"email address",
		"e-mail",
		"email",
		"e-mail address",
		"mail"
	],
	domain: [
		"company domain",
		"website",
		"domain",
		"url",
		"web",
		"homepage"
	],
	phone: [
		"phone number",
		"phone",
		"tel",
		"telephone",
		"mobile",
		"cell"
	],
	industry: [
		"industry",
		"sector",
		"branche",
		"vertical"
	],
	primary_contact: [
		"contact name",
		"contact person",
		"contact",
		"ansprechpartner",
		"kontakt"
	],
	timezone: [
		"timezone",
		"time zone",
		"tz"
	],
	notes: [
		"notes",
		"description",
		"body",
		"comment",
		"details",
		"note",
		"inhalt",
		"subject",
		"summary"
	],
	date: [
		"activity date",
		"activity_date",
		"due date",
		"date",
		"created_at",
		"timestamp",
		"time"
	],
	activityType: [
		"activity type",
		"activity_type",
		"activitytype",
		"type",
		"category",
		"art"
	],
	sourceId: [
		"record id",
		"record_id",
		"source id",
		"source_id",
		"external id",
		"external_id",
		"activity id"
	]
};
function mapCsvFieldsHeuristic(headers, targetFields) {
	const result = {};
	const usedHeaders = /* @__PURE__ */ new Set();
	for (const field of targetFields) {
		const aliases = FIELD_ALIASES[field] ?? [field];
		let matched = null;
		for (const header of headers) {
			if (usedHeaders.has(header)) continue;
			const lower = header.toLowerCase();
			if (aliases.some((alias) => lower === alias || lower.includes(alias))) {
				matched = header;
				break;
			}
		}
		result[field] = matched;
		if (matched) usedHeaders.add(matched);
	}
	return result;
}
const FIELD_SEMANTICS = `CRM field semantics:
- name: Company or organization name (required)
- email: Contact email address
- domain: Company website or domain (e.g. "acme.com")
- notes: Interaction notes, description, or subject text
- date: Date of activity/interaction (ISO 8601 or YYYY-MM-DD)
- activityType: Type of interaction — Call, Email, Meeting, Note
- sourceId: Unique ID from the source system used for deduplication`;
async function mapCsvFields(headers, targetFields) {
	const client = getClient();
	if (!client) return mapCsvFieldsHeuristic(headers, targetFields);
	try {
		const textBlock = (await client.messages.create({
			model: MODEL,
			max_tokens: 300,
			system: [{
				type: "text",
				text: `You are a CRM data-import assistant. Map CSV column headers to internal CRM field names.

${FIELD_SEMANTICS}

Rules:
1. Return ONLY valid JSON: { "<crmField>": "<csvColumn>" | null, ... }
2. Every requested CRM field must appear as a key in the response.
3. Use null when no column is a reasonable match.
4. Each CSV column may only be assigned to one CRM field.
5. Only use column names that appear exactly in the provided CSV columns list.`,
				cache_control: { type: "ephemeral" }
			}],
			messages: [{
				role: "user",
				content: `CSV columns: ${JSON.stringify(headers)}\nMap to CRM fields: ${JSON.stringify(targetFields)}`
			}]
		})).content.find((b) => b.type === "text");
		if (!textBlock || textBlock.type !== "text") return mapCsvFieldsHeuristic(headers, targetFields);
		try {
			const raw = JSON.parse(textBlock.text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
			const validated = {};
			const headerSet = new Set(headers);
			for (const field of targetFields) {
				const col = raw[field] ?? null;
				validated[field] = col !== null && headerSet.has(col) ? col : null;
			}
			if (!validated["name"]) return mapCsvFieldsHeuristic(headers, targetFields);
			return validated;
		} catch {
			return mapCsvFieldsHeuristic(headers, targetFields);
		}
	} catch {
		return mapCsvFieldsHeuristic(headers, targetFields);
	}
}
//#endregion
export { summarizeEmail as a, recognizeCustomer as i, mapCsvFields as n, guardIsoDate as o, mapCsvFieldsHeuristic as r, callLlm as t };

//# sourceMappingURL=llm-PZzgPphl.js.map