Commit d05289c
authored
fix(providers): route 6-10MB attachments to the provider large-file path (#6232)
* fix(providers): route 6-10MB attachments to the provider large-file path
The inline base64 cap was 10 MB of raw bytes, but the execution payload store
refuses a single value above 8 MiB and base64 inflates by 4/3. Every raw file
over 6 MiB therefore produced a base64 string the store rejected — and the
rejection came from the base64 *cache* write, which threw and failed the run
with "Execution memory limit exceeded" even though the bytes had already been
read successfully. Because shouldUseLargeFilePath only fires above the inline
cap, 6-10 MB attachments had no path at all on any provider: they never reached
the OpenAI or Gemini Files API upload they were supposed to take.
Derive the cap from the payload-store ceiling instead of hardcoding it, and
degrade a refused cache write to "not cached" rather than failing a request
whose bytes are in hand. Every other size guard in the chain compares raw bytes
against maxBytes; only the Redis write sees the encoded size, which is why this
went unnoticed — and why it failed only where Redis is configured.
Also correct the provider ceilings against the vendors' current documentation:
- openai: 50 MiB -> 50,000,000. The gate is `size > maxBytes`, so 50 MiB
admitted 52,428,800 bytes; the docs say each file must be *under* 50 MB.
- bedrock: had no entry and inherited the inline cap, which is above what
Converse accepts (3.75 MB per image, 4.5 MB per document).
- groq: 20 MiB -> 20,000,000, and modelled as the request cap the docs
actually describe rather than a per-file MiB ceiling.
- fireworks: had no entry; its 10 MB budget is on the base64 total, so the
raw-byte equivalent is 7.5 MB.
Add perRequestMaxBytes for the combined ceilings, enforced before any upload
spend, and cover the OpenAI upload path end to end — it had no test at all.
* chore(deps): upgrade @google/genai to 2.13.0 and @anthropic-ai/sdk to 0.115.0
@google/genai 2.x reworks the Interactions API, which the Gemini deep-research
provider is built on. Migrate it:
- `Interaction.outputs` (a flat content array) is now `steps`, a discriminated
timeline; the report text lives in the `model_output` steps' text content,
alongside thought and tool steps we skip.
- `Usage.total_reasoning_tokens` is now `total_thought_tokens`. The old code
already fell back to that name through a cast, so this just makes the field
the SDK actually returns the typed one.
- SSE events renamed: `content.delta` -> `step.delta`, `interaction.start` ->
`interaction.created`, `interaction.complete` -> `interaction.completed`.
The new event types are discriminated, so the payload casts are gone.
Both `interactions.create` calls also stop annotating their params with
`Interactions.CreateAgentInteractionParams{,Non}Streaming`. In 2.13.0 those
namespace aliases resolve to `CreateAgentInteraction`, whose `stream` is a
plain `boolean` rather than a literal — annotating with them erases the
discriminant and the call resolves to the union-returning overload, so the
result is typed as `Interaction | Stream` at every use. An inline
`stream: true as const` keeps the correct overload.
Neither upgrade required a `minimum-release-age` waiver: 2.15.0 and 0.115.0
were checked and 2.13.0 is the newest genai release clearing the 7-day window.
* chore(deps): upgrade openai to 7.0.0
v5 is the only major with real breaking changes for us; v6 widened a Responses
output type and v7 only raised the Node floor to 22, which apps/sim already
requires. Three things needed fixing:
`ChatCompletionMessageToolCall` became a union of function and custom tool
calls, and the custom variant has no `function` field — 43 unguarded `.function`
accesses across the OpenAI-compatible providers. Narrow once at each
`message.tool_calls` read through a shared `isFunctionToolCall` guard rather
than casting at every use.
That guard deliberately tests for the `function` payload instead of
`type === 'function'`. Many OpenAI-compatible vendors omit `type` on tool calls
entirely — our own fixtures do — so discriminating on it type-checks perfectly
and then silently drops every tool call those providers return.
`ChatCompletionCreateParams.verbosity` narrowed from `string` to a literal
union, and the Responses API's output and input item unions now diverge on
members Sim never emits (computer-use call outputs, whose `status` admits
`failed`, and the `AdditionalTools` escape hatch). Echoing output back as input
is what a tool loop is supposed to do, so that conversion is asserted once in
convertResponseOutputToInputItems and the streaming loop now routes through it
instead of pushing raw output items.
The hand-rolled multipart upload in file-attachments.server.ts can now be
replaced with the SDK's typed `expires_after` — left for a follow-up so this
commit stays a pure upgrade.
* fix(providers): correct defects found auditing the attachment and SDK changes
The mechanical rewrite that added `isFunctionToolCall` to every `tool_calls`
read also rewrote three truthiness guards, where the filtered array was
computed, discarded, and the unfiltered value used in the body. Filter once and
use that value. The helper also landed between `trackForcedToolUsage`'s TSDoc
block and its declaration, leaving that block documenting the wrong function.
Raise the Bedrock ceiling from 3.75 MB to 4.5 MB. Converse caps an image at
3.75 MB and a document at 4.5 MB, and a single `maxBytes` cannot express both.
Taking the lower bound looked conservative but regressed 3.75-4.5 MB documents,
which Converse accepts and which work today. At the document bound every size
that works now still works, and only genuinely-too-large files are rejected
early; oversized images in that band keep surfacing as a Bedrock API error,
exactly as they do without the entry.
Both limits re-verified verbatim against the primary docs: Converse's Message
reference ("Each image's size ... no more than 3.75 MB", "Each document's size
must be no more than 4.5 MB") and Fireworks' vision guide ("Total base64-encoded
images must be less than 10MB").
* fix(providers): keep every attachment that works today working
Auditing the routing change for backwards compatibility turned up two bands it
silently broke.
Lowering the single inline cap made the upload path mandatory above ~6 MB, but
every large-file path reads its bytes back out of cloud object storage. A
deployment without it — local dev, any disk-backed self-host — inlines those
files as base64 today and would have started failing outright with "requires
cloud file storage". Split the one number in two: the inline ceiling stays at
10 MiB, and a separate threshold marks where an upload becomes *preferable*
because the base64 copy no longer fits the payload store. Where no upload path
is reachable, base64 hydration now runs to the inline ceiling as before, and a
missing cloud-storage backend leaves the file for the inline path instead of
throwing.
The two strategies also cross over at different sizes now. `files-api` carries
every type the provider already accepts, so it takes over at the lower
threshold. `remote-url` only fetches images and PDFs, so switching early would
have started rejecting 6-10 MB text documents that inline fine today; it takes
over only once inlining is genuinely impossible.
Revert the Groq ceilings. Its published "20MB" governs a request carrying an
image URL, and on this path the body holds only the URL, so it cannot bind on
the files maxBytes guards. Groq documents no limit on the image it fetches, so
tightening the per-file cap to 20,000,000 and summing raw bytes against the
request cap would both reject uploads that work today on no documented basis.
* fix(providers): drop the provider ceiling changes and close the audit findings
A six-agent line-by-line audit against the vendors' live docs found the
`models.ts` ceiling work was not the strict improvement it was written as, so
all of it is reverted:
- bedrock's 4.5 MB cap broke video. Converse takes image, document AND video
blocks, and video is allowed 25 MB base64 — a single `maxBytes` cannot
express three content classes, and every 4.5-10 MiB `.mp4` that works today
would have started failing.
- openai's combined 50 MB cap is the FILE-input limit. Image inputs are
governed separately at 512 MB / 1500 images, so summing every attachment
rejected eight 8 MB PNGs that OpenAI documents as legal.
- fireworks' per-file ceiling was unreachable behind the request budget, while
the upload picker went on advertising it — a size the UI accepts and
execution always rejects.
- The whole `perRequestMaxBytes` feature goes with them: it summed raw bytes
against caps that are variously on encoded bytes, on one content class, or on
a body that carries only URLs, and it double-counted a file referenced from
several messages even though the uploader dedupes by key.
Only openai's per-file `maxBytes` stays corrected, to decimal 50,000,000 — the
one number a vendor states unambiguously and writes no MiB against.
Also fixed, all found by the same audit:
The hydration cap stopped short of where `remote-url` actually switches over,
so 6-10 MiB attachments on anthropic/openrouter/xai/groq/together/baseten/vllm
had neither base64 nor a handle and failed outright — the very band this branch
exists to fix. Both decisions now come from one function so they cannot drift.
Eight more sites where the mechanical rewrite computed a filtered array and
then read the unfiltered one (deepseek, sakana, nvidia, kimi), leaving those
providers without the narrowing they appear to have.
`isFunctionToolCall` threw on a null or primitive `tool_calls` entry, because
`in` requires an object — reachable exactly on the self-hosted gateways this
filter was added for. It is now total, and all 32 test mocks match it rather
than being quietly more permissive.
`checkForForcedToolUsage` in utils/litellm/mistral evaluated the response before
the `tool_choice` test, turning a tolerated malformed body into a TypeError on a
path that never used to touch it.
Gemini: `satisfies` restores the excess-property checking the dropped
annotations removed, the poll loop recognises the terminal statuses v2 added
instead of spinning for an hour and reporting a timeout, and the streaming doc
block no longer names five events that were renamed six lines below it.
* fix(providers): report attachment limits in the unit vendors publish
The size ceilings are decimal MB — that is how OpenAI, AWS and Fireworks all
write them — but the error messages divided by 1024², so OpenAI's 50 MB cap
was reported to the user as "48MB". Someone shrinking a 49 MB file to get
under it was chasing a limit that does not exist.
One formatter, used by all three messages, so the file size and the ceiling in
the same sentence are always in the same unit.
* fix(providers): derive the limit unit from the ceiling it belongs to
The previous commit fixed OpenAI's "48MB" by dividing every ceiling by 10⁶ —
which broke the other seven. Only OpenAI's constant is decimal; anthropic,
google, together and openrouter are 50 MiB, baseten and vllm 25 MiB, groq and
xai 20 MiB. Rendering those as decimal MB overstated each by ~5%, so a 21 MB
file on groq was rejected with "(21MB) exceeds the 21MB limit" — a sentence
that contradicts itself and sends the user to shrink a file to a size that is
still over. Same class of bug as the one being fixed, sign flipped.
Both figures now render through one unit taken from the ceiling, so the number
a user is told is the number the vendor publishes and the two sizes in a
sentence are always comparable. Tested against every ceiling in the registry
rather than only the values that happened to round cleanly.
Two more from the same audit:
A file with a missing or zero declared size was stranded on a files-api
provider: hydration bailed on the real byte length while `shouldUseLargeFilePath`
saw `0 > threshold` as false, so it got neither base64 nor a handle and failed
as "may no longer be accessible" — a size failure wearing an access failure's
message. Uploads read the real bytes and enforce the ceiling themselves, so an
unknown size now routes to one.
The oversized-attachment error blamed the provider for a deployment problem:
a files-api provider on a host without cloud storage reported that the provider
"has no large-file upload path", which is not true of the provider.
`isFunctionToolCall` only proves `function` is present, never that it is well
formed, so the trace enricher is defensive again about a hollow payload without
giving up the compile-time gate. The 32 test mocks now match production exactly.
* fix(providers): stop an over-limit size rendering as the limit itself
Deriving the unit from the ceiling fixed the 5% error but left the precision
fixed at two decimals, so a file one byte over a 20 MiB cap still printed
"(20.00MB) exceeds the 20MB agent attachment limit" — the same self-contradicting
sentence, now in a ~5 KB band above every ceiling in the registry. The size
rounds up and the ceiling rounds down, so the two can no longer collide.
The test that was supposed to guard this asserted a file 0.03MB over and an
OpenAI file that was under the limit — neither anywhere near the band — so it
passed while the bug was live. It now walks `limit + 1` for every ceiling, and
goes red against the old rounding.
The reason clause added last commit also claimed a deployment had no cloud file
storage whenever the strategy was not inline. A generated document on a
remote-url provider reaches that same error with storage fully configured,
because a signed URL points at the generation source rather than the rendered
artifact — so it was told something false about its own deployment. That case
now names itself.
* fix(providers): order the attachment failure reason by how general the cause is
The generated-document arm was checked first, so it won over both other causes
and told users two things that were not true.
On an inline-strategy provider — bedrock, mistral, ollama, fireworks, litellm,
vertex, kimi — there is no upload path for any file, generated or not, but the
message blamed the document format and implied a plain PDF would go through.
On openai or google with cloud storage unconfigured it was simply false: a
generated document does take the Files API path there, and that exact file
uploads fine once storage exists. The one actionable fix was hidden from the
operator.
A provider with no upload path cannot be helped by changing the file, and a
deployment with no object storage cannot reach any upload path whatever the
file is, so both now outrank the format-specific case — which is left saying
only what is true of it: a signed URL points at the generation source rather
than the rendered file.
The formatter is unchanged. It was brute-forced over every real ceiling and
three million random pairs with no collision or inversion, but the test's six
ceilings all divide to exact integers, so floor, round and ceil are
indistinguishable on them and the limit-side rounding was unpinned. A ceiling
with a fractional remainder now covers it.1 parent ed17bb2 commit d05289c
65 files changed
Lines changed: 914 additions & 220 deletions
File tree
- apps/sim
- app/api
- knowledge/search
- providers
- baseten/models
- ollama-cloud/models
- together/models
- blocks
- ee/access-control/utils
- executor/handlers
- agent
- pi
- lib
- api-key
- copilot/tools/server/workflow/edit-workflow
- model-router
- uploads/utils
- providers
- anthropic
- azure-openai
- baseten
- bedrock
- cerebras
- deepseek
- fireworks
- gemini
- groq
- kimi
- litellm
- meta
- mistral
- nvidia
- ollama-cloud
- ollama
- openai-compat
- openai
- openrouter
- sakana
- together
- vllm
- xai
- zai
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
47 | 52 | | |
48 | 53 | | |
49 | 54 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
20 | 25 | | |
21 | 26 | | |
22 | 27 | | |
| |||
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
48 | 53 | | |
49 | 54 | | |
50 | 55 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
69 | 74 | | |
70 | 75 | | |
71 | 76 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
33 | 38 | | |
34 | 39 | | |
35 | 40 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
| 57 | + | |
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
60 | 65 | | |
61 | 66 | | |
62 | 67 | | |
| |||
946 | 951 | | |
947 | 952 | | |
948 | 953 | | |
| 954 | + | |
| 955 | + | |
949 | 956 | | |
950 | 957 | | |
951 | 958 | | |
| |||
963 | 970 | | |
964 | 971 | | |
965 | 972 | | |
966 | | - | |
| 973 | + | |
967 | 974 | | |
968 | 975 | | |
969 | 976 | | |
970 | | - | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
971 | 980 | | |
972 | 981 | | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
973 | 1000 | | |
974 | | - | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
975 | 1004 | | |
976 | 1005 | | |
977 | 1006 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
21 | 26 | | |
22 | 27 | | |
23 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
80 | 85 | | |
81 | 86 | | |
82 | 87 | | |
| |||
0 commit comments