mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
🪆 fix: Rebase Activity Phase Bounds over Sparse Content (#14729)
The aggregator writes content parts at provider-source indexes, which can skip slots and leave holes in contentParts. Array.prototype.map preserves those holes and the Map constructor iterates them as undefined, so rebaseActivityPhaseBounds threw "Iterator value undefined is not an entry object" at the end of every run with sparse content — deterministic with parent phase summaries enabled, on both the completion and resume paths. Build the identity map with an index loop that skips nullish slots. Holes must stay out of the map: one undefined key would falsely match every hole in previousParts as a retained part and corrupt the rebased bound.
This commit is contained in:
parent
a3cec67e08
commit
09cbd54f48
2 changed files with 36 additions and 1 deletions
|
|
@ -2276,13 +2276,24 @@ class AgentClient extends BaseClient {
|
|||
* so prepended skill cards cannot enter a phase and a filtered-away leading
|
||||
* reasoning part advances the bound to the first retained child.
|
||||
*
|
||||
* Both arrays may be sparse: the aggregator writes parts at provider-source
|
||||
* indexes, which can skip slots. Holes must not enter the identity map — a
|
||||
* hole reads as `undefined`, and one `undefined` key would falsely match
|
||||
* every hole in `previousParts` as a retained part.
|
||||
*
|
||||
* @param {Array<import('librechat-data-provider').ContentPart | null | undefined>} previousParts
|
||||
*/
|
||||
rebaseActivityPhaseBounds(previousParts) {
|
||||
if (!Array.isArray(previousParts) || !Array.isArray(this.contentParts)) {
|
||||
return;
|
||||
}
|
||||
const retainedIndexes = new Map(this.contentParts.map((part, index) => [part, index]));
|
||||
const retainedIndexes = new Map();
|
||||
for (let index = 0; index < this.contentParts.length; index += 1) {
|
||||
const part = this.contentParts[index];
|
||||
if (part != null) {
|
||||
retainedIndexes.set(part, index);
|
||||
}
|
||||
}
|
||||
for (let markerIndex = 0; markerIndex < this.contentParts.length; markerIndex += 1) {
|
||||
const marker = this.contentParts[markerIndex];
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -186,6 +186,30 @@ describe('AgentClient - applyHideSequentialOutputsFilter', () => {
|
|||
expect(ctx.contentParts).toEqual([skillCard, activityTool, phase, final]);
|
||||
expect(phase.activity_start_index).toBe(1);
|
||||
});
|
||||
|
||||
it('rebases phase bounds over sparse content without treating holes as retained parts', () => {
|
||||
const reasoning = { type: ContentTypes.THINK, think: 'planning' };
|
||||
const toolCall = toolCallPart('tc-sparse');
|
||||
const phase = {
|
||||
type: ContentTypes.ACTIVITY_LABEL,
|
||||
activity_label: 'Searched for tools',
|
||||
activity_label_type: 'phase',
|
||||
activity_start_index: 1,
|
||||
};
|
||||
const final = textPart('answer');
|
||||
const contentParts = [];
|
||||
contentParts[0] = reasoning;
|
||||
contentParts[2] = toolCall;
|
||||
contentParts[3] = phase;
|
||||
contentParts[4] = final;
|
||||
const previousParts = [...contentParts];
|
||||
const ctx = { options: { agent: {} }, contentParts };
|
||||
|
||||
expect(() =>
|
||||
AgentClient.prototype.rebaseActivityPhaseBounds.call(ctx, previousParts),
|
||||
).not.toThrow();
|
||||
expect(phase.activity_start_index).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AgentClient - startup telemetry', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue