mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-22 10:02:41 +00:00
fix(api): anchor retained phase partitions
This commit is contained in:
parent
69c117bb1c
commit
2d7597bd30
4 changed files with 64 additions and 3 deletions
|
|
@ -253,7 +253,8 @@ describe('groupActivityPhases', () => {
|
|||
type: ContentTypes.TEXT,
|
||||
text: 'Substantial result',
|
||||
} as TMessageContentParts;
|
||||
const content = [tool, tool, boundary, tool, first as never, tool, second as never];
|
||||
const final = { type: ContentTypes.TEXT, text: 'Final result' } as TMessageContentParts;
|
||||
const content = [tool, tool, boundary, tool, first as never, tool, final, second as never];
|
||||
|
||||
const segments = groupActivityPhases(content);
|
||||
|
||||
|
|
@ -272,9 +273,14 @@ describe('groupActivityPhases', () => {
|
|||
expect.objectContaining({
|
||||
type: 'phase',
|
||||
startIndex: 3,
|
||||
labelIndex: 6,
|
||||
labelIndex: 7,
|
||||
contentIndices: [3, 5],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'content',
|
||||
startIndex: 6,
|
||||
contentIndices: [6],
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ export function groupActivityPhases(
|
|||
* segment so a later phase marker can still claim its declared span. */
|
||||
if (start < cursor) {
|
||||
const recoveredIndices: number[] = [];
|
||||
const deferredTrailingIndices: number[] = [];
|
||||
for (let segmentIndex = segments.length - 1; segmentIndex >= 0; segmentIndex -= 1) {
|
||||
const segment = segments[segmentIndex];
|
||||
if (segment.type !== 'content') {
|
||||
|
|
@ -207,6 +208,8 @@ export function groupActivityPhases(
|
|||
const childIndex = segment.contentIndices[childPosition];
|
||||
if (childIndex >= start && childIndex < end) {
|
||||
recoveredIndices.push(childIndex);
|
||||
} else if (childIndex >= end) {
|
||||
deferredTrailingIndices.push(childIndex);
|
||||
} else {
|
||||
retainedContent.push(segment.content[childPosition]);
|
||||
retainedIndices.push(childIndex);
|
||||
|
|
@ -224,6 +227,10 @@ export function groupActivityPhases(
|
|||
for (const recoveredIndex of recoveredIndices) {
|
||||
append(phase, recoveredIndex);
|
||||
}
|
||||
deferredTrailingIndices.sort((a, b) => a - b);
|
||||
for (const trailingIndex of deferredTrailingIndices) {
|
||||
append(trailing, trailingIndex);
|
||||
}
|
||||
}
|
||||
while (definedPosition < definedIndices.length && definedIndices[definedPosition] < index) {
|
||||
const childIndex = definedIndices[definedPosition];
|
||||
|
|
|
|||
|
|
@ -1210,6 +1210,50 @@ describe('createActivityPhaseWiring', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('anchors a phase represented entirely by retained overflow bookkeeping', async () => {
|
||||
const parts: LooseContentPart[] = [
|
||||
{ type: ContentTypes.THINK, think: 'Earlier retained work' },
|
||||
{ type: ContentTypes.TEXT, text: substantialText('Persisted intermediate result.') },
|
||||
{ type: ContentTypes.TOOL_CALL, tool_call: { id: 'overflow-a' } },
|
||||
{ type: ContentTypes.TOOL_CALL, tool_call: { id: 'overflow-b' } },
|
||||
];
|
||||
const wiring = createActivityPhaseWiring({
|
||||
initialSnapshot: {
|
||||
version: 1,
|
||||
generated: 0,
|
||||
activityCount: 15,
|
||||
failedActivityCount: 0,
|
||||
partialActivityCount: 0,
|
||||
agentIds: [],
|
||||
activities: Array.from({ length: 13 }, () => ({
|
||||
startIndex: 0,
|
||||
status: 'success' as const,
|
||||
})),
|
||||
overflowActivityStartIndex: 30,
|
||||
overflowToolCallIds: ['overflow-a', 'overflow-b'],
|
||||
overflowBoundaryToolCallIds: ['overflow-a', 'overflow-b'],
|
||||
assistantContext: [],
|
||||
pendingReasoning: [],
|
||||
},
|
||||
getContentParts: () => parts,
|
||||
bumpIndexOffset: jest.fn(),
|
||||
emitLabelEvent: jest.fn(async () => undefined),
|
||||
trackPendingFill: jest.fn(),
|
||||
generatePhase: jest.fn(async () => ({ label: 'Completed the overflow phase' })),
|
||||
});
|
||||
|
||||
wiring.complete();
|
||||
await flushDetached();
|
||||
|
||||
expect(parts[5]).toMatchObject({
|
||||
activity_start_index: 2,
|
||||
activity_end_index: 5,
|
||||
activity_count: 2,
|
||||
});
|
||||
expect(Number.isFinite(parts[5]?.activity_start_index)).toBe(true);
|
||||
expect(Number.isFinite(parts[5]?.activity_end_index)).toBe(true);
|
||||
});
|
||||
|
||||
it('rebases a resumed overflow anchor after content compaction', async () => {
|
||||
const parts: LooseContentPart[] = Array.from({ length: 13 }, (_, index) => ({
|
||||
type: ContentTypes.TOOL_CALL,
|
||||
|
|
|
|||
|
|
@ -815,6 +815,7 @@ export function createActivityPhaseWiring(deps: ActivityPhaseHostDeps): Activity
|
|||
overflowCount > 0 &&
|
||||
(requestedEndIndex == null ||
|
||||
(overflowActivityStartIndex != null && overflowActivityStartIndex < requestedEndIndex));
|
||||
const closingOverflowStartIndex = overflowCloses ? overflowActivityStartIndex : undefined;
|
||||
const failedCount =
|
||||
snapshot.filter((activity) => activity.status === 'error').length +
|
||||
(overflowCloses ? overflowFailedCount : 0);
|
||||
|
|
@ -846,7 +847,10 @@ export function createActivityPhaseWiring(deps: ActivityPhaseHostDeps): Activity
|
|||
|
||||
generated += 1;
|
||||
const phaseIndex = generated - 1;
|
||||
let startIndex = Math.min(...snapshot.map((activity) => activity.startIndex));
|
||||
let startIndex =
|
||||
snapshot.length > 0
|
||||
? Math.min(...snapshot.map((activity) => activity.startIndex))
|
||||
: (closingOverflowStartIndex ?? 0);
|
||||
/** Pull leading commentary/reasoning into the parent card. A prior phase
|
||||
* marker or steer is the only hard UI boundary; plain text can be
|
||||
* intermediate context on providers that do not expose phase metadata. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue