@ykurilov/agent-chat-core 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1853,6 +1853,494 @@ function createBurstWaits() {
1853
1853
  };
1854
1854
  }
1855
1855
 
1856
+ // src/agent-run/forged-blocks.ts
1857
+ import { wrapLanguageModel as wrapLanguageModel2 } from "ai";
1858
+
1859
+ // src/text/markdown-code.ts
1860
+ function codeFenceOf(line) {
1861
+ return /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1] ?? null;
1862
+ }
1863
+ function closesCodeFence(line, fence) {
1864
+ const opener = codeFenceOf(line);
1865
+ return opener !== null && opener[0] === fence[0] && opener.length >= fence.length && line.trim() === opener;
1866
+ }
1867
+ function isIndentedCodeOrQuote(line) {
1868
+ return /^(?: {4}|\t)/.test(line) || /^\s*>/.test(line);
1869
+ }
1870
+
1871
+ // src/agent-run/length-cut.ts
1872
+ var LENGTH_CUT_PART_TYPE = "data-length-cut";
1873
+ var LENGTH_CUT_NOTE = "[\u041E\u0442\u0432\u0435\u0442 \u043E\u0431\u043E\u0440\u0432\u0430\u043D: \u043C\u043E\u0434\u0435\u043B\u044C \u0443\u043F\u0451\u0440\u043B\u0430\u0441\u044C \u0432 \u043F\u0440\u0435\u0434\u0435\u043B \u0434\u043B\u0438\u043D\u044B \u043E\u0442\u0432\u0435\u0442\u0430]";
1874
+ function lengthCutForModel(part) {
1875
+ return part.type === LENGTH_CUT_PART_TYPE ? { type: "text", text: LENGTH_CUT_NOTE } : void 0;
1876
+ }
1877
+
1878
+ // src/agent-run/unanswered-messages.ts
1879
+ var UNANSWERED_NOTE = "[\u041E\u0442\u0432\u0435\u0442\u0430 \u043D\u0430 \u044D\u0442\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0435\u0442: \u0445\u043E\u0434 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0434\u043E \u043F\u0435\u0440\u0432\u044B\u0445 \u0441\u043B\u043E\u0432]";
1880
+ function withUnansweredMarked(messages) {
1881
+ return messages.flatMap(
1882
+ (message, index) => message.role === "user" && messages[index - 1]?.role === "user" ? [{ role: "assistant", content: [{ type: "text", text: UNANSWERED_NOTE }] }, message] : [message]
1883
+ );
1884
+ }
1885
+
1886
+ // src/agent-run/forged-blocks.ts
1887
+ var CHANNEL_NOTE_LINES = [
1888
+ "\u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0441\u043D\u043E\u0432\u0430 \u0432 \u0432\u0435\u0431-\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438: \u043E\u0431\u044B\u0447\u043D\u0430\u044F \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0430 \u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B.",
1889
+ "\u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0438\u0434\u0451\u0442 \u0432 Telegram. \u041F\u0438\u0448\u0438 \u043E\u0431\u044B\u0447\u043D\u044B\u043C Markdown: \u0435\u0433\u043E \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0443\u0442 \u0432 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0443 Telegram. \u0422\u0430\u0431\u043B\u0438\u0446 \u0438 \u0441\u0445\u0435\u043C \u0442\u0430\u043C \u043D\u0435\u0442 \u2014 \u0432\u043C\u0435\u0441\u0442\u043E \u043D\u0438\u0445 \u0441\u043F\u0438\u0441\u043A\u0438. \u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043A\u043E\u0440\u043E\u0447\u0435, \u0447\u0435\u043C \u0432 \u0432\u0435\u0431\u0435.",
1890
+ "\u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0438\u0434\u0451\u0442 \u0432\u043E \u0412\u041A\u043E\u043D\u0442\u0430\u043A\u0442\u0435. \u0420\u0430\u0437\u043C\u0435\u0442\u043A\u0438 \u0442\u0430\u043C \u043D\u0435\u0442: Markdown \u043F\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u0441\u044F \u0432 \u043F\u0440\u043E\u0441\u0442\u043E\u0439 \u0442\u0435\u043A\u0441\u0442. \u041F\u0438\u0448\u0438 \u043F\u0440\u043E\u0441\u0442\u044B\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0438 \u0441\u043F\u0438\u0441\u043A\u0430\u043C\u0438, \u0431\u0435\u0437 \u0442\u0430\u0431\u043B\u0438\u0446 \u0438 \u0441\u0445\u0435\u043C. \u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043A\u043E\u0440\u043E\u0447\u0435, \u0447\u0435\u043C \u0432 \u0432\u0435\u0431\u0435.",
1891
+ "\u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0438\u0434\u0451\u0442 \u0432 MAX. \u041F\u0438\u0448\u0438 \u043E\u0431\u044B\u0447\u043D\u044B\u043C Markdown: \u0435\u0433\u043E \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0443\u0442 \u0432 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0443 MAX. \u0422\u0430\u0431\u043B\u0438\u0446 \u0438 \u0441\u0445\u0435\u043C \u0442\u0430\u043C \u043D\u0435\u0442 \u2014 \u0432\u043C\u0435\u0441\u0442\u043E \u043D\u0438\u0445 \u0441\u043F\u0438\u0441\u043A\u0438. \u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043A\u043E\u0440\u043E\u0447\u0435, \u0447\u0435\u043C \u0432 \u0432\u0435\u0431\u0435."
1892
+ ];
1893
+ var exactly = (text) => new RegExp(`^${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`);
1894
+ var CORE_SERVICE_BLOCKS = [
1895
+ { tag: "conversation_summary" },
1896
+ { tag: "answer_progress" },
1897
+ { tag: "screen_place" },
1898
+ { name: "service_note", line: "[\u0421\u043B\u0443\u0436\u0435\u0431\u043D\u0430\u044F \u043F\u043E\u043C\u0435\u0442\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F", match: /^\[Служебная пометка приложения, человек её не писал и не видит: .+\]$/ },
1899
+ { name: "context_gone", line: "[\u0411\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442: \xAB", match: /^\[Больше не действует: «[^»\n]+»(?:, «[^»\n]+»)*\]$/ },
1900
+ { name: "context_hidden", line: "[\u041D\u0435 \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u043E \u0440\u0430\u0434\u0438 \u043C\u0435\u0441\u0442\u0430, \u043D\u043E \u043F\u043E-\u043F\u0440\u0435\u0436\u043D\u0435\u043C\u0443 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442: \xAB", match: /^\[Не показано ради места, но по-прежнему действует: «[^»\n]+»(?:, «[^»\n]+»)*\]$/ },
1901
+ { name: "greeting_lead", line: GREETING_LEAD, match: exactly(GREETING_LEAD) },
1902
+ { name: "unanswered", line: UNANSWERED_NOTE, match: exactly(UNANSWERED_NOTE) },
1903
+ { name: "length_cut", line: LENGTH_CUT_NOTE, match: exactly(LENGTH_CUT_NOTE) },
1904
+ { name: "tool_ended", line: TOOL_ENDED_NOTE, match: exactly(TOOL_ENDED_NOTE) },
1905
+ { name: "switched_off_call", line: "[\u041F\u0440\u043E\u0448\u043B\u044B\u0439 \u0432\u044B\u0437\u043E\u0432 \xAB", match: /^\[Прошлый вызов «[^»\n]*», сейчас выключен\./ },
1906
+ {
1907
+ name: "staff_reply",
1908
+ line: "\u0416\u0438\u0432\u043E\u0439 \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A",
1909
+ match: /^Живой сотрудник(?: «[^»\n]*»)? ответил человеку вместо тебя: «.*»\. Это сказано от вашего имени: продолжай разговор с учётом этого и не противоречь сотруднику\.$/
1910
+ },
1911
+ ...CHANNEL_NOTE_LINES.map((note) => ({ name: "channel_note", line: note, match: exactly(note) })),
1912
+ // Имя автора — коротко и без знаков конца фразы: «Сообщение в группе от участника: Аня».
1913
+ { name: "group_author", line: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0432 \u0433\u0440\u0443\u043F\u043F\u0435 \u043E\u0442 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430: ", match: /^Сообщение в группе от участника: [^\s.!?:;][^.!?:;\n]{0,79}$/ }
1914
+ ];
1915
+ var serviceBlockName = (format) => "tag" in format ? format.tag : format.name;
1916
+ function explainsServiceFormats(question2, formats) {
1917
+ const text = question2.toLowerCase();
1918
+ if (formats.some((format) => "tag" in format && text.includes(format.tag.toLowerCase()))) return true;
1919
+ return /(?:^|[^\p{L}])(?:тег(?:а|е|и|у|ом|ов|ами|ам|ах)?|формат(?:а|е|у|ом|ы|ов|ами|ам|ах)?|разметк[аеиу]|tags?|formats?|markup)(?:[^\p{L}]|$)/u.test(text);
1920
+ }
1921
+ var EXAMPLE_LEAD = /(?:^|[^\p{L}])(?:например|пример\p{L}*|образец|шаблон\p{L}*|выгляд\p{L}*|формат\p{L}*|такой|такая|такое|такие|так|example|e\.g\.|looks like|format)(?:[^\p{L}][^\n]*)?:\s*$/iu;
1922
+ var textOfMessage = (message) => message.parts.map((part) => part.type === "text" ? part.text : "").join("\n");
1923
+ function lastQuestionOf(history) {
1924
+ const question2 = history.findLast((message) => message.role === "user");
1925
+ return question2 ? textOfMessage(question2) : "";
1926
+ }
1927
+ function checkServiceBlockFormats(formats) {
1928
+ for (const format of formats) {
1929
+ const shown = JSON.stringify(format);
1930
+ if (!format || typeof format !== "object") throw new TypeError(`serviceBlocks: \u043D\u0443\u0436\u0435\u043D \u043E\u0431\u044A\u0435\u043A\u0442 \u0444\u043E\u0440\u043C\u0430\u0442\u0430, \u0430 \u043D\u0435 ${shown}`);
1931
+ if ("tag" in format) {
1932
+ if (typeof format.tag !== "string" || !/^[A-Za-z][\w:.-]*$/.test(format.tag)) throw new TypeError(`serviceBlocks: \u0438\u043C\u044F \u0442\u0435\u0433\u0430 \u2014 \u043B\u0430\u0442\u0438\u043D\u0438\u0446\u0430, \u0446\u0438\u0444\u0440\u044B, _ : . -, \u0430 \u043D\u0435 ${shown}`);
1933
+ continue;
1934
+ }
1935
+ if (typeof format.name !== "string" || format.name === "") throw new TypeError(`serviceBlocks: \u0443 \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u0438 \u043D\u0443\u0436\u043D\u043E \u0438\u043C\u044F \u0434\u043B\u044F \u0436\u0443\u0440\u043D\u0430\u043B\u0430: ${shown}`);
1936
+ if (typeof format.line !== "string" || format.line.trim() === "") throw new TypeError(`serviceBlocks: \u0443 \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u0438 \u043D\u0443\u0436\u043D\u043E \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0435 \u043D\u0430\u0447\u0430\u043B\u043E line: ${shown}`);
1937
+ if (format.match !== void 0 && !(format.match instanceof RegExp)) throw new TypeError(`serviceBlocks: match \u2014 \u0440\u0435\u0433\u0443\u043B\u044F\u0440\u043D\u043E\u0435 \u0432\u044B\u0440\u0430\u0436\u0435\u043D\u0438\u0435: ${format.name}`);
1938
+ if (format.until !== void 0 && format.until !== "paragraph" && !(format.until instanceof RegExp)) {
1939
+ throw new TypeError(`serviceBlocks: until \u2014 "paragraph" \u0438\u043B\u0438 \u0440\u0435\u0433\u0443\u043B\u044F\u0440\u043D\u043E\u0435 \u0432\u044B\u0440\u0430\u0436\u0435\u043D\u0438\u0435: ${format.name}`);
1940
+ }
1941
+ }
1942
+ }
1943
+ var compiledByList = /* @__PURE__ */ new WeakMap();
1944
+ function compile(formats) {
1945
+ const known = compiledByList.get(formats);
1946
+ if (known) return known;
1947
+ const tags = formats.flatMap((format) => "tag" in format ? [format.tag] : []);
1948
+ const lines = formats.filter((format) => !("tag" in format));
1949
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1950
+ const starts = [...tags.map((tag) => `<\\/?${escape(tag)}`), ...lines.map((format) => escape(format.line))];
1951
+ const hint = starts.length > 0 ? new RegExp(`(?:^|\\n) {0,3}(?:${starts.join("|")})`) : /(?!)/;
1952
+ const compiled = { tags, lines, hint };
1953
+ compiledByList.set(formats, compiled);
1954
+ return compiled;
1955
+ }
1956
+ var MAX_ATTRIBUTES_LENGTH = 200;
1957
+ function paragraphEnd(text) {
1958
+ const blank = /\n[ \t]*(?:\n|$)/g;
1959
+ blank.lastIndex = Math.max(0, text.indexOf("\n"));
1960
+ const found = text.indexOf("\n") < 0 ? null : blank.exec(text);
1961
+ return found ? found.index + 1 : -1;
1962
+ }
1963
+ function createForgedBlockScanner(formats, options2 = {}) {
1964
+ const compiled = compile(formats);
1965
+ const lines = compiled.lines;
1966
+ const tags = options2.explaining ? [] : compiled.tags;
1967
+ let fence = null;
1968
+ let previousLine = "";
1969
+ let pending = "";
1970
+ let ordinaryLine = null;
1971
+ let block = null;
1972
+ let skipSpace = false;
1973
+ let out = [];
1974
+ const emitText = (text) => {
1975
+ if (!text) return;
1976
+ const last = out.at(-1);
1977
+ if (last?.kind === "text") last.text += text;
1978
+ else out.push({ kind: "text", text });
1979
+ };
1980
+ const emitGap = (text) => {
1981
+ const last = out.at(-1);
1982
+ if (last?.kind === "gap") last.text += text;
1983
+ else out.push({ kind: "gap", text });
1984
+ };
1985
+ const closeOrdinaryLine = (line) => {
1986
+ if (line.trim() !== "") previousLine = line;
1987
+ if (fence !== null) {
1988
+ if (closesCodeFence(line, fence)) fence = null;
1989
+ } else {
1990
+ const opener = codeFenceOf(line);
1991
+ if (opener) fence = opener;
1992
+ }
1993
+ };
1994
+ function classify(line, complete) {
1995
+ if (fence !== null || isIndentedCodeOrQuote(line) || codeFenceOf(line)) return "text";
1996
+ const indent = /^ {0,3}/.exec(line)[0].length;
1997
+ const body = line.slice(indent);
1998
+ if (body === "") return complete ? "text" : "wait";
1999
+ let waiting = false;
2000
+ if (body[0] === "<") {
2001
+ const closing = body[1] === "/";
2002
+ const nameAt = closing ? 2 : 1;
2003
+ for (const tag of tags) {
2004
+ const head = body.slice(nameAt, nameAt + tag.length);
2005
+ if (!tag.startsWith(head)) continue;
2006
+ if (head.length < tag.length) {
2007
+ waiting ||= body.length === nameAt + head.length;
2008
+ continue;
2009
+ }
2010
+ if (closing) continue;
2011
+ const next = body[nameAt + tag.length];
2012
+ if (next === void 0) {
2013
+ waiting = true;
2014
+ continue;
2015
+ }
2016
+ if (next !== ">" && !/\s/.test(next)) continue;
2017
+ const end = body.indexOf(">", nameAt + tag.length);
2018
+ if (end >= 0 && end - (nameAt + tag.length) <= MAX_ATTRIBUTES_LENGTH && !body.slice(nameAt + tag.length, end).includes("\n")) {
2019
+ if (EXAMPLE_LEAD.test(previousLine.trimEnd())) continue;
2020
+ const close = `</${tag}>`;
2021
+ const rest = body.slice(end + 1);
2022
+ if (rest.includes(close)) return { kind: "tag", name: tag, close, from: indent + end + 1 };
2023
+ if (!complete) {
2024
+ waiting = true;
2025
+ continue;
2026
+ }
2027
+ if (rest.trim() === "" || /^\s*[[{"]/.test(rest)) return { kind: "tag", name: tag, close, from: indent + end + 1 };
2028
+ continue;
2029
+ }
2030
+ if (end < 0 && !complete && body.length - (nameAt + tag.length) <= MAX_ATTRIBUTES_LENGTH) waiting = true;
2031
+ }
2032
+ }
2033
+ for (const format of lines) {
2034
+ if (body.length < format.line.length) {
2035
+ waiting ||= format.line.startsWith(body);
2036
+ continue;
2037
+ }
2038
+ if (!body.startsWith(format.line)) continue;
2039
+ if (!complete) {
2040
+ waiting = true;
2041
+ continue;
2042
+ }
2043
+ if (!format.match || format.match.test(body.trimEnd())) return { kind: "line", format };
2044
+ }
2045
+ return waiting && !complete ? "wait" : "text";
2046
+ }
2047
+ function openBlock(opener, line, newline) {
2048
+ const whole = line + (newline ? "\n" : "");
2049
+ if (opener.kind === "tag") {
2050
+ block = { kind: "tag", name: opener.name, close: opener.close, text: whole, searchFrom: opener.from };
2051
+ return;
2052
+ }
2053
+ const { format } = opener;
2054
+ if (!format.until) {
2055
+ out.push({ kind: "block", name: format.name, text: whole });
2056
+ skipSpace = true;
2057
+ return;
2058
+ }
2059
+ block = format.until === "paragraph" ? { kind: "paragraph", name: format.name, text: whole } : { kind: "until", name: format.name, until: format.until, text: whole };
2060
+ }
2061
+ function feedBlock(current, delta) {
2062
+ current.text += delta;
2063
+ if (current.kind === "tag") {
2064
+ const at = current.text.indexOf(current.close, current.searchFrom);
2065
+ if (at < 0) {
2066
+ current.searchFrom = Math.max(current.searchFrom, current.text.length - current.close.length + 1);
2067
+ return null;
2068
+ }
2069
+ const end = at + current.close.length;
2070
+ return finishBlock(current, end);
2071
+ }
2072
+ let lineStart = current.text.indexOf("\n") + 1;
2073
+ while (lineStart > 0) {
2074
+ const lineEnd = current.text.indexOf("\n", lineStart);
2075
+ if (lineEnd < 0) return null;
2076
+ const line = current.text.slice(lineStart, lineEnd);
2077
+ if (current.kind === "paragraph" && line.trim() === "") return finishBlock(current, lineStart);
2078
+ if (current.kind === "until" && current.until.test(line.trim())) return finishBlock(current, lineEnd + 1);
2079
+ lineStart = lineEnd + 1;
2080
+ }
2081
+ return null;
2082
+ }
2083
+ function finishBlock(current, end) {
2084
+ out.push({ kind: "block", name: current.name, text: current.text.slice(0, end) });
2085
+ block = null;
2086
+ skipSpace = true;
2087
+ previousLine = "";
2088
+ return current.text.slice(end);
2089
+ }
2090
+ function feed(delta) {
2091
+ let rest = delta;
2092
+ while (rest) {
2093
+ if (block) {
2094
+ const left = feedBlock(block, rest);
2095
+ if (left === null) return;
2096
+ rest = left;
2097
+ continue;
2098
+ }
2099
+ if (skipSpace) {
2100
+ const space = /^\s*/.exec(rest)[0];
2101
+ if (space) emitGap(space);
2102
+ rest = rest.slice(space.length);
2103
+ if (!rest) return;
2104
+ skipSpace = false;
2105
+ pending = "";
2106
+ ordinaryLine = null;
2107
+ continue;
2108
+ }
2109
+ if (ordinaryLine !== null) {
2110
+ const newline2 = rest.indexOf("\n");
2111
+ if (newline2 < 0) {
2112
+ ordinaryLine += rest;
2113
+ emitText(rest);
2114
+ return;
2115
+ }
2116
+ closeOrdinaryLine(ordinaryLine + rest.slice(0, newline2));
2117
+ emitText(rest.slice(0, newline2 + 1));
2118
+ ordinaryLine = null;
2119
+ rest = rest.slice(newline2 + 1);
2120
+ continue;
2121
+ }
2122
+ const newline = rest.indexOf("\n");
2123
+ const line = pending + (newline < 0 ? rest : rest.slice(0, newline));
2124
+ const verdict = classify(line, newline >= 0);
2125
+ if (verdict === "wait") {
2126
+ pending = line;
2127
+ return;
2128
+ }
2129
+ pending = "";
2130
+ const after = newline < 0 ? "" : rest.slice(newline + 1);
2131
+ if (verdict === "text") {
2132
+ if (newline < 0) {
2133
+ ordinaryLine = line;
2134
+ emitText(line);
2135
+ return;
2136
+ }
2137
+ closeOrdinaryLine(line);
2138
+ emitText(`${line}
2139
+ `);
2140
+ rest = after;
2141
+ continue;
2142
+ }
2143
+ openBlock(verdict, line, newline >= 0);
2144
+ rest = after;
2145
+ }
2146
+ }
2147
+ const take = () => {
2148
+ const pieces = out;
2149
+ out = [];
2150
+ return pieces;
2151
+ };
2152
+ return {
2153
+ /** Следующий кусок текста → готовые куски. Начало возможного блока придерживается до следующего. */
2154
+ push(delta) {
2155
+ feed(delta);
2156
+ return take();
2157
+ },
2158
+ /**
2159
+ * Текст кончился: придержанное решается на неполной строке. Незакрытый тег — блок до первой пустой строки, остальное
2160
+ * разбирается дальше; строчный блок — до конца текста.
2161
+ */
2162
+ end() {
2163
+ for (; ; ) {
2164
+ const current = block;
2165
+ if (current) {
2166
+ block = null;
2167
+ const blank = current.kind === "tag" ? paragraphEnd(current.text) : -1;
2168
+ if (blank < 0) {
2169
+ out.push({ kind: "block", name: current.name, text: current.text });
2170
+ continue;
2171
+ }
2172
+ finishBlock(current, blank);
2173
+ feed(current.text.slice(blank));
2174
+ continue;
2175
+ }
2176
+ if (!pending) break;
2177
+ const line = pending;
2178
+ pending = "";
2179
+ const verdict = classify(line, true);
2180
+ if (verdict === "text" || verdict === "wait") emitText(line);
2181
+ else openBlock(verdict, line, false);
2182
+ }
2183
+ return take();
2184
+ }
2185
+ };
2186
+ }
2187
+ function cutForgedBlocks(text, formats, options2 = {}) {
2188
+ if (!compile(formats).hint.test(text)) return { text, blocks: [] };
2189
+ const scanner = createForgedBlockScanner(formats, options2);
2190
+ const pieces = [...scanner.push(text), ...scanner.end()];
2191
+ return {
2192
+ text: pieces.map((piece) => piece.kind === "text" ? piece.text : "").join(""),
2193
+ blocks: pieces.flatMap((piece) => piece.kind === "block" ? [piece.name] : [])
2194
+ };
2195
+ }
2196
+ var hasWords = (text) => /[\p{L}\p{N}]/u.test(text);
2197
+ function cutForgedBlocksInParts(parts, formats, options2 = {}) {
2198
+ const blocks = [];
2199
+ const cleaned = parts.flatMap((part) => {
2200
+ if (part.type !== "text") return [part];
2201
+ const cut = cutForgedBlocks(part.text, formats, options2);
2202
+ if (cut.blocks.length === 0) return [part];
2203
+ blocks.push(...cut.blocks);
2204
+ return cut.text.trim() === "" ? [] : [{ ...part, text: cut.text }];
2205
+ });
2206
+ if (blocks.length === 0) return null;
2207
+ if (!cleaned.some((part) => part.type === "text" && hasWords(part.text))) return null;
2208
+ return { parts: cleaned, blocks };
2209
+ }
2210
+ function withoutForgedBlocks(history, formats) {
2211
+ let changed = false;
2212
+ let question2 = "";
2213
+ const cleaned = history.map((message) => {
2214
+ if (message.role === "user") question2 = textOfMessage(message);
2215
+ if (message.role !== "assistant") return message;
2216
+ const cut = cutForgedBlocksInParts(message.parts, formats, { explaining: explainsServiceFormats(question2, formats) });
2217
+ if (!cut) return message;
2218
+ changed = true;
2219
+ return { ...message, parts: cut.parts };
2220
+ });
2221
+ return changed ? cleaned : history;
2222
+ }
2223
+ function createForgedBlockGuard(formats) {
2224
+ let worded = false;
2225
+ let explaining = false;
2226
+ const cut = [];
2227
+ const kept = [];
2228
+ const transformFor = () => {
2229
+ const scanners = /* @__PURE__ */ new Map();
2230
+ let held = null;
2231
+ const starts = /* @__PURE__ */ new Map();
2232
+ const started = /* @__PURE__ */ new Set();
2233
+ const enqueue = (part, controller) => {
2234
+ if (part.type === "text-start" && scanners.has(part.id)) {
2235
+ starts.set(part.id, part);
2236
+ return;
2237
+ }
2238
+ if (part.type === "text-delta" && !started.has(part.id) && starts.has(part.id)) {
2239
+ controller.enqueue(starts.get(part.id));
2240
+ started.add(part.id);
2241
+ }
2242
+ if (part.type === "text-end" && starts.has(part.id) && !started.has(part.id)) return;
2243
+ controller.enqueue(part);
2244
+ };
2245
+ const isBlock = (item) => "name" in item && "text" in item && !("type" in item);
2246
+ const settle = (keep, controller) => {
2247
+ const items = held ?? [];
2248
+ held = null;
2249
+ for (const item of items) {
2250
+ if (!isBlock(item)) enqueue(item, controller);
2251
+ else {
2252
+ if (keep) enqueue({ type: "text-delta", id: item.id, delta: item.text }, controller);
2253
+ if (item.name) (keep ? kept : cut).push(item.name);
2254
+ }
2255
+ }
2256
+ };
2257
+ const send = (part, controller) => {
2258
+ if (held) held.push(part);
2259
+ else enqueue(part, controller);
2260
+ };
2261
+ const deltaOf = (id, pieces, controller) => {
2262
+ let delta = "";
2263
+ for (const piece of pieces) {
2264
+ if (piece.kind === "text" && hasWords(piece.text)) {
2265
+ worded = true;
2266
+ if (held) settle(false, controller);
2267
+ }
2268
+ if (piece.kind === "text" && !held) delta += piece.text;
2269
+ else if (worded) {
2270
+ if (piece.kind === "block") cut.push(piece.name);
2271
+ } else {
2272
+ (held ??= []).push({ id, name: piece.kind === "block" ? piece.name : "", text: piece.text });
2273
+ }
2274
+ }
2275
+ return delta;
2276
+ };
2277
+ const transform = (part, controller) => {
2278
+ switch (part.type) {
2279
+ case "text-start":
2280
+ scanners.set(part.id, createForgedBlockScanner(formats, { explaining }));
2281
+ break;
2282
+ case "text-delta": {
2283
+ const scanner = scanners.get(part.id);
2284
+ if (!scanner) break;
2285
+ const delta = deltaOf(part.id, scanner.push(part.delta), controller);
2286
+ if (delta) send({ ...part, delta }, controller);
2287
+ return;
2288
+ }
2289
+ case "text-end": {
2290
+ const scanner = scanners.get(part.id);
2291
+ if (!scanner) break;
2292
+ scanners.delete(part.id);
2293
+ const delta = deltaOf(part.id, scanner.end(), controller);
2294
+ if (delta) send({ type: "text-delta", id: part.id, delta }, controller);
2295
+ break;
2296
+ }
2297
+ case "tool-input-start":
2298
+ case "tool-call":
2299
+ if (held) settle(false, controller);
2300
+ break;
2301
+ case "finish":
2302
+ if (held) settle(part.finishReason.unified !== "tool-calls", controller);
2303
+ break;
2304
+ }
2305
+ send(part, controller);
2306
+ };
2307
+ const flush = (controller) => {
2308
+ if (held) settle(true, controller);
2309
+ };
2310
+ return new TransformStream({ transform, flush });
2311
+ };
2312
+ return {
2313
+ /**
2314
+ * Новая попытка с последним вопросом человека: шаги прежней стёрты — слов у ответа снова нет. Вырезанное раньше
2315
+ * остаётся в счёте для журнала.
2316
+ */
2317
+ reset(question2 = "") {
2318
+ worded = false;
2319
+ explaining = explainsServiceFormats(question2, formats);
2320
+ },
2321
+ /** Законченный текст мимо потока (переписка проверкой) — по тем же правилам, что поток этой попытки. */
2322
+ cutText: (text) => cutForgedBlocks(text, formats, { explaining }),
2323
+ /** Модель попытки под присмотром: каждый запрос — новый шаг. */
2324
+ wrap(model) {
2325
+ if (typeof model === "string" || formats.length === 0) return model;
2326
+ return wrapLanguageModel2({
2327
+ model,
2328
+ middleware: {
2329
+ specificationVersion: "v4",
2330
+ wrapStream: async ({ doStream }) => {
2331
+ const result = await doStream();
2332
+ return { ...result, stream: result.stream.pipeThrough(transformFor()) };
2333
+ }
2334
+ }
2335
+ });
2336
+ },
2337
+ /** Вырезанные блоки хода по порядку (имена) — для журнала. */
2338
+ cutBlocks: () => cut,
2339
+ /** Блоки, оставленные ради непустого ответа. */
2340
+ keptBlocks: () => kept
2341
+ };
2342
+ }
2343
+
1856
2344
  // src/storage/chat-storage.ts
1857
2345
  var RELEASED_ANSWER_ALIVE_AT = (/* @__PURE__ */ new Date(0)).toISOString();
1858
2346
  function aliveAtByLocalClock(mark) {
@@ -2918,7 +3406,7 @@ function usageMinusJournaled(usage, journaled) {
2918
3406
  }
2919
3407
 
2920
3408
  // src/agent-run/build-model-request.ts
2921
- import { convertToModelMessages, wrapLanguageModel as wrapLanguageModel4 } from "ai";
3409
+ import { convertToModelMessages, wrapLanguageModel as wrapLanguageModel5 } from "ai";
2922
3410
 
2923
3411
  // src/conversation/hanging-tool-calls.ts
2924
3412
  var PRELIMINARY_OUTPUT_MAX_LENGTH = 2e3;
@@ -3138,7 +3626,7 @@ ${chunk.delta}` } : chunk];
3138
3626
  }
3139
3627
 
3140
3628
  // src/agent-run/observe-model-calls.ts
3141
- import { wrapLanguageModel as wrapLanguageModel2 } from "ai";
3629
+ import { wrapLanguageModel as wrapLanguageModel3 } from "ai";
3142
3630
  function observeModelCalls(model, context) {
3143
3631
  if (typeof model === "string") return model;
3144
3632
  const now = context.now ?? Date.now;
@@ -3148,7 +3636,7 @@ function observeModelCalls(model, context) {
3148
3636
  attempts.set(params.prompt, attempt);
3149
3637
  return attempt;
3150
3638
  };
3151
- return wrapLanguageModel2({
3639
+ return wrapLanguageModel3({
3152
3640
  model,
3153
3641
  middleware: {
3154
3642
  specificationVersion: "v4",
@@ -3579,21 +4067,6 @@ function estimateMessagesTokens(messages) {
3579
4067
  }, 0);
3580
4068
  }
3581
4069
 
3582
- // src/agent-run/length-cut.ts
3583
- var LENGTH_CUT_PART_TYPE = "data-length-cut";
3584
- var LENGTH_CUT_NOTE = "[\u041E\u0442\u0432\u0435\u0442 \u043E\u0431\u043E\u0440\u0432\u0430\u043D: \u043C\u043E\u0434\u0435\u043B\u044C \u0443\u043F\u0451\u0440\u043B\u0430\u0441\u044C \u0432 \u043F\u0440\u0435\u0434\u0435\u043B \u0434\u043B\u0438\u043D\u044B \u043E\u0442\u0432\u0435\u0442\u0430]";
3585
- function lengthCutForModel(part) {
3586
- return part.type === LENGTH_CUT_PART_TYPE ? { type: "text", text: LENGTH_CUT_NOTE } : void 0;
3587
- }
3588
-
3589
- // src/agent-run/unanswered-messages.ts
3590
- var UNANSWERED_NOTE = "[\u041E\u0442\u0432\u0435\u0442\u0430 \u043D\u0430 \u044D\u0442\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0435\u0442: \u0445\u043E\u0434 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0434\u043E \u043F\u0435\u0440\u0432\u044B\u0445 \u0441\u043B\u043E\u0432]";
3591
- function withUnansweredMarked(messages) {
3592
- return messages.flatMap(
3593
- (message, index) => message.role === "user" && messages[index - 1]?.role === "user" ? [{ role: "assistant", content: [{ type: "text", text: UNANSWERED_NOTE }] }, message] : [message]
3594
- );
3595
- }
3596
-
3597
4070
  // src/agent-run/staff-replies.ts
3598
4071
  function isStaffReply(message) {
3599
4072
  if (message.role !== "user") return false;
@@ -3751,7 +4224,7 @@ function withoutUndelivered(history) {
3751
4224
  }
3752
4225
 
3753
4226
  // src/agent-run/garbled-tail.ts
3754
- import { wrapLanguageModel as wrapLanguageModel3 } from "ai";
4227
+ import { wrapLanguageModel as wrapLanguageModel4 } from "ai";
3755
4228
  var SIGN_ORDER = ["think", "role", "restart", "tool"];
3756
4229
  var TOKEN_SOURCE = String.raw`<\/?think>|<\|[^|\n<>]{1,40}\|>|<|[^|\n<>]{1,40}|>`;
3757
4230
  var TEMPLATE_TOKEN = new RegExp(TOKEN_SOURCE, "g");
@@ -3923,7 +4396,7 @@ function createGarbledTailGuard() {
3923
4396
  /** Модель попытки под присмотром: каждый запрос — новый шаг. */
3924
4397
  wrap(model) {
3925
4398
  if (typeof model === "string") return model;
3926
- return wrapLanguageModel3({
4399
+ return wrapLanguageModel4({
3927
4400
  model,
3928
4401
  middleware: {
3929
4402
  specificationVersion: "v4",
@@ -4028,16 +4501,16 @@ function visibleBefore(earlier, visible, offset) {
4028
4501
  function withoutCodeAndQuotes(text) {
4029
4502
  let fence = null;
4030
4503
  return text.split("\n").map((line) => {
4031
- const opener = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
4032
4504
  if (fence !== null) {
4033
- if (opener && opener[0] === fence[0] && opener.length >= fence.length && line.trim() === opener) fence = null;
4505
+ if (closesCodeFence(line, fence)) fence = null;
4034
4506
  return " ".repeat(line.length);
4035
4507
  }
4508
+ const opener = codeFenceOf(line);
4036
4509
  if (opener) {
4037
4510
  fence = opener;
4038
4511
  return " ".repeat(line.length);
4039
4512
  }
4040
- if (/^(?: {4}|\t)/.test(line) || /^\s*>/.test(line)) return " ".repeat(line.length);
4513
+ if (isIndentedCodeOrQuote(line)) return " ".repeat(line.length);
4041
4514
  return line.replace(/(`+)[^`\n]*?\1/g, (code) => " ".repeat(code.length));
4042
4515
  }).join("\n");
4043
4516
  }
@@ -4065,7 +4538,8 @@ async function buildModelRequest(input) {
4065
4538
  ...input.repeatSilence && { repeatSilence: input.repeatSilence }
4066
4539
  });
4067
4540
  const tools = input.tools ? sortToolsByName(input.tools) : void 0;
4068
- const sendable = input.switchableTools ? withSwitchedOffCallsAsText(sendableHistory(input.history), input.switchableTools, tools) : sendableHistory(input.history);
4541
+ const cleaned = sendableHistory(input.serviceBlocks ? withoutForgedBlocks(input.history, input.serviceBlocks) : input.history);
4542
+ const sendable = input.switchableTools ? withSwitchedOffCallsAsText(cleaned, input.switchableTools, tools) : cleaned;
4069
4543
  const noted = withStaffRepliesJoined(withChannelNotes(withModeNotes(withBurstsJoined(withCorrectionsJoined(sendable)), input.modeNotes)));
4070
4544
  const held = input.frozenContext ? holdNoteAfterApprovals(withFrozenContext(noted, input.frozenContext)) : { messages: noted };
4071
4545
  const history = selectReasoningForModel(held.messages, {
@@ -4150,7 +4624,7 @@ function withoutLoneSurrogatesDeep(value) {
4150
4624
  }
4151
4625
  function withRequestWatch(model, provider, watch) {
4152
4626
  if (!watch || typeof model === "string") return model;
4153
- return wrapLanguageModel4({
4627
+ return wrapLanguageModel5({
4154
4628
  model,
4155
4629
  middleware: {
4156
4630
  specificationVersion: "v4",
@@ -4170,7 +4644,7 @@ function withRequestWatch(model, provider, watch) {
4170
4644
  });
4171
4645
  }
4172
4646
  function withTransientRetries(model, context) {
4173
- return typeof model === "string" ? model : wrapLanguageModel4({ model, middleware: createTransientRetryMiddleware(context) });
4647
+ return typeof model === "string" ? model : wrapLanguageModel5({ model, middleware: createTransientRetryMiddleware(context) });
4174
4648
  }
4175
4649
  function withLastMessageMark(messages, options2) {
4176
4650
  if (!options2 || messages.length === 0) return messages;
@@ -5970,6 +6444,7 @@ async function runTurn(turnInput, emitRaw) {
5970
6444
  );
5971
6445
  const turnUsage = createTurnUsage();
5972
6446
  const garbledTail = createGarbledTailGuard();
6447
+ const forgedBlocks = createForgedBlockGuard(turnInput.serviceBlocks ?? CORE_SERVICE_BLOCKS);
5973
6448
  const turnBudget = createTurnBudget(input.turnRules, { catalog: input.catalog, modelId: input.modelId, turnUsage });
5974
6449
  const messageUsage = () => {
5975
6450
  const spent = turnUsage.total();
@@ -6146,6 +6621,7 @@ async function runTurn(turnInput, emitRaw) {
6146
6621
  turnUsage,
6147
6622
  turnBudget,
6148
6623
  garbledTail,
6624
+ forgedBlocks,
6149
6625
  // Есть куда перейти при молчании — у того же провайдера оно не повторяется.
6150
6626
  repeatSilence: () => silenceTarget() === void 0,
6151
6627
  ...silenceSwitch.scope === "step" && switchedBySilence && {
@@ -6365,6 +6841,8 @@ async function runTurn(turnInput, emitRaw) {
6365
6841
  const garbled = cutGarbledTail(garbledTail.forCut(parts));
6366
6842
  const droppedCalls = garbledTail.droppedCalls();
6367
6843
  const garbleLog = `\u0447\u0430\u0442=${input.chatId} \u043E\u0442\u0432\u0435\u0442=${messageId} \u043C\u043E\u0434\u0435\u043B\u044C=${modelId} \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440=${answeredProviderId}`;
6844
+ for (const name of new Set(forgedBlocks.cutBlocks())) logError(`[\u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0439 \u0431\u043B\u043E\u043A \u0432\u044B\u0440\u0435\u0437\u0430\u043D] \u0442\u0435\u0433=${name} ${garbleLog}`);
6845
+ for (const name of new Set(forgedBlocks.keptBlocks())) logError(`[\u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0439 \u0431\u043B\u043E\u043A \u043D\u0435 \u0432\u044B\u0440\u0435\u0437\u0430\u043D: \u043E\u0442\u0432\u0435\u0442 \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u0431\u044B \u043F\u0443\u0441\u0442\u044B\u043C] \u0442\u0435\u0433=${name} ${garbleLog}`);
6368
6846
  if (garbled) {
6369
6847
  logError(`[\u0445\u0432\u043E\u0441\u0442 \u043E\u0431\u0440\u0435\u0437\u0430\u043D] ${garbleLog} \u043F\u0440\u0438\u0437\u043D\u0430\u043A\u0438=${garbled.signs.join(",")} \u0437\u043D\u0430\u043A\u043E\u0432=${garbled.removedChars} \u0432\u044B\u0437\u043E\u0432\u043E\u0432_\u043D\u0435_\u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E=${droppedCalls}`);
6370
6848
  const cut = garbled.parts.filter((part) => !garbledTail.isDroppedCall(part));
@@ -6376,7 +6854,7 @@ async function runTurn(turnInput, emitRaw) {
6376
6854
  const textIndex = lastTextIndex(parts);
6377
6855
  if (replyChecks && status === "complete" && textIndex !== -1 && !takeover.signal.aborted) {
6378
6856
  const original = parts[textIndex].text;
6379
- const text = await checkedReplyText({
6857
+ const checked2 = await checkedReplyText({
6380
6858
  checks: replyChecks.checks,
6381
6859
  text: original,
6382
6860
  ownerId: input.ownerId ?? "",
@@ -6386,6 +6864,7 @@ async function runTurn(turnInput, emitRaw) {
6386
6864
  rewrite: createReplyRewrite({ ...input, providerId: answeredProviderId, messageId, logError }),
6387
6865
  logError
6388
6866
  });
6867
+ const text = checked2 === original ? checked2 : withoutForgedBlocksInRewrite(forgedBlocks.cutText(checked2), checked2, (name) => logError(`[\u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0439 \u0431\u043B\u043E\u043A \u0432\u044B\u0440\u0435\u0437\u0430\u043D] \u0442\u0435\u0433=${name} ${garbleLog}`));
6389
6868
  if (text !== original) {
6390
6869
  const corrected = withTextAt(parts, textIndex, text);
6391
6870
  if (heldText?.holdsText()) for (const chunk of heldText.release(text)) emitShown(chunk);
@@ -6643,6 +7122,7 @@ async function openAttempt(input, attempt, attemptSignal) {
6643
7122
  } = input;
6644
7123
  const request = await buildModelRequest({
6645
7124
  ...requestInput,
7125
+ serviceBlocks: input.serviceBlocks ?? CORE_SERVICE_BLOCKS,
6646
7126
  providerId,
6647
7127
  history: attempt.history,
6648
7128
  frozenContext: { current: { answerId: input.messageId, data: attempt.frozenContext }, group: Boolean(input.group) },
@@ -6685,9 +7165,11 @@ async function openAttempt(input, attempt, attemptSignal) {
6685
7165
  };
6686
7166
  const { noteAfterApprovals, ...modelRequest } = request;
6687
7167
  attempt.garbledTail.reset();
7168
+ attempt.forgedBlocks.reset(lastQuestionOf(attempt.history));
6688
7169
  const result = streamText({
6689
7170
  ...modelRequest,
6690
- model: attempt.garbledTail.wrap(modelRequest.model),
7171
+ // Сначала вырезка блоков, потом проверка на кашу: каша судит уже чистый текст.
7172
+ model: attempt.garbledTail.wrap(attempt.forgedBlocks.wrap(modelRequest.model)),
6691
7173
  ...approval.toolApproval && { toolApproval: gateToolApproval(approval.toolApproval, () => toolsClosed) },
6692
7174
  // Попытке — остаток шагов хода.
6693
7175
  // Помощник встал на подтверждении человека — ход ждёт, модель основного агента его незаконченным не видит.
@@ -7110,6 +7592,11 @@ function emitReply(text, emit) {
7110
7592
  emit({ type: "text-end", id });
7111
7593
  emit({ type: "finish-step" });
7112
7594
  }
7595
+ function withoutForgedBlocksInRewrite(cut, text, logCut) {
7596
+ if (cut.blocks.length === 0 || !/[\p{L}\p{N}]/u.test(cut.text)) return text;
7597
+ for (const name of new Set(cut.blocks)) logCut(name);
7598
+ return cut.text;
7599
+ }
7113
7600
 
7114
7601
  // src/helpers/run-helper.ts
7115
7602
  var DEFAULT_HELPER_MAX_STEPS = 10;
@@ -7797,7 +8284,7 @@ var fireworksProvider = createFireworksProvider();
7797
8284
 
7798
8285
  // src/models/providers/openrouter.ts
7799
8286
  import { createOpenRouter } from "@openrouter/ai-sdk-provider";
7800
- import { wrapLanguageModel as wrapLanguageModel5 } from "ai";
8287
+ import { wrapLanguageModel as wrapLanguageModel6 } from "ai";
7801
8288
 
7802
8289
  // src/models/providers/openrouter-cache-mark-limit.ts
7803
8290
  function openrouterCacheMarkLimit(providerModelId) {
@@ -8047,7 +8534,7 @@ function createOpenRouterProvider(options2 = {}) {
8047
8534
  createLanguageModel: (providerModelId, apiKey, requestOptions) => {
8048
8535
  const markLimit = openrouterCacheMarkLimit(providerModelId);
8049
8536
  const fetch = markLimit === void 0 ? requestOptions?.fetch : fetchWithCacheMarkLimit(requestOptions?.fetch, markLimit);
8050
- return wrapLanguageModel5({
8537
+ return wrapLanguageModel6({
8051
8538
  model: createOpenRouter({ apiKey, fetch, ...connection }).chat(providerModelId, routingOptions),
8052
8539
  middleware: [
8053
8540
  openrouterRoutingMiddleware(routing),
@@ -9949,7 +10436,7 @@ function isPlainObject3(value) {
9949
10436
  }
9950
10437
 
9951
10438
  // src/memory/memory-after-answer.ts
9952
- import { wrapLanguageModel as wrapLanguageModel6 } from "ai";
10439
+ import { wrapLanguageModel as wrapLanguageModel7 } from "ai";
9953
10440
  var DRAIN_WAIT_MS = 5e3;
9954
10441
  function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turnObserver }) {
9955
10442
  const running = /* @__PURE__ */ new Set();
@@ -9994,7 +10481,7 @@ function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turn
9994
10481
  };
9995
10482
  }
9996
10483
  function withRetries(model, context) {
9997
- return typeof model === "string" ? model : wrapLanguageModel6({ model, middleware: createTransientRetryMiddleware(context) });
10484
+ return typeof model === "string" ? model : wrapLanguageModel7({ model, middleware: createTransientRetryMiddleware(context) });
9998
10485
  }
9999
10486
 
10000
10487
  // src/conversation/to-agent-chat-message.ts
@@ -10249,6 +10736,7 @@ function createCompactionService(options2) {
10249
10736
  ...observation(ownerId, answerId),
10250
10737
  history,
10251
10738
  ...options2.attachments && { attachments: options2.attachments },
10739
+ ...options2.serviceBlocks && { serviceBlocks: options2.serviceBlocks },
10252
10740
  settings,
10253
10741
  logError,
10254
10742
  abortSignal: signal,
@@ -10392,6 +10880,7 @@ function createCompactionService(options2) {
10392
10880
  ...observation(ownerId, answerId),
10393
10881
  history: [...history, { id: `${chatId}-answer-steps`, role: "assistant", parts: steps }],
10394
10882
  ...options2.attachments && { attachments: options2.attachments },
10883
+ ...options2.serviceBlocks && { serviceBlocks: options2.serviceBlocks },
10395
10884
  settings,
10396
10885
  task: answerStepsTask(settings.summaryCeilingTokens),
10397
10886
  logError,
@@ -11252,6 +11741,13 @@ function createChatService(input) {
11252
11741
  const logError = input.logError ?? ((message) => console.error(message));
11253
11742
  const now = input.now ?? (() => /* @__PURE__ */ new Date());
11254
11743
  const replyChecks = checkedReplyChecks(input.replyChecks);
11744
+ const serviceBlocks = [
11745
+ ...CORE_SERVICE_BLOCKS,
11746
+ ...input.memory?.serviceBlocks ?? [],
11747
+ ...input.attachments?.serviceBlocks ?? [],
11748
+ ...input.agent.serviceBlocks ?? []
11749
+ ];
11750
+ checkServiceBlockFormats(serviceBlocks);
11255
11751
  const widgets = createAgentWidgets(input.agent.widgets);
11256
11752
  checkWidgetNamesFree(widgets, input.memory, input.background);
11257
11753
  const browserToolDescriptions = checkedBrowserTools(input.agent, input.memory, widgets);
@@ -11400,6 +11896,7 @@ function createChatService(input) {
11400
11896
  // Сжатие берёт кэш хода: у него те же инструменты, что у модели агента, — с браузерными и помощниками.
11401
11897
  agent: { ...options2.agent, tools: withBrowserDescriptions(options2.agent.tools, describedOnlyTools(browserToolDescriptions, options2.agent.helpers, toolResultLimit)) },
11402
11898
  ...options2.attachments && { attachments: options2.attachments },
11899
+ serviceBlocks,
11403
11900
  settings: resolveCompactionSettings(options2.compaction),
11404
11901
  logError,
11405
11902
  ...options2.turnObserver && { turnObserver: options2.turnObserver },
@@ -12063,6 +12560,7 @@ function createChatService(input) {
12063
12560
  browserTools: { descriptions: browserToolDescriptions, pending: pendingBrowserCalls, timeoutMs: browserToolTimeoutMs, tabId: launch.page?.tabId }
12064
12561
  },
12065
12562
  ...options2.attachments && { attachments: options2.attachments },
12563
+ serviceBlocks,
12066
12564
  ...options2.memory && { memory: options2.memory },
12067
12565
  ...widgets.waiting.size > 0 && { waitingWidgets: widgets.waiting },
12068
12566
  ...widgets.switchable.size > 0 && { switchableTools: widgets.switchable },
@@ -13515,6 +14013,10 @@ export {
13515
14013
  BURST_HOLD_MS,
13516
14014
  BURST_SWEEP_LIMIT,
13517
14015
  isBurstMessage,
14016
+ CORE_SERVICE_BLOCKS,
14017
+ serviceBlockName,
14018
+ checkServiceBlockFormats,
14019
+ cutForgedBlocks,
13518
14020
  RELEASED_ANSWER_ALIVE_AT,
13519
14021
  aliveAtByLocalClock,
13520
14022
  silenceBoundaryByLocalClock,
@@ -13614,4 +14116,4 @@ export {
13614
14116
  InvalidWidgetAnswerError,
13615
14117
  createChatService
13616
14118
  };
13617
- //# sourceMappingURL=chunk-KAG3QTZC.js.map
14119
+ //# sourceMappingURL=chunk-QA5DVEXF.js.map