diff --git a/patches/@mariozechner__pi-ai@0.42.2.patch b/patches/@mariozechner__pi-ai@0.42.2.patch index 0dee9b488..16edc1104 100644 --- a/patches/@mariozechner__pi-ai@0.42.2.patch +++ b/patches/@mariozechner__pi-ai@0.42.2.patch @@ -27,3 +27,19 @@ index 188a8294f26fe1bfe3fb298a7f58e4d8eaf2a529..a3aeb6a7ff53bc4f7f44362adb950b2c })); } function mapStopReason(status) { +diff --git a/dist/providers/openai-responses.js b/dist/providers/openai-responses.js +index 7b58a79c989abc76bb8fc9e99fb49126e5fd7de4..a1a7f35ad47975dc1268d1a0c2078b0b651e97b4 100644 +--- a/dist/providers/openai-responses.js ++++ b/dist/providers/openai-responses.js +@@ -396,9 +396,10 @@ function convertMessages(model, context) { + } + else if (msg.role === "assistant") { + const output = []; ++ const hasAssistantText = msg.content.some((block) => block.type === "text"); + for (const block of msg.content) { + // Do not submit thinking blocks if the completion had an error (i.e. abort) +- if (block.type === "thinking" && msg.stopReason !== "error") { ++ if (block.type === "thinking" && msg.stopReason !== "error" && hasAssistantText) { + if (block.thinkingSignature) { + const reasoningItem = JSON.parse(block.thinkingSignature); + output.push(reasoningItem); diff --git a/src/commands/doctor.test.ts b/src/commands/doctor.test.ts index c4d195126..7b42184bc 100644 --- a/src/commands/doctor.test.ts +++ b/src/commands/doctor.test.ts @@ -639,8 +639,10 @@ describe("doctor", () => { ([message, title]) => title === "Sandbox" && typeof message === "string" && - message.includes('agents.list (id "work") sandbox docker') && - message.includes('scope resolves to "shared"'), + message + .replace(/\s+/g, " ") + .includes('agents.list (id "work") sandbox docker') && + message.replace(/\s+/g, " ").includes('scope resolves to "shared"'), ), ).toBe(true); }); diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 9a2acfbf4..d3657a5d5 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -1,10 +1,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { - intro as clackIntro, - outro as clackOutro, -} from "@clack/prompts"; +import { intro as clackIntro, outro as clackOutro } from "@clack/prompts"; import { resolveAgentWorkspaceDir, resolveDefaultAgentId, @@ -39,8 +36,8 @@ import { runGatewayUpdate } from "../infra/update-runner.js"; import { runCommandWithTimeout } from "../process/exec.js"; import type { RuntimeEnv } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; -import { stylePromptTitle } from "../terminal/prompt-style.js"; import { note } from "../terminal/note.js"; +import { stylePromptTitle } from "../terminal/prompt-style.js"; import { sleep } from "../utils.js"; import { DEFAULT_GATEWAY_DAEMON_RUNTIME, diff --git a/src/terminal/note.ts b/src/terminal/note.ts index 54ed49025..7a35cf069 100644 --- a/src/terminal/note.ts +++ b/src/terminal/note.ts @@ -1,12 +1,7 @@ import { note as clackNote } from "@clack/prompts"; +import { visibleWidth } from "./ansi.js"; import { stylePromptTitle } from "./prompt-style.js"; -const ANSI_ESCAPE = /\u001b\[[0-9;]*m/g; - -function visibleLength(value: string): number { - return Array.from(value.replace(ANSI_ESCAPE, "")).length; -} - function splitLongWord(word: string, maxLen: number): string[] { if (maxLen <= 0) return [word]; const chars = Array.from(word); @@ -25,8 +20,8 @@ function wrapLine(line: string, maxWidth: number): string[] { const content = match?.[3] ?? ""; const firstPrefix = `${indent}${bullet}`; const nextPrefix = `${indent}${bullet ? " ".repeat(bullet.length) : ""}`; - const firstWidth = Math.max(10, maxWidth - visibleLength(firstPrefix)); - const nextWidth = Math.max(10, maxWidth - visibleLength(nextPrefix)); + const firstWidth = Math.max(10, maxWidth - visibleWidth(firstPrefix)); + const nextWidth = Math.max(10, maxWidth - visibleWidth(nextPrefix)); const words = content.split(/\s+/).filter(Boolean); const lines: string[] = []; @@ -36,7 +31,7 @@ function wrapLine(line: string, maxWidth: number): string[] { for (const word of words) { if (!current) { - if (visibleLength(word) > available) { + if (visibleWidth(word) > available) { const parts = splitLongWord(word, available); const first = parts.shift() ?? ""; lines.push(prefix + first); @@ -50,7 +45,7 @@ function wrapLine(line: string, maxWidth: number): string[] { } const candidate = `${current} ${word}`; - if (visibleLength(candidate) <= available) { + if (visibleWidth(candidate) <= available) { current = candidate; continue; } @@ -59,7 +54,7 @@ function wrapLine(line: string, maxWidth: number): string[] { prefix = nextPrefix; available = nextWidth; - if (visibleLength(word) > available) { + if (visibleWidth(word) > available) { const parts = splitLongWord(word, available); const first = parts.shift() ?? ""; lines.push(prefix + first); diff --git a/src/wizard/clack-prompter.ts b/src/wizard/clack-prompter.ts index 0b5d135ef..c5146d292 100644 --- a/src/wizard/clack-prompter.ts +++ b/src/wizard/clack-prompter.ts @@ -11,13 +11,13 @@ import { text, } from "@clack/prompts"; import { createCliProgress } from "../cli/progress.js"; +import { note as emitNote } from "../terminal/note.js"; import { stylePromptHint, stylePromptMessage, stylePromptTitle, } from "../terminal/prompt-style.js"; import { theme } from "../terminal/theme.js"; -import { note as emitNote } from "../terminal/note.js"; import type { WizardProgress, WizardPrompter } from "./prompts.js"; import { WizardCancelledError } from "./prompts.js";