MoonshotAI / MoonshotAI/kimi-code

[Bug] 通用工具输出截断会拆开 UTF-16 surrogate pair

Open
#3,756 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

你运行的 Kimi Code 版本是?

源码复现,commit 46233953795c62051b433956db60c6c218d2cede(未使用已安装 CLI 复现)。发布前已同步上游 main 并复核相关源码。

你使用的是哪个开放平台/订阅?

不适用:不涉及登录/模型请求,源码隔离复现。

你使用的是哪个模型?

不适用:未调用模型。

你的电脑平台是?

uname -mprsLinux 6.18.38-Unraid x86_64 unknown;Node.js v26.5.1

你遇到了什么问题?

Bash/MCP 返回包含 emoji 的长日志;字符刚好跨越 head/tail、每行或 retained cap 边界时,模型预览出现孤立 UTF-16 surrogate,TextEncoder 落盘可变成替换字符。
通用 spill 服务直接 slice(0,4096)、slice(tailStart)、每行 slice 和 retained slice;accumulator 也直接 slice。Read 已有 surrogate 边界检查,不能据此误称通用服务也已修复。

复现步骤?
  1. checkout 46233953795c62051b433956db60c6c218d2cede,在仓库根目录执行。
  2. 将以下脚本保存到仓库外的 /tmp/kimi-repro-2.mjs,执行 node /tmp/kimi-repro-2.mjs。不需要登录或安装 monorepo 依赖。
  3. 本次实际观测:{ headWellFormed: false, lastCodeUnit: 'd83d' }
import fs from 'node:fs';
import vm from 'node:vm';
import { stripTypeScriptTypes } from 'node:module';
const read = p => fs.readFileSync(p, 'utf8');
const load = (source, extra = {}) => {
  const ctx = vm.createContext(extra);
  vm.runInContext(stripTypeScriptTypes(source), ctx);
  return ctx;
};
const source = read('packages/agent-core-v2/src/agent/toolResultTruncation/toolResultTruncationService.ts');
const fragment = source.slice(source.indexOf('function appendPreviewLines('), source.indexOf('function dropInlineSuffixLines('));
const ctx = load('const TOOL_RESULT_PREVIEW_HEAD_CHARS = 4096; const TOOL_RESULT_PREVIEW_TAIL_CHARS = 1024;' + fragment);
const lines = [];
ctx.appendPreviewLines(lines, 'a'.repeat(4095) + '😀' + 'b'.repeat(52000));
console.log({ headWellFormed: lines[2].isWellFormed(), lastCodeUnit: lines[2].charCodeAt(lines[2].length - 1).toString(16) });

复现边界:从当前源码摘取函数/方法执行,显式模拟依赖边界;不是完整 CLI 或仓库 Vitest 测试。

期望的行为是什么?
  • 合法 Unicode 输入经过通用裁切后不产生孤立 surrogate,前缀和后缀均保持完整码点。
  • 边界调整不超出现有字符预算,预览范围标注与实际内容一致。
  • 覆盖 head/tail 的 emoji 边界,并保持 ASCII 行为不变。
补充信息

本次执行验证聚焦 appendPreviewLines 的 head 边界;其余 slice 裁切点为源码审查结果,不宣称均已端到端复现。#3611 是 VS Code 导出 topic 截断;#3645 是 Read 分页的 Unicode 保护,且 Read 绕过通用 spill;#3233 是远程运行时的 spill 路径问题,均不同于此通用预览边界。

已检索当前 open/closed issues 和 PR:external editor/signal、surrogate/Unicode/truncation、MCP pagination/nextCursor/listTools 及对应源码符号。未发现与此具体路径和触发条件等价的记录;不据此保证绝无重复。

固定源码、对应测试与参考边界

只运行上述隔离复现,未运行仓库完整测试套件或 Codex 测试;源码引用用于定位与讨论,不代表已经实现修复。

Contribution
  • 我愿意自己提交修复此 bug 的 PR(请先等待维护者在本 issue 中批准)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/agent-core-v2/src/agent/toolResultTruncation/toolResultTruncationService.ts, focusing on the listed head, tail, line, and retained slicing points, then inspect packages/agent-core-v2/src/tool/output-accumulator.ts. Run the named toolResultTruncation and output-accumulator tests, adding coverage for emoji at each boundary; done means valid Unicode remains intact without changing ASCII behavior or exceeding the existing character budget.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
cli, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.