refactor(dashboard): migrate to zod 4 and openai 6 (#44345)

* refactor(dashboard): migrate to zod 4 and openai 6

Bump the dashboard to real zod 4.6.5 and openai 6.49.0 so every module
imports from bare "zod" instead of "zod/v4". Ports the ten files that
still used the zod 3 API (error params, record, passthrough, strict,
email/date validators, union discriminator codes) and adapts the
LiteAdmin tool schemas and form plumbing where openai 6 and zod 4
changed behaviour.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* style(dashboard): prettier format zod 4 schema files

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(dashboard): restore system one missing state message under zod 4

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-03 06:53:41 +00:00 • committed by GitHub
parent d96e56c76f
commit 0c86d6bfc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
73 changed files with 360 additions and 478 deletions

View file

@ -58,6 +58,10 @@ const eslintConfig = [
message:
"@tremor/react is being phased out; build new UI with shadcn/ui primitives instead of adding tremor imports.",
},
{
group: ["zod/*"],
message: 'Import Zod from "zod"; the dashboard uses Zod 4 only.',
},
],
},
],

View file

@ -28,7 +28,7 @@
"next": "16.3.6",
"next-themes": "^0.4.6",
"nuqs": "^2.9.4",
"openai": "4.104.0",
"openai": "6.49.0",
"openapi-fetch": "^0.17.0",
"openapi-react-query": "^0.5.4",
"papaparse": "5.5.3",
@ -44,7 +44,7 @@
"sonner": "2.0.8",
"tailwind-merge": "3.4.0",
"uuid": "14.0.0",
"zod": "3.25.76"
"zod": "4.6.5"
},
"devDependencies": {
"@eslint/js": "9.39.2",
@ -3780,16 +3780,6 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.4"
}
},
"node_modules/@types/papaparse": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",
@ -4547,18 +4537,6 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@ -4592,18 +4570,6 @@
"node": ">= 14"
}
},
"node_modules/agentkeepalive": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
"license": "MIT",
"dependencies": {
"humanize-ms": "^1.2.1"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@ -4880,12 +4846,6 @@
"node": ">= 0.4"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@ -5047,6 +5007,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@ -5241,18 +5202,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@ -5645,15 +5594,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@ -5710,6 +5650,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@ -5834,6 +5775,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -5843,6 +5785,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -5887,6 +5830,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@ -5899,6 +5843,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@ -6545,15 +6490,6 @@
"node": ">=0.10.0"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
@ -6765,28 +6701,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/form-data": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
"license": "MIT"
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
@ -6811,19 +6725,6 @@
"node": ">=18.3.0"
}
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"license": "MIT",
"dependencies": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
},
"engines": {
"node": ">= 12.20"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@ -6843,6 +6744,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -6903,6 +6805,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@ -6927,6 +6830,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@ -7014,6 +6918,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -7085,6 +6990,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -7097,6 +7003,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@ -7112,6 +7019,7 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@ -7325,15 +7233,6 @@
"node": ">= 14"
}
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -8239,16 +8138,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/knip/node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@ -8684,6 +8573,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -9578,27 +9468,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@ -9774,26 +9643,6 @@
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@ -9823,48 +9672,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-fetch/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/node-fetch/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/node-fetch/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/node-releases": {
"version": "2.0.54",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz",
@ -10049,27 +9856,27 @@
}
},
"node_modules/openai": {
"version": "4.104.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
"integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
"version": "6.49.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.49.0.tgz",
"integrity": "sha512-aYCc0C6L864eR6WSYIwQGyXriw/nIyZx0ObvhzOEVuk0zoBDpynjSbrionWI7q65B5H8jJX0DXR9snEzM6bfPg==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7"
},
"bin": {
"openai": "bin/cli"
},
"peerDependencies": {
"@aws-sdk/credential-provider-node": ">=3.972.0 <4",
"@smithy/hash-node": ">=4.3.0 <5",
"@smithy/signature-v4": ">=5.4.0 <6",
"ws": "^8.18.0",
"zod": "^3.23.8"
"zod": "^3.25 || ^4.0"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-provider-node": {
"optional": true
},
"@smithy/hash-node": {
"optional": true
},
"@smithy/signature-v4": {
"optional": true
},
"ws": {
"optional": true
},
@ -10078,21 +9885,6 @@
}
}
},
"node_modules/openai/node_modules/@types/node": {
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/openai/node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/openapi-fetch": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.17.0.tgz",
@ -12575,15 +12367,6 @@
"node": "20 || >=22"
}
},
"node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/webidl-conversions": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
@ -12836,9 +12619,9 @@
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz",
"integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"

View file

