Handle 413 context overflow errors gracefully

When the conversation context exceeds the model's limit, instead of
throwing an opaque error or returning raw JSON, we now:

1. Detect context overflow errors (413, request_too_large, etc.)
2. Return a user-friendly message explaining the issue
3. Suggest using /new or /reset to start fresh

This prevents the assistant from becoming completely unresponsive
when context grows too large (e.g., from many screenshots or long
tool outputs).

Addresses issue #394
This commit is contained in:
alejandro maza
2026-01-07 07:51:04 -06:00
committed by Peter Steinberger
parent 42b637bbc8
commit 579828b2d5
3 changed files with 80 additions and 1 deletions

View File

@@ -1,6 +1,12 @@
import { describe, expect, it } from "vitest";
import { buildBootstrapContextFiles } from "./pi-embedded-helpers.js";
import type { AssistantMessage } from "@mariozechner/pi-ai";
import {
buildBootstrapContextFiles,
formatAssistantErrorText,
isContextOverflowError,
} from "./pi-embedded-helpers.js";
import {
DEFAULT_AGENTS_FILENAME,
type WorkspaceBootstrapFile,
@@ -46,3 +52,35 @@ describe("buildBootstrapContextFiles", () => {
expect(result?.content.endsWith(long.slice(-120))).toBe(true);
});
});
describe("isContextOverflowError", () => {
it("matches known overflow hints", () => {
const samples = [
"request_too_large",
"Request exceeds the maximum size",
"context length exceeded",
"Maximum context length",
"413 Request Entity Too Large",
];
for (const sample of samples) {
expect(isContextOverflowError(sample)).toBe(true);
}
});
it("ignores unrelated errors", () => {
expect(isContextOverflowError("rate limit exceeded")).toBe(false);
});
});
describe("formatAssistantErrorText", () => {
const makeAssistantError = (errorMessage: string): AssistantMessage =>
({
stopReason: "error",
errorMessage,
}) as AssistantMessage;
it("returns a friendly message for context overflow", () => {
const msg = makeAssistantError("request_too_large");
expect(formatAssistantErrorText(msg)).toContain("Context overflow");
});
});

View File

@@ -126,6 +126,18 @@ export function buildBootstrapContextFiles(
return result;
}
export function isContextOverflowError(errorMessage?: string): boolean {
if (!errorMessage) return false;
const lower = errorMessage.toLowerCase();
return (
lower.includes("request_too_large") ||
lower.includes("request exceeds the maximum size") ||
lower.includes("context length exceeded") ||
lower.includes("maximum context length") ||
(lower.includes("413") && lower.includes("too large"))
);
}
export function formatAssistantErrorText(
msg: AssistantMessage,
): string | undefined {
@@ -133,6 +145,14 @@ export function formatAssistantErrorText(
const raw = (msg.errorMessage ?? "").trim();
if (!raw) return "LLM request failed with an unknown error.";
// Check for context overflow (413) errors
if (isContextOverflowError(raw)) {
return (
"Context overflow: the conversation history is too large. " +
"Use /new or /reset to start a fresh session."
);
}
const invalidRequest = raw.match(
/"type":"invalid_request_error".*?"message":"([^"]+)"/,
);

View File

@@ -59,6 +59,7 @@ import {
formatAssistantErrorText,
isAuthAssistantError,
isAuthErrorMessage,
isContextOverflowError,
isRateLimitAssistantError,
isRateLimitErrorMessage,
pickFallbackThinkingLevel,
@@ -1153,6 +1154,26 @@ export async function runEmbeddedPiAgent(params: {
}
if (promptError && !aborted) {
const errorText = describeUnknownError(promptError);
if (isContextOverflowError(errorText)) {
return {
payloads: [
{
text:
"Context overflow: the conversation history is too large for the model. " +
"Use /new or /reset to start a fresh session, or try a model with a larger context window.",
isError: true,
},
],
meta: {
durationMs: Date.now() - started,
agentMeta: {
sessionId: sessionIdUsed,
provider,
model: model.id,
},
},
};
}
if (
(isAuthErrorMessage(errorText) ||
isRateLimitErrorMessage(errorText)) &&