






















@@ -81,6 +81,8 @@ function createMessageEndContext(
8181emitBlockReply?: ReturnType<typeof vi.fn>;
8282finalizeAssistantTexts?: ReturnType<typeof vi.fn>;
8383consumeReplyDirectives?: ReturnType<typeof vi.fn>;
84+warn?: ReturnType<typeof vi.fn>;
85+builtinToolNames?: ReadonlySet<string>;
8486state?: Record<string, unknown>;
8587} = {},
8688) {
@@ -118,7 +120,8 @@ function createMessageEndContext(
118120noteLastAssistant: vi.fn(),
119121recordAssistantUsage: vi.fn(),
120122commitAssistantUsage: vi.fn(),
121-log: { debug: vi.fn(), warn: vi.fn() },
123+log: { debug: vi.fn(), warn: params.warn ?? vi.fn() },
124+builtinToolNames: params.builtinToolNames,
122125stripBlockTags: (text: string) => text,
123126finalizeAssistantTexts: params.finalizeAssistantTexts ?? vi.fn(),
124127emitBlockReply: params.emitBlockReply ?? vi.fn(),
@@ -604,6 +607,57 @@ describe("handleMessageUpdate", () => {
604607});
605608606609describe("handleMessageEnd", () => {
610+it("warns when assistant text only pretends to call a registered tool", () => {
611+const warn = vi.fn();
612+const ctx = createMessageEndContext({
613+ warn,
614+builtinToolNames: new Set(["read"]),
615+});
616+617+void handleMessageEnd(ctx, {
618+type: "message_end",
619+message: {
620+role: "assistant",
621+provider: "ollama",
622+model: "qwen-local",
623+content: [{ type: "text", text: '{"name":"read","arguments":{"path":"README.md"}}' }],
624+stopReason: "stop",
625+},
626+} as never);
627+628+expect(warn).toHaveBeenCalledWith(
629+"Assistant reply looks like a tool call, but no structured tool invocation was emitted; treating it as text.",
630+expect.objectContaining({
631+runId: "run-1",
632+sessionId: "session-1",
633+provider: "ollama",
634+model: "qwen-local",
635+pattern: "json_tool_call",
636+toolName: "read",
637+registeredTool: true,
638+}),
639+);
640+});
641+642+it("does not warn when the assistant emitted a structured tool call", () => {
643+const warn = vi.fn();
644+const ctx = createMessageEndContext({
645+ warn,
646+builtinToolNames: new Set(["read"]),
647+});
648+649+void handleMessageEnd(ctx, {
650+type: "message_end",
651+message: {
652+role: "assistant",
653+content: [{ type: "toolCall", id: "call_1", name: "read", arguments: {} }],
654+stopReason: "toolUse",
655+},
656+} as never);
657+658+expect(warn).not.toHaveBeenCalled();
659+});
660+607661it("suppresses commentary-phase replies from user-visible output", () => {
608662const onAgentEvent = vi.fn();
609663const emitBlockReply = vi.fn();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。