@librechat/agents 3.3.7 → 3.3.9
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/dist/cjs/graphs/Graph.cjs +47 -13
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +56 -6
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestion.cjs +3 -2
- package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +18 -48
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +174 -29
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseConfig.cjs +12 -0
- package/dist/cjs/langfuseConfig.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeContext.cjs +23 -2
- package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeScope.cjs +39 -8
- package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
- package/dist/cjs/langfuseSpanRegistry.cjs +95 -0
- package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -0
- package/dist/cjs/langfuseTraceShaping.cjs +121 -4
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +39 -15
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +25 -5
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +3 -3
- package/dist/cjs/llm/invoke.cjs +5 -5
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/main.cjs +10 -10
- package/dist/cjs/messages/format.cjs +124 -15
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/injected.cjs +10 -1
- package/dist/cjs/messages/injected.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +13 -1
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/prompts/activityLabel.cjs +51 -11
- package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
- package/dist/cjs/run.cjs +54 -24
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/messageSerialization.cjs +6 -0
- package/dist/cjs/session/messageSerialization.cjs.map +1 -1
- package/dist/cjs/stream.cjs +21 -10
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +60 -0
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +253 -24
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +1 -1
- package/dist/cjs/tools/search/tool.cjs +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
- package/dist/cjs/utils/index.cjs +2 -2
- package/dist/esm/graphs/Graph.mjs +48 -14
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +56 -6
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestion.mjs +3 -2
- package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +18 -48
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +176 -28
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseConfig.mjs +10 -1
- package/dist/esm/langfuseConfig.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeContext.mjs +21 -3
- package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeScope.mjs +39 -10
- package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
- package/dist/esm/langfuseSpanRegistry.mjs +91 -0
- package/dist/esm/langfuseSpanRegistry.mjs.map +1 -0
- package/dist/esm/langfuseTraceShaping.mjs +121 -4
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +39 -15
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +25 -5
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +2 -2
- package/dist/esm/llm/invoke.mjs +5 -5
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/main.mjs +8 -8
- package/dist/esm/messages/format.mjs +124 -15
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/injected.mjs +10 -1
- package/dist/esm/messages/injected.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +13 -1
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/prompts/activityLabel.mjs +51 -11
- package/dist/esm/prompts/activityLabel.mjs.map +1 -1
- package/dist/esm/run.mjs +54 -24
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/messageSerialization.mjs +6 -0
- package/dist/esm/session/messageSerialization.mjs.map +1 -1
- package/dist/esm/stream.mjs +21 -10
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +60 -0
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +254 -25
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +1 -1
- package/dist/esm/tools/search/tool.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
- package/dist/esm/utils/index.mjs +2 -2
- package/dist/types/graphs/Graph.d.ts +19 -0
- package/dist/types/hitl/askUserQuestion.d.ts +11 -1
- package/dist/types/langfuse.d.ts +16 -8
- package/dist/types/langfuseConfig.d.ts +6 -0
- package/dist/types/langfuseRuntimeContext.d.ts +27 -1
- package/dist/types/langfuseRuntimeScope.d.ts +17 -2
- package/dist/types/langfuseSpanRegistry.d.ts +17 -0
- package/dist/types/langfuseTraceShaping.d.ts +2 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -0
- package/dist/types/messages/format.d.ts +9 -8
- package/dist/types/prompts/activityLabel.d.ts +8 -1
- package/dist/types/run.d.ts +1 -1
- package/dist/types/session/types.d.ts +1 -0
- package/dist/types/tools/ToolNode.d.ts +7 -1
- package/dist/types/types/activityLabel.d.ts +8 -0
- package/dist/types/types/hitl.d.ts +8 -0
- package/dist/types/types/stream.d.ts +19 -0
- package/dist/types/types/tools.d.ts +30 -0
- package/package.json +7 -4
- package/src/__tests__/stream.eagerArgsDivergence.test.ts +753 -0
- package/src/graphs/Graph.ts +69 -20
- package/src/graphs/MultiAgentGraph.ts +74 -6
- package/src/graphs/__tests__/composition.smoke.test.ts +4 -0
- package/src/hitl/askUserQuestion.ts +14 -1
- package/src/instrumentation.ts +35 -77
- package/src/langfuse.ts +320 -43
- package/src/langfuseConfig.ts +24 -0
- package/src/langfuseRuntimeContext.ts +43 -1
- package/src/langfuseRuntimeScope.ts +94 -21
- package/src/langfuseSpanRegistry.ts +131 -0
- package/src/langfuseTraceShaping.ts +194 -7
- package/src/llm/anthropic/utils/message_inputs.ts +70 -19
- package/src/llm/anthropic/utils/streaming-tool-input.test.ts +186 -11
- package/src/llm/bedrock/utils/message_inputs.test.ts +120 -4
- package/src/llm/bedrock/utils/message_inputs.ts +32 -7
- package/src/messages/format.ts +222 -50
- package/src/messages/formatAgentMessages.test.ts +308 -6
- package/src/messages/injected.test.ts +18 -1
- package/src/messages/injected.ts +8 -1
- package/src/messages/prune.ts +12 -1
- package/src/prompts/activityLabel.ts +67 -2
- package/src/run.ts +86 -46
- package/src/scripts/activity-labels/captured.json +56 -0
- package/src/scripts/activity-labels/checks.cjs +205 -0
- package/src/scripts/activity-labels/corpus.cjs +473 -0
- package/src/scripts/activity-labels/report.cjs +203 -0
- package/src/scripts/activity-labels/rescore.cjs +102 -0
- package/src/scripts/activity-labels/run.ts +705 -0
- package/src/scripts/activity-labels/variants.ts +71 -0
- package/src/session/messageSerialization.ts +12 -1
- package/src/session/types.ts +1 -0
- package/src/specs/activity-label-prompt.test.ts +109 -0
- package/src/specs/agent-handoffs.test.ts +306 -0
- package/src/specs/langfuse-callbacks.test.ts +456 -0
- package/src/specs/langfuse-routing.integration.test.ts +138 -1
- package/src/specs/langfuse-span-registry.test.ts +70 -0
- package/src/specs/langfuse-trace-shaping.test.ts +294 -0
- package/src/specs/prune.test.ts +38 -1
- package/src/stream.ts +70 -6
- package/src/summarization/__tests__/node.test.ts +188 -0
- package/src/summarization/node.ts +72 -0
- package/src/tools/ToolNode.ts +400 -9
- package/src/tools/__tests__/ToolNode.invalidToolCalls.test.ts +757 -0
- package/src/tools/__tests__/hitl.test.ts +58 -0
- package/src/types/activityLabel.ts +8 -0
- package/src/types/hitl.ts +8 -0
- package/src/types/stream.ts +20 -0
- package/src/types/tools.ts +35 -1
package/src/messages/format.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
MessageContentComplex,
|
|
21
21
|
ReasoningContentText,
|
|
22
22
|
SummaryContentBlock,
|
|
23
|
+
SummaryCoverage,
|
|
23
24
|
ThinkingContentText,
|
|
24
25
|
ToolCallContent,
|
|
25
26
|
ToolResultContent,
|
|
@@ -1185,20 +1186,73 @@ function extractToolNamesFromSearchOutput(output: string): string[] {
|
|
|
1185
1186
|
return [];
|
|
1186
1187
|
}
|
|
1187
1188
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1189
|
+
/**
|
|
1190
|
+
* How far back a persisted summary reaches.
|
|
1191
|
+
*
|
|
1192
|
+
* `coverage` is authoritative: the block named the first source message that
|
|
1193
|
+
* compaction retained, so `messageIndex` is exclusive — everything before it is
|
|
1194
|
+
* covered and it survives whole. `positional` is the legacy reading for blocks
|
|
1195
|
+
* written before coverage existed (or whose anchor is no longer in the payload)
|
|
1196
|
+
* — the block's own location is the boundary, which is why it cannot
|
|
1197
|
+
* distinguish a retained tail from covered history.
|
|
1198
|
+
*/
|
|
1199
|
+
type SummaryBoundary =
|
|
1200
|
+
| {
|
|
1201
|
+
mode: 'coverage';
|
|
1202
|
+
messageIndex: number;
|
|
1203
|
+
text: string;
|
|
1204
|
+
tokenCount: number;
|
|
1205
|
+
}
|
|
1206
|
+
| {
|
|
1207
|
+
mode: 'positional';
|
|
1208
|
+
messageIndex: number;
|
|
1209
|
+
contentIndex: number;
|
|
1210
|
+
text: string;
|
|
1211
|
+
tokenCount: number;
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
type SummaryTokenAdjustment = {
|
|
1215
|
+
original: number;
|
|
1216
|
+
adjusted: number;
|
|
1217
|
+
remainingChars: number;
|
|
1218
|
+
totalChars: number;
|
|
1193
1219
|
};
|
|
1194
1220
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1221
|
+
type SummaryScan = {
|
|
1222
|
+
boundary?: SummaryBoundary;
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
function resolveCoverageIndex(
|
|
1226
|
+
coverage: SummaryCoverage | undefined,
|
|
1227
|
+
indexBySourceId: Map<string, number>,
|
|
1228
|
+
summaryMessageIndex: number
|
|
1229
|
+
): number | undefined {
|
|
1230
|
+
/** Persisted JSON, so the declared string type is not a runtime guarantee. */
|
|
1231
|
+
if (typeof coverage?.retainedFromMessageId !== 'string') {
|
|
1232
|
+
return undefined;
|
|
1233
|
+
}
|
|
1234
|
+
const retainedFromMessageId = coverage.retainedFromMessageId.trim();
|
|
1235
|
+
if (retainedFromMessageId === '') {
|
|
1236
|
+
return undefined;
|
|
1237
|
+
}
|
|
1238
|
+
const retainedIndex = indexBySourceId.get(retainedFromMessageId);
|
|
1239
|
+
return retainedIndex != null && retainedIndex <= summaryMessageIndex
|
|
1240
|
+
? retainedIndex
|
|
1241
|
+
: undefined;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function scanSummaryBlocks(payload: TPayload): SummaryScan {
|
|
1245
|
+
let boundary: SummaryBoundary | undefined;
|
|
1246
|
+
/** Filled as the scan advances, so a coverage lookup only ever resolves to a
|
|
1247
|
+
* message already passed — no second pass over the payload. */
|
|
1248
|
+
const indexBySourceId = new Map<string, number>();
|
|
1199
1249
|
|
|
1200
1250
|
for (let i = 0; i < payload.length; i++) {
|
|
1201
1251
|
const message = payload[i];
|
|
1252
|
+
const sourceMessageId = getSourceMessageId(message);
|
|
1253
|
+
if (sourceMessageId != null) {
|
|
1254
|
+
indexBySourceId.set(sourceMessageId, i);
|
|
1255
|
+
}
|
|
1202
1256
|
if (!Array.isArray(message.content)) {
|
|
1203
1257
|
continue;
|
|
1204
1258
|
}
|
|
@@ -1230,20 +1284,37 @@ function getLatestSummaryBoundary(
|
|
|
1230
1284
|
continue;
|
|
1231
1285
|
}
|
|
1232
1286
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1287
|
+
const tokenCount =
|
|
1288
|
+
typeof summaryPart.tokenCount === 'number' &&
|
|
1289
|
+
Number.isFinite(summaryPart.tokenCount)
|
|
1290
|
+
? summaryPart.tokenCount
|
|
1291
|
+
: 0;
|
|
1292
|
+
|
|
1293
|
+
const retainedIndex = resolveCoverageIndex(
|
|
1294
|
+
summaryPart.coverage,
|
|
1295
|
+
indexBySourceId,
|
|
1296
|
+
i
|
|
1297
|
+
);
|
|
1298
|
+
|
|
1299
|
+
boundary =
|
|
1300
|
+
retainedIndex != null
|
|
1301
|
+
? {
|
|
1302
|
+
mode: 'coverage',
|
|
1303
|
+
messageIndex: retainedIndex,
|
|
1304
|
+
text: summaryText,
|
|
1305
|
+
tokenCount,
|
|
1306
|
+
}
|
|
1307
|
+
: {
|
|
1308
|
+
mode: 'positional',
|
|
1309
|
+
messageIndex: i,
|
|
1310
|
+
contentIndex: j,
|
|
1311
|
+
text: summaryText,
|
|
1312
|
+
tokenCount,
|
|
1313
|
+
};
|
|
1243
1314
|
}
|
|
1244
1315
|
}
|
|
1245
1316
|
|
|
1246
|
-
return
|
|
1317
|
+
return { boundary };
|
|
1247
1318
|
}
|
|
1248
1319
|
|
|
1249
1320
|
function applySummaryBoundary(
|
|
@@ -1255,6 +1326,14 @@ function applySummaryBoundary(
|
|
|
1255
1326
|
return message;
|
|
1256
1327
|
}
|
|
1257
1328
|
|
|
1329
|
+
/** The boundary names the first retained message, so it is exclusive: that
|
|
1330
|
+
* message and everything after it — the recency tail included — stays
|
|
1331
|
+
* verbatim, and only genuinely covered history is dropped. Summary parts on
|
|
1332
|
+
* surviving messages are filtered later by `formatAssistantMessage`. */
|
|
1333
|
+
if (summaryBoundary.mode === 'coverage') {
|
|
1334
|
+
return messageIndex < summaryBoundary.messageIndex ? null : message;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1258
1337
|
if (messageIndex < summaryBoundary.messageIndex) {
|
|
1259
1338
|
return null;
|
|
1260
1339
|
}
|
|
@@ -1272,6 +1351,61 @@ function applySummaryBoundary(
|
|
|
1272
1351
|
};
|
|
1273
1352
|
}
|
|
1274
1353
|
|
|
1354
|
+
/**
|
|
1355
|
+
* Whether `formatAssistantMessage` filters this part out of the emitted message.
|
|
1356
|
+
* Such a part contributes no prompt tokens, so measuring it as zero characters
|
|
1357
|
+
* is accurate — it must not be mistaken for content the heuristic cannot see.
|
|
1358
|
+
*/
|
|
1359
|
+
function isDroppedByFormatting(
|
|
1360
|
+
part: MessageContentComplex | undefined
|
|
1361
|
+
): boolean {
|
|
1362
|
+
if (part == null) {
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
if (
|
|
1366
|
+
part.type === ContentTypes.SUMMARY ||
|
|
1367
|
+
part.type === ContentTypes.ERROR ||
|
|
1368
|
+
part.type === ContentTypes.AGENT_UPDATE ||
|
|
1369
|
+
part.type === ContentTypes.ACTIVITY_LABEL
|
|
1370
|
+
) {
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
return part.type === ContentTypes.TEXT && getTextContent(part).trim() === '';
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function measureValueChars(value: unknown): number {
|
|
1377
|
+
if (typeof value === 'string') {
|
|
1378
|
+
return value.length;
|
|
1379
|
+
}
|
|
1380
|
+
if (value == null || typeof value !== 'object') {
|
|
1381
|
+
return 0;
|
|
1382
|
+
}
|
|
1383
|
+
const measured = serializeStructuredValueBounded(value, 0).originalChars;
|
|
1384
|
+
return measured === Number.MAX_SAFE_INTEGER
|
|
1385
|
+
? HARD_MAX_TOOL_RESULT_CHARS
|
|
1386
|
+
: Math.min(measured, HARD_MAX_TOOL_RESULT_CHARS);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Whether a retained part's whole prompt cost is the text the char heuristic
|
|
1391
|
+
* reads, making it safe to represent in a character ratio.
|
|
1392
|
+
*
|
|
1393
|
+
* An allowlist, not a denylist. Media, resources, and tool calls carry cost that
|
|
1394
|
+
* is unrelated to their serialized length — a short image URL nested in
|
|
1395
|
+
* `tool_call.output` stands in for a fixed four-figure media charge — and
|
|
1396
|
+
* rejecting those case by case has repeatedly missed a nesting level. Listing
|
|
1397
|
+
* the two shapes whose characters `contentPartCharLength` actually reads makes
|
|
1398
|
+
* every other shape, present or future, ineligible by default: the ratio is
|
|
1399
|
+
* skipped and the entry keeps its original count, which prunes early rather than
|
|
1400
|
+
* exceeding the window.
|
|
1401
|
+
*/
|
|
1402
|
+
function isCharRatioEligible(part: MessageContentComplex | undefined): boolean {
|
|
1403
|
+
if (part == null) {
|
|
1404
|
+
return false;
|
|
1405
|
+
}
|
|
1406
|
+
return part.type === ContentTypes.TEXT || part.type === ContentTypes.THINKING;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1275
1409
|
function contentPartCharLength(part: MessageContentComplex): number {
|
|
1276
1410
|
const record = part as Record<string, unknown>;
|
|
1277
1411
|
let len = 0;
|
|
@@ -1281,15 +1415,15 @@ function contentPartCharLength(part: MessageContentComplex): number {
|
|
|
1281
1415
|
if (typeof record.thinking === 'string') {
|
|
1282
1416
|
len += record.thinking.length;
|
|
1283
1417
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1418
|
+
len += measureValueChars(record.input);
|
|
1419
|
+
/** Tool calls nest their payload a level down, so measuring only the
|
|
1420
|
+
* top-level fields scores an entire tool turn as zero characters. */
|
|
1421
|
+
const { tool_call: toolCall } = record;
|
|
1422
|
+
if (toolCall != null && typeof toolCall === 'object') {
|
|
1423
|
+
const call = toolCall as Record<string, unknown>;
|
|
1424
|
+
len += measureValueChars(call.name);
|
|
1425
|
+
len += measureValueChars(call.args);
|
|
1426
|
+
len += measureValueChars(call.output);
|
|
1293
1427
|
}
|
|
1294
1428
|
return len;
|
|
1295
1429
|
}
|
|
@@ -1340,14 +1474,9 @@ export const formatAgentMessages = (
|
|
|
1340
1474
|
/** Cross-run summary extracted from the payload. Should be forwarded to the
|
|
1341
1475
|
* agent run so it can be included in the system message via AgentContext. */
|
|
1342
1476
|
summary?: { text: string; tokenCount: number };
|
|
1343
|
-
/** When a summary boundary sliced content from a message, the token
|
|
1344
|
-
* was proportionally reduced. Returned so the caller can log it. */
|
|
1345
|
-
boundaryTokenAdjustment?:
|
|
1346
|
-
original: number;
|
|
1347
|
-
adjusted: number;
|
|
1348
|
-
remainingChars: number;
|
|
1349
|
-
totalChars: number;
|
|
1350
|
-
};
|
|
1477
|
+
/** When a positional summary boundary sliced content from a message, the token
|
|
1478
|
+
* count was proportionally reduced. Returned so the caller can log it. */
|
|
1479
|
+
boundaryTokenAdjustment?: SummaryTokenAdjustment;
|
|
1351
1480
|
} => {
|
|
1352
1481
|
const messages: Array<
|
|
1353
1482
|
| RoleBearingMessage<HumanMessage>
|
|
@@ -1387,17 +1516,10 @@ export const formatAgentMessages = (
|
|
|
1387
1516
|
};
|
|
1388
1517
|
// If indexTokenCountMap is provided, create a new map to track the updated indices
|
|
1389
1518
|
const updatedIndexTokenCountMap: Record<number, number> = {};
|
|
1390
|
-
let boundaryTokenAdjustment:
|
|
1391
|
-
| {
|
|
1392
|
-
original: number;
|
|
1393
|
-
adjusted: number;
|
|
1394
|
-
remainingChars: number;
|
|
1395
|
-
totalChars: number;
|
|
1396
|
-
}
|
|
1397
|
-
| undefined;
|
|
1519
|
+
let boundaryTokenAdjustment: SummaryTokenAdjustment | undefined;
|
|
1398
1520
|
// Keep track of the mapping from original payload indices to result indices
|
|
1399
1521
|
const indexMapping: Record<number, number[] | undefined> = {};
|
|
1400
|
-
const summaryBoundary =
|
|
1522
|
+
const { boundary: summaryBoundary } = scanSummaryBlocks(payload);
|
|
1401
1523
|
|
|
1402
1524
|
// Summary metadata is returned to the caller so it can be forwarded to the
|
|
1403
1525
|
// agent run and included in the single system message via AgentContext.
|
|
@@ -1723,8 +1845,24 @@ export const formatAgentMessages = (
|
|
|
1723
1845
|
continue;
|
|
1724
1846
|
}
|
|
1725
1847
|
|
|
1848
|
+
/**
|
|
1849
|
+
* Coverage mode deliberately leaves the count alone, even though the entry
|
|
1850
|
+
* holding the block is charged for summary text that `formatAssistantMessage`
|
|
1851
|
+
* filters out and `summary.tokenCount` accounts separately.
|
|
1852
|
+
*
|
|
1853
|
+
* Discounting it needs the summary's cost in the same units as
|
|
1854
|
+
* `indexTokenCountMap`, and that figure is not obtainable here: this
|
|
1855
|
+
* function receives no tokenizer, and a count recorded at write time is in
|
|
1856
|
+
* the writing run's units — `Run.create` derives its counter from the model
|
|
1857
|
+
* in play, and a consumer may supply its own — so a conversation continued
|
|
1858
|
+
* on a different model would subtract across encodings. Attempts to proxy
|
|
1859
|
+
* it (character ratios, provider identity) all under-count some shape,
|
|
1860
|
+
* which risks an over-context request; over-counting merely prunes early.
|
|
1861
|
+
* Fixing it properly means passing the reader a tokenizer, which is a
|
|
1862
|
+
* consumer-facing change and out of scope here.
|
|
1863
|
+
*/
|
|
1726
1864
|
if (
|
|
1727
|
-
summaryBoundary &&
|
|
1865
|
+
summaryBoundary?.mode === 'positional' &&
|
|
1728
1866
|
originalIndex === summaryBoundary.messageIndex &&
|
|
1729
1867
|
Array.isArray(payload[originalIndex].content)
|
|
1730
1868
|
) {
|
|
@@ -1734,14 +1872,48 @@ export const formatAgentMessages = (
|
|
|
1734
1872
|
if (contentIndex >= 0 && contentIndex < content.length - 1) {
|
|
1735
1873
|
let totalCharLen = 0;
|
|
1736
1874
|
let remainingCharLen = 0;
|
|
1875
|
+
/**
|
|
1876
|
+
* The ratio applies only when *every* part of the entry is one whose
|
|
1877
|
+
* token cost tracks its character length. A single ineligible part
|
|
1878
|
+
* cancels the discount, whichever side of the boundary it sits on.
|
|
1879
|
+
*
|
|
1880
|
+
* Both sides can break it, in opposite directions. A retained image has
|
|
1881
|
+
* its fixed cost scaled away, collapsing the entry. A removed base64
|
|
1882
|
+
* payload inflates the denominator — serializing to a huge length while
|
|
1883
|
+
* the counter charges a fixed estimate — dragging retained text below
|
|
1884
|
+
* its real cost. Either way the request can exceed the window.
|
|
1885
|
+
*
|
|
1886
|
+
* Telling a text-bearing tool payload from a media-bearing one means
|
|
1887
|
+
* recursing into arbitrary nested output, which has already missed a
|
|
1888
|
+
* level twice here. Cancelling instead keeps the original count: an
|
|
1889
|
+
* over-count that prunes early rather than overflowing. Entries of
|
|
1890
|
+
* plain text and reasoning — the common shape — still proportion.
|
|
1891
|
+
*/
|
|
1892
|
+
let everyRetainedPartMeasurable = true;
|
|
1737
1893
|
for (let p = 0; p < content.length; p++) {
|
|
1738
|
-
const
|
|
1894
|
+
const part = content[p];
|
|
1895
|
+
const retained = p > contentIndex;
|
|
1896
|
+
|
|
1897
|
+
if (isDroppedByFormatting(part)) {
|
|
1898
|
+
/** Removed summary text is real removed content: it is read as
|
|
1899
|
+
* plain text and belongs in the denominator. */
|
|
1900
|
+
if (!retained && part.type === ContentTypes.SUMMARY) {
|
|
1901
|
+
totalCharLen += contentPartCharLength(part);
|
|
1902
|
+
}
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
const charLen = contentPartCharLength(part);
|
|
1907
|
+
if (!isCharRatioEligible(part) || (retained && charLen === 0)) {
|
|
1908
|
+
everyRetainedPartMeasurable = false;
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1739
1911
|
totalCharLen += charLen;
|
|
1740
|
-
if (
|
|
1912
|
+
if (retained) {
|
|
1741
1913
|
remainingCharLen += charLen;
|
|
1742
1914
|
}
|
|
1743
1915
|
}
|
|
1744
|
-
if (totalCharLen > 0) {
|
|
1916
|
+
if (totalCharLen > 0 && everyRetainedPartMeasurable) {
|
|
1745
1917
|
const original = tokenCount;
|
|
1746
1918
|
tokenCount = Math.max(
|
|
1747
1919
|
1,
|