Merge branch 'main' into fix/issue-2954-image-maxbytes-config
This commit is contained in:
commit
840a3962b9
@ -70,6 +70,7 @@ Status: unreleased.
|
|||||||
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma.
|
||||||
- Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94.
|
- Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94.
|
||||||
- Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355.
|
- Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355.
|
||||||
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
||||||
|
|||||||
@ -31,6 +31,7 @@ describe("classifyFailoverReason", () => {
|
|||||||
"messages.84.content.1.image.source.base64.data: At least one of the image dimensions exceed max allowed size for many-image requests: 2000 pixels",
|
"messages.84.content.1.image.source.base64.data: At least one of the image dimensions exceed max allowed size for many-image requests: 2000 pixels",
|
||||||
),
|
),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
|
expect(classifyFailoverReason("image exceeds 5 MB maximum")).toBeNull();
|
||||||
});
|
});
|
||||||
it("classifies OpenAI usage limit errors as rate_limit", () => {
|
it("classifies OpenAI usage limit errors as rate_limit", () => {
|
||||||
expect(classifyFailoverReason("You have hit your ChatGPT usage limit (plus plan)")).toBe(
|
expect(classifyFailoverReason("You have hit your ChatGPT usage limit (plus plan)")).toBe(
|
||||||
|
|||||||
14
src/agents/pi-embedded-helpers.image-size-error.test.ts
Normal file
14
src/agents/pi-embedded-helpers.image-size-error.test.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { parseImageSizeError } from "./pi-embedded-helpers.js";
|
||||||
|
|
||||||
|
describe("parseImageSizeError", () => {
|
||||||
|
it("parses max MB values from error text", () => {
|
||||||
|
expect(parseImageSizeError("image exceeds 5 MB maximum")?.maxMb).toBe(5);
|
||||||
|
expect(parseImageSizeError("Image exceeds 5.5 MB limit")?.maxMb).toBe(5.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for unrelated errors", () => {
|
||||||
|
expect(parseImageSizeError("context overflow")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -23,12 +23,14 @@ export {
|
|||||||
isFailoverAssistantError,
|
isFailoverAssistantError,
|
||||||
isFailoverErrorMessage,
|
isFailoverErrorMessage,
|
||||||
isImageDimensionErrorMessage,
|
isImageDimensionErrorMessage,
|
||||||
|
isImageSizeError,
|
||||||
isOverloadedErrorMessage,
|
isOverloadedErrorMessage,
|
||||||
isRawApiErrorPayload,
|
isRawApiErrorPayload,
|
||||||
isRateLimitAssistantError,
|
isRateLimitAssistantError,
|
||||||
isRateLimitErrorMessage,
|
isRateLimitErrorMessage,
|
||||||
isTimeoutErrorMessage,
|
isTimeoutErrorMessage,
|
||||||
parseImageDimensionError,
|
parseImageDimensionError,
|
||||||
|
parseImageSizeError,
|
||||||
} from "./pi-embedded-helpers/errors.js";
|
} from "./pi-embedded-helpers/errors.js";
|
||||||
export { isGoogleModelApi, sanitizeGoogleTurnOrdering } from "./pi-embedded-helpers/google.js";
|
export { isGoogleModelApi, sanitizeGoogleTurnOrdering } from "./pi-embedded-helpers/google.js";
|
||||||
|
|
||||||
|
|||||||
@ -401,6 +401,7 @@ const ERROR_PATTERNS = {
|
|||||||
const IMAGE_DIMENSION_ERROR_RE =
|
const IMAGE_DIMENSION_ERROR_RE =
|
||||||
/image dimensions exceed max allowed size for many-image requests:\s*(\d+)\s*pixels/i;
|
/image dimensions exceed max allowed size for many-image requests:\s*(\d+)\s*pixels/i;
|
||||||
const IMAGE_DIMENSION_PATH_RE = /messages\.(\d+)\.content\.(\d+)\.image/i;
|
const IMAGE_DIMENSION_PATH_RE = /messages\.(\d+)\.content\.(\d+)\.image/i;
|
||||||
|
const IMAGE_SIZE_ERROR_RE = /image exceeds\s*(\d+(?:\.\d+)?)\s*mb/i;
|
||||||
|
|
||||||
function matchesErrorPatterns(raw: string, patterns: readonly ErrorPattern[]): boolean {
|
function matchesErrorPatterns(raw: string, patterns: readonly ErrorPattern[]): boolean {
|
||||||
if (!raw) return false;
|
if (!raw) return false;
|
||||||
@ -467,6 +468,25 @@ export function isImageDimensionErrorMessage(raw: string): boolean {
|
|||||||
return Boolean(parseImageDimensionError(raw));
|
return Boolean(parseImageDimensionError(raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseImageSizeError(raw: string): {
|
||||||
|
maxMb?: number;
|
||||||
|
raw: string;
|
||||||
|
} | null {
|
||||||
|
if (!raw) return null;
|
||||||
|
const lower = raw.toLowerCase();
|
||||||
|
if (!lower.includes("image exceeds") || !lower.includes("mb")) return null;
|
||||||
|
const match = raw.match(IMAGE_SIZE_ERROR_RE);
|
||||||
|
return {
|
||||||
|
maxMb: match?.[1] ? Number.parseFloat(match[1]) : undefined,
|
||||||
|
raw,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isImageSizeError(errorMessage?: string): boolean {
|
||||||
|
if (!errorMessage) return false;
|
||||||
|
return Boolean(parseImageSizeError(errorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
export function isCloudCodeAssistFormatError(raw: string): boolean {
|
export function isCloudCodeAssistFormatError(raw: string): boolean {
|
||||||
return !isImageDimensionErrorMessage(raw) && matchesErrorPatterns(raw, ERROR_PATTERNS.format);
|
return !isImageDimensionErrorMessage(raw) && matchesErrorPatterns(raw, ERROR_PATTERNS.format);
|
||||||
}
|
}
|
||||||
@ -478,6 +498,7 @@ export function isAuthAssistantError(msg: AssistantMessage | undefined): boolean
|
|||||||
|
|
||||||
export function classifyFailoverReason(raw: string): FailoverReason | null {
|
export function classifyFailoverReason(raw: string): FailoverReason | null {
|
||||||
if (isImageDimensionErrorMessage(raw)) return null;
|
if (isImageDimensionErrorMessage(raw)) return null;
|
||||||
|
if (isImageSizeError(raw)) return null;
|
||||||
if (isRateLimitErrorMessage(raw)) return "rate_limit";
|
if (isRateLimitErrorMessage(raw)) return "rate_limit";
|
||||||
if (isOverloadedErrorMessage(raw)) return "rate_limit";
|
if (isOverloadedErrorMessage(raw)) return "rate_limit";
|
||||||
if (isCloudCodeAssistFormatError(raw)) return "format";
|
if (isCloudCodeAssistFormatError(raw)) return "format";
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
isContextOverflowError,
|
isContextOverflowError,
|
||||||
isFailoverAssistantError,
|
isFailoverAssistantError,
|
||||||
isFailoverErrorMessage,
|
isFailoverErrorMessage,
|
||||||
|
parseImageSizeError,
|
||||||
parseImageDimensionError,
|
parseImageDimensionError,
|
||||||
isRateLimitAssistantError,
|
isRateLimitAssistantError,
|
||||||
isTimeoutErrorMessage,
|
isTimeoutErrorMessage,
|
||||||
@ -440,6 +441,34 @@ export async function runEmbeddedPiAgent(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Handle image size errors with a user-friendly message (no retry needed)
|
||||||
|
const imageSizeError = parseImageSizeError(errorText);
|
||||||
|
if (imageSizeError) {
|
||||||
|
const maxMb = imageSizeError.maxMb;
|
||||||
|
const maxMbLabel =
|
||||||
|
typeof maxMb === "number" && Number.isFinite(maxMb) ? `${maxMb}` : null;
|
||||||
|
const maxBytesHint = maxMbLabel ? ` (max ${maxMbLabel}MB)` : "";
|
||||||
|
return {
|
||||||
|
payloads: [
|
||||||
|
{
|
||||||
|
text:
|
||||||
|
`Image too large for the model${maxBytesHint}. ` +
|
||||||
|
"Please compress or resize the image and try again.",
|
||||||
|
isError: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
durationMs: Date.now() - started,
|
||||||
|
agentMeta: {
|
||||||
|
sessionId: sessionIdUsed,
|
||||||
|
provider,
|
||||||
|
model: model.id,
|
||||||
|
},
|
||||||
|
systemPromptReport: attempt.systemPromptReport,
|
||||||
|
error: { kind: "image_size", message: errorText },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
const promptFailoverReason = classifyFailoverReason(errorText);
|
const promptFailoverReason = classifyFailoverReason(errorText);
|
||||||
if (promptFailoverReason && promptFailoverReason !== "timeout" && lastProfileId) {
|
if (promptFailoverReason && promptFailoverReason !== "timeout" && lastProfileId) {
|
||||||
await markAuthProfileFailure({
|
await markAuthProfileFailure({
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export type EmbeddedPiRunMeta = {
|
|||||||
aborted?: boolean;
|
aborted?: boolean;
|
||||||
systemPromptReport?: SessionSystemPromptReport;
|
systemPromptReport?: SessionSystemPromptReport;
|
||||||
error?: {
|
error?: {
|
||||||
kind: "context_overflow" | "compaction_failure" | "role_ordering";
|
kind: "context_overflow" | "compaction_failure" | "role_ordering" | "image_size";
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
/** Stop reason for the agent run (e.g., "completed", "tool_calls"). */
|
/** Stop reason for the agent run (e.g., "completed", "tool_calls"). */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user