/**
* Keeping a model's reasoning out of its answer, at the provider boundary.
*
* A cerebras reply put reasoning into an ntfy notification whose surface
* policy is `reasoningVisibility: 'suppress'`. The suppression was never at
* fault, the reasoning never reached the reasoning channel at all, so nothing
* downstream could tell it apart from the answer. It reached the transcript,
* the session export and every channel body as plain assistant text.
*
* Reasoning arrives in two shapes, and BOTH were landing in `content`:
*
* 1. A structured field (`reasoning`, `reasoning_content`).
* openai-stream-delta.ts knows how to route these, but only when its
* `allowReasoning` option is set, and openai-compat derived that from the
* provider's `reasoningFormat`, which is a REQUEST-side setting naming
* which reasoning PARAMETER an endpoint accepts. Cerebras, groq and mistral
* are all registered `reasoningFormat: 'none'`, which told the extractor to
* FOLD a returned reasoning field into `content`. Cerebras returns
* reasoning on exactly that field, so its chain-of-thought became ordinary
* answer text, interleaved with the real answer. What a response CARRIES is
* not a function of what the request ASKED FOR: a reasoning field is
* reasoning on every endpoint, so openai-compat now always classifies it.
*
* 2. A tag inside the content stream (`…`), which some models
* emit with no structured field at all. {@link splitInlineReasoning} and
* {@link InlineReasoningStreamSplitter} below handle that shape.
*
* Both belong here rather than at a render site: the wire format is the
* provider's concern, and splitting once means every consumer, TUI
* transcript, webui, session export, channel surfaces, gets the same correct
* content/reasoning split and applies its own visibility policy to it. A
* filter at any single render site would fix that site and leave every other
* one reading reasoning as the answer.
*
* One invariant governs all of it: the split must never EMPTY a reply. A model
* that writes nothing outside its reasoning has no answer beside it, so the
* reasoning IS the answer, see the floor in openai-compat.ts, which is what
* the old folding behaviour was really protecting.
*/
/**
* Tag names recognised as inline reasoning wrappers.
*
* Deliberately short. Each entry is a name that models emit as a literal
* XML-ish wrapper around chain-of-thought; a name that could plausibly appear
* as real markup in an answer does not belong here.
*/
export declare const INLINE_REASONING_TAGS: readonly string[];
export interface InlineReasoningSplit {
/** The answer, with every reasoning span removed. */
readonly content: string;
/** The reasoning spans, joined in the order they appeared. */
readonly reasoning: string;
}
/**
* Split a COMPLETE assistant message into answer and inline reasoning.
*
* An unterminated opening tag (the model was cut off mid-thought, or the
* endpoint dropped the closing tag) takes everything after it as reasoning
* rather than leaving a dangling `` in the answer.
*/
export declare function splitInlineReasoning(text: string): InlineReasoningSplit;
/**
* Streaming form of {@link splitInlineReasoning}.
*
* Applied to the delta loop rather than only to the assembled response so the
* two agree: splitting only at the end would stream the reasoning to the TUI
* as answer text and then retract it when the turn completed. Text that could
* still turn out to be the start of a delimiter is held back until the next
* chunk resolves it, so a tag split across chunk boundaries is not missed and
* no partial tag is ever emitted.
*
* `flush()` must be called once the stream ends to release whatever is held.
*/
export declare class InlineReasoningStreamSplitter {
private buffer;
private openTag;
push(chunk: string): InlineReasoningSplit;
/** Release the held-back tail. Inside an unterminated tag it is reasoning. */
flush(): InlineReasoningSplit;
}
/** What a stream-delta extractor produced for one chunk. */
export interface StreamTextFragments {
readonly content: readonly string[];
readonly reasoning: readonly string[];
}
/** Forwards a classified fragment to the caller's live delta handler. */
export type StreamTextEmit = (delta: {
content?: string;
reasoning?: string;
}) => void;
/**
* Accumulates one streamed turn, keeping reasoning out of the answer.
*
* Owns both shapes reasoning arrives in, a structured `reasoning` /
* `reasoning_content` field, and a tag inside the content stream, and keeps
* the running answer and the running reasoning apart, forwarding each fragment
* as it is classified so a live view is never shown reasoning as answer text
* and then made to retract it.
*/
export declare class StreamTextAccumulator {
private readonly splitter;
private contentText;
private reasoningText;
push(fragments: StreamTextFragments, emit?: StreamTextEmit): void;
/**
* Release whatever the splitter still holds back and report the turn.
*
* `content` is empty when the model wrote nothing outside its reasoning,
* the caller decides what that means, since an empty answer is normal on a
* tool-call turn and a lost reply on any other.
*/
finish(emit?: StreamTextEmit): InlineReasoningSplit;
private take;
}
//# sourceMappingURL=inline-reasoning.d.ts.map