diff --git a/package-lock.json b/package-lock.json index 2c7857a..021faee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@astrojs/preact": "^5.0.2", "@fontsource-variable/archivo": "^5.3.0", "@fontsource/jetbrains-mono": "^5.3.0", + "@scalar/api-reference": "^1.64.0", "@tailwindcss/vite": "^4.2.2", "astro": "^6.0.8", "astro-i18n": "^2.2.4", @@ -38,6 +39,69 @@ "node": ">=22.12.0" } }, + "node_modules/@ai-sdk/gateway": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.13.tgz", + "integrity": "sha512-g7nE4PFtngOZNZSy1lOPpkC+FAiHxqBJXqyRMEG7NUrEVZlz5goBdtHg1YgWRJIX776JTXAmbOI5JreAKVAsVA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.2", + "@ai-sdk/provider-utils": "4.0.5", + "@vercel/oidc": "3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.2.tgz", + "integrity": "sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.5.tgz", + "integrity": "sha512-Ow/X/SEkeExTTc1x+nYLB9ZHK2WUId8+9TlkamAx7Tl9vxU+cKzWx2dwjgMHeCN6twrgwkLrrtqckQeO4mxgVA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.2", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/vue": { + "version": "3.0.33", + "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-3.0.33.tgz", + "integrity": "sha512-czM9Js3a7f+Eo35gjEYEeJYUoPvMg5Dfi4bOLyDBghLqn0gaVg8yTmTaSuHCg+3K/+1xPjyXd4+2XcQIohWWiQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "4.0.5", + "ai": "6.0.33", + "swrv": "^1.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "vue": "^3.3.4" + } + }, "node_modules/@astrojs/check": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.8.tgz", @@ -451,18 +515,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -491,12 +555,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -587,13 +651,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -761,6 +825,160 @@ "devOptional": true, "license": "MIT OR Apache-2.0" }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.4.tgz", + "integrity": "sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.8.tgz", + "integrity": "sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1271,6 +1489,80 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/core/node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.9.tgz", + "integrity": "sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4", + "@floating-ui/utils": "^0.2.10", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@fontsource-variable/archivo": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource-variable/archivo/-/archivo-5.3.0.tgz", @@ -1289,6 +1581,33 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@headlessui/tailwindcss": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz", + "integrity": "sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "tailwindcss": "^3.0 || ^4.0" + } + }, + "node_modules/@headlessui/vue": { + "version": "1.7.23", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", + "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==", + "license": "MIT", + "dependencies": { + "@tanstack/vue-virtual": "^3.0.0-beta.60" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -1754,6 +2073,24 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@internationalized/date": { + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.3.tgz", + "integrity": "sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz", + "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1799,6 +2136,102 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.6.tgz", + "integrity": "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz", + "integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==", + "license": "MIT" + }, "node_modules/@mdx-js/mdx": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", @@ -1845,12 +2278,27 @@ "@types/estree": "^1.0.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", "license": "MIT" }, + "node_modules/@phosphor-icons/core": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@phosphor-icons/core/-/core-2.1.1.tgz", + "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==", + "license": "MIT" + }, "node_modules/@poppinss/colors": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", @@ -1988,6 +2436,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@replit/codemirror-css-color-picker": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@replit/codemirror-css-color-picker/-/codemirror-css-color-picker-6.3.0.tgz", + "integrity": "sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", @@ -2335,51 +2794,565 @@ "win32" ] }, - "node_modules/@shikijs/core": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", - "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", + "node_modules/@scalar/agent-chat": { + "version": "0.12.23", + "resolved": "https://registry.npmjs.org/@scalar/agent-chat/-/agent-chat-0.12.23.tgz", + "integrity": "sha512-jBddLcMpID7MFoDzEMRXDiu+OIbzgU8OzdJNFXrpndDlsEssC3gDMz+2ztgIqwrXPOir8CMXFt66K+jFs15ZKQ==", + "license": "MIT", + "dependencies": { + "@ai-sdk/vue": "3.0.33", + "@scalar/api-client": "3.14.0", + "@scalar/components": "0.27.10", + "@scalar/helpers": "0.9.2", + "@scalar/icons": "0.7.5", + "@scalar/json-magic": "0.12.19", + "@scalar/openapi-types": "0.9.4", + "@scalar/schemas": "0.8.0", + "@scalar/themes": "0.17.2", + "@scalar/types": "0.17.0", + "@scalar/use-toasts": "0.10.4", + "@scalar/validation": "0.6.2", + "@scalar/workspace-store": "0.56.0", + "@vueuse/core": "13.9.0", + "ai": "6.0.33", + "js-base64": "^3.7.8", + "neverpanic": "0.0.8", + "truncate-json": "3.0.1", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-client": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@scalar/api-client/-/api-client-3.14.0.tgz", + "integrity": "sha512-GX97fACHV2ht3/IpSUYJw7ozrDtszcBw+O0uKl6XlD33aKvzCHqvNRLvf/JgLGeZpyqFkJEh/jsBJwPqBUarsA==", + "license": "MIT", + "dependencies": { + "@headlessui/tailwindcss": "^0.2.2", + "@headlessui/vue": "1.7.23", + "@scalar/blocks": "0.1.9", + "@scalar/components": "0.27.10", + "@scalar/helpers": "0.9.2", + "@scalar/icons": "0.7.5", + "@scalar/oas-utils": "0.19.9", + "@scalar/openapi-types": "0.9.4", + "@scalar/sidebar": "0.9.34", + "@scalar/snippetz": "0.9.24", + "@scalar/themes": "0.17.2", + "@scalar/typebox": "^0.1.3", + "@scalar/types": "0.17.0", + "@scalar/use-codemirror": "0.14.14", + "@scalar/use-hooks": "0.4.9", + "@scalar/use-toasts": "0.10.4", + "@scalar/workspace-store": "0.56.0", + "@vueuse/core": "13.9.0", + "@vueuse/integrations": "13.9.0", + "focus-trap": "^7.8.0", + "fuse.js": "^7.1.0", + "js-base64": "^3.7.8", + "jsonc-parser": "3.3.1", + "nanoid": "^5.1.6", + "pretty-ms": "^9.3.0", + "radix-vue": "^1.9.17", + "set-cookie-parser": "3.1.0", + "vue": "^3.5.30", + "yaml": "^2.8.3", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-client/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/@scalar/api-client/node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "@shikijs/primitive": "4.0.2", - "@shikijs/types": "4.0.2", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.5" + "bin": { + "nanoid": "bin/nanoid.js" }, "engines": { - "node": ">=20" + "node": "^18 || >=20" } }, - "node_modules/@shikijs/engine-javascript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", - "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", + "node_modules/@scalar/api-reference": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@scalar/api-reference/-/api-reference-1.64.0.tgz", + "integrity": "sha512-CFtFWX+X8XJvrSoa1OGPpTzf7p81sbL10fBSCiRO/nN3tKUc3IqEhL0EYzK+K3HThApTOEgtfSY/3bIS2mA6ew==", + "license": "MIT", + "dependencies": { + "@headlessui/vue": "1.7.23", + "@scalar/agent-chat": "0.12.23", + "@scalar/api-client": "3.14.0", + "@scalar/blocks": "0.1.9", + "@scalar/code-highlight": "0.4.3", + "@scalar/components": "0.27.10", + "@scalar/helpers": "0.9.2", + "@scalar/icons": "0.7.5", + "@scalar/oas-utils": "0.19.9", + "@scalar/schemas": "0.8.0", + "@scalar/sidebar": "0.9.34", + "@scalar/snippetz": "0.9.24", + "@scalar/themes": "0.17.2", + "@scalar/types": "0.17.0", + "@scalar/use-hooks": "0.4.9", + "@scalar/use-toasts": "0.10.4", + "@scalar/validation": "0.6.2", + "@scalar/workspace-store": "0.56.0", + "@unhead/vue": "^2.1.4", + "@vueuse/core": "13.9.0", + "fuse.js": "^7.1.0", + "microdiff": "^1.5.0", + "nanoid": "^5.1.6", + "vue": "^3.5.30", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/api-reference/node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "4.0.2", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" + "bin": { + "nanoid": "bin/nanoid.js" }, "engines": { - "node": ">=20" + "node": "^18 || >=20" } }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", - "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", + "node_modules/@scalar/asyncapi-upgrader": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@scalar/asyncapi-upgrader/-/asyncapi-upgrader-0.1.4.tgz", + "integrity": "sha512-pyAoyuFVlUf+ZThiuHAShV4lyF9LlPab5AJrvoER8JBz00bHF1NWiGveYySRcNcvke5U5nWCr7NxtiMIokP8sA==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.0.2", - "@shikijs/vscode-textmate": "^10.0.2" + "@scalar/helpers": "0.9.2" }, "engines": { - "node": ">=20" + "node": ">=22" } }, - "node_modules/@shikijs/langs": { - "version": "4.0.2", + "node_modules/@scalar/blocks": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@scalar/blocks/-/blocks-0.1.9.tgz", + "integrity": "sha512-D0Y5HnQ+VPf6Cj9lHZl93Ner8gxdQ85qRP7c/njCeB8cqVSxvB22iiY5gxKELLNvO6YVd6b4BFU3II0DQQPv0g==", + "license": "MIT", + "dependencies": { + "@scalar/components": "0.27.10", + "@scalar/helpers": "0.9.2", + "@scalar/icons": "0.7.5", + "@scalar/snippetz": "0.9.24", + "@scalar/themes": "0.17.2", + "@scalar/types": "0.17.0", + "@scalar/workspace-store": "0.56.0", + "@types/har-format": "^1.2.16", + "js-base64": "^3.7.8", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/code-highlight": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@scalar/code-highlight/-/code-highlight-0.4.3.tgz", + "integrity": "sha512-uueW22siajrJUtszH+k/PmABqau2MmJzajpEFTPapK7Zak167xcKUIjcMbFFFgW8SGKLMSVzLkuB/VNDbOHuOg==", + "license": "MIT", + "dependencies": { + "hast-util-to-text": "^4.0.2", + "highlight.js": "^11.11.1", + "lowlight": "^3.3.0", + "rehype-external-links": "^3.0.0", + "rehype-format": "^5.0.1", + "rehype-parse": "^9.0.1", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-stringify": "^11.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/components": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@scalar/components/-/components-0.27.10.tgz", + "integrity": "sha512-nCN7lksiktG8sPzqWNIbxehNNbbbcXsSGZCbnnpQtRveNkVuOuceCpn7EbbPG65hB+H0o0ZBPki3arPUwBUDwA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "0.2.10", + "@floating-ui/vue": "1.1.9", + "@headlessui/tailwindcss": "^0.2.2", + "@headlessui/vue": "1.7.23", + "@scalar/code-highlight": "0.4.3", + "@scalar/helpers": "0.9.2", + "@scalar/icons": "0.7.5", + "@scalar/themes": "0.17.2", + "@scalar/use-hooks": "0.4.9", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.4", + "radix-vue": "^1.9.17", + "vue": "^3.5.30", + "vue-component-type-helpers": "^3.2.6" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/helpers": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.9.2.tgz", + "integrity": "sha512-hjyMpMZjTBZQhyByZmz5oUgRKUQJO5V5AOiJxsVEGbUmgA7sJRQeTrXLB+BEwzaKS5nm2opJeNyMBYLFNK4hiQ==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/icons": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@scalar/icons/-/icons-0.7.5.tgz", + "integrity": "sha512-kWYkjmYlHzrZ+31d8L1uJpVeil6r1xNcyR+MewnxFslpMBCDIeo9udhYzg09v8s6nWKxwuPokhdQPLbKS+SSgg==", + "license": "MIT", + "dependencies": { + "@phosphor-icons/core": "^2.1.1", + "@types/node": "^24.1.0", + "chalk": "^5.6.2", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/json-magic": { + "version": "0.12.19", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.12.19.tgz", + "integrity": "sha512-1T4QoFYZ1nKt25xFeHtghAuZzaLq2X4CpCSLFXG0Fjcz6K2HIZqo+RtywfI0WD8RRRRgS34keo7X4Gv1BQUNoQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.2", + "pathe": "^2.0.3", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/oas-utils": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@scalar/oas-utils/-/oas-utils-0.19.9.tgz", + "integrity": "sha512-GnpsFGJTavHNX5qsMydQzAgpylcjv5eQK1Ee0TqiIf9Y8N79snsONepsmfS469/ld5SgYkY06DfgTzkuw/ANYQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.2", + "@scalar/themes": "0.17.2", + "@scalar/types": "0.17.0", + "@scalar/workspace-store": "0.56.0", + "flatted": "^3.4.0", + "vue": "^3.5.30", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-types": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@scalar/openapi-types/-/openapi-types-0.9.4.tgz", + "integrity": "sha512-eUSIjZEBLEF2i2pvcNdzXhzlKx6qt+hZsNklLmCyzPBoXWxHcQaEClgMFavXDRRvJDdi6LkyjqGUqqf0XgRgFg==", + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-upgrader": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@scalar/openapi-upgrader/-/openapi-upgrader-0.2.12.tgz", + "integrity": "sha512-oj8O79xVDCHx759owTknwyBAYynaDQ8NGa5wIHduEUAFCob4YnsMEaFDpsconzogchH/whOGzg63BXHST8XPAw==", + "license": "MIT", + "dependencies": { + "@scalar/openapi-types": "0.9.4" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/schemas": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@scalar/schemas/-/schemas-0.8.0.tgz", + "integrity": "sha512-bwu/NCOghZI/cL6Ayti/Oeb5XEMRTncBsyQeHhkFwk4PZsR910me+kZPd5TAp6TqifMfDbAe3xyLSdlzU/KFLA==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.2", + "@scalar/validation": "0.6.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/sidebar": { + "version": "0.9.34", + "resolved": "https://registry.npmjs.org/@scalar/sidebar/-/sidebar-0.9.34.tgz", + "integrity": "sha512-QFVicMkQhudDnBVA5/fFqmIp1nTKiSovSlueeSqgmr+cvd6oKb399PEU5MKuf5kFQTP5Wh3eQfVLwHBwjUeRZg==", + "license": "MIT", + "dependencies": { + "@scalar/components": "0.27.10", + "@scalar/helpers": "0.9.2", + "@scalar/icons": "0.7.5", + "@scalar/themes": "0.17.2", + "@scalar/use-hooks": "0.4.9", + "@scalar/workspace-store": "0.56.0", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/snippetz": { + "version": "0.9.24", + "resolved": "https://registry.npmjs.org/@scalar/snippetz/-/snippetz-0.9.24.tgz", + "integrity": "sha512-ukzC48sWv0cYGzdhGLpZYI9DsL818F0M70OHAQcxlE+kOT0WMrzWQqyt7t5WRWQYd6frK4qp32GcLaWb90cfkQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.2", + "@scalar/types": "0.17.0", + "js-base64": "^3.7.8", + "stringify-object": "^6.0.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/themes": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@scalar/themes/-/themes-0.17.2.tgz", + "integrity": "sha512-r/jgXyddfp7oq9o8UQB8+/c0R6rLRlD0ClfFCxivnxUKsuRnBuTs4xWF6DyZJ2M0D2rpUQiVgWeIVdg+T5FInA==", + "license": "MIT", + "dependencies": { + "nanoid": "^5.1.6" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/themes/node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/typebox": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@scalar/typebox/-/typebox-0.1.3.tgz", + "integrity": "sha512-lU055AUccECZMIfGA0z/C1StYmboAYIPJLDFBzOO81yXBi35Pxdq+I4fWX6iUZ8qcoHneiLGk9jAUM1rA93iEg==", + "license": "MIT" + }, + "node_modules/@scalar/types": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.17.0.tgz", + "integrity": "sha512-mj033MX0EFOEwfpO3ch7FGGnMbye1aLlnP6LmTC9Il2AlBCDL41rYPk67jF5ICAMsAES1RQ+8N2bcC0MJnupLQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.9.2", + "nanoid": "^5.1.6", + "type-fest": "^5.3.1", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/types/node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/use-codemirror": { + "version": "0.14.14", + "resolved": "https://registry.npmjs.org/@scalar/use-codemirror/-/use-codemirror-0.14.14.tgz", + "integrity": "sha512-A7Exfctudg7d3DeQFSSrZYc6yhrEsLX3Iq6GJ7m7ruqgev9y8Hbx7g8yvZWYRQuMUdFtLH5euP7a1ZwM8OrHKA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.18.3", + "@codemirror/commands": "^6.7.1", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.8", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/language": "^6.10.7", + "@codemirror/lint": "^6.8.4", + "@codemirror/state": "^6.5.0", + "@codemirror/view": "^6.35.3", + "@lezer/common": "^1.2.3", + "@lezer/highlight": "^1.2.1", + "@replit/codemirror-css-color-picker": "^6.3.0", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/use-hooks": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@scalar/use-hooks/-/use-hooks-0.4.9.tgz", + "integrity": "sha512-RnflJ178BPdH7pKY9/YBcRlhyc7uJxtqG4hBbi8IPEM4YkpLHcMmxWKgHAgC3MBc21rpuKKBE+DTJOiYmU+UyA==", + "license": "MIT", + "dependencies": { + "@scalar/use-toasts": "0.10.4", + "@scalar/validation": "0.6.2", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.4", + "tailwind-merge": "3.5.0", + "vue": "^3.5.30" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/use-toasts": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@scalar/use-toasts/-/use-toasts-0.10.4.tgz", + "integrity": "sha512-OHXkVfFKV0qx2WpKlzUpvIUkoA/PiloBcXe6soX2S/1z/D042QlKO8rNxkuC3Hlamvyvj8ru8XN17XNl+D0OPg==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.30", + "vue-sonner": "^1.3.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/validation": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@scalar/validation/-/validation-0.6.2.tgz", + "integrity": "sha512-Sc1TkcwGV6aVCO51AyKeaGiP8gpwAHxEtO5d3tZzPV+KsnlC/YokQxFxwBrbIXw73k9hmcExnJyGu3k5i6n6VA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/workspace-store": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@scalar/workspace-store/-/workspace-store-0.56.0.tgz", + "integrity": "sha512-Kt6I/eLvyzGrD409Fl5qlNhJSLNVYlJ2T+IgY/J++PAtqcKPmWePL5KKmXZhooXKKBBkzN7CXIRkjEKTKHXx5A==", + "license": "MIT", + "dependencies": { + "@scalar/asyncapi-upgrader": "0.1.4", + "@scalar/helpers": "0.9.2", + "@scalar/json-magic": "0.12.19", + "@scalar/openapi-upgrader": "0.2.12", + "@scalar/schemas": "0.8.0", + "@scalar/snippetz": "0.9.24", + "@scalar/typebox": "0.1.3", + "@scalar/types": "0.17.0", + "@scalar/validation": "0.6.2", + "js-base64": "^3.7.8", + "type-fest": "^5.3.1", + "vue": "^3.5.30", + "yaml": "^2.8.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@shikijs/core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", + "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", + "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", + "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", "license": "MIT", @@ -2457,9 +3430,17 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, "license": "MIT" }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@tailwindcss/node": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", @@ -2717,6 +3698,32 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.7", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz", + "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.35", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.35.tgz", + "integrity": "sha512-lOfSPvgPdlaH6Qy+CyIc3XpycitaSQ9GECndGpTuDiu+uDA1am+90yWXwzDSd/20ZM196ggWJLS+Qb6WjVd/OA==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -2759,6 +3766,12 @@ "@types/estree": "*" } }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -2798,18 +3811,58 @@ "@types/unist": "*" } }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@unhead/vue": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.1.17.tgz", + "integrity": "sha512-pnC8x9HLV3qQXdvWfylUEU25uhfCAy3ly9nmpQz84j9py818DRfU8jOsQ5wjdWtxyU1vX/fW2udfm4jtxUK8Bg==", + "license": "MIT", + "dependencies": { + "hookable": "^6.0.1", + "unhead": "2.1.17" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=3.5.18" + } + }, + "node_modules/@vercel/oidc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", + "integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, "node_modules/@vitest/expect": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", @@ -3031,6 +4084,248 @@ "dev": true, "license": "MIT" }, + "node_modules/@vue/compiler-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", + "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.41", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", + "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", + "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.41", + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-ssr": "3.5.41", + "@vue/shared": "3.5.41", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", + "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", + "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", + "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/runtime-core": "3.5.41", + "@vue/shared": "3.5.41", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", + "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/integrations": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.9.0.tgz", + "integrity": "sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7 || ^8", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -3052,6 +4347,24 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ai": { + "version": "6.0.33", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.33.tgz", + "integrity": "sha512-bVokbmy2E2QF6Efl+5hOJx5MRWoacZ/CZY/y1E+VcewknvGlgaiCzMu8Xgddz6ArFJjiMFNUPHKxAhIePE4rmg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "3.0.13", + "@ai-sdk/provider": "3.0.2", + "@ai-sdk/provider-utils": "4.0.5", + "@opentelemetry/api": "1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -3141,6 +4454,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -3411,6 +4736,18 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -3561,7 +4898,19 @@ "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", "license": "BlueOak-1.0.0", "engines": { - "node": ">= 18" + "node": ">= 18" + } + }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/convert-source-map": { @@ -3589,6 +4938,12 @@ "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", "license": "MIT" }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, "node_modules/crossws": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", @@ -3672,6 +5027,32 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/cva": { + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.4.tgz", + "integrity": "sha512-F/JS9hScapq4DBVQXcK85l9U91M6ePeXoBMSp7vypzShoefUBxjQTo3g3935PUHgQd+IW77DjbPRIxugy4/GCQ==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + }, + "peerDependencies": { + "typescript": ">= 4.5.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4109,6 +5490,15 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -4129,7 +5519,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-string-truncated-width": { @@ -4190,6 +5579,12 @@ } } }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "license": "ISC" + }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", @@ -4199,6 +5594,15 @@ "node": ">=8" } }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, "node_modules/fontace": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", @@ -4234,6 +5638,31 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fuse.js": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.5.0.tgz", + "integrity": "sha512-sQtrEfA+ez/3G0cCZecF70oqpCRttCexYUG4mUrtWL49ULUzUyxokt5kyqwtKzj1270RaKih+hcP3qLcumccow==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/krisk" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4253,6 +5682,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -4265,6 +5706,15 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/guess-json-indent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/guess-json-indent/-/guess-json-indent-3.0.1.tgz", + "integrity": "sha512-LWZ3Vr8BG7DHE3TzPYFqkhjNRw4vYgFSsv2nfMuHklAlOfiy54/EwiDQuQfFVLxENCVv20wpbjfTayooQHrEhQ==", + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/h3": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", @@ -4282,6 +5732,39 @@ "uncrypto": "^0.1.3" } }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-html": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", @@ -4320,6 +5803,32 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-is-element": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", @@ -4333,6 +5842,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -4346,6 +5872,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-raw": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", @@ -4371,6 +5914,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-estree": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", @@ -4536,6 +6094,21 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "license": "MIT" + }, "node_modules/html-escaper": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", @@ -4552,12 +6125,37 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, + "node_modules/identifier-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.1.0.tgz", + "integrity": "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA==", + "license": "MIT", + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "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", @@ -4573,6 +6171,18 @@ "url": "https://github.com/sponsors/brc-dd" } }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -4642,6 +6252,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.1.0.tgz", + "integrity": "sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==", + "license": "MIT", + "dependencies": { + "identifier-regex": "^1.1.0", + "super-regex": "^1.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -4660,6 +6286,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -4672,6 +6310,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-wsl": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", @@ -4696,6 +6346,12 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-base64": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.2.tgz", + "integrity": "sha512-6zayE8QlUdiweYI6cETD/XBSqFcoCUlufn/29PJR99r82x1yDnIprRca0YvAYpAW+ez0GuQkVBC6xG5QkD7OjA==", + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4726,6 +6382,12 @@ "node": ">=6" } }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -5026,6 +6688,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5055,6 +6732,35 @@ "source-map-js": "^1.2.1" } }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -5385,6 +7091,12 @@ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "license": "CC0-1.0" }, + "node_modules/microdiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/microdiff/-/microdiff-1.6.0.tgz", + "integrity": "sha512-w7JWt8Bno6I8h0rEqlxr4lNG4UbT1FVtWo42wWosIiaJ+rP3pXh7shCYDnyqBrYx36LJ1CZ64p8A62aVp0sJpQ==", + "license": "MIT" + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -6145,9 +7857,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "funding": [ { "type": "github", @@ -6171,6 +7883,12 @@ "node": ">= 10" } }, + "node_modules/neverpanic": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/neverpanic/-/neverpanic-0.0.8.tgz", + "integrity": "sha512-vVdkelrLxaow/fdWDumzNBO+jwm6X8bxeLJc34THtpj70u0C5QBkcV6CRCu2X726km7XD45N0A3QtYCla4RvKw==", + "license": "MIT" + }, "node_modules/nlcst-to-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", @@ -6277,6 +7995,33 @@ "regex-recursion": "^6.0.2" } }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-event/node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", @@ -6369,6 +8114,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-numeric-range": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", @@ -6540,6 +8297,21 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -6559,6 +8331,140 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/radix-vue": { + "version": "1.9.17", + "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.17.tgz", + "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.7", + "@floating-ui/vue": "^1.1.0", + "@internationalized/date": "^3.5.4", + "@internationalized/number": "^3.5.3", + "@tanstack/vue-virtual": "^3.8.1", + "@vueuse/core": "^10.11.0", + "@vueuse/shared": "^10.11.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.4", + "fast-deep-equal": "^3.1.3", + "nanoid": "^5.0.7" + }, + "peerDependencies": { + "vue": ">= 3.2.0" + } + }, + "node_modules/radix-vue/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/radix-vue/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/radix3": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", @@ -6686,6 +8592,38 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-external-links": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz", + "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-is-element": "^3.0.0", + "is-absolute-url": "^4.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-parse": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", @@ -6751,6 +8689,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-sanitize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", + "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-stringify": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", @@ -6888,6 +8840,18 @@ "node": ">=0.10.0" } }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/retext": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", @@ -7011,6 +8975,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "license": "MIT" + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -7171,6 +9141,24 @@ "dev": true, "license": "MIT" }, + "node_modules/string-byte-length": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-byte-length/-/string-byte-length-3.0.1.tgz", + "integrity": "sha512-yJ8vP0HMwZ54CcA8S8mKoXbkezpZHANFtmafFo8lGxZThCQcAwRHjdFabuSLgOzxj9OFJcmssmiAvmcOK4O2Hw==", + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/string-byte-slice": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-byte-slice/-/string-byte-slice-3.0.1.tgz", + "integrity": "sha512-GWv2K4lYyd2+AhmKH3BV+OVx62xDX+99rSLfKpaqFiQU7uOMaUY1tDjdrRD4gsrCr9lTyjMgjna7tZcCOw+Smg==", + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -7200,6 +9188,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stringify-object": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-6.0.0.tgz", + "integrity": "sha512-6f94vIED6vmJJfh3lyVsVWxCYSfI5uM+16ntED/Ql37XIyV6kj0mRAAiTeMMc/QLYIaizC3bUprQ8pQnDDrKfA==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-identifier": "^1.0.1", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -7213,6 +9219,12 @@ "node": ">=8" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -7231,6 +9243,23 @@ "inline-style-parser": "0.2.7" } }, + "node_modules/super-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", + "license": "MIT", + "dependencies": { + "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", @@ -7268,6 +9297,43 @@ "url": "https://opencollective.com/svgo" } }, + "node_modules/swrv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.2.0.tgz", + "integrity": "sha512-lH/g4UcNyj+7lzK4eRGT4C68Q4EhQ6JtM9otPRIASfhhzfLWtbZPHcMuhuba7S9YVYuxkMUGImwMyGpfbkH07A==", + "license": "Apache-2.0", + "peerDependencies": { + "vue": ">=3.2.26 < 4" + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", @@ -7287,6 +9353,21 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -7364,6 +9445,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/truncate-json": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/truncate-json/-/truncate-json-3.0.1.tgz", + "integrity": "sha512-QVsbr1WhGLq2F0oDyYbqtOXcf3gcnL8C9H5EX8bBwAr8ZWvWGJzukpPrDrWgJMrNtgDbo74BIjI4kJu3q2xQWw==", + "license": "MIT", + "dependencies": { + "guess-json-indent": "^3.0.1", + "string-byte-length": "^3.0.1", + "string-byte-slice": "^3.0.1" + }, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/tsconfck": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", @@ -7388,8 +9483,22 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/typesafe-path": { "version": "0.2.2", @@ -7462,6 +9571,12 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, "node_modules/unenv": { "version": "2.0.0-rc.24", "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", @@ -7471,6 +9586,18 @@ "pathe": "^2.0.3" } }, + "node_modules/unhead": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.17.tgz", + "integrity": "sha512-HLMKXOszRhAPBrr6VlqCeVeJq2kbC4kXwzGLEZvvojPLWNYTJw22xG7Bfwhsvs31+IBet3Wl8ADg9dwYdyphfQ==", + "license": "MIT", + "dependencies": { + "hookable": "^6.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -8310,6 +10437,45 @@ "dev": true, "license": "MIT" }, + "node_modules/vue": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", + "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-sfc": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/server-renderer": "3.5.41", + "@vue/shared": "3.5.41" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.9.tgz", + "integrity": "sha512-3c/UfMe0SqyEfcGTyH7mfshHagJ9QTCbppCb0/uGpHZpFug7+If3GeGZN7I0YheKEExemx3xldQPoO7PQSOLQg==", + "license": "MIT" + }, + "node_modules/vue-sonner": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-1.3.2.tgz", + "integrity": "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==", + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", @@ -8320,6 +10486,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, "node_modules/which-pm-runs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", @@ -8922,7 +11094,6 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "devOptional": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 0abc8de..d459bba 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@astrojs/preact": "^5.0.2", "@fontsource-variable/archivo": "^5.3.0", "@fontsource/jetbrains-mono": "^5.3.0", + "@scalar/api-reference": "^1.64.0", "@tailwindcss/vite": "^4.2.2", "astro": "^6.0.8", "astro-i18n": "^2.2.4", diff --git a/src/components/docs/ApiReference.astro b/src/components/docs/ApiReference.astro new file mode 100644 index 0000000..820b360 --- /dev/null +++ b/src/components/docs/ApiReference.astro @@ -0,0 +1,210 @@ +--- +/* + * ApiReference: the interactive API reference at /docs/api, served with Scalar + * from src/data/openapi.json instead of hand-written in MDX. + * + * It is a FULL page, not a component inside Docs.astro: Scalar brings its own + * layout (synced sidebar, sticky panels, a 100dvh grid) and putting it inside + * the docs layout means fighting it. What we add is NaN's top bar above it, + * and Scalar owns the rest. + * + * Both languages render the SAME spec: an API reference's content stays in + * English by convention, and what gets translated is the chrome and Scalar's + * UI labels (its `locale` option). Switching language uses ordinary links, + * like the rest of the site (Nav.astro), not the hot swap built in helmcode. + */ +/* + * Self-hosted fonts, as in Docs.astro and NanBase.astro. Without these the page + * declares `--scalar-font: var(--font-sans)` (Archivo) but the browser has + * nowhere to get it from and falls back to the system one: correct in the CSS + * variables and wrong on screen. They cannot be left to Scalar, which would + * serve them from fonts.scalar.com (see `withDefaultFonts: false` below). + */ +import '@fontsource-variable/archivo/wdth.css'; +import '@fontsource/jetbrains-mono/400.css'; +import '@fontsource/jetbrains-mono/500.css'; +import '@fontsource/jetbrains-mono/700.css'; + +import DocsTopBar from './DocsTopBar.astro'; +import { type Locale } from '../../lib/i18n'; +import '../../styles/docs-shell.css'; + +interface Props { + lang?: Locale; +} + +const { lang = 'en' } = Astro.props; + +/* Only this page's copy: the bar's lives in DocsTopBar.astro. */ +const t = { + en: { + title: 'API Reference · NaN Docs', + description: + 'Interactive reference for the NaN API: OpenAI-compatible endpoints for chat, embeddings, rerank, audio, images and web search.', + }, + es: { + title: 'Referencia API · NaN Docs', + description: + 'Referencia interactiva de la API de NaN: endpoints compatibles con OpenAI para chat, embeddings, rerank, audio, imágenes y búsqueda web.', + }, +}[lang]; + +const SITE = 'https://nan.builders'; +const canonical = `${SITE}${Astro.url.pathname.replace(/\/+$/, '') || '/'}`; +const ogImage = `${SITE}/og/og-${lang}.png`; + +/* + * `connect-src` has to allow api.nan.builders or "try it" does not work: the + * browser sends the request from this page to the API's domain. It goes direct + * on purpose, without Scalar's cloud proxy (`proxyUrl: ''` below): sending a + * member's key to a third party contradicts the product's "zero logs". The API + * answers with open CORS, so no intermediary is needed. `worker-src blob:` is + * for Scalar's syntax highlighting. + */ +const CSP = [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline'", + "style-src 'self' 'unsafe-inline'", + "font-src 'self' data:", + "img-src 'self' data: https:", + "connect-src 'self' https://api.nan.builders", + "worker-src 'self' blob:", + "frame-src 'none'", + "object-src 'none'", + "base-uri 'self'", + "form-action 'self'", +].join('; '); +--- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {t.title} + + + + + + + + +
+ + + + + + diff --git a/src/components/docs/DocsTopBar.astro b/src/components/docs/DocsTopBar.astro new file mode 100644 index 0000000..4368237 --- /dev/null +++ b/src/components/docs/DocsTopBar.astro @@ -0,0 +1,109 @@ +--- +/* + * Top bar of the documentation, shared by the guides layout (Docs.astro) and + * the API reference (ApiReference.astro). + * + * It exists because otherwise the two surfaces drift apart: the reference got + * its own header and the guides another, and they already looked different + * from each other one click apart. Ported from helmcode's DocsLayout. + * + * The EN/ES switcher is only rendered where the page has a Spanish version, + * which today is just /docs/api: the guides come from a collection that is not + * translated, and offering a language that leads to a 404 is worse than not + * offering it. + */ +import { switchLocalePath, type Locale } from '../../lib/i18n'; + +interface Props { + lang?: Locale; + /** With `false` the language switcher is not rendered (English-only pages). */ + bilingual?: boolean; + /** Marks the active link in the bar. */ + current?: 'guides' | 'reference'; +} + +const { lang = 'en', bilingual = false, current } = Astro.props; +const pfx = lang === 'es' ? '/es' : ''; + +const t = { + en: { + docs: 'Docs', + guides: 'Guides', + reference: 'API Reference', + platform: 'Platform ↗', + site: 'nan.builders ↗', + home: 'NaN, back to the docs', + menu: 'Toggle menu', + language: 'Language', + }, + es: { + docs: 'Docs', + guides: 'Guías', + reference: 'Referencia API', + platform: 'Plataforma ↗', + site: 'nan.builders ↗', + home: 'NaN, volver a los docs', + menu: 'Abrir o cerrar el menú', + language: 'Idioma', + }, +}[lang]; +--- + +
+
+ {/* Only shown where there is a sidebar to open: the reference has none. */} + + + {t.docs} +
+ + +
diff --git a/src/content.config.ts b/src/content.config.ts index bbf43e8..4eef4c1 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -8,6 +8,16 @@ const docs = defineCollection({ title: z.string(), description: z.string(), order: z.number().int().min(0), + /* + * The heading the page appears under in the docs navigation. + * + * helmcode's nav writes the groups by hand in the layout; here they live in + * the data so adding a guide stays a matter of creating a file rather than + * also editing the layout, which is how these things drift apart. The order + * between groups comes from the lowest `order` in each, so there is no + * second list to maintain either. + */ + group: z.string().default('Guides'), locale: z.string().default('es'), }), }); diff --git a/src/content/docs/agents.md b/src/content/docs/agents.md index 3faacf0..bbcf284 100644 --- a/src/content/docs/agents.md +++ b/src/content/docs/agents.md @@ -2,6 +2,7 @@ title: Agents description: "Deploy AI agents in an isolated microVM with QEMU: Hermes, web terminal, file uploads, and observability." order: 5 +group: Guides --- # Agents. @@ -9,7 +10,7 @@ order: 5 NaN Cloud lets you deploy AI agents in your own **microVM**: a lightweight virtual machine with QEMU + KVM, its own kernel, its own filesystem, and full root access. Isolated from the host and from other members. The first available agent type is **Hermes**. > **Using an agent you host yourself?** -> If you run your own MCP-compatible agent elsewhere, you can plug our tools (such as web search) straight into it with the same API key via our remote [MCP server](/docs/api#mcp). +> If you run your own MCP-compatible agent elsewhere, you can plug our tools (such as web search) straight into it with the same API key via our remote [MCP server](/docs/api#tag/mcp). ## Architecture diff --git a/src/content/docs/api.mdx b/src/content/docs/api.mdx deleted file mode 100644 index 03dad00..0000000 --- a/src/content/docs/api.mdx +++ /dev/null @@ -1,1154 +0,0 @@ ---- -title: API -description: Public API endpoint reference. OpenAI-compatible. -order: 2 ---- - -import Callout from '../../components/docs/Callout.astro'; -import EndpointGrid from '../../components/docs/EndpointGrid.astro'; -import LimitationsCard from '../../components/docs/LimitationsCard.astro'; -import RateLimits from '../../components/docs/RateLimits.astro'; - -# API Reference. - -Our API is OpenAI-compatible: any client or SDK that accepts a `base URL` + `API key` works without changes. The base URL is `https://api.nan.builders/v1` and authentication is via `Bearer token`. To get your key, see [Getting Started](/docs/getting-started). - - - If you use the Helmcode enterprise service, remember that the API URL is api.helmcode.com. All other endpoints are identical. - - -## Endpoints - -List of available endpoints. Each links to its section with `request`, `response`, and a `curl` example. - - - -## Authentication - -All requests require the `Authorization: Bearer ` header. The key is personal and non-transferable — see [Getting Started](/docs/getting-started) to get yours. - -```bash -curl https://api.nan.builders/v1/models \ - -H "Authorization: Bearer sk-your-key-here" -``` - -

GET /v1/models

- -Returns the list of available models for your key. Published models: `deepseek-v4-flash`, `mimo-v2.5`, `qwen3.6`, `gemma4`, `qwen3-embedding`, `rerank`, `kokoro`, `whisper`, `flux-2-klein` (includes the `flux-2-klein` image model). `glm5.2` is served too, and is callable only with a key on the GLM 5.2 premium tier. - -### Request - -No body. Only the authentication header required. - -### Response - -```json -{ - "object": "list", - "data": [ - { - "id": "qwen3.6", - "object": "model", - "created": 1677610602, - "owned_by": "openai" - }, - { - "id": "gemma4", - "object": "model", - "created": 1677610602, - "owned_by": "openai" - } - ] -} -``` - -### Example - -```bash -curl https://api.nan.builders/v1/models \ - -H "Authorization: Bearer sk-your-key-here" -``` - -

POST /v1/chat/completions

- -The main chat endpoint. OpenAI Chat Completions compatible. Compatible models: `deepseek-v4-flash`, `mimo-v2.5`, `qwen3.6`, `gemma4`, and `glm5.2`. `glm5.2` needs a key on the GLM 5.2 premium tier; the other models are available to every inference member. - -reasoning_content in the message).', - }, - { - title: 'gemma4', - body: 'Chat, streaming, vision (image input), reasoning (opt-in).', - }, - { - title: 'glm5.2 · premium tier', - body: 'Chat, streaming, tool calling, reasoning trace, 500K token context. Coding and long-horizon agentic tasks. 3,000M token quota per member, reset when your billing period starts, and 400M tokens per rolling 4h window. Requires a key on the GLM 5.2 premium tier.', - }, - ]} -/> - -### Request - -| Field | Type | Description | -|---|---|---| -| `model` | string · required | `deepseek-v4-flash`, `mimo-v2.5`, `qwen3.6`, `gemma4` or `glm5.2` (premium tier). | -| `messages` | array · required | List of messages `{ role, content }`. `content` can be a string or an array of parts `[{type:"text",text}, {type:"image_url",image_url:{url}}]` for multimodal input. | -| `max_tokens` | integer · optional | Maximum tokens to generate. | -| `stream` | boolean · optional | Default `false`. If `true`, the response arrives as SSE. | -| `tools` | array · optional | Standard OpenAI function calling: `{type:"function",function:{name,description,parameters}}`. Validated only with `qwen3.6`. | -| `tool_choice` | string \| object · optional | Controls which tool the model can invoke. Standard OpenAI. | -| `temperature` | number · optional | Default `0.6`. | -| `top_p` | number · optional | Default `0.95`. | - -### Response - -Non-streaming response. `finish_reason` puede ser `stop`, `length` o `tool_calls`. - -```json -{ - "id": "chatcmpl-...", - "created": 1778258163, - "model": "qwen3.6", - "object": "chat.completion", - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "role": "assistant", - "content": "...", - "reasoning_content": "..." - } - } - ], - "usage": { - "completion_tokens": 20, - "prompt_tokens": 17, - "total_tokens": 37 - } -} -``` - -The `reasoning_content` field is included only when using `qwen3.6`. It is optional to ignore it. - -### Example - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "messages": [{"role": "user", "content": "Hola"}], - "max_tokens": 200 - }' -``` - -### Streaming - -With `stream: true`, the response is delivered as Server-Sent Events. Each chunk is `data: {...}\n\n` with the delta in `choices[0].delta.content`. The stream ends with `data: [DONE]`. - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -N \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "messages": [{"role": "user", "content": "Cuéntame un chiste corto"}], - "stream": true - }' -``` - -### Tool calling - -`qwen3.6` supports standard OpenAI function calling. When the model decides to invoke a tool, the response includes `choices[0].message.tool_calls` with `{id, type:"function", function:{name, arguments}}` and `finish_reason: "tool_calls"`. - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "messages": [{"role": "user", "content": "¿Qué tiempo hace en Madrid?"}], - "tools": [ - { - "type": "function", - "function": { - "name": "get_weather", - "description": "Gets the current weather for a city", - "parameters": { - "type": "object", - "properties": { - "city": {"type": "string"} - }, - "required": ["city"] - } - } - } - ] - }' -``` - -### Vision - -`mimo-v2.5`, `qwen3.6` and `gemma4` accept multimodal input. The `content` field of the message changes from string to an array of parts of type `text` and/or `image_url`. `mimo-v2.5` also accepts `input_audio` as part of `content`. - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "messages": [{ - "role": "user", - "content": [ - {"type": "text", "text": "¿Qué hay en esta imagen?"}, - {"type": "image_url", "image_url": {"url": "https://example.com/foto.jpg"}} - ] - }] - }' -``` - -### Structured outputs - -Chat models accept the standard OpenAI `response_format` field to force valid JSON responses. We support both modes: - -- **json_object**: Garantiza que la respuesta sea JSON sintácticamente válido. No impone estructura. -- **json_schema**: Restricts output to a specific JSON Schema. With `strict: true` the model cannot emit fields outside the schema. - -Funciona en `qwen3.6` y `gemma4`. - -**json_object:** - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "messages": [ - {"role": "user", "content": "Devuelve un objeto user con name=Alice y age=30."} - ], - "response_format": { "type": "json_object" } - }' -``` - -**json_schema (strict):** - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "messages": [ - {"role": "user", "content": "Alice, 30 años."} - ], - "response_format": { - "type": "json_schema", - "json_schema": { - "name": "user", - "strict": true, - "schema": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "age": { "type": "integer" } - }, - "required": ["name", "age"], - "additionalProperties": false - } - } - } - }' -``` - -Con el SDK de `openai` en Python: - -```python -from openai import OpenAI - -client = OpenAI( - api_key="sk-your-key-here", - base_url="https://api.nan.builders/v1" -) - -response = client.chat.completions.create( - model="qwen3.6", - messages=[{"role": "user", "content": "Alice, 30 años."}], - response_format={ - "type": "json_schema", - "json_schema": { - "name": "user", - "strict": True, - "schema": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"} - }, - "required": ["name", "age"], - "additionalProperties": False - } - } - } -) - -import json -data = json.loads(response.choices[0].message.content) -print(data["name"], data["age"]) -``` - -### Reasoning - -Every chat LLM generates reasoning and returns it in `choices[0].message.reasoning_content`. The control mechanism varies by model: - -| Model | Control | -|---|---| -| `qwen3.6` | `chat_template_kwargs.enable_thinking` · activo por defecto | -| `gemma4` | `chat_template_kwargs.enable_thinking` · desactivado por defecto | -| `deepseek-v4-flash` | `reasoning_effort`: `low` \| `medium` \| `high` · default `medium` | -| `mimo-v2.5` | siempre activo · no configurable por API hoy | -| `glm5.2` | razonamiento activo · sin parámetro publicado para controlarlo | - -#### enable_thinking (qwen3.6, gemma4) - -Binary toggle. The field goes in the request body as `chat_template_kwargs.enable_thinking`: - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "gemma4", - "messages": [{"role": "user", "content": "Qué es 2+2?"}], - "chat_template_kwargs": { "enable_thinking": true } - }' -``` - -To disable it on `qwen3.6` pass `{ "enable_thinking": false }`. - -En SDKs como `openai` de Python o Node, este campo va dentro de `extra_body`: - -```python -from openai import OpenAI - -client = OpenAI( - api_key="sk-your-key-here", - base_url="https://api.nan.builders/v1" -) - -response = client.chat.completions.create( - model="gemma4", - messages=[{"role": "user", "content": "Qué es 2+2?"}], - extra_body={"chat_template_kwargs": {"enable_thinking": True}} -) - -print(response.choices[0].message.reasoning_content) -``` - -#### reasoning_effort (deepseek-v4-flash) - -Standard OpenAI parameter. Accepts `low`, `medium`, or `high` and goes as a top-level body field — not inside `extra_body`. If not provided, defaults to `medium`. - -```bash -curl https://api.nan.builders/v1/chat/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "deepseek-v4-flash", - "messages": [{"role": "user", "content": "Resuelve paso a paso: 3x + 7 = 22"}], - "reasoning_effort": "high" - }' -``` - -Con el SDK `openai` de Python: - -```python -from openai import OpenAI - -client = OpenAI( - api_key="sk-your-key-here", - base_url="https://api.nan.builders/v1" -) - -response = client.chat.completions.create( - model="deepseek-v4-flash", - messages=[{"role": "user", "content": "Resuelve paso a paso: 3x + 7 = 22"}], - reasoning_effort="high" -) - -print(response.choices[0].message.reasoning_content) -print(response.choices[0].message.content) -``` - -A más `effort`, más tokens dedicados al razonamiento y mejor calidad en problemas complejos — a cambio de latencia y consumo de tu cuota de tokens. - -#### mimo-v2.5 - -MiMo V2.5 always reasons and emits `reasoning_content` in every response. Currently upstream Xiaomi ignores both `reasoning_effort` and `enable_thinking`, so the reasoning level is not configurable via the API. If you need to control it, use `deepseek-v4-flash`. - -

POST /v1/completions

- -Endpoint legacy de OpenAI para text completion. Compatible model: `qwen3.6`. - -### Request - -| Field | Type | Description | -|---|---|---| -| `model` | string · required | `qwen3.6`. | -| `prompt` | string · required | The prompt to complete. | -| `max_tokens` | integer · optional | Maximum tokens to generate. | -| `temperature` | number · optional | Default `0.6`. | -| `top_p` | number · optional | Default `0.95`. | -| `stream` | boolean · optional | Default `false`. | - -### Response - -```json -{ - "id": "cmpl-...", - "object": "text_completion", - "created": 1778258166, - "model": "qwen3.6", - "choices": [ - { - "text": "...", - "index": 0, - "finish_reason": "stop", - "logprobs": null - } - ], - "usage": { - "completion_tokens": 10, - "prompt_tokens": 5, - "total_tokens": 15 - } -} -``` - -### Example - -```bash -curl https://api.nan.builders/v1/completions \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "prompt": "The capital of France is", - "max_tokens": 10 - }' -``` - -### Notes - -Endpoint legacy de OpenAI. Para conversaciones, usa [/v1/chat/completions](#chat-completions). - -

POST /v1/embeddings

- -Genera embeddings vectoriales. Compatible model: `qwen3-embedding`. Vectores de **4096 dimensiones**. - -### Request - -| Field | Type | Description | -|---|---|---| -| `model` | string · required | `qwen3-embedding`. | -| `input` | string \| array · required | Single text or array of strings to embed. | -| `encoding_format` | string · optional | `"float"` (default) o `"base64"`. | - -### Response - -```json -{ - "object": "list", - "model": "qwen3-embedding", - "data": [ - { - "object": "embedding", - "index": 0, - "embedding": [0.0210, 0.0105, -0.0204, "..."] - } - ], - "usage": { - "prompt_tokens": 3, - "total_tokens": 3 - } -} -``` - -### Example - -```bash -curl https://api.nan.builders/v1/embeddings \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3-embedding", - "input": ["Hola mundo", "Hello world"] - }' -``` - -

POST /v1/rerank

- -Reordena una lista de documentos por relevancia a una query. Compatible model: `rerank` (`Qwen3-Reranker-8B`). Completa el stack RAG junto a `qwen3-embedding`: primero recuperas top-K por embeddings, después reordenas con `rerank`. Soporta 100+ idiomas, recuperación de código y búsqueda cross-lingual. Endpoint alias: `/v2/rerank`. - -### Request - -| Field | Type | Description | -|---|---|---| -| `model` | string · required | `rerank`. | -| `query` | string · required | Query against which each document's relevance is measured. | -| `documents` | array · required | Array de strings a reordenar. La respuesta los returns ordenados de mayor a menor `relevance_score` con su `index` original. | -| `top_n` | integer · optional | Limita la respuesta a los `N` documentos más relevantes. Por defecto returns todos. | - -### Response - -```json -{ - "id": "score-a032ee5767cab0ee", - "results": [ - { - "index": 0, - "relevance_score": 0.7390941977500916, - "document": { - "text": "Paris is the capital of France." - } - }, - { - "index": 1, - "relevance_score": 0.6002889275550842, - "document": { - "text": "Berlin is the capital of Germany." - } - }, - { - "index": 2, - "relevance_score": 0.12374333292245865, - "document": { - "text": "Madrid is the capital of Spain." - } - } - ], - "meta": { - "billed_units": { - "total_tokens": 43 - }, - "tokens": { - "input_tokens": 43 - } - } -} -``` - -La respuesta incluye `id`, `results` (array de `{index, relevance_score, document}`) y `meta` con `billed_units` y conteo de tokens. `relevance_score` está en el rango [0, 1]. El `index` se refiere a la posición original del documento en el array de entrada. - -### Example - -```bash -curl https://api.nan.builders/v1/rerank \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "rerank", - "query": "What is the capital of France?", - "documents": [ - "Paris is the capital of France and home to the Eiffel Tower.", - "Berlin is the capital of Germany.", - "Madrid is the capital of Spain." - ] - }' -``` - -Con el SDK de `openai` de Python (usando `post` directo, ya que `rerank` no forma parte del cliente OpenAI): - -```python -import os -from openai import OpenAI - -client = OpenAI( - api_key=os.environ["NAN_API_KEY"], - base_url="https://api.nan.builders/v1" -) - -response = client.post( - path="/rerank", - cast_to=object, - body={ - "model": "rerank", - "query": "What is the capital of France?", - "documents": [ - "Paris is the capital of France and home to the Eiffel Tower.", - "Berlin is the capital of Germany.", - "Madrid is the capital of Spain.", - ], - }, -) - -for r in response["results"]: - print(r["index"], r["relevance_score"]) -``` - -

POST /v1/audio/speech

- -Synthesizes audio from text (text-to-speech). Compatible model: `kokoro`. - -### Request - -| Field | Type | Description | -|---|---|---| -| `model` | string · required | `kokoro`. | -| `input` | string · required | Text to synthesize. | -| `voice` | string · required | Voice to use. Some options: `af_heart` (English female), `ef_dora` (Spanish female), `em_alex` (Spanish male). [See full list](https://huggingface.co/hexgrad/Kokoro-82M/blob/main/VOICES.md). | -| `response_format` | string · optional | Format of the returned audio. Validated: `mp3` (default), `wav`, `flac`, `aac`, `pcm`, `opus`. | -| `speed` | number · optional | Default `1.0`. | - -### Response - -Binary audio file in the requested format (without JSON wrapper). - -### Example - -```bash -curl https://api.nan.builders/v1/audio/speech \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "kokoro", - "input": "Bienvenido a NaN.", - "voice": "ef_dora", - "response_format": "mp3" - }' \ - -o speech.mp3 -``` - -

POST /v1/audio/transcriptions

- -Transcribes audio to text (speech-to-text). Compatible model: `whisper`. The request is `multipart/form-data`. - -### Request - -| Field | Type | Description | -|---|---|---| -| `file` | file · required | Audio file to transcribe. | -| `model` | string · required | `whisper`. | -| `language` | string · optional | ISO-639-1 code (ej. `es`, `en`). If not provided, detected automatically. | -| `response_format` | string · optional | Validated: `json` (default) y `verbose_json`. Other values work but return content wrapped in JSON; we recommend only these two. | -| `timestamp_granularities[]` | string · optional | Only with `verbose_json`. Valores: `word` (timestamps por palabra) o `segment` (default). | -| `temperature` | number · optional | Sampling temperature. | - -### Response - -Example with `response_format=verbose_json`: - -```json -{ - "text": "Hola, esto es una prueba.", - "language": "es", - "task": "transcribe", - "duration": 1.728, - "segments": [ - { - "id": 1, - "start": 0.0, - "end": 1.4, - "text": " Hola, esto es una prueba.", - "tokens": [50365, 22637, "..."], - "avg_logprob": -0.059, - "compression_ratio": 0.806, - "no_speech_prob": 0.044, - "temperature": 0.0 - } - ], - "words": null -} -``` - -If you pass `timestamp_granularities[]=word`, el campo `words` is populated with `[{word, start, end, probability}]`. - -### Example - -```bash -curl https://api.nan.builders/v1/audio/transcriptions \ - -H "Authorization: Bearer sk-your-key-here" \ - -F "model=whisper" \ - -F "file=@grabacion.mp3" \ - -F "language=es" \ - -F "response_format=verbose_json" -``` - -### Limitations - - 2 min pueden devolver timeout 524', - body: 'Recomendamos dividir en segmentos de ≤ 2 min.', - }, - { - title: 'Formats recomendados', - body: 'OGG/Opus y MP3 — mejor compresión, misma calidad de transcripción.', - }, - ]} -/> - -

POST /v1/responses

- -OpenAI-style Responses endpoint. Modelos compatibles: `qwen3.6` y `gemma4`. - -### Request - -| Field | Type | Description | -|---|---|---| -| `model` | string · required | `qwen3.6` o `gemma4`. | -| `input` | string \| array · required | Single text or array of messages in OpenAI Responses format. | -| `max_output_tokens` | integer · optional | Default `65536` en `qwen3.6`. | -| `temperature` | number · optional | Default `0.6`. | -| `top_p` | number · optional | Default `0.95`. | -| `instructions` | string · optional | System instructions. | - -### Response - -The array `output` can contain blocks of type `reasoning` (solo `qwen3.6`) y `message`. - -```json -{ - "id": "resp_...", - "created_at": 1778258181, - "model": "qwen3.6", - "object": "response", - "status": "completed", - "output": [ - { - "id": "rs_...", - "type": "reasoning", - "summary": [], - "content": [ - { "type": "reasoning_text", "text": "..." } - ] - }, - { - "id": "msg_...", - "type": "message", - "role": "assistant", - "status": "completed", - "content": [ - { "type": "output_text", "text": "Hola.", "annotations": [] } - ] - } - ], - "usage": { - "input_tokens": 17, - "output_tokens": 118, - "total_tokens": 135 - } -} -``` - -### Example - -```bash -curl https://api.nan.builders/v1/responses \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "qwen3.6", - "input": "Hola, ¿cómo estás?" - }' -``` - -### Notes - -Streaming on this endpoint currently delivers a single event `response.completed` at the end, not incremental chunks. For token-by-token streaming use [/v1/chat/completions](#chat-completions) con `stream: true`. - -

POST /v1/images/generations

- -Generates images from text (text-to-image). Compatible with OpenAI's Images API. Compatible model: `flux-2-klein` (only model available today; the endpoint is designed to add more). The body is JSON. - -### Request - -| Field | Type | Description | -|---|---|---| -| `prompt` | string · required | Textual description of the image to generate. | -| `model` | string · optional | Default `flux-2-klein` (único modelo disponible). An unknown model returns `404` (`model_not_found`). | -| `n` | integer · optional | Number of images to generate, between `1` y `4`. Default `1`. A value greater than `4` returns `400`. | -| `size` | string · optional | Format `"ANCHOxALTO"` con both sides divisible by 16, each between 256 and 1536, and aspect ratio between 1:3 and 3:1. Standard values like `1024x1024`, `1536x1024` o `1024x1536` work. `"auto"` or omitted → `1024x1024`. | -| `response_format` | string · optional | `"url"` (default) o `"b64_json"`. Con `url` a temporary R2 link is returned valid ~60 minutos (same contract as OpenAI). Con `b64_json` the image bytes are returned inline as base64. | - - - For OpenAI SDK compatibility, the following are accepted quality, style, background, moderation, output_format, output_compression y user, pero are ignored — Flux does not act on them. Además, stream: true is not supported y returns 400. - - -### Parámetros adicionales (extensiones NaN) - -These parameters **no** forman parte de la Images API de OpenAI. Con el SDK de `openai` are passed via `extra_body`. - -| Field | Type | Description | -|---|---|---| -| `seed` | integer · optional | Base seed for reproducibility. Each variant (when `n > 1`) starts from an offset on this value. | -| `guidance` | number · optional | FLUX guidance scale. | - -### Response - -Same wrapper as OpenAI's Images API. `created` is the Unix timestamp in seconds. - -```json -{ - "created": 1778258200, - "data": [ - { "url": "https://...r2.../image.png" } - ] -} -``` - -Con `response_format=b64_json`, cada elemento de `data` es `{ "b64_json": "..." }` instead of `{ "url": "..." }`. - -### Example - -```bash -curl https://api.nan.builders/v1/images/generations \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "flux-2-klein", - "prompt": "Un faro al atardecer sobre acantilados, estilo cinemático", - "size": "1024x1024" - }' -``` - -**response_format=b64_json:** - -```bash -curl https://api.nan.builders/v1/images/generations \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "flux-2-klein", - "prompt": "Un faro al atardecer sobre acantilados", - "size": "1024x1024", - "response_format": "b64_json" - }' -``` - -Con el SDK de `openai` en Python (the extensions `seed` y `guidance` van en `extra_body`): - -```python -from openai import OpenAI - -client = OpenAI( - api_key="sk-your-key-here", - base_url="https://api.nan.builders/v1" -) - -response = client.images.generate( - model="flux-2-klein", - prompt="Un faro al atardecer sobre acantilados, estilo cinemático", - size="1024x1024", - n=1, - extra_body={"seed": 42, "guidance": 3.5} -) - -print(response.data[0].url) -``` - -### Rate limits y cuota - -La generación de imágenes tiene sus propios límites, independientes de los [rate limits](#rate-limits) de los endpoints de modelos: generar imágenes no consume tu presupuesto de rpm del chat, y viceversa. Estos límites aplican igual a la API y a la consola web, y son propios de los endpoints de imágenes: - -| Límite | Valor | Descripción | -|---|---|---| -| Rate limit | `1 req/s · burst 3` | 1 request por segundo sostenido, con burst de hasta 3 (puedes disparar hasta 3 generaciones seguidas sin error). Al excederlo returns `429` (`rate_limit_exceeded`). | -| Cuota mensual | `100 requests / mes` | 100 requests por mes y por usuario (1 request = 1 uso, independientemente del valor de `n`). Al excederla returns `429` (`insufficient_quota`). Esta cuota es independiente de la cuota mensual de tokens del chat, que depende del modelo. | -| Tier | `inference` | Requiere membresía de tier inference. Las keys de tier community reciben `403` (`tier_restricted`). | - -

POST /v1/images/edits

- -Generates an image from one or more reference images (image-to-image). Compatible with OpenAI's Images API. Compatible model: `flux-2-klein`. The request is `multipart/form-data`. Aplican la misma membresía inference-tier y la misma cuota mensual de 100 requests que [/v1/images/generations](#images-generations). - -### Request - -| Field | Type | Description | -|---|---|---| -| `image` / `image[]` | file · required | One or more reference images (up to 4; extras are discarded). PNG, JPEG, or WebP, each < 25 MB. | -| `prompt` | string · required | Description of the edit or transformation to apply. | -| `model`, `n`, `size`, `response_format` | optional | Same behavior as in [/v1/images/generations](#images-generations). Las extensiones `seed` y `guidance` are also accepted (as form fields). | - - - El parámetro mask is not supported y returns 400 — Flux Klein does not do inpainting. - - -### Response - -Same wrapper as [/v1/images/generations](#images-generations): `{ "created": ..., "data": [{ "url": "..." }] }` (o elementos `{ "b64_json": "..." }` con `response_format=b64_json`). - -### Example - -```bash -curl https://api.nan.builders/v1/images/edits \ - -H "Authorization: Bearer sk-your-key-here" \ - -F "model=flux-2-klein" \ - -F "image[]=@ref.png" \ - -F "prompt=Convierte la escena en invierno con nieve" \ - -F "size=1024x1024" -``` - - - -An authenticated web-search tool for your agents. You send a query and we return ranked results (title, URL, snippet, and optionally the page's readable content) through our API — your key never talks to a third-party search provider directly. Results come from a hybrid of an external search API (primary) and an internal fallback; you don't need to know which served a given request. - -Designed to be wired as an agent tool: give the model the [function schema below](#web-search-tool), and when it emits a `web_search` tool call, invoke this endpoint with the arguments and feed the JSON response back as the tool result. - -### Request - -The body is JSON. - -| Field | Type | Description | -|---|---|---| -| `query` | string · required | The search query. | -| `count` | integer · optional | Number of results to return, `1`–`20` (clamped). Default `5`. | -| `freshness` | string · optional | Recency filter: `pd` (past day), `pw` (past week), `pm` (past month), `py` (past year), or a `YYYY-MM-DDtoYYYY-MM-DD` date range. Omit for no time filter. | -| `fetch_content` | boolean · optional | When `true`, also fetch and include the readable text of the top results. Adds latency. Default `false` returns snippets only. | - - - When fetch_content is true, the server fetches page content only for the URLs the search engine returned, through a hardened anti-SSRF fetcher — you cannot pass an arbitrary URL to fetch. - - -### Response - -```json -{ - "results": [ - { - "title": "Go 1.23 is released", - "url": "https://go.dev/blog/go1.23", - "snippet": "The latest Go release ...", - "content": "Full readable page text (only present when fetch_content=true)", - "source": "primary" - } - ], - "cached": false -} -``` - -| Field | Type | Description | -|---|---|---| -| `results[].title` | string | Result title (plain text). | -| `results[].url` | string | Result URL. | -| `results[].snippet` | string | Short excerpt. | -| `results[].content` | string | Readable page text — present only when `fetch_content=true` and the fetch succeeded. | -| `results[].source` | string | Which tier served the result: `primary` or `fallback`. | -| `cached` | boolean | `true` when the results came from the short-lived (~15 min) exact-query cache. Cached hits still count toward your rate limit and daily quota. | - -### Example - -```bash -curl https://api.nan.builders/v1/search \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "query": "latest go release", - "count": 5, - "freshness": "pw" - }' -``` - -

Wire it as an agent tool

- -Drop this into your model call's `tools` array (OpenAI-style function calling). When the model emits a `web_search` tool call, invoke `POST /v1/search` with the arguments and return the JSON response as the tool result. - -```json -{ - "type": "function", - "function": { - "name": "web_search", - "description": "Search the public web and return relevant results (title, URL, snippet, and optional page content). Use it when the answer may depend on current events, recent developments, prices, release/version numbers, or facts you are not confident are up to date.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The web search query." - }, - "count": { - "type": "integer", - "description": "Number of results to return (1-20).", - "minimum": 1, - "maximum": 20, - "default": 5 - }, - "freshness": { - "type": "string", - "description": "Restrict results by recency: 'pd' (past day), 'pw' (past week), 'pm' (past month), 'py' (past year), or a 'YYYY-MM-DDtoYYYY-MM-DD' date range. Omit for no time filter." - }, - "fetch_content": { - "type": "boolean", - "description": "When true, also fetch and include the readable text of the top results (slower). Default false returns snippets only.", - "default": false - } - }, - "required": ["query"] - } - } -} -``` - -### Rate limits and quota - -Web search has its own limits, separate from the standard [rate limits](#rate-limits) of the model endpoints: searching does not consume your chat rpm budget, and vice versa. These limits are enforced per API key: - -| Limit | Value | Description | -|---|---|---| -| Rate limit | `20 req/min` | 20 requests per minute. Exceeding it returns `429` (`rate_limit_exceeded`); respect the `Retry-After` header. | -| Concurrency | `3` | Up to 3 concurrent searches. Exceeding it returns `429` (`rate_limit_exceeded`). | -| Daily quota | `500 / day` | 500 searches per day per API key. Exceeding it returns `429` (`insufficient_quota`). | - -### Search errors - -In addition to the [common errors](#errors), this endpoint may return: - -| Status | `code` | Description | -|---|---|---| -| 401 | `invalid_api_key` | The `Authorization` header is missing or the key is invalid. | -| 429 | `rate_limit_exceeded` | Per-minute or concurrency limit reached. Respect the `Retry-After` header. | -| 429 | `insufficient_quota` | Daily search quota exhausted. | -| 503 | `search_unavailable` | Search is temporarily unavailable; retry shortly. | - -

MCP server

- -We expose a remote [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server so you can use our tools directly inside any MCP-compatible agent or client, authenticated with the **same** `sk-` API key as the REST API. The endpoint is `https://api.nan.builders/mcp` (Helmcode enterprise uses the same shape on `api.helmcode.com/mcp`). - -Today the server exposes a single tool, [`web_search`](#web-search) (same capability and arguments as `POST /v1/search`), and it is a growing registry — more tools will be added over time and appear automatically under `tools/list`. - -| Property | Value | -|---|---| -| Endpoint | `https://api.nan.builders/mcp` | -| Transport | Streamable HTTP (remote), stateless | -| Protocol | JSON-RPC 2.0 — methods: `initialize`, `tools/list`, `tools/call`, `ping` | -| Auth | `Authorization: Bearer ` — the same `sk-` key as the REST API. Unauthenticated requests get `401`. | -| Tools | `web_search` (more coming). Arguments match [`POST /v1/search`](#web-search): `query`, `count` (1–20, default 5), `freshness`, `fetch_content` (default false). | - - - MCP calls share the same per-key rate limit, daily quota, and concurrency as the equivalent REST endpoint — there is no separate budget. A web_search tool call over MCP counts exactly like a POST /v1/search request. - - -### Connect an MCP client - -Most native remote MCP clients accept a `url` plus `headers`. Point them at the endpoint and pass your key in the `Authorization` header: - -```json -{ - "mcpServers": { - "nan": { - "url": "https://api.nan.builders/mcp", - "headers": { - "Authorization": "Bearer sk-your-key-here" - } - } - } -} -``` - -Clients that only speak stdio (some desktop apps) can bridge to the remote server with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote): - -```json -{ - "mcpServers": { - "nan": { - "command": "npx", - "args": [ - "-y", - "mcp-remote", - "https://api.nan.builders/mcp", - "--header", - "Authorization:Bearer sk-your-key-here" - ] - } - } -} -``` - - - Many clients prefix each tool with the server name, so a server entry named nan surfaces the tool as nan_web_search. A verbose name like nan-web-search would produce a duplicated-looking nan_web_search_web_search — name the entry nan. - - -### Raw JSON-RPC - -You can also drive the server directly. A `tools/call` for `web_search`: - -```bash -curl https://api.nan.builders/mcp \ - -H "Authorization: Bearer sk-your-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "web_search", - "arguments": { "query": "kubernetes 1.34 release", "count": 5 } - } - }' -``` - -Use `tools/list` to discover the current tool set and each tool's input schema. - -## Errors - -Errors follow the standard OpenAI format: HTTP non-2xx status with a JSON body describing the problem. - -```json -{ - "error": { - "message": "...", - "type": null, - "param": null, - "code": "..." - } -} -``` - -| Código | Descripción | -|---|---| -| 400 | Parámetro inválido — the body includes `param` with the field that failed (e.g. `prompt`, `n`, `size`, `stream`, `mask` o `image` en los endpoints de imágenes). El filtro de seguridad returns `content_policy_violation`. | -| 401 | `Authorization` header invalid or missing (`invalid_api_key`). | -| 402 | Token allowance for the billing period exhausted on a model that carries one (`monthly_cap_reached`), such as `glm5.2`. Not retryable: the counter goes back to zero when your billing period starts. | -| 403 | Your tier does not have access to the endpoint (`tier_restricted`). Image generation requires inference membership. | -| 404 | Model does not exist (campo `model`, `model_not_found`). | -| 429 | Rate limit exceeded — `rpm_limit` o `max_parallel_requests` (`rate_limit_exceeded`), the rolling 4h token budget of `glm5.2` (the body says how much frees up and when), or monthly quota exhausted (`quota_exceeded` / `insufficient_quota`, como la de 100 requests de imágenes). | -| 500 | Internal error (includes upstream model errors). | -| 524 | Timeout (typical with large audios on [/v1/audio/transcriptions](#audio-transcriptions)). | - -## Rate limits - - diff --git a/src/content/docs/apps.md b/src/content/docs/apps.md index 7a8201f..b02db3c 100644 --- a/src/content/docs/apps.md +++ b/src/content/docs/apps.md @@ -2,6 +2,7 @@ title: Apps description: Deploy your apps from GitHub to NaN Cloud in minutes. order: 6 +group: Guides --- # Apps. diff --git a/src/content/docs/examples.md b/src/content/docs/examples.md index 6625b1a..662682a 100644 --- a/src/content/docs/examples.md +++ b/src/content/docs/examples.md @@ -2,6 +2,7 @@ title: Examples description: Code snippets to connect to the NaN API with Python, Node.js, curl, and more. order: 4 +group: Guides --- # Code snippets. diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md index 2194638..2305c66 100644 --- a/src/content/docs/getting-started.md +++ b/src/content/docs/getting-started.md @@ -2,6 +2,7 @@ title: Getting Started description: Configure your favorite IDE or tool to connect to NaN models. order: 1 +group: Get started --- # Getting Started. diff --git a/src/content/docs/intro.md b/src/content/docs/intro.md index a31aaf2..cbddd14 100644 --- a/src/content/docs/intro.md +++ b/src/content/docs/intro.md @@ -2,6 +2,7 @@ title: Introduction description: Connect your favorite tools (OpenCode, Cursor, Cline, etc.) to our shared inference cluster. order: 0 +group: Get started --- # Welcome to NaN. diff --git a/src/content/docs/models.mdx b/src/content/docs/models.mdx index ca42326..5d1f1b3 100644 --- a/src/content/docs/models.mdx +++ b/src/content/docs/models.mdx @@ -2,6 +2,7 @@ title: Models description: Technical specifications, capabilities, and parameters of the shared cluster models. order: 3 +group: Reference --- import ModelCard from '../../components/docs/ModelCard.astro'; diff --git a/src/data/modelos.json b/src/data/modelos.json index 9677a2a..b867251 100644 --- a/src/data/modelos.json +++ b/src/data/modelos.json @@ -37,7 +37,7 @@ { "id": "qwen3.6", "by": "Alibaba", - "specs": "35B-A3B MoE · FP8 · 256K context · tool calling · reasoning", + "specs": "35B-A3B MoE · FP8 · 256K context · tool calling · reasoning · vision", "cuota": "sin contador", "frontier": false, "mostUsed": true diff --git a/src/data/openapi.json b/src/data/openapi.json new file mode 100644 index 0000000..af89494 --- /dev/null +++ b/src/data/openapi.json @@ -0,0 +1,2529 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "NaN API", + "version": "1.0.0", + "description": "Open models on a shared EU inference cluster. Zero logs.\n\nThe NaN API is OpenAI-compatible: predictable, resource-oriented URLs, JSON request and response bodies, and standard HTTP verbs and status codes. Point any OpenAI SDK at our base URL and your existing code keeps working. Change the base URL and the API key, and that's it.\n\nOne schema across every model, so you only learn the API once. Change the `model` field to switch models; everything else stays the same.\n\n- Base URL: `https://api.nan.builders/v1`\n- OpenAPI spec: this document. Import it into Postman, Insomnia, or your own tooling.\n\nIf you use the [Helmcode](https://helmcode.com) enterprise service, the base URL is `https://api.helmcode.com/v1` instead. Every other endpoint is identical.\n\n## Authentication\n\nEvery request authenticates with an API key, sent as a Bearer token:\n\n```\nAuthorization: Bearer $NAN_API_KEY\n```\n\nYou must be a NaN community member. Generate your key from user settings, under \"API Keys\", on the [platform](https://cloud.nan.builders/). The key is personal and non-transferable. Keep it secret: never embed one in client-side code or commit it to source control. Requests must go over HTTPS; calls over plain HTTP fail.\n\n## Making requests\n\nThe API is OpenAI-compatible, so point an official OpenAI SDK at our base URL and change nothing else:\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.chat.completions.create(\n model=\"qwen3.6\",\n messages=[{\"role\": \"user\", \"content\": \"Hello\"}],\n)\nprint(resp.choices[0].message.content)\n```\n\n## Streaming\n\nChat responses can stream token-by-token. Set `\"stream\": true` on `/chat/completions` and the response arrives as Server-Sent Events: each event is a `data:` line carrying a `chat.completion.chunk`, with the new text in `choices[0].delta.content`. A final `data: [DONE]` line ends the stream. Only `/chat/completions` streams incrementally; `/responses` currently emits a single terminal event.\n\n## Rate limits\n\n{{RATE_LIMITS}}\n\nWeb search runs on its own budget, separate from the model endpoints: 20 requests per minute, 3 concurrent, and 500 searches per day per key. Image endpoints have their own too: 20 requests per minute and 100 requests per month. Exceed any limit and you get a `429`.\n\n## Errors\n\nNaN uses conventional HTTP status codes: `2xx` on success, `4xx` for a problem with the request (a missing parameter, an invalid key, an unavailable model) and `5xx` for a server-side error. Every error returns a JSON body in the OpenAI shape:\n\n```json\n{\n \"error\": {\n \"message\": \"The model 'foo' does not exist.\",\n \"type\": \"invalid_request_error\",\n \"param\": \"model\",\n \"code\": \"model_not_found\"\n }\n}\n```\n\n`message` is human-readable, `param` names the offending field when applicable, and `code` is a short machine-readable string you can branch on.\n\n| Status | Meaning | `code` |\n| --- | --- | --- |\n| `400` | Invalid or malformed parameter (`param` says which); or content blocked by the safety filter. | `invalid_request_error` · `content_policy_violation` |\n| `401` | Missing or invalid API key. | `invalid_api_key` |\n| `402` | The token allowance for the billing period is spent on a model that carries one, such as `glm5.2`. Not retryable: the counter returns to zero when your billing period starts. | `monthly_cap_reached` |\n| `403` | Your tier can't access this endpoint or model. Image generation requires inference membership. | `tier_restricted` |\n| `404` | The requested model doesn't exist. | `model_not_found` |\n| `429` | Rate limit hit (`rpm_limit`, `max_parallel_requests`), the rolling 4h token budget of `glm5.2`, or a quota exhausted. | `rate_limit_exceeded` · `insufficient_quota` · `quota_exceeded` |\n| `500` | Something went wrong on our side (includes upstream model errors). | (none) |\n| `503` | Web search is temporarily unavailable; retry shortly. | `search_unavailable` |\n| `524` | Timeout, typical with large audio files on `/audio/transcriptions`. | (none) |\n\nRetry `429` and `5xx` responses with exponential backoff. Don't retry `400`, `401`, `403`, or `404` blindly: they'll fail the same way every time until you change the request. `402` cannot be fixed by repetition either: it clears when your billing period starts.\n\n## Model catalog\n\nEvery endpoint takes a `model` id. Capabilities vary by model:\n\n| Model | Use for | Capabilities |\n| --- | --- | --- |\n| `deepseek-v4-flash` | Chat, reasoning | Streaming, tool calling, reasoning, 1M-token context. 500M tokens/month per member |\n| `mimo-v2.5` | Chat, vision, audio | Streaming, tool calling, reasoning, image input, audio input, 1M-token context. 1.0B tokens/month per member |\n| `qwen3.6` | Chat, agents | Streaming, tool calling, vision, reasoning (opt-out, returns `reasoning_content`) |\n| `gemma4` | Chat, vision, agents | Streaming, tool calling, vision, reasoning (opt-in) |\n| `glm5.2` | Coding, long-horizon agents | Streaming, tool calling, reasoning trace, 500K-token context. Premium tier only |\n| `qwen3-embedding` | Embeddings | 4096-dimension vectors |\n| `rerank` | RAG reranking | Qwen3-Reranker-8B, 100+ languages |\n| `kokoro` | Text-to-speech | Multiple voices and audio formats |\n| `whisper` | Speech-to-text | Transcription with word/segment timestamps |\n| `flux-2-klein` | Image generation | Text-to-image and image-to-image |\n\n`glm5.2` is served only to keys on the GLM 5.2 premium tier; every other model is available to any inference member. Call [List models](#tag/Models) for the exact set available to your key.\n\n## Versioning & compatibility\n\nThe API tracks the OpenAI API surface, so OpenAI SDKs and tools work against `https://api.nan.builders/v1` unchanged. This reference documents the stable public `/v1` endpoints, and we add capabilities without breaking existing fields.", + "contact": { + "name": "NaN", + "url": "https://nan.builders" + }, + "termsOfService": "https://nan.builders/terms" + }, + "servers": [ + { + "url": "https://api.nan.builders/v1", + "description": "Production (NaN community cluster)" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + { + "name": "Models", + "description": "List available models." + }, + { + "name": "Chat", + "description": "Chat completions, the main conversational endpoint." + }, + { + "name": "Completions", + "description": "Legacy text completions." + }, + { + "name": "Embeddings", + "description": "Vector embeddings." + }, + { + "name": "Rerank", + "description": "Reorder documents by relevance (RAG)." + }, + { + "name": "Audio", + "description": "Text-to-speech and speech-to-text." + }, + { + "name": "Responses", + "description": "OpenAI-style Responses endpoint." + }, + { + "name": "Images", + "description": "Text-to-image and image-to-image." + }, + { + "name": "Search", + "description": "Authenticated web search for agents." + }, + { + "name": "MCP", + "description": "Remote MCP server for agents and MCP clients." + } + ], + "paths": { + "/models": { + "get": { + "operationId": "listModels", + "tags": [ + "Models" + ], + "summary": "List models", + "description": "Returns a list of the models available to your API key, most-capable first. Each entry is a [Model](#tag/Models) object. Use an `id` as the `model` field in your requests.", + "responses": { + "200": { + "description": "A list of models.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "description": "Always `list`." + }, + "data": { + "type": "array", + "description": "The available models.", + "items": { + "$ref": "#/components/schemas/Model" + } + } + } + }, + "example": { + "object": "list", + "data": [ + { + "id": "qwen3.6", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "deepseek-v4-flash", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + } + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/models \\\n -H \"Authorization: Bearer $NAN_API_KEY\"" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nfor m in client.models.list().data:\n print(m.id)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nconst models = await client.models.list();\nfor (const m of models.data) console.log(m.id);" + } + ] + } + }, + "/chat/completions": { + "post": { + "operationId": "createChatCompletion", + "tags": [ + "Chat" + ], + "summary": "Create chat completion", + "description": "Creates a model response for the given chat conversation. Compatible with OpenAI's Chat Completions API.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "model", + "messages" + ], + "properties": { + "model": { + "type": "string", + "description": "Model id. See [List models](#tag/Models) for what your key can use. Chat models are `deepseek-v4-flash`, `mimo-v2.5`, `qwen3.6`, `gemma4` and `glm5.2`.\n\n`glm5.2` requires a key on the GLM 5.2 premium tier; other keys get `403` `tier_restricted`. The rest are available to every inference member.", + "example": "qwen3.6" + }, + "messages": { + "type": "array", + "minItems": 1, + "description": "The conversation so far, oldest first. `content` is a string, or an array of parts (`text` + `image_url`) for vision input on `qwen3.6` and `gemma4`.", + "items": { + "$ref": "#/components/schemas/Message" + }, + "example": [ + { + "role": "user", + "content": "Hola" + } + ] + }, + "max_tokens": { + "type": "integer", + "minimum": 1, + "description": "Maximum number of tokens to generate.", + "example": 512 + }, + "temperature": { + "type": "number", + "minimum": 0, + "maximum": 2, + "default": 0.6, + "description": "Sampling temperature. Higher is more random, lower more deterministic.", + "example": 0.6 + }, + "top_p": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0.95, + "description": "Nucleus sampling: only consider the top `top_p` probability mass.", + "example": 0.95 + }, + "stream": { + "type": "boolean", + "default": false, + "description": "When `true`, the reply arrives as Server-Sent Events: each `data:` line is a `chat.completion.chunk` with new text in `choices[0].delta.content`, ending with `data: [DONE]`.", + "example": false + }, + "tools": { + "type": "array", + "description": "Functions the model may call (OpenAI function schema). When it calls one, the reply carries `choices[0].message.tool_calls` and `finish_reason: \"tool_calls\"`. Validated on `qwen3.6`.", + "items": { + "$ref": "#/components/schemas/Tool" + } + }, + "tool_choice": { + "$ref": "#/components/schemas/ToolChoice" + }, + "response_format": { + "$ref": "#/components/schemas/ResponseFormat" + }, + "reasoning_effort": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "default": "medium", + "description": "Reasoning depth (`deepseek-v4-flash` only). The trace comes back in `message.reasoning_content`. More effort helps on hard problems, at the cost of latency and tokens.", + "example": "medium" + }, + "chat_template_kwargs": { + "type": "object", + "properties": { + "enable_thinking": { + "type": "boolean" + } + }, + "description": "Reasoning toggle for `qwen3.6`/`gemma4`: `{ \"enable_thinking\": boolean }`. In OpenAI SDKs, pass it inside `extra_body`." + } + } + }, + "examples": { + "basic": { + "summary": "Basic", + "value": { + "model": "qwen3.6", + "messages": [ + { + "role": "user", + "content": "Hola" + } + ], + "max_tokens": 200 + } + }, + "streaming": { + "summary": "Streaming (SSE)", + "value": { + "model": "qwen3.6", + "messages": [ + { + "role": "user", + "content": "Cuéntame un chiste corto" + } + ], + "stream": true + } + }, + "vision": { + "summary": "Vision (multimodal input)", + "value": { + "model": "qwen3.6", + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "¿Qué hay en esta imagen?" + }, + { + "type": "image_url", + "image_url": { + "url": "https://example.com/foto.jpg" + } + } + ] + } + ] + } + }, + "json_schema": { + "summary": "Structured output (json_schema, strict)", + "value": { + "model": "qwen3.6", + "messages": [ + { + "role": "user", + "content": "Alice, 30 años." + } + ], + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "user", + "strict": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer" + } + }, + "required": [ + "name", + "age" + ], + "additionalProperties": false + } + } + } + } + }, + "reasoning_effort": { + "summary": "Reasoning effort (deepseek-v4-flash)", + "value": { + "model": "deepseek-v4-flash", + "messages": [ + { + "role": "user", + "content": "Resuelve paso a paso: 3x + 7 = 22" + } + ], + "reasoning_effort": "high" + } + }, + "premium_tier": { + "summary": "Premium tier model (glm5.2)", + "description": "`glm5.2` is served only to keys on the GLM 5.2 premium tier. Same request shape as any other model, only `model` changes. A key without the tier gets `403` `tier_restricted`.", + "value": { + "model": "glm5.2", + "messages": [ + { + "role": "user", + "content": "Refactor this function and explain the trade-offs." + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The chat completion. When `stream: false` the body is a single `chat.completion` (`application/json`). When `stream: true` the body is a `text/event-stream` of `chat.completion.chunk` events ending with `data: [DONE]`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatCompletion" + }, + "example": { + "id": "chatcmpl-...", + "created": 1778258163, + "model": "qwen3.6", + "object": "chat.completion", + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "role": "assistant", + "content": "¡Hola! ¿En qué puedo ayudarte?", + "reasoning_content": "..." + } + } + ], + "usage": { + "completion_tokens": 20, + "prompt_tokens": 17, + "total_tokens": 37 + } + } + }, + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/ChatCompletionChunk" + }, + "example": "data: {\"id\":\"chatcmpl-...\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"¡Hola\"}}]}\n\ndata: {\"id\":\"chatcmpl-...\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"!\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n" + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "402": { + "$ref": "#/components/responses/PaymentRequired" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/chat/completions \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"model\": \"qwen3.6\",\n \"messages\": [{\"role\": \"user\", \"content\": \"Hola\"}]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.chat.completions.create(\n model=\"qwen3.6\",\n messages=[{\"role\": \"user\", \"content\": \"Hola\"}],\n)\nprint(resp.choices[0].message.content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nconst resp = await client.chat.completions.create({\n model: \"qwen3.6\",\n messages: [{ role: \"user\", content: \"Hola\" }],\n});\nconsole.log(resp.choices[0].message.content);" + } + ] + } + }, + "/completions": { + "post": { + "operationId": "createCompletion", + "tags": [ + "Completions" + ], + "summary": "Create completion", + "description": "Creates a completion for the provided prompt. This is a legacy endpoint; for conversations use [Create chat completion](#tag/Chat) instead. Model: `qwen3.6`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "model", + "prompt" + ], + "properties": { + "model": { + "type": "string", + "enum": [ + "qwen3.6" + ], + "description": "The model to use. Currently `qwen3.6`.", + "example": "qwen3.6" + }, + "prompt": { + "type": "string", + "description": "The prompt to complete.", + "example": "The capital of France is" + }, + "max_tokens": { + "type": "integer", + "minimum": 1, + "description": "The maximum number of tokens to generate.", + "example": 16 + }, + "temperature": { + "type": "number", + "minimum": 0, + "maximum": 2, + "default": 0.6, + "description": "Sampling temperature. Higher is more random." + }, + "top_p": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0.95, + "description": "Nucleus sampling." + }, + "stream": { + "type": "boolean", + "default": false, + "description": "Stream partial completions as Server-Sent Events." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The completion.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Completion" + }, + "example": { + "id": "cmpl-...", + "object": "text_completion", + "created": 1778258166, + "model": "qwen3.6", + "choices": [ + { + "text": " Paris.", + "index": 0, + "finish_reason": "stop", + "logprobs": null + } + ], + "usage": { + "prompt_tokens": 5, + "completion_tokens": 3, + "total_tokens": 8 + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/completions \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{ \"model\": \"qwen3.6\", \"prompt\": \"The capital of France is\", \"max_tokens\": 16 }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.completions.create(\n model=\"qwen3.6\",\n prompt=\"The capital of France is\",\n max_tokens=16,\n)\nprint(resp.choices[0].text)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nconst resp = await client.completions.create({\n model: \"qwen3.6\",\n prompt: \"The capital of France is\",\n max_tokens: 16,\n});\nconsole.log(resp.choices[0].text);" + } + ] + } + }, + "/embeddings": { + "post": { + "operationId": "createEmbedding", + "tags": [ + "Embeddings" + ], + "summary": "Create embeddings", + "description": "Creates an embedding vector representing the input text. Pair with [Rerank](#tag/Rerank) to build a retrieval stack. Model: `qwen3-embedding` (4096-dimension vectors).", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "model", + "input" + ], + "properties": { + "model": { + "type": "string", + "enum": [ + "qwen3-embedding" + ], + "description": "The embedding model. Currently `qwen3-embedding`.", + "example": "qwen3-embedding" + }, + "input": { + "description": "The text to embed: a single string, or an array of strings to embed in one request.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "example": [ + "Hola mundo", + "Hello world" + ] + }, + "encoding_format": { + "type": "string", + "enum": [ + "float", + "base64" + ], + "default": "float", + "description": "The format of the returned vectors: `float` (default) or `base64`." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The embeddings.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmbeddingList" + }, + "example": { + "object": "list", + "model": "qwen3-embedding", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + 0.021, + 0.0105, + -0.0204, + "..." + ] + } + ], + "usage": { + "prompt_tokens": 3, + "total_tokens": 3 + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/embeddings \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{ \"model\": \"qwen3-embedding\", \"input\": [\"Hola mundo\", \"Hello world\"] }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.embeddings.create(\n model=\"qwen3-embedding\",\n input=[\"Hola mundo\", \"Hello world\"],\n)\nprint(resp.data[0].embedding[:3])" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nconst resp = await client.embeddings.create({\n model: \"qwen3-embedding\",\n input: [\"Hola mundo\", \"Hello world\"],\n});\nconsole.log(resp.data[0].embedding.slice(0, 3));" + } + ] + } + }, + "/rerank": { + "post": { + "operationId": "rerank", + "tags": [ + "Rerank" + ], + "summary": "Rerank documents", + "description": "Reranks a list of documents by their relevance to a query, returning them ordered most-relevant first. Completes the RAG stack with `qwen3-embedding`: retrieve top-K by embedding similarity, then reorder here. Supports 100+ languages and code. Model: `rerank`. Also available at `/v2/rerank`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "model", + "query", + "documents" + ], + "properties": { + "model": { + "type": "string", + "enum": [ + "rerank" + ], + "description": "The rerank model. Currently `rerank` (Qwen3-Reranker-8B).", + "example": "rerank" + }, + "query": { + "type": "string", + "description": "The query each document is scored against.", + "example": "What is the capital of France?" + }, + "documents": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The documents to rerank. Returned sorted by descending relevance, each keeping its original `index`.", + "example": [ + "Paris is the capital of France.", + "Berlin is the capital of Germany.", + "Madrid is the capital of Spain." + ] + }, + "top_n": { + "type": "integer", + "minimum": 1, + "description": "Return only the `top_n` most relevant documents. Defaults to all." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The reranked documents.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RerankResponse" + }, + "example": { + "id": "score-a032ee5767cab0ee", + "results": [ + { + "index": 0, + "relevance_score": 0.739, + "document": { + "text": "Paris is the capital of France." + } + }, + { + "index": 1, + "relevance_score": 0.6, + "document": { + "text": "Berlin is the capital of Germany." + } + } + ], + "meta": { + "billed_units": { + "total_tokens": 43 + }, + "tokens": { + "input_tokens": 43 + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/rerank \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"model\": \"rerank\",\n \"query\": \"What is the capital of France?\",\n \"documents\": [\"Paris is the capital of France.\", \"Berlin is the capital of Germany.\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\n# rerank isn't part of the OpenAI client, so call it directly:\nresp = client.post(\n \"/rerank\",\n cast_to=object,\n body={\n \"model\": \"rerank\",\n \"query\": \"What is the capital of France?\",\n \"documents\": [\"Paris is the capital of France.\", \"Berlin is the capital of Germany.\"],\n },\n)\nfor r in resp[\"results\"]:\n print(r[\"index\"], r[\"relevance_score\"])" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\n// rerank isn't part of the OpenAI client, so call it directly:\nconst res = await fetch(\"https://api.nan.builders/v1/rerank\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${process.env.NAN_API_KEY}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: \"rerank\",\n query: \"What is the capital of France?\",\n documents: [\"Paris is the capital of France.\", \"Berlin is the capital of Germany.\"],\n }),\n});\nconst data = await res.json();\nconsole.log(data.results);" + } + ] + } + }, + "/audio/speech": { + "post": { + "operationId": "createSpeech", + "tags": [ + "Audio" + ], + "summary": "Create speech", + "description": "Generates audio from the input text (text-to-speech). Returns the raw audio file, not JSON. Model: `kokoro`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "model", + "input", + "voice" + ], + "properties": { + "model": { + "type": "string", + "enum": [ + "kokoro" + ], + "description": "The TTS model. Currently `kokoro`.", + "example": "kokoro" + }, + "input": { + "type": "string", + "description": "The text to synthesize into audio.", + "example": "Bienvenido a NaN." + }, + "voice": { + "type": "string", + "description": "The voice to use, e.g. `af_heart` (English female), `ef_dora` (Spanish female) or `em_alex` (Spanish male).", + "example": "ef_dora" + }, + "response_format": { + "type": "string", + "enum": [ + "mp3", + "wav", + "flac", + "aac", + "pcm", + "opus" + ], + "default": "mp3", + "description": "The audio format to return." + }, + "speed": { + "type": "number", + "default": 1, + "description": "Playback speed multiplier." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The generated audio file, in the requested format.", + "content": { + "audio/mpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/audio/speech \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{ \"model\": \"kokoro\", \"voice\": \"ef_dora\", \"input\": \"Bienvenido a NaN.\" }' \\\n -o speech.mp3" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nspeech = client.audio.speech.create(\n model=\"kokoro\",\n voice=\"ef_dora\",\n input=\"Bienvenido a NaN.\",\n)\nspeech.stream_to_file(\"speech.mp3\")" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nimport fs from \"node:fs\";\nconst speech = await client.audio.speech.create({\n model: \"kokoro\",\n voice: \"ef_dora\",\n input: \"Bienvenido a NaN.\",\n});\nfs.writeFileSync(\"speech.mp3\", Buffer.from(await speech.arrayBuffer()));" + } + ] + } + }, + "/audio/transcriptions": { + "post": { + "operationId": "createTranscription", + "tags": [ + "Audio" + ], + "summary": "Create transcription", + "description": "Transcribes audio into text (speech-to-text). Model: `whisper`. Send the audio as `multipart/form-data`.\n\nLimits: max 25 MB per request. Audio longer than ~2 minutes may time out (`524`); split it into shorter segments. OGG/Opus and MP3 give the best size-to-quality ratio.", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file", + "model" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "The audio file to transcribe." + }, + "model": { + "type": "string", + "enum": [ + "whisper" + ], + "description": "The transcription model. Currently `whisper`." + }, + "language": { + "type": "string", + "description": "The input language as an ISO-639-1 code (e.g. `es`, `en`). Auto-detected if omitted." + }, + "response_format": { + "type": "string", + "enum": [ + "json", + "verbose_json" + ], + "default": "json", + "description": "Response shape. `verbose_json` adds `duration` and `segments`." + }, + "timestamp_granularities[]": { + "type": "string", + "enum": [ + "word", + "segment" + ], + "description": "Timestamp detail (with `verbose_json`): `word` fills the `words` array; `segment` (default) fills `segments`." + }, + "temperature": { + "type": "number", + "description": "Sampling temperature for the decoder." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The transcription (example shown for `verbose_json`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transcription" + }, + "example": { + "text": "Hola, esto es una prueba.", + "language": "es", + "task": "transcribe", + "duration": 1.728, + "segments": [ + { + "id": 1, + "start": 0, + "end": 1.4, + "text": " Hola, esto es una prueba.", + "avg_logprob": -0.059, + "no_speech_prob": 0.044 + } + ], + "words": null + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "524": { + "description": "Timeout: the audio was too long to transcribe in one request. Split it into segments of 2 minutes or less." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/audio/transcriptions \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -F \"model=whisper\" \\\n -F \"file=@audio.mp3\"" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.audio.transcriptions.create(\n model=\"whisper\",\n file=open(\"audio.mp3\", \"rb\"),\n)\nprint(resp.text)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nimport fs from \"node:fs\";\nconst resp = await client.audio.transcriptions.create({\n model: \"whisper\",\n file: fs.createReadStream(\"audio.mp3\"),\n});\nconsole.log(resp.text);" + } + ] + } + }, + "/responses": { + "post": { + "operationId": "createResponse", + "tags": [ + "Responses" + ], + "summary": "Create response", + "description": "Creates a model response using the OpenAI-style Responses API. Models: `qwen3.6`, `gemma4`. Streaming currently emits a single terminal event; for token-by-token streaming, use [Create chat completion](#tag/Chat) with `stream: true`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "model", + "input" + ], + "properties": { + "model": { + "type": "string", + "enum": [ + "qwen3.6", + "gemma4" + ], + "description": "The model to use.", + "example": "qwen3.6" + }, + "input": { + "description": "The input: a single string, or an array of messages in the Responses format.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "object" + } + } + ], + "example": "Hola, ¿cómo estás?" + }, + "max_output_tokens": { + "type": "integer", + "minimum": 1, + "description": "The maximum number of output tokens. Default 65536 on `qwen3.6`." + }, + "temperature": { + "type": "number", + "minimum": 0, + "maximum": 2, + "default": 0.6, + "description": "Sampling temperature." + }, + "top_p": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0.95, + "description": "Nucleus sampling." + }, + "instructions": { + "type": "string", + "description": "System-level instructions that steer the model." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseObject" + }, + "example": { + "id": "resp_...", + "created_at": 1778258181, + "model": "qwen3.6", + "object": "response", + "status": "completed", + "output": [ + { + "id": "rs_...", + "type": "reasoning", + "summary": [], + "content": [ + { + "type": "reasoning_text", + "text": "..." + } + ] + }, + { + "id": "msg_...", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [ + { + "type": "output_text", + "text": "Hola.", + "annotations": [] + } + ] + } + ], + "usage": { + "input_tokens": 17, + "output_tokens": 118, + "total_tokens": 135 + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/responses \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{ \"model\": \"qwen3.6\", \"input\": \"Hola, ¿cómo estás?\" }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.responses.create(\n model=\"qwen3.6\",\n input=\"Hola, ¿cómo estás?\",\n)\nprint(resp.output_text)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nconst resp = await client.responses.create({\n model: \"qwen3.6\",\n input: \"Hola, ¿cómo estás?\",\n});\nconsole.log(resp.output_text);" + } + ] + } + }, + "/images/generations": { + "post": { + "operationId": "createImage", + "tags": [ + "Images" + ], + "summary": "Create image", + "description": "Creates one or more images from a text prompt (text-to-image). Model: `flux-2-klein`.\n\nRequires `inference`-tier membership (`403` otherwise). Image endpoints are rate-limited separately from chat: 20 requests per minute and 100 requests per month per user. These don't consume your chat token budget. `stream: true` is not supported. For OpenAI SDK compatibility, `quality`, `style`, `background`, `moderation`, `output_format`, `output_compression`, and `user` are accepted but ignored.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "prompt" + ], + "properties": { + "prompt": { + "type": "string", + "description": "A text description of the image to generate.", + "example": "Un faro al atardecer sobre acantilados, estilo cinemático" + }, + "model": { + "type": "string", + "default": "flux-2-klein", + "description": "The image model. An unknown model returns `404`.", + "example": "flux-2-klein" + }, + "n": { + "type": "integer", + "minimum": 1, + "maximum": 4, + "default": 1, + "description": "The number of images to generate (1–4). Values above 4 return `400`." + }, + "size": { + "type": "string", + "default": "1024x1024", + "description": "\"WIDTHxHEIGHT\". Each side divisible by 16 and between 256 and 1536, aspect ratio 1:3–3:1. `auto` or omitted → `1024x1024`.", + "example": "1024x1024" + }, + "response_format": { + "type": "string", + "enum": [ + "url", + "b64_json" + ], + "default": "url", + "description": "`url` returns a temporary link (~60 min); `b64_json` returns inline base64 bytes." + }, + "seed": { + "type": "integer", + "description": "NaN extension: base seed for reproducibility (in OpenAI SDKs, pass via `extra_body`)." + }, + "guidance": { + "type": "number", + "description": "NaN extension: FLUX guidance scale (in OpenAI SDKs, pass via `extra_body`)." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The generated images.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImageResponse" + }, + "example": { + "created": 1778258200, + "data": [ + { + "url": "https://...r2.../image.png" + } + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "description": "The model does not exist (`model_not_found`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/images/generations \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{ \"model\": \"flux-2-klein\", \"prompt\": \"Un faro al atardecer sobre acantilados\", \"size\": \"1024x1024\" }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nimg = client.images.generate(\n model=\"flux-2-klein\",\n prompt=\"Un faro al atardecer sobre acantilados\",\n size=\"1024x1024\",\n)\nprint(img.data[0].url)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nconst img = await client.images.generate({\n model: \"flux-2-klein\",\n prompt: \"Un faro al atardecer sobre acantilados\",\n size: \"1024x1024\",\n});\nconsole.log(img.data[0].url);" + } + ] + } + }, + "/images/edits": { + "post": { + "operationId": "editImage", + "tags": [ + "Images" + ], + "summary": "Edit image", + "description": "Creates an image from one or more reference images and a prompt (image-to-image). Model: `flux-2-klein`. Send images as `multipart/form-data`. Same `inference`-tier membership and monthly quota as [Create image](#tag/Images). `mask` is not supported (returns `400`).", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "image", + "prompt" + ], + "properties": { + "image": { + "type": "string", + "format": "binary", + "description": "One or more reference images (up to 4; extras are dropped). PNG, JPEG or WebP, each under 25 MB. Repeat the field as `image[]` for multiple." + }, + "prompt": { + "type": "string", + "description": "A description of the edit or transformation to apply." + }, + "model": { + "type": "string", + "default": "flux-2-klein", + "description": "The image model." + }, + "n": { + "type": "integer", + "minimum": 1, + "maximum": 4, + "default": 1, + "description": "The number of images to generate (1–4)." + }, + "size": { + "type": "string", + "default": "1024x1024", + "description": "Output size, same rules as Create image." + }, + "response_format": { + "type": "string", + "enum": [ + "url", + "b64_json" + ], + "default": "url", + "description": "`url` (default) or `b64_json`." + }, + "seed": { + "type": "integer", + "description": "NaN extension: base seed (form field)." + }, + "guidance": { + "type": "number", + "description": "NaN extension: FLUX guidance scale (form field)." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The generated image(s).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImageResponse" + }, + "example": { + "created": 1778258200, + "data": [ + { + "url": "https://...r2.../image.png" + } + ] + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/images/edits \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -F \"model=flux-2-klein\" \\\n -F \"image[]=@ref.png\" \\\n -F \"prompt=Convierte la escena en invierno con nieve\"" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nimg = client.images.edit(\n model=\"flux-2-klein\",\n image=open(\"ref.png\", \"rb\"),\n prompt=\"Convierte la escena en invierno con nieve\",\n)\nprint(img.data[0].url)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.NAN_API_KEY,\n baseURL: \"https://api.nan.builders/v1\",\n});\n\nimport fs from \"node:fs\";\nconst img = await client.images.edit({\n model: \"flux-2-klein\",\n image: fs.createReadStream(\"ref.png\"),\n prompt: \"Convierte la escena en invierno con nieve\",\n});\nconsole.log(img.data[0].url);" + } + ] + } + }, + "/search": { + "post": { + "operationId": "search", + "tags": [ + "Search" + ], + "summary": "Web search", + "description": "An authenticated web search tool for agents. Give it a query and it returns ranked results (title, URL, snippet, source), drawn from a hybrid of upstream search providers. Results come back through the NaN API, so your key never talks to a third-party search provider directly, and you never hold a provider key.\n\nSet `fetch_content: true` to also extract the readable main text of the top results (adds latency). You supply only `query`; when `fetch_content` is `true` the server fetches page content **only** for the URLs the search returned, through a hardened anti-SSRF fetcher, so you cannot pass an arbitrary URL to fetch.\n\nLimits are per API key and separate from the model endpoints, so searching does not consume your chat RPM budget or vice versa: 20 requests per minute, 3 concurrent, and 500 searches per day. Exceeding the per-minute or concurrency limit returns `429` `rate_limit_exceeded`; exhausting the daily quota returns `429` `insufficient_quota`. A `429` carries a `Retry-After` header. Repeated identical queries within ~15 minutes are served from a short-lived cache (`cached: true`); cached hits still count toward your rate and quota.\n\n## Use it as an agent tool\n\nDrop this OpenAI-style function schema into your model call's `tools` array. When the model emits a `web_search` tool call, invoke `POST /v1/search` with the arguments and feed the JSON response back as the tool result.\n\n```json\n{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"web_search\",\n \"description\": \"Search the public web and return relevant results (title, URL, snippet, and optional page content). Use it when the answer may depend on current events, recent developments, prices, release/version numbers, or facts you are not confident are up to date.\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"query\": { \"type\": \"string\", \"description\": \"The web search query.\" },\n \"count\": { \"type\": \"integer\", \"description\": \"Number of results to return (1-20).\", \"minimum\": 1, \"maximum\": 20, \"default\": 5 },\n \"freshness\": { \"type\": \"string\", \"description\": \"Restrict results by recency: 'pd' (past day), 'pw' (past week), 'pm' (past month), 'py' (past year), or a 'YYYY-MM-DDtoYYYY-MM-DD' date range. Omit for no time filter.\" },\n \"fetch_content\": { \"type\": \"boolean\", \"description\": \"When true, also fetch and include the readable text of the top results (slower). Default false returns snippets only.\", \"default\": false }\n },\n \"required\": [\"query\"]\n }\n }\n}\n```", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "description": "The search query.", + "example": "latest go release" + }, + "count": { + "type": "integer", + "minimum": 1, + "maximum": 20, + "default": 5, + "description": "Number of results to return, 1–20 (values outside the range are clamped).", + "example": 5 + }, + "freshness": { + "type": "string", + "description": "Recency filter: `pd` (past day), `pw` (past week), `pm` (past month), `py` (past year), or a `YYYY-MM-DDtoYYYY-MM-DD` date range. Omit for no time filter.", + "example": "pw" + }, + "fetch_content": { + "type": "boolean", + "default": false, + "description": "When `true`, also fetch and include the readable main text of the top results (`content`). Slower; defaults to snippets only.", + "example": false + } + } + }, + "example": { + "query": "latest go release", + "count": 5, + "freshness": "pw", + "fetch_content": false + } + } + } + }, + "responses": { + "200": { + "description": "The search results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponse" + }, + "example": { + "results": [ + { + "title": "Go 1.23 is released", + "url": "https://go.dev/blog/go1.23", + "snippet": "The latest Go release adds ...", + "source": "primary" + } + ], + "cached": false + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "description": "Search is temporarily unavailable (`search_unavailable`); retry shortly.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/v1/search \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{ \"query\": \"latest go release\", \"count\": 5, \"freshness\": \"pw\" }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\n# search isn't part of the OpenAI client, so call it directly:\nresp = client.post(\n \"/search\",\n cast_to=object,\n body={\"query\": \"latest go release\", \"count\": 5, \"freshness\": \"pw\"},\n)\nfor r in resp[\"results\"]:\n print(r[\"title\"], r[\"url\"])" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "const res = await fetch(\"https://api.nan.builders/v1/search\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${process.env.NAN_API_KEY}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ query: \"latest go release\", count: 5, freshness: \"pw\" }),\n});\nconst data = await res.json();\nfor (const r of data.results) console.log(r.title, r.url);" + } + ] + } + }, + "/mcp": { + "post": { + "operationId": "mcpJsonRpc", + "tags": [ + "MCP" + ], + "summary": "MCP server (JSON-RPC)", + "description": "Remote [Model Context Protocol](https://modelcontextprotocol.io) server, so our tools can be used inside any MCP-compatible agent or client with the same `sk-` key as the REST API.\n\nTransport is streamable HTTP and stateless; the protocol is JSON-RPC 2.0 with the methods `initialize`, `tools/list`, `tools/call` and `ping`. Today the server exposes a single tool, `web_search`, with the same arguments as [Web search](#tag/Search); it is a growing registry, so use `tools/list` to discover the current set.\n\nMCP calls share the **same** per-key rate limit, daily quota and concurrency as the equivalent REST endpoint, so there is no separate budget. A `web_search` tool call over MCP counts exactly like a `POST /v1/search` request.\n\nNote this endpoint lives at the host root, `https://api.nan.builders/mcp`, not under `/v1`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonRpcRequest" + }, + "example": { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "web_search", + "arguments": { + "query": "kubernetes 1.34 release", + "count": 5 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "A JSON-RPC 2.0 response. Protocol-level failures are reported in `error` with HTTP `200`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonRpcResponse" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl https://api.nan.builders/mcp \\\n -H \"Authorization: Bearer $NAN_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"web_search\",\n \"arguments\": { \"query\": \"kubernetes 1.34 release\", \"count\": 5 }\n }\n }'" + }, + { + "lang": "json", + "label": "MCP client config", + "source": "{\n \"mcpServers\": {\n \"nan\": {\n \"url\": \"https://api.nan.builders/mcp\",\n \"headers\": {\n \"Authorization\": \"Bearer sk-your-key-here\"\n }\n }\n }\n}" + } + ] + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "Personal, non-transferable API key. Send as `Authorization: Bearer ` on every request." + } + }, + "schemas": { + "Model": { + "type": "object", + "description": "An available model, in the OpenAI-compatible model shape.", + "properties": { + "id": { + "type": "string", + "description": "The model's unique slug. Pass this value as the `model` field in your requests.", + "example": "qwen3.6" + }, + "object": { + "type": "string", + "enum": [ + "model" + ], + "description": "The object type, which is always `model`.", + "example": "model" + }, + "created": { + "type": "integer", + "description": "The Unix timestamp (in seconds) when the model became available.", + "example": 1677610602 + }, + "owned_by": { + "type": "string", + "description": "The organization that owns the model.", + "example": "nan" + } + } + }, + "ContentPart": { + "description": "One part of a multimodal message. Use an array of these as `content` for vision input.", + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "text" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "text" + ] + }, + "text": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "image_url" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "image_url" + ] + }, + "image_url": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "Public URL or `data:` URI of the image." + } + } + } + } + } + ] + }, + "Message": { + "type": "object", + "required": [ + "role" + ], + "description": "A single message in a conversation. The `role` determines which other fields apply.", + "properties": { + "role": { + "type": "string", + "enum": [ + "system", + "user", + "assistant", + "tool" + ], + "description": "The role of the message author: `system`, `user`, `assistant`, or `tool`." + }, + "content": { + "description": "The contents of the message. A plain string, or an array of content parts for multimodal input. Can be `null` on an `assistant` message that only returns tool calls.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContentPart" + } + } + ] + }, + "name": { + "type": "string", + "description": "An optional name for the author of this message." + }, + "tool_calls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolCall" + }, + "description": "The tool calls generated by the model. Present on `assistant` messages when the model decides to invoke one or more tools." + }, + "tool_call_id": { + "type": "string", + "description": "The id of the tool call this message answers. Required when `role` is `tool`." + }, + "reasoning_content": { + "type": "string", + "description": "The model's reasoning trace. Present only for reasoning models; safe to display or ignore." + } + } + }, + "Tool": { + "type": "object", + "required": [ + "type", + "function" + ], + "description": "A function the model may call. Provide these in the request's `tools` array.", + "properties": { + "type": { + "type": "string", + "enum": [ + "function" + ], + "description": "The type of the tool. Currently only `function` is supported." + }, + "function": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the function to call. Up to 64 characters; letters, digits, underscores and dashes." + }, + "description": { + "type": "string", + "description": "What the function does, used by the model to decide when and how to call it. Be descriptive." + }, + "parameters": { + "type": "object", + "description": "The parameters the function accepts, described as a JSON Schema object." + } + } + } + } + }, + "ToolChoice": { + "description": "Controls tool use: `\"auto\"` (default), `\"none\"`, `\"required\"`, or force one with `{ \"type\": \"function\", \"function\": { \"name\": \"...\" } }`.", + "oneOf": [ + { + "type": "string", + "enum": [ + "auto", + "none", + "required" + ] + }, + { + "type": "object" + } + ] + }, + "ToolCall": { + "type": "object", + "description": "A tool the model chose to call. Execute it, then send the result back in a `tool` message referencing `id`.", + "properties": { + "id": { + "type": "string", + "description": "A unique id for this tool call. Pass it as `tool_call_id` in your follow-up `tool` message." + }, + "type": { + "type": "string", + "enum": [ + "function" + ], + "description": "The type of the tool call. Always `function`." + }, + "function": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the function to call." + }, + "arguments": { + "type": "string", + "description": "The function arguments as a JSON-encoded string. Parse and validate it before use; models can occasionally emit malformed JSON." + } + } + } + } + }, + "ResponseFormat": { + "description": "Force structured output. `json_object` guarantees syntactically valid JSON; `json_schema` (with `strict: true`) constrains the output to a schema. Works on `qwen3.6` and `gemma4`.", + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "json_object" + ] + } + } + }, + { + "type": "object", + "required": [ + "type", + "json_schema" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "json_schema" + ] + }, + "json_schema": { + "type": "object", + "required": [ + "name", + "schema" + ], + "properties": { + "name": { + "type": "string" + }, + "strict": { + "type": "boolean" + }, + "schema": { + "type": "object" + } + } + } + } + } + ] + }, + "Usage": { + "type": "object", + "description": "Token accounting for the request.", + "properties": { + "prompt_tokens": { + "type": "integer", + "description": "The number of tokens in the prompt (input)." + }, + "completion_tokens": { + "type": "integer", + "description": "The number of tokens in the generated completion (output)." + }, + "total_tokens": { + "type": "integer", + "description": "The total tokens used: `prompt_tokens` plus `completion_tokens`. This is what counts against your quota." + } + } + }, + "ChatCompletion": { + "type": "object", + "description": "The model's response to a chat request. Represents a single, non-streamed completion.", + "properties": { + "id": { + "type": "string", + "description": "A unique identifier for the completion." + }, + "object": { + "type": "string", + "enum": [ + "chat.completion" + ], + "description": "The object type, always `chat.completion`." + }, + "created": { + "type": "integer", + "description": "The Unix timestamp (in seconds) when the completion was created." + }, + "model": { + "type": "string", + "description": "The model that produced the completion." + }, + "choices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "index": { + "type": "integer", + "description": "The index of this choice in the `choices` array." + }, + "finish_reason": { + "type": "string", + "enum": [ + "stop", + "length", + "tool_calls" + ], + "description": "Why the model stopped: `stop` (natural end or a stop sequence), `length` (hit `max_tokens`), or `tool_calls` (the model called a tool)." + }, + "message": { + "$ref": "#/components/schemas/Message" + } + } + }, + "description": "The list of completion choices. Contains a single choice unless you request more." + }, + "usage": { + "$ref": "#/components/schemas/Usage", + "description": "Token usage for the request." + } + } + }, + "ChatCompletionChunk": { + "type": "object", + "description": "One Server-Sent Event chunk when `stream: true`. New text arrives in `choices[0].delta.content`; the stream ends with `data: [DONE]`.", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "chat.completion.chunk" + ], + "description": "The object type, always `chat.completion.chunk`." + }, + "created": { + "type": "integer", + "description": "The Unix timestamp (in seconds) when the chunk was created. Identical across all chunks of one response." + }, + "model": { + "type": "string", + "description": "The model producing the response." + }, + "choices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "index": { + "type": "integer" + }, + "delta": { + "type": "object", + "properties": { + "role": { + "type": "string" + }, + "content": { + "type": "string" + }, + "reasoning_content": { + "type": "string" + }, + "tool_calls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolCall" + } + } + }, + "description": "The incremental update for this chunk. `role` is set on the first chunk; `content` carries the new text to append." + }, + "finish_reason": { + "type": [ + "string", + "null" + ], + "description": "`null` on intermediate chunks; set to the stop reason on the final content chunk." + } + } + } + } + } + }, + "Error": { + "type": "object", + "description": "OpenAI-standard error envelope: a non-2xx HTTP status with a JSON body describing the problem.", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "type": { + "type": [ + "string", + "null" + ] + }, + "param": { + "type": [ + "string", + "null" + ] + }, + "code": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "example": { + "error": { + "message": "...", + "type": null, + "param": null, + "code": "..." + } + } + }, + "Embedding": { + "type": "object", + "description": "A single embedding vector for one input.", + "properties": { + "object": { + "type": "string", + "enum": [ + "embedding" + ], + "description": "The object type, always `embedding`." + }, + "index": { + "type": "integer", + "description": "The position of this embedding in the request `input` array." + }, + "embedding": { + "type": "array", + "items": { + "type": "number" + }, + "description": "The embedding vector: a list of 4096 floats. Cosine similarity between vectors measures semantic closeness." + } + } + }, + "EmbeddingList": { + "type": "object", + "description": "A list of embeddings, one per input.", + "properties": { + "object": { + "type": "string", + "enum": [ + "list" + ], + "description": "Always `list`." + }, + "model": { + "type": "string", + "description": "The model used to create the embeddings." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Embedding" + }, + "description": "The embeddings, in the same order as the request `input`." + }, + "usage": { + "type": "object", + "description": "Token usage for the request.", + "properties": { + "prompt_tokens": { + "type": "integer", + "description": "Tokens in the input." + }, + "total_tokens": { + "type": "integer", + "description": "Total tokens used." + } + } + } + } + }, + "RerankResult": { + "type": "object", + "description": "One reranked document.", + "properties": { + "index": { + "type": "integer", + "description": "The position of this document in the request `documents` array." + }, + "relevance_score": { + "type": "number", + "description": "Relevance to the query, in the range [0, 1]. Higher is more relevant. Results are returned sorted by this, descending." + }, + "document": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "The document text." + } + }, + "description": "The document, echoed back." + } + } + }, + "RerankResponse": { + "type": "object", + "description": "The reranked documents, most-relevant first.", + "properties": { + "id": { + "type": "string", + "description": "A unique identifier for the rerank request." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RerankResult" + }, + "description": "The documents, sorted by descending `relevance_score`. Each carries its original `index`." + }, + "meta": { + "type": "object", + "description": "Request metadata, including token accounting.", + "properties": { + "billed_units": { + "type": "object", + "properties": { + "total_tokens": { + "type": "integer" + } + } + }, + "tokens": { + "type": "object", + "properties": { + "input_tokens": { + "type": "integer" + } + } + } + } + } + } + }, + "Transcription": { + "type": "object", + "description": "A transcription of the supplied audio.", + "properties": { + "text": { + "type": "string", + "description": "The full transcribed text." + }, + "language": { + "type": "string", + "description": "The detected (or supplied) language, as an ISO-639-1 code." + }, + "task": { + "type": "string", + "description": "The task performed, always `transcribe`." + }, + "duration": { + "type": "number", + "description": "The audio duration in seconds. Present with `verbose_json`." + }, + "segments": { + "type": "array", + "description": "Time-aligned segments. Present with `verbose_json`.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "start": { + "type": "number", + "description": "Segment start time in seconds." + }, + "end": { + "type": "number", + "description": "Segment end time in seconds." + }, + "text": { + "type": "string" + }, + "avg_logprob": { + "type": "number" + }, + "no_speech_prob": { + "type": "number" + } + } + } + }, + "words": { + "type": [ + "array", + "null" + ], + "description": "Per-word timestamps `[{ word, start, end, probability }]`. Populated only when `timestamp_granularities[]=word`." + } + } + }, + "ResponseObject": { + "type": "object", + "description": "A response produced by the Responses API.", + "properties": { + "id": { + "type": "string", + "description": "A unique identifier for the response." + }, + "created_at": { + "type": "integer", + "description": "The Unix timestamp (in seconds) when the response was created." + }, + "model": { + "type": "string", + "description": "The model that produced the response." + }, + "object": { + "type": "string", + "enum": [ + "response" + ], + "description": "The object type, always `response`." + }, + "status": { + "type": "string", + "description": "The response status, e.g. `completed`." + }, + "output": { + "type": "array", + "description": "The output items. May contain `reasoning` blocks (reasoning models) followed by a `message` block with the reply text in `content[].text`.", + "items": { + "type": "object" + } + }, + "usage": { + "type": "object", + "description": "Token usage for the request.", + "properties": { + "input_tokens": { + "type": "integer" + }, + "output_tokens": { + "type": "integer" + }, + "total_tokens": { + "type": "integer" + } + } + } + } + }, + "ImageResponse": { + "type": "object", + "description": "The generated image(s).", + "properties": { + "created": { + "type": "integer", + "description": "The Unix timestamp (in seconds) when the images were created." + }, + "data": { + "type": "array", + "description": "The generated images. Each item has a `url` (default) or a `b64_json` payload, depending on `response_format`.", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "A temporary URL to the image, valid ~60 minutes. Returned when `response_format` is `url`." + }, + "b64_json": { + "type": "string", + "description": "The base64-encoded image bytes. Returned when `response_format` is `b64_json`." + } + } + } + } + } + }, + "Completion": { + "type": "object", + "description": "A completion for the provided prompt (legacy).", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "enum": [ + "text_completion" + ], + "description": "Always `text_completion`." + }, + "created": { + "type": "integer", + "description": "The Unix timestamp (in seconds) when the completion was created." + }, + "model": { + "type": "string" + }, + "choices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "The generated text." + }, + "index": { + "type": "integer" + }, + "finish_reason": { + "type": "string", + "enum": [ + "stop", + "length" + ], + "description": "Why generation stopped." + }, + "logprobs": { + "type": [ + "object", + "null" + ] + } + } + } + }, + "usage": { + "$ref": "#/components/schemas/Usage" + } + } + }, + "SearchResult": { + "type": "object", + "description": "One web search result.", + "properties": { + "title": { + "type": "string", + "description": "The result title (plain text)." + }, + "url": { + "type": "string", + "description": "The result URL." + }, + "snippet": { + "type": "string", + "description": "A short excerpt from the page." + }, + "content": { + "type": "string", + "description": "The readable main text of the page. Present only when `fetch_content` was `true` and the fetch succeeded." + }, + "source": { + "type": "string", + "description": "Which tier served this result: `primary` or `fallback`." + } + } + }, + "SearchResponse": { + "type": "object", + "description": "The web search results.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchResult" + }, + "description": "The ranked results, most relevant first." + }, + "cached": { + "type": "boolean", + "description": "`true` when the results came from the short-lived (~15 min) exact-query cache." + } + } + }, + "JsonRpcRequest": { + "type": "object", + "required": [ + "jsonrpc", + "method" + ], + "properties": { + "jsonrpc": { + "type": "string", + "enum": [ + "2.0" + ], + "description": "Always `2.0`." + }, + "id": { + "description": "Request id, echoed back in the response.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "method": { + "type": "string", + "enum": [ + "initialize", + "tools/list", + "tools/call", + "ping" + ], + "description": "The JSON-RPC method to invoke." + }, + "params": { + "type": "object", + "additionalProperties": true, + "description": "Method arguments. For `tools/call`: `name` (the tool) and `arguments` (its input)." + } + } + }, + "JsonRpcResponse": { + "type": "object", + "properties": { + "jsonrpc": { + "type": "string", + "enum": [ + "2.0" + ] + }, + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "result": { + "type": "object", + "additionalProperties": true, + "description": "Present on success. Shape depends on the method." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, + "description": "Present on a protocol-level failure." + } + } + } + }, + "responses": { + "BadRequest": { + "description": "Invalid parameter. The body includes `param` with the offending field. Safety filter returns `content_policy_violation`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Unauthorized": { + "description": "Invalid or missing `Authorization` header (`invalid_api_key`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Forbidden": { + "description": "Your tier lacks access to this endpoint (`tier_restricted`). Image generation requires `inference` membership.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "RateLimited": { + "description": "Rate limit exceeded: `rpm_limit` or `max_parallel_requests` (`rate_limit_exceeded`), or monthly quota exhausted (`quota_exceeded` / `insufficient_quota`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "PaymentRequired": { + "description": "The token allowance for the billing period is spent on a model that carries one, such as `glm5.2`. The request was refused before it reached the model, so nothing ran. Not retryable: the counter returns to zero when your billing period starts. The wire `code` is `cap_reached` or `monthly_cap_reached`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": { + "message": "Token allowance for this billing period exhausted for model glm5.2. The counter returns to zero when your next billing period starts.", + "type": "monthly_cap_reached", + "param": null, + "code": "monthly_cap_reached" + } + } + } + } + } + } + }, + "externalDocs": { + "description": "NaN Docs", + "url": "https://nan.builders/docs" + } +} diff --git a/src/layouts/Docs.astro b/src/layouts/Docs.astro index ef7bad7..3f1c5be 100644 --- a/src/layouts/Docs.astro +++ b/src/layouts/Docs.astro @@ -1,61 +1,126 @@ --- import '../styles/global.css'; +import '../styles/docs-shell.css'; -// Fuentes autohospedadas, igual que NanBase. Antes venían de Google Fonts: eso -// bloqueaba el render y mandaba la IP de cada visitante a un tercero. +// Self-hosted fonts, as in NanBase. They used to come from Google Fonts: that +// blocked rendering and sent every visitor's IP to a third party. import '@fontsource-variable/archivo/wdth.css'; import '@fontsource/jetbrains-mono/400.css'; import '@fontsource/jetbrains-mono/500.css'; import '@fontsource/jetbrains-mono/700.css'; -import { getCollection } from 'astro:content'; +import { getCollection, render } from 'astro:content'; +import DocsTopBar from '../components/docs/DocsTopBar.astro'; +import { API_DOC_META, API_DOC_SLUG } from '../lib/apiDoc'; +import spec from '../data/openapi.json'; +import { + apiSearchHeadings, + groupDocsNav, + isActiveDocPath, + type DocsNavItem, +} from '../lib/docsNav'; interface Props { title: string; description?: string; } -// La documentación existe solo en inglés (la colección MDX no está traducida), -// así que el idioma de la página, el copy por defecto y la tarjeta social lo son. +// The documentation exists in English only (the MDX collection is not +// translated), so the page language, the default copy and the social card are. const { title, description = 'NaN docs. Connect to our open models through an OpenAI-compatible API.' } = Astro.props; const siteUrl = 'https://nan.builders'; -// Sin barra final, igual que el canonical que emite NanBase, para que las dos -// variantes de cada URL no compitan entre sí. +// No trailing slash, matching the canonical NanBase emits, so the two variants +// of each URL do not compete with each other. const canonical = `${siteUrl}${Astro.url.pathname.replace(/\/+$/, '') || '/'}`; const ogImage = `${siteUrl}/og/og-en.png`; -const socialTitle = `${title} — NaN Docs`; +const socialTitle = `${title} · NaN Docs`; const entries = await getCollection('docs'); -entries.sort((a, b) => a.data.order - b.data.order); -interface NavItem { - slug: string; - label: string; -} - -const navItems: NavItem[] = entries.map((entry) => ({ - slug: entry.id === 'intro' ? '/docs' : `/docs/${entry.id}`, - label: entry.data.title, -})); - -const currentPageIndex = navItems.findIndex((item) => item.slug === Astro.url.pathname); -const prevPage = currentPageIndex > 0 ? navItems[currentPageIndex - 1] : null; -const nextPage = currentPageIndex >= 0 && currentPageIndex < navItems.length - 1 - ? navItems[currentPageIndex + 1] - : null; - -const pathParts = Astro.url.pathname.split('/').filter(Boolean); -const breadcrumbSegments = pathParts.map((part, i) => { - const path = '/' + pathParts.slice(0, i + 1).join('/'); - const item = navItems.find((n) => n.slug === path); - return { - label: item ? item.label : part.replace(/-/g, ' '), - path: path === Astro.url.pathname ? null : path, - isCurrent: path === Astro.url.pathname, - }; -}); +/* + * The API reference does not come from the collection: Scalar serves /docs/api + * from the spec, so there is no entry to take a title, order or group from. It + * is added by hand from apiDoc.ts, the same module /api/docs reads them from, + * so the page and the manifest cannot disagree. + */ +const navItems: DocsNavItem[] = [ + ...entries.map((entry) => ({ + slug: entry.id === 'intro' ? '/docs' : `/docs/${entry.id}`, + label: entry.data.title, + order: entry.data.order, + group: entry.data.group, + description: entry.data.description, + })), + { + slug: `/docs/${API_DOC_SLUG}`, + label: API_DOC_META.title, + order: API_DOC_META.order, + group: API_DOC_META.group, + description: API_DOC_META.description, + }, +].sort((a, b) => a.order - b.order); + +const navGroups = groupDocsNav(navItems); + +const here = Astro.url.pathname.replace(/\/+$/, '') || '/docs'; +const isActive = (slug: string) => isActiveDocPath(here, slug); + +/* + * Breadcrumbs. The previous layout had them and helmcode does not, so porting + * its structure dropped them; they are restored because under /docs they are + * the only clue to where you are when you arrive from a search engine. + */ +const breadcrumb = Astro.url.pathname + .split('/') + .filter(Boolean) + .map((part, i, parts) => { + const path = '/' + parts.slice(0, i + 1).join('/'); + const item = navItems.find((n) => n.slug === path); + return { + label: item ? item.label : part.replace(/-/g, ' '), + path: path === here ? null : path, + isCurrent: path === here, + }; + }); + +const currentIndex = navItems.findIndex((item) => isActive(item.slug)); +const prevPage = currentIndex > 0 ? navItems[currentIndex - 1] : null; +const nextPage = + currentIndex >= 0 && currentIndex < navItems.length - 1 ? navItems[currentIndex + 1] : null; + +/* + * Search index. + * + * helmcode builds it with import.meta.glob over .md pages; here the guides are + * a content collection, so the headings come from render(entry), and the API + * reference's come from the spec, which is its only textual representation + * (Scalar renders on the client). + */ +const collectionIndex = await Promise.all( + entries.map(async (entry) => { + const { headings } = await render(entry); + return { + title: entry.data.title, + href: entry.id === 'intro' ? '/docs' : `/docs/${entry.id}`, + description: entry.data.description, + headings: headings + .filter((h) => h.depth >= 2 && h.depth <= 3) + .map((h) => ({ text: h.text, slug: h.slug })), + }; + }), +); + +const searchData = [ + ...collectionIndex, + { + title: API_DOC_META.title, + href: `/docs/${API_DOC_SLUG}`, + description: API_DOC_META.description, + headings: apiSearchHeadings(spec), + }, +]; --- @@ -64,7 +129,7 @@ const breadcrumbSegments = pathParts.map((part, i) => { - {/* El mismo juego de iconos que NanBase, para que /docs no cambie de favicon. */} + {/* The same icon set as NanBase, so /docs does not change favicon. */} @@ -83,188 +148,252 @@ const breadcrumbSegments = pathParts.map((part, i) => { + {/* + `font-src` needs `data:`: Vite turns the font subsets that fall under the + inline limit into data URIs (several Archivo and JetBrains Mono subsets), + and with a bare `'self'` the browser blocked them. This predates the port; + it only showed up in the console. + */} {socialTitle} - - -
- - NaN - NaN - - -
- - - - -
- -