feat(api): SSE-proxy tools and response_format on chat completions - #606
feat(api): SSE-proxy tools and response_format on chat completions#606cursor[bot] wants to merge 25 commits into
Conversation
…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.
…losed otherwise Legacy /v1/completions treated max_tool_calls as unknown_fields. Accept the key for named invalid_max_tool_calls (null/empty/whitespace omit-equivalent), matching chat/Responses honesty so SDKs get a clear migration path.
SDK clients often send include_usage/include_obfuscation as JSON null. Drop null flag values before validation so null (and null+false mixes) match omit / all-false no-ops on chat, Completions, and Responses. True flags remain fail-closed with invalid_stream_options.
…age honesty Null include_usage/include_obfuscation stay omit-equivalent, but unknown stream_options keys no longer become no-ops just because their value is null. Weight, prefix, refusal, annotations, developer role, empty user/system content, and participant name now use the same named errors on the tools passthrough path as on orchestration. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist stream, required model, stream_options, and temperature/top_p range checks before proxy_completion so a tools or response_format body cannot return a billed JSON completion when the SDK asked for SSE, or silently pick a pool model when model is omitted. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist stream, required model, stream_options, and temperature/top_p range checks before proxy_completion so a tools or response_format body cannot return a billed JSON completion when the SDK asked for SSE, or silently pick a pool model when model is omitted. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist attribution and routing validation before proxy_completion so a tools or response_format body cannot bill a sync completion with an unknown spend dimension or a batch/latency_tolerant hint. Tools passthrough has no batch job plane. Buyer next action: send known sync attribution; omit routing.channel=batch and latency_tolerant=true on tool-calling requests. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Call the orchestration message, max_tokens, attribution, and routing validators before proxy_completion so a tools or response_format body cannot bill a completion with no prompt, crash on a non-object entry, or silently drop unknown spend/routing keys. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Match _validate_messages: tools + user content 123 must 400 invalid_message instead of billing a sync completion. Assistant/tool JSON null stays omit-equivalent. Buyer next action: send user/system content as a non-empty string or a content-parts array. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Match the tools-path honesty contract to the invalid_message behavior so SDK clients send a string or content-parts array. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Passthrough has no batch job plane. Reject routing.channel=batch and latency_tolerant=true before proxy_completion so a tool-calling body cannot bill a silent sync completion. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist the remaining chat request knobs before proxy_completion so an OpenAI SDK tool-calling body cannot bill a sync completion for seed, stop, n>1, logprobs, logit_bias, out-of-range token/penalty values, unsupported reasoning_effort, or a non-default service_tier. Buyer next action: omit those fields on tool-calling requests; they are not applied on this gateway. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Call _validate_messages before proxy_completion so tools + [] / omitted / null / non-list / non-object messages raise invalid_message instead of billing a completion with no prompt. Buyer next action: always send a non-empty messages array of objects on tool-calling requests. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist the orchestration request-knob validators before proxy_completion so a tools or response_format body cannot bill a completion that silently drops seed, stop, n>1, logprobs, logit_bias, or out-of-range penalties. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
stream_chat defaulted to 0.2 even after the HTTP path wrote default_temperature from the request. A streamed invoice summary at temperature=0.8 no longer silently falls back to 0.2. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
… proxy SDK optional defaults serialize omitted fields as JSON null. Accepting those keys without popping them is not omit-equivalent: proxy_completion forwards the body and several providers reject a null JSON Schema object. Pop the keys in place, keep non-null wrong types on invalid_tools, and assert omit-real via mock echo on chat and Responses. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
OpenAI SDK tool callers send stream=true. Returning 400 was honest but left every streaming tool client failing. Pipe a single pool agent as chat.completion.chunk frames so concatenated mock content matches the non-stream JSON body, and pipe live provider text so tool_calls deltas survive. Keep include_usage, empty messages, and unsupported knobs fail-closed. Buyer next action: send stream=true on tool-calling requests; omit stream_options.include_usage. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Keep tools/response_format SSE proxy from this branch and take #601's null tool.function omit-pop, request-knob HTTP cases, and streamed route temperature. Conflict resolution keeps stream=true as SSE, not 400. CHANGELOG records the combined unreleased slice. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
There was a problem hiding this comment.
Stale comment
Next slice already ported — cherry-pick, do not open a parallel stack
This head is the current honesty + SSE-tools tip. Do not merge #582–#605 in parallel.
Remaining fail-open named in the last review: tools + unknown
tool_callsentry/function keys still 200. That hole is closed on #591 (b7f81a6) and already ported as cherry-pick sourcea3b7da0oncursor/bc-b3f7c89f-c445-4d73-a0cd-16d6a8452e1d-1c23(tests/test_tool_calls_entry_keys_http_honesty.py). Also port4e3bc12if this merge dropped thread-localModelClient.request_sampling.Do not merge that cherry-pick branch as a 140-file stack. Put
a3b7da0on this tip, then hoistinclude_orchestration_trace/modetype checks beforeproxy_completion/ SSE proxy.Buyer next action: send only
id/type/function/ optionalindexon assistanttool_calls. Omitstreamonly if you still need JSON; SSE tools passthrough is this tip's unique work. After the key port: rebase #598a50911fKV allowlist onto this head.Independent non-author APPROVE + Full unit / Semgrep still required. Do not self-approve.
Sent by Cursor Automation: Fix Issues
There was a problem hiding this comment.
Stale comment
Review (#606 @
6372c25)Do not merge this draft in parallel with #600 / #601 / #608.
This is the SSE tools tip:
proxy_completion_stream+ live_stream_rawso providerdelta.tool_callssurvive. Keep it as the stream-path landing vehicle. Independent non-author APPROVE + Full unit / Semgrep still required. This automation will not APPROVE or merge.Residual buyer gap on the mock path:
_mock_raw_ssestill emits content chunks andfinish_reason=stopeven whentoolsis present. Offlinemock://tool-calling streams therefore do not look like OpenAI streamed function calls. Live providers are already covered bytest_stream_raw_pipes_tool_call_deltas_verbatim.Do not open another SSE stack. Port only the mock
tool_callsframes (function name from the first tool) onto this head. A discarded parallel at364554foncursor/bc-4cb50318-065a-46bd-aedf-e0abfaf37c2c-8155has that mock plustests/test_passthrough_tools_stream_sse_http_honesty.py— cherry-pick the mock, not the stack.Buyer next action: on this tip, send
stream=truewith a poolmodeland a non-emptymessagesarray; omitstream_options.include_usage. After this lands: unknowntool_callsentry keys (a3b7da0), then rebase #598 / #608 KV allowlist onto this tip.Sent by Cursor Automation: Fix Issues
Unknown assistant tool_calls entry/function fields, non-boolean include_orchestration_trace, and unknown mode used to bill a JSON or SSE completion on the tools proxy. They now return the same named 400s as the orchestration path before the first byte. Buyer next action: send only id/type/function/optional index on assistant tool_calls; send a boolean or omit include_orchestration_trace; send auto/route/conduct or omit mode. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
|
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. |
There was a problem hiding this comment.
Stale comment
Review
#606 at
0717921still framesmock://tools asdelta.content+finish_reason=stop. A realistic invoice-lookup body (tools[0].function.name = lookup_balance, user textINV-9) must emitdelta.tool_callsand finish astool_callsso offline SDK clients match the live_stream_rawpath this PR already preserves.That helper change is on successor #617
63089e4(this branch plus the mocktool_callsframes). Prefer #617 over merging #606. Do not merge #582–#606 in parallel.Buyer next action: point stream clients at #617; send
stream=trueand include the invoice id in the user message.Independent non-author APPROVE + Full unit/Semgrep still required. This automation does not self-approve.
Sent by Cursor Automation: Fix Issues
There was a problem hiding this comment.
Do not merge this tip. Live _stream_raw already preserves delta.tool_calls, but _mock_raw_sse still finishes every tools stream as stop with message.content chunks (contextual_orchestrator/orchestrator.py around the final _frame({}, finish="stop")). An invoice-lookup body (tools[0].function.name = lookup_balance) must emit delta.tool_calls and finish_reason=tool_calls so offline SDK clients match the billed provider stream.
The fix is already on successor #620 (8d7d534): JSON and SSE reconstruct to the same message.tool_calls, INV- binds from the user text (default INV-9), and tool_choice=none stays content/stop.
Buyer next action: review and land #620 instead of this head. Send stream=true on tool-calling requests and put the invoice id in the user message. Do not merge #606 / #609 / #610 / #613 in parallel. Independent non-author APPROVE is still required — this run cannot approve its own stack.
Sent by Cursor Automation: Fix Issues
There was a problem hiding this comment.
Do not merge this tip. _mock_raw_sse still finishes tools as stop with content chunks, so an invoice lookup_balance stream does not match the live _stream_raw delta.tool_calls shape.
Land #620 (baf2e84) instead. That tip already contains this stack plus:
- mock JSON/SSE
tool_calls(INV-binds from the user text, defaultINV-9;tool_choice=nonestays content/stop) - streamed route
top_p/ penalties matchingchat() - APA 7th citations (OpenAI function calling; Schick et al., 2023; Yao et al., 2023; Holtzman et al., 2020)
#617 is the same functional mock+sampling slice without those citations. Do not merge #606 / #609 / #610 / #613 / #617 in parallel with #620.
Buyer next action: review #620, send stream=true on tool-calling requests, and put the invoice id in the user message. Independent non-author APPROVE is still required.
Sent by Cursor Automation: Fix Issues


