//#region src/harmony.ts
const START_TOKEN = "<|start|>";
const END_TOKEN = "<|end|>";
const RETURN_TOKEN = "<|return|>";
const CALL_TOKEN = "<|call|>";
const CHANNEL_TOKEN = "<|channel|>";
const MESSAGE_TOKEN = "<|message|>";
const CONSTRAIN_TOKEN = "<|constrain|>";
const CONTROL_TOKENS = [
	{
		type: "START",
		literal: START_TOKEN
	},
	{
		type: "END",
		literal: END_TOKEN
	},
	{
		type: "RETURN",
		literal: RETURN_TOKEN
	},
	{
		type: "CALL",
		literal: CALL_TOKEN
	},
	{
		type: "CHANNEL",
		literal: CHANNEL_TOKEN
	},
	{
		type: "MESSAGE",
		literal: MESSAGE_TOKEN
	},
	{
		type: "CONSTRAIN",
		literal: CONSTRAIN_TOKEN
	}
];
const CONTROL_LITERAL = {
	START: START_TOKEN,
	END: END_TOKEN,
	RETURN: RETURN_TOKEN,
	CALL: CALL_TOKEN,
	CHANNEL: CHANNEL_TOKEN,
	MESSAGE: MESSAGE_TOKEN,
	CONSTRAIN: CONSTRAIN_TOKEN
};
const RECIPIENT_RE = /to=functions\.([A-Za-z_][\w.-]*)/;
/**
* Cheap detection guard — only ATTEMPT a parse when a harmony structure looks
* present, i.e. a `<|channel|>` followed (anywhere after it) by a `<|message|>`,
* OR a `<|message|>` appearing after a `<|start|>`.
*
* This is a fast-path gate, NOT the authority on well-formedness: the state
* machine in {@link parseHarmonyContent} makes the real decision and is itself
* fully fail-safe. Requiring the token pairing keeps hosted/structured answers
* that merely MENTION a single token as prose out of the parser entirely.
*/
function isHarmonyContent(content) {
	const channelIdx = content.indexOf(CHANNEL_TOKEN);
	if (channelIdx !== -1) {
		if (content.indexOf(MESSAGE_TOKEN, channelIdx + 11) !== -1) return true;
	}
	const startIdx = content.indexOf(START_TOKEN);
	if (startIdx !== -1) {
		if (content.indexOf(MESSAGE_TOKEN, startIdx + 9) !== -1) return true;
	}
	return false;
}
/**
* Lex the accumulated content into an ordered token stream via a single
* left-to-right cursor walk. At each position, match a control token by exact
* prefix; otherwise accumulate bytes into the current TEXT run until the next
* control token or EOF.
*
* Bytes consumed into a TEXT span are NEVER re-scanned for control tokens, so
* an embedded literal "<|call|>"/"<|channel|>" inside a JSON string or prose is
* inert. The lexer NEVER throws; it always returns a complete token stream.
*/
function lex(raw) {
	const tokens = [];
	let cursor = 0;
	let textStart = 0;
	const flushText = (end) => {
		if (end > textStart) tokens.push({
			kind: "text",
			value: raw.slice(textStart, end)
		});
	};
	while (cursor < raw.length) {
		let matched;
		if (raw.startsWith("<|", cursor)) {
			for (const tok of CONTROL_TOKENS) if (raw.startsWith(tok.literal, cursor)) {
				matched = tok;
				break;
			}
		}
		if (matched) {
			flushText(cursor);
			tokens.push({
				kind: "control",
				type: matched.type
			});
			cursor += matched.literal.length;
			textStart = cursor;
		} else cursor += 1;
	}
	flushText(raw.length);
	return tokens;
}
/** True when `s` is empty or only whitespace. */
function isBlank(s) {
	return s.trim().length === 0;
}
/**
* True when `s` parses as a JSON OBJECT — a non-null, non-array `{...}` value.
*
* Harmony tool-call arguments are JSON OBJECTS. A bare JSON SCALAR (number /
* boolean / string / null) or ARRAY parses as valid JSON but is NOT a valid
* tool-call argument, so it must not terminate a tool call (fail-safe verbatim
* per the uniform contract). The object check (and ONLY the object check) is
* what makes a commentary `<|call|>` body a tool call; embedded token-shaped
* substrings INSIDE the object's string values remain valid data (matrix 13/14).
*/
function isToolArgsObject(s) {
	let value;
	try {
		value = JSON.parse(s);
	} catch {
		return false;
	}
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
* Extract the channel name from the header text that follows `<|channel|>`. The
* channel name is the leading token, delimited by whitespace (the rest of the
* header carries optional `to=functions.NAME` routing). A `<|constrain|>` token
* is lexed separately, so it never appears inside this text.
*/
function headerChannel(headerText) {
	return headerText.trim().split(/\s+/)[0] ?? "";
}
/** The harmony channels a real `<|channel|>` header may name. */
const KNOWN_CHANNELS = new Set([
	"analysis",
	"commentary",
	"final"
]);
/**
* True when token index `idx` begins a well-formed harmony message header —
* used as lookahead to decide whether a `<|start|>` is a real message boundary
* (terminating the current body) or a literal `<|start|>` quoted inside a prose
* body. A real message header reaches a `<|message|>` token via the optional
* `START? role-TEXT? CHANNEL? header-TEXT? CONSTRAIN? constraint-TEXT?` prefix
* WITHOUT first crossing a body terminator (END/RETURN/CALL) or EOF.
*
* When the lookahead carries a `<|channel|>` header, the channel name it names
* must be a KNOWN harmony channel (analysis/commentary/final). A lookahead like
* `<|start|>...<|channel|>X<|message|>` whose X is unknown is NOT a real
* boundary — it narrows the quoted-message ambiguity so a body quoting a
* bogus-channel pseudo-message is not split on it. A channel-LESS header
* (`<|start|>role<|message|>...`) is unaffected (KNOWN_CHANNELS only gates a
* present `<|channel|>` name).
*/
function looksLikeMessageStart(tokens, idx) {
	let k = idx;
	if (tokens[k]?.kind === "control" && tokens[k].type === "START") {
		k += 1;
		if (tokens[k]?.kind === "text") k += 1;
	}
	if (tokens[k]?.kind === "control" && tokens[k].type === "CHANNEL") {
		k += 1;
		if (tokens[k]?.kind === "text") {
			if (!KNOWN_CHANNELS.has(headerChannel(tokens[k].value))) return false;
			k += 1;
		}
		if (tokens[k]?.kind === "control" && tokens[k].type === "CONSTRAIN") {
			k += 1;
			if (tokens[k]?.kind === "text") k += 1;
		}
	}
	return tokens[k]?.kind === "control" && tokens[k].type === "MESSAGE";
}
/**
* True when the position right after a body terminator candidate (END/RETURN/
* CALL at the token before `idx`) is a REAL message boundary: either EOF
* (optionally preceded by whitespace-only TEXT spans) or the start of a
* well-formed next message. When false, the terminator candidate is a literal
* control token embedded in a prose body.
*/
function isRealBoundaryAfter(tokens, idx) {
	let k = idx;
	while (tokens[k]?.kind === "text" && tokens[k].value.trim().length === 0) k += 1;
	if (k >= tokens.length) return true;
	return looksLikeMessageStart(tokens, k);
}
/**
* True when the token at index `idx` is a NON-BLANK TEXT span — i.e. real prose
* follows. A control-token literal embedded in a non-tool body is only
* LEGITIMATELY prose when it is bracketed by real text (e.g. a final answer that
* quotes "the `<|end|>` token"); the lexer will have tokenized the quoted
* literal, and its immediate follower being non-blank prose is what makes it
* inert body text rather than structure. When the follower is instead another
* control token or EOF (or only whitespace), the literal is NOT embedded prose —
* it is a control token that would leak into routed content/reasoning, so the
* body must fail safe. This is the STRUCTURAL fail-safe invariant: it fires at
* absorption time on EVERY exit path, not per-exit, so a leak-shaped body can
* never reach the routing step regardless of how its loop terminates.
*/
function hasProseFollower(tokens, idx) {
	const next = tokens[idx];
	return next !== void 0 && next.kind === "text" && next.value.trim().length > 0;
}
/** Sentinel thrown internally to unwind to the uniform fail-safe path. */
const FAIL = Symbol("harmony-fail");
/**
* Walk the token stream against the harmony grammar and route each message by
* channel. Throws {@link FAIL} on ANY grammar deviation so {@link
* parseHarmonyContent} returns the original bytes verbatim (uniform
* all-or-nothing fail-safe). On success returns fully-routed channels.
*/
function parseTokens(tokens) {
	let content = "";
	let reasoning = "";
	const toolCalls = [];
	let i = 0;
	const peek = () => tokens[i];
	const fail = () => {
		throw FAIL;
	};
	if (peek()?.kind === "text") {
		const t = tokens[i];
		const next = tokens[i + 1];
		if (next !== void 0 && next.kind === "control" && (next.type === "START" || next.type === "CHANNEL")) {
			if (!isBlank(t.value)) content += t.value;
			i += 1;
		}
	}
	let sawMessage = false;
	let nextIsQuotedSplit = false;
	while (i < tokens.length) {
		const tok = peek();
		if (tok === void 0) break;
		if (tok.kind === "text") {
			if (isBlank(tok.value)) {
				i += 1;
				continue;
			}
			fail();
		}
		const fromQuotedSplit = nextIsQuotedSplit;
		nextIsQuotedSplit = false;
		let recipient;
		let channel = "";
		let sawHeader = false;
		if (tok.kind === "control" && tok.type === "START") {
			sawHeader = true;
			i += 1;
			if (peek()?.kind === "text") {
				recipient = tokens[i].value.match(RECIPIENT_RE)?.[1];
				i += 1;
			}
		}
		if (peek()?.kind === "control" && peek().type === "CHANNEL") {
			sawHeader = true;
			i += 1;
			if (peek()?.kind === "text") {
				const headerText = tokens[i].value;
				channel = headerChannel(headerText);
				const headerRecipient = headerText.match(RECIPIENT_RE)?.[1];
				if (headerRecipient !== void 0) recipient = headerRecipient;
				i += 1;
			}
			if (peek()?.kind === "control" && peek().type === "CONSTRAIN") {
				i += 1;
				if (peek()?.kind === "text") i += 1;
			}
			if (!KNOWN_CHANNELS.has(channel)) fail();
		}
		if (!sawHeader) fail();
		if (!(peek()?.kind === "control" && peek().type === "MESSAGE")) fail();
		i += 1;
		const bodyStart = i;
		if (recipient !== void 0 && channel === "commentary") {
			let acc = "";
			let j = bodyStart;
			let parsed;
			for (; j < tokens.length; j++) {
				const t = tokens[j];
				if (t.kind === "control" && t.type === "CALL") {
					const candidate = acc.trim();
					if (isToolArgsObject(candidate)) {
						parsed = candidate;
						break;
					}
					acc += CONTROL_LITERAL.CALL;
					continue;
				}
				if (t.kind === "control") acc += CONTROL_LITERAL[t.type];
				else acc += t.value;
			}
			if (parsed === void 0) fail();
			i = j + 1;
			toolCalls.push({
				name: recipient,
				arguments: parsed
			});
			sawMessage = true;
			continue;
		}
		let body = "";
		let terminated = false;
		let reachedEof = false;
		let absorbedControlLiteral = false;
		let j = i;
		for (; j < tokens.length; j++) {
			const t = tokens[j];
			if (t.kind === "control" && (t.type === "END" || t.type === "RETURN" || t.type === "CALL")) {
				if (isRealBoundaryAfter(tokens, j + 1)) {
					terminated = true;
					break;
				}
				if (fromQuotedSplit || !hasProseFollower(tokens, j + 1)) fail();
				absorbedControlLiteral = true;
				body += CONTROL_LITERAL[t.type];
				continue;
			}
			if (t.kind === "control" && t.type === "START") {
				if (looksLikeMessageStart(tokens, j)) {
					terminated = true;
					break;
				}
				if (fromQuotedSplit || !hasProseFollower(tokens, j + 1)) fail();
				absorbedControlLiteral = true;
				body += CONTROL_LITERAL.START;
				continue;
			}
			if (t.kind === "control" && (t.type === "CHANNEL" || t.type === "MESSAGE")) fail();
			if (t.kind === "control") {
				if (fromQuotedSplit || !hasProseFollower(tokens, j + 1)) fail();
				absorbedControlLiteral = true;
				body += CONTROL_LITERAL[t.type];
				continue;
			}
			body += t.value;
		}
		if (j >= tokens.length) reachedEof = true;
		if (terminated) {
			if (tokens[j].type === "START") {
				i = j;
				nextIsQuotedSplit = true;
			} else i = j + 1;
			routeBody(channel, body);
			sawMessage = true;
			continue;
		}
		if (reachedEof) {
			if (channel === "analysis" || absorbedControlLiteral) fail();
			i = j;
			routeBody(channel, body);
			sawMessage = true;
			break;
		}
		fail();
	}
	if (!sawMessage) fail();
	return {
		content,
		reasoning,
		toolCalls
	};
	function routeBody(ch, body) {
		if (ch === "analysis") reasoning += body;
		else content += body;
	}
}
/**
* Parse harmony channel tokens out of an accumulated assistant `content`
* string, splitting them into final-channel content, analysis-channel
* reasoning, and commentary-channel tool calls. Pure function — no I/O.
*
* Callers should gate this behind {@link isHarmonyContent} so ordinary
* (already-structured) streams are never touched. Even so, this function is
* itself UNIFORM all-or-nothing fail-safe: on ANY structural/validation failure
* it returns `{ content: raw, reasoning: "", toolCalls: [], failed: true }` so
* the original content is preserved VERBATIM and the caller can surface a
* distinct `harmonyUnparsed` signal (NOT a dropped/truncated chunk).
*/
function parseHarmonyContent(raw) {
	const tokens = lex(raw);
	try {
		const { content, reasoning, toolCalls } = parseTokens(tokens);
		return {
			content,
			reasoning,
			toolCalls,
			failed: false
		};
	} catch (err) {
		if (err === FAIL) return {
			content: raw,
			reasoning: "",
			toolCalls: [],
			failed: true
		};
		return {
			content: raw,
			reasoning: "",
			toolCalls: [],
			failed: true
		};
	}
}

//#endregion
export { isHarmonyContent, parseHarmonyContent };
//# sourceMappingURL=harmony.js.map