mirror of https://github.com/openclaw/openclaw.git
fix(agents): prevent false billing error replacing valid response text (#40616)
Merged via squash.
Prepared head SHA: 05179362b4
Co-authored-by: ingyukoh <6015960+ingyukoh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
This commit is contained in:
parent
78b9384aa7
commit
2a18cbb110
|
|
@ -107,6 +107,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Telegram/config schema: accept `channels.telegram.actions.editMessage` and `createForumTopic` in strict config validation so existing Telegram action toggles no longer fail as unrecognized keys. (#35498) Thanks @ingyukoh.
|
||||
- Agents/cooldowns: default cooldown windows with no recorded failure history to `unknown` instead of `rate_limit`, avoiding false API rate-limit warnings while preserving cooldown recovery probes. (#42911) Thanks @VibhorGautam.
|
||||
- Discord/config typing: expose channel-level `autoThread` on the canonical guild-channel config type so strict config loading matches the existing Discord schema and runtime behavior. (#35608) Thanks @ingyukoh.
|
||||
- Agents/error rendering: ignore stale assistant `errorMessage` fields on successful turns so background/tool-side failures no longer prepend synthetic billing errors over valid replies. (#40616) Thanks @ingyukoh.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,18 @@ describe("buildEmbeddedRunPayloads", () => {
|
|||
expect(payloads[0]?.isError).toBe(true);
|
||||
});
|
||||
|
||||
it("does not emit a synthetic billing error for successful turns with stale errorMessage", () => {
|
||||
const payloads = buildPayloads({
|
||||
lastAssistant: makeAssistant({
|
||||
stopReason: "stop",
|
||||
errorMessage: "insufficient credits for embedding model",
|
||||
content: [{ type: "text", text: "Handle payment required errors in your API." }],
|
||||
}),
|
||||
});
|
||||
|
||||
expectSinglePayloadText(payloads, "Handle payment required errors in your API.");
|
||||
});
|
||||
|
||||
it("suppresses raw error JSON even when errorMessage is missing", () => {
|
||||
const payloads = buildPayloads({
|
||||
assistantTexts: [errorJsonPretty],
|
||||
|
|
|
|||
|
|
@ -128,16 +128,17 @@ export function buildEmbeddedRunPayloads(params: {
|
|||
const useMarkdown = params.toolResultFormat === "markdown";
|
||||
const suppressAssistantArtifacts = params.didSendDeterministicApprovalPrompt === true;
|
||||
const lastAssistantErrored = params.lastAssistant?.stopReason === "error";
|
||||
const errorText = params.lastAssistant
|
||||
? suppressAssistantArtifacts
|
||||
? undefined
|
||||
: formatAssistantErrorText(params.lastAssistant, {
|
||||
cfg: params.config,
|
||||
sessionKey: params.sessionKey,
|
||||
provider: params.provider,
|
||||
model: params.model,
|
||||
})
|
||||
: undefined;
|
||||
const errorText =
|
||||
params.lastAssistant && lastAssistantErrored
|
||||
? suppressAssistantArtifacts
|
||||
? undefined
|
||||
: formatAssistantErrorText(params.lastAssistant, {
|
||||
cfg: params.config,
|
||||
sessionKey: params.sessionKey,
|
||||
provider: params.provider,
|
||||
model: params.model,
|
||||
})
|
||||
: undefined;
|
||||
const rawErrorMessage = lastAssistantErrored
|
||||
? params.lastAssistant?.errorMessage?.trim() || undefined
|
||||
: undefined;
|
||||
|
|
|
|||
|
|
@ -134,6 +134,20 @@ describe("extractAssistantText", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("preserves response when errorMessage set from background failure (#13935)", () => {
|
||||
const responseText = "Handle payment required errors in your API.";
|
||||
const msg = makeAssistantMessage({
|
||||
role: "assistant",
|
||||
errorMessage: "insufficient credits for embedding model",
|
||||
stopReason: "stop",
|
||||
content: [{ type: "text", text: responseText }],
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const result = extractAssistantText(msg);
|
||||
expect(result).toBe(responseText);
|
||||
});
|
||||
|
||||
it("strips Minimax tool invocations with extra attributes", () => {
|
||||
const msg = makeAssistantMessage({
|
||||
role: "assistant",
|
||||
|
|
|
|||
|
|
@ -245,7 +245,9 @@ export function extractAssistantText(msg: AssistantMessage): string {
|
|||
}) ?? "";
|
||||
// Only apply keyword-based error rewrites when the assistant message is actually an error.
|
||||
// Otherwise normal prose that *mentions* errors (e.g. "context overflow") can get clobbered.
|
||||
const errorContext = msg.stopReason === "error" || Boolean(msg.errorMessage?.trim());
|
||||
// Gate on stopReason only — a non-error response with an errorMessage set (e.g. from a
|
||||
// background tool failure) should not have its content rewritten (#13935).
|
||||
const errorContext = msg.stopReason === "error";
|
||||
return sanitizeUserFacingText(extracted, { errorContext });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,9 +166,9 @@ export function extractAssistantText(message: unknown): string | undefined {
|
|||
normalizeText: (text) => text.trim(),
|
||||
}) ?? "";
|
||||
const stopReason = (message as { stopReason?: unknown }).stopReason;
|
||||
const errorMessage = (message as { errorMessage?: unknown }).errorMessage;
|
||||
const errorContext =
|
||||
stopReason === "error" || (typeof errorMessage === "string" && Boolean(errorMessage.trim()));
|
||||
// Gate on stopReason only — a non-error response with a stale/background errorMessage
|
||||
// should not have its content rewritten with error templates (#13935).
|
||||
const errorContext = stopReason === "error";
|
||||
|
||||
return joined ? sanitizeUserFacingText(joined, { errorContext }) : undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,6 +199,16 @@ describe("extractAssistantText", () => {
|
|||
"Firebase downgraded us to the free Spark plan. Check whether billing should be re-enabled.",
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves successful turns with stale background errorMessage", () => {
|
||||
const message = {
|
||||
role: "assistant",
|
||||
stopReason: "end_turn",
|
||||
errorMessage: "insufficient credits for embedding model",
|
||||
content: [{ type: "text", text: "Handle payment required errors in your API." }],
|
||||
};
|
||||
expect(extractAssistantText(message)).toBe("Handle payment required errors in your API.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveAnnounceTarget", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue