feat(ui): add OpenAPI-to-TypeScript type generation pipeline

Add tooling to generate TypeScript types from the backend's OpenAPI
schema, eliminating the need to maintain types in two places. POC
using /key/block as the first route.

- scripts/export_openapi.py: exports FastAPI OpenAPI schema to JSON
- openapi-typescript: generates TS types from the schema
- src/types/key-management.ts: ergonomic alias file for key mgmt types
This commit is contained in:
Ryan Crabbe 2026-03-18 17:23:16 -07:00
parent 488b93c477
commit fa89eb651c
6 changed files with 120851 additions and 2 deletions

62
scripts/export_openapi.py Normal file
View file

@ -0,0 +1,62 @@
"""
Export the LiteLLM proxy's OpenAPI schema to a JSON file.
Usage:
python scripts/export_openapi.py [output_path]
Defaults to ui/litellm-dashboard/openapi.json if no path is given.
Does NOT require a running server — imports the FastAPI app and calls app.openapi().
"""
import json
import sys
from pathlib import Path
from typing import Any
def deduplicate_operation_ids(schema: dict[str, Any]) -> dict[str, Any]:
"""
FastAPI pass-through proxy routes generate duplicate operationIds when
multiple HTTP methods share the same path pattern. This causes
openapi-typescript to emit duplicate TS identifiers.
Fix: append _2, _3, etc. to duplicates.
"""
seen: dict[str, int] = {}
for path_obj in schema.get("paths", {}).values():
for method_obj in path_obj.values():
if not isinstance(method_obj, dict):
continue
op_id = method_obj.get("operationId")
if op_id is None:
continue
count = seen.get(op_id, 0) + 1
seen[op_id] = count
if count > 1:
method_obj["operationId"] = f"{op_id}_{count}"
return schema
def main() -> None:
# Determine output path
repo_root = Path(__file__).resolve().parent.parent
default_output = repo_root / "ui" / "litellm-dashboard" / "openapi.json"
output_path = Path(sys.argv[1]) if len(sys.argv) > 1 else default_output
# Import the FastAPI app — this triggers module-level setup including
# the assignment of app.openapi = get_openapi_schema
from litellm.proxy.proxy_server import app
schema = app.openapi()
schema = deduplicate_operation_ids(schema)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(schema, indent=2) + "\n")
print(f"OpenAPI schema written to {output_path}")
print(f" paths: {len(schema.get('paths', {}))}")
print(f" schemas: {len(schema.get('components', {}).get('schemas', {}))}")
if __name__ == "__main__":
main()

File diff suppressed because one or more lines are too long

View file

