@ykurilov/agent-chat-core 0.57.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) {
@@ -2484,10 +2972,9 @@ var NOT_RETRYABLE_SIGNS = signs([
2484
2972
  "available balance",
2485
2973
  "insufficient_quota",
2486
2974
  "out of budget",
2487
- "quota exceeded",
2488
- "billing",
2489
2975
  "subscription_sharing_usage_limit_exceeded"
2490
2976
  ]);
2977
+ var QUOTA_WORDS = signs(["quota exceeded", "billing"]);
2491
2978
  var RETRYABLE_SIGNS = signs([
2492
2979
  "overloaded",
2493
2980
  "currently experiencing high demand",
@@ -2534,8 +3021,8 @@ function isContextOverflow(text, statusCode) {
2534
3021
  if (NOT_OVERFLOW_SIGNS.some((sign) => sign.test(text))) return false;
2535
3022
  return OVERFLOW_SIGNS.some((sign) => sign.test(text));
2536
3023
  }
2537
- function isNotRetryable(text) {
2538
- return NOT_RETRYABLE_SIGNS.test(text);
3024
+ function isNotRetryable(text, statusCode) {
3025
+ return NOT_RETRYABLE_SIGNS.test(text) || statusCode !== 429 && QUOTA_WORDS.test(text);
2539
3026
  }
2540
3027
  function looksTransient(text) {
2541
3028
  return RETRYABLE_SIGNS.test(text);
@@ -2573,7 +3060,7 @@ function isTransient(error) {
2573
3060
  if (verdict === "false") return false;
2574
3061
  const status = errorStatusCode(error);
2575
3062
  const text = errorText(error);
2576
- if (isNotRetryable(text) || isContextOverflow(text, status)) return false;
3063
+ if (isNotRetryable(text, status) || isContextOverflow(text, status)) return false;
2577
3064
  if (status !== void 0) return RETRYABLE_STATUS_CODES.has(status) || status >= 500;
2578
3065
  if (APICallError2.isInstance(error) && error.isRetryable) return true;
2579
3066
  return looksTransient(text);
@@ -2747,6 +3234,9 @@ function sleep(ms, signal) {
2747
3234
  }
2748
3235
 
2749
3236
  // src/provider-errors/classify-provider-error.ts
3237
+ var KEY_SIGNS = /api[ _-]?key|authenticat|unauthori[sz]ed|credential|access[ _-]?token|key (?:is )?(?:invalid|revoked|expired|disabled)/i;
3238
+ var FUNDS_SIGNS = /key limit exceeded|insufficient (?:credits|funds|balance)/i;
3239
+ var MODERATION_SIGNS = /flagged|moderation|content[ _-]?policy|safety/i;
2750
3240
  var RATE_LIMIT_SIGNS = /rate.?limit|too many requests|throttl|please wait|ResourceExhausted/i;
2751
3241
  function classifyProviderError(error, provider) {
2752
3242
  if (error instanceof MissingProviderKeyError) return "invalid_key";
@@ -2756,8 +3246,12 @@ function classifyProviderError(error, provider) {
2756
3246
  for (const hint of provider?.errorHints ?? []) {
2757
3247
  if (hint.match.test(text)) return hint.kind;
2758
3248
  }
2759
- if (status === 401 || status === 403) return "invalid_key";
2760
- if (status === 402 || isNotRetryable(text)) return "insufficient_funds";
3249
+ if (status === 401) return "invalid_key";
3250
+ if (status === 403) {
3251
+ if (KEY_SIGNS.test(text)) return "invalid_key";
3252
+ return FUNDS_SIGNS.test(text) || isNotRetryable(text) ? "insufficient_funds" : "other";
3253
+ }
3254
+ if (status === 402 || isNotRetryable(text, status)) return "insufficient_funds";
2761
3255
  if (isContextOverflow(text, status)) return "context_overflow";
2762
3256
  if (status === 429) return "rate_limited";
2763
3257
  if (isProviderSilence(cause)) return "overloaded";
@@ -2765,6 +3259,10 @@ function classifyProviderError(error, provider) {
2765
3259
  if (RATE_LIMIT_SIGNS.test(text)) return "rate_limited";
2766
3260
  return looksTransient(text) ? "overloaded" : "other";
2767
3261
  }
3262
+ function isAccessRefusal(error) {
3263
+ const cause = underlyingError(error);
3264
+ return errorStatusCode(cause) === 403 && !MODERATION_SIGNS.test(errorText(cause));
3265
+ }
2768
3266
 
2769
3267
  // src/provider-errors/provider-failure-log.ts
2770
3268
  import { APICallError as APICallError3 } from "ai";
@@ -2790,6 +3288,49 @@ function oneLine(text) {
2790
3288
  return text.replace(/\s*[\r\n]+\s*/g, " ");
2791
3289
  }
2792
3290
 
3291
+ // src/provider-errors/provider-outages.ts
3292
+ function isProviderOutage(kind) {
3293
+ return kind === "insufficient_funds" || kind === "invalid_key";
3294
+ }
3295
+ var PROVIDER_OUTAGE_PAUSE_MS = 5 * 6e4;
3296
+ var PROVIDER_OUTAGE_ALERT_EVERY_MS = 15 * 6e4;
3297
+ var PROVIDER_OUTAGE_PROBE_MS = 6e4;
3298
+ var SAID = {
3299
+ insufficient_funds: "\u043A\u043E\u043D\u0447\u0438\u043B\u0438\u0441\u044C \u0434\u0435\u043D\u044C\u0433\u0438",
3300
+ invalid_key: "\u043A\u043B\u044E\u0447 \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u0442"
3301
+ };
3302
+ function createProviderOutages(options2) {
3303
+ const outages = /* @__PURE__ */ new Map();
3304
+ const alertedAt = /* @__PURE__ */ new Map();
3305
+ const pausedKind = (providerId) => {
3306
+ const outage = outages.get(providerId);
3307
+ if (!outage) return void 0;
3308
+ const time = options2.now();
3309
+ return time < outage.pausedUntil || time < (outage.probeUntil ?? 0) ? outage.kind : void 0;
3310
+ };
3311
+ return {
3312
+ report({ providerId, kind, fallbackProviderId, fallbacksPaused }) {
3313
+ const time = options2.now();
3314
+ outages.set(providerId, { kind, pausedUntil: time + PROVIDER_OUTAGE_PAUSE_MS });
3315
+ const last = alertedAt.get(providerId);
3316
+ if (last !== void 0 && time - last < PROVIDER_OUTAGE_ALERT_EVERY_MS) return;
3317
+ alertedAt.set(providerId, time);
3318
+ const route = fallbackProviderId !== void 0 ? `\u0445\u043E\u0434\u044B \u0438\u0434\u0443\u0442 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0439 ${fallbackProviderId}` : `${fallbacksPaused ? "\u0437\u0430\u043F\u0430\u0441\u043D\u044B\u0435 \u0442\u043E\u0436\u0435 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435" : "\u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0433\u043E \u043D\u0435\u0442"}, \u0445\u043E\u0434\u044B \u043F\u0430\u0434\u0430\u044E\u0442 \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439`;
3319
+ options2.logError(`[\u0412\u041D\u0418\u041C\u0410\u041D\u0418\u0415 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440] ${providerId}: ${SAID[kind]} \u2014 ${route}`);
3320
+ },
3321
+ pausedKind,
3322
+ admit(providerId) {
3323
+ const kind = pausedKind(providerId);
3324
+ const outage = outages.get(providerId);
3325
+ if (kind === void 0 && outage) outage.probeUntil = options2.now() + PROVIDER_OUTAGE_PROBE_MS;
3326
+ return kind;
3327
+ },
3328
+ recovered(providerId) {
3329
+ outages.delete(providerId);
3330
+ }
3331
+ };
3332
+ }
3333
+
2793
3334
  // src/tool-approval/tool-approval-policy.ts
2794
3335
  var needsApprovalOf = (tool4) => tool4.needsApproval;
2795
3336
  function prepareToolApproval(tools, alwaysAllowed) {
@@ -2865,7 +3406,7 @@ function usageMinusJournaled(usage, journaled) {
2865
3406
  }
2866
3407
 
2867
3408
  // src/agent-run/build-model-request.ts
2868
- import { convertToModelMessages, wrapLanguageModel as wrapLanguageModel4 } from "ai";
3409
+ import { convertToModelMessages, wrapLanguageModel as wrapLanguageModel5 } from "ai";
2869
3410
 
2870
3411
  // src/conversation/hanging-tool-calls.ts
2871
3412
  var PRELIMINARY_OUTPUT_MAX_LENGTH = 2e3;
@@ -3085,7 +3626,7 @@ ${chunk.delta}` } : chunk];
3085
3626
  }
3086
3627
 
3087
3628
  // src/agent-run/observe-model-calls.ts
3088
- import { wrapLanguageModel as wrapLanguageModel2 } from "ai";
3629
+ import { wrapLanguageModel as wrapLanguageModel3 } from "ai";
3089
3630
  function observeModelCalls(model, context) {
3090
3631
  if (typeof model === "string") return model;
3091
3632
  const now = context.now ?? Date.now;
@@ -3095,7 +3636,7 @@ function observeModelCalls(model, context) {
3095
3636
  attempts.set(params.prompt, attempt);
3096
3637
  return attempt;
3097
3638
  };
3098
- return wrapLanguageModel2({
3639
+ return wrapLanguageModel3({
3099
3640
  model,
3100
3641
  middleware: {
3101
3642
  specificationVersion: "v4",
@@ -3526,21 +4067,6 @@ function estimateMessagesTokens(messages) {
3526
4067
  }, 0);
3527
4068
  }
3528
4069
 
3529
- // src/agent-run/length-cut.ts
3530
- var LENGTH_CUT_PART_TYPE = "data-length-cut";
3531
- 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]";
3532
- function lengthCutForModel(part) {
3533
- return part.type === LENGTH_CUT_PART_TYPE ? { type: "text", text: LENGTH_CUT_NOTE } : void 0;
3534
- }
3535
-
3536
- // src/agent-run/unanswered-messages.ts
3537
- 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]";
3538
- function withUnansweredMarked(messages) {
3539
- return messages.flatMap(
3540
- (message, index) => message.role === "user" && messages[index - 1]?.role === "user" ? [{ role: "assistant", content: [{ type: "text", text: UNANSWERED_NOTE }] }, message] : [message]
3541
- );
3542
- }
3543
-
3544
4070
  // src/agent-run/staff-replies.ts
3545
4071
  function isStaffReply(message) {
3546
4072
  if (message.role !== "user") return false;
@@ -3698,7 +4224,7 @@ function withoutUndelivered(history) {
3698
4224
  }
3699
4225
 
3700
4226
  // src/agent-run/garbled-tail.ts
3701
- import { wrapLanguageModel as wrapLanguageModel3 } from "ai";
4227
+ import { wrapLanguageModel as wrapLanguageModel4 } from "ai";
3702
4228
  var SIGN_ORDER = ["think", "role", "restart", "tool"];
3703
4229
  var TOKEN_SOURCE = String.raw`<\/?think>|<\|[^|\n<>]{1,40}\|>|<|[^|\n<>]{1,40}|>`;
3704
4230
  var TEMPLATE_TOKEN = new RegExp(TOKEN_SOURCE, "g");
@@ -3870,7 +4396,7 @@ function createGarbledTailGuard() {
3870
4396
  /** Модель попытки под присмотром: каждый запрос — новый шаг. */
3871
4397
  wrap(model) {
3872
4398
  if (typeof model === "string") return model;
3873
- return wrapLanguageModel3({
4399
+ return wrapLanguageModel4({
3874
4400
  model,
3875
4401
  middleware: {
3876
4402
  specificationVersion: "v4",
@@ -3975,16 +4501,16 @@ function visibleBefore(earlier, visible, offset) {
3975
4501
  function withoutCodeAndQuotes(text) {
3976
4502
  let fence = null;
3977
4503
  return text.split("\n").map((line) => {
3978
- const opener = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
3979
4504
  if (fence !== null) {
3980
- if (opener && opener[0] === fence[0] && opener.length >= fence.length && line.trim() === opener) fence = null;
4505
+ if (closesCodeFence(line, fence)) fence = null;
3981
4506
  return " ".repeat(line.length);
3982
4507
  }
4508
+ const opener = codeFenceOf(line);
3983
4509
  if (opener) {
3984
4510
  fence = opener;
3985
4511
  return " ".repeat(line.length);
3986
4512
  }
3987
- if (/^(?: {4}|\t)/.test(line) || /^\s*>/.test(line)) return " ".repeat(line.length);
4513
+ if (isIndentedCodeOrQuote(line)) return " ".repeat(line.length);
3988
4514
  return line.replace(/(`+)[^`\n]*?\1/g, (code) => " ".repeat(code.length));
3989
4515
  }).join("\n");
3990
4516
  }
@@ -4012,7 +4538,8 @@ async function buildModelRequest(input) {
4012
4538
  ...input.repeatSilence && { repeatSilence: input.repeatSilence }
4013
4539
  });
4014
4540
  const tools = input.tools ? sortToolsByName(input.tools) : void 0;
4015
- 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;
4016
4543
  const noted = withStaffRepliesJoined(withChannelNotes(withModeNotes(withBurstsJoined(withCorrectionsJoined(sendable)), input.modeNotes)));
4017
4544
  const held = input.frozenContext ? holdNoteAfterApprovals(withFrozenContext(noted, input.frozenContext)) : { messages: noted };
4018
4545
  const history = selectReasoningForModel(held.messages, {
@@ -4097,7 +4624,7 @@ function withoutLoneSurrogatesDeep(value) {
4097
4624
  }
4098
4625
  function withRequestWatch(model, provider, watch) {
4099
4626
  if (!watch || typeof model === "string") return model;
4100
- return wrapLanguageModel4({
4627
+ return wrapLanguageModel5({
4101
4628
  model,
4102
4629
  middleware: {
4103
4630
  specificationVersion: "v4",
@@ -4117,7 +4644,7 @@ function withRequestWatch(model, provider, watch) {
4117
4644
  });
4118
4645
  }
4119
4646
  function withTransientRetries(model, context) {
4120
- return typeof model === "string" ? model : wrapLanguageModel4({ model, middleware: createTransientRetryMiddleware(context) });
4647
+ return typeof model === "string" ? model : wrapLanguageModel5({ model, middleware: createTransientRetryMiddleware(context) });
4121
4648
  }
4122
4649
  function withLastMessageMark(messages, options2) {
4123
4650
  if (!options2 || messages.length === 0) return messages;
@@ -4348,8 +4875,14 @@ function decideNextAttempt(result, state) {
4348
4875
  if (state.overflowCompaction?.answerSteps) return { action: "compact-and-continue", scope: "answer-steps" };
4349
4876
  return { action: "fail" };
4350
4877
  }
4351
- if (!TRANSIENT_KINDS.has(result.kind)) return { action: "fail" };
4878
+ if (isProviderOutage(result.kind)) {
4879
+ const next2 = state.fallbackProviderIds[0];
4880
+ return next2 === void 0 ? { action: "fail" } : { action: "fallback", providerId: next2 };
4881
+ }
4882
+ const accessRefused = result.kind === "other" && isAccessRefusal(result.error);
4883
+ if (!TRANSIENT_KINDS.has(result.kind) && !accessRefused) return { action: "fail" };
4352
4884
  if (result.contentSeen) {
4885
+ if (accessRefused) return { action: "fail" };
4353
4886
  const auto = state.autoContinue;
4354
4887
  if (!auto || auto.approvalRequested || auto.done >= AUTO_CONTINUES_PER_TURN) return { action: "fail" };
4355
4888
  return { action: "auto-continue", pauseMs: autoContinuePauseMs({ index: auto.done, error: result.error, random: auto.random ?? Math.random }) };
@@ -4358,6 +4891,10 @@ function decideNextAttempt(result, state) {
4358
4891
  if (state.fallbackUsed || next === void 0) return { action: "fail" };
4359
4892
  return { action: "fallback", providerId: next };
4360
4893
  }
4894
+ function errorKindForPerson(lastKind, firstOutage) {
4895
+ if (firstOutage === void 0 || TRANSIENT_KINDS.has(lastKind) || lastKind === "context_overflow") return lastKind;
4896
+ return firstOutage;
4897
+ }
4361
4898
  function checkProviderFallback(setting, catalog) {
4362
4899
  if (setting === void 0 || setting === false) return;
4363
4900
  if (typeof setting !== "object" || setting === null || Array.isArray(setting)) {
@@ -5907,6 +6444,7 @@ async function runTurn(turnInput, emitRaw) {
5907
6444
  );
5908
6445
  const turnUsage = createTurnUsage();
5909
6446
  const garbledTail = createGarbledTailGuard();
6447
+ const forgedBlocks = createForgedBlockGuard(turnInput.serviceBlocks ?? CORE_SERVICE_BLOCKS);
5910
6448
  const turnBudget = createTurnBudget(input.turnRules, { catalog: input.catalog, modelId: input.modelId, turnUsage });
5911
6449
  const messageUsage = () => {
5912
6450
  const spent = turnUsage.total();
@@ -5995,13 +6533,21 @@ async function runTurn(turnInput, emitRaw) {
5995
6533
  const seen = latestFrozenContextOf(resume.message.parts);
5996
6534
  if (seen) resumeNote = { ...resumeNote ?? { blocks: seen.blocks, sent: { changed: [], removed: [] }, base: messageId }, notices: turnNotices };
5997
6535
  }
5998
- const fallbackProviderIds = fallbackProviders({
6536
+ const outages = input.providerOutages ?? createProviderOutages({ now: () => (input.now ?? (() => /* @__PURE__ */ new Date()))().getTime(), logError });
6537
+ const allFallbackIds = fallbackProviders({
5999
6538
  catalog: input.catalog,
6000
6539
  modelId,
6001
6540
  chosenProviderId: input.providerId,
6002
6541
  env: input.env,
6003
6542
  ...input.providerFallback !== void 0 && { setting: input.providerFallback }
6004
6543
  });
6544
+ const fallbackProviderIds = allFallbackIds.filter((candidate) => outages.pausedKind(candidate) === void 0);
6545
+ let firstOutage;
6546
+ const primaryPaused = outages.admit(input.providerId);
6547
+ if (primaryPaused !== void 0 && fallbackProviderIds[0] !== void 0) {
6548
+ providerId = fallbackProviderIds[0];
6549
+ logError(`[\u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440] \u0447\u0430\u0442=${input.chatId} \u043C\u043E\u0434\u0435\u043B\u044C=${modelId} \u0441=${input.providerId} \u043D\u0430=${providerId} \u043F\u0440\u0438\u0447\u0438\u043D\u0430=${primaryPaused} \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435`);
6550
+ }
6005
6551
  emit({ type: "start", messageId, messageMetadata: { modelId, providerId: input.providerId } });
6006
6552
  if (input.liveDemos && chatHasExternalContent(await storage.listMessages(input.chatId)) && externalContent.announce()) emitRaw(externalContentChunk());
6007
6553
  const silenceSetting = resolveSilenceSwitch(input.providerFallback);
@@ -6075,6 +6621,7 @@ async function runTurn(turnInput, emitRaw) {
6075
6621
  turnUsage,
6076
6622
  turnBudget,
6077
6623
  garbledTail,
6624
+ forgedBlocks,
6078
6625
  // Есть куда перейти при молчании — у того же провайдера оно не повторяется.
6079
6626
  repeatSilence: () => silenceTarget() === void 0,
6080
6627
  ...silenceSwitch.scope === "step" && switchedBySilence && {
@@ -6092,13 +6639,14 @@ async function runTurn(turnInput, emitRaw) {
6092
6639
  } catch (error2) {
6093
6640
  if (error2 instanceof MissingProviderKeyError) logError(error2.message);
6094
6641
  if (!input.abortSignal.aborted) {
6095
- errorKind = classify(error2);
6642
+ errorKind = errorKindForPerson(classify(error2), firstOutage);
6096
6643
  errors = [{ type: "error", errorText: errorKind }];
6097
6644
  }
6098
6645
  break;
6099
6646
  }
6100
6647
  const { result } = attempt;
6101
6648
  historyRewritten = false;
6649
+ if (result.outcome === "ok") outages.recovered(providerId);
6102
6650
  if (result.outcome !== "failed" || result.contentSeen) answeredProviderId = providerId;
6103
6651
  if (result.outcome === "ok" && backToPrimary) {
6104
6652
  backToPrimary = false;
@@ -6133,12 +6681,24 @@ async function runTurn(turnInput, emitRaw) {
6133
6681
  silenceSwitch: { done: silenceSwitches, max: silenceSwitch.maxSwitches, nextProviderId: silenceTarget() }
6134
6682
  });
6135
6683
  let next = stepsDone < maxSteps ? decideNextAttempt(result, nextState()) : { action: "fail" };
6684
+ const outage = isProviderOutage(result.kind) ? result.kind : void 0;
6685
+ if (outage) {
6686
+ if (providerId === input.providerId) firstOutage ??= outage;
6687
+ outages.report({
6688
+ providerId,
6689
+ kind: outage,
6690
+ ...next.action === "fallback" ? { fallbackProviderId: next.providerId } : { fallbacksPaused: allFallbackIds.some((candidate) => candidate !== providerId && outages.pausedKind(candidate) !== void 0) }
6691
+ });
6692
+ }
6136
6693
  if (next.action === "fallback") {
6137
6694
  logError(`[\u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440] \u0447\u0430\u0442=${input.chatId} \u043C\u043E\u0434\u0435\u043B\u044C=${modelId} \u0441=${providerId} \u043D\u0430=${next.providerId} \u043F\u0440\u0438\u0447\u0438\u043D\u0430=${result.kind} \u043A\u044D\u0448 \u043F\u043E\u0442\u0435\u0440\u044F\u043D`);
6138
- fallbackUsed = true;
6695
+ if (!outage) fallbackUsed = true;
6139
6696
  switchedBySilence = false;
6140
6697
  providerId = next.providerId;
6141
- answer.update(parts2, { modelId, providerId: answeredProviderId });
6698
+ const closing2 = result.contentSeen ? [...openParts.closingChunks(), ...closingChunksForHangingCalls(parts2)] : [];
6699
+ for (const chunk of closing2) emit(chunk);
6700
+ if (closing2.length > 0) answer.continueAfterBreak(parts2, { modelId, providerId: answeredProviderId });
6701
+ else answer.update(parts2, { modelId, providerId: answeredProviderId });
6142
6702
  turnUsage.closeAttempt();
6143
6703
  if (input.abortSignal.aborted) break;
6144
6704
  continue;
@@ -6210,8 +6770,8 @@ async function runTurn(turnInput, emitRaw) {
6210
6770
  turnUsage.closeAttempt();
6211
6771
  continue;
6212
6772
  }
6213
- errorKind = result.kind;
6214
- errors = attempt.errors;
6773
+ errorKind = errorKindForPerson(result.kind, firstOutage);
6774
+ errors = errorKind === result.kind ? attempt.errors : [{ type: "error", errorText: errorKind }];
6215
6775
  terminal = attempt.terminal;
6216
6776
  logError(
6217
6777
  describeProviderFailure(result.error, { chatId: input.chatId, modelId, providerId, kind: result.kind, env: input.env, catalog: input.catalog })
@@ -6281,6 +6841,8 @@ async function runTurn(turnInput, emitRaw) {
6281
6841
  const garbled = cutGarbledTail(garbledTail.forCut(parts));
6282
6842
  const droppedCalls = garbledTail.droppedCalls();
6283
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}`);
6284
6846
  if (garbled) {
6285
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}`);
6286
6848
  const cut = garbled.parts.filter((part) => !garbledTail.isDroppedCall(part));
@@ -6292,7 +6854,7 @@ async function runTurn(turnInput, emitRaw) {
6292
6854
  const textIndex = lastTextIndex(parts);
6293
6855
  if (replyChecks && status === "complete" && textIndex !== -1 && !takeover.signal.aborted) {
6294
6856
  const original = parts[textIndex].text;
6295
- const text = await checkedReplyText({
6857
+ const checked2 = await checkedReplyText({
6296
6858
  checks: replyChecks.checks,
6297
6859
  text: original,
6298
6860
  ownerId: input.ownerId ?? "",
@@ -6302,6 +6864,7 @@ async function runTurn(turnInput, emitRaw) {
6302
6864
  rewrite: createReplyRewrite({ ...input, providerId: answeredProviderId, messageId, logError }),
6303
6865
  logError
6304
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}`));
6305
6868
  if (text !== original) {
6306
6869
  const corrected = withTextAt(parts, textIndex, text);
6307
6870
  if (heldText?.holdsText()) for (const chunk of heldText.release(text)) emitShown(chunk);
@@ -6559,6 +7122,7 @@ async function openAttempt(input, attempt, attemptSignal) {
6559
7122
  } = input;
6560
7123
  const request = await buildModelRequest({
6561
7124
  ...requestInput,
7125
+ serviceBlocks: input.serviceBlocks ?? CORE_SERVICE_BLOCKS,
6562
7126
  providerId,
6563
7127
  history: attempt.history,
6564
7128
  frozenContext: { current: { answerId: input.messageId, data: attempt.frozenContext }, group: Boolean(input.group) },
@@ -6601,9 +7165,11 @@ async function openAttempt(input, attempt, attemptSignal) {
6601
7165
  };
6602
7166
  const { noteAfterApprovals, ...modelRequest } = request;
6603
7167
  attempt.garbledTail.reset();
7168
+ attempt.forgedBlocks.reset(lastQuestionOf(attempt.history));
6604
7169
  const result = streamText({
6605
7170
  ...modelRequest,
6606
- model: attempt.garbledTail.wrap(modelRequest.model),
7171
+ // Сначала вырезка блоков, потом проверка на кашу: каша судит уже чистый текст.
7172
+ model: attempt.garbledTail.wrap(attempt.forgedBlocks.wrap(modelRequest.model)),
6607
7173
  ...approval.toolApproval && { toolApproval: gateToolApproval(approval.toolApproval, () => toolsClosed) },
6608
7174
  // Попытке — остаток шагов хода.
6609
7175
  // Помощник встал на подтверждении человека — ход ждёт, модель основного агента его незаконченным не видит.
@@ -6823,6 +7389,7 @@ function helperRunContext(input, toolQueue, logError) {
6823
7389
  ...input.toolTimeout && { toolTimeout: input.toolTimeout },
6824
7390
  ...input.sequentialTools && { sequentialTools: input.sequentialTools },
6825
7391
  ...input.providerFallback !== void 0 && { providerFallback: input.providerFallback },
7392
+ ...input.providerOutages && { providerOutages: input.providerOutages },
6826
7393
  ...input.modelSilence && { modelSilence: input.modelSilence },
6827
7394
  ...input.turnObserver && { turnObserver: input.turnObserver },
6828
7395
  ...input.assertWithinLimit && { assertWithinLimit: input.assertWithinLimit }
@@ -7025,6 +7592,11 @@ function emitReply(text, emit) {
7025
7592
  emit({ type: "text-end", id });
7026
7593
  emit({ type: "finish-step" });
7027
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
+ }
7028
7600
 
7029
7601
  // src/helpers/run-helper.ts
7030
7602
  var DEFAULT_HELPER_MAX_STEPS = 10;
@@ -7053,16 +7625,26 @@ async function* helperSteps(input) {
7053
7625
  const messages = [...input.priorMessages ?? []];
7054
7626
  const maxSteps = helper.maxSteps ?? DEFAULT_HELPER_MAX_STEPS;
7055
7627
  const { modelId } = helper.model;
7056
- const fallbackProviderIds = fallbackProviders({
7628
+ const outages = context.providerOutages ?? createProviderOutages({ now: Date.now, logError: context.logError });
7629
+ const allFallbackIds = fallbackProviders({
7057
7630
  catalog: context.catalog,
7058
7631
  modelId,
7059
7632
  chosenProviderId: helper.model.providerId,
7060
7633
  env: context.env,
7061
7634
  ...context.providerFallback !== void 0 && { setting: context.providerFallback }
7062
7635
  });
7636
+ const fallbackProviderIds = allFallbackIds.filter((candidate) => outages.pausedKind(candidate) === void 0);
7063
7637
  const toolQueue = context.toolQueue ?? createToolQueue();
7064
7638
  let providerId = helper.model.providerId;
7065
7639
  let fallbackUsed = false;
7640
+ let firstOutage;
7641
+ const primaryPaused = outages.admit(providerId);
7642
+ if (primaryPaused !== void 0 && fallbackProviderIds[0] !== void 0) {
7643
+ context.logError(
7644
+ `[\u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440] \u0447\u0430\u0442=${context.chatId} \u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A=${input.name} \u043C\u043E\u0434\u0435\u043B\u044C=${modelId} \u0441=${providerId} \u043D\u0430=${fallbackProviderIds[0]} \u043F\u0440\u0438\u0447\u0438\u043D\u0430=${primaryPaused} \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435`
7645
+ );
7646
+ providerId = fallbackProviderIds[0];
7647
+ }
7066
7648
  let externalContent = false;
7067
7649
  const report = (status, extra = {}) => ({
7068
7650
  messages: [...messages],
@@ -7089,17 +7671,28 @@ async function* helperSteps(input) {
7089
7671
  } catch (error) {
7090
7672
  if (abortSignal.aborted) throw error;
7091
7673
  const kind = classifyProviderError(error, provider);
7092
- const next = decideNextAttempt({ outcome: "failed", kind, error, contentSeen: false }, { fallbackUsed, fallbackProviderIds });
7674
+ const candidates = providerId === helper.model.providerId ? fallbackProviderIds : fallbackProviderIds.slice(fallbackProviderIds.indexOf(providerId) + 1);
7675
+ const next = decideNextAttempt({ outcome: "failed", kind, error, contentSeen: false }, { fallbackUsed, fallbackProviderIds: candidates });
7676
+ const outage = isProviderOutage(kind) ? kind : void 0;
7677
+ if (outage) {
7678
+ if (providerId === helper.model.providerId) firstOutage ??= outage;
7679
+ outages.report({
7680
+ providerId,
7681
+ kind: outage,
7682
+ ...next.action === "fallback" ? { fallbackProviderId: next.providerId } : { fallbacksPaused: allFallbackIds.some((candidate) => candidate !== providerId && outages.pausedKind(candidate) !== void 0) }
7683
+ });
7684
+ }
7093
7685
  if (next.action === "fallback") {
7094
7686
  context.logError(`[\u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440] \u0447\u0430\u0442=${context.chatId} \u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A=${input.name} \u043C\u043E\u0434\u0435\u043B\u044C=${modelId} \u0441=${providerId} \u043D\u0430=${next.providerId} \u043F\u0440\u0438\u0447\u0438\u043D\u0430=${kind}`);
7095
- fallbackUsed = true;
7687
+ if (!outage) fallbackUsed = true;
7096
7688
  providerId = next.providerId;
7097
7689
  continue;
7098
7690
  }
7099
7691
  context.logError(describeProviderFailure(error, { chatId: context.chatId, modelId, providerId, kind, env: context.env, catalog: context.catalog }));
7100
- yield report("failed", { error: FAILURE_REASONS[kind] });
7692
+ yield report("failed", { error: FAILURE_REASONS[errorKindForPerson(kind, firstOutage)] });
7101
7693
  return;
7102
7694
  }
7695
+ outages.recovered(providerId);
7103
7696
  messages.push(...response);
7104
7697
  externalContent ||= callsExternalContentTool(messages, tools);
7105
7698
  const pendingApproval = pendingApprovalOf2(messages, tools);
@@ -7691,7 +8284,7 @@ var fireworksProvider = createFireworksProvider();
7691
8284
 
7692
8285
  // src/models/providers/openrouter.ts
7693
8286
  import { createOpenRouter } from "@openrouter/ai-sdk-provider";
7694
- import { wrapLanguageModel as wrapLanguageModel5 } from "ai";
8287
+ import { wrapLanguageModel as wrapLanguageModel6 } from "ai";
7695
8288
 
7696
8289
  // src/models/providers/openrouter-cache-mark-limit.ts
7697
8290
  function openrouterCacheMarkLimit(providerModelId) {
@@ -7941,7 +8534,7 @@ function createOpenRouterProvider(options2 = {}) {
7941
8534
  createLanguageModel: (providerModelId, apiKey, requestOptions) => {
7942
8535
  const markLimit = openrouterCacheMarkLimit(providerModelId);
7943
8536
  const fetch = markLimit === void 0 ? requestOptions?.fetch : fetchWithCacheMarkLimit(requestOptions?.fetch, markLimit);
7944
- return wrapLanguageModel5({
8537
+ return wrapLanguageModel6({
7945
8538
  model: createOpenRouter({ apiKey, fetch, ...connection }).chat(providerModelId, routingOptions),
7946
8539
  middleware: [
7947
8540
  openrouterRoutingMiddleware(routing),
@@ -9843,7 +10436,7 @@ function isPlainObject3(value) {
9843
10436
  }
9844
10437
 
9845
10438
  // src/memory/memory-after-answer.ts
9846
- import { wrapLanguageModel as wrapLanguageModel6 } from "ai";
10439
+ import { wrapLanguageModel as wrapLanguageModel7 } from "ai";
9847
10440
  var DRAIN_WAIT_MS = 5e3;
9848
10441
  function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turnObserver }) {
9849
10442
  const running = /* @__PURE__ */ new Set();
@@ -9888,7 +10481,7 @@ function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turn
9888
10481
  };
9889
10482
  }
9890
10483
  function withRetries(model, context) {
9891
- return typeof model === "string" ? model : wrapLanguageModel6({ model, middleware: createTransientRetryMiddleware(context) });
10484
+ return typeof model === "string" ? model : wrapLanguageModel7({ model, middleware: createTransientRetryMiddleware(context) });
9892
10485
  }
9893
10486
 
9894
10487
  // src/conversation/to-agent-chat-message.ts
@@ -10143,6 +10736,7 @@ function createCompactionService(options2) {
10143
10736
  ...observation(ownerId, answerId),
10144
10737
  history,
10145
10738
  ...options2.attachments && { attachments: options2.attachments },
10739
+ ...options2.serviceBlocks && { serviceBlocks: options2.serviceBlocks },
10146
10740
  settings,
10147
10741
  logError,
10148
10742
  abortSignal: signal,
@@ -10286,6 +10880,7 @@ function createCompactionService(options2) {
10286
10880
  ...observation(ownerId, answerId),
10287
10881
  history: [...history, { id: `${chatId}-answer-steps`, role: "assistant", parts: steps }],
10288
10882
  ...options2.attachments && { attachments: options2.attachments },
10883
+ ...options2.serviceBlocks && { serviceBlocks: options2.serviceBlocks },
10289
10884
  settings,
10290
10885
  task: answerStepsTask(settings.summaryCeilingTokens),
10291
10886
  logError,
@@ -11035,6 +11630,7 @@ var InvalidWidgetAnswerError = class extends Error {
11035
11630
  }
11036
11631
  };
11037
11632
  var FAR_FUTURE_ISO = "9999-12-31T23:59:59.999Z";
11633
+ var abandonedBurstReceivers = /* @__PURE__ */ new WeakMap();
11038
11634
  var TurnEndedBeforeSubscribeError = class extends Error {
11039
11635
  constructor(chatId) {
11040
11636
  super(`\u0425\u043E\u0434 \u0432 \u0447\u0430\u0442\u0435 ${chatId} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u0434\u043E \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438`);
@@ -11145,6 +11741,13 @@ function createChatService(input) {
11145
11741
  const logError = input.logError ?? ((message) => console.error(message));
11146
11742
  const now = input.now ?? (() => /* @__PURE__ */ new Date());
11147
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);
11148
11751
  const widgets = createAgentWidgets(input.agent.widgets);
11149
11752
  checkWidgetNamesFree(widgets, input.memory, input.background);
11150
11753
  const browserToolDescriptions = checkedBrowserTools(input.agent, input.memory, widgets);
@@ -11232,6 +11835,7 @@ function createChatService(input) {
11232
11835
  const modelSilence = resolveModelSilence(modelSilenceTimeout);
11233
11836
  const cacheMissWatch = cacheMissLog === false ? void 0 : createCacheMissWatch({ logError });
11234
11837
  checkProviderFallback(providerFallback, options2.catalog);
11838
+ const providerOutages = createProviderOutages({ now: () => now().getTime(), logError });
11235
11839
  const placeMaxLength = resolvePlaceMaxLength(placeMaxLengthSetting);
11236
11840
  const browserToolTimeoutMs = resolveBrowserToolTimeoutMs(browserToolTimeoutSetting);
11237
11841
  const pendingBrowserCalls = createPendingBrowserCalls();
@@ -11292,6 +11896,7 @@ function createChatService(input) {
11292
11896
  // Сжатие берёт кэш хода: у него те же инструменты, что у модели агента, — с браузерными и помощниками.
11293
11897
  agent: { ...options2.agent, tools: withBrowserDescriptions(options2.agent.tools, describedOnlyTools(browserToolDescriptions, options2.agent.helpers, toolResultLimit)) },
11294
11898
  ...options2.attachments && { attachments: options2.attachments },
11899
+ serviceBlocks,
11295
11900
  settings: resolveCompactionSettings(options2.compaction),
11296
11901
  logError,
11297
11902
  ...options2.turnObserver && { turnObserver: options2.turnObserver },
@@ -11907,6 +12512,7 @@ function createChatService(input) {
11907
12512
  modelSilence,
11908
12513
  ...limits.maxOutputTokens !== void 0 && { maxOutputTokens: limits.maxOutputTokens },
11909
12514
  ...providerFallback !== void 0 && { providerFallback },
12515
+ providerOutages,
11910
12516
  ...cacheMissWatch && { cacheMissWatch },
11911
12517
  ...options2.turnObserver && { turnObserver: options2.turnObserver },
11912
12518
  ...options2.limits && { assertWithinLimit: () => assertWithinSpendingLimit(chat.ownerId, chatId) },
@@ -11954,6 +12560,7 @@ function createChatService(input) {
11954
12560
  browserTools: { descriptions: browserToolDescriptions, pending: pendingBrowserCalls, timeoutMs: browserToolTimeoutMs, tabId: launch.page?.tabId }
11955
12561
  },
11956
12562
  ...options2.attachments && { attachments: options2.attachments },
12563
+ serviceBlocks,
11957
12564
  ...options2.memory && { memory: options2.memory },
11958
12565
  ...widgets.waiting.size > 0 && { waitingWidgets: widgets.waiting },
11959
12566
  ...widgets.switchable.size > 0 && { switchableTools: widgets.switchable },
@@ -12229,10 +12836,10 @@ function createChatService(input) {
12229
12836
  ...page && { page }
12230
12837
  });
12231
12838
  }
12232
- function renewingBurstHold(chatId, messageId, dueAt) {
12839
+ function renewingBurstHold(chatId, messageId, dueAt, holder) {
12233
12840
  const timer = setInterval(() => {
12234
12841
  const heldUntil = new Date(Math.max(now().getTime(), dueAt) + BURST_HOLD_MS).toISOString();
12235
- storage.renewPendingBurst({ chatId, messageId, holder: copyId, heldUntil }).then(
12842
+ storage.renewPendingBurst({ chatId, messageId, holder, heldUntil }).then(
12236
12843
  (held) => {
12237
12844
  if (!held) clearInterval(timer);
12238
12845
  },
@@ -12246,7 +12853,7 @@ function createChatService(input) {
12246
12853
  function answeringRecordedBurst(ownerId, chatId, questionId, page, burst) {
12247
12854
  const unattended = burst.kept && burst.unattended === true;
12248
12855
  if (!burst.kept || !keepsPendingBursts) return burstWaits.holding(chatId, () => answeringBurst(ownerId, chatId, questionId, page, unattended));
12249
- const stopRenewing = renewingBurstHold(chatId, questionId, burst.dueAt);
12856
+ const stopRenewing = renewingBurstHold(chatId, questionId, burst.dueAt, burst.holder ?? copyId);
12250
12857
  const answering = burstWaits.holding(chatId, () => answeringBurst(ownerId, chatId, questionId, page, unattended)).finally(stopRenewing);
12251
12858
  void answering.then(
12252
12859
  () => true,
@@ -12272,6 +12879,37 @@ function createChatService(input) {
12272
12879
  const why = error instanceof TurnRefusedError ? `\u043E\u0442\u043A\u0430\u0437 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F ${error.code}: ${error.message}` : error instanceof Error ? error.message : String(error);
12273
12880
  logError(`\u0425\u043E\u0434 \u043D\u0430 \u0431\u0440\u043E\u0448\u0435\u043D\u043D\u0443\u044E \u043F\u0430\u0447\u043A\u0443 \u0447\u0430\u0442\u0430 ${chatId} \u043D\u0435 \u043D\u0430\u0447\u0430\u043B\u0441\u044F: ${why}`);
12274
12881
  }
12882
+ async function receivingAbandonedBurst(burst) {
12883
+ if (burstWaits.has(burst.chatId)) return;
12884
+ await answeringRecordedBurst(burst.ownerId, burst.chatId, burst.messageId, burst.page, { kept: true, dueAt: Date.parse(burst.dueAt), unattended: true, holder: burst.holder }).then(
12885
+ () => {
12886
+ },
12887
+ (error) => reportingAbandonedBurst(burst.chatId, error)
12888
+ );
12889
+ }
12890
+ async function routingAbandonedBurst(burst) {
12891
+ const serviceFor = options2.abandonedBursts?.serviceFor;
12892
+ if (!serviceFor) return receivingAbandonedBurst(burst);
12893
+ let target;
12894
+ try {
12895
+ const chat = await storage.getChat(burst.chatId);
12896
+ if (!chat) return receivingAbandonedBurst(burst);
12897
+ target = await serviceFor({ ownerId: burst.ownerId, chatId: burst.chatId, chat });
12898
+ } catch (error) {
12899
+ logError(`\u0421\u043B\u0443\u0436\u0431\u0430 \u0434\u043B\u044F \u0431\u0440\u043E\u0448\u0435\u043D\u043D\u043E\u0439 \u043F\u0430\u0447\u043A\u0438 \u0447\u0430\u0442\u0430 ${burst.chatId} \u043D\u0435 \u043D\u0430\u0448\u043B\u0430\u0441\u044C (serviceFor): ${error instanceof Error ? error.message : String(error)} \u2014 \u043F\u0430\u0447\u043A\u0430 \u0436\u0434\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u0431\u0445\u043E\u0434\u0430`);
12900
+ return;
12901
+ }
12902
+ if (!target) {
12903
+ logError(`\u0414\u043B\u044F \u0431\u0440\u043E\u0448\u0435\u043D\u043D\u043E\u0439 \u043F\u0430\u0447\u043A\u0438 \u0447\u0430\u0442\u0430 ${burst.chatId} \u043D\u0435\u0442 \u0441\u043B\u0443\u0436\u0431\u044B (serviceFor \u2192 ${String(target)}): \u043F\u0430\u0447\u043A\u0430 \u0436\u0434\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u0431\u0445\u043E\u0434\u0430`);
12904
+ return;
12905
+ }
12906
+ const receive = abandonedBurstReceivers.get(target);
12907
+ if (!receive) {
12908
+ logError(`serviceFor \u0434\u043B\u044F \u0431\u0440\u043E\u0448\u0435\u043D\u043D\u043E\u0439 \u043F\u0430\u0447\u043A\u0438 \u0447\u0430\u0442\u0430 ${burst.chatId} \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435 \u0441\u043B\u0443\u0436\u0431\u0443 \u0447\u0430\u0442\u0430 SDK: \u043F\u0430\u0447\u043A\u0430 \u0436\u0434\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u0431\u0445\u043E\u0434\u0430`);
12909
+ return;
12910
+ }
12911
+ await receive(burst);
12912
+ }
12275
12913
  function sweepingBursts(take) {
12276
12914
  if (!keepsPendingBursts || closing) return Promise.resolve();
12277
12915
  const sweep = (async () => {
@@ -12286,9 +12924,8 @@ function createChatService(input) {
12286
12924
  const taken = await storage.takeAbandonedBursts({ ...take, holder: copyId, heldUntil, limit: free });
12287
12925
  if (taken.length === 0) return;
12288
12926
  for (const burst of taken) {
12289
- if (burstWaits.has(burst.chatId)) continue;
12290
12927
  burstDecisions++;
12291
- answeringRecordedBurst(burst.ownerId, burst.chatId, burst.messageId, burst.page, { kept: true, dueAt: Date.parse(burst.dueAt), unattended: true }).catch((error) => reportingAbandonedBurst(burst.chatId, error)).finally(releasingBurstDecision);
12928
+ void routingAbandonedBurst(burst).finally(releasingBurstDecision);
12292
12929
  }
12293
12930
  }
12294
12931
  })().catch((error) => logError(`\u041E\u0431\u0445\u043E\u0434 \u0436\u0434\u0443\u0449\u0438\u0445 \u043F\u0430\u0447\u0435\u043A \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F: ${error instanceof Error ? error.message : String(error)}`)).finally(() => burstSweeps.delete(sweep));
@@ -13203,6 +13840,7 @@ function createChatService(input) {
13203
13840
  modelSilence,
13204
13841
  logError,
13205
13842
  ...providerFallback !== void 0 && { providerFallback },
13843
+ providerOutages,
13206
13844
  ...options2.turnObserver && { turnObserver: options2.turnObserver },
13207
13845
  ...options2.limits && { assertWithinLimit: () => assertWithinSpendingLimit(ownerId, chatId) }
13208
13846
  });
@@ -13212,6 +13850,7 @@ function createChatService(input) {
13212
13850
  helpers: backgroundHelpers
13213
13851
  };
13214
13852
  }
13853
+ abandonedBurstReceivers.set(service, receivingAbandonedBurst);
13215
13854
  return service;
13216
13855
  function isDetachedAnswer(last, runningId) {
13217
13856
  return runningId === null && last?.status === "streaming" && !liveAnswerIds.has(last.id);
@@ -13374,6 +14013,10 @@ export {
13374
14013
  BURST_HOLD_MS,
13375
14014
  BURST_SWEEP_LIMIT,
13376
14015
  isBurstMessage,
14016
+ CORE_SERVICE_BLOCKS,
14017
+ serviceBlockName,
14018
+ checkServiceBlockFormats,
14019
+ cutForgedBlocks,
13377
14020
  RELEASED_ANSWER_ALIVE_AT,
13378
14021
  aliveAtByLocalClock,
13379
14022
  silenceBoundaryByLocalClock,
@@ -13473,4 +14116,4 @@ export {
13473
14116
  InvalidWidgetAnswerError,
13474
14117
  createChatService
13475
14118
  };
13476
- //# sourceMappingURL=chunk-BS2HDKZB.js.map
14119
+ //# sourceMappingURL=chunk-QA5DVEXF.js.map