mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 12:10:45 +03:00
fix(core): preserve unicode in compaction truncation
This commit is contained in:
@@ -116,8 +116,19 @@ export interface Interface extends State.Transformable<Draft> {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
|
||||
|
||||
const truncate = (value: string) =>
|
||||
value.length <= TOOL_OUTPUT_MAX_CHARS ? value : `${value.slice(0, TOOL_OUTPUT_MAX_CHARS)}\n[truncated]`
|
||||
export const truncateToolOutput = (value: string) => {
|
||||
if (value.length <= TOOL_OUTPUT_MAX_CHARS) return value
|
||||
let end = 0
|
||||
for (let count = 0; count < TOOL_OUTPUT_MAX_CHARS && end < value.length; count++) {
|
||||
const code = value.charCodeAt(end)
|
||||
end +=
|
||||
code >= 0xd800 && code <= 0xdbff && value.charCodeAt(end + 1) >= 0xdc00 && value.charCodeAt(end + 1) <= 0xdfff
|
||||
? 2
|
||||
: 1
|
||||
}
|
||||
if (end === value.length) return value
|
||||
return `${value.slice(0, end)}\n[truncated]`
|
||||
}
|
||||
|
||||
export const serializeToolContent = (content: SessionMessage.ToolStateCompleted["content"]) =>
|
||||
content
|
||||
@@ -146,7 +157,7 @@ const serialize = (message: SessionMessage.Info) => {
|
||||
if (part.state.status === "completed")
|
||||
return [
|
||||
`[Assistant tool call]: ${part.name}(${input})`,
|
||||
`[Tool result]: ${truncate(serializeToolContent(part.state.content))}`,
|
||||
`[Tool result]: ${truncateToolOutput(serializeToolContent(part.state.content))}`,
|
||||
]
|
||||
if (part.state.status === "error")
|
||||
return [`[Assistant tool call]: ${part.name}(${input})`, `[Tool error]: ${part.state.error.message}`]
|
||||
@@ -157,7 +168,8 @@ const serialize = (message: SessionMessage.Info) => {
|
||||
if (message.type === "system") return `[System update]: ${message.text}`
|
||||
if (message.type === "synthetic") return `[Synthetic context]: ${message.text}`
|
||||
if (message.type === "skill") return `[Skill activated: ${message.name}]\n${message.text}`
|
||||
if (message.type === "shell") return `[Shell]: ${message.command}\n${truncate(message.output?.output ?? "")}`
|
||||
if (message.type === "shell")
|
||||
return `[Shell]: ${message.command}\n${truncateToolOutput(message.output?.output ?? "")}`
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,13 @@ test("compaction describes tool media without embedding base64", () => {
|
||||
expect(serialized).not.toContain(base64)
|
||||
})
|
||||
|
||||
test("compaction truncation does not split surrogate pairs", () => {
|
||||
const prefix = "a".repeat(1_999)
|
||||
|
||||
expect(SessionCompaction.truncateToolOutput(`${prefix}😀suffix`)).toBe(`${prefix}😀\n[truncated]`)
|
||||
expect(SessionCompaction.truncateToolOutput("😀".repeat(2_000))).toBe("😀".repeat(2_000))
|
||||
})
|
||||
|
||||
test("compaction prompt requires the checkpoint headings in order", () => {
|
||||
const prompt = SessionCompaction.buildPrompt({ context: ["Conversation history"] })
|
||||
expect(prompt.match(/^#{2,3} .+$/gm)).toEqual([
|
||||
|
||||
Reference in New Issue
Block a user