/** * OllamaThinkingHandler — normalizes a local reasoning model's thinking * into the framework's `ThinkingBlock[]` contract. * * Local reasoning models (deepseek-r1, qwen3, gpt-oss, …) reason in the * open, and where that reasoning LANDS depends on how they were asked: * * 1. ASKED (`ollama('qwen3', { think: true })`, or the agent's * `.thinking({ budget })`) — Ollama lifts the reasoning out of the * answer into `message.thinking`. The adapter forwards it as * `{ kind: 'field', thinking }`. This is the good path. * * 2. NOT ASKED — the same model writes `` straight into * the answer text. The adapter recognizes the shape and forwards the * whole answer as `{ kind: 'inline', content }`. * * **The library recognizes the inline shape; it never rewrites the * answer.** Blocks produced from case 2 describe text that is STILL * present, verbatim, in `LLMResponse.content` — because silently editing * a model's answer is a meaning change, and that is the application's * decision to make, not this library's. If you want the reasoning out of * the answer, ask for it: turn `think` on, and the model stops putting it * there. * * **No signature** — nothing on this wire is signed, so there is no * round-trip integrity invariant (unlike Anthropic). `signature` stays * undefined. * * **No `summary` flag** — this is raw reasoning, not a structured summary * (unlike OpenAI's `reasoning_summary`). * * **`parseChunk`** — the adapter already emits `LLMChunk.thinkingDelta` * directly while streaming (Ollama sends `message.thinking` deltas frame * by frame), so the framework does not need this to see live reasoning. * It is implemented anyway for consumers driving the handler themselves. */ import type { ThinkingHandler } from './types.js'; /** * What `OllamaProvider` puts on `LLMResponse.rawThinking`. * * Tagged rather than a bare string so the handler never has to GUESS which * of the two situations it is looking at — and so a consumer reading the * raw value can tell whether the reasoning is also sitting in the answer. */ export type OllamaRawThinking = { readonly kind: 'field'; readonly thinking: string; } | { readonly kind: 'inline'; readonly content: string; }; /** * Pull the reasoning out of text that carries `` tags. * * All tag PARSING lives here, in the handler — the adapter only notices * that a `