🔄 Upstream Sync: LiteLLM v1.98.0 - #132
Conversation
… CheckBatchCost A managed batch whose request lines all failed can reach a terminal provider status (completed) with output_file_id=None and only an error_file_id. Such a row matched neither the completed-with-output billing branch nor the failed/expired/cancelled branch, so batch_processed stayed False and the poller re-selected it on every cycle for the lifetime of the deployment; output/error file deletion is also gated on batch_processed, so those files could never be deleted. Broaden the terminal handling so a completed/complete/expired batch with an output file is billed, and any terminal batch with nothing to bill (failed/cancelled, or completed/expired with no output) is marked terminal exactly once. Non-terminal statuses (validating/in_progress) are still left for the next poll, and an expired batch that did produce output is now billed. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…provider fix(ui): add nvidia riva to the model provider list
…summary fix(scripts): end make check with a ran/skipped summary and verdict
…I#36660) * fix(proxy): track spend for OpenAI passthrough /v1/embeddings OpenAI passthrough embeddings returned 200 but wrote no spend because the route was unsupported and Cohere's /v1/embed prefix stole the match. * fix(proxy): clear embeddings lint and Greptile comment nits Inline embeddings cost tracking to avoid new LIT001/002 hits, trim redundant doc comments, and cover the Cohere /v1/embeddings collision. * fix(proxy): drop unreachable embeddings TypeError guard convert_to_model_response_object with response_type=embedding already returns EmbeddingResponse; the isinstance check was dead patch coverage.
…itellm_lit002_typeddict_dict_literals # Conflicts: # type-discipline-budget.json
request_id is the primary key of LiteLLM_SpendLogs and the flush inserts with
skip_duplicates, so a spend log whose id already exists is dropped with no error
raised and a "processed 1 spend log" line still logged. Batch cost accounting
produced exactly such an id twice over, and on a proxy with message redaction
enabled no batch cost row could be written at all.
get_spend_logs_id derived the id by md5-hashing the response for two call types,
aretrieve_batch and acreate_file. Redaction makes that hash a constant:
perform_redaction returns the fixed {"text": "redacted-by-litellm"} placeholder
for any shape it cannot redact, which is what a batch object and a file body both
become, so every such row hashed to md5('{"text": "redacted-by-litellm"}') =
00fcbef15a3b0097e14b0ca016ed30a0 regardless of provider, user, or amount. The
first row to claim that id owned it and every later row was discarded. Verified
against a live proxy: four payloads spanning two providers and three distinct
spend values all computed that id, and the table held one acreate_file row dating
to 2025-05-25, the row that had claimed it.
Keying off the batch's own identity instead is necessary but not sufficient,
because creating a batch already writes an acreate_batch row under exactly that
id, so the cost row becomes a duplicate of the batch's own creation row. Also
verified live: after the hash was removed the poller computed and flushed a
batch's cost, and the only row carrying that id was the acreate_batch row from
when the batch was submitted.
The id now comes from the response's own id, then the standard logging payload's
id, then litellm_call_id, and a batch cost row is namespaced with a _batch_cost
suffix so it cannot collide with the creation row. The middle term is what keeps
this correct under redaction: that payload is built from the unredacted response,
so it still carries the batch id after redaction has flattened the body. Keying
the cost row to the batch rather than to the call also keeps accounting the same
batch twice collapsing to one row instead of billing it twice. Every other call
type still derives its key exactly as before.
Cost and usage themselves are unaffected by redaction: the token columns fall back
to the standard logging payload and spend comes from its response_cost, neither of
which redaction touches. generate_hash_from_response had no other caller and is
removed with it.
…uted and cost-poller paths get_configured_s3_bucket_name accepts the output bucket only from the immutable _litellm_internal_model_credentials snapshot or AWS_S3_BUCKET_NAME. That refusal to read litellm_params is deliberate: the bucket is what validate_managed_cloud_file_id checks a file id against, so trusting a request-supplied value would let a caller redirect reads to a bucket of their choosing Two live entry points reach the Bedrock file-content transformation without ever building that snapshot. The managed-files pre-call hook sets data["model"] for any id carrying llm_output_file_id, which is every batch output, so get_file_content always takes the model-routed branch; that branch called llm_router.afile_content directly, and managed_files_obj.afile_content, the only caller that built the snapshot, is therefore unreachable for batch output. CheckBatchCost spread the deployment credentials as plain kwargs, and get_litellm_params does not carry s3_bucket_name across (gcs_bucket_name is listed for exactly this reason, its S3 counterpart is not), so the poller lost the bucket the same way The result was that every completed Bedrock managed batch failed files.content with "S3 bucket_name is required" and never had its cost tracked, leaving the row to be re-polled every cycle. Both paths now resolve the deployment credentials and pass the same MappingProxyType snapshot the managed-files hook already builds
The mock merged every call into one shared dict, so a second routed retrieval would overwrite the first and the assertions would still pass. Keep one frozen snapshot per call and assert exactly one call, which also makes an unintended second retrieval a failure rather than something the merge hides
…ccounting path too A third path reads a completed batch's output file, and it could not resolve the bucket either. When cost is accounted from the retrieve itself rather than from the poller, the batch success handler calls _handle_completed_batch, which fetches the output file through _extract_file_access_credentials. That helper forwarded a whitelist covering Azure and Vertex, gcs_bucket_name included, but nothing for Bedrock, and retrieve_batch built its litellm_params through get_litellm_params, whose fixed signature drops the trusted credential snapshot. So the snapshot never reached the file read and it failed with "S3 bucket_name is required" for a bucket the deployment had configured, leaving the batch's cost unrecorded. Adding s3_bucket_name to that whitelist would not have worked. The Bedrock file config deliberately resolves the bucket only from the immutable server-side snapshot or the environment, never from a request param, because the bucket is what managed file ids are validated against. The snapshot is therefore what has to flow, exactly as it already does for the model-routed and cost-poller paths. retrieve_batch now re-adds the snapshot after get_litellm_params, the same way the file operations already do, the whitelist forwards it, and the proxy attaches it for router-routed managed batches from the deployment behind the unified id. Verified against a live proxy reading a real completed Bedrock batch: the cost row appears within seconds of the retrieve carrying the batch's real spend and usage, where before the read raised and no row was written. Resolving those credentials is best effort. A batch whose deployment no longer resolves, which happens when a model group is removed while batches are in flight, still serves its status instead of failing the request on the lookup. This matters for the OSS and polling-disabled configurations, where the retrieve path is the only thing that accounts for a batch at all.
…all paths The helper that carries the credential snapshot into litellm_params lived private in files/main.py, and the batch retrieve needed it too. It now sits beside get_litellm_params, which is what it augments, so neither caller reaches into the other's private surface. Typed as Mapping/MutableMapping of object rather than Any, which the strict import rules ban. The file-content route builds the snapshot through the same helper as the batch route instead of assembling a conditional mapping inline, which drops two mutable constructions and leaves one way to attach it. Its name loses the batch suffix now that both routes use it.
Two components computed a managed batch's cost and each assumed it was the only one. Retrieving a batch computed it through the @client decorator's success callback, and CheckBatchCost computed it on its own schedule. Whichever observed completion first decided the outcome, so cost was either counted once per retrieve or not at all. The lockout is the worse half. Retrieving a batch that had reached completion set batch_processed=True, which is what takes a batch out of CheckBatchCost's queue, since it selects batch_processed=False. That write claimed the cost had been accounted for on behalf of a callback that had not run yet and was not awaited. When the callback then failed the cost was gone permanently, with the poller already retired and no retry left. Observed on a live proxy: two completed batches whose callbacks raised inside the logging worker, one on a provider output path that did not resolve and one on a batch whose output file id was still None, both left marked processed with no spend row and no way to recover them. Nothing logged at error level for the batches themselves. The over-count is the other half. Nothing suppressed recomputation, so each retrieve of an already-completed batch recorded that batch's full cost again. A caller polling its own batch to see whether it had finished inflated spend by however many times it looked. The flag now means what its name says, and only the component that actually recorded the cost sets it. When the poller is running it owns accounting, so retrieving a managed batch records no cost and leaves the flag alone; the poller computes once and sets it. When the poller cannot be relied on, either because polling is disabled by config or because the enterprise job never registered, the retrieve path is the only accountant and behaves exactly as before. Batches with no managed object row are untouched either way, since neither the flag nor the poller queue applies to them.
…ches done The handoff asked whether the poller was running, when what matters is whether it will actually account for the batch. Those differ on a schema without the batch_processed column: the poller cannot filter on it, so it falls back to a query that excludes complete and completed rows, and it cannot set it either. A caller retrieving a provider-completed batch before the poller saw it therefore suppressed inline accounting, then marked the row complete, and the fallback query could never find it again. Nobody accounted for that batch, so its cost escaped the caller's budget entirely. The poller now publishes batch_processed_support_confirmed, set only once a filtered query has actually succeeded, and the handoff requires it. Defaulting to unconfirmed keeps accounting on the retrieve path in exactly the cases the poller would drop the batch, including the window before the poller's first cycle. All four combinations account exactly once: unconfirmed leaves the retrieve accounting and setting the marker, whether or not the column exists, and confirmed is only reachable when the column is present, where the poller accounts and sets it. A scheduler that hands back something other than a bound method leaves no poller to interrogate, which reads as unconfirmed rather than as working.
The ownership question was asked twice for one retrieve: once before the provider call to decide whether to suppress inline accounting, and again afterwards to decide whether to mark the batch accounted. Between those two points the poller can complete its first successful filtered query and become usable, so the two answers disagree. The retrieve then accounts for the batch inline, having decided the poller was unusable, while the later check sees a usable poller and leaves the marker unset, so the poller accounts for the same batch again and its spend is counted twice. The retrieve now decides once and passes that decision to update_batch_in_database, which prefers it over re-deriving one. Callers that record no cost of their own leave it unset and keep deriving it as before, so the cancel path is unchanged.
…itellm_/remove-test-migrate-gha-d1eae8
Both are covered on GitHub Actions. test-litellm-ui-build.yml runs the dashboard build on every PR, and test-litellm-ui-unit.yml runs the vitest suite with ui-unit-tests already a required check, so neither CircleCI job gates anything that GHA does not already gate. ui_build additionally produced nothing anyone consumed. It persisted litellm/proxy/_experimental/out to the workspace, and the only job downstream of it was ui_unit_tests, which never attached the workspace and reinstalled from source instead. The requires edge was pure sequencing, so the build output was written and discarded on every client-touching PR. One real narrowing comes with this, and it is deliberate. ui_unit_tests ran the full vitest suite on PRs, while the GHA job scopes PR runs to tests reachable from the diff and keeps the full suite on pushes to staging. That split was a measured decision in BerriAI#34175 and it still holds: the suite is 252s and 248s of that is CreateMCPServer.integration.test.tsx alone, so running everything per PR buys about four minutes to re-run one file. Note that assert-ci-coverage does not speak to this. It walks tests/**/test_*.py only, so it is blind to vitest files by construction; it stays green here because no Python test lost a runner, which is a narrower claim than the UI side being unaffected. auth_ui_unit_tests is a different job, a Python suite on a Postgres sidecar, and is untouched
…ayload (BerriAI#36744) Request metadata carries the whole UserAPIKeyAuth object, whose team_metadata holds the customer's own langfuse callback_vars. The only filter on the emitted blob was a four key deny list written as a circular reference crash guard, so those credentials reached the customer's own langfuse traces. The emitted blob is now the StandardLoggingPayload allowlist plus the litellm computed enrichments, and nothing is copied across from raw request metadata. That makes the credential exclusion structural rather than a filter someone has to keep correct. Steering keys keep reading raw metadata, matching literal_ai. Proxy callers are unaffected: their request metadata already rides under the allowlisted requester_metadata key, nesting intact. debug_langfuse dumped raw request metadata into the trace as a second copy of the same leak. It now emits caller scalars only. When StandardLoggingPayload is absent the trace is still emitted with the existing trace_id fallback, so failure traces survive.
Replaces Ant Design with the in-repo shadcn layer across every Navbar component, removing the last antd imports from src/components/Navbar. - CommunityEngagementButtons, NotificationsBell, ViewSwitcher, BlogDropdown, WorkerDropdown and UserDropdown now compose @/components/ui primitives - antd icons render at 1em while lucide defaults to 24px, so every icon carries an explicit size class matching what it replaced - UserDropdown uses Popover rather than DropdownMenu: its panel holds switches and badges, and form controls inside role="menu" are invalid - WorkerDropdown moves to Combobox since shadcn Select has no search - drops the nine no-restricted-imports suppressions these files no longer need
Replaces Ant Design across every source file under src/components/view_logs, so the request log drawer and its viewers compose @/components/ui primitives. - Drawer becomes Sheet, Collapse becomes Collapsible, Segmented and Radio.Group become Tabs, Tag becomes Badge, Descriptions becomes a local grid helper - every lucide icon carries an explicit size class, since antd icons render at 1em while lucide defaults to 24px - two tests dropped assertions on antd internal class names in favour of rendered text and roles, and the Pretty/JSON case now proves the toggle actually swaps the body rather than only that both controls render - drops the eslint suppressions these files no longer need
## TLDR Signed-off-by: Ishaan <ishaangupta0408@gmail.com>
Replaces Ant Design and Tremor across src/components/AIHub, so the model, agent, MCP and skill hub views compose @/components/ui primitives. - Modal becomes Dialog, tremor TabGroup becomes Tabs, tremor Card and Table become their shadcn counterparts, and Tag and tremor Badge become Badge - the three publish forms wrapped antd Form around zero Form.Item fields, so the wrapper became a div and the dead useForm and resetFields calls went with it, rather than pulling in react-hook-form for a form with no fields - antd Steps has no shadcn equivalent, so each form inlines a small ol stepper - cells holding model names, server ids and URLs gained min-w-0 and break-words so a long value cannot bleed into the neighbouring column - the three form tests dropped assertions invented by their antd mocks in favour of roles and rendered text - drops the eslint suppressions these files no longer need
The panel holds switches and ordinary buttons rather than menu items, so menu semantics promised keyboard behavior it does not provide.
Screen readers announced an unnamed dialog. The visible header is a custom layout, so the title is visually hidden to keep the drawer layout unchanged.
Replaces Ant Design and Tremor in the six shared components under src/components/common_components, which between them are reached by nine routes. - antd Table becomes the ui/table primitives, and the Actions column keeps antd's fixed: "right" behaviour via a sticky cell - Tremor Icon, Text and Badge become a plain span, p and StatusBadge - antd Tooltip and Typography copyable become the shadcn Tooltip and the shared CopyButton - every public prop signature is unchanged, since these are shared components and a renamed prop would break callers far from this folder - two tests dropped assertions on antd internal class names and on DOM structure, and gained cases proving a disabled action does not fire onClick MemberTable keeps a type-only import of antd's ColumnsType because a consumer annotates its own column array with it. No antd code ships from the file.
The migration closed a double submit hole that antd left open, but the
rewritten tests only proved the flow had not completed, so removing the
guard would not have failed them. Verified by mutation: dropping
disabled={loading} fails exactly this case.
Replaces Ant Design and Tremor in the key info header and detail view, the agent and vector store permission panels, and the team member permissions table. - antd Popover, Dropdown and Modal become HoverCard, DropdownMenu and Dialog, and Tremor TabGroup becomes Tabs with keepMounted so panel state survives a tab switch the way Tremor's did - the key id copy control moves to the shared CopyButton, which also fixes an icon that rendered at 24px because it inherited the heading font size - antd Checkbox onChange becomes onCheckedChange - every public prop signature is unchanged, since these are shared views - three member permission tests were passing vacuously: they searched for an unchecked box by reading .checked, which is undefined on a Base UI checkbox, so the assertions sat inside an if that never ran. They now scope the checkbox to its own row and assert the toggle, the save and the revert - drops the eslint suppressions these files no longer need
…tremor Replaces Ant Design and Tremor in the fallbacks views, the router general settings panel, and the two shared banner and badge components. - Tremor Card, Table and Icon become the ui/card, ui/table and lucide equivalents, reproducing Tremor's icon box so click targets keep their size - antd Alert becomes a composed role="alert" region, since the shadcn CLI's alert pulls in class-variance-authority, which this repo does not have - antd InputNumber becomes a native number input, and Switch onChange becomes onCheckedChange - shadcn TableCell ships whitespace-nowrap where Tremor's did not, so cells holding model names and setting descriptions get whitespace-normal back - adds a DeprecationBanner test covering naming, the link, and dismissal, proven against the antd version first and mutation checked - drops the eslint suppressions these files no longer need
…slot_locks docs(claude): tell agents to let heavy gates queue for machine-wide slots
…itellm_/nice-wilson-9fbed6
…ine-triage-9b92e5 test: unstick the suites CircleCI is failing on
…of_format docs(github): proof-of-fix section shows only the latest run as Before/After with nested cases
…bed6 test(e2e): assert provider error shape instead of pinned prose
fix(ui): de-duplicate the reset budget option and polish shadcn surfaces
…ld-1e15d9 chore: rebuild Admin UI bundle from litellm_internal_staging
The log details drawer moved off Ant Design in 03d2b16, so its section header renders lucide ChevronUp/ChevronDown rather than antd's UpOutlined and DownOutlined. The collapse test still waited on .anticon-up and .anticon-down, which no longer exist anywhere under view_logs, so it failed on every run and burned all three attempts identically. Point the three assertions at .lucide-chevron-up and .lucide-chevron-down, matching how the dashboard's other suites address lucide icons.
…ser-93a2a1 test(e2e/ui): assert the log drawer chevrons by their lucide classes
chore(ci): promote internal staging to main
Automatic sync from upstream BerriAI/litellm tag v1.97.0 Strategy: Merge with tree-level conflict resolution (accepted all upstream changes) Conflicts resolved: 13 files (3 rename/rename, 4 modify/delete, 4 rename/delete, 0 content)
…(backport to rc/1.98.0) (BerriAI#37955) * fix(ui): keep completion-mode models in the playground chat dropdown (BerriAI#37954) PR BerriAI#36130 added a KNOWN_MODEL_MODES guard to isModelCompatibleWithEndpoint that hides any model whose mode isn't in the ModelMode enum, to keep rerank/ocr/batch/etc. models out of chat-style endpoints. mode: completion (legacy text-completion models) wasn't in that enum, so it got caught by the same guard and disappeared from every endpoint, including chat, where it routes fine. Add ModelMode.COMPLETION and map it to EndpointType.CHAT like the other chat-compatible modes. (cherry picked from commit 1c421f3) * chore: update Next.js build artifacts (2026-08-22 18:42 UTC, node v24.19.0)
Automatic sync from upstream BerriAI/litellm tag v1.98.0 Strategy: Merge with history preservation (main syncs to stable tag)
|
No description provided. |
🤖 Conflict Resolution StartedStatus: ⏳ In progress... Claude Code (Opus 4.5) is resolving merge conflicts in this PR.
Note This may take 30-90 minutes for large PRs. Resolution commits will be pushed directly to this PR. 📋 Resolution Process (click to expand)
|
Conflicts resolved by Claude Code following CARTO priority rules. Resolution strategy: - Preserved CARTO customizations (workflows, docs, infrastructure) - Accepted upstream improvements (core litellm, tests, dependencies) - Manually merged mixed files (Dockerfile, Makefile) This is a MERGE COMMIT with both main and carto/main as parents, preserving full git history from upstream. Resolves: #132
✅ Conflict Resolution CompleteAll conflicts resolved and pushed to this PR.
Important Ready to merge! Use "Create a merge commit" — do NOT squash or rebase. CARTO Customization DecisionsSummary
Preserved CARTOFiles where CARTO implementation was kept:
Merged/CustomizedFiles where both sources were combined:
Synced (Required)Files synced entirely from upstream:
Fix Loop InterventionsFiles synced due to repeated conflicts:
Next Steps
🔧 Workflow Details (click to expand)Workflow Run: https://github.com/CartoDB/litellm/actions/runs/33120932726 |
|
Caution
|
📊 CARTO Feature Analysis StartedMode: Analysis + Auto-fix if issues found Analyzing how each CARTO customization was handled during conflict resolution.
|
|
| Decision | Count |
|---|---|
| Upstream Substitutes | 0 |
| Customized Upstream | 0 |
| Preserved CARTO | 11 |
| Incorrectly Dropped | 1 |
Overall Assessment: NEEDS_ATTENTION
🔧 Auto-fix enabled: The fix job will run next to restore dropped features.
📋 Full details in PR description above.
🔧 CARTO Feature Fix StartedRestoring 1 incorrectly dropped CARTO feature(s). |
Restores PR #112 functionality lost during upstream sync conflict resolution. The _content_to_text_string function was defined but never called, leaving it as dead code. Snowflake Cortex /chat/completions rejects array-form content with error 390142; the OpenAI Agents SDK replays prior assistant turns in that shape, breaking multi-turn conversations on the second turn. Added _flatten_messages_content helper and wired it into _transform_request_openai to flatten list-form content to strings before sending to Cortex
🔧 CARTO Feature Fix CompleteSummaryRestored 1 CARTO feature (PR #112) by wiring the orphaned _content_to_text_string function into the OpenAI transformation path. The function flattens array-form message content to plain strings, preventing Snowflake Cortex error 390142 on multi-turn conversations. Decisions MadePR #112: fix(snowflake): flatten array-form message content for CortexDecision: The upstream sync completely restructured the file from a single _transform_messages method to dual-endpoint routing (Anthropic vs OpenAI). The _content_to_text_string function was preserved but the call sites were lost. Since Anthropic format natively accepts array content, the fix was applied only to the OpenAI path (_transform_request_openai) which routes to /chat/completions. Added a helper method to iterate messages and flatten list-form content to strings before sending to Cortex. Next Steps:
|
📊 CARTO Feature Analysis StartedMode: Analysis + Auto-fix if issues found Analyzing how each CARTO customization was handled during conflict resolution.
|
✅ CARTO Feature Analysis Complete
Overall Assessment: PASS 📋 Full details in PR description above. |
🔄 Upstream Sync: LiteLLM v1.98.0
Syncs CARTO's LiteLLM fork with upstream stable release v1.98.0.
1.92.0→v1.98.0Caution
Use "Create a merge commit" only. Squashing destroys upstream history and breaks future syncs.
🧪 Pre-Merge Checklist
pyproject.tomlversion matches upstream📊 Release Information (click to expand)
v1.98.01.92.0🔀 Branch Flow (click to expand)
BerriAI/litellm:mainmerged intoCartoDB/litellm:mainupstream-sync/v1.98.0upstream-sync/v1.98.0→carto/main📝 CARTO-Specific File Guidelines (click to expand)
When reviewing or resolving conflicts:
✅ Keep CARTO Versions (Ours)
.github/workflows/carto_*.yaml- CARTO workflows.github/workflows/carto-*.yml- CARTO workflowsCARTO_*.md,docs/CARTO_*.md- CARTO documentation🔄 Accept Upstream (Theirs)
pyproject.toml- Version fieldlitellm/- Core library codetests/- Upstream testsrequirements.txt- DependenciesDockerfile,docker/Dockerfile.non_root- CARTO customizationsMakefile- Check# CARTO:sections🔧 Conflict Resolution (click to expand)
If this PR has conflicts:
Option 1: Automated (Recommended)
The carto-upstream-sync-resolver workflow triggers automatically.
What it does:
carto/main→ ✏️ Resolves conflicts → 🧪 Runs tests → 📌 Pushes to this PRYou just need to: Wait for resolution commits, verify CARTO customizations, merge.
Option 2: Manual Resolution
📚 Documentation Links (click to expand)
🤖 This PR was automatically created by the carto-upstream-sync workflow.
🔧 CARTO Feature Fixes Applied
Status: ✅ Fixed
Features Restored: 1
PR #112: fix(snowflake): flatten array-form message content for Cortex
Fixed: 2026-08-27 22:53:30 UTC
Workflow Run: #43
CARTO Customizations Analysis
Overall Assessment: ✅ PASS
CARTO Feature Preservation Analysis
Summary
Overall Assessment: PASS
All 12 CARTO features are present and properly wired in the resolved state. One feature required a post-merge fix commit but is now fully functional
Post-Merge Fixes Required
00acb9bec6_content_to_text_stringwas defined but never called; added wrapper and wiringFeature Details
Customized Upstream (1)
Snowflake Streaming + Tool Calling (PRs #38, #58)
SnowflakeStreamingHandlerand_extract_system_and_messagespreservedtool_choice_value is None and tools) maintained in Responses API transformationPreserved CARTO (11)
OCI Features:
setdefaultlogic preserved at generic.py:397-399_reorder_tool_results_to_match_tool_callsdefined and called at line 238load_private_key_from_strhelper wired into credential loadingSnowflake Features:
_strip_openai_annotationscalled at lines 273 and 29800acb9bec6; now wired via_flatten_messages_contentAzure Features:
Core Features:
_validate_and_repair_tool_argumentscalled at lines 507 and 549Responses API Features:
_store_session_in_redisand_patch_store_session_in_redisproperly wiredDatabricks Features:
_normalize_empty_tool_call_argumentsand streaming name-chunk fix both present_strip_openai_annotationscalled at line 478Issues Found
None. All features are correctly preserved and wired
Wiring Verification Notes
Per the analysis guidelines, each feature was verified not just for pattern presence but for actual wiring:
The one issue caught (Snowflake array content flattening) was correctly identified and fixed before this analysis via commit
00acb9bec6Feature-by-Feature Breakdown
PR #68: OCI Gemini Tool Call UUIDs
PR #121: OCI Parallel Tool Result Reordering
PR BerriAI#17159: OCI Inline PEM Key Normalization
PR #[38,58]: Snowflake Streaming + Tool Calling
PR #[]: Snowflake Full URL Passthrough
PR #70: Azure URL Suffix Stripping
PR #54: JSON Repair for Streaming Tool Calls
PR #16: Redis Session Storage
PR #111: Snowflake Cortex Claude Function-Calling Follow-up Turns
PR #112: Snowflake Cortex Array Content Flattening
PR #[109,110]: Databricks Empty Tool Call Arguments Normalization
PR #110: Databricks Strip OpenAI Annotations
Analyzed: 2026-08-27 23:05:35 UTC
Workflow Run: #44
Analysis Artifacts: Download JSON/MD
Method: Claude Code (Opus 4.5) post-resolution semantic analysis