




















@@ -1,3 +1,4 @@
1+import type { StreamFn } from "@earendil-works/pi-agent-core";
12import type { Context, Model } from "@earendil-works/pi-ai";
23import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
34import {
@@ -30,6 +31,10 @@ type ReplayToolCall = {
3031};
31323233type RegisteredProvider = Awaited<ReturnType<typeof registerSingleProviderPlugin>>;
34+type FakeStream = {
35+result: () => Promise<unknown>;
36+[Symbol.asyncIterator]: () => AsyncIterator<unknown>;
37+};
33383439const emptyUsage = {
3540input: 0,
@@ -141,6 +146,29 @@ function createPayloadCapturingStream(capture: PayloadCapture, model: OpenAIComp
141146};
142147}
143148149+function createFakeStream(params: { events: unknown[]; resultMessage: unknown }): FakeStream {
150+return {
151+async result() {
152+return params.resultMessage;
153+},
154+[Symbol.asyncIterator]() {
155+return (async function* () {
156+for (const event of params.events) {
157+yield event;
158+}
159+})();
160+},
161+};
162+}
163+164+function createResultStreamFn(params: { events?: unknown[]; resultMessage: unknown }): StreamFn {
165+return () =>
166+createFakeStream({
167+events: params.events ?? [],
168+resultMessage: params.resultMessage,
169+}) as ReturnType<StreamFn>;
170+}
171+144172function requireThinkingWrapper(
145173wrapper: ReturnType<typeof createMiMoThinkingWrapper>,
146174label: string,
@@ -312,4 +340,99 @@ describe("xiaomi provider plugin", () => {
312340"reasoning_content",
313341);
314342});
343+344+it.each(["mimo-v2-pro", "mimo-v2-omni"] as const)(
345+"promotes reasoning-only terminal output to visible text for %s",
346+async (modelId) => {
347+const model = mimoReasoningModel(modelId);
348+const wrapped = requireThinkingWrapper(
349+createMiMoThinkingWrapper(
350+createResultStreamFn({
351+events: [
352+{
353+type: "message_end",
354+message: {
355+role: "assistant",
356+content: [{ type: "thinking", thinking: "MiMo final answer" }],
357+stopReason: "stop",
358+},
359+},
360+],
361+resultMessage: {
362+role: "assistant",
363+content: [{ type: "thinking", thinking: "MiMo final answer" }],
364+stopReason: "stop",
365+},
366+}),
367+"high",
368+),
369+modelId,
370+);
371+372+const stream = (await wrapped(model, { messages: [] } as Context, {})) as FakeStream;
373+const events: unknown[] = [];
374+for await (const event of stream) {
375+events.push(event);
376+}
377+378+expect(events).toEqual([
379+{
380+type: "message_end",
381+message: {
382+role: "assistant",
383+content: [{ type: "text", text: "MiMo final answer" }],
384+stopReason: "stop",
385+},
386+},
387+]);
388+await expect(stream.result()).resolves.toEqual({
389+role: "assistant",
390+content: [{ type: "text", text: "MiMo final answer" }],
391+stopReason: "stop",
392+});
393+},
394+);
395+396+it("does not promote reasoning when the MiMo assistant turn also has text or tool calls", async () => {
397+const model = mimoReasoningModel("mimo-v2-pro");
398+const textMessage = {
399+role: "assistant",
400+content: [
401+{ type: "thinking", thinking: "internal" },
402+{ type: "text", text: "already visible" },
403+],
404+stopReason: "stop",
405+};
406+const toolMessage = {
407+role: "assistant",
408+content: [{ type: "thinking", thinking: "call reasoning" }, readToolCall],
409+stopReason: "toolUse",
410+};
411+412+for (const resultMessage of [textMessage, toolMessage]) {
413+const wrapped = requireThinkingWrapper(
414+createMiMoThinkingWrapper(createResultStreamFn({ resultMessage }), "high"),
415+"mixed-content",
416+);
417+const stream = (await wrapped(model, { messages: [] } as Context, {})) as FakeStream;
418+419+await expect(stream.result()).resolves.toEqual(resultMessage);
420+}
421+});
422+423+it("does not promote reasoning-only output for newer MiMo replay models", async () => {
424+const model = mimoReasoningModel("mimo-v2.5-pro");
425+const resultMessage = {
426+role: "assistant",
427+content: [{ type: "thinking", thinking: "actual reasoning" }],
428+stopReason: "stop",
429+};
430+const wrapped = requireThinkingWrapper(
431+createMiMoThinkingWrapper(createResultStreamFn({ resultMessage }), "high"),
432+"mimo-v2.5-pro",
433+);
434+const stream = (await wrapped(model, { messages: [] } as Context, {})) as FakeStream;
435+436+await expect(stream.result()).resolves.toEqual(resultMessage);
437+});
315438});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。