fix(files): reduce any scheme or case wrapping files/<id> the way gemini does before the ownership lookup

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kerry 2026-11-13 00:52:40 +00:00
parent d2a5f07994
commit e2dad6c914
3 changed files with 36 additions and 6 deletions

View file

@ -3,7 +3,7 @@ import itertools
import logging
import mimetypes
import re
from collections.abc import Mapping, Sequence
from collections.abc import Iterator, Mapping, Sequence
from dataclasses import dataclass, field
from datetime import datetime, timezone
from types import MappingProxyType
@ -457,11 +457,27 @@ def canonical_provider_file_id(file_id: str) -> str:
_GEMINI_FILES_URL: Final = "https://generativelanguage.googleapis.com/v1beta/files/"
def _gemini_files_tail(file_id: str) -> str:
"""The id Gemini resolves ``file_id`` to, whatever scheme or case wraps it: the text after its ``files/``."""
files_index: Final = file_id.find("files/")
return file_id[files_index:].strip("/").removeprefix("files/").strip("/") if files_index != -1 else ""
def _iter_stored_forms(bare_ids: Sequence[str]) -> Iterator[str]:
for bare_id in bare_ids:
yield bare_id
yield f"files/{bare_id}"
yield f"{_GEMINI_FILES_URL}{bare_id}"
def provider_file_id_spellings(file_id: str) -> tuple[str, ...]:
"""Every spelling an ownership row may hold for ``file_id``: as sent, unwrapped from a model-encoded id and
canonicalized, plus the ``files/`` and Gemini URL forms that upload rows stored before ids were canonicalized."""
canonical: Final = canonical_provider_file_id(get_original_file_id(file_id))
return tuple(dict.fromkeys((file_id, canonical, f"files/{canonical}", f"{_GEMINI_FILES_URL}{canonical}")))
canonicalized, reduced the way Gemini reduces it, plus the ``files/`` and Gemini URL forms that upload rows
stored before ids were canonicalized."""
original: Final = get_original_file_id(file_id)
reductions: Final = (canonical_provider_file_id(original), _gemini_files_tail(unquote(original)))
bare_ids: Final = tuple(dict.fromkeys(bare for bare in reductions if bare))
return tuple(dict.fromkeys((file_id, *_iter_stored_forms(bare_ids))))
def is_model_embedded_id(file_id: str) -> bool:

View file

@ -61,8 +61,7 @@ class _InMemoryManagedFileTable:
or_filters: Final = where.get("OR")
if isinstance(or_filters, list):
for clause in or_filters:
matched: Final = await self.find_first(clause)
if matched is not None:
if (matched := await self.find_first(clause)) is not None:
return matched
return None
unified_file_id: Final = where.get("unified_file_id")
@ -6581,6 +6580,8 @@ _GEMINI_OUTPUT_SPELLINGS: Final = (
"files%2Fbatch-out1",
"batch-out1/",
"https://generativelanguage.googleapis.com/v1beta/files/batch-out1",
"HTTPS://generativelanguage.googleapis.com/v1beta/files/batch-out1",
"x://h/files/batch-out1",
)

View file

@ -1003,3 +1003,16 @@ def test_provider_file_id_spellings_unwraps_a_model_encoded_id_and_covers_the_st
)
for spelling in ("batch-out1", "files%2Fbatch-out1", "batch-out1/"):
assert set(provider_file_id_spellings(spelling)) >= set(provider_file_id_spellings(wrapped)[1:]), spelling
@pytest.mark.parametrize(
"respelling",
(
"HTTPS://generativelanguage.googleapis.com/v1beta/files/batch-out1",
"x://h/files/batch-out1",
"S3://b/files/batch-out1",
"x%3A%2F%2Fh%2Ffiles%2Fbatch-out1",
),
)
def test_provider_file_id_spellings_reduce_any_scheme_the_way_gemini_does(respelling: str):
assert "batch-out1" in provider_file_id_spellings(respelling)