mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(codex): keep dynamic tool output UTF-16 safe (#102622)
* fix(codex-dynamic-tools): use truncateUtf16Safe for notice text truncation * fix(codex): keep dynamic tool text UTF-16 safe --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
parent
18fd6ee9e6
commit
e99b5deb7a
2 changed files with 42 additions and 4 deletions
|
|
@ -733,6 +733,41 @@ describe("createCodexDynamicToolBridge", () => {
|
|||
expect(text).toContain("rerun with narrower args");
|
||||
});
|
||||
|
||||
it("keeps a whole code point when dynamic tool text crosses the configured boundary", async () => {
|
||||
const maxChars = 180;
|
||||
const totalChars = 400;
|
||||
const noticeText = `...(OpenClaw truncated dynamic tool result: original ${totalChars} chars, showing ${maxChars}; rerun with narrower args.)`;
|
||||
const textBudget = maxChars - noticeText.length - 1;
|
||||
const prefix = "a".repeat(textBudget - 1);
|
||||
const longText = `${prefix}😀${"z".repeat(totalChars - prefix.length - 2)}`;
|
||||
const bridge = createCodexDynamicToolBridge({
|
||||
tools: [
|
||||
createTool({
|
||||
name: "large_lookup",
|
||||
execute: vi.fn(async () => textToolResult(longText)),
|
||||
}),
|
||||
],
|
||||
signal: new AbortController().signal,
|
||||
hookContext: {
|
||||
agentId: "main",
|
||||
config: {
|
||||
agents: { defaults: { contextLimits: { toolResultMaxChars: maxChars } } },
|
||||
} as never,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await bridge.handleToolCall({
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
callId: "call-1",
|
||||
namespace: null,
|
||||
tool: "large_lookup",
|
||||
arguments: {},
|
||||
});
|
||||
|
||||
expect(result.contentItems).toEqual([{ type: "inputText", text: `${prefix}\n${noticeText}` }]);
|
||||
});
|
||||
|
||||
it("honors normalized per-agent dynamic tool result caps", async () => {
|
||||
const bridge = createCodexDynamicToolBridge({
|
||||
tools: [
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import {
|
|||
asOptionalRecord as readRecord,
|
||||
isRecord,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import type { CodexDynamicToolsLoading } from "./config.js";
|
||||
import { invalidInlineImageText, sanitizeInlineImageDataUrl } from "./image-payload-sanitizer.js";
|
||||
import type {
|
||||
|
|
@ -1359,16 +1360,18 @@ function convertToolContents(
|
|||
continue;
|
||||
}
|
||||
if (notice.length >= maxChars) {
|
||||
output.push({ type: "inputText", text: noticeText.slice(0, maxChars) });
|
||||
output.push({ type: "inputText", text: truncateUtf16Safe(noticeText, maxChars) });
|
||||
appendedNotice = true;
|
||||
continue;
|
||||
}
|
||||
const sliceLength = Math.min(item.text.length, remainingTextBudget);
|
||||
remainingTextBudget -= sliceLength;
|
||||
const shouldAppendNotice = remainingTextBudget <= 0;
|
||||
const text = item.text.slice(0, sliceLength);
|
||||
const text = truncateUtf16Safe(item.text, sliceLength);
|
||||
if (shouldAppendNotice) {
|
||||
output.push({ type: "inputText", text: `${text.trimEnd()}${notice}`.slice(0, maxChars) });
|
||||
// The notice budget is reserved before slicing text, so the combined
|
||||
// result is already bounded without another boundary-sensitive cut.
|
||||
output.push({ type: "inputText", text: `${text.trimEnd()}${notice}` });
|
||||
appendedNotice = true;
|
||||
} else if (text.length > 0) {
|
||||
output.push({ type: "inputText", text });
|
||||
|
|
@ -1376,7 +1379,7 @@ function convertToolContents(
|
|||
}
|
||||
|
||||
if (!appendedNotice) {
|
||||
output.push({ type: "inputText", text: noticeText.slice(0, maxChars) });
|
||||
output.push({ type: "inputText", text: truncateUtf16Safe(noticeText, maxChars) });
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue