fix: normalize response images missing index + guard audio duration overflow#22950
Conversation
…or DB storage (BerriAI#22936) When the messages or response JSON fields in spend logs are truncated before being written to the database, the truncation marker now includes a note explaining: - This is a DB storage safeguard - Full, untruncated data is still sent to logging callbacks (OTEL, Datadog, etc.) - The MAX_STRING_LENGTH_PROMPT_IN_DB env var can be used to increase the limit Also emits a verbose_proxy_logger.info message when truncation occurs in the request body or response spend log paths. Adds 3 new tests: - test_truncation_includes_db_safeguard_note - test_response_truncation_logs_info_message - test_request_body_truncation_logs_info_message Co-authored-by: Cursor Agent <cursoragent@cursor.com>
The /organization/list endpoint only checked for PROXY_ADMIN role, causing PROXY_ADMIN_VIEW_ONLY users to fall into the else branch which restricts results to orgs the user is a member of. Use the existing _user_has_admin_view() helper to include both roles.
…r_org_list Fix admin viewer unable to see all organizations
…eaming (BerriAI#22937) * feat(ui): add chat message and conversation types * feat(ui): add useChatHistory hook for localStorage-backed conversations * feat(ui): add ConversationList sidebar component * feat(ui): add MCPConnectPicker for attaching MCP servers to chat * feat(ui): add ModelSelector dropdown for chat * feat(ui): add ChatInputBar with MCP tool attachment support * feat(ui): add MCPAppsPanel with list/detail view for MCP servers * feat(ui): add ChatMessages component; remove auto-scrollIntoView that caused scroll-lock bypass * feat(ui): add ChatPage — ChatGPT-like UI with scroll lock, MCP tools, streaming * feat(ui): add /chat route wired to ChatPage * feat(ui): remove chat from leftnav — chat accessible via navbar button * feat(ui): add Chat button to top navbar * feat(ui): add dismissible Chat UI announcement banner to Playground page * feat(proxy): add Chat UI link to Swagger description * feat(ui): add react-markdown and syntax-highlighter deps for chat UI * fix(ui): replace missing BorderOutlined import with inline stop icon div * fix(ui): apply remark-gfm plugin to ReactMarkdown for GFM support * fix(ui): remove unused isEvenRow variable in MCPAppsPanel * fix(ui): add ellipsis when truncating conversation title * fix(ui): wire search button to chats view; remove non-functional keyboard hint * fix(ui): use serverRootPath in navbar chat link for sub-path deployments * fix(ui): remove unused ChatInputBar and ModelSelector files * fix(ui): correct grid bottom-border condition for odd server count * fix(chat): move localStorage writes out of setConversations updater (React purity) * fix(chat): fix stale closure in handleEditAndResend - compute history before async state update * fix(chat): fix 4 issues in ChatMessages - array redaction, clipboard error, inline detection, remove unused ref
- Add PayGo / Priority Cost Tracking section to Vertex AI provider docs - Document trafficType to service_tier mapping (ON_DEMAND_PRIORITY, FLEX, etc.) - Add service tier cost keys to custom pricing docs - Add provider-specific cost tracking note to spend tracking overview Made-with: Cursor
docs: add PayGo/priority cost tracking for Gemini Vertex AI
…verflow 1. convert_dict_to_response.py (BerriAI#22640): Providers like OpenRouter/Gemini return images without the required `index` field, causing pydantic ValidationError when constructing Message. Added _normalize_images() to backfill index from enumeration position. 2. audio_utils/utils.py (BerriAI#22622): libsndfile can report 2^63-1 frames for malformed audio files, causing astronomically large duration values used for cost calculation. Added guards for sentinel frame counts and implausible durations (>24h). Co-Authored-By: claude-flow <ruv@ruv.net>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes two independent bugs: (1) providers like OpenRouter with Gemini image models returning images without the Key changes:
Minor issues:
Confidence Score: 4/5
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[convert_to_model_response_object] --> B{choice message\nhas 'images'?}
B -- No / None --> C[images = None]
B -- Yes --> D[_normalize_images list]
D --> E{For each img\nin list}
E --> F{isinstance dict\nAND 'index' missing?}
F -- Yes --> G["img = {**img, 'index': i}"]
F -- No --> H[keep img as-is]
G --> I[append to normalized]
H --> I
I --> E
E -- done --> J[return normalized list]
C --> K[Message constructor]
J --> K
L[calculate_request_duration] --> M{frames <= 0\nOR frames >= 2^63-1?}
M -- Yes --> N[return None]
M -- No --> O{samplerate <= 0?}
O -- Yes --> N
O -- No --> P[duration = frames / samplerate]
P --> Q{duration > 86400s?}
Q -- Yes --> N
Q -- No --> R[return duration]
Last reviewed commit: 57e7e70 |
| def _normalize_images(images): | ||
| """Normalize image items to include required 'index' field if missing.""" | ||
| if images is None: | ||
| return None | ||
| normalized = [] | ||
| for i, img in enumerate(images): | ||
| if isinstance(img, dict) and "index" not in img: | ||
| img = {**img, "index": i} | ||
| normalized.append(img) | ||
| return normalized |
There was a problem hiding this comment.
Missing type annotations on _normalize_images
The new helper function lacks type hints, which are used consistently throughout the rest of this file (e.g., _safe_convert_created_field, convert_tool_call_to_json_mode). Adding them makes the expected contract explicit and helps static analysis tools catch misuse early.
| def _normalize_images(images): | |
| """Normalize image items to include required 'index' field if missing.""" | |
| if images is None: | |
| return None | |
| normalized = [] | |
| for i, img in enumerate(images): | |
| if isinstance(img, dict) and "index" not in img: | |
| img = {**img, "index": i} | |
| normalized.append(img) | |
| return normalized | |
| def _normalize_images( | |
| images: Optional[List[ImageURLListItem]], | |
| ) -> Optional[List[ImageURLListItem]]: | |
| """Normalize image items to include required 'index' field if missing.""" | |
| if images is None: | |
| return None | |
| normalized: List[ImageURLListItem] = [] | |
| for i, img in enumerate(images): | |
| if isinstance(img, dict) and "index" not in img: | |
| img = {**img, "index": i} | |
| normalized.append(img) | |
| return normalized |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 57e7e70 — added type annotations to the function signature and local variable.
| frames = len(audio) | ||
| # Guard against sentinel/invalid frame counts (e.g., 2^63-1 from libsndfile) | ||
| if frames <= 0 or frames >= 2**63 - 1: | ||
| return None | ||
| duration = frames / audio.samplerate |
There was a problem hiding this comment.
samplerate zero-check missing
After guarding against invalid frames values, the code still divides by audio.samplerate without verifying it is non-zero. A malformed audio file could report samplerate == 0, which would raise a ZeroDivisionError. In practice the outer except Exception block catches this and returns None, but an explicit guard is safer and communicates intent clearly.
| frames = len(audio) | |
| # Guard against sentinel/invalid frame counts (e.g., 2^63-1 from libsndfile) | |
| if frames <= 0 or frames >= 2**63 - 1: | |
| return None | |
| duration = frames / audio.samplerate | |
| frames = len(audio) | |
| # Guard against sentinel/invalid frame counts (e.g., 2^63-1 from libsndfile) | |
| if frames <= 0 or frames >= 2**63 - 1: | |
| return None | |
| if audio.samplerate <= 0: | |
| return None | |
| duration = frames / audio.samplerate |
There was a problem hiding this comment.
Good catch — fixed in 57e7e70. Added if audio.samplerate <= 0: return None guard before the division.
Address review feedback: - Add type hints to _normalize_images() for consistency with codebase - Guard against samplerate <= 0 to prevent ZeroDivisionError on malformed audio files Co-Authored-By: claude-flow <ruv@ruv.net>
|
|
|
|
||
| from .get_headers import get_response_headers | ||
|
|
||
| def _normalize_images( |
There was a problem hiding this comment.
Missing blank line before top-level function (PEP 8 E302)
PEP 8 requires two blank lines before a top-level function definition. Only one blank line separates the from .get_headers import get_response_headers import on line 46 from _normalize_images on line 48. This will trigger an E302 linting error.
| def _normalize_images( | |
| def _normalize_images( |
To fix, add one more blank line before the function definition.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
a58d859
into
BerriAI:litellm_oss_staging_03_06_2026
Summary
Response images missing
indexfield ([Bug]: OpenRouter + google/gemini-3-pro-image-preview: ValidationError - missingindexfield in images array #22640): Providers like OpenRouter with Gemini image models return images without theindexfield required byImageURLListItem. This causesValidationErrorwhen constructingMessage. Added_normalize_images()helper to backfillindexfrom enumeration position before passing toMessage.Audio duration overflow ([Bug]: Audio transcription can be massively overbilled when local soundfile fallback returns invalid duration #22622):
libsndfilecan report2^63-1frames as a sentinel for unknown frame counts in malformed audio files.calculate_request_duration()computeslen(audio) / audio.sampleratewithout sanity checking, producing absurdly large durations (e.g., 192 trillion seconds) used for cost calculation. Added guards for sentinel frame counts and implausible durations (>24 hours), returningNoneto trigger the existing fallback path.Test plan
llm_response_utilstests pass_normalize_imagescorrectly addsindexto images missing it_normalize_images(None)returnsNone(no-op for non-image responses)ValidationErrorNoneduration instead of overflow values🤖 Generated with claude-flow