@ykurilov/agent-chat-core 0.56.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 +16 -0
- package/README.md +2 -0
- package/dist/access.js +2 -2
- package/dist/{chunk-7M7M7DAN.js → chunk-BS2HDKZB.js} +502 -28
- 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 +29 -3
- package/dist/index.js.map +1 -1
- package/dist/storage-contract.js +87 -5
- package/dist/storage-contract.js.map +1 -1
- 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/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 +18 -0
- package/dist/types/index.d.ts +2 -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-7M7M7DAN.js.map +0 -1
- package/dist/chunk-PWGI6NH6.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;
|
|
@@ -1718,6 +1718,9 @@ var DEFAULT_BURST_QUIET_MS = 3e3;
|
|
|
1718
1718
|
var MAX_BURST_QUIET_MS = 6e5;
|
|
1719
1719
|
var BURST_PENDING_GRACE_MS = 12e4;
|
|
1720
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;
|
|
1721
1724
|
function burstPart(quietMs, at) {
|
|
1722
1725
|
return { type: BURST_PART_TYPE, data: { quietMs, at } };
|
|
1723
1726
|
}
|
|
@@ -1826,6 +1829,9 @@ function createBurstWaits() {
|
|
|
1826
1829
|
wake(chatId) {
|
|
1827
1830
|
for (const entry of [...timers]) if (entry.chatId === chatId) entry.done();
|
|
1828
1831
|
},
|
|
1832
|
+
wakeAll() {
|
|
1833
|
+
for (const entry of [...timers]) entry.done();
|
|
1834
|
+
},
|
|
1829
1835
|
has: (chatId) => waiting.has(chatId),
|
|
1830
1836
|
async holding(chatId, work) {
|
|
1831
1837
|
enter(chatId);
|
|
@@ -2859,7 +2865,7 @@ function usageMinusJournaled(usage, journaled) {
|
|
|
2859
2865
|
}
|
|
2860
2866
|
|
|
2861
2867
|
// src/agent-run/build-model-request.ts
|
|
2862
|
-
import { convertToModelMessages, wrapLanguageModel as
|
|
2868
|
+
import { convertToModelMessages, wrapLanguageModel as wrapLanguageModel4 } from "ai";
|
|
2863
2869
|
|
|
2864
2870
|
// src/conversation/hanging-tool-calls.ts
|
|
2865
2871
|
var PRELIMINARY_OUTPUT_MAX_LENGTH = 2e3;
|
|
@@ -3535,6 +3541,37 @@ function withUnansweredMarked(messages) {
|
|
|
3535
3541
|
);
|
|
3536
3542
|
}
|
|
3537
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
|
+
|
|
3538
3575
|
// src/frozen-context/frozen-context-messages.ts
|
|
3539
3576
|
function frozenContextBase(history) {
|
|
3540
3577
|
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
@@ -3660,6 +3697,298 @@ function withoutUndelivered(history) {
|
|
|
3660
3697
|
return dropped.size === 0 ? history : history.filter((_, index) => !dropped.has(index));
|
|
3661
3698
|
}
|
|
3662
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
|
+
|
|
3663
3992
|
// src/agent-run/build-model-request.ts
|
|
3664
3993
|
async function buildModelRequest(input) {
|
|
3665
3994
|
const found = findModel(input.catalog, input.modelId, input.providerId);
|
|
@@ -3684,7 +4013,7 @@ async function buildModelRequest(input) {
|
|
|
3684
4013
|
});
|
|
3685
4014
|
const tools = input.tools ? sortToolsByName(input.tools) : void 0;
|
|
3686
4015
|
const sendable = input.switchableTools ? withSwitchedOffCallsAsText(sendableHistory(input.history), input.switchableTools, tools) : sendableHistory(input.history);
|
|
3687
|
-
const noted = withChannelNotes(withModeNotes(withBurstsJoined(withCorrectionsJoined(sendable)), input.modeNotes));
|
|
4016
|
+
const noted = withStaffRepliesJoined(withChannelNotes(withModeNotes(withBurstsJoined(withCorrectionsJoined(sendable)), input.modeNotes)));
|
|
3688
4017
|
const held = input.frozenContext ? holdNoteAfterApprovals(withFrozenContext(noted, input.frozenContext)) : { messages: noted };
|
|
3689
4018
|
const history = selectReasoningForModel(held.messages, {
|
|
3690
4019
|
modelId: profile.id,
|
|
@@ -3720,7 +4049,7 @@ async function buildModelRequest(input) {
|
|
|
3720
4049
|
};
|
|
3721
4050
|
}
|
|
3722
4051
|
function sendableHistory(history) {
|
|
3723
|
-
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) => {
|
|
3724
4053
|
const closed = message.role === "assistant" ? closeHangingToolCalls(message.parts) : message.parts;
|
|
3725
4054
|
const parts = withoutLoneSurrogatesInParts(closed);
|
|
3726
4055
|
return parts === message.parts ? message : { ...message, parts };
|
|
@@ -3768,7 +4097,7 @@ function withoutLoneSurrogatesDeep(value) {
|
|
|
3768
4097
|
}
|
|
3769
4098
|
function withRequestWatch(model, provider, watch) {
|
|
3770
4099
|
if (!watch || typeof model === "string") return model;
|
|
3771
|
-
return
|
|
4100
|
+
return wrapLanguageModel4({
|
|
3772
4101
|
model,
|
|
3773
4102
|
middleware: {
|
|
3774
4103
|
specificationVersion: "v4",
|
|
@@ -3788,7 +4117,7 @@ function withRequestWatch(model, provider, watch) {
|
|
|
3788
4117
|
});
|
|
3789
4118
|
}
|
|
3790
4119
|
function withTransientRetries(model, context) {
|
|
3791
|
-
return typeof model === "string" ? model :
|
|
4120
|
+
return typeof model === "string" ? model : wrapLanguageModel4({ model, middleware: createTransientRetryMiddleware(context) });
|
|
3792
4121
|
}
|
|
3793
4122
|
function withLastMessageMark(messages, options2) {
|
|
3794
4123
|
if (!options2 || messages.length === 0) return messages;
|
|
@@ -5052,6 +5381,10 @@ function withTextAt(parts, index, text) {
|
|
|
5052
5381
|
function createShownSteps() {
|
|
5053
5382
|
let steps = 0;
|
|
5054
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
|
+
};
|
|
5055
5388
|
return {
|
|
5056
5389
|
observe(chunk) {
|
|
5057
5390
|
if (chunk.type === "start-step") steps += 1;
|
|
@@ -5062,10 +5395,9 @@ function createShownSteps() {
|
|
|
5062
5395
|
}
|
|
5063
5396
|
},
|
|
5064
5397
|
/** Сколько шагов стереть с конца, чтобы убрать шаг последнего текста; `null` — текста на экране нет. */
|
|
5065
|
-
resetsToLastText()
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
}
|
|
5398
|
+
resetsToLastText: () => resetsToText(0),
|
|
5399
|
+
/** То же для текста `fromEnd`-го с конца (0 — последний); `null` — такого текста на экране нет. */
|
|
5400
|
+
resetsToText
|
|
5069
5401
|
};
|
|
5070
5402
|
}
|
|
5071
5403
|
function replacementChunks(corrected, index, resets, providerId) {
|
|
@@ -5531,6 +5863,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5531
5863
|
const externalContent = createExternalContentWatch();
|
|
5532
5864
|
const emitShown = (chunk) => {
|
|
5533
5865
|
shownSteps?.observe(chunk);
|
|
5866
|
+
screenSteps.observe(chunk);
|
|
5534
5867
|
emitRaw(chunk);
|
|
5535
5868
|
if (turnInput.liveDemos && externalContent.observe(chunk)) emitRaw(externalContentChunk());
|
|
5536
5869
|
};
|
|
@@ -5538,6 +5871,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5538
5871
|
const seenTexts = replyChecks && createSeenTexts();
|
|
5539
5872
|
const heldText = replyChecks && (replyChecks.web === "hold" || (turnInput.channel ?? "web") !== "web") ? createHeldReplyText() : void 0;
|
|
5540
5873
|
const shownSteps = replyChecks && createShownSteps();
|
|
5874
|
+
const screenSteps = createShownSteps();
|
|
5541
5875
|
const emitToClient = (chunk) => {
|
|
5542
5876
|
for (const passed of heldText ? heldText.pass(chunk) : [chunk]) emitShown(passed);
|
|
5543
5877
|
};
|
|
@@ -5572,6 +5906,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5572
5906
|
}
|
|
5573
5907
|
);
|
|
5574
5908
|
const turnUsage = createTurnUsage();
|
|
5909
|
+
const garbledTail = createGarbledTailGuard();
|
|
5575
5910
|
const turnBudget = createTurnBudget(input.turnRules, { catalog: input.catalog, modelId: input.modelId, turnUsage });
|
|
5576
5911
|
const messageUsage = () => {
|
|
5577
5912
|
const spent = turnUsage.total();
|
|
@@ -5739,6 +6074,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5739
6074
|
emit,
|
|
5740
6075
|
turnUsage,
|
|
5741
6076
|
turnBudget,
|
|
6077
|
+
garbledTail,
|
|
5742
6078
|
// Есть куда перейти при молчании — у того же провайдера оно не повторяется.
|
|
5743
6079
|
repeatSilence: () => silenceTarget() === void 0,
|
|
5744
6080
|
...silenceSwitch.scope === "step" && switchedBySilence && {
|
|
@@ -5942,6 +6278,15 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
5942
6278
|
const failed = input.abortSignal.aborted || errors.length > 0;
|
|
5943
6279
|
const closing = !failed && pendingHumanInput(savedParts, input.waitingWidgets).length > 0 && (closure !== void 0 || await chatClosedNow());
|
|
5944
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}`);
|
|
5945
6290
|
const status = input.abortSignal.aborted ? "stopped" : errors.length > 0 ? "error" : pendingHumanInput(parts, input.waitingWidgets).length > 0 ? "awaiting_approval" : "complete";
|
|
5946
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}`);
|
|
5947
6292
|
const textIndex = lastTextIndex(parts);
|
|
@@ -6255,8 +6600,10 @@ async function openAttempt(input, attempt, attemptSignal) {
|
|
|
6255
6600
|
return true;
|
|
6256
6601
|
};
|
|
6257
6602
|
const { noteAfterApprovals, ...modelRequest } = request;
|
|
6603
|
+
attempt.garbledTail.reset();
|
|
6258
6604
|
const result = streamText({
|
|
6259
6605
|
...modelRequest,
|
|
6606
|
+
model: attempt.garbledTail.wrap(modelRequest.model),
|
|
6260
6607
|
...approval.toolApproval && { toolApproval: gateToolApproval(approval.toolApproval, () => toolsClosed) },
|
|
6261
6608
|
// Попытке — остаток шагов хода.
|
|
6262
6609
|
// Помощник встал на подтверждении человека — ход ждёт, модель основного агента его незаконченным не видит.
|
|
@@ -6264,6 +6611,7 @@ async function openAttempt(input, attempt, attemptSignal) {
|
|
|
6264
6611
|
// Инструмент кончил ход: без текста — стоп сразу; с текстом — ещё шаг подставной модели, его держит только помощник
|
|
6265
6612
|
// на подтверждении (ход ждёт человека). Потолок шагов и прочие стопы готовый текст не теряют.
|
|
6266
6613
|
stopWhen: async (options2) => {
|
|
6614
|
+
if (attempt.garbledTail.garbled()) return true;
|
|
6267
6615
|
if (attempt.endTurns.requested()) {
|
|
6268
6616
|
if (attempt.endTurns.hasText()) return helperAwaitsApproval(options2);
|
|
6269
6617
|
attempt.endTurns.stopWithoutText();
|
|
@@ -6599,6 +6947,19 @@ function withLastUserPart(history, part) {
|
|
|
6599
6947
|
return history.map((message, position) => position === index ? { ...message, parts: [...message.parts, part] } : message);
|
|
6600
6948
|
}
|
|
6601
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
|
+
}
|
|
6602
6963
|
async function finishWithRetries(storage, messageId, final, logError) {
|
|
6603
6964
|
for (let attempt = 0; ; attempt += 1) {
|
|
6604
6965
|
let failure;
|
|
@@ -6951,6 +7312,7 @@ function createChatEvents(logError = () => {
|
|
|
6951
7312
|
listeners.delete(listener);
|
|
6952
7313
|
};
|
|
6953
7314
|
},
|
|
7315
|
+
hasListeners: () => listeners.size > 0,
|
|
6954
7316
|
emit(event) {
|
|
6955
7317
|
for (const listener of [...listeners]) {
|
|
6956
7318
|
try {
|
|
@@ -7033,9 +7395,9 @@ function editAnswerParts(parts, edit, uneditable) {
|
|
|
7033
7395
|
return { ok: true, parts: textPlaced ? edited : [...edited, textPart] };
|
|
7034
7396
|
}
|
|
7035
7397
|
function toolCallIdOf(part) {
|
|
7036
|
-
return
|
|
7398
|
+
return isToolPart7(part) ? part.toolCallId : void 0;
|
|
7037
7399
|
}
|
|
7038
|
-
var
|
|
7400
|
+
var isToolPart7 = (part) => part.type.startsWith("tool-") || part.type === "dynamic-tool";
|
|
7039
7401
|
function toolNameOf2(part) {
|
|
7040
7402
|
return part.type === "dynamic-tool" ? part.toolName : part.type.slice("tool-".length);
|
|
7041
7403
|
}
|
|
@@ -7329,7 +7691,7 @@ var fireworksProvider = createFireworksProvider();
|
|
|
7329
7691
|
|
|
7330
7692
|
// src/models/providers/openrouter.ts
|
|
7331
7693
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
7332
|
-
import { wrapLanguageModel as
|
|
7694
|
+
import { wrapLanguageModel as wrapLanguageModel5 } from "ai";
|
|
7333
7695
|
|
|
7334
7696
|
// src/models/providers/openrouter-cache-mark-limit.ts
|
|
7335
7697
|
function openrouterCacheMarkLimit(providerModelId) {
|
|
@@ -7579,7 +7941,7 @@ function createOpenRouterProvider(options2 = {}) {
|
|
|
7579
7941
|
createLanguageModel: (providerModelId, apiKey, requestOptions) => {
|
|
7580
7942
|
const markLimit = openrouterCacheMarkLimit(providerModelId);
|
|
7581
7943
|
const fetch = markLimit === void 0 ? requestOptions?.fetch : fetchWithCacheMarkLimit(requestOptions?.fetch, markLimit);
|
|
7582
|
-
return
|
|
7944
|
+
return wrapLanguageModel5({
|
|
7583
7945
|
model: createOpenRouter({ apiKey, fetch, ...connection }).chat(providerModelId, routingOptions),
|
|
7584
7946
|
middleware: [
|
|
7585
7947
|
openrouterRoutingMiddleware(routing),
|
|
@@ -9481,7 +9843,7 @@ function isPlainObject3(value) {
|
|
|
9481
9843
|
}
|
|
9482
9844
|
|
|
9483
9845
|
// src/memory/memory-after-answer.ts
|
|
9484
|
-
import { wrapLanguageModel as
|
|
9846
|
+
import { wrapLanguageModel as wrapLanguageModel6 } from "ai";
|
|
9485
9847
|
var DRAIN_WAIT_MS = 5e3;
|
|
9486
9848
|
function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turnObserver }) {
|
|
9487
9849
|
const running = /* @__PURE__ */ new Set();
|
|
@@ -9526,7 +9888,7 @@ function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turn
|
|
|
9526
9888
|
};
|
|
9527
9889
|
}
|
|
9528
9890
|
function withRetries(model, context) {
|
|
9529
|
-
return typeof model === "string" ? model :
|
|
9891
|
+
return typeof model === "string" ? model : wrapLanguageModel6({ model, middleware: createTransientRetryMiddleware(context) });
|
|
9530
9892
|
}
|
|
9531
9893
|
|
|
9532
9894
|
// src/conversation/to-agent-chat-message.ts
|
|
@@ -10248,6 +10610,7 @@ function createTurnGate(hooks) {
|
|
|
10248
10610
|
...input.text !== void 0 && { text: input.text },
|
|
10249
10611
|
...input.messageId !== void 0 && { messageId: input.messageId },
|
|
10250
10612
|
...input.burstMessageIds && { burstMessageIds: [...input.burstMessageIds] },
|
|
10613
|
+
...input.unattended && { unattended: true },
|
|
10251
10614
|
...abandonedOf(input)
|
|
10252
10615
|
});
|
|
10253
10616
|
if (!decision) return TURN;
|
|
@@ -10280,6 +10643,7 @@ function createTurnGate(hooks) {
|
|
|
10280
10643
|
...input.messageId !== void 0 && { messageId: input.messageId },
|
|
10281
10644
|
...input.text !== void 0 && { text: input.text },
|
|
10282
10645
|
...input.burstMessageIds && { burstMessageIds: [...input.burstMessageIds] },
|
|
10646
|
+
...input.unattended && { unattended: true },
|
|
10283
10647
|
reason,
|
|
10284
10648
|
...abandonedOf(input)
|
|
10285
10649
|
});
|
|
@@ -10670,6 +11034,7 @@ var InvalidWidgetAnswerError = class extends Error {
|
|
|
10670
11034
|
this.name = "InvalidWidgetAnswerError";
|
|
10671
11035
|
}
|
|
10672
11036
|
};
|
|
11037
|
+
var FAR_FUTURE_ISO = "9999-12-31T23:59:59.999Z";
|
|
10673
11038
|
var TurnEndedBeforeSubscribeError = class extends Error {
|
|
10674
11039
|
constructor(chatId) {
|
|
10675
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`);
|
|
@@ -10913,7 +11278,9 @@ function createChatService(input) {
|
|
|
10913
11278
|
const steersInFlight = /* @__PURE__ */ new Map();
|
|
10914
11279
|
const startingByChat = /* @__PURE__ */ new Map();
|
|
10915
11280
|
const burstWaits = createBurstWaits();
|
|
10916
|
-
const
|
|
11281
|
+
const copyId = crypto.randomUUID();
|
|
11282
|
+
const turnLeases = createChatTurnLeases({ storage, holder: copyId, logError });
|
|
11283
|
+
const burstSweeps = /* @__PURE__ */ new Set();
|
|
10917
11284
|
const burstWrites = /* @__PURE__ */ new Map();
|
|
10918
11285
|
const chatToolQueues = /* @__PURE__ */ new Map();
|
|
10919
11286
|
let staleSweep = null;
|
|
@@ -11661,7 +12028,15 @@ function createChatService(input) {
|
|
|
11661
12028
|
steeringTurn = steering.begin(chatId, answerId);
|
|
11662
12029
|
if (stopsWhileStarting.delete(chatId)) registry.abort(chatId);
|
|
11663
12030
|
const turnEvent = { ownerId: chat.ownerId, chatId, messageId: answerId, trigger, channel };
|
|
11664
|
-
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
|
+
}
|
|
11665
12040
|
launch.onStarted?.({ answerId, finished });
|
|
11666
12041
|
liveAnswerIds.add(answerId);
|
|
11667
12042
|
const slot = startingSlots.get(chatId);
|
|
@@ -11779,6 +12154,7 @@ function createChatService(input) {
|
|
|
11779
12154
|
await assertStillOpen(chatId);
|
|
11780
12155
|
const earlier = await storage.getMessage(message.id);
|
|
11781
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);
|
|
11782
12158
|
const written = await writingHumanMessage(userId, chatId, await currentBranch(chatId), message, text, files, sendOptions, { quietMs });
|
|
11783
12159
|
chatEvents.emit({ type: "message-added", ownerId: userId, chatId, messageId: written.id });
|
|
11784
12160
|
return written;
|
|
@@ -11797,11 +12173,11 @@ function createChatService(input) {
|
|
|
11797
12173
|
return true;
|
|
11798
12174
|
};
|
|
11799
12175
|
}
|
|
11800
|
-
async function answeringBurst(userId, chatId,
|
|
12176
|
+
async function answeringBurst(userId, chatId, questionId, page, unattended = false) {
|
|
11801
12177
|
const otherCopy = waitingForOtherCopy(chatId);
|
|
11802
12178
|
for (; ; ) {
|
|
11803
12179
|
if (closing) throw new ChatBusyError(chatId);
|
|
11804
|
-
const verdict = burstVerdict(await currentBranch(chatId),
|
|
12180
|
+
const verdict = burstVerdict(await currentBranch(chatId), questionId, now().getTime());
|
|
11805
12181
|
if (verdict.kind === "skip") return null;
|
|
11806
12182
|
if (verdict.kind === "wait") {
|
|
11807
12183
|
if (closing) throw new ChatBusyError(chatId);
|
|
@@ -11818,16 +12194,20 @@ function createChatService(input) {
|
|
|
11818
12194
|
const burstMessageIds = verdict.burst.map((message) => message.id);
|
|
11819
12195
|
try {
|
|
11820
12196
|
const stream = await startingTurn(
|
|
11821
|
-
{ userId, chat, action: "send", text: burstText(verdict.burst), messageId:
|
|
12197
|
+
{ userId, chat, action: "send", text: burstText(verdict.burst), messageId: questionId, burstMessageIds, ...unattended && { unattended: true } },
|
|
11822
12198
|
async (pass) => {
|
|
11823
12199
|
const branch = await currentBranch(chatId);
|
|
11824
|
-
if (burstVerdict(branch,
|
|
11825
|
-
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);
|
|
11826
12202
|
takeTurnSlot(userId, chatId);
|
|
11827
|
-
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";
|
|
11828
12207
|
},
|
|
11829
12208
|
(result) => result === null ? "busy" : void 0
|
|
11830
12209
|
);
|
|
12210
|
+
if (stream === "unattended") return null;
|
|
11831
12211
|
return stream && viewerStream(userId, chat, {}, async () => stream);
|
|
11832
12212
|
} catch (error) {
|
|
11833
12213
|
if (error instanceof ChatBusyError && !closing && localTurnOf(chatId)) continue;
|
|
@@ -11836,6 +12216,85 @@ function createChatService(input) {
|
|
|
11836
12216
|
}
|
|
11837
12217
|
}
|
|
11838
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
|
+
}
|
|
11839
12298
|
async function stoppingPendingBurst(userId, chatId) {
|
|
11840
12299
|
if (!burstWaits.has(chatId) && !isBurstPending(await currentBranch(chatId), now().getTime())) return false;
|
|
11841
12300
|
try {
|
|
@@ -12261,7 +12720,13 @@ function createChatService(input) {
|
|
|
12261
12720
|
}).catch(() => {
|
|
12262
12721
|
});
|
|
12263
12722
|
const question2 = await recording;
|
|
12264
|
-
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
|
+
);
|
|
12265
12730
|
turn.catch(() => {
|
|
12266
12731
|
});
|
|
12267
12732
|
return { messageId: question2.id, turn };
|
|
@@ -12546,9 +13011,15 @@ function createChatService(input) {
|
|
|
12546
13011
|
},
|
|
12547
13012
|
async recoverInterruptedAnswers(recoverOptions) {
|
|
12548
13013
|
await afterTurn?.recoverPending(liveAnswerIds);
|
|
12549
|
-
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() });
|
|
12550
13017
|
return count;
|
|
12551
13018
|
},
|
|
13019
|
+
async tickBursts() {
|
|
13020
|
+
burstWaits.wakeAll();
|
|
13021
|
+
await sweepingBursts({ abandonedBefore: now().toISOString() });
|
|
13022
|
+
},
|
|
12552
13023
|
async closeChat({ ownerId, chatId, code, message }) {
|
|
12553
13024
|
const closed = normalizeChatClosure({ code, message });
|
|
12554
13025
|
await changeClosure(await ownChat(ownerId, chatId), closed);
|
|
@@ -12576,6 +13047,7 @@ function createChatService(input) {
|
|
|
12576
13047
|
burstWaits.cancelAll();
|
|
12577
13048
|
try {
|
|
12578
13049
|
await sweeping;
|
|
13050
|
+
while (burstSweeps.size > 0) await Promise.allSettled([...burstSweeps]);
|
|
12579
13051
|
while (startingWork.size > 0) await Promise.allSettled([...startingWork]);
|
|
12580
13052
|
const unfinished = await liveTurns.drain(waitForAnswersMs);
|
|
12581
13053
|
await burstWaits.settled();
|
|
@@ -12614,7 +13086,7 @@ function createChatService(input) {
|
|
|
12614
13086
|
const next = () => {
|
|
12615
13087
|
if (sweep.stopped) return;
|
|
12616
13088
|
sweep.timer = setTimeout(() => {
|
|
12617
|
-
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(
|
|
12618
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"}`)
|
|
12619
13091
|
).finally(() => {
|
|
12620
13092
|
sweep.running = void 0;
|
|
@@ -12899,6 +13371,8 @@ export {
|
|
|
12899
13371
|
isCorrection,
|
|
12900
13372
|
BURST_PART_TYPE,
|
|
12901
13373
|
DEFAULT_BURST_QUIET_MS,
|
|
13374
|
+
BURST_HOLD_MS,
|
|
13375
|
+
BURST_SWEEP_LIMIT,
|
|
12902
13376
|
isBurstMessage,
|
|
12903
13377
|
RELEASED_ANSWER_ALIVE_AT,
|
|
12904
13378
|
aliveAtByLocalClock,
|
|
@@ -12999,4 +13473,4 @@ export {
|
|
|
12999
13473
|
InvalidWidgetAnswerError,
|
|
13000
13474
|
createChatService
|
|
13001
13475
|
};
|
|
13002
|
-
//# sourceMappingURL=chunk-
|
|
13476
|
+
//# sourceMappingURL=chunk-BS2HDKZB.js.map
|