fix(api): strip incidental whitespace on logit_bias digit keys - #731
fix(api): strip incidental whitespace on logit_bias digit keys#731seonghobae wants to merge 59 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.
…or Responses parallel true SDK optional defaults often send function.strict and json_schema.strict as null — treat as omit rather than type errors. Align Responses parallel_tool_calls=true with chat by requiring a non-empty tools array.
SDK optional defaults often send description and parameters as JSON null. Treat null as omit rather than type errors; non-null non-string/object values remain fail-closed with invalid_tools.
OpenAI-style tool descriptions are at most 1024 characters. Over-long descriptions fail closed with named invalid_tools so SDKs never believe a truncated description was accepted.
SDK optional participant name blanks ("" / whitespace) are omit-equivalent
like JSON null. Non-string, over-long, and invalid charset names remain
fail-closed with invalid_message_name.
SDK optional defaults serialize omitted tool.function description/parameters/strict as JSON null. Accepting those keys without popping them is not omit-equivalent: proxy_completion forwards the body and several providers reject null parameters. Pop in place so passthrough matches omit; keep non-null wrong types on invalid_tools. Also pop response_format.json_schema.strict null. Tip substrate from #614. Local full unit: 989 passed.
Null flag values on allowed keys (include_usage / include_obfuscation) stay
omit-equivalent. Dropping nulls before the allow-list made {unknown: null}
look empty and silently omit — dishonest for buyers. Fail closed with
invalid_stream_options on chat, Completions, and Responses. Tip substrate
from #638. Local full unit: 996 passed.
…ix/mode/metadata) Parallel tip #653 lacked later omit seams. Restore top_logprobs empty-string omit, tool_calls arguments null→empty string, Responses instructions blank omit, Completions whitespace suffix omit, mode strip, and metadata null value key-omit. Fail-closed paths for nonzero/non-string remain.
#668 restored accept-path 200s but left omit-equivalent keys on the proxied body. Write back empty tool_calls arguments, pop blank Responses instructions, persist cleaned metadata, and hoist chat logprobs/top_logprobs before tools passthrough so providers see the omit-equivalent payload. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
str.isalnum() accepted Unicode letters and digits (café, 名前,
Arabic-Indic digits), so the documented [a-zA-Z0-9_-]{1,64} check still
forwarded illegal names and buyers saw an opaque provider 400.
Require name.isascii() on tool.function.name, tool_calls function.name,
message name, and response_format.json_schema.name (plus 64-char cap on
json_schema.name). HTTP honesty locks Unicode reject and legal keep on
chat and Responses. Request-body fuzz exercises both validators.
Re-lands parallel tip #685/#669 seams onto the highest #686 substrate.
The gateway rejected all non-empty text as invalid_text, so official SDK
defaults (text.format.type=text) and structured json_object/json_schema
never reached the provider. Accept the official plane, pop null/blank
optionals, fail-closed on verbosity and dual-plane text+response_format,
and require ASCII [a-zA-Z0-9_-]{1,64} on flat json_schema names.
Mock echo includes text so HTTP honesty locks forward shape. Re-lands
parallel tip #681 onto the #687 substrate.
OpenAI SDKs send truncation auto/disabled; this gateway has no multi-turn conversation window to truncate, so those values are omit-equivalent. Unknown truncation strings remain fail-closed with invalid_truncation. Also align conversation-controls tests with official text.format text.
Pop null/blank description and null strict inside response_format.json_schema before passthrough, and fail closed on unknown nested keys — parity with Responses text.format structured-output honesty.
Fail closed when tool_choice=required is sent without a non-empty tools array (chat + Responses), matching parallel_tool_calls=true honesty so clients cannot mandate tool use with no tools declared.
Reject blank metadata keys on chat/Responses so cost and observability surfaces never index empty labels; non-empty keys keep the existing 16-entry and length contracts.
…nals SDK clients send service_tier as " AUTO " and routing optionals as null. Casefold after strip so auto/default match; treat routing channel/priority empty and latency_tolerant null as omit. flex/priority and non-boolean latency_tolerant remain fail-closed.
…seed JS SDKs often send 0/1 for booleans and integer seeds as strings. Accept int 0/1 (not True/False via int subclass) for store, stream, and parallel_tool_calls; parse digit strings for seed (Responses passthrough; Completions still fail-closed after type check as unsupported).
Assistants-style tool_resources must not surface as opaque unknown_fields. Accept the key for named invalid_tool_resources; JSON null and empty object are treat-as-omit on chat, Completions, and Responses.
Coerce digit-string n and best_of (write back int) on Completions/chat/ Responses. Accept JS int 0/1 for echo, background, and logprobs via the shared optional-bool helper; digit-string top_logprobs "0" is omit. Tip-substrate from #697; local unit: 1091 passed.
Form/query JS SDKs send store/stream/parallel as \"true\"/\"false\" and sampling knobs as digit strings. Extend optional bool coerce for those strings and int/float digit-string coerce for max_tokens and sampling penalties/temperature/top_p. Non-numeric strings remain fail-closed.
…ws omit JS JSON often serializes integers as 1.0. Coerce whole-number floats (and digit strings for Responses max_output_tokens) on n/best_of/seed/max_* paths. Treat chat stop whitespace as omit-equivalent like empty string. Tip substrate from #703. Local full unit: 1111 passed.
…ons digits Strip+lower orchestration mode aliases so SDK-padded ROUTE/Conduct match. Coerce embeddings dimensions digit strings and whole floats before the existing fail-closed unsupported reject (type-honest invalid path).
SDK clients send 0/"0" when no tool rounds are requested; treat as omit (parity with top_logprobs 0). Nonzero values still fail closed as unsupported; bool is not an integer count.
Accept "1.0"/"0.0" in _coerce_optional_int so JS form encodings match native 1.0 floats. top_logprobs and max_tool_calls zero omit use digit coerce then nonzero reject (parity with tip #711). HTTP honesty coverage for n/best_of/seed/top_logprobs/max_tool_calls float strings. Local unit: 1161 passed.
…, dim 0 Responses reasoning with effort=none (casefold) is an honest no-op. Empty text.format.type alone omits. Completions logprobs whole-float zero strings omit. Embeddings dimensions 0 is omit-equivalent (not applied).
Accept float 0.0/1.0 and whole-float strings ("0.0"/"1.0") in
_coerce_optional_bool so stream/store/parallel_tool_calls/logprobs/echo
and related controls match int 0/1 and "0"/"1" honesty. Non-whole floats
remain fail-closed.
SDK form/JS clients send weight as "0"/"1"/"0.0" and strict/prefix as 0/1/0.0/"true"/"false". Coerce via shared helpers; prefix=true still fail-closed (unsupported).
Form/JS SDKs send User/Function with incidental whitespace. Strip+casefold message roles and tools/tool_choice/tool_calls type to function; developer and function roles still fail closed after casefold with migration errors.
Some SDKs send already-parsed function.arguments objects/arrays. Encode them as compact JSON strings so passthrough matches the OpenAI wire shape; non-JSON scalar types still fail closed.
Form/JS SDKs pad OpenAI wire ids and names. Strip+writeback before length/charset so tool_calls.id, tool_call_id, function.name, message name, and json_schema.name stay honest; blank-after-strip still omit/reject.
Official Responses SDKs send tools as {type,name,parameters,...} without a
nested function object. Accept that shape on chat and Responses while
preserving wire form for passthrough; tool_choice names match flat tools.
Accept official Responses flat tool_choice ({type,name}) in addition to
chat nested {type,function:{name}}. Preserve wire shape, strip/casefold
type, require name match against nested or flat tools, and fail closed
on mixed shapes.
Strip and casefold content part type so TEXT/Image_Url match OpenAI text and image_url. Unknown part types remain fail-closed.
… nested Map Responses-style content part types (input_text, output_text, input_image) onto chat text/image_url. Treat web_search_options with only null/empty nested values as omit; non-empty web search still fails closed as unsupported.
SDK clients send prediction/audio/tool_resources/reasoning objects whose entries are all null or blank. Treat those as omit (parity with web_search_options nested omit); non-empty values still fail closed. Also omit include lists whose items are only null/blank.
Modern OpenAI SDKs send role=developer instead of system. Map it to system so instruction messages still apply; keep function role fail-closed with a migration path to tool.
JS SDKs often send numeric/boolean metadata values; stringify scalars to match OpenAI's string-pair map. Nested objects/arrays still fail closed.
JS/form SDKs often send numeric end-user ids. Mirror metadata scalar coerce: write back string identities on chat/completions/responses/ embeddings; keep empty/object fail-closed and null omit.
OpenAI o-series adds minimal effort; map casefold none/minimal to omit on chat, Completions chat-era surface, and Responses reasoning (no effort plane).
Form/JS SDKs pad model names. Validators stripped for local pool checks but left body["model"] padded, so proxy_completion (tools, response_format, Responses) failed pool match with the padded id. Write back after strip on chat/Completions/Responses/embeddings; call model validation before chat tools passthrough. HTTP honesty tests cover tools + Responses paths.
JS form SDKs send bias values as strings ("-5"). Coerce to float in [-100,100]
for Responses passthrough; Completions still type-checks then rejects non-empty
maps as unsupported.
Form/JS SDKs pad numeric token ids (\" 100 \"). Strip before digit check and write cleaned keys on Responses passthrough. Completions/chat still type-check then fail closed on non-empty maps. Tip substrate from #730. Local full unit: 1270 passed.
|
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. |
|
Important Review skippedToo many files! This PR contains 213 files, which is 113 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (213)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current head333ffcf1fa344ee4a1d1327b40c0bb9ab23bd19a. -
Head SHA:
333ffcf1fa344ee4a1d1327b40c0bb9ab23bd19a -
Workflow run: 32174284722
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
Evidence["OpenCode evidence"] --> Review["Current PR review path"]
Review --> Verify["Required checks"]
OpenCode Review Overview
Pull request overviewOpenCode cannot approve yet because required coverage evidence did not pass. Review outcome1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
Coverage evidenceCoverage evidence job did not run or did not publish coverage evidence. Changed-File Evidence Mapflowchart LR
Evidence["OpenCode evidence"] --> Review["Current PR review path"]
Review --> Verify["Required checks"]
|
Summary
logit_biasmap keys before digit-token-id validation (_coerce_logit_bias_token_key).Test plan
python -m pytest tests -q→ 1270 passed