@ -44,7 +44,7 @@
"next": "16.3.6",
"next-themes": "^0.4.6",
"nuqs": "^2.9.4",
"openai": "4.104.0",
"openai": "6.49.0",
"openapi-fetch": "^0.17.0",
"openapi-react-query": "^0.5.4",
"papaparse": "5.5.3",
@ -60,7 +60,7 @@
"sonner": "2.0.8",
"tailwind-merge": "3.4.0",
"uuid": "14.0.0",
"zod": "3.25.76"
"zod": "4.6.5"
},
"devDependencies": {
"@eslint/js": "9.39.2",

View file

@ -2,7 +2,7 @@
import { BotIcon, InfoIcon, LayersIcon, ServerIcon } from "lucide-react";
import type { UseFormReturn } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents";
import { useMCPServers } from "@/app/(dashboard)/hooks/mcpServers/useMCPServers";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
export const accessGroupCreateSchema = z.object({
name: z.string().refine((value) => value.trim() !== "", "Please enter the access group name"),

View file

@ -30,7 +30,7 @@ import {
type SSOSettingsFormValues,
} from "@/components/Settings/AdminSettings/SSOSettings/Modals/BaseSSOSettingsForm";
import UIAccessControlForm from "@/components/UIAccessControlForm";
import { z } from "zod/v4";
import { z } from "zod";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";
import { Input } from "@/components/ui/input";

View file

@ -13,6 +13,7 @@ export const IDENTITY_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a
const stringGrants = (fallback: string[]) =>
z
.unknown()
.optional()
.transform((value) =>
Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string") : fallback,
);

View file

@ -1,6 +1,6 @@
import { ChevronRight } from "lucide-react";
import React from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { useCreateBudget } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import { applyBudgetPrecision } from "./budgetPrecision";
import { toast } from "@/lib/toast";

View file

@ -2,7 +2,7 @@
import React, { useState } from "react";
import { CircleAlert } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { Alert, AlertTitle } from "@/components/shared/Alert";
import { PasswordInput } from "@/components/shared/PasswordInput";

View file

@ -2,7 +2,7 @@
import React, { useCallback, useEffect, useState } from "react";
import { CircleHelp } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { createGuardrailCall, getGuardrailsList } from "@/components/networking";

View file

@ -17,7 +17,7 @@ import {
InfoIcon,
CircleHelp,
} from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import {
listGuardrailSubmissions,
approveGuardrailSubmission,

View file

@ -1,5 +1,5 @@
import React, { useState, useCallback } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { SortingState } from "@tanstack/react-table";
import { Inbox, Plus, X } from "lucide-react";
import { useMCPToolsets } from "@/app/(dashboard)/hooks/mcpServers/useMCPToolsets";

View file

@ -1,7 +1,7 @@
import React from "react";
import { CircleAlert, Info } from "lucide-react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { z } from "zod/v4";
import { z } from "zod";
import { MCPServer, MCPUserEnvVarsStatus, MCPUserEnvVarSpec } from "@/components/mcp_tools/types";
import { clearMCPUserEnvVars, getMCPUserEnvVars, storeMCPUserEnvVars } from "@/components/networking";
import { toast } from "@/lib/toast";

View file

@ -2,7 +2,7 @@
import { CircleHelp } from "lucide-react";
import React, { useEffect, useState } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import type { MemoryRow } from "@/components/networking";
import { FieldGroup } from "@/components/ui/field";

View file

@ -2,7 +2,7 @@
import { CircleHelp } from "lucide-react";
import React from "react";
import { z } from "zod/v4";
import { z } from "zod";
import BudgetDurationDropdown from "@/components/common_components/budget_duration_dropdown";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";

View file

@ -3,89 +3,79 @@ import { z } from "zod";
const MAX_CHOICE_OPTIONS = 255;
const nonEmptyString = (message: string) =>
z.string({ invalid_type_error: message }).refine((value) => value.trim().length > 0, message);
z.string({ error: message }).refine((value) => value.trim().length > 0, message);
const instructions = z.string({
required_error: "Required property 'instructions' is missing.",
invalid_type_error: "Instructions must be a string.",
error: (iss) =>
iss.input === undefined ? "Required property 'instructions' is missing." : "Instructions must be a string.",
});
const choiceQuestionSchema = z
.object({
type: z.literal("choice"),
instructions,
criteria: z
.record(z.string({ invalid_type_error: "Choice descriptions must be strings." }), {
required_error: "Choice criteria must be an object mapping labels to descriptions.",
invalid_type_error: "Choice criteria must be an object mapping labels to descriptions.",
})
.refine((criteria) => {
const count = Object.keys(criteria).length;
return count >= 1 && count <= MAX_CHOICE_OPTIONS;
}, `Choice criteria must contain between 1 and ${MAX_CHOICE_OPTIONS} options.`),
})
.passthrough();
const choiceQuestionSchema = z.looseObject({
type: z.literal("choice"),
instructions,
criteria: z
.record(z.string(), z.string({ error: "Choice descriptions must be strings." }), {
error: "Choice criteria must be an object mapping labels to descriptions.",
})
.refine((criteria) => {
const count = Object.keys(criteria).length;
return count >= 1 && count <= MAX_CHOICE_OPTIONS;
}, `Choice criteria must contain between 1 and ${MAX_CHOICE_OPTIONS} options.`),
});
const noulQuestionSchema = z
.object({
type: z.literal("noul"),
instructions,
criteria: z
.object(
{
true: z.string({ invalid_type_error: "Noul true criteria must be a string." }).optional(),
false: z.string({ invalid_type_error: "Noul false criteria must be a string." }).optional(),
},
{ invalid_type_error: "Noul criteria, when provided, must be an object." },
)
.passthrough()
.optional(),
})
.passthrough();
const noulQuestionSchema = z.looseObject({
type: z.literal("noul"),
instructions,
criteria: z
.looseObject(
{
true: z.string({ error: "Noul true criteria must be a string." }).optional(),
false: z.string({ error: "Noul false criteria must be a string." }).optional(),
},
{ error: "Noul criteria, when provided, must be an object." },
)
.optional(),
});
const scoreQuestionSchema = z
.object({
type: z.literal("score"),
instructions,
criteria: z
.array(z.string({ invalid_type_error: "Score levels must be strings." }), {
required_error: "Score criteria must be an array of levels.",
invalid_type_error: "Score criteria must be an array of levels.",
})
.min(2, "Score criteria must contain at least 2 levels."),
})
.passthrough();
const scoreQuestionSchema = z.looseObject({
type: z.literal("score"),
instructions,
criteria: z
.array(z.string({ error: "Score levels must be strings." }), {
error: "Score criteria must be an array of levels.",
})
.min(2, "Score criteria must contain at least 2 levels."),
});
const QUESTION_ERROR_MESSAGES: Partial<Record<z.ZodIssueCode, string>> = {
[z.ZodIssueCode.invalid_union_discriminator]: "Question type must be choice, noul, or score.",
[z.ZodIssueCode.invalid_type]: "Question must be an object.",
const QUESTION_ERROR_MESSAGES: Partial<Record<z.core.$ZodIssue["code"], string>> = {
invalid_union: "Question type must be choice, noul, or score.",
invalid_type: "Question must be an object.",
};
const questionSchema = z.discriminatedUnion("type", [choiceQuestionSchema, noulQuestionSchema, scoreQuestionSchema], {
errorMap: (issue, context) => ({ message: QUESTION_ERROR_MESSAGES[issue.code] ?? context.defaultError }),
error: (iss) => QUESTION_ERROR_MESSAGES[iss.code],
});
export const systemOneRequestSchema = z
.object(
{
model: nonEmptyString("Model must be a non-empty string.").optional(),
state: z.unknown(),
questions: z
.record(questionSchema, {
required_error: "Required property 'questions' is missing.",
invalid_type_error: "Questions must be a non-empty object.",
})
.refine((questions) => Object.keys(questions).length > 0, "At least one question is required."),
},
{ invalid_type_error: "Payload must be a JSON object." },
)
.passthrough()
.refine((request) => "state" in request, { message: "Required property 'state' is missing.", path: ["state"] });
export const systemOneRequestSchema = z.looseObject(
{
model: nonEmptyString("Model must be a non-empty string.").optional(),
state: z.unknown().nonoptional({ error: "Required property 'state' is missing." }),
questions: z
.record(z.string(), questionSchema, {
error: (iss) =>
iss.input === undefined
? "Required property 'questions' is missing."
: "Questions must be a non-empty object.",
})
.refine((questions) => Object.keys(questions).length > 0, "At least one question is required."),
},
{ error: "Payload must be a JSON object." },
);
const probability = z.number().finite().min(0).max(1);
const probabilities = z.record(probability);
const probabilities = z.record(z.string(), probability);
const noulAnswerSchema = z.object({ type: z.literal("noul"), noul: probability }).passthrough();
const noulAnswerSchema = z.looseObject({ type: z.literal("noul"), noul: probability });
const choiceAnswerShape = {
type: z.literal("choice"),
@ -93,26 +83,27 @@ const choiceAnswerShape = {
confidence: probability.optional(),
probabilities,
};
const choiceAnswerSchema = z.object(choiceAnswerShape).passthrough();
const choiceAnswerSchema = z.looseObject(choiceAnswerShape);
const scoreAnswerShape = {
type: z.literal("score"),
score: z.number().finite(),
confidence: probability.optional(),
legend: z.record(z.string()).optional(),
legend: z.record(z.string(), z.string()).optional(),
probabilities,
};
const scoreAnswerSchema = z.object(scoreAnswerShape).passthrough();
const scoreAnswerSchema = z.looseObject(scoreAnswerShape);
const tokenCount = z.number().finite().nonnegative();
export const systemOneResponseSchema = z
.object({
model: nonEmptyString("Model must be a non-empty string.").nullish(),
answers: z.record(z.discriminatedUnion("type", [noulAnswerSchema, choiceAnswerSchema, scoreAnswerSchema])),
usage: z.object({ input_tokens: tokenCount, output_tokens: tokenCount }).optional(),
})
.passthrough();
export const systemOneResponseSchema = z.looseObject({
model: nonEmptyString("Model must be a non-empty string.").nullish(),
answers: z.record(
z.string(),
z.discriminatedUnion("type", [noulAnswerSchema, choiceAnswerSchema, scoreAnswerSchema]),
),
usage: z.object({ input_tokens: tokenCount, output_tokens: tokenCount }).optional(),
});
export type SystemOneRequest = z.infer<typeof systemOneRequestSchema>;
export type SystemOneQuestion = z.infer<typeof questionSchema>;

View file

@ -23,37 +23,56 @@ describe("validateSystemOnePayload", () => {
});
it("requires an object root", () => {
expect(validateSystemOnePayload("[]").issues[0]?.path).toBe("root");
expect(validateSystemOnePayload("[]").issues).toContainEqual(
expect.objectContaining({ path: "root", message: "Payload must be a JSON object.", severity: "error" }),
);
});
it("requires the state key while accepting null state", () => {
expect(validateSystemOnePayload(requestWith({ state: undefined })).issues).toContainEqual(
expect.objectContaining({ path: "state", severity: "error" }),
expect.objectContaining({
path: "state",
message: "Required property 'state' is missing.",
severity: "error",
}),
);
expect(validateSystemOnePayload(requestWith({ state: null })).isValid).toBe(true);
});
it.each([null, "", 123])("requires a non-empty string model when present: %s", (model) => {
expect(validateSystemOnePayload(requestWith({ model })).issues).toContainEqual(
expect.objectContaining({ path: "model", severity: "error" }),
expect.objectContaining({ path: "model", message: "Model must be a non-empty string.", severity: "error" }),
);
});
it("requires questions", () => {
expect(validateSystemOnePayload(JSON.stringify({ state: null })).issues).toContainEqual(
expect.objectContaining({ path: "questions", severity: "error" }),
expect.objectContaining({
path: "questions",
message: "Required property 'questions' is missing.",
severity: "error",
}),
);
});
it.each([null, [], "questions", {}])("requires a non-empty questions object: %s", (questions) => {
it.each([
[null, "Questions must be a non-empty object."],
[[], "Questions must be a non-empty object."],
["questions", "Questions must be a non-empty object."],
[{}, "At least one question is required."],
])("requires a non-empty questions object: %s", (questions, message) => {
expect(validateSystemOnePayload(requestWith({ questions })).issues).toContainEqual(
expect.objectContaining({ path: "questions", severity: "error" }),
expect.objectContaining({ path: "questions", message, severity: "error" }),
);
});
it.each([null, [], "question"])("requires each question to be an object: %s", (question) => {
expect(validateSystemOnePayload(requestWith({ questions: { invalid: question } })).issues).toContainEqual(
expect.objectContaining({ path: "questions.invalid", severity: "error" }),
expect.objectContaining({
path: "questions.invalid",
message: "Question must be an object.",
severity: "error",
}),
);
});
@ -61,12 +80,22 @@ describe("validateSystemOnePayload", () => {
expect(
validateSystemOnePayload(requestWith({ questions: { invalid: { type: "other", instructions: "Do this" } } }))
.issues,
).toContainEqual(expect.objectContaining({ path: "questions.invalid.type", severity: "error" }));
).toContainEqual(
expect.objectContaining({
path: "questions.invalid.type",
message: "Question type must be choice, noul, or score.",
severity: "error",
}),
);
});
it("requires instructions", () => {
expect(validateSystemOnePayload(requestWith({ questions: { category: { type: "noul" } } })).issues).toContainEqual(
expect.objectContaining({ path: "questions.category.instructions", severity: "error" }),
expect.objectContaining({
path: "questions.category.instructions",
message: "Required property 'instructions' is missing.",
severity: "error",
}),
);
});
@ -78,7 +107,11 @@ describe("validateSystemOnePayload", () => {
);
expect(result.isValid).toBe(false);
expect(result.issues).toContainEqual(
expect.objectContaining({ path: "questions.category.instructions", severity: "error" }),
expect.objectContaining({
path: "questions.category.instructions",
message: "Instructions must be a string.",
severity: "error",
}),
);
});
@ -87,7 +120,13 @@ describe("validateSystemOnePayload", () => {
validateSystemOnePayload(
requestWith({ questions: { category: { type: "choice", instructions: "Route", criteria } } }),
).issues,
).toContainEqual(expect.objectContaining({ path: "questions.category.criteria", severity: "error" }));
).toContainEqual(
expect.objectContaining({
path: "questions.category.criteria",
message: "Choice criteria must be an object mapping labels to descriptions.",
severity: "error",
}),
);
});
it("requires choice descriptions to be strings", () => {
@ -98,7 +137,11 @@ describe("validateSystemOnePayload", () => {
);
expect(result.isValid).toBe(false);
expect(result.issues).toContainEqual(
expect.objectContaining({ path: "questions.category.criteria.support", severity: "error" }),
expect.objectContaining({
path: "questions.category.criteria.support",
message: "Choice descriptions must be strings.",
severity: "error",
}),
);
});
@ -109,7 +152,13 @@ describe("validateSystemOnePayload", () => {
validateSystemOnePayload(
requestWith({ questions: { category: { type: "choice", instructions: "Route", criteria } } }),
).issues,
).toContainEqual(expect.objectContaining({ path: "questions.category.criteria", severity: "error" }));
).toContainEqual(
expect.objectContaining({
path: "questions.category.criteria",
message: "Choice criteria must contain between 1 and 255 options.",
severity: "error",
}),
);
},
);
@ -137,7 +186,13 @@ describe("validateSystemOnePayload", () => {
validateSystemOnePayload(
requestWith({ questions: { urgency: { type: "score", instructions: "Rate urgency", criteria } } }),
).issues,
).toContainEqual(expect.objectContaining({ path: "questions.urgency.criteria", severity: "error" }));
).toContainEqual(
expect.objectContaining({
path: "questions.urgency.criteria",
message: "Score criteria must be an array of levels.",
severity: "error",
}),
);
});
it("requires at least two score levels", () => {
@ -145,7 +200,13 @@ describe("validateSystemOnePayload", () => {
validateSystemOnePayload(
requestWith({ questions: { urgency: { type: "score", instructions: "Rate urgency", criteria: ["Low"] } } }),
).issues,
).toContainEqual(expect.objectContaining({ path: "questions.urgency.criteria", severity: "error" }));
).toContainEqual(
expect.objectContaining({
path: "questions.urgency.criteria",
message: "Score criteria must contain at least 2 levels.",
severity: "error",
}),
);
});
it("requires score levels to be strings", () => {
@ -158,7 +219,11 @@ describe("validateSystemOnePayload", () => {
);
expect(result.isValid).toBe(false);
expect(result.issues).toContainEqual(
expect.objectContaining({ path: "questions.urgency.criteria.1", severity: "error" }),
expect.objectContaining({
path: "questions.urgency.criteria.1",
message: "Score levels must be strings.",
severity: "error",
}),
);
});
@ -176,7 +241,11 @@ describe("validateSystemOnePayload", () => {
);
expect(result.isValid).toBe(true);
expect(result.issues).toContainEqual(
expect.objectContaining({ path: "questions.urgency.criteria", severity: "warning" }),
expect.objectContaining({
path: "questions.urgency.criteria",
message: "More than 10 score levels may reduce result quality.",
severity: "warning",
}),
);
});
@ -195,7 +264,13 @@ describe("validateSystemOnePayload", () => {
validateSystemOnePayload(
requestWith({ questions: { escalation: { type: "noul", instructions: "Escalate?", criteria } } }),
).issues,
).toContainEqual(expect.objectContaining({ path: "questions.escalation.criteria", severity: "error" }));
).toContainEqual(
expect.objectContaining({
path: "questions.escalation.criteria",
message: "Noul criteria, when provided, must be an object.",
severity: "error",
}),
);
});
it("allows omitted noul criteria", () => {
@ -220,8 +295,16 @@ describe("validateSystemOnePayload", () => {
expect(result.isValid).toBe(false);
expect(result.issues).toEqual(
expect.arrayContaining([
expect.objectContaining({ path: "questions.escalation.criteria.true", severity: "error" }),
expect.objectContaining({ path: "questions.escalation.criteria.false", severity: "error" }),
expect.objectContaining({
path: "questions.escalation.criteria.true",
message: "Noul true criteria must be a string.",
severity: "error",
}),
expect.objectContaining({
path: "questions.escalation.criteria.false",
message: "Noul false criteria must be a string.",
severity: "error",
}),
]),
);
});

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { CircleHelp } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import { Policy } from "@/components/policies/types";
import { teamListCall, keyListCall, modelAvailableCall, estimateAttachmentImpactCall } from "@/components/networking";
import { toast } from "@/lib/toast";

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { Policy, PolicyCreateRequest, PolicyUpdateRequest } from "@/components/policies/types";
import { Guardrail } from "@/components/guardrails/types";
import { getResolvedGuardrails, modelAvailableCall } from "@/components/networking";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
export const ALL_TEAM_MODELS = "all-team-models";

View file

@ -1,6 +1,6 @@
import React, { useRef, useState } from "react";
import { Upload as UploadIcon, X } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import { convertPromptFileToJson, createPromptCall } from "@/components/networking";
import { toast } from "@/lib/toast";
import { Field, FieldDescription, FieldGroup, FieldSeparator, FieldTitle } from "@/components/ui/field";

View file

@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query";
import { CircleHelp } from "lucide-react";
import React, { useCallback, useMemo, useState } from "react";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { Logo } from "@/components/molecules/logo/Logo";
import { toast } from "@/lib/toast";
import { createSearchTool, fetchAvailableSearchProviders } from "@/components/networking";

View file

@ -1,7 +1,7 @@
import { isAdminRole } from "@/utils/roles";
import { useQuery } from "@tanstack/react-query";
import React, { useState } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import { toast } from "@/lib/toast";
import {

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { CircleHelp } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import { toast } from "@/lib/toast";
import { registerClaudeCodePlugin } from "@/components/networking";
import { FieldGroup } from "@/components/ui/field";

View file

@ -2,7 +2,7 @@
import { ChevronRight, CircleHelp } from "lucide-react";
import React from "react";
import { z } from "zod/v4";
import { z } from "zod";
import BudgetDurationDropdown from "@/components/common_components/budget_duration_dropdown";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";

View file

@ -2,7 +2,7 @@
import React, { useState, useEffect } from "react";
import { SimpleTooltip } from "@/components/ui/tooltip";
import { z } from "zod/v4";
import { z } from "zod";
import { fetchUserModels } from "@/components/organisms/create_key_button";
import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
import { tagInfoCall, tagUpdateCall } from "@/components/networking";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
import type { components } from "@/lib/http/schema";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
const isBlank = (value: string): boolean => value.trim() === "";

View file

@ -1,5 +1,5 @@
import React, { useMemo, useState } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { all_admin_roles } from "@/utils/roles";
import BudgetDurationDropdown from "@/components/common_components/budget_duration_dropdown";
import { ModelMaxBudget, ModelMaxBudgetField } from "@/components/key_team_helpers/ModelMaxBudgetEditor";

View file

@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { CircleHelp, Eye, EyeOff, Info } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { CredentialItem, vectorStoreCreateCall } from "@/components/networking";
import {
VectorStoreProviders,

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { ArrowLeft, CircleHelp, Lock } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import {
vectorStoreInfoCall,
vectorStoreUpdateCall,

View file

@ -21,7 +21,7 @@ import { consumeReturnUrl, getLoginUrl, getReturnUrl, isValidReturnUrl } from "@
import { CircleAlert, Info, TriangleAlert, X } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useId, useState } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { useWorker } from "@/hooks/useWorker";
const loginSchema = z.object({

View file

@ -1,6 +1,6 @@
import React from "react";
import { CircleAlert, Info } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
import { PasswordInput } from "@/components/shared/PasswordInput";
import { Field, FieldLabel, FieldGroup } from "@/components/ui/field";

View file

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { useCloudZeroCreate } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroCreate";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";

View file

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { useCloudZeroUpdateSettings } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroSettings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";

View file

@ -5,7 +5,7 @@ import { CircleHelp } from "lucide-react";
import type { Dayjs } from "dayjs";
import * as React from "react";
import { useForm, type Resolver } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { TagsInput } from "@/app/(dashboard)/guardrails/_components/content_filter/TagsInput";
import { FormField } from "@/components/shared/form/FormField";

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { keyCreateCall } from "./networking";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { CircleAlert, CirclePlus, Copy, Info, KeyRound, Link } from "lucide-react";

View file

@ -5,7 +5,7 @@ import { useUpdateCyberArkConfig } from "@/app/(dashboard)/hooks/configOverrides
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { toast } from "@/lib/toast";
import React, { useMemo } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";
import { PasswordInput } from "@/components/shared/PasswordInput";

View file

@ -5,7 +5,7 @@ import { useUpdateHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOve
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { toast } from "@/lib/toast";
import React, { useMemo } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";
import { PasswordInput } from "@/components/shared/PasswordInput";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
const IPV4 = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}";
const SEG = "[a-fA-F\\d]{1,4}";

View file

@ -2,7 +2,7 @@
import React from "react";
import { FormProvider, useFormContext, useWatch, type UseFormReturn } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { ssoProviderLogoMap, ssoProviderDisplayNames } from "../constants";
import { Logo } from "@/components/molecules/logo/Logo";
import { FieldGroup } from "@/components/ui/field";

View file

@ -1,7 +1,7 @@
"use client";
import { Controller } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import { useUpdateUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUpdateUISettings";

View file

@ -19,7 +19,7 @@ import { TagsInput } from "@/app/(dashboard)/guardrails/_components/content_filt
import { TabsContent } from "@/components/ui/tabs";
import { ChevronDown, Plus, Users } from "lucide-react";
import React, { useEffect, useMemo, useState } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { PageHeader, PageHeaderControls, PageHeaderDescription, PageHeaderTitle } from "@/components/shared/PageHeader";
import { ToolbarSeparator } from "@/components/shared/ToolbarSeparator";

View file

@ -1,7 +1,7 @@
import { CircleHelp } from "lucide-react";
import React, { useEffect, useState } from "react";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";

View file

@ -4,7 +4,7 @@ import { getForecastConfigError, isForecastClassifier } from "../add_model/forec
import React, { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";

View file

@ -24,11 +24,11 @@ export const buildSavedJevConnectionTestRequest = (
const result = z
.object({
classifier_type: z.enum(["jev", "oss_classifier"]),
tiers: z.record(z.unknown()),
tiers: z.record(z.string(), z.unknown()),
jev_classifier_config: jevClassifierConfigSchema.optional(),
opensource_classifier_config: jevClassifierConfigSchema.optional(),
})
.passthrough()
.loose()
.safeParse(parsed);
if (!result.success) return undefined;
const { jev_classifier_config, opensource_classifier_config, ...config } = result.data;

View file

@ -16,7 +16,8 @@ const jevClassifierConfigFields = {
instructions: z
.string()
.nullish()
.transform((value) => value ?? undefined),
.transform((value) => value ?? undefined)
.optional(),
circuit_breaker_enabled: z.boolean().optional(),
circuit_breaker_cooldown_seconds: z.number().finite().positive().optional(),
};
@ -34,7 +35,7 @@ export const jevClassifierConfigSchema = z
!config.provider ||
config.provider === "jev" ||
OSS_CLASSIFIER_MODELS[config.provider].some((model) => model === config.model),
{ message: "Select a supported classifier model", path: ["model"] },
{ error: "Select a supported classifier model", path: ["model"] },
);
export type JevClassifierConfig = z.infer<typeof jevClassifierConfigSchema>;

View file

@ -4,7 +4,7 @@ import React, { useState } from "react";
import { CircleHelp, Info, Plug } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { createPassThroughEndpoint } from "./networking";
import NumericalInput from "./shared/numerical_input";

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { CircleCheck, FileDown } from "lucide-react";
import { z } from "zod/v4";
import { z } from "zod";
import { getGlobalLitellmHeaderName } from "@/components/networking";
import { toast } from "@/lib/toast";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";

View file

@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, expect, it, vi } from "vitest";
import { z } from "zod/v4";
import { z } from "zod";
import { TeamMetadataField } from "@/app/(dashboard)/hooks/teams/useTeamMetadataSchema";
import { useZodForm } from "@/lib/forms/useZodForm";
import MetadataKeyValueFields, {

View file

@ -8,7 +8,7 @@ import {
type FieldValues,
type UseFormGetValues,
} from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { TeamMetadataField } from "@/app/(dashboard)/hooks/teams/useTeamMetadataSchema";
import { FormField } from "@/components/shared/form/FormField";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
const sharedShape = {
auto_router_name: z.string().min(1, "Auto router name is required"),

View file

@ -1,4 +1,3 @@
import "openai/shims/web";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

View file

@ -250,7 +250,7 @@ describe("fixed management operations", () => {
it("provides strict JSON schemas for nullable and transformed tool arguments", async () => {
const model = transport([completion()]);
await runLiteAdmin(options(), model.client);
const tools = model.requests[0].tools!;
const tools = model.requests[0].tools!.filter((tool) => tool.type === "function");
for (const tool of tools) expect(tool.function.strict).toBe(true);
const createUser = tools.find((tool) => tool.function.name === "user_create")!;
const expectedSchema = {

View file

@ -105,7 +105,7 @@ export async function runLiteAdmin(options: LiteAdminOptions, client?: OpenAI):
max_tokens: 2_048,
};
const runnerOptions = { signal: options.signal, maxChatCompletions: 6, maxRetries: 0, timeout: 60_000 };
const runner = modelClient.beta.chat.completions.runTools(parameters, runnerOptions);
const runner = modelClient.chat.completions.runTools(parameters, runnerOptions);
runner.on("chatCompletion", (completion) => {
active();
const message = completion.choices[0]?.message;

View file

@ -27,8 +27,12 @@ export interface OperationContext {
type Schemas = components["schemas"];
const text = z.string().min(1).max(200);
const hash = z.string().regex(/^[a-f0-9]{64}$/i, "Use the key hash from a lookup, not a raw API key.");
const optional = <Schema extends z.ZodType<unknown>>(schema: Schema) =>
schema.nullable().transform((value) => value ?? undefined);
const optional = <Schema extends z.ZodType<unknown>>(schema: Schema) => schema.nullable();
type NullsStripped<T> = { [K in keyof T]: Exclude<T[K], null> };
const stripNulls = <T extends Record<string, unknown>>(args: T): NullsStripped<T> =>
Object.fromEntries(
Object.entries(args).map(([key, value]) => [key, value === null ? undefined : value]),
) as NullsStripped<T>;
const optionalText = optional(text);
const amount = optional(z.number().finite().nonnegative());
const limit = optional(z.number().int().nonnegative());
@ -47,15 +51,15 @@ const keyFields = {
};
const teamFields = { team_alias: optionalText, organization_id: optionalText, models, ...limits };
const userFields = {
user_email: optional(z.string().email().max(200)),
user_email: optional(z.email().max(200)),
user_alias: optionalText,
user_role: optional(z.enum(["proxy_admin", "proxy_admin_viewer", "internal_user", "internal_user_viewer"])),
models,
...limits,
};
const object = <Shape extends z.ZodRawShape>(shape: Shape) => z.object(shape).strict();
const object = <Shape extends z.ZodRawShape>(shape: Shape) => z.strictObject(shape);
const dated = <Shape extends z.ZodRawShape>(shape: Shape) =>
object({ start_date: z.string().date(), end_date: z.string().date(), ...shape }).refine((value) => {
object({ start_date: z.iso.date(), end_date: z.iso.date(), ...shape }).refine((value: Record<string, unknown>) => {
if (typeof value.start_date !== "string" || typeof value.end_date !== "string") return false;
const days = (Date.parse(value.end_date) - Date.parse(value.start_date)) / 86_400_000;
return days >= 0 && days <= 366;
@ -108,18 +112,19 @@ export function createLiteAdminOperations(context: OperationContext) {
};
const auth = { accessToken: context.accessToken, signal: context.signal };
const operation =
(mode: "read" | "write" | "delete", kind: ResultKind) =>
(mode: "read" | "write" | "delete", kind: ResultKind, extraArguments?: Record<string, unknown>) =>
<Schema extends z.ZodType<Record<string, unknown>>>(
name: string,
title: string,
schema: Schema,
execute: (args: z.output<Schema>) => Promise<unknown>,
execute: (args: NullsStripped<z.output<Schema>>) => Promise<unknown>,
) => {
const definition = {
name,
description: `${title}. Null means omitted or unchanged. ${mode === "read" ? "Read-only." : "Requires the administrator to review and confirm the change."}`,
parameters: schema,
function: async (args: z.output<Schema>) => {
function: async (rawArgs: unknown) => {
const args = stripNulls(rawArgs as z.output<Schema>);
active();
context.beforeTool();
const action: LiteAdminAction | undefined =
@ -129,7 +134,10 @@ export function createLiteAdminOperations(context: OperationContext) {
id: crypto.randomUUID(),
name,
title,
arguments: Object.fromEntries(Object.entries(args).filter(([, value]) => value !== undefined)),
arguments: {
...Object.fromEntries(Object.entries(args).filter(([, value]) => value !== undefined)),
...extraArguments,
},
destructive: mode === "delete",
};
if (action) {
@ -140,7 +148,7 @@ export function createLiteAdminOperations(context: OperationContext) {
const outcome = await Promise.resolve()
.then(() => {
active();
return execute(args);
return execute({ ...args, ...extraArguments } as NullsStripped<z.output<Schema>>);
})
.then(
(value) => ({ ok: true, value }) as const,
@ -256,10 +264,10 @@ export function createLiteAdminOperations(context: OperationContext) {
operation("read", "user")("user_info", "View a user", object({ user_id: text }), (a) =>
apiClient.get<unknown>("/v2/user/info", { ...auth, query: a }),
),
operation("write", "user")(
operation("write", "user", { auto_create_key: false })(
"user_create",
"Create a user without a key",
object({ user_id: optionalText, ...userFields }).transform((a) => ({ ...a, auto_create_key: false })),
object({ user_id: optionalText, ...userFields }),
(a) => apiClient.post<unknown>("/user/new", { ...auth, body: a satisfies Partial<Schemas["NewUserRequest"]> }),
),
operation("write", "user")("user_update", "Update a user", object({ user_id: text, ...userFields }), (a) =>

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
import { MCPServer, MCPToolset } from "../mcp_tools/types";
import { MCP_ALL_TOOLS_WILDCARD } from "../mcp_tools/constants";

View file

@ -1,5 +1,5 @@
import React from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";

View file

@ -6,7 +6,7 @@ import { Check, CircleHelp, Copy, RefreshCw, TriangleAlert } from "lucide-react"
import React, { useEffect, useMemo, useState } from "react";
import { useWatch } from "react-hook-form";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { z } from "zod/v4";
import { z } from "zod";
import { KeyResponse } from "../key_team_helpers/key_list";
import { toast } from "@/lib/toast";
import { regenerateKeyCall } from "../networking";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
import type { components } from "@/lib/http/schema";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
import type { Organization } from "@/components/networking";
import type { components } from "@/lib/http/schema";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
const isBlank = (value: string): boolean => value.trim() === "";

View file

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { updatePassThroughEndpoint, deletePassThroughEndpointsCall } from "./networking";
import { Eye, EyeOff } from "lucide-react";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import RoutePreview from "./route_preview";
import { toast } from "@/lib/toast";
import PassThroughSecuritySection from "./common_components/PassThroughSecuritySection";

View file

@ -2,7 +2,7 @@
import React, { useEffect, useMemo } from "react";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { FieldGroup } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";
import {

View file

@ -4,7 +4,7 @@ import userEvent from "@testing-library/user-event";
import * as React from "react";
import { useForm } from "react-hook-form";
import { describe, expect, it, vi } from "vitest";
import { z } from "zod/v4";
import { z } from "zod";
import { Input } from "@/components/ui/input";

View file

@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from "react";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { z } from "zod/v4";
import { z } from "zod";
import NumericalInput from "../shared/numerical_input";
import BudgetDurationDropdown from "../common_components/budget_duration_dropdown";
import { FieldGroup } from "@/components/ui/field";

View file

@ -2,7 +2,7 @@
import { Save } from "lucide-react";
import { useWatch } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";

View file

@ -48,7 +48,7 @@ import { toast } from "@/lib/toast";
import { CheckIcon, ChevronDown, CircleMinus, CopyIcon, Info, Pencil, Plus, Save } from "lucide-react";
import React, { useEffect, useMemo, useState } from "react";
import { useFieldArray } from "react-hook-form";
import { z } from "zod/v4";
import { z } from "zod";
import GuardrailsSelect from "./GuardrailsSelect";
import {
type CallerEditAccess,

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
export const TEAM_ADMIN_EDITABLE_TEAM_FIELDS_SETTING = "team_admin_editable_team_fields";

View file

@ -1,4 +1,4 @@
import { z } from "zod/v4";
import { z } from "zod";
import { KeyResponse } from "../key_team_helpers/key_list";
import { extractLoggingSettings, formatMetadataForDisplay, stripTagsFromMetadata } from "../key_info_utils";
@ -122,50 +122,52 @@ export const toKeyEditFormValues = (keyData: KeyResponse): KeyEditFormValues =>
});
export const keyEditFormSchema = z.object({
key_alias: z.custom<string | undefined>(),
models: z.custom<string[] | undefined>(),
allowed_routes: z.custom<string | undefined>(),
max_budget: z.custom<number | string | null | undefined>(),
soft_budget: z.custom<number | string | null | undefined>(),
budget_duration: z.custom<string | null | undefined>(),
tpm_limit: z.custom<number | string | null | undefined>(),
tpm_limit_type: z.custom<string | null | undefined>(),
rpm_limit: z.custom<number | string | null | undefined>(),
rpm_limit_type: z.custom<string | null | undefined>(),
tpd_limit: z.custom<number | string | null | undefined>(),
throttle_on_budget_exceeded: z.custom<boolean | undefined>(),
enable_prompt_caching: z.custom<boolean | undefined>(),
max_parallel_requests: z.custom<number | string | null | undefined>(),
model_tpm_limit: z.custom<string | undefined>(),
model_rpm_limit: z.custom<string | undefined>(),
key_alias: z.custom<string | undefined>().optional(),
models: z.custom<string[] | undefined>().optional(),
allowed_routes: z.custom<string | undefined>().optional(),
max_budget: z.custom<number | string | null | undefined>().optional(),
soft_budget: z.custom<number | string | null | undefined>().optional(),
budget_duration: z.custom<string | null | undefined>().optional(),
tpm_limit: z.custom<number | string | null | undefined>().optional(),
tpm_limit_type: z.custom<string | null | undefined>().optional(),
rpm_limit: z.custom<number | string | null | undefined>().optional(),
rpm_limit_type: z.custom<string | null | undefined>().optional(),
tpd_limit: z.custom<number | string | null | undefined>().optional(),
throttle_on_budget_exceeded: z.custom<boolean | undefined>().optional(),
enable_prompt_caching: z.custom<boolean | undefined>().optional(),
max_parallel_requests: z.custom<number | string | null | undefined>().optional(),
model_tpm_limit: z.custom<string | undefined>().optional(),
model_rpm_limit: z.custom<string | undefined>().optional(),
default_estimated_output_tokens: z
.custom<number | string | null | undefined>()
.refine(estimateChecks.positive.isValid, estimateChecks.positive.message),
.refine(estimateChecks.positive.isValid, estimateChecks.positive.message)
.optional(),
default_estimated_output_tokens_per_model: z
.custom<string | undefined>()
.refine(estimateChecks.perModel.isValid, estimateChecks.perModel.message),
guardrails: z.custom<string[] | undefined>(),
disable_global_guardrails: z.custom<boolean | undefined>(),
policies: z.custom<string[] | undefined>(),
tags: z.custom<string[] | undefined>(),
prompts: z.custom<string[] | undefined>(),
access_group_ids: z.custom<string[] | undefined>(),
allowed_passthrough_routes: z.custom<string[] | undefined>(),
vector_stores: z.custom<string[] | undefined>(),
mcp_servers_and_groups: z.custom<McpServersAndGroups | undefined>(),
mcp_tool_permissions: z.custom<Record<string, string[]> | undefined>(),
agents_and_groups: z.custom<AgentsAndGroups | undefined>(),
skills: z.custom<string[] | undefined>(),
organization_id: z.custom<string | null | undefined>(),
team_id: z.custom<string | null | undefined>(),
.refine(estimateChecks.perModel.isValid, estimateChecks.perModel.message)
.optional(),
guardrails: z.custom<string[] | undefined>().optional(),
disable_global_guardrails: z.custom<boolean | undefined>().optional(),
policies: z.custom<string[] | undefined>().optional(),
tags: z.custom<string[] | undefined>().optional(),
prompts: z.custom<string[] | undefined>().optional(),
access_group_ids: z.custom<string[] | undefined>().optional(),
allowed_passthrough_routes: z.custom<string[] | undefined>().optional(),
vector_stores: z.custom<string[] | undefined>().optional(),
mcp_servers_and_groups: z.custom<McpServersAndGroups | undefined>().optional(),
mcp_tool_permissions: z.custom<Record<string, string[]> | undefined>().optional(),
agents_and_groups: z.custom<AgentsAndGroups | undefined>().optional(),
skills: z.custom<string[] | undefined>().optional(),
organization_id: z.custom<string | null | undefined>().optional(),
team_id: z.custom<string | null | undefined>().optional(),
project_id: z.string().nullable().optional(),
logging_settings: z.custom<unknown[] | undefined>(),
metadata: z.custom<string | undefined>(),
duration: z.custom<string | null | undefined>(),
token: z.custom<string | undefined>(),
disabled_callbacks: z.custom<string[] | undefined>(),
auto_rotate: z.custom<boolean | undefined>(),
rotation_interval: z.custom<string | undefined>(),
logging_settings: z.custom<unknown[] | undefined>().optional(),
metadata: z.custom<string | undefined>().optional(),
duration: z.custom<string | null | undefined>().optional(),
token: z.custom<string | undefined>().optional(),
disabled_callbacks: z.custom<string[] | undefined>().optional(),
auto_rotate: z.custom<boolean | undefined>().optional(),
rotation_interval: z.custom<string | undefined>().optional(),
});
export interface MountedFieldGates {

View file

@ -1,6 +1,6 @@
import { TriangleAlert } from "lucide-react";
import { useState } from "react";
import { z } from "zod/v4";
import { z } from "zod";
import { modelPatchUpdateCall } from "./networking";
import { toast } from "@/lib/toast";
import { FieldGroup } from "@/components/ui/field";

View file

@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import * as React from "react";
import { describe, expect, it, vi } from "vitest";
import { z } from "zod/v4";
import { z } from "zod";
import { useZodForm } from "./useZodForm";

View file

@ -2,17 +2,27 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, type FieldValues, type UseFormProps, type UseFormReturn } from "react-hook-form";
import type { $ZodType } from "zod/v4/core";
import { z } from "zod";
type FormValuesOf<T> = Extract<T, FieldValues>;
// TInput is what the widgets hold (strings), TOutput is what handleSubmit receives after
// zod runs coerce/transform; typing useForm with z.infer would collapse the two and lie
// about defaultValues. zod's $ZodType declares them as <Output, Input>, in that order.
// The `unknown` in the middle is RHF's context generic, which we never use.
export const useZodForm = <TInput extends FieldValues, TOutput extends FieldValues>(
schema: $ZodType<TOutput, TInput>,
// about defaultValues. The `unknown` in the middle is RHF's context generic, which we never use.
export function useZodForm<Schema extends z.core.$ZodType>(
schema: Schema,
props?: Omit<UseFormProps<FormValuesOf<z.input<Schema>>, unknown, FormValuesOf<z.output<Schema>>>, "resolver">,
): UseFormReturn<FormValuesOf<z.input<Schema>>, unknown, FormValuesOf<z.output<Schema>>>;
export function useZodForm<TInput extends FieldValues, TOutput extends FieldValues>(
schema: z.core.$ZodType<TOutput, TInput>,
props?: Omit<UseFormProps<TInput, unknown, TOutput>, "resolver">,
): UseFormReturn<TInput, unknown, TOutput> =>
useForm<TInput, unknown, TOutput>({
): UseFormReturn<TInput, unknown, TOutput>;
export function useZodForm(
schema: z.core.$ZodType,
props?: Omit<UseFormProps<FieldValues, unknown, FieldValues>, "resolver">,
): UseFormReturn<FieldValues, unknown, FieldValues> {
return useForm<FieldValues, unknown, FieldValues>({
...props,
resolver: zodResolver(schema),
resolver: zodResolver(schema as z.core.$ZodType<FieldValues, FieldValues>),
});
}