Merge remote-tracking branch 'origin/main'

This commit is contained in:
Peter Steinberger
2026-01-07 20:11:32 +01:00
7 changed files with 102 additions and 8 deletions

View File

@@ -1,6 +1,11 @@
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import { buildBootstrapContextFiles } from "./pi-embedded-helpers.js";
import {
buildBootstrapContextFiles,
formatAssistantErrorText,
isContextOverflowError,
} from "./pi-embedded-helpers.js";
import {
DEFAULT_AGENTS_FILENAME,
type WorkspaceBootstrapFile,
@@ -46,3 +51,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)) &&

View File

@@ -22,6 +22,7 @@ describe("pi tool definition adapter", () => {
status: "error",
tool: "boom",
});
expect(JSON.stringify(result.details)).toContain("nope");
expect(result.details).toMatchObject({ error: "nope" });
expect(JSON.stringify(result.details)).not.toContain("\n at ");
});
});

View File

@@ -4,12 +4,23 @@ import type {
AgentToolUpdateCallback,
} from "@mariozechner/pi-agent-core";
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
import { logError } from "../logger.js";
import { logDebug, logError } from "../logger.js";
import { jsonResult } from "./tools/common.js";
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-agent-core uses a different module instance.
type AnyAgentTool = AgentTool<any, unknown>;
function describeToolExecutionError(err: unknown): {
message: string;
stack?: string;
} {
if (err instanceof Error) {
const message = err.message?.trim() ? err.message : String(err);
return { message, stack: err.stack };
}
return { message: String(err) };
}
export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
return tools.map((tool) => {
const name = tool.name || "tool";
@@ -37,13 +48,15 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
? String((err as { name?: unknown }).name)
: "";
if (name === "AbortError") throw err;
const message =
err instanceof Error ? (err.stack ?? err.message) : String(err);
logError(`[tools] ${tool.name} failed: ${message}`);
const described = describeToolExecutionError(err);
if (described.stack && described.stack !== described.message) {
logDebug(`tools: ${tool.name} failed stack:\n${described.stack}`);
}
logError(`[tools] ${tool.name} failed: ${described.message}`);
return jsonResult({
status: "error",
tool: tool.name,
error: message,
error: described.message,
});
}
},