You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two request shapes pass all client-side validation and only fail on the server:
A noul question with neither instructions nor criteria. The API answers 400 Noul question must have criteria or instructions — the rule was live-verified against jev-latest in typesafe-ai/typesafe-sdk-js#6, which also shows that empty criteria ({}) and criteria whose sides are both null count as no criteria.
state=None. The API answers 422 Field required for body.state — null is treated as missing (same live verification).
Both sync and async clients are affected (they share prepare_system_one).
Repro (offline, no API key)
fromtypesafe_sdkimportNoulfromtypesafe_sdk._core.configimportConfigfromtypesafe_sdk._core.endpointsimportprepare_system_onefromtypesafe_sdk._core.response_typesimportSystemOneResponsecfg=Config(api_key="offline", base_url="https://example.invalid",
default_model="jev-latest", timeout=10.0, default_headers={})
prepare_system_one(cfg, "s", {"q": Noul()}, None, None, None, None, SystemOneResponse)
# passes; wire body: {"state":"s","model":"jev-latest","questions":{"q":{"type":"noul"}}}prepare_system_one(cfg, "s", {"q": Noul(criteria={})}, None, None, None, None, SystemOneResponse)
# passes; "criteria":{} — counts as no criteria per the JS issue's live tableprepare_system_one(cfg, "s", {"q": Noul(criteria={"true": None, "false": None})}, None, None, None, None, SystemOneResponse)
# passes; both-null sides count as no criteriaprepare_system_one(cfg, None, {"q": Noul(instructions="x")}, None, None, None, None, SystemOneResponse)
# passes; wire body starts {"state":null,...
I verified the wire bytes locally (typesafe-sdk 0.7.2); the 400/422 refusals are cross-referenced from the JS issue's live verification of the same endpoint.
Where the gap lives
normalize_questions() (_core/questions.py) already fail-fasts on a Score question with empty criteria — there is no matching check that a Noul has instructions or criteria (neither the object path nor the raw-dict path).
prepare_system_one() (_core/endpoints.py) serializes state as-is. The wire SystemOneRequest.state is typed str | dict | list — null isn't even in the wire type — but nothing enforces that on the request path, so a None that is legal for the Python signature goes out as null.
This is the same family as #12 (Choice criteria): the system_one docstring already promises a local TypeSafeError for empty questions and empty score criteria — these are two more holes in that net.
Suggested directions
Extend normalize_questions() with the noul check mirroring the score one (instructions or criteria, where {}/all-null counts as none).
Reject state=None explicitly, or validate the outgoing body against the wire SystemOneRequest before send.
Summary
Two request shapes pass all client-side validation and only fail on the server:
instructionsnorcriteria. The API answers400 Noul question must have criteria or instructions— the rule was live-verified againstjev-latestin typesafe-ai/typesafe-sdk-js#6, which also shows that emptycriteria({}) and criteria whose sides are bothnullcount as no criteria.state=None. The API answers422 Field requiredforbody.state— null is treated as missing (same live verification).Both sync and async clients are affected (they share
prepare_system_one).Repro (offline, no API key)
I verified the wire bytes locally (typesafe-sdk 0.7.2); the 400/422 refusals are cross-referenced from the JS issue's live verification of the same endpoint.
Where the gap lives
normalize_questions()(_core/questions.py) already fail-fasts on a Score question with empty criteria — there is no matching check that a Noul has instructions or criteria (neither the object path nor the raw-dict path).prepare_system_one()(_core/endpoints.py) serializesstateas-is. The wireSystemOneRequest.stateis typedstr | dict | list— null isn't even in the wire type — but nothing enforces that on the request path, so aNonethat is legal for the Python signature goes out asnull.This is the same family as #12 (Choice criteria): the
system_onedocstring already promises a localTypeSafeErrorfor empty questions and empty score criteria — these are two more holes in that net.Suggested directions
normalize_questions()with the noul check mirroring the score one (instructions or criteria, where{}/all-null counts as none).state=Noneexplicitly, or validate the outgoing body against the wireSystemOneRequestbefore send.criteria, but Score questions don't #12 lands the Choice check, these could ride the same pattern.Happy to paste a patch for either direction here.