@ -62,6 +62,7 @@
"eslint-plugin-unused-imports": "^4.2.0",
"jsdom": "^27.0.0",
"knip": "^5.83.1",
"openapi-typescript": "^7.13.0",
"postcss": "^8.4.33",
"prettier": "3.2.5",
"tailwindcss": "^3.4.1",
@ -428,6 +429,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@ -468,6 +470,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@ -2330,6 +2333,7 @@
"integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"playwright": "1.58.1"
},
@ -2591,6 +2595,59 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@redocly/ajv": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
"integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js-replace": "^1.0.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@redocly/ajv/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
"node_modules/@redocly/config": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz",
"integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@redocly/openapi-core": {
"version": "1.34.11",
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.11.tgz",
"integrity": "sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@redocly/ajv": "8.11.2",
"@redocly/config": "0.22.0",
"colorette": "1.4.0",
"https-proxy-agent": "7.0.6",
"js-levenshtein": "1.1.6",
"js-yaml": "4.1.1",
"minimatch": "5.1.9",
"pluralize": "8.0.0",
"yaml-ast-parser": "0.0.43"
},
"engines": {
"node": ">=18.17.0",
"npm": ">=9.5.0"
}
},
"node_modules/@remixicon/react": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@remixicon/react/-/react-4.9.0.tgz",
@ -3111,6 +3168,7 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@ -3406,6 +3464,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@ -3442,6 +3501,7 @@
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@ -3464,6 +3524,7 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^18.0.0"
}
@ -3543,6 +3604,7 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
@ -4143,6 +4205,7 @@
"integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/utils": "3.2.4",
"fflate": "^0.8.2",
@ -4192,6 +4255,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -4248,6 +4312,16 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -4804,6 +4878,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@ -4961,6 +5036,13 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/change-case": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
"dev": true,
"license": "MIT"
},
"node_modules/character-entities": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
@ -5090,6 +5172,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/colorette": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
"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",
@ -5439,6 +5528,7 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"license": "MIT",
"peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
@ -5448,7 +5538,8 @@
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "4.4.3",
@ -5936,6 +6027,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -6125,6 +6217,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -7281,6 +7374,19 @@
"node": ">=8"
}
},
"node_modules/index-to-position": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
"integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/inline-style-parser": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
@ -7881,10 +7987,21 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
},
"node_modules/js-levenshtein": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -7910,6 +8027,7 @@
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.28",
"@asamuzakjp/dom-selector": "^6.7.6",
@ -9310,6 +9428,7 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"peer": true,
"engines": {
"node": "*"
}
@ -9729,6 +9848,40 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/openapi-typescript": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz",
"integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@redocly/openapi-core": "^1.34.6",
"ansi-colors": "^4.1.3",
"change-case": "^5.4.4",
"parse-json": "^8.3.0",
"supports-color": "^10.2.2",
"yargs-parser": "^21.1.1"
},
"bin": {
"openapi-typescript": "bin/cli.js"
},
"peerDependencies": {
"typescript": "^5.x"
}
},
"node_modules/openapi-typescript/node_modules/supports-color": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@ -9873,6 +10026,24 @@
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
"license": "MIT"
},
"node_modules/parse-json": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
"integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"index-to-position": "^1.1.0",
"type-fest": "^4.39.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parse5": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
@ -10018,6 +10189,16 @@
"node": ">=18"
}
},
"node_modules/pluralize": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@ -10048,6 +10229,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@ -10936,6 +11118,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@ -10975,6 +11158,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@ -12224,6 +12408,7 @@
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@ -12547,6 +12732,19 @@
"node": ">= 0.8.0"
}
},
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typed-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@ -12631,6 +12829,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -12827,6 +13026,13 @@
"punycode": "^2.1.0"
}
},
"node_modules/uri-js-replace": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
"integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
"dev": true,
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -12903,6 +13109,7 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@ -13016,6 +13223,7 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@ -13287,6 +13495,7 @@
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
@ -13329,6 +13538,23 @@
"node": ">=0.4"
}
},
"node_modules/yaml-ast-parser": {
"version": "0.0.43",
"resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
"integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -17,7 +17,10 @@
"e2e": "playwright test --config e2e_tests/playwright.config.ts",
"e2e:ui": "playwright test --ui --config e2e_tests/playwright.config.ts",
"knip": "knip",
"knip:fix": "knip --fix"
"knip:fix": "knip --fix",
"generate:schema": "python ../../scripts/export_openapi.py",
"generate:types": "openapi-typescript ./openapi.json -o src/types/api.generated.ts",
"generate:all": "npm run generate:schema && npm run generate:types"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.54.0",
@ -74,6 +77,7 @@
"eslint-plugin-unused-imports": "^4.2.0",
"jsdom": "^27.0.0",
"knip": "^5.83.1",
"openapi-typescript": "^7.13.0",
"postcss": "^8.4.33",
"prettier": "3.2.5",
"tailwindcss": "^3.4.1",

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
/**
* Key management types — generated from the backend OpenAPI schema.
*
* To regenerate: `npm run generate:all` from ui/litellm-dashboard/
*/
import type { components, paths } from "./api.generated";
// -- /key/block --
export type BlockKeyRequest = components["schemas"]["BlockKeyRequest"];
export type BlockKeyResponse =
paths["/key/block"]["post"]["responses"]["200"]["content"]["application/json"];
// -- /key/unblock --
export type UnblockKeyRequest = components["schemas"]["BlockKeyRequest"]; // same request model
export type UnblockKeyResponse =
paths["/key/unblock"]["post"]["responses"]["200"]["content"]["application/json"];