Skip to content

fix(api): fail-closed stream, model, and sampling range on tools passthrough - #591

Closed
cursor[bot] wants to merge 11 commits into
mainfrom
cursor/bc-fcf74698-983f-4000-8fd9-21de15680524-b335
Closed

fix(api): fail-closed stream, model, and sampling range on tools passthrough#591
cursor[bot] wants to merge 11 commits into
mainfrom
cursor/bc-fcf74698-983f-4000-8fd9-21de15680524-b335

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

Summary

  • Close the request-level fail-open that fix(api): fail-closed role, content, and name on tools passthrough #586 left on the tools/response_format early-return: stream=true, omitted model, stream_options.include_usage=true, and out-of-range temperature / top_p now fail closed before proxy_completion.
  • stream=true with tools or response_format returns named invalid_stream (SSE passthrough is still a follow-up). Set stream=false or omit stream.
  • Missing/unknown model returns named invalid_model instead of a silent pool pick. In-range temperature still forwards on passthrough.
  • Route-path SSE without tools is unchanged.

This branch is #586 (afd8208) plus the request-level hoist. Prefer this head over #582#586. Do not merge those heads.

Test plan

  • python3 tests/test_passthrough_stream_model_http_honesty.py
  • python3 tests/test_message_role_content_name_tools_passthrough_http_honesty.py
  • python3 tests/test_openai_passthrough.py
  • python3 tests/test_chat_stream_options_http_honesty.py
  • CI Full unit + Semgrep product gates

Product gates only: Full unit + Semgrep (Strix ignored). Independent non-author APPROVE still required.

Open in Web View Automation 

seonghobae and others added 11 commits August 16, 2026 21:18
…closed otherwise

Chat history: message-level audio and legacy function_call are null/empty omit
no-ops; non-empty fail closed with named errors (including tools passthrough).
Tip substrate from #577 assistant refusal/annotations honesty. Local full unit:
940 passed.
…ed otherwise

OpenAI fine-tune style message weight is not applied on this gateway.
Accept null/0/1 as honest no-ops; reject other types and values with
invalid_message_weight. Tip substrate from #578. Local full unit: 943 passed.
…ion role

Reject unsupported message keys with named unknown_message_fields (not silent
strip or tools-passthrough smuggle). Reject legacy function role with
invalid_message_role migration to tool. Tip substrate from #579. Local full
unit: 947 passed.
OpenAI partial-assistant prefix flag is not applied on this gateway.
null/false are honest no-ops; true and non-booleans fail closed with
invalid_message_prefix. Tip substrate from #580. Local full unit: 950 passed.
…therwise

Named invalid_max_tool_calls on /v1/chat/completions instead of opaque
unknown_fields. Aligns with Responses max_tool_calls honesty; gateway has
no multi-step tool loop.
Validate weight/prefix/refusal/annotations before the tools early-return so
SDK tool-calling bodies cannot smuggle unsupported message fields. Strip
omit-equivalent max_tool_calls before provider passthrough and name-reject
the field on /v1/completions.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Reject non-OpenAI keys on assistant tool_calls objects with named
unknown_tool_call_fields / unknown_tool_call_function_fields. Accept
optional non-negative index (null omit) for stream-assembled histories.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Developer role, empty user/system content, multimodal part shape, and
participant name were only checked in _validate_messages, which the
tools/response_format early-return skips. SDK tool-calling bodies could
therefore proxy unsupported values. Run the same fail-closed checks
before passthrough and cover the realistic tools+field HTTP cases.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Completions and chat applied temperature, top_p, penalties, and
max_tokens by mutating shared ModelClient defaults. Concurrent
ThreadingHTTPServer workers could observe another request's knobs.
request_sampling stores overrides on threading.local and restores
them on exit; chat/stream_chat read the calling thread first.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Missing, empty, or non-list messages skipped every pre-passthrough
check and still reached proxy_completion when tools or
response_format forced the early-return. Reject those bodies with
invalid_message, and cover response_format+weight plus unknown
tool_calls.function keys on the tools path.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
…through

SDK tool-calling bodies skipped request-level honesty on the
tools/response_format early-return: stream=true returned JSON, omitted
model silently picked a pool agent, and out-of-range temperature/top_p
or include_usage=true were accepted. Reject those with named errors
before proxy_completion; keep route-path SSE without tools.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 15:20
@seonghobae
seonghobae marked this pull request as ready for review August 16, 2026 15:21
@seonghobae
seonghobae enabled auto-merge (squash) August 16, 2026 15:21
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Author

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the unique slice (afd8208..b7f81a6)

The claimed request-level hoist is real. stream=true, omitted/unknown model, stream_options.include_usage=true, and out-of-range temperature / top_p now fail closed inside the tools/response_format early-return, before proxy_completion. Route-path SSE without tools stays 200.

Live loopback POST /v1/chat/completions at b7f81a6 with nonempty tools:

Body Status Code
stream=true 400 invalid_stream
omitted model 400 invalid_model
model=gpt-4o (unknown pool) 400 invalid_model
stream_options.include_usage=true 400 invalid_stream_options
temperature=99 / top_p=2 400 invalid_temperature / invalid_top_p
temperature=0.2 200 chat.completion
stream=true without tools 200 SSE

tests/test_passthrough_stream_model_http_honesty.py is real HTTP honesty, not a stub. Local script is green. This tip is more complete than #593 (cfcbdfe), which hoists only model + stream.

Do not merge this head in parallel

Prefer #596 (7a07562, draft) as the honesty-stack merge candidate. It is a documented superset: empty messages, attribution/routing, and the remaining request knobs (max_tokens, penalties, user, n/seed/stop/logprobs type-and-support checks) that still return 200 on this tip.

Do not merge #582#586, #589, or #593 at their current heads. Independent non-author APPROVE + Full unit / Semgrep are still required on the merge candidate. Product gates only (Strix ignored).

Buyer next action

On tool-calling requests today: omit stream or set stream=false; send a pool model; keep temperature in [0, 2] and top_p in (0, 1]. Do not send stream_options.include_usage=true until SSE passthrough exists. For spend keys and the remaining knobs, wait for #596 or omit them.

CodeRabbit CLI 0.7.3 is installed here but auth status --agent is not_authenticated (browser callback). This review is live-HTTP + source of the unique 3-file slice.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction after comparing #596 / #597

The unique slice on this PR is still correct. Do not merge this head in parallel with #596 or #597.

#596 (7a07562) closes the remaining request knobs, empty messages, and attribution/routing that this tip still leaves 200. It is not a git or functional superset of this head: it drops unknown_tool_call_fields / unknown_tool_call_function_fields / optional index (cbd0420) and thread-local request_sampling (28ef9d2). Live on #596: tools + tool_calls[].smuggle returns 200; the same body is 400 here.

#597 (dbfa0e5) is a smaller spend/messages hoist and also lacks those tool_calls key checks.

Buyer next action: keep sending only id / type / function / optional index on assistant tool_calls. Do not merge #582#597 at their current heads. The landing vehicle must carry this tip's tool_calls key honesty plus #596's remaining knobs.

Independent non-author APPROVE + Full unit / Semgrep still required. Do not self-approve.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Copy link
Copy Markdown
Contributor

Superseded by the #592#597#601#611 landing line. #592 explicitly incorporates this PR’s stream/model/sampling-range hoist and #611 is the current KV/security landing vehicle on the later honesty substrate. Keeping #591 open duplicates an older request-boundary implementation. No checks, reviews, or approvals transfer.

@seonghobae seonghobae closed this Aug 17, 2026
auto-merge was automatically disabled August 17, 2026 13:40

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants