When both stdout and stderr are empty strings, the `if parts else
str(content)` fallback produced the raw dict representation as logs.
Drop the fallback so logs is correctly empty.
stream_chunk_builder uses "last value wins" for list-valued
provider_specific_fields keys. _build_code_interpreter_results was
emitting only new items (incremental), so earlier results were silently
dropped when multiple sequential code executions occurred.
- Emit cumulative list from _build_code_interpreter_results, matching
web_search_results pattern
- Assemble server_tool_use input from input_json_delta deltas at
content_block_stop (Anthropic streams input: {} in start block)
- Handle dict items in _extract_tool_result_output_items after
model_dump() serialization in stream_chunk_builder
- Simplify _merge_provider_specific_fields to last-value-wins for lists,
matching stream_chunk_builder semantics
PR #18945 added support for capturing Anthropic server-side tool results
(bash_code_execution_tool_result, etc.) in provider_specific_fields, but
the data never reached the Responses API output because:
1. Non-streaming: provider_specific_fields wasn't copied into _hidden_params
2. Streaming: chunk delta's provider_specific_fields wasn't accumulated
3. Tool results weren't mapped to standard output items
This fix:
- Copies provider_specific_fields to _hidden_params in transform_response()
- Accumulates provider_specific_fields from streaming chunk deltas
- Maps bash_code_execution_tool_result to code_interpreter_call output items
with code and outputs (matching OpenAI's native shape)
- Removes redundant function_call items for server-side tools
- Adds OutputCodeInterpreterCall type to the output union
Restore backward-compatible behavior: only insert assistant_continue
between directly adjacent user messages, not across tool-call chains.
The _counts_for_alternation skip logic was a silent behavioral change
for [user, assistant(tc), tool, user] sequences.
Made-with: Cursor
- Fix async path: call async_get_chat_completion_prompt in aresponses()
before executor dispatch, mirroring acompletion() in main.py. Discard
merged_optional_params in async path (sync responses() handles them
via local_vars), avoiding TypeError from duplicate kwargs in partial().
- Fix provider re-resolution: replace "/" in model heuristic with
model != original_model comparison so bare model names are handled.
- Add 3 async tests covering hook invocation, optional param
propagation, and non-message item filtering in aresponses().
Made-with: Cursor
- Restore backward compat for leading assistant(tool_calls) — always prepend user_continue
- Replace partial assertions with full list assertion in trailing tool-call test
Made-with: Cursor
Avoid inserting assistant continue messages in the middle of assistant tool_call->tool chains by inserting before the next counted user turn, and add regression coverage for this edge case.
Made-with: Cursor
These were only used in block_key/unblock_key for cache refresh,
which now uses _delete_cache_key_object instead.
Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
- test_block_key_nonexistent_key_returns_404: verifies block_key returns
404 (not misleading 401) when the key doesn't exist in the DB
- test_unblock_key_nonexistent_key_returns_404: same for unblock_key
- test_block_key_existing_key_succeeds: verifies block_key succeeds and
invalidates cache for existing keys
- Update test_unblock_key_supports_both_sk_and_hashed_tokens to reflect
the new cache invalidation pattern (_delete_cache_key_object instead
of get_key_object + _cache_key_object)
Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
The block_key() and unblock_key() handlers previously returned a
misleading 401 'Authentication Error' when the body 'key' didn't exist
in the database, even though authentication (via Authorization header)
succeeded correctly.
Root cause: After auth passed, the handlers called get_key_object() for
cache refresh. This function was designed for auth token lookup and
raises ProxyException(code=401) when a token isn't found. Additionally,
Prisma's update() silently returns None for non-existent records instead
of raising an error, so the code reached get_key_object() without
detecting the missing key.
Fix:
- Add an explicit existence check (find_unique) before the update
- Return 404 ProxyException with 'Key not found' if the key doesn't exist
- Replace get_key_object() + manual cache update with _delete_cache_key_object()
to invalidate the cache (next read will re-fetch from DB)
- Reuse the find_unique result for audit logs, eliminating duplicate queries
Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
Extract duplicate file preview JSX blocks (responses and chat image
previews) into a reusable FilePreviewCard component, reducing ~50
lines of duplicated markup in ChatUI.tsx.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Promote _fetch_managed_vector_stores_by_uuids from @staticmethod to a module-level
async helper get_managed_vector_store_rows_by_uuids, following the same standalone
helper pattern as get_team_object / get_key_object so the hot-path DB read is a
named importable function rather than an inline prisma_client.db.* call
- Pass no-log=True to both inner _call_aresponses sub-calls so they do not fire
independent billing/monitoring callbacks; cost is accumulated in the synthesized
response's _hidden_params for the outer responses() call
- Add test_H11b covering the primary queries (plural array) function-tool schema,
complementing H11 which exercises only the backward-compat singular query path
Made-with: Cursor
On /key/block, /key/unblock, and /key/update, the request body 'key'
field could contaminate the api_key Security dependency, causing the
auth layer to authenticate against the target key instead of the
caller's bearer token. This returned 401 for a nonexistent body key
even when the Authorization header contained a valid master key.
Added a guard in user_api_key_auth that re-reads the Authorization
header directly from the request, ensuring the header is always the
authoritative source for authentication.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Re-add should_use_emulated_file_search() to emulated_handler.py so H5/H6/H7/H13 tests don't fail with ImportError
- Remove per-file-id deduplication from _build_search_results_for_include so all chunks are returned (matching OpenAI native file_search behaviour); update test_H14 to assert 2 results
- Extract raw prisma DB query in check_vector_store_ids_access into a static _fetch_managed_vector_stores_by_uuids helper so the hot request path uses a named, testable function instead of an inline prisma_client.db.* call
- Remove developer-local path from test module docstring
Made-with: Cursor
- Remove dead elif branch in retrieve_api_base derivation
- Replace unreachable try/except httpx.HTTPStatusError around GET
calls with logging inside the status_code check (HTTPHandler.get()
does not call raise_for_status())
- Add comments noting HTTPHandler.get()/AsyncHTTPHandler.get() do not
accept a timeout parameter
Made-with: Cursor
- Document intentional legacy-matching behavior: when user_id is
provided to an org admin, no org filter is applied (returns all of
that user's teams across all orgs, same as legacy endpoint)
- Fix two existing security tests to properly patch user_api_key_cache,
proxy_logging_obj, and get_user_object instead of relying on
incidental error handling
- Add three new org admin test cases:
- Org admin sees org-scoped teams (200 with correct where clause)
- Org admin rejected when filtering by other org (403)
- Org admin with user_id filter returns target user's teams
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pydantic model instances (ResponseFunctionToolCall, etc.) from first_response.output
were included raw in follow_up_input; the transformation layer expects plain dicts and
called .get() on them, raising AttributeError. Serialize via model_dump(exclude_none=True).
Made-with: Cursor
- Remove dead `should_use_emulated_file_search` (main.py uses its own inline guard)
- Remove dead `fallback_vector_store_ids` param from `_run_vector_searches`
- Include all first_response.output items in follow_up_input so text blocks/reasoning
from providers like Anthropic aren't dropped from conversation context
- Accumulate first provider call's response_cost into synthesized _hidden_params so
billing callbacks see the total cost of both emulated-flow LLM calls
- Remove broad tools=[] filter from transformation.py (backward-incompatible); the
follow-up call already passes tools=None which is filtered by the v is not None guard
Made-with: Cursor
- Replace raw find_unique with get_user_object in
_build_team_list_where_conditions for cache/metrics consistency
- Remove over-complex OR clause for org admin + user_id: when user_id
is provided, filter by that user's direct team memberships (same as
regular users) since the access control gate already verified the
org admin's authority
- Preserve caller-supplied organization_id instead of overwriting with
org_admin_org_ids
- Update test mock to match get_user_object call path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- _get_org_admin_org_ids: catch only ValueError (user not found) instead
of bare Exception — DB errors now propagate as 500s instead of silently
demoting org admins to regular users
- _build_team_list_where_conditions: return None (not a sentinel string)
when user has no team memberships; list_team_v2 short-circuits to empty
response without hitting the DB
- Org admin + team_id + user_id: use exact team_id match with org scope
instead of OR expansion that effectively ignored the team_id filter
- Org admin + user_id (no team_id): OR(org teams, direct memberships)
now matches legacy _authorize_and_filter_teams behaviour
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>