From c8080a09f7d0d3443826fb7b6b5a90a038cdcf79 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Thu, 13 Aug 2026 07:44:37 -0400 Subject: [PATCH] fix(api): reanchor substantial text boundaries --- .../src/agents/activityPhases/runtime.spec.ts | 10 +++++-- .../api/src/agents/activityPhases/runtime.ts | 30 ++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/api/src/agents/activityPhases/runtime.spec.ts b/packages/api/src/agents/activityPhases/runtime.spec.ts index fe9490a78d..da1b640335 100644 --- a/packages/api/src/agents/activityPhases/runtime.spec.ts +++ b/packages/api/src/agents/activityPhases/runtime.spec.ts @@ -24,7 +24,9 @@ const batch = (id: string): PostToolBatchHookInput => ], }) as PostToolBatchHookInput; -const substantialText = (prefix: string): string => `${prefix} ${'x'.repeat(201)}`; +const SUBSTANTIAL_TEXT_CHARS = 200; +const substantialText = (prefix: string): string => + `${prefix} ${'x'.repeat(SUBSTANTIAL_TEXT_CHARS + 1)}`; async function flushDetached(): Promise { for (let i = 0; i < 4; i += 1) { @@ -40,7 +42,7 @@ describe('createActivityPhaseWiring', () => { const generatePhase = jest.fn(async () => ({ label: 'Resolved the release compatibility' })); const wiring = createActivityPhaseWiring({ getContentParts: () => parts, - getStepIndex: (stepId) => (stepId === 'final-step' ? 2 : undefined), + getStepIndex: (stepId) => (stepId === 'final-step' ? 1 : undefined), bumpIndexOffset: jest.fn(), emitLabelEvent, trackPendingFill: jest.fn(), @@ -96,7 +98,9 @@ describe('createActivityPhaseWiring', () => { GraphEvents.ON_MESSAGE_DELTA, { id: 'final-step', - delta: { content: { type: ContentTypes.TEXT, text: 'A'.repeat(201) } }, + delta: { + content: { type: ContentTypes.TEXT, text: 'A'.repeat(SUBSTANTIAL_TEXT_CHARS + 1) }, + }, }, undefined, undefined, diff --git a/packages/api/src/agents/activityPhases/runtime.ts b/packages/api/src/agents/activityPhases/runtime.ts index c0a1ec5dd0..0176e97b01 100644 --- a/packages/api/src/agents/activityPhases/runtime.ts +++ b/packages/api/src/agents/activityPhases/runtime.ts @@ -364,6 +364,27 @@ function findReasoningStart( return startIndex ?? findLastPartIndex(parts, ContentTypes.THINK); } +function findTextBoundary( + parts: ReadonlyArray, + text: string, + stepIndex?: number, +): number | undefined { + const needle = text.trim().slice(-REASONING_ANCHOR_CHARS); + const matches = (part: LooseContentPart | null | undefined) => + part?.type === ContentTypes.TEXT && textValue(part.text).includes(needle); + if (stepIndex != null && matches(parts[stepIndex])) { + return stepIndex; + } + const indices = definedPartIndices(parts); + for (let position = indices.length - 1; position >= 0; position -= 1) { + const index = indices[position]; + if (matches(parts[index])) { + return index; + } + } + return undefined; +} + /** Persists Open Responses text-phase metadata onto LibreChat text parts. * Installed for existing batch labels too, so commentary can supply intent * even when parent phase summaries are disabled. */ @@ -992,7 +1013,14 @@ export function createActivityPhaseWiring(deps: ActivityPhaseHostDeps): Activity contextEntry.text = `${contextEntry.text}${text}`.slice(-MAX_EXCERPT_CHARS); } const result = messageHandler.handle(event, data, metadata, graph); - const boundaryIndex = id ? deps.getStepIndex?.(id) : undefined; + const boundaryIndex = + id && contextEntry != null + ? findTextBoundary( + deps.getContentParts(), + contextEntry.text, + deps.getStepIndex?.(id), + ) + : undefined; if ( contextEntry != null && contextEntry.text.trim().length > SUBSTANTIAL_TEXT_CHARS &&