Skip to content

Conversation

@owenlin0
Copy link
Contributor

@owenlin0 owenlin0 commented Oct 30, 2025

We had this annotation everywhere in app-server APIs which made it so that fields get serialized as field?: T, meaning if the field as None we would omit the field in the payload. Removing this annotation changes it so that we return field: T | null instead, which makes codex app-server's API more aligned with the convention of public OpenAI APIs like Responses.

Separately, remove the #[ts(optional_fields = nullable)] annotations that were recently added which made all the TS types become field?: T | null which is not great since clients need to handle undefined and null.

I think generally it'll be best to have optional types be either:

I updated @etraut-openai's unit test to check that all generated TS types are one or the other, not both (so will error if we have a type that has field?: T | null). I don't think there's currently a good use case for that - but we can always revisit.

@owenlin0 owenlin0 force-pushed the owen/clean_up_skip_serializing_if_none branch from c2ac3f8 to 24a8b03 Compare October 30, 2025 03:02
@owenlin0 owenlin0 changed the title Owen/clean up skip serializing if none Oct 30, 2025
@owenlin0 owenlin0 marked this pull request as ready for review October 30, 2025 03:28
@owenlin0 owenlin0 changed the title [app-server] remove serde(skip_serializing_if = "Option::is_none") for responses and objects Oct 30, 2025
@owenlin0 owenlin0 force-pushed the owen/clean_up_skip_serializing_if_none branch from 02b292f to 534c5d1 Compare October 30, 2025 16:58
@owenlin0 owenlin0 force-pushed the owen/clean_up_skip_serializing_if_none branch from 534c5d1 to c8186a4 Compare October 30, 2025 16:58
"params": {
"pageSize": null,
"cursor": null
}
Copy link
Contributor Author

@owenlin0 owenlin0 Oct 30, 2025

Choose a reason for hiding this comment

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

FYI app-server can still take this as a valid request:

                "method": "model/list",
                "id": 6,
                "params": {}

Even though this is what the struct serializes to in this test, this is independent from how codex app-server deserializes the incoming request.

return RustProp(prop_name, serde_str)

if is_optional and serde_str:
ts_str = "#[ts(optional)]"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

@etraut-openai etraut-openai left a comment

Choose a reason for hiding this comment

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

Overall, looks good. I added a couple of questions.

id: String,
summary: Vec<ReasoningItemReasoningSummary>,
#[serde(default, skip_serializing_if = "should_serialize_reasoning_content")]
#[ts(optional = nullable)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the difference between #[ts(optional = nullable)] and #[ts(optional)]? Just want to make sure this change was intended.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#[ts(optional = nullable)] generates field?: T | null

whereas #[ts(optional)] generates field: T | null

id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
#[ts(optional)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm curious why this field gets a #[ts(optional)] but other Option<String> fields in the same struct do not. Just want to make sure that's intended.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh this is a really good question

just checked, it looks like fields marked #[serde(skip_serializing)] will still be rendered in the TS definition, which is wrong... we need to omit those fields. I'll look into this. Good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I only added #[ts(optional)] for fields with skip_serializing_if = "Option::is_none")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated so that fields with #[serde(skip_serializing)] also have #[ts(skip)]

@owenlin0 owenlin0 enabled auto-merge (squash) October 30, 2025 18:18
@owenlin0 owenlin0 merged commit 89c0061 into main Oct 30, 2025
25 checks passed
@owenlin0 owenlin0 deleted the owen/clean_up_skip_serializing_if_none branch October 30, 2025 18:18
@github-actions github-actions bot locked and limited conversation to collaborators Oct 30, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

3 participants