chore: make non-i18n code English-only (#117)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yaowei ZhengandClaude Opus 5 authored and GitHub committed 2026-07-29 18:03:41 +08:00
1 parent f63037866e
commit 4681135423
19 files changed
+113 -106

No files matched your search

+7 -7
View File
@@ -129,10 +129,10 @@ describe("describe_image (the text-only-model variant of read_image)", () => {
it("image + custom prompt is sent to the vision model in a single shot, its text comes back, the result carries no images", async () => {
await writeFile(path.join(tmp, "a.png"), PNG_1X1);
const { llm, calls } = fakeLLM("图里是一只企鹅。");
const { llm, calls } = fakeLLM("The image shows a penguin.");
const describer: VisionDescriberService = { modelId: "vis-1", createLLM: () => llm };
const { messages, result, text } = await run(
{ source: "a.png", prompt: "图里是什么动物?" },
{ source: "a.png", prompt: "What animal is in the image?" },
tmp,
describer,
);
@@ -143,17 +143,17 @@ describe("describe_image (the text-only-model variant of read_image)", () => {
(m) => m.payload as { type: string; text?: string; image_url?: string },
);
expect(payloads[0]!.type).toBe("text");
expect(payloads[0]!.text).toBe("图里是什么动物?");
expect(payloads[0]!.text).toBe("What animal is in the image?");
expect(payloads[1]!.type).toBe("image_url");
expect(payloads[1]!.image_url).toBe(`data:image/png;base64,${PNG_1X1.toString("base64")}`);
expect(text).toContain("described by vis-1");
expect(text).toContain("图里是一只企鹅。");
expect(text).toContain("The image shows a penguin.");
// Streaming forward: the header line and description deltas are emitted as separate chunks (not buffered as a whole), and the complete text is not forwarded again.
const outputs = messages.map((m) => (m.payload as { output?: string }).output ?? "");
expect(outputs.length).toBeGreaterThanOrEqual(3); // header + >=2 description delta chunks
expect(outputs[0]).toContain("described by vis-1");
expect(outputs.slice(1).join("")).toBe("图里是一只企鹅。");
expect(outputs.slice(1).join("")).toBe("The image shows a penguin.");
// Text-based description: the result carries no images (images never enter session history).
expect(result?.images).toBeUndefined();
expect(result?.stopReason).toBeUndefined(); // defaults to completed
@@ -200,7 +200,7 @@ describe("describe_image (the text-only-model variant of read_image)", () => {
it("Environment assembles the describe_image entry with the delegated-description implementation; definition matches the config", async () => {
await writeFile(path.join(tmp, "a.png"), PNG_1X1);
const { llm } = fakeLLM("代读结果");
const { llm } = fakeLLM("delegated description result");
const env = new Environment({
workspaceDir: tmp,
toolConfig: {
@@ -235,7 +235,7 @@ describe("describe_image (the text-only-model variant of read_image)", () => {
};
expect(complete.type).toBe("tool_call_output");
expect(complete.stop_reason).toBe("completed");
expect(complete.output).toContain("代读结果");
expect(complete.output).toContain("delegated description result");
expect(complete.images).toBeUndefined();
});
});
+18 -14
View File
@@ -79,19 +79,19 @@ describe("stripConversationMarkers (whole-message title cleaning)", () => {
// The skill-invocation block that wraps a first user message must not reach the title.
expect(
stripConversationMarkers(
"[use_skills]\nskills: penguin-sdk, web-design\n[/use_skills]\n做一个 RAG 应用",
"[use_skills]\nskills: penguin-sdk, web-design\n[/use_skills]\nBuild a RAG app",
),
).toBe("做一个 RAG 应用");
).toBe("Build a RAG app");
// Handoff and scheduled-task markers are stripped too; ordinary bracketed text stays.
expect(stripConversationMarkers("[handoff_from]data_analyst[/handoff_from]继续分析")).toBe(
"继续分析",
);
expect(
stripConversationMarkers("[handoff_from]data_analyst[/handoff_from]continue the analysis"),
).toBe("continue the analysis");
// The /model switch origin block (the new session's first message) must not leak into the title either.
expect(
stripConversationMarkers(
"[model_switch_from]\nsession: session-01\ntrace: /t/x_001.jsonl\n[/model_switch_from]\n继续这个任务",
"[model_switch_from]\nsession: session-01\ntrace: /t/x_001.jsonl\n[/model_switch_from]\ncontinue this task",
),
).toBe("继续这个任务");
).toBe("continue this task");
expect(stripConversationMarkers("render a <div> element")).toBe("render a <div> element");
expect(stripConversationMarkers("check the [config] section")).toBe(
"check the [config] section",
@@ -100,14 +100,18 @@ describe("stripConversationMarkers (whole-message title cleaning)", () => {
it("the old angle-bracket marker form is still stripped (material from old Traces)", () => {
expect(
stripConversationMarkers("<use_skills>\nskills: web-design\n</use_skills>\n做一个落地页"),
).toBe("做一个落地页");
expect(stripConversationMarkers("<handoff_from>data_analyst</handoff_from>继续分析")).toBe(
"继续分析",
);
stripConversationMarkers(
"<use_skills>\nskills: web-design\n</use_skills>\nBuild a landing page",
),
).toBe("Build a landing page");
expect(
stripConversationMarkers("<model_switch_from>session: s1</model_switch_from>继续这个任务"),
).toBe("继续这个任务");
stripConversationMarkers("<handoff_from>data_analyst</handoff_from>continue the analysis"),
).toBe("continue the analysis");
expect(
stripConversationMarkers(
"<model_switch_from>session: s1</model_switch_from>continue this task",
),
).toBe("continue this task");
});
});
+14 -14
View File
@@ -64,18 +64,18 @@ describe("session-title", () => {
const result = await generateTitleWithLLM(
fakeLLM(
[
thinkingMessage("想一下"), // thinking does not count
assistantText("「Tailwind 主题配置」。"),
thinkingMessage("thinking"), // thinking does not count
assistantText("「Tailwind theme setup」。"),
tokenUsage(emptyTokenCounts(), { cache_read: 1, cache_write: 2, output: 3, total: 6 }),
],
{ status: "completed" },
seen,
),
{ userText: "解释 @theme", assistantText: "好的……" },
{ userText: "explain @theme", assistantText: "sure thing." },
);
expect(result.title).toBe("Tailwind 主题配置");
expect(result.title).toBe("Tailwind theme setup");
expect(result.usage).toEqual({ cache_read: 1, cache_write: 2, output: 3, total: 6 });
expect(seen[0]).toBe(buildTitlePrompt("解释 @theme", "好的……"));
expect(seen[0]).toBe(buildTitlePrompt("explain @theme", "sure thing."));
expect(seen[0]).toContain("SAME language");
});
@@ -105,23 +105,23 @@ describe("session-title", () => {
it("still generates with empty assistant material (tool-only turn): uses only the user request, prompt omits the assistant section", async () => {
const seen: string[] = [];
const result = await generateTitleWithLLM(
fakeLLM([assistantText("配置 Tailwind 主题")], { status: "completed" }, seen),
{ userText: "帮我配置 @theme", assistantText: "" },
fakeLLM([assistantText("Configure the Tailwind theme")], { status: "completed" }, seen),
{ userText: "help me configure @theme", assistantText: "" },
);
expect(result.title).toBe("配置 Tailwind 主题");
expect(seen[0]).toBe(buildTitlePrompt("帮我配置 @theme", ""));
expect(result.title).toBe("Configure the Tailwind theme");
expect(seen[0]).toBe(buildTitlePrompt("help me configure @theme", ""));
expect(seen[0]).not.toContain("[Assistant]");
});
it("sanitizeTitle: strips quotes/punctuation to a fixed point, collapses whitespace, truncates overlong input, returns null for empty", () => {
expect(sanitizeTitle("“ 构建配置 说明 。”")).toBe("构建配置 说明");
expect(sanitizeTitle("『标题』!")).toBe("标题");
expect(sanitizeTitle("“ Build config notes 。”")).toBe("Build config notes");
expect(sanitizeTitle("『Title』!")).toBe("Title");
expect(sanitizeTitle(" \n ")).toBeNull();
expect(sanitizeTitle("x".repeat(50))).toHaveLength(30);
// A leaked [use_skills] block is stripped from the model output.
expect(sanitizeTitle("[use_skills]\nskills: web-design\n[/use_skills]\n构建落地页")).toBe(
"构建落地页",
);
expect(
sanitizeTitle("[use_skills]\nskills: web-design\n[/use_skills]\nBuild a landing page"),
).toBe("Build a landing page");
});
it("Session.generateTitle: sends via createBareLLM; returns null when no factory is provided", async () => {