opencode-lore 0.2.3 → 0.2.5
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/package.json +1 -1
- package/src/gradient.ts +49 -15
- package/src/index.ts +15 -2
package/package.json
CHANGED
package/src/gradient.ts
CHANGED
|
@@ -926,6 +926,23 @@ export function estimateMessages(messages: MessageWithParts[]): number {
|
|
|
926
926
|
return messages.reduce((sum, m) => sum + estimateMessage(m), 0);
|
|
927
927
|
}
|
|
928
928
|
|
|
929
|
+
// Identify the current agentic turn: the last user message plus all subsequent
|
|
930
|
+
// assistant messages that share its ID as parentID. These messages form an atomic
|
|
931
|
+
// unit — the model must see all of them or it will lose track of its own prior
|
|
932
|
+
// tool calls and re-issue them in an infinite loop.
|
|
933
|
+
function currentTurnStart(messages: MessageWithParts[]): number {
|
|
934
|
+
// Find the last user message
|
|
935
|
+
let lastUserIdx = -1;
|
|
936
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
937
|
+
if (messages[i].info.role === "user") {
|
|
938
|
+
lastUserIdx = i;
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
if (lastUserIdx === -1) return 0; // no user message — treat all as current turn
|
|
943
|
+
return lastUserIdx;
|
|
944
|
+
}
|
|
945
|
+
|
|
929
946
|
function tryFit(input: {
|
|
930
947
|
messages: MessageWithParts[];
|
|
931
948
|
prefix: MessageWithParts[];
|
|
@@ -939,32 +956,49 @@ function tryFit(input: {
|
|
|
939
956
|
if (input.prefixTokens > input.distilledBudget && input.prefix.length > 0)
|
|
940
957
|
return null;
|
|
941
958
|
|
|
942
|
-
//
|
|
943
|
-
|
|
944
|
-
|
|
959
|
+
// Identify the current turn (last user message + all following assistant messages).
|
|
960
|
+
// These are always included — they must never be evicted. If they alone exceed the
|
|
961
|
+
// raw budget, escalate to the next layer (which strips tool outputs to reduce size).
|
|
962
|
+
const turnStart = currentTurnStart(input.messages);
|
|
963
|
+
const currentTurn = input.messages.slice(turnStart);
|
|
964
|
+
const currentTurnTokens = currentTurn.reduce((s, m) => s + estimateMessage(m), 0);
|
|
965
|
+
|
|
966
|
+
if (currentTurnTokens > input.rawBudget) {
|
|
967
|
+
// Current turn alone exceeds budget — can't fit even with everything else dropped.
|
|
968
|
+
// Signal failure so the caller escalates to the next layer (tool-output stripping).
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Walk backwards through older messages (before the current turn),
|
|
973
|
+
// filling the remaining budget after reserving space for the current turn.
|
|
974
|
+
const olderMessages = input.messages.slice(0, turnStart);
|
|
975
|
+
const remainingBudget = input.rawBudget - currentTurnTokens;
|
|
976
|
+
let olderTokens = 0;
|
|
977
|
+
let cutoff = olderMessages.length; // default: include none of the older messages
|
|
945
978
|
const protectedTurns = input.protectedTurns ?? 0;
|
|
946
|
-
let turns = 0;
|
|
947
979
|
|
|
948
|
-
for (let i =
|
|
949
|
-
const msg =
|
|
950
|
-
if (msg.info.role === "user") turns++;
|
|
980
|
+
for (let i = olderMessages.length - 1; i >= 0; i--) {
|
|
981
|
+
const msg = olderMessages[i];
|
|
951
982
|
const tokens = estimateMessage(msg);
|
|
952
|
-
if (
|
|
983
|
+
if (olderTokens + tokens > remainingBudget) {
|
|
953
984
|
cutoff = i + 1;
|
|
954
985
|
break;
|
|
955
986
|
}
|
|
956
|
-
|
|
987
|
+
olderTokens += tokens;
|
|
957
988
|
if (i === 0) cutoff = 0;
|
|
958
989
|
}
|
|
959
990
|
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
if (!raw.length) return null;
|
|
991
|
+
const rawMessages = [...olderMessages.slice(cutoff), ...currentTurn];
|
|
992
|
+
const rawTokens = olderTokens + currentTurnTokens;
|
|
963
993
|
|
|
964
|
-
// Apply system-reminder stripping + optional tool output stripping
|
|
965
|
-
|
|
966
|
-
|
|
994
|
+
// Apply system-reminder stripping + optional tool output stripping.
|
|
995
|
+
// The current turn (end of rawMessages) is always "protected" — never stripped.
|
|
996
|
+
const currentTurnSet = new Set(currentTurn.map((m) => m.info.id));
|
|
997
|
+
const processed = rawMessages.map((msg, idx) => {
|
|
998
|
+
const fromEnd = rawMessages.length - idx;
|
|
999
|
+
const isCurrentTurn = currentTurnSet.has(msg.info.id);
|
|
967
1000
|
const isProtected =
|
|
1001
|
+
isCurrentTurn ||
|
|
968
1002
|
input.strip === "none" ||
|
|
969
1003
|
(input.strip === "old-tools" && fromEnd <= protectedTurns * 2);
|
|
970
1004
|
const parts = isProtected
|
package/src/index.ts
CHANGED
|
@@ -395,12 +395,25 @@ export const LorePlugin: Plugin = async (ctx) => {
|
|
|
395
395
|
// so the append-only sequence stays intact for prompt caching.
|
|
396
396
|
if (result.layer > 0) {
|
|
397
397
|
// The API requires the conversation to end with a user message.
|
|
398
|
-
//
|
|
399
|
-
//
|
|
398
|
+
// Drop trailing non-user messages, but stop if we hit an assistant message
|
|
399
|
+
// with an in-progress (non-completed) tool call — dropping it would cause
|
|
400
|
+
// the model to lose its pending tool invocation and re-issue it in an
|
|
401
|
+
// infinite loop. A completed tool part is safe to drop; a pending one is not.
|
|
400
402
|
while (
|
|
401
403
|
result.messages.length > 0 &&
|
|
402
404
|
result.messages.at(-1)!.info.role !== "user"
|
|
403
405
|
) {
|
|
406
|
+
const last = result.messages.at(-1)!;
|
|
407
|
+
const hasPendingTool = last.parts.some(
|
|
408
|
+
(p) => p.type === "tool" && p.state.status !== "completed",
|
|
409
|
+
);
|
|
410
|
+
if (hasPendingTool) {
|
|
411
|
+
console.error(
|
|
412
|
+
"[lore] WARN: cannot drop trailing assistant message with pending tool call — may cause prefill error. id:",
|
|
413
|
+
last.info.id,
|
|
414
|
+
);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
404
417
|
const dropped = result.messages.pop()!;
|
|
405
418
|
console.error(
|
|
406
419
|
"[lore] WARN: dropping trailing",
|