bbb8b1d536
* fix: don't drop pending tool-call-marker prefix on tool.started/run.done The `filterBridgeToolCallMarkupDelta` filter holds back any text that ends in a partial prefix of `[Calling tool:` (i.e. `[`, `[C`, `[Ca`, ..., `[Calling tool`) so it can decide whether the buffered chars are the start of a tool-call markup block to be hidden, or just regular text to be released by the next delta. The bug: that "release on next delta" assumption breaks at TWO points: 1. **On `tool.started`**: the next chunk for this assistant message is the tool call itself, NOT a follow-up text delta. Buffered chars sit there forever and nothing flushes them — they vanish silently from the user-visible stream. 2. **On run completion**: the code did `state.bridgePendingToolCallMarkup = undefined` directly, dropping any pending chars without forwarding them. Both cases produce the user-visible symptom of "abrupt cuts in text right before/after tool calls (terminal, read_file, write_file...)" — 1 to 13 characters disappear at exactly the boundary where the model was emitting natural prose that happened to end with `[`. The fix introduces `flushPendingToolCallMarkup(state)` and calls it: - In the `tool.started` branch BEFORE recording the tool call, so the buffered chars are appended to the open assistant message and emitted as a normal `message.delta` to the client. - At run-done BEFORE clearing the buffer, same flush path. This is a pure recovery patch — no change to the marker detection logic itself. If the buffer turns out to actually be a real `[Calling tool: ...]` marker that just hasn't completed yet, that case is still caught by the existing `markerIdx >= 0` branch in the filter on the next delta. The only behavioral change is that the "orphan" cases (text that ends with `[` but never becomes a marker) are no longer dropped. * fix bridge marker flush persistence --------- Co-authored-by: Paulo Cavallari <paulocavallari@users.noreply.github.com>
49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { filterBridgeToolCallMarkupDelta, flushPendingToolCallMarkup } from '../../packages/server/src/services/hermes/run-chat/bridge-delta'
|
|
|
|
describe('run-chat bridge delta filtering', () => {
|
|
it('keeps ordinary assistant text', () => {
|
|
const state = {}
|
|
|
|
expect(filterBridgeToolCallMarkupDelta(state, 'hello')).toBe('hello')
|
|
expect(filterBridgeToolCallMarkupDelta(state, ' world')).toBe(' world')
|
|
})
|
|
|
|
it('removes complete textual tool-call markup from bridge deltas', () => {
|
|
const state = {}
|
|
const delta = 'Before\n[Calling tool: terminal with arguments: {"cmd":"pwd"}]\nAfter'
|
|
|
|
expect(filterBridgeToolCallMarkupDelta(state, delta)).toBe('Before\nAfter')
|
|
})
|
|
|
|
it('removes tool-call markup split across multiple chunks', () => {
|
|
const state = {}
|
|
|
|
expect(filterBridgeToolCallMarkupDelta(state, '[Calling tool: terminal with arguments: {"cmd"')).toBe('')
|
|
expect(filterBridgeToolCallMarkupDelta(state, ':"pwd"}]\nDone')).toBe('Done')
|
|
})
|
|
|
|
it('keeps json arrays and brackets inside tool arguments from leaking', () => {
|
|
const state = {}
|
|
const delta = '[Calling tool: terminal with arguments: {"cmd":"printf \\"[x]\\"","items":["a","b"]}]\nDone'
|
|
|
|
expect(filterBridgeToolCallMarkupDelta(state, delta)).toBe('Done')
|
|
})
|
|
|
|
it('holds a partial marker suffix until the next chunk', () => {
|
|
const state = {}
|
|
|
|
expect(filterBridgeToolCallMarkupDelta(state, 'Text [Call')).toBe('Text ')
|
|
expect(filterBridgeToolCallMarkupDelta(state, 'ing tool: terminal with arguments: {}]\nDone')).toBe('Done')
|
|
})
|
|
|
|
it('flushes an orphan partial marker suffix when no text chunk follows', () => {
|
|
const state = {}
|
|
|
|
expect(filterBridgeToolCallMarkupDelta(state, 'Text [Call')).toBe('Text ')
|
|
expect(flushPendingToolCallMarkup(state)).toBe('[Call')
|
|
expect(flushPendingToolCallMarkup(state)).toBe('')
|
|
})
|
|
})
|