mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 20:54:33 +00:00
fix: narrow finalize boundary-drop guard (#27711) (thanks @scz2011)
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- TUI/stream assembly: preserve streamed text across real tool-boundary drops without keeping stale streamed text when non-text blocks appear only in the final payload. Landed from contributor PR #27711 by @scz2011. (#27674)
|
||||||
- Models/MiniMax auth header defaults: set `authHeader: true` for both onboarding-generated MiniMax API providers and implicit built-in MiniMax (`minimax`, `minimax-portal`) provider templates so first requests no longer fail with MiniMax `401 authentication_error` due to missing `Authorization` header. Landed from contributor PRs #27622 by @riccoyuanft and #27631 by @kevinWangSheng. (#27600, #15303)
|
- Models/MiniMax auth header defaults: set `authHeader: true` for both onboarding-generated MiniMax API providers and implicit built-in MiniMax (`minimax`, `minimax-portal`) provider templates so first requests no longer fail with MiniMax `401 authentication_error` due to missing `Authorization` header. Landed from contributor PRs #27622 by @riccoyuanft and #27631 by @kevinWangSheng. (#27600, #15303)
|
||||||
- Pi image-token usage: stop re-injecting history image blocks each turn, process image references from the current prompt only, and prune already-answered user-image blocks in stored history to prevent runaway token growth. (#27602)
|
- Pi image-token usage: stop re-injecting history image blocks each turn, process image references from the current prompt only, and prune already-answered user-image blocks in stored history to prevent runaway token growth. (#27602)
|
||||||
- BlueBubbles/SSRF: auto-allowlist the configured `serverUrl` hostname for attachment fetches so localhost/private-IP BlueBubbles setups are no longer false-blocked by default SSRF checks. Landed from contributor PR #27648 by @lailoo. (#27599) Thanks @taylorhou for reporting.
|
- BlueBubbles/SSRF: auto-allowlist the configured `serverUrl` hostname for attachment fetches so localhost/private-IP BlueBubbles setups are no longer false-blocked by default SSRF checks. Landed from contributor PR #27648 by @lailoo. (#27599) Thanks @taylorhou for reporting.
|
||||||
|
|||||||
@@ -152,6 +152,35 @@ describe("TuiStreamAssembler", () => {
|
|||||||
expect(finalText).toBe("Draft line 1");
|
expect(finalText).toBe("Draft line 1");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prefers final text when non-text blocks appear only in final payload", () => {
|
||||||
|
const assembler = new TuiStreamAssembler();
|
||||||
|
assembler.ingestDelta(
|
||||||
|
"run-5c",
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "Draft line 1" },
|
||||||
|
{ type: "text", text: "Draft line 2" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const finalText = assembler.finalize(
|
||||||
|
"run-5c",
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "tool_use", name: "search" },
|
||||||
|
{ type: "text", text: "Draft line 2" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(finalText).toBe("Draft line 2");
|
||||||
|
});
|
||||||
|
|
||||||
it("accepts richer final payload when it extends streamed text", () => {
|
it("accepts richer final payload when it extends streamed text", () => {
|
||||||
const assembler = new TuiStreamAssembler();
|
const assembler = new TuiStreamAssembler();
|
||||||
assembler.ingestDelta(
|
assembler.ingestDelta(
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ export class TuiStreamAssembler {
|
|||||||
state: RunStreamState,
|
state: RunStreamState,
|
||||||
message: unknown,
|
message: unknown,
|
||||||
showThinking: boolean,
|
showThinking: boolean,
|
||||||
opts?: { protectBoundaryDrops?: boolean },
|
opts?: {
|
||||||
|
protectBoundaryDrops?: boolean;
|
||||||
|
useIncomingNonTextForBoundaryDrops?: boolean;
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const thinkingText = extractThinkingFromMessage(message);
|
const thinkingText = extractThinkingFromMessage(message);
|
||||||
const contentText = extractContentFromMessage(message);
|
const contentText = extractContentFromMessage(message);
|
||||||
@@ -108,9 +111,11 @@ export class TuiStreamAssembler {
|
|||||||
}
|
}
|
||||||
if (contentText) {
|
if (contentText) {
|
||||||
const nextContentBlocks = textBlocks.length > 0 ? textBlocks : [contentText];
|
const nextContentBlocks = textBlocks.length > 0 ? textBlocks : [contentText];
|
||||||
|
const useIncomingNonTextForBoundaryDrops = opts?.useIncomingNonTextForBoundaryDrops !== false;
|
||||||
const shouldPreserveBoundaryDroppedText =
|
const shouldPreserveBoundaryDroppedText =
|
||||||
opts?.protectBoundaryDrops === true &&
|
opts?.protectBoundaryDrops === true &&
|
||||||
(state.sawNonTextContentBlocks || sawNonTextContentBlocks) &&
|
(state.sawNonTextContentBlocks ||
|
||||||
|
(useIncomingNonTextForBoundaryDrops && sawNonTextContentBlocks)) &&
|
||||||
isDroppedBoundaryTextBlockSubset({
|
isDroppedBoundaryTextBlockSubset({
|
||||||
streamedTextBlocks: state.contentBlocks,
|
streamedTextBlocks: state.contentBlocks,
|
||||||
finalTextBlocks: nextContentBlocks,
|
finalTextBlocks: nextContentBlocks,
|
||||||
@@ -151,7 +156,10 @@ export class TuiStreamAssembler {
|
|||||||
const streamedDisplayText = state.displayText;
|
const streamedDisplayText = state.displayText;
|
||||||
const streamedTextBlocks = [...state.contentBlocks];
|
const streamedTextBlocks = [...state.contentBlocks];
|
||||||
const streamedSawNonTextContentBlocks = state.sawNonTextContentBlocks;
|
const streamedSawNonTextContentBlocks = state.sawNonTextContentBlocks;
|
||||||
this.updateRunState(state, message, showThinking, { protectBoundaryDrops: true });
|
this.updateRunState(state, message, showThinking, {
|
||||||
|
protectBoundaryDrops: true,
|
||||||
|
useIncomingNonTextForBoundaryDrops: false,
|
||||||
|
});
|
||||||
const finalComposed = state.displayText;
|
const finalComposed = state.displayText;
|
||||||
const shouldKeepStreamedText =
|
const shouldKeepStreamedText =
|
||||||
streamedSawNonTextContentBlocks &&
|
streamedSawNonTextContentBlocks &&
|
||||||
|
|||||||
Reference in New Issue
Block a user