omit falsy models in agent form#2906
Conversation
dimaMachina
commented
Mar 30, 2026
🦋 Changeset detectedLatest commit: 3c38720 The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
TL;DR — Prevents the agent form from submitting model configuration objects when the user hasn't selected a model. Previously, empty model fields ( Key changes
Summary | 3 files | 4 commits | base:
The fix chains a
|
|
@claude add a changeset |
|
Claude finished @dimaMachina's task in 47s —— View job Adding changeset
Added a |
Co-authored-by: Dimitri POSTOLOV <[email protected]>
There was a problem hiding this comment.
Clean, targeted fix. The transform correctly omits model sections with falsy model values, preventing empty model configurations from being submitted. Downstream consumers (API validation, DAL, processModels in serialize.ts) all handle absent sections safely via optional chaining and their own empty-checks. One minor nit below.
Claude Opus | 𝕏
| .transform(({ base, structuredOutput, summarizer, ...value }) => { | ||
| return { | ||
| ...value, | ||
| ...(base.model && { base }), | ||
| ...(structuredOutput.model && { structuredOutput }), | ||
| ...(summarizer.model && { summarizer }), | ||
| }; |
There was a problem hiding this comment.
Nit: strictObject only defines base, structuredOutput, and summarizer — destructuring all three means value is always {}. The spread is a no-op and adds a small cognitive overhead. Consider simplifying:
| .transform(({ base, structuredOutput, summarizer, ...value }) => { | |
| return { | |
| ...value, | |
| ...(base.model && { base }), | |
| ...(structuredOutput.model && { structuredOutput }), | |
| ...(summarizer.model && { summarizer }), | |
| }; | |
| .transform(({ base, structuredOutput, summarizer }) => ({ | |
| ...(base.model && { base }), | |
| ...(structuredOutput.model && { structuredOutput }), | |
| ...(summarizer.model && { summarizer }), | |
| })); |
| .transform(({ base, structuredOutput, summarizer, ...value }) => { | ||
| return { | ||
| ...value, | ||
| ...(base.model && { base }), |
There was a problem hiding this comment.
Worth noting: if a user sets providerOptions on a section but leaves model empty, the entire section (including providerOptions) is silently dropped here. This is arguably correct — providerOptions without a model is meaningless — but it differs from processModels in serialize.ts which preserves the full models object when any section has non-empty providerOptions. In practice this shouldn't matter since processModels runs after the Zod transform and won't see dropped sections anyway, but wanted to flag the asymmetry.
There was a problem hiding this comment.
PR Review Summary
(2) Total Issues | Risk: Low
🟡 Minor (1) 🟡
Inline Comments:
- 🟡 Minor:
validation.test.ts:113-115Test doesn't validate the new omission behavior — existing tests have populated models, missing explicit test for empty model omission
💭 Consider (1) 💭
Inline Comments:
- 💭 Consider:
validation.ts:81-88Redundant...valuespread —strictObjectonly defines 3 fields, sovalueis always{}
💡 APPROVE WITH SUGGESTIONS
Summary: Clean, targeted fix that correctly omits model sections with falsy model values, preventing empty model configurations from being submitted to the API. The transform logic is correct — empty strings are falsy and whitespace is already trimmed by the upstream ModelSettingsSchema. The downstream processModels function in serialize.ts handles undefined sections safely via optional chaining.
The suggestions above are non-blocking improvements: (1) simplifying the no-op spread for readability, and (2) adding explicit test coverage for the new omission behavior to catch regressions. Both were also flagged by pullfrog's prior review. Nice work! 🎉
Discarded (2)
| Location | Issue | Reason Discarded |
|---|---|---|
validation.ts:65 |
Type/runtime mismatch — transform makes properties optional at runtime but TypeScript type remains required | The API schema (ModelSchema in agents-core) already has all three properties as optional, so runtime optionality matches API expectations. The test file's optional chaining is defensive programming, not evidence of a type problem. The downstream processModels in serialize.ts casts to `AgentModels |
validation.ts:81-87 |
Whitespace-only model strings are truthy and won't be omitted | ModelSettingsSchema applies .trim() during parsing (line 222 in agents-core schemas.ts), so whitespace-only strings become empty strings before the transform runs. Empty strings are correctly omitted. |
Reviewers (3)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-tests |
3 | 0 | 0 | 0 | 1 | 0 | 2 |
pr-review-types |
2 | 0 | 0 | 0 | 0 | 0 | 2 |
pr-review-standards |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 5 | 0 | 0 | 0 | 1 | 0 | 4 |
Note: pr-review-standards found no issues after deep analysis — confirmed the transform logic is correct for empty strings and whitespace.
| .transform(({ base, structuredOutput, summarizer, ...value }) => { | ||
| return { | ||
| ...value, | ||
| ...(base.model && { base }), | ||
| ...(structuredOutput.model && { structuredOutput }), | ||
| ...(summarizer.model && { summarizer }), | ||
| }; | ||
| }); |
There was a problem hiding this comment.
💭 Consider: Redundant ...value spread
Issue: strictObject only defines base, structuredOutput, and summarizer — destructuring all three means value is always {}. The spread is a no-op and adds cognitive overhead.
Why: Minor readability improvement — readers may wonder what other properties are being preserved.
Fix: Simplify (non-blocking):
.transform(({ base, structuredOutput, summarizer }) => ({
...(base.model && { base }),
...(structuredOutput.model && { structuredOutput }),
...(summarizer.model && { summarizer }),
}));Refs:
- Zod strictObject docs — strictObject disallows extra keys, so
...valueis guaranteed empty
| expect(result.data.models.base?.providerOptions).toBe(undefined); | ||
| expect(result.data.models.structuredOutput?.providerOptions).toBe(undefined); | ||
| expect(result.data.models.summarizer?.providerOptions).toBe(undefined); |
There was a problem hiding this comment.
🟡 Minor: Test doesn't validate the new omission behavior
Issue: This test has all three model sections with non-empty model strings (lines 91-100), so the transform keeps all sections rather than omitting them. The optional chaining (?.) masks this gap — assertions pass regardless of whether sections are present.
Why: If the transform logic breaks (e.g., omits models when it shouldn't, or keeps them when it shouldn't), no test would catch it.
Fix: Add explicit tests for the new behavior:
it('should omit model sections when model field is empty', () => {
const result = FullAgentFormSchema.safeParse({
id: '_', name: '_',
statusUpdates: {},
contextConfig: { id: '_', contextVariables: '', headersSchema: '' },
models: {
base: { model: '' },
structuredOutput: { model: '' },
summarizer: { model: '' },
},
subAgents: {}, externalAgents: {}, teamAgents: {}, tools: {},
});
expect(result.success).toBe(true);
expect(result.data?.models.base).toBeUndefined();
expect(result.data?.models.structuredOutput).toBeUndefined();
expect(result.data?.models.summarizer).toBeUndefined();
});Refs:
- validation.ts:81-88 — transform being tested
Ito Test Report ✅12 test cases ran. 12 passed. The unified run passed all 12 of 12 test cases with zero failures, confirming stable save behavior and payload serialization across agent and sub-agent model settings in a non-production local environment. Key findings were that cleared/inherited model fields were correctly omitted and stayed omitted after reload/navigation (including back-forward, refresh, mobile, rapid-save, and two-tab stale-update flows), explicit model selections persisted when set, and provider-options handling was robust with malformed JSON and literal null blocked by validation while empty values were normalized without null serialization or save regressions. ✅ Passed (12)Commit: Tell us how we did: Give Ito Feedback |
* omit falsy models in agent form * Apply suggestion from @dimaMachina * Add changeset for agents-manage-ui patch Co-authored-by: Dimitri POSTOLOV <[email protected]> * fix typecheck --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Dimitri POSTOLOV <[email protected]>