chore: make non-i18n code English-only (#117)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f63037866e
commit
4681135423
19 files changed
+113
-106
No files matched your search
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in new issue
Block a user