Summary
Successor to #596 at
7a07562. Keeps that head's fail-closed empty messages, spend/routing hoist, and request-knob honesty, and closes the buyer-visible gap those drafts still list as residual:tools/response_format+stream=trueused to return400 invalid_stream(or, before that, a billed JSON completion while the SDK waited for SSE).TaskOrchestrator.proxy_completion_stream/ModelClient.proxy_stream_sendSSE-proxy a single pool agent.chat.completion.chunkwhose concatenated content equals the non-stream JSONmessage.content.delta.tool_callssurvive (content-only parsers drop them).seed/stop/n>1/logprobs, batch routing, andstream_options.include_usage=truestill fail closed before the first byte./v1/responsesstreaming stays rejected.Buyer next action: send
stream=trueon tool-calling requests when the client reads SSE; omitstream_options.include_usage; still send a non-emptymessagesarray of objects and known sync attribution.Prefer this head over #582–#587, #589, #591–#597. Do not merge those in parallel. Independent non-author APPROVE + Full unit/Semgrep still required.
Test plan
python3 tests/test_passthrough_sse_tools_http_honesty.pypython3 tests/test_passthrough_stream_model_http_honesty.pypython3 tests/test_passthrough_attribution_routing_http_honesty.pypython3 tests/test_chat_messages_array_tools_passthrough_http_honesty.pypython3 tests/test_message_honesty_tools_passthrough_http_honesty.pypython3 tests/test_openai_passthrough.pytest_true_streaming.pypython3 tests/test_paper_contracts.pytest_self_check.pytest_conventions.pytest_api_contract.pyDocs
docs/rest_api_design.mdhonesty contract (APA: OpenAI, 2024; WHATWG SSE).docs/library_research.mdPonytail note: stdlib urllib, no httpx/sse-starlette.stream=trueon tool-calling requests.