@ykurilov/agent-chat-core 0.55.0 → 0.57.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.
- package/CHANGELOG.md +33 -0
- package/README.md +2 -0
- package/dist/access.js +2 -2
- package/dist/{chunk-PWW3R3VL.js → chunk-BS2HDKZB.js} +586 -41
- package/dist/chunk-BS2HDKZB.js.map +1 -0
- package/dist/{chunk-SNPF4BPB.js → chunk-FBULM4V6.js} +2 -2
- package/dist/{chunk-PWGI6NH6.js → chunk-FOAIG4DY.js} +1 -1
- package/dist/chunk-FOAIG4DY.js.map +1 -0
- package/dist/index.js +31 -3
- package/dist/index.js.map +1 -1
- package/dist/storage-contract.js +89 -7
- package/dist/storage-contract.js.map +1 -1
- package/dist/types/agent-run/agent-viewer.d.ts +60 -0
- package/dist/types/agent-run/garbled-tail.d.ts +69 -0
- package/dist/types/agent-run/reply-checks.d.ts +3 -1
- package/dist/types/agent-run/run-agent-turn.d.ts +8 -0
- package/dist/types/agent-run/staff-replies.d.ts +7 -0
- package/dist/types/bursts/message-bursts.d.ts +12 -0
- package/dist/types/chat-events/chat-events.d.ts +7 -0
- package/dist/types/chat-service.d.ts +29 -0
- package/dist/types/compaction/compaction-service.d.ts +3 -0
- package/dist/types/conversation/types.d.ts +3 -1
- package/dist/types/frozen-context/context-blocks.d.ts +3 -0
- package/dist/types/frozen-context/turn-context.d.ts +3 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/storage/chat-storage.d.ts +47 -0
- package/dist/types/storage/storage-contract.d.ts +5 -0
- package/dist/types/turn-hooks/turn-gate.d.ts +2 -0
- package/dist/types/turn-hooks/turn-hooks.d.ts +7 -0
- package/package.json +1 -1
- package/dist/chunk-PWGI6NH6.js.map +0 -1
- package/dist/chunk-PWW3R3VL.js.map +0 -1
- /package/dist/{chunk-SNPF4BPB.js.map → chunk-FBULM4V6.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
TurnRefusedError,
|
|
4
4
|
normalizeTurnRefusal,
|
|
5
5
|
validateAppEvent
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FOAIG4DY.js";
|
|
7
7
|
|
|
8
8
|
// src/conversation/types.ts
|
|
9
9
|
var CURRENT_FORMAT_VERSION = 1;
|
|
@@ -41,7 +41,8 @@ var BUILT_IN_USAGE_SOURCES = [
|
|
|
41
41
|
"judge",
|
|
42
42
|
"first_message",
|
|
43
43
|
"voice_input",
|
|
44
|
-
"voice_polish"
|
|
44
|
+
"voice_polish",
|
|
45
|
+
"mcp_tool"
|
|
45
46
|
];
|
|
46
47
|
|
|
47
48
|
// src/conversation/branches.ts
|
|
@@ -1717,6 +1718,9 @@ var DEFAULT_BURST_QUIET_MS = 3e3;
|
|
|
1717
1718
|
var MAX_BURST_QUIET_MS = 6e5;
|
|
1718
1719
|
var BURST_PENDING_GRACE_MS = 12e4;
|
|
1719
1720
|
var BURST_BUSY_RETRY_MS = 1e3;
|
|
1721
|
+
var BURST_HOLD_MS = 3e4;
|
|
1722
|
+
var BURST_HOLD_RENEW_MS = 1e4;
|
|
1723
|
+
var BURST_SWEEP_LIMIT = 10;
|
|
1720
1724
|
function burstPart(quietMs, at) {
|
|
1721
1725
|
return { type: BURST_PART_TYPE, data: { quietMs, at } };
|
|
1722
1726
|
}
|
|
@@ -1825,6 +1829,9 @@ function createBurstWaits() {
|
|
|
1825
1829
|
wake(chatId) {
|
|
1826
1830
|
for (const entry of [...timers]) if (entry.chatId === chatId) entry.done();
|
|
1827
1831
|
},
|
|
1832
|
+
wakeAll() {
|
|
1833
|
+
for (const entry of [...timers]) entry.done();
|
|
1834
|
+
},
|
|
1828
1835
|
has: (chatId) => waiting.has(chatId),
|
|
1829
1836
|
async holding(chatId, work) {
|
|
1830
1837
|
enter(chatId);
|
|
@@ -2284,6 +2291,57 @@ function jsonBytes(value) {
|
|
|
2284
2291
|
}
|
|
2285
2292
|
}
|
|
2286
2293
|
|
|
2294
|
+
// src/agent-run/agent-viewer.ts
|
|
2295
|
+
var AgentViewerError = class extends Error {
|
|
2296
|
+
constructor(message, options2) {
|
|
2297
|
+
super(message, options2);
|
|
2298
|
+
this.name = "AgentViewerError";
|
|
2299
|
+
}
|
|
2300
|
+
};
|
|
2301
|
+
async function resolveAgentViewer(source, input) {
|
|
2302
|
+
input.abortSignal.throwIfAborted();
|
|
2303
|
+
let viewer;
|
|
2304
|
+
try {
|
|
2305
|
+
viewer = await Promise.race([
|
|
2306
|
+
Promise.resolve().then(() => source(input)),
|
|
2307
|
+
new Promise((_, reject) => input.abortSignal.addEventListener("abort", () => reject(input.abortSignal.reason), { once: true }))
|
|
2308
|
+
]);
|
|
2309
|
+
} catch (error) {
|
|
2310
|
+
if (input.abortSignal.aborted) throw error;
|
|
2311
|
+
throw new AgentViewerError(`agent.viewer \u0431\u0440\u043E\u0441\u0438\u043B\u0430: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
2312
|
+
}
|
|
2313
|
+
if (typeof viewer !== "object" || viewer === null || Array.isArray(viewer)) throw new AgentViewerError("agent.viewer \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u043D\u0435 \u043E\u0431\u044A\u0435\u043A\u0442");
|
|
2314
|
+
return Object.freeze({ ...viewer });
|
|
2315
|
+
}
|
|
2316
|
+
function viewerInput(turn) {
|
|
2317
|
+
const ownerId = turn.ownerId ?? "";
|
|
2318
|
+
return {
|
|
2319
|
+
userId: turn.staff?.id ?? ownerId,
|
|
2320
|
+
chatId: turn.chatId,
|
|
2321
|
+
ownerId,
|
|
2322
|
+
messageId: turn.messageId,
|
|
2323
|
+
chat: structuredClone({ ...turn.chatData && { data: turn.chatData } }),
|
|
2324
|
+
...turn.subject && { subject: structuredClone(turn.subject) },
|
|
2325
|
+
channel: turn.channel ?? "web",
|
|
2326
|
+
audience: turn.audience,
|
|
2327
|
+
...turn.staff && { staff: structuredClone(turn.staff) },
|
|
2328
|
+
abortSignal: turn.abortSignal
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
var VIEWER_OUTSIDE_TURN_TIMEOUT_MS = 1e4;
|
|
2332
|
+
async function resolveViewerOutsideTurn(source, turn, signal) {
|
|
2333
|
+
const deadline = new AbortController();
|
|
2334
|
+
const timer = setTimeout(
|
|
2335
|
+
() => deadline.abort(new AgentViewerError(`agent.viewer \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B\u0430 \u0437\u0430 ${VIEWER_OUTSIDE_TURN_TIMEOUT_MS / 1e3} \u0441`)),
|
|
2336
|
+
VIEWER_OUTSIDE_TURN_TIMEOUT_MS
|
|
2337
|
+
);
|
|
2338
|
+
try {
|
|
2339
|
+
return await resolveAgentViewer(source, viewerInput({ ...turn, abortSignal: signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal }));
|
|
2340
|
+
} finally {
|
|
2341
|
+
clearTimeout(timer);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2287
2345
|
// src/helpers/run-helper.ts
|
|
2288
2346
|
import { generateText as generateText2, stepCountIs as stepCountIs2 } from "ai";
|
|
2289
2347
|
|
|
@@ -2807,7 +2865,7 @@ function usageMinusJournaled(usage, journaled) {
|
|
|
2807
2865
|
}
|
|
2808
2866
|
|
|
2809
2867
|
// src/agent-run/build-model-request.ts
|
|
2810
|
-
import { convertToModelMessages, wrapLanguageModel as
|
|
2868
|
+
import { convertToModelMessages, wrapLanguageModel as wrapLanguageModel4 } from "ai";
|
|
2811
2869
|
|
|
2812
2870
|
// src/conversation/hanging-tool-calls.ts
|
|
2813
2871
|
var PRELIMINARY_OUTPUT_MAX_LENGTH = 2e3;
|
|
@@ -3483,6 +3541,37 @@ function withUnansweredMarked(messages) {
|
|
|
3483
3541
|
);
|
|
3484
3542
|
}
|
|
3485
3543
|
|
|
3544
|
+
// src/agent-run/staff-replies.ts
|
|
3545
|
+
function isStaffReply(message) {
|
|
3546
|
+
if (message.role !== "user") return false;
|
|
3547
|
+
const notices = message.parts.flatMap(noticesOfPart);
|
|
3548
|
+
return notices.length > 0 && notices.every((notice) => notice.kind === "staff");
|
|
3549
|
+
}
|
|
3550
|
+
function staffPartsJoined(parts) {
|
|
3551
|
+
return parts.flatMap((part) => {
|
|
3552
|
+
if (part.type !== AGENT_NOTICE_PART_TYPE) return joinedParts([part]);
|
|
3553
|
+
const text = noticePartForModel(part);
|
|
3554
|
+
return text ? [{ type: "text", text: `
|
|
3555
|
+
|
|
3556
|
+
${text.text}` }] : [];
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
function withStaffRepliesJoined(history) {
|
|
3560
|
+
const joined = [];
|
|
3561
|
+
let afterStaff = false;
|
|
3562
|
+
let changed = false;
|
|
3563
|
+
for (const message of history) {
|
|
3564
|
+
const previous = joined.at(-1);
|
|
3565
|
+
const staff = isStaffReply(message);
|
|
3566
|
+
if (previous?.role === "user" && message.role === "user" && (staff || afterStaff)) {
|
|
3567
|
+
joined[joined.length - 1] = { ...previous, parts: [...previous.parts, ...staff ? staffPartsJoined(message.parts) : joinedParts(message.parts)] };
|
|
3568
|
+
changed = true;
|
|
3569
|
+
} else joined.push(message);
|
|
3570
|
+
afterStaff = staff;
|
|
3571
|
+
}
|
|
3572
|
+
return changed ? joined : history;
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3486
3575
|
// src/frozen-context/frozen-context-messages.ts
|
|
3487
3576
|
function frozenContextBase(history) {
|
|
3488
3577
|
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
@@ -3608,6 +3697,298 @@ function withoutUndelivered(history) {
|
|
|
3608
3697
|
return dropped.size === 0 ? history : history.filter((_, index) => !dropped.has(index));
|
|
3609
3698
|
}
|
|
3610
3699
|
|
|
3700
|
+
// src/agent-run/garbled-tail.ts
|
|
3701
|
+
import { wrapLanguageModel as wrapLanguageModel3 } from "ai";
|
|
3702
|
+
var SIGN_ORDER = ["think", "role", "restart", "tool"];
|
|
3703
|
+
var TOKEN_SOURCE = String.raw`<\/?think>|<\|[^|\n<>]{1,40}\|>|<|[^|\n<>]{1,40}|>`;
|
|
3704
|
+
var TEMPLATE_TOKEN = new RegExp(TOKEN_SOURCE, "g");
|
|
3705
|
+
var ROLE_LINE = /(?<=^|\n)(user|assistant)(?=[ \t]*(?:\n|$)|[А-ЯЁа-яё])/g;
|
|
3706
|
+
var TOKEN_ROLE_LINE = /(?<=^|\n)(?:(?:<\|im_start\|>|<\|start_header_id\|>)(?:user|assistant)\b|<\|(?:user|assistant)\|>|<|(?:User|Assistant)|>)/g;
|
|
3707
|
+
var ANCHOR_HINT = /(?:^|\n)(?:(?:user|assistant)(?:[ \t]*(?:\n|$)|[А-ЯЁа-яё])|(?:<\|im_start\|>|<\|start_header_id\|>)(?:user|assistant)|<\|(?:user|assistant)\|>|<|(?:User|Assistant)|>)/;
|
|
3708
|
+
var ROLE_TOKEN = /(?:<\|im_start\|>|<\|start_header_id\|>)(user|assistant)\b|<\|(user|assistant)\|>|<|(User|Assistant)|>/g;
|
|
3709
|
+
var GLUED_SENTENCES = /[а-яё]{2}[.!?…][А-ЯЁ][а-яё]/;
|
|
3710
|
+
var changesVerdict = (lines) => ANCHOR_HINT.test(lines) || GLUED_SENTENCES.test(lines) || new RegExp(TOKEN_SOURCE).test(lines);
|
|
3711
|
+
var FINISHED = /[.!?…»)"]$/;
|
|
3712
|
+
var TRAILING_TOKENS = new RegExp(String.raw`(?:\s|${TOKEN_SOURCE})+$`);
|
|
3713
|
+
function findGarbledTail(parts) {
|
|
3714
|
+
if (!parts.some((part) => part.type === "text" && ANCHOR_HINT.test(part.text))) return null;
|
|
3715
|
+
const steps = stepsOf(parts);
|
|
3716
|
+
const markersByStep = steps.map((step) => step.texts.flatMap((text, at) => markersIn(text, step.texts.slice(0, at))));
|
|
3717
|
+
const bareRoles = new Set(markersByStep.flat().flatMap((marker) => marker.bareRole ? [marker.bareRole.name] : []));
|
|
3718
|
+
const tokenRoles = new Set(
|
|
3719
|
+
steps.flatMap((step) => step.texts.flatMap((text) => [...text.visible.matchAll(ROLE_TOKEN)].map((match) => (match[1] ?? match[2] ?? match[3]).toLowerCase())))
|
|
3720
|
+
);
|
|
3721
|
+
const dialog = bareRoles.size > 1;
|
|
3722
|
+
const template = tokenRoles.size > 1;
|
|
3723
|
+
for (const [index, step] of steps.entries()) {
|
|
3724
|
+
const markers = markersByStep[index].filter((marker) => !(dialog && marker.bareRole) && !(template && marker.roleToken));
|
|
3725
|
+
const found = garbleInStep(parts, step, markers);
|
|
3726
|
+
if (found) return found;
|
|
3727
|
+
}
|
|
3728
|
+
return null;
|
|
3729
|
+
}
|
|
3730
|
+
function cutGarbledTail(parts) {
|
|
3731
|
+
const found = findGarbledTail(parts);
|
|
3732
|
+
if (!found) return null;
|
|
3733
|
+
const stepEnd = parts.findIndex((part, index) => index > found.partIndex && part.type === "step-start");
|
|
3734
|
+
const end = stepEnd === -1 ? parts.length : stepEnd;
|
|
3735
|
+
const cutPart = parts[found.partIndex];
|
|
3736
|
+
const answer = cutPart.text.slice(0, found.offset).replace(TRAILING_TOKENS, "");
|
|
3737
|
+
let removedChars = cutPart.text.length - answer.length;
|
|
3738
|
+
const kept = parts.slice(0, found.partIndex);
|
|
3739
|
+
if (answer) kept.push({ ...cutPart, text: answer, state: "done" });
|
|
3740
|
+
for (const part of parts.slice(found.partIndex + 1, end)) {
|
|
3741
|
+
if (part.type === "text") removedChars += part.text.length;
|
|
3742
|
+
else if (part.type === "reasoning") continue;
|
|
3743
|
+
else if (!(stepEnd === -1 && part.type === LENGTH_CUT_PART_TYPE)) kept.push(part);
|
|
3744
|
+
}
|
|
3745
|
+
kept.push(...parts.slice(end));
|
|
3746
|
+
if (!kept.some((part) => part.type === "text" && /[\p{L}\p{N}]/u.test(part.text.replace(TEMPLATE_TOKEN, "")))) return null;
|
|
3747
|
+
const textIndex = answer ? found.partIndex : parts.slice(0, found.partIndex).findLastIndex((part) => part.type === "text");
|
|
3748
|
+
return { parts: kept, signs: found.signs, removedChars, textIndex };
|
|
3749
|
+
}
|
|
3750
|
+
function withoutGarbledTails(history) {
|
|
3751
|
+
let changed = false;
|
|
3752
|
+
const cleaned = history.map((message) => {
|
|
3753
|
+
if (message.role !== "assistant") return message;
|
|
3754
|
+
const parts = rememberedCut(message);
|
|
3755
|
+
if (!parts) return message;
|
|
3756
|
+
changed = true;
|
|
3757
|
+
return { ...message, parts };
|
|
3758
|
+
});
|
|
3759
|
+
return changed ? cleaned : history;
|
|
3760
|
+
}
|
|
3761
|
+
var rememberedCuts = /* @__PURE__ */ new Map();
|
|
3762
|
+
var REMEMBERED_CUTS_LIMIT = 500;
|
|
3763
|
+
function rememberedCut(message) {
|
|
3764
|
+
if (!message.parts.some((part) => part.type === "text" && ANCHOR_HINT.test(part.text))) return null;
|
|
3765
|
+
const source = JSON.stringify(message.parts);
|
|
3766
|
+
const known = rememberedCuts.get(message.id);
|
|
3767
|
+
if (known?.source === source) return known.parts;
|
|
3768
|
+
const parts = cutGarbledTail(message.parts)?.parts ?? null;
|
|
3769
|
+
rememberedCuts.delete(message.id);
|
|
3770
|
+
rememberedCuts.set(message.id, { source, parts });
|
|
3771
|
+
if (rememberedCuts.size > REMEMBERED_CUTS_LIMIT) rememberedCuts.delete(rememberedCuts.keys().next().value);
|
|
3772
|
+
return parts;
|
|
3773
|
+
}
|
|
3774
|
+
var DROPPED_CALL = { type: "tool-dropped", toolCallId: "", state: "input-available", input: {} };
|
|
3775
|
+
function createGarbledTailGuard() {
|
|
3776
|
+
let parts = [];
|
|
3777
|
+
let garbled = false;
|
|
3778
|
+
let droppedCalls = 0;
|
|
3779
|
+
let hinted = false;
|
|
3780
|
+
const worthChecking = (lines) => {
|
|
3781
|
+
hinted ||= ANCHOR_HINT.test(lines);
|
|
3782
|
+
return hinted && changesVerdict(lines);
|
|
3783
|
+
};
|
|
3784
|
+
const textAt = /* @__PURE__ */ new Map();
|
|
3785
|
+
const open = /* @__PURE__ */ new Set();
|
|
3786
|
+
const droppedIds = /* @__PURE__ */ new Set();
|
|
3787
|
+
const garbledNow = (withCall) => {
|
|
3788
|
+
if (!hinted) return false;
|
|
3789
|
+
const known = parts.map(
|
|
3790
|
+
(part, index) => part.type === "text" && open.has(index) ? { ...part, text: part.text.slice(0, part.text.lastIndexOf("\n") + 1) } : part
|
|
3791
|
+
);
|
|
3792
|
+
return cutGarbledTail(withCall ? [...known, DROPPED_CALL] : known) !== null;
|
|
3793
|
+
};
|
|
3794
|
+
const transform = (part, controller) => {
|
|
3795
|
+
switch (part.type) {
|
|
3796
|
+
case "text-start": {
|
|
3797
|
+
const index = parts.push({ type: "text", text: "" }) - 1;
|
|
3798
|
+
textAt.set(part.id, index);
|
|
3799
|
+
open.add(index);
|
|
3800
|
+
break;
|
|
3801
|
+
}
|
|
3802
|
+
case "text-delta": {
|
|
3803
|
+
if (garbled) return;
|
|
3804
|
+
const index = textAt.get(part.id);
|
|
3805
|
+
if (index === void 0) break;
|
|
3806
|
+
const text = parts[index];
|
|
3807
|
+
const before = text.text.length;
|
|
3808
|
+
const lineStart = text.text.lastIndexOf("\n") + 1;
|
|
3809
|
+
text.text += part.delta;
|
|
3810
|
+
if (!part.delta.includes("\n")) break;
|
|
3811
|
+
if (worthChecking(text.text.slice(lineStart, text.text.lastIndexOf("\n") + 1)) && garbledNow(false)) {
|
|
3812
|
+
garbled = true;
|
|
3813
|
+
const keep = text.text.lastIndexOf("\n") + 1 - before;
|
|
3814
|
+
text.text = text.text.slice(0, before + Math.max(keep, 0));
|
|
3815
|
+
if (keep > 0) controller.enqueue({ ...part, delta: part.delta.slice(0, keep) });
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
break;
|
|
3819
|
+
}
|
|
3820
|
+
case "text-end": {
|
|
3821
|
+
const index = textAt.get(part.id);
|
|
3822
|
+
if (index === void 0 || garbled) break;
|
|
3823
|
+
open.delete(index);
|
|
3824
|
+
const { text } = parts[index];
|
|
3825
|
+
if (worthChecking(text.slice(text.lastIndexOf("\n") + 1)) && garbledNow(false)) garbled = true;
|
|
3826
|
+
break;
|
|
3827
|
+
}
|
|
3828
|
+
case "reasoning-delta":
|
|
3829
|
+
if (garbled) return;
|
|
3830
|
+
break;
|
|
3831
|
+
case "tool-input-start":
|
|
3832
|
+
case "tool-call": {
|
|
3833
|
+
const callId = part.type === "tool-call" ? part.toolCallId : part.id;
|
|
3834
|
+
if (droppedIds.has(callId)) return;
|
|
3835
|
+
const started = part.type === "tool-call" && parts.some((known) => known.toolCallId === callId);
|
|
3836
|
+
if (started) break;
|
|
3837
|
+
if (garbled || garbledNow(true)) {
|
|
3838
|
+
garbled = true;
|
|
3839
|
+
droppedIds.add(callId);
|
|
3840
|
+
droppedCalls += 1;
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3843
|
+
parts.push({ type: "tool-call", toolCallId: callId, state: "input-streaming" });
|
|
3844
|
+
break;
|
|
3845
|
+
}
|
|
3846
|
+
case "tool-input-delta":
|
|
3847
|
+
case "tool-input-end":
|
|
3848
|
+
if (droppedIds.has(part.id)) return;
|
|
3849
|
+
break;
|
|
3850
|
+
case "finish":
|
|
3851
|
+
if (droppedCalls > 0 && part.finishReason.unified === "tool-calls") {
|
|
3852
|
+
controller.enqueue({ ...part, finishReason: { ...part.finishReason, unified: "stop" } });
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
break;
|
|
3856
|
+
}
|
|
3857
|
+
controller.enqueue(part);
|
|
3858
|
+
};
|
|
3859
|
+
return {
|
|
3860
|
+
/** Новая попытка: шаги прежней стёрты — всё сначала. */
|
|
3861
|
+
reset() {
|
|
3862
|
+
parts = [];
|
|
3863
|
+
garbled = false;
|
|
3864
|
+
droppedCalls = 0;
|
|
3865
|
+
hinted = false;
|
|
3866
|
+
textAt.clear();
|
|
3867
|
+
open.clear();
|
|
3868
|
+
droppedIds.clear();
|
|
3869
|
+
},
|
|
3870
|
+
/** Модель попытки под присмотром: каждый запрос — новый шаг. */
|
|
3871
|
+
wrap(model) {
|
|
3872
|
+
if (typeof model === "string") return model;
|
|
3873
|
+
return wrapLanguageModel3({
|
|
3874
|
+
model,
|
|
3875
|
+
middleware: {
|
|
3876
|
+
specificationVersion: "v4",
|
|
3877
|
+
wrapStream: async ({ doStream }) => {
|
|
3878
|
+
const result = await doStream();
|
|
3879
|
+
parts.push({ type: "step-start" });
|
|
3880
|
+
return { ...result, stream: result.stream.pipeThrough(new TransformStream({ transform })) };
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
});
|
|
3884
|
+
},
|
|
3885
|
+
/** Срыв уже замечен в этой попытке: нового шага не нужно. */
|
|
3886
|
+
garbled: () => garbled,
|
|
3887
|
+
/** Сколько вызовов отброшено до запуска. */
|
|
3888
|
+
droppedCalls: () => droppedCalls,
|
|
3889
|
+
/** Части ответа для правила при сохранении: отброшенный вызов — на своём месте, в конце. */
|
|
3890
|
+
forCut: (saved) => droppedCalls > 0 ? [...saved, DROPPED_CALL] : saved,
|
|
3891
|
+
/** Заглушка отброшенного вызова из `forCut`: в запись не идёт. */
|
|
3892
|
+
isDroppedCall: (part) => part === DROPPED_CALL
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
var isToolPart6 = (part) => part.type.startsWith("tool-") || part.type === "dynamic-tool";
|
|
3896
|
+
function stepsOf(parts) {
|
|
3897
|
+
const steps = [];
|
|
3898
|
+
let current = { from: 0, to: 0, texts: [] };
|
|
3899
|
+
parts.forEach((part, partIndex) => {
|
|
3900
|
+
if (part.type === "step-start") {
|
|
3901
|
+
current.to = partIndex;
|
|
3902
|
+
steps.push(current);
|
|
3903
|
+
current = { from: partIndex, to: partIndex, texts: [] };
|
|
3904
|
+
} else if (part.type === "text") current.texts.push({ partIndex, visible: withoutCodeAndQuotes(part.text) });
|
|
3905
|
+
});
|
|
3906
|
+
current.to = parts.length;
|
|
3907
|
+
steps.push(current);
|
|
3908
|
+
return steps.filter((step) => step.texts.length > 0);
|
|
3909
|
+
}
|
|
3910
|
+
function garbleInStep(parts, step, markers) {
|
|
3911
|
+
const roles = markers.filter((marker) => marker.kind === "role");
|
|
3912
|
+
const role = roles.find((marker) => finishedBefore(step.texts, marker));
|
|
3913
|
+
if (!role) return null;
|
|
3914
|
+
const after = (marker) => byPlace(marker, role) > 0;
|
|
3915
|
+
const tokens2 = markers.filter((marker) => marker.kind !== "role");
|
|
3916
|
+
const gluedAfter = () => textAfter(step.texts, role).some((text) => GLUED_SENTENCES.test(text));
|
|
3917
|
+
const others = /* @__PURE__ */ new Set();
|
|
3918
|
+
if (role.roleToken) {
|
|
3919
|
+
if (tokens2.some((marker) => !after(marker))) others.add("think");
|
|
3920
|
+
if (roles.some(after) || gluedAfter()) others.add("restart");
|
|
3921
|
+
} else {
|
|
3922
|
+
if (tokens2.length > 0) others.add("think");
|
|
3923
|
+
if (roles.some(after)) others.add("restart");
|
|
3924
|
+
if (role.bareRole.glued) {
|
|
3925
|
+
if (gluedAfter()) others.add("restart");
|
|
3926
|
+
if (parts.slice(role.partIndex + 1, step.to).some(isToolPart6)) others.add("tool");
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
if (others.size === 0) return null;
|
|
3930
|
+
const closeThinks = markers.filter((marker) => marker.kind === "close-think" && !after(marker));
|
|
3931
|
+
const anchor = closeThinks.length >= 2 ? closeThinks[0] : role;
|
|
3932
|
+
const signs2 = /* @__PURE__ */ new Set(["role", ...others]);
|
|
3933
|
+
return { partIndex: anchor.partIndex, offset: anchor.offset, signs: SIGN_ORDER.filter((sign) => signs2.has(sign)) };
|
|
3934
|
+
}
|
|
3935
|
+
var byPlace = (a, b) => a.partIndex - b.partIndex || a.offset - b.offset;
|
|
3936
|
+
function textAfter(texts, anchor) {
|
|
3937
|
+
return texts.flatMap(
|
|
3938
|
+
(text) => text.partIndex < anchor.partIndex ? [] : [text.partIndex === anchor.partIndex ? text.visible.slice(anchor.offset) : text.visible]
|
|
3939
|
+
);
|
|
3940
|
+
}
|
|
3941
|
+
function markersIn(text, earlier) {
|
|
3942
|
+
const { partIndex, visible } = text;
|
|
3943
|
+
const markers = [];
|
|
3944
|
+
const hasTextBefore = (offset) => visibleBefore(earlier, visible, offset) !== "";
|
|
3945
|
+
const roleTokens = /* @__PURE__ */ new Set();
|
|
3946
|
+
for (const match of visible.matchAll(TOKEN_ROLE_LINE)) {
|
|
3947
|
+
roleTokens.add(match.index);
|
|
3948
|
+
if (hasTextBefore(match.index)) markers.push({ partIndex, offset: match.index, kind: "role", roleToken: true });
|
|
3949
|
+
}
|
|
3950
|
+
for (const match of visible.matchAll(TEMPLATE_TOKEN)) {
|
|
3951
|
+
if (roleTokens.has(match.index) || !hasTextBefore(match.index)) continue;
|
|
3952
|
+
markers.push({ partIndex, offset: match.index, kind: match[0] === "</think>" ? "close-think" : "token" });
|
|
3953
|
+
}
|
|
3954
|
+
for (const match of visible.matchAll(ROLE_LINE)) {
|
|
3955
|
+
if (!hasTextBefore(match.index)) continue;
|
|
3956
|
+
const name = match[1];
|
|
3957
|
+
const glued = /[А-ЯЁа-яё]/.test(visible[match.index + name.length] ?? "");
|
|
3958
|
+
markers.push({ partIndex, offset: match.index, kind: "role", bareRole: { name, glued } });
|
|
3959
|
+
}
|
|
3960
|
+
return markers.sort(byPlace);
|
|
3961
|
+
}
|
|
3962
|
+
function finishedBefore(texts, marker) {
|
|
3963
|
+
const at = texts.findIndex((text) => text.partIndex === marker.partIndex);
|
|
3964
|
+
return FINISHED.test(visibleBefore(texts.slice(0, at), texts[at].visible, marker.offset));
|
|
3965
|
+
}
|
|
3966
|
+
function visibleBefore(earlier, visible, offset) {
|
|
3967
|
+
const own = visible.slice(0, offset).replace(TRAILING_TOKENS, "");
|
|
3968
|
+
if (own) return own;
|
|
3969
|
+
for (let index = earlier.length - 1; index >= 0; index -= 1) {
|
|
3970
|
+
const text = earlier[index].visible.replace(TRAILING_TOKENS, "");
|
|
3971
|
+
if (text) return text;
|
|
3972
|
+
}
|
|
3973
|
+
return "";
|
|
3974
|
+
}
|
|
3975
|
+
function withoutCodeAndQuotes(text) {
|
|
3976
|
+
let fence = null;
|
|
3977
|
+
return text.split("\n").map((line) => {
|
|
3978
|
+
const opener = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
3979
|
+
if (fence !== null) {
|
|
3980
|
+
if (opener && opener[0] === fence[0] && opener.length >= fence.length && line.trim() === opener) fence = null;
|
|
3981
|
+
return " ".repeat(line.length);
|
|
3982
|
+
}
|
|
3983
|
+
if (opener) {
|
|
3984
|
+
fence = opener;
|
|
3985
|
+
return " ".repeat(line.length);
|
|
3986
|
+
}
|
|
3987
|
+
if (/^(?: {4}|\t)/.test(line) || /^\s*>/.test(line)) return " ".repeat(line.length);
|
|
3988
|
+
return line.replace(/(`+)[^`\n]*?\1/g, (code) => " ".repeat(code.length));
|
|
3989
|
+
}).join("\n");
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3611
3992
|
// src/agent-run/build-model-request.ts
|
|
3612
3993
|
async function buildModelRequest(input) {
|
|
3613
3994
|
const found = findModel(input.catalog, input.modelId, input.providerId);
|
|
@@ -3632,7 +4013,7 @@ async function buildModelRequest(input) {
|
|
|
3632
4013
|
});
|
|
3633
4014
|
const tools = input.tools ? sortToolsByName(input.tools) : void 0;
|
|
3634
4015
|
const sendable = input.switchableTools ? withSwitchedOffCallsAsText(sendableHistory(input.history), input.switchableTools, tools) : sendableHistory(input.history);
|
|
3635
|
-
const noted = withChannelNotes(withModeNotes(withBurstsJoined(withCorrectionsJoined(sendable)), input.modeNotes));
|
|
4016
|
+
const noted = withStaffRepliesJoined(withChannelNotes(withModeNotes(withBurstsJoined(withCorrectionsJoined(sendable)), input.modeNotes)));
|
|
3636
4017
|
const held = input.frozenContext ? holdNoteAfterApprovals(withFrozenContext(noted, input.frozenContext)) : { messages: noted };
|
|
3637
4018
|
const history = selectReasoningForModel(held.messages, {
|
|
3638
4019
|
modelId: profile.id,
|
|
@@ -3668,7 +4049,7 @@ async function buildModelRequest(input) {
|
|
|
3668
4049
|
};
|
|
3669
4050
|
}
|
|
3670
4051
|
function sendableHistory(history) {
|
|
3671
|
-
return withoutUndelivered(history).filter((message) => (message.role !== "assistant" || hasAnswerContent(message.parts)) && !isStateMessage(message)).map(withAssistantFilesAsText).map((message) => {
|
|
4052
|
+
return withoutGarbledTails(withoutUndelivered(history)).filter((message) => (message.role !== "assistant" || hasAnswerContent(message.parts)) && !isStateMessage(message)).map(withAssistantFilesAsText).map((message) => {
|
|
3672
4053
|
const closed = message.role === "assistant" ? closeHangingToolCalls(message.parts) : message.parts;
|
|
3673
4054
|
const parts = withoutLoneSurrogatesInParts(closed);
|
|
3674
4055
|
return parts === message.parts ? message : { ...message, parts };
|
|
@@ -3716,7 +4097,7 @@ function withoutLoneSurrogatesDeep(value) {
|
|
|
3716
4097
|
}
|
|
3717
4098
|
function withRequestWatch(model, provider, watch) {
|
|
3718
4099
|
if (!watch || typeof model === "string") return model;
|
|
3719
|
-
return
|
|
4100
|
+
return wrapLanguageModel4({
|
|
3720
4101
|
model,
|
|
3721
4102
|
middleware: {
|
|
3722
4103
|
specificationVersion: "v4",
|
|
@@ -3736,7 +4117,7 @@ function withRequestWatch(model, provider, watch) {
|
|
|
3736
4117
|
});
|
|
3737
4118
|
}
|
|
3738
4119
|
function withTransientRetries(model, context) {
|
|
3739
|
-
return typeof model === "string" ? model :
|
|
4120
|
+
return typeof model === "string" ? model : wrapLanguageModel4({ model, middleware: createTransientRetryMiddleware(context) });
|
|
3740
4121
|
}
|
|
3741
4122
|
function withLastMessageMark(messages, options2) {
|
|
3742
4123
|
if (!options2 || messages.length === 0) return messages;
|
|
@@ -5000,6 +5381,10 @@ function withTextAt(parts, index, text) {
|
|
|
5000
5381
|
function createShownSteps() {
|
|
5001
5382
|
let steps = 0;
|
|
5002
5383
|
let textSteps = [];
|
|
5384
|
+
const resetsToText = (fromEnd) => {
|
|
5385
|
+
const step = textSteps.at(-1 - fromEnd);
|
|
5386
|
+
return step === void 0 || step === 0 ? null : steps - step + 1;
|
|
5387
|
+
};
|
|
5003
5388
|
return {
|
|
5004
5389
|
observe(chunk) {
|
|
5005
5390
|
if (chunk.type === "start-step") steps += 1;
|
|
@@ -5010,10 +5395,9 @@ function createShownSteps() {
|
|
|
5010
5395
|
}
|
|
5011
5396
|
},
|
|
5012
5397
|
/** Сколько шагов стереть с конца, чтобы убрать шаг последнего текста; `null` — текста на экране нет. */
|
|
5013
|
-
resetsToLastText()
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
}
|
|
5398
|
+
resetsToLastText: () => resetsToText(0),
|
|
5399
|
+
/** То же для текста `fromEnd`-го с конца (0 — последний); `null` — такого текста на экране нет. */
|
|
5400
|
+
resetsToText
|
|
5017
5401
|
};
|
|
5018
5402
|
}
|
|
5019
5403
|
function replacementChunks(corrected, index, resets, providerId) {
|
|
@@ -5436,6 +5820,7 @@ async function freezeTurnContext(input, turn, base, place) {
|
|
|
5436
5820
|
channel: input.channel ?? "web",
|
|
5437
5821
|
audience: input.group ? "group" : "private",
|
|
5438
5822
|
...input.staff && { staff: structuredClone(input.staff) },
|
|
5823
|
+
...turn.viewer && { viewer: turn.viewer },
|
|
5439
5824
|
modelId: input.modelId,
|
|
5440
5825
|
providerId: input.providerId
|
|
5441
5826
|
}
|
|
@@ -5478,6 +5863,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5478
5863
|
const externalContent = createExternalContentWatch();
|
|
5479
5864
|
const emitShown = (chunk) => {
|
|
5480
5865
|
shownSteps?.observe(chunk);
|
|
5866
|
+
screenSteps.observe(chunk);
|
|
5481
5867
|
emitRaw(chunk);
|
|
5482
5868
|
if (turnInput.liveDemos && externalContent.observe(chunk)) emitRaw(externalContentChunk());
|
|
5483
5869
|
};
|
|
@@ -5485,6 +5871,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5485
5871
|
const seenTexts = replyChecks && createSeenTexts();
|
|
5486
5872
|
const heldText = replyChecks && (replyChecks.web === "hold" || (turnInput.channel ?? "web") !== "web") ? createHeldReplyText() : void 0;
|
|
5487
5873
|
const shownSteps = replyChecks && createShownSteps();
|
|
5874
|
+
const screenSteps = createShownSteps();
|
|
5488
5875
|
const emitToClient = (chunk) => {
|
|
5489
5876
|
for (const passed of heldText ? heldText.pass(chunk) : [chunk]) emitShown(passed);
|
|
5490
5877
|
};
|
|
@@ -5519,6 +5906,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5519
5906
|
}
|
|
5520
5907
|
);
|
|
5521
5908
|
const turnUsage = createTurnUsage();
|
|
5909
|
+
const garbledTail = createGarbledTailGuard();
|
|
5522
5910
|
const turnBudget = createTurnBudget(input.turnRules, { catalog: input.catalog, modelId: input.modelId, turnUsage });
|
|
5523
5911
|
const messageUsage = () => {
|
|
5524
5912
|
const spent = turnUsage.total();
|
|
@@ -5577,10 +5965,12 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5577
5965
|
let errorKind;
|
|
5578
5966
|
let providerId = input.providerId;
|
|
5579
5967
|
let answeredProviderId = input.providerId;
|
|
5968
|
+
let viewer;
|
|
5580
5969
|
try {
|
|
5581
5970
|
const { history: historySource } = input;
|
|
5582
5971
|
const fullHistory = typeof historySource === "function" ? await historySource() : historySource;
|
|
5583
5972
|
const branchState = input.appState ?? foldAppState(fullHistory);
|
|
5973
|
+
viewer = input.viewer && await resolveAgentViewer(input.viewer, viewerInput({ ...input, audience: input.group ? "group" : "private" }));
|
|
5584
5974
|
const prepared = input.prepareTurn && await input.prepareTurn({ state: branchState, abortSignal: input.abortSignal });
|
|
5585
5975
|
const turnNotices = prepared && prepared.notices.length > 0 ? prepared.notices : void 0;
|
|
5586
5976
|
const stateBase = prepared?.state ?? branchState;
|
|
@@ -5596,10 +5986,11 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5596
5986
|
const frozenOfTurn = await frozenContextForTurn(input, {
|
|
5597
5987
|
history: answer.request().history,
|
|
5598
5988
|
answer: resume?.message ?? fullHistory.find((message) => message.id === messageId),
|
|
5599
|
-
state: publicAppState(appState.values())
|
|
5989
|
+
state: publicAppState(appState.values()),
|
|
5990
|
+
...viewer && { viewer }
|
|
5600
5991
|
});
|
|
5601
5992
|
const frozen = turnNotices && frozenOfTurn.fresh ? { ...frozenOfTurn, data: { ...frozenOfTurn.data, notices: turnNotices } } : frozenOfTurn;
|
|
5602
|
-
let resumeNote = resume && !resume.replay && !frozen.fresh ? await resumeNoteForTurn(input, { history: answer.request().history, answer: resume.message, state: publicAppState(appState.values()) }) : void 0;
|
|
5993
|
+
let resumeNote = resume && !resume.replay && !frozen.fresh ? await resumeNoteForTurn(input, { history: answer.request().history, answer: resume.message, state: publicAppState(appState.values()), ...viewer && { viewer } }) : void 0;
|
|
5603
5994
|
if (resume && turnNotices && !frozen.fresh) {
|
|
5604
5995
|
const seen = latestFrozenContextOf(resume.message.parts);
|
|
5605
5996
|
if (seen) resumeNote = { ...resumeNote ?? { blocks: seen.blocks, sent: { changed: [], removed: [] }, base: messageId }, notices: turnNotices };
|
|
@@ -5629,7 +6020,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5629
6020
|
if (resume && !resume.replay) {
|
|
5630
6021
|
const continued = helpersToContinue(resume.message.parts);
|
|
5631
6022
|
if (continued.length > 0) {
|
|
5632
|
-
await continueHelpers(input, continued, { toolQueue, dataChanges, appEvents, chatClosure, appState, emit, logError });
|
|
6023
|
+
await continueHelpers(input, continued, { ...viewer && { viewer }, toolQueue, dataChanges, appEvents, chatClosure, appState, emit, logError });
|
|
5633
6024
|
const parts2 = await saver.snapshot();
|
|
5634
6025
|
answer.update(parts2, { modelId, providerId: answeredProviderId });
|
|
5635
6026
|
mainLoopWaits = input.abortSignal.aborted || pendingHumanInput(parts2, input.waitingWidgets).length > 0;
|
|
@@ -5671,6 +6062,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5671
6062
|
steps: { done: stepsDone, max: maxSteps, beforeResume: stepsBeforeResume },
|
|
5672
6063
|
historyRewritten,
|
|
5673
6064
|
frozenContext: frozen.data,
|
|
6065
|
+
...viewer && { viewer },
|
|
5674
6066
|
toolQueue,
|
|
5675
6067
|
endTurns,
|
|
5676
6068
|
browser,
|
|
@@ -5682,6 +6074,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5682
6074
|
emit,
|
|
5683
6075
|
turnUsage,
|
|
5684
6076
|
turnBudget,
|
|
6077
|
+
garbledTail,
|
|
5685
6078
|
// Есть куда перейти при молчании — у того же провайдера оно не повторяется.
|
|
5686
6079
|
repeatSilence: () => silenceTarget() === void 0,
|
|
5687
6080
|
...silenceSwitch.scope === "step" && switchedBySilence && {
|
|
@@ -5853,7 +6246,8 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5853
6246
|
}
|
|
5854
6247
|
} catch (error2) {
|
|
5855
6248
|
if (!input.abortSignal.aborted) {
|
|
5856
|
-
|
|
6249
|
+
if (error2 instanceof AgentViewerError) logError(`[agent.viewer] \u0447\u0430\u0442=${input.chatId} \u0445\u043E\u0434 \u043D\u0435 \u043D\u0430\u0447\u0430\u0442: ${error2.message}`);
|
|
6250
|
+
errorKind = error2 instanceof AgentViewerError ? "other" : classifyProviderError(error2, void 0);
|
|
5857
6251
|
errors = [{ type: "error", errorText: errorKind }];
|
|
5858
6252
|
}
|
|
5859
6253
|
}
|
|
@@ -5884,6 +6278,15 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5884
6278
|
const failed = input.abortSignal.aborted || errors.length > 0;
|
|
5885
6279
|
const closing = !failed && pendingHumanInput(savedParts, input.waitingWidgets).length > 0 && (closure !== void 0 || await chatClosedNow());
|
|
5886
6280
|
let parts = failed || closing ? closeHangingToolCalls(denyPendingApprovals(savedParts, INTERRUPTED_REASON, RESULT_UNKNOWN_REASON), { notStarted: toolQueue.notStarted }) : savedParts;
|
|
6281
|
+
const garbled = cutGarbledTail(garbledTail.forCut(parts));
|
|
6282
|
+
const droppedCalls = garbledTail.droppedCalls();
|
|
6283
|
+
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}`;
|
|
6284
|
+
if (garbled) {
|
|
6285
|
+
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
|
+
const cut = garbled.parts.filter((part) => !garbledTail.isDroppedCall(part));
|
|
6287
|
+
if (!takeover.signal.aborted) showCutAnswer(parts, cut, garbled.textIndex, { heldText, screenSteps, providerId: answeredProviderId, emit: emitShown });
|
|
6288
|
+
parts = cut;
|
|
6289
|
+
} else if (findGarbledTail(garbledTail.forCut(parts))) logError(`[\u0445\u0432\u043E\u0441\u0442 \u043D\u0435 \u043E\u0431\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] ${garbleLog}`);
|
|
5887
6290
|
const status = input.abortSignal.aborted ? "stopped" : errors.length > 0 ? "error" : pendingHumanInput(parts, input.waitingWidgets).length > 0 ? "awaiting_approval" : "complete";
|
|
5888
6291
|
if (endTurns.hasText()) logError(`[endTurn] \u0447\u0430\u0442=${input.chatId} \u0433\u043E\u0442\u043E\u0432\u044B\u0439 \u0442\u0435\u043A\u0441\u0442 \u043D\u0435 \u043F\u043E\u043A\u0430\u0437\u0430\u043D: \u0445\u043E\u0434 \u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u0441\u043E \u0441\u0442\u0430\u0442\u0443\u0441\u043E\u043C ${status}`);
|
|
5889
6292
|
const textIndex = lastTextIndex(parts);
|
|
@@ -6197,8 +6600,10 @@ async function openAttempt(input, attempt, attemptSignal) {
|
|
|
6197
6600
|
return true;
|
|
6198
6601
|
};
|
|
6199
6602
|
const { noteAfterApprovals, ...modelRequest } = request;
|
|
6603
|
+
attempt.garbledTail.reset();
|
|
6200
6604
|
const result = streamText({
|
|
6201
6605
|
...modelRequest,
|
|
6606
|
+
model: attempt.garbledTail.wrap(modelRequest.model),
|
|
6202
6607
|
...approval.toolApproval && { toolApproval: gateToolApproval(approval.toolApproval, () => toolsClosed) },
|
|
6203
6608
|
// Попытке — остаток шагов хода.
|
|
6204
6609
|
// Помощник встал на подтверждении человека — ход ждёт, модель основного агента его незаконченным не видит.
|
|
@@ -6206,6 +6611,7 @@ async function openAttempt(input, attempt, attemptSignal) {
|
|
|
6206
6611
|
// Инструмент кончил ход: без текста — стоп сразу; с текстом — ещё шаг подставной модели, его держит только помощник
|
|
6207
6612
|
// на подтверждении (ход ждёт человека). Потолок шагов и прочие стопы готовый текст не теряют.
|
|
6208
6613
|
stopWhen: async (options2) => {
|
|
6614
|
+
if (attempt.garbledTail.garbled()) return true;
|
|
6209
6615
|
if (attempt.endTurns.requested()) {
|
|
6210
6616
|
if (attempt.endTurns.hasText()) return helperAwaitsApproval(options2);
|
|
6211
6617
|
attempt.endTurns.stopWithoutText();
|
|
@@ -6462,6 +6868,7 @@ function turnToolsModel(input, found, turn) {
|
|
|
6462
6868
|
...input.subject && { subject: input.subject },
|
|
6463
6869
|
chat: { ...input.chatData && { data: input.chatData } },
|
|
6464
6870
|
...input.staff && { staff: input.staff },
|
|
6871
|
+
...turn.viewer && { viewer: turn.viewer },
|
|
6465
6872
|
...input.closeChat && { closeChat: turn.chatClosure.request },
|
|
6466
6873
|
// Инструменту — только чтение и запись: конец записи и служебные ключи — дело ядра.
|
|
6467
6874
|
state: { get: turn.appState.get, set: turn.appState.set, unset: turn.appState.unset }
|
|
@@ -6540,6 +6947,19 @@ function withLastUserPart(history, part) {
|
|
|
6540
6947
|
return history.map((message, position) => position === index ? { ...message, parts: [...message.parts, part] } : message);
|
|
6541
6948
|
}
|
|
6542
6949
|
var FINISH_RETRY_PAUSES_MS = [1e3, 3e3];
|
|
6950
|
+
function showCutAnswer(original, cut, anchor, screen) {
|
|
6951
|
+
if (anchor === -1) return;
|
|
6952
|
+
const textsAfter = original.slice(anchor + 1).filter((part) => part.type === "text").length;
|
|
6953
|
+
if (screen.heldText?.holdsText()) {
|
|
6954
|
+
if (textsAfter === 0 && lastTextIndex(cut) === anchor) {
|
|
6955
|
+
for (const chunk of screen.heldText.release(cut[anchor].text)) screen.emit(chunk);
|
|
6956
|
+
return;
|
|
6957
|
+
}
|
|
6958
|
+
for (const chunk of screen.heldText.release()) screen.emit(chunk);
|
|
6959
|
+
}
|
|
6960
|
+
const resets = screen.screenSteps.resetsToText(textsAfter);
|
|
6961
|
+
for (const chunk of resets && replacementChunks(cut, anchor, resets, screen.providerId) || []) screen.emit(chunk);
|
|
6962
|
+
}
|
|
6543
6963
|
async function finishWithRetries(storage, messageId, final, logError) {
|
|
6544
6964
|
for (let attempt = 0; ; attempt += 1) {
|
|
6545
6965
|
let failure;
|
|
@@ -6892,6 +7312,7 @@ function createChatEvents(logError = () => {
|
|
|
6892
7312
|
listeners.delete(listener);
|
|
6893
7313
|
};
|
|
6894
7314
|
},
|
|
7315
|
+
hasListeners: () => listeners.size > 0,
|
|
6895
7316
|
emit(event) {
|
|
6896
7317
|
for (const listener of [...listeners]) {
|
|
6897
7318
|
try {
|
|
@@ -6974,9 +7395,9 @@ function editAnswerParts(parts, edit, uneditable) {
|
|
|
6974
7395
|
return { ok: true, parts: textPlaced ? edited : [...edited, textPart] };
|
|
6975
7396
|
}
|
|
6976
7397
|
function toolCallIdOf(part) {
|
|
6977
|
-
return
|
|
7398
|
+
return isToolPart7(part) ? part.toolCallId : void 0;
|
|
6978
7399
|
}
|
|
6979
|
-
var
|
|
7400
|
+
var isToolPart7 = (part) => part.type.startsWith("tool-") || part.type === "dynamic-tool";
|
|
6980
7401
|
function toolNameOf2(part) {
|
|
6981
7402
|
return part.type === "dynamic-tool" ? part.toolName : part.type.slice("tool-".length);
|
|
6982
7403
|
}
|
|
@@ -7270,7 +7691,7 @@ var fireworksProvider = createFireworksProvider();
|
|
|
7270
7691
|
|
|
7271
7692
|
// src/models/providers/openrouter.ts
|
|
7272
7693
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
7273
|
-
import { wrapLanguageModel as
|
|
7694
|
+
import { wrapLanguageModel as wrapLanguageModel5 } from "ai";
|
|
7274
7695
|
|
|
7275
7696
|
// src/models/providers/openrouter-cache-mark-limit.ts
|
|
7276
7697
|
function openrouterCacheMarkLimit(providerModelId) {
|
|
@@ -7520,7 +7941,7 @@ function createOpenRouterProvider(options2 = {}) {
|
|
|
7520
7941
|
createLanguageModel: (providerModelId, apiKey, requestOptions) => {
|
|
7521
7942
|
const markLimit = openrouterCacheMarkLimit(providerModelId);
|
|
7522
7943
|
const fetch = markLimit === void 0 ? requestOptions?.fetch : fetchWithCacheMarkLimit(requestOptions?.fetch, markLimit);
|
|
7523
|
-
return
|
|
7944
|
+
return wrapLanguageModel5({
|
|
7524
7945
|
model: createOpenRouter({ apiKey, fetch, ...connection }).chat(providerModelId, routingOptions),
|
|
7525
7946
|
middleware: [
|
|
7526
7947
|
openrouterRoutingMiddleware(routing),
|
|
@@ -9422,7 +9843,7 @@ function isPlainObject3(value) {
|
|
|
9422
9843
|
}
|
|
9423
9844
|
|
|
9424
9845
|
// src/memory/memory-after-answer.ts
|
|
9425
|
-
import { wrapLanguageModel as
|
|
9846
|
+
import { wrapLanguageModel as wrapLanguageModel6 } from "ai";
|
|
9426
9847
|
var DRAIN_WAIT_MS = 5e3;
|
|
9427
9848
|
function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turnObserver }) {
|
|
9428
9849
|
const running = /* @__PURE__ */ new Set();
|
|
@@ -9467,7 +9888,7 @@ function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turn
|
|
|
9467
9888
|
};
|
|
9468
9889
|
}
|
|
9469
9890
|
function withRetries(model, context) {
|
|
9470
|
-
return typeof model === "string" ? model :
|
|
9891
|
+
return typeof model === "string" ? model : wrapLanguageModel6({ model, middleware: createTransientRetryMiddleware(context) });
|
|
9471
9892
|
}
|
|
9472
9893
|
|
|
9473
9894
|
// src/conversation/to-agent-chat-message.ts
|
|
@@ -9717,7 +10138,7 @@ function createCompactionService(options2) {
|
|
|
9717
10138
|
chatId,
|
|
9718
10139
|
model,
|
|
9719
10140
|
instructions: await instructionsOf(chat),
|
|
9720
|
-
tools: resolveTools(model, chat, chatId, ownerId),
|
|
10141
|
+
tools: await resolveTools(model, chat, chatId, ownerId, branch, signal),
|
|
9721
10142
|
...options2.switchableTools && { switchableTools: options2.switchableTools },
|
|
9722
10143
|
...observation(ownerId, answerId),
|
|
9723
10144
|
history,
|
|
@@ -9764,19 +10185,26 @@ function createCompactionService(options2) {
|
|
|
9764
10185
|
function observation(ownerId, answerId) {
|
|
9765
10186
|
return options2.turnObserver ? { observe: { observer: options2.turnObserver, ownerId, messageId: answerId ?? null } } : {};
|
|
9766
10187
|
}
|
|
9767
|
-
function resolveTools(model, chat, chatId, ownerId) {
|
|
9768
|
-
const { tools } = options2.agent;
|
|
10188
|
+
async function resolveTools(model, chat, chatId, ownerId, branch, signal) {
|
|
10189
|
+
const { tools, viewer: viewerSource } = options2.agent;
|
|
9769
10190
|
if (typeof tools !== "function") return tools;
|
|
9770
10191
|
const subject = chat ? subjectOfChat(chat) : void 0;
|
|
10192
|
+
const chatData = chat ? chatDataOfInput(chat) : void 0;
|
|
10193
|
+
const { channel, group } = turnChannelOf(branch);
|
|
10194
|
+
const audience = group ? "group" : "private";
|
|
10195
|
+
const viewer = viewerSource && await resolveViewerOutsideTurn(viewerSource, { chatId, ownerId, messageId: "", ...chatData && { chatData }, ...subject && { subject }, channel, audience }, signal);
|
|
9771
10196
|
return tools({
|
|
9772
10197
|
profile: findModel(catalog, model.modelId, model.providerId).profile,
|
|
9773
10198
|
providerId: model.providerId,
|
|
9774
10199
|
chatId,
|
|
9775
10200
|
ownerId,
|
|
9776
10201
|
place: null,
|
|
10202
|
+
channel,
|
|
10203
|
+
audience,
|
|
9777
10204
|
notifyDataChanged: (change) => logError(`\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442 \u0441\u043E\u043E\u0431\u0449\u0438\u043B \u043E\u0431 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \xAB${String(change?.tag)}\xBB \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u0441\u0436\u0430\u0442\u0438\u044F \u0447\u0430\u0442\u0430 ${chatId}: \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E`),
|
|
9778
10205
|
...subject && { subject },
|
|
9779
|
-
chat: { ...
|
|
10206
|
+
chat: { ...chatData && { data: chatData } },
|
|
10207
|
+
...viewer && { viewer }
|
|
9780
10208
|
});
|
|
9781
10209
|
}
|
|
9782
10210
|
return {
|
|
@@ -9853,7 +10281,7 @@ function createCompactionService(options2) {
|
|
|
9853
10281
|
chatId,
|
|
9854
10282
|
model,
|
|
9855
10283
|
instructions: await instructionsOf(chat),
|
|
9856
|
-
tools: resolveTools(model, chat, chatId, ownerId),
|
|
10284
|
+
tools: await resolveTools(model, chat, chatId, ownerId, history, abortSignal),
|
|
9857
10285
|
...options2.switchableTools && { switchableTools: options2.switchableTools },
|
|
9858
10286
|
...observation(ownerId, answerId),
|
|
9859
10287
|
history: [...history, { id: `${chatId}-answer-steps`, role: "assistant", parts: steps }],
|
|
@@ -10182,6 +10610,7 @@ function createTurnGate(hooks) {
|
|
|
10182
10610
|
...input.text !== void 0 && { text: input.text },
|
|
10183
10611
|
...input.messageId !== void 0 && { messageId: input.messageId },
|
|
10184
10612
|
...input.burstMessageIds && { burstMessageIds: [...input.burstMessageIds] },
|
|
10613
|
+
...input.unattended && { unattended: true },
|
|
10185
10614
|
...abandonedOf(input)
|
|
10186
10615
|
});
|
|
10187
10616
|
if (!decision) return TURN;
|
|
@@ -10214,6 +10643,7 @@ function createTurnGate(hooks) {
|
|
|
10214
10643
|
...input.messageId !== void 0 && { messageId: input.messageId },
|
|
10215
10644
|
...input.text !== void 0 && { text: input.text },
|
|
10216
10645
|
...input.burstMessageIds && { burstMessageIds: [...input.burstMessageIds] },
|
|
10646
|
+
...input.unattended && { unattended: true },
|
|
10217
10647
|
reason,
|
|
10218
10648
|
...abandonedOf(input)
|
|
10219
10649
|
});
|
|
@@ -10604,6 +11034,7 @@ var InvalidWidgetAnswerError = class extends Error {
|
|
|
10604
11034
|
this.name = "InvalidWidgetAnswerError";
|
|
10605
11035
|
}
|
|
10606
11036
|
};
|
|
11037
|
+
var FAR_FUTURE_ISO = "9999-12-31T23:59:59.999Z";
|
|
10607
11038
|
var TurnEndedBeforeSubscribeError = class extends Error {
|
|
10608
11039
|
constructor(chatId) {
|
|
10609
11040
|
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`);
|
|
@@ -10847,7 +11278,9 @@ function createChatService(input) {
|
|
|
10847
11278
|
const steersInFlight = /* @__PURE__ */ new Map();
|
|
10848
11279
|
const startingByChat = /* @__PURE__ */ new Map();
|
|
10849
11280
|
const burstWaits = createBurstWaits();
|
|
10850
|
-
const
|
|
11281
|
+
const copyId = crypto.randomUUID();
|
|
11282
|
+
const turnLeases = createChatTurnLeases({ storage, holder: copyId, logError });
|
|
11283
|
+
const burstSweeps = /* @__PURE__ */ new Set();
|
|
10851
11284
|
const burstWrites = /* @__PURE__ */ new Map();
|
|
10852
11285
|
const chatToolQueues = /* @__PURE__ */ new Map();
|
|
10853
11286
|
let staleSweep = null;
|
|
@@ -11595,7 +12028,15 @@ function createChatService(input) {
|
|
|
11595
12028
|
steeringTurn = steering.begin(chatId, answerId);
|
|
11596
12029
|
if (stopsWhileStarting.delete(chatId)) registry.abort(chatId);
|
|
11597
12030
|
const turnEvent = { ownerId: chat.ownerId, chatId, messageId: answerId, trigger, channel };
|
|
11598
|
-
chatEvents.emit({
|
|
12031
|
+
chatEvents.emit({
|
|
12032
|
+
type: "turn-started",
|
|
12033
|
+
...turnEvent,
|
|
12034
|
+
...launch.deliveredByApp && { deliveredByApp: true },
|
|
12035
|
+
...launch.unattended && { unattended: true }
|
|
12036
|
+
});
|
|
12037
|
+
if (launch.unattended && chatEvents.hasListeners?.() === false) {
|
|
12038
|
+
logError(`\u0425\u043E\u0434 \u0431\u0435\u0437 \u0432\u044B\u0437\u043E\u0432\u0430 \u043D\u0430 \u043F\u0430\u0447\u043A\u0443 \u0447\u0430\u0442\u0430 ${chatId}: \u043D\u0430 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u0447\u0430\u0442\u0430 \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u043D \u2014 \u043E\u0442\u0432\u0435\u0442 \u0431\u0443\u0434\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438`);
|
|
12039
|
+
}
|
|
11599
12040
|
launch.onStarted?.({ answerId, finished });
|
|
11600
12041
|
liveAnswerIds.add(answerId);
|
|
11601
12042
|
const slot = startingSlots.get(chatId);
|
|
@@ -11713,6 +12154,7 @@ function createChatService(input) {
|
|
|
11713
12154
|
await assertStillOpen(chatId);
|
|
11714
12155
|
const earlier = await storage.getMessage(message.id);
|
|
11715
12156
|
if (earlier?.chatId === chatId && earlier.role === "user") return earlier;
|
|
12157
|
+
if (sendOptions?.answerAfterRestart) await savingPendingBurst(userId, chatId, message.id, now().getTime() + quietMs, sendOptions.page);
|
|
11716
12158
|
const written = await writingHumanMessage(userId, chatId, await currentBranch(chatId), message, text, files, sendOptions, { quietMs });
|
|
11717
12159
|
chatEvents.emit({ type: "message-added", ownerId: userId, chatId, messageId: written.id });
|
|
11718
12160
|
return written;
|
|
@@ -11731,11 +12173,11 @@ function createChatService(input) {
|
|
|
11731
12173
|
return true;
|
|
11732
12174
|
};
|
|
11733
12175
|
}
|
|
11734
|
-
async function answeringBurst(userId, chatId,
|
|
12176
|
+
async function answeringBurst(userId, chatId, questionId, page, unattended = false) {
|
|
11735
12177
|
const otherCopy = waitingForOtherCopy(chatId);
|
|
11736
12178
|
for (; ; ) {
|
|
11737
12179
|
if (closing) throw new ChatBusyError(chatId);
|
|
11738
|
-
const verdict = burstVerdict(await currentBranch(chatId),
|
|
12180
|
+
const verdict = burstVerdict(await currentBranch(chatId), questionId, now().getTime());
|
|
11739
12181
|
if (verdict.kind === "skip") return null;
|
|
11740
12182
|
if (verdict.kind === "wait") {
|
|
11741
12183
|
if (closing) throw new ChatBusyError(chatId);
|
|
@@ -11752,16 +12194,20 @@ function createChatService(input) {
|
|
|
11752
12194
|
const burstMessageIds = verdict.burst.map((message) => message.id);
|
|
11753
12195
|
try {
|
|
11754
12196
|
const stream = await startingTurn(
|
|
11755
|
-
{ userId, chat, action: "send", text: burstText(verdict.burst), messageId:
|
|
12197
|
+
{ userId, chat, action: "send", text: burstText(verdict.burst), messageId: questionId, burstMessageIds, ...unattended && { unattended: true } },
|
|
11756
12198
|
async (pass) => {
|
|
11757
12199
|
const branch = await currentBranch(chatId);
|
|
11758
|
-
if (burstVerdict(branch,
|
|
11759
|
-
if (pass.kind === "hold") return heldTurn(userId, chatId,
|
|
12200
|
+
if (burstVerdict(branch, questionId, now().getTime()).kind !== "start") return null;
|
|
12201
|
+
if (pass.kind === "hold") return unattended ? (heldTurn(userId, chatId, questionId), "unattended") : heldTurn(userId, chatId, questionId);
|
|
11760
12202
|
takeTurnSlot(userId, chatId);
|
|
11761
|
-
return startAnswer("send", userId, chatId, branch,
|
|
12203
|
+
if (!unattended) return startAnswer("send", userId, chatId, branch, page, void 0, void 0, replyOf(pass));
|
|
12204
|
+
const reply = replyOf(pass);
|
|
12205
|
+
await launchTurn(userId, chatId, branch, { parentId: branch.at(-1).id, page, action: "send", detached: true, unattended: true, ...reply !== void 0 && { reply } });
|
|
12206
|
+
return "unattended";
|
|
11762
12207
|
},
|
|
11763
12208
|
(result) => result === null ? "busy" : void 0
|
|
11764
12209
|
);
|
|
12210
|
+
if (stream === "unattended") return null;
|
|
11765
12211
|
return stream && viewerStream(userId, chat, {}, async () => stream);
|
|
11766
12212
|
} catch (error) {
|
|
11767
12213
|
if (error instanceof ChatBusyError && !closing && localTurnOf(chatId)) continue;
|
|
@@ -11770,6 +12216,85 @@ function createChatService(input) {
|
|
|
11770
12216
|
}
|
|
11771
12217
|
}
|
|
11772
12218
|
}
|
|
12219
|
+
const keepsPendingBursts = Boolean(storage.savePendingBurst && storage.takeAbandonedBursts && storage.renewPendingBurst && storage.removePendingBurst);
|
|
12220
|
+
async function savingPendingBurst(ownerId, chatId, messageId, dueAt, page) {
|
|
12221
|
+
if (!keepsPendingBursts) return;
|
|
12222
|
+
await storage.savePendingBurst({
|
|
12223
|
+
chatId,
|
|
12224
|
+
ownerId,
|
|
12225
|
+
messageId,
|
|
12226
|
+
dueAt: new Date(dueAt).toISOString(),
|
|
12227
|
+
holder: copyId,
|
|
12228
|
+
heldUntil: new Date(dueAt + BURST_HOLD_MS).toISOString(),
|
|
12229
|
+
...page && { page }
|
|
12230
|
+
});
|
|
12231
|
+
}
|
|
12232
|
+
function renewingBurstHold(chatId, messageId, dueAt) {
|
|
12233
|
+
const timer = setInterval(() => {
|
|
12234
|
+
const heldUntil = new Date(Math.max(now().getTime(), dueAt) + BURST_HOLD_MS).toISOString();
|
|
12235
|
+
storage.renewPendingBurst({ chatId, messageId, holder: copyId, heldUntil }).then(
|
|
12236
|
+
(held) => {
|
|
12237
|
+
if (!held) clearInterval(timer);
|
|
12238
|
+
},
|
|
12239
|
+
(error) => logError(`\u0423\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u043F\u0430\u0447\u043A\u0438 \u0447\u0430\u0442\u0430 ${chatId} \u043D\u0435 \u043F\u0440\u043E\u0434\u043B\u0438\u043B\u043E\u0441\u044C: ${error instanceof Error ? error.message : String(error)}`)
|
|
12240
|
+
);
|
|
12241
|
+
}, BURST_HOLD_RENEW_MS);
|
|
12242
|
+
timer.unref?.();
|
|
12243
|
+
return () => clearInterval(timer);
|
|
12244
|
+
}
|
|
12245
|
+
const burstWaitsForNextSweep = (error) => error instanceof ChatBusyError || error instanceof TooManyTurnsError;
|
|
12246
|
+
function answeringRecordedBurst(ownerId, chatId, questionId, page, burst) {
|
|
12247
|
+
const unattended = burst.kept && burst.unattended === true;
|
|
12248
|
+
if (!burst.kept || !keepsPendingBursts) return burstWaits.holding(chatId, () => answeringBurst(ownerId, chatId, questionId, page, unattended));
|
|
12249
|
+
const stopRenewing = renewingBurstHold(chatId, questionId, burst.dueAt);
|
|
12250
|
+
const answering = burstWaits.holding(chatId, () => answeringBurst(ownerId, chatId, questionId, page, unattended)).finally(stopRenewing);
|
|
12251
|
+
void answering.then(
|
|
12252
|
+
() => true,
|
|
12253
|
+
(error) => !burstWaitsForNextSweep(error)
|
|
12254
|
+
).then((decided) => {
|
|
12255
|
+
if (decided)
|
|
12256
|
+
return storage.removePendingBurst(chatId, questionId).catch((error) => logError(`\u0417\u0430\u043F\u0438\u0441\u044C \u043F\u0430\u0447\u043A\u0438 \u0447\u0430\u0442\u0430 ${chatId} \u043D\u0435 \u0441\u043D\u044F\u043B\u0430\u0441\u044C: ${error instanceof Error ? error.message : String(error)}`));
|
|
12257
|
+
});
|
|
12258
|
+
return answering;
|
|
12259
|
+
}
|
|
12260
|
+
let burstDecisions = 0;
|
|
12261
|
+
const burstDecisionWaiters = /* @__PURE__ */ new Set();
|
|
12262
|
+
const burstDecisionFreed = () => new Promise((resolve) => burstDecisionWaiters.add(resolve));
|
|
12263
|
+
function releasingBurstDecision() {
|
|
12264
|
+
burstDecisions--;
|
|
12265
|
+
for (const waiter of [...burstDecisionWaiters]) {
|
|
12266
|
+
burstDecisionWaiters.delete(waiter);
|
|
12267
|
+
waiter();
|
|
12268
|
+
}
|
|
12269
|
+
}
|
|
12270
|
+
function reportingAbandonedBurst(chatId, error) {
|
|
12271
|
+
if (burstWaitsForNextSweep(error)) return;
|
|
12272
|
+
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
|
+
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
|
+
}
|
|
12275
|
+
function sweepingBursts(take) {
|
|
12276
|
+
if (!keepsPendingBursts || closing) return Promise.resolve();
|
|
12277
|
+
const sweep = (async () => {
|
|
12278
|
+
for (; ; ) {
|
|
12279
|
+
if (closing) return;
|
|
12280
|
+
const free = BURST_SWEEP_LIMIT - burstDecisions;
|
|
12281
|
+
if (free <= 0) {
|
|
12282
|
+
await burstDecisionFreed();
|
|
12283
|
+
continue;
|
|
12284
|
+
}
|
|
12285
|
+
const heldUntil = new Date(now().getTime() + BURST_HOLD_MS).toISOString();
|
|
12286
|
+
const taken = await storage.takeAbandonedBursts({ ...take, holder: copyId, heldUntil, limit: free });
|
|
12287
|
+
if (taken.length === 0) return;
|
|
12288
|
+
for (const burst of taken) {
|
|
12289
|
+
if (burstWaits.has(burst.chatId)) continue;
|
|
12290
|
+
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);
|
|
12292
|
+
}
|
|
12293
|
+
}
|
|
12294
|
+
})().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));
|
|
12295
|
+
burstSweeps.add(sweep);
|
|
12296
|
+
return sweep;
|
|
12297
|
+
}
|
|
11773
12298
|
async function stoppingPendingBurst(userId, chatId) {
|
|
11774
12299
|
if (!burstWaits.has(chatId) && !isBurstPending(await currentBranch(chatId), now().getTime())) return false;
|
|
11775
12300
|
try {
|
|
@@ -12195,7 +12720,13 @@ function createChatService(input) {
|
|
|
12195
12720
|
}).catch(() => {
|
|
12196
12721
|
});
|
|
12197
12722
|
const question2 = await recording;
|
|
12198
|
-
const turn =
|
|
12723
|
+
const turn = answeringRecordedBurst(
|
|
12724
|
+
userId,
|
|
12725
|
+
chatId,
|
|
12726
|
+
question2.id,
|
|
12727
|
+
burstOptions?.page,
|
|
12728
|
+
burstOptions?.answerAfterRestart ? { kept: true, dueAt: Date.parse(question2.createdAt) + quietMs } : { kept: false }
|
|
12729
|
+
);
|
|
12199
12730
|
turn.catch(() => {
|
|
12200
12731
|
});
|
|
12201
12732
|
return { messageId: question2.id, turn };
|
|
@@ -12480,9 +13011,15 @@ function createChatService(input) {
|
|
|
12480
13011
|
},
|
|
12481
13012
|
async recoverInterruptedAnswers(recoverOptions) {
|
|
12482
13013
|
await afterTurn?.recoverPending(liveAnswerIds);
|
|
12483
|
-
const
|
|
13014
|
+
const soleServer = recoverOptions?.soleServer === true;
|
|
13015
|
+
const [count] = await Promise.all([recoverAnswersOnStart(soleServer), options2.background?.recover()]);
|
|
13016
|
+
await sweepingBursts(soleServer ? { abandonedBefore: FAR_FUTURE_ISO, exceptHolder: copyId } : { abandonedBefore: now().toISOString() });
|
|
12484
13017
|
return count;
|
|
12485
13018
|
},
|
|
13019
|
+
async tickBursts() {
|
|
13020
|
+
burstWaits.wakeAll();
|
|
13021
|
+
await sweepingBursts({ abandonedBefore: now().toISOString() });
|
|
13022
|
+
},
|
|
12486
13023
|
async closeChat({ ownerId, chatId, code, message }) {
|
|
12487
13024
|
const closed = normalizeChatClosure({ code, message });
|
|
12488
13025
|
await changeClosure(await ownChat(ownerId, chatId), closed);
|
|
@@ -12510,6 +13047,7 @@ function createChatService(input) {
|
|
|
12510
13047
|
burstWaits.cancelAll();
|
|
12511
13048
|
try {
|
|
12512
13049
|
await sweeping;
|
|
13050
|
+
while (burstSweeps.size > 0) await Promise.allSettled([...burstSweeps]);
|
|
12513
13051
|
while (startingWork.size > 0) await Promise.allSettled([...startingWork]);
|
|
12514
13052
|
const unfinished = await liveTurns.drain(waitForAnswersMs);
|
|
12515
13053
|
await burstWaits.settled();
|
|
@@ -12548,7 +13086,7 @@ function createChatService(input) {
|
|
|
12548
13086
|
const next = () => {
|
|
12549
13087
|
if (sweep.stopped) return;
|
|
12550
13088
|
sweep.timer = setTimeout(() => {
|
|
12551
|
-
sweep.running = Promise.all([recoverStaleAnswers(), afterTurn?.recoverPending(liveAnswerIds)]).then(() => void 0).catch(
|
|
13089
|
+
sweep.running = Promise.all([recoverStaleAnswers(), afterTurn?.recoverPending(liveAnswerIds), sweepingBursts({ abandonedBefore: now().toISOString() })]).then(() => void 0).catch(
|
|
12552
13090
|
(failure) => logError(`\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0445 \u043E\u0442\u0432\u0435\u0442\u043E\u0432 \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C: ${failure instanceof Error ? failure.message : "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430"}`)
|
|
12553
13091
|
).finally(() => {
|
|
12554
13092
|
sweep.running = void 0;
|
|
@@ -12632,6 +13170,9 @@ function createChatService(input) {
|
|
|
12632
13170
|
const choice = effectiveChoiceOfChat(options2.catalog, env, chat, defaultChoice);
|
|
12633
13171
|
const found = findModel(options2.catalog, choice.modelId, choice.providerId);
|
|
12634
13172
|
const subject = subjectOfChat(chat);
|
|
13173
|
+
const { channel, group } = turnChannelOf(await loadCurrentBranch(storage, chatId));
|
|
13174
|
+
const audience = group ? "group" : "private";
|
|
13175
|
+
const viewer = options2.agent.viewer && await resolveViewerOutsideTurn(options2.agent.viewer, { chatId, ownerId, messageId: "", chatData: chatDataOfInput(chat), subject, channel, audience });
|
|
12635
13176
|
const model = {
|
|
12636
13177
|
profile: found.profile,
|
|
12637
13178
|
providerId: found.provider.id,
|
|
@@ -12641,11 +13182,12 @@ function createChatService(input) {
|
|
|
12641
13182
|
// Вкладки у фоновой задачи нет: сообщать об изменённых данных некому.
|
|
12642
13183
|
notifyDataChanged: () => {
|
|
12643
13184
|
},
|
|
12644
|
-
channel
|
|
12645
|
-
audience
|
|
13185
|
+
channel,
|
|
13186
|
+
audience,
|
|
12646
13187
|
messageId: "",
|
|
12647
13188
|
...subject && { subject },
|
|
12648
|
-
chat: { ...chatDataOfInput(chat) && { data: chatDataOfInput(chat) } }
|
|
13189
|
+
chat: { ...chatDataOfInput(chat) && { data: chatDataOfInput(chat) } },
|
|
13190
|
+
...viewer && { viewer }
|
|
12649
13191
|
};
|
|
12650
13192
|
return helperToolsModel(model, {
|
|
12651
13193
|
storage,
|
|
@@ -12829,6 +13371,8 @@ export {
|
|
|
12829
13371
|
isCorrection,
|
|
12830
13372
|
BURST_PART_TYPE,
|
|
12831
13373
|
DEFAULT_BURST_QUIET_MS,
|
|
13374
|
+
BURST_HOLD_MS,
|
|
13375
|
+
BURST_SWEEP_LIMIT,
|
|
12832
13376
|
isBurstMessage,
|
|
12833
13377
|
RELEASED_ANSWER_ALIVE_AT,
|
|
12834
13378
|
aliveAtByLocalClock,
|
|
@@ -12856,6 +13400,7 @@ export {
|
|
|
12856
13400
|
ChatClosedError,
|
|
12857
13401
|
isScreenPlaceInput,
|
|
12858
13402
|
isTabId,
|
|
13403
|
+
AgentViewerError,
|
|
12859
13404
|
runHelper,
|
|
12860
13405
|
MAX_SERVER_TURNS_IN_ROW,
|
|
12861
13406
|
createChatEvents,
|
|
@@ -12928,4 +13473,4 @@ export {
|
|
|
12928
13473
|
InvalidWidgetAnswerError,
|
|
12929
13474
|
createChatService
|
|
12930
13475
|
};
|
|
12931
|
-
//# sourceMappingURL=chunk-
|
|
13476
|
+
//# sourceMappingURL=chunk-BS2HDKZB.js.map
|