From 2d54efe851b3bf920f6393a553c3843c3f6980ed Mon Sep 17 00:00:00 2001 From: George Pickett Date: Wed, 14 Jan 2026 16:52:10 -0800 Subject: [PATCH] Embedded runner: suppress raw API error payloads (#919) --- CHANGELOG.md | 2 ++ .../pi-embedded-runner/run/payloads.test.ts | 32 +++++++++++++++++++ src/agents/pi-embedded-runner/run/payloads.ts | 16 +++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 src/agents/pi-embedded-runner/run/payloads.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bcb423033..7a3b7cf57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Onboarding: add a security checkpoint prompt (docs link + sandboxing hint); require `--accept-risk` for `--non-interactive`. - Docs: expand gateway security hardening guidance and incident response checklist. +### Fixes +- Embedded runner: suppress raw API error payloads from replies. (#924) — thanks @grp06. ## 2026.1.14 ### Changes diff --git a/src/agents/pi-embedded-runner/run/payloads.test.ts b/src/agents/pi-embedded-runner/run/payloads.test.ts new file mode 100644 index 000000000..4680303f4 --- /dev/null +++ b/src/agents/pi-embedded-runner/run/payloads.test.ts @@ -0,0 +1,32 @@ +import type { AssistantMessage } from "@mariozechner/pi-ai"; +import { describe, expect, it } from "vitest"; +import { buildEmbeddedRunPayloads } from "./payloads.js"; + +describe("buildEmbeddedRunPayloads", () => { + it("suppresses raw API error JSON when the assistant errored", () => { + const errorJson = + '{"type":"error","error":{"details":null,"type":"overloaded_error","message":"Overloaded"},"request_id":"req_011CX7DwS7tSvggaNHmefwWg"}'; + const lastAssistant = { + stopReason: "error", + errorMessage: errorJson, + content: [{ type: "text", text: errorJson }], + } as AssistantMessage; + + const payloads = buildEmbeddedRunPayloads({ + assistantTexts: [errorJson], + toolMetas: [], + lastAssistant, + sessionKey: "session:telegram", + inlineToolResultsAllowed: false, + verboseLevel: "off", + reasoningLevel: "off", + }); + + expect(payloads).toHaveLength(1); + expect(payloads[0]?.text).toBe( + "The AI service is temporarily overloaded. Please try again in a moment.", + ); + expect(payloads[0]?.isError).toBe(true); + expect(payloads.some((payload) => payload.text === errorJson)).toBe(false); + }); +}); diff --git a/src/agents/pi-embedded-runner/run/payloads.ts b/src/agents/pi-embedded-runner/run/payloads.ts index 0b8115ac3..9eea8ec01 100644 --- a/src/agents/pi-embedded-runner/run/payloads.ts +++ b/src/agents/pi-embedded-runner/run/payloads.ts @@ -48,6 +48,10 @@ export function buildEmbeddedRunPayloads(params: { sessionKey: params.sessionKey, }) : undefined; + const rawErrorMessage = + params.lastAssistant?.stopReason === "error" + ? params.lastAssistant.errorMessage?.trim() || undefined + : undefined; if (errorText) replyItems.push({ text: errorText, isError: true }); const inlineToolResults = @@ -83,11 +87,13 @@ export function buildEmbeddedRunPayloads(params: { if (reasoningText) replyItems.push({ text: reasoningText }); const fallbackAnswerText = params.lastAssistant ? extractAssistantText(params.lastAssistant) : ""; - const answerTexts = params.assistantTexts.length - ? params.assistantTexts - : fallbackAnswerText - ? [fallbackAnswerText] - : []; + const answerTexts = ( + params.assistantTexts.length + ? params.assistantTexts + : fallbackAnswerText + ? [fallbackAnswerText] + : [] + ).filter((text) => (rawErrorMessage ? text.trim() !== rawErrorMessage : true)); for (const text of answerTexts) { const {