Compare commits

..

34 Commits

Author SHA1 Message Date
dependabot[bot] 5718b29649 Bump the npm-dependencies group across 1 directory with 72 updates
Bumps the npm-dependencies group with 72 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@asteasolutions/zod-to-openapi](https://github.com/asteasolutions/zod-to-openapi) | `8.5.0` | `9.1.0` |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1056.0` | `3.1116.0` |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.4.0` | `5.9.1` |
| [@node-rs/argon2](https://github.com/napi-rs/node-rs) | `2.0.2` | `2.1.0` |
| [@radix-ui/react-avatar](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/avatar) | `1.1.11` | `1.2.6` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.3` | `1.3.11` |
| [@radix-ui/react-collapsible](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/collapsible) | `1.1.12` | `1.1.20` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.15` | `1.1.23` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.16` | `2.1.24` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.8` | `2.1.15` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.15` | `1.1.23` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.8` | `1.1.16` |
| [@radix-ui/react-radio-group](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radio-group) | `1.3.8` | `1.4.7` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.10` | `1.2.18` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.2.6` | `2.3.7` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.8` | `1.1.15` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.2.4` | `1.3.3` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.2.6` | `1.3.7` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.13` | `1.1.21` |
| [@radix-ui/react-toast](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/toast) | `1.2.15` | `1.2.23` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.8` | `1.2.16` |
| [@react-email/render](https://github.com/resend/react-email/tree/HEAD/packages/render) | `2.0.8` | `2.1.0` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.3.1` | `13.3.2` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.100.14` | `5.101.4` |
| [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) | `8.21.3` | `9.1.2` |
| [axios](https://github.com/axios/axios) | `1.18.0` | `1.19.0` |
| [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) | `8.5.2` | `8.6.2` |
| [gpt-tokenizer](https://github.com/niieani/gpt-tokenizer) | `3.4.0` | `4.0.0` |
| [helmet](https://github.com/helmetjs/helmet) | `8.2.0` | `8.3.0` |
| [input-otp](https://github.com/guilhermerodz/input-otp/tree/HEAD/packages/input-otp) | `1.4.2` | `1.5.0` |
| [ioredis](https://github.com/redis/ioredis) | `5.11.0` | `6.0.0` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `4.3.1` | `5.3.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.17.0` | `1.33.0` |
| [maxmind](https://github.com/runk/node-maxmind) | `5.0.6` | `5.0.7` |
| [next](https://github.com/vercel/next.js) | `16.3.1` | `16.3.2` |
| [next-intl](https://github.com/amannn/next-intl) | `4.13.0` | `4.13.7` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `9.0.1` | `9.0.5` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.21.0` | `8.23.0` |
| [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) | `8.20.0` | `8.23.1` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.35.6` | `5.50.0` |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.8` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.15` | `19.2.18` |
| [react-day-picker](https://github.com/gpbl/react-day-picker/tree/HEAD/packages/react-day-picker) | `9.14.0` | `10.0.1` |
| [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.6` | `19.2.8` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.3` | `19.2.4` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.76.1` | `7.86.0` |
| [react-icons](https://github.com/react-icons/react-icons) | `5.6.0` | `5.7.0` |
| [recharts](https://github.com/recharts/recharts) | `3.8.1` | `3.10.1` |
| [semver](https://github.com/npm/node-semver) | `7.8.1` | `7.8.5` |
| [@types/semver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver) | `7.7.1` | `7.8.0` |
| [stripe](https://github.com/stripe/stripe-node) | `22.2.0` | `22.5.0` |
| [uuid](https://github.com/uuidjs/uuid) | `14.0.0` | `14.0.2` |
| [ws](https://github.com/websockets/ws) | `8.21.0` | `8.21.3` |
| [yargs](https://github.com/yargs/yargs) | `18.0.0` | `18.1.0` |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `1.69.1` | `2.21.0` |
| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.0` | `4.3.3` |
| [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools) | `5.100.14` | `5.101.4` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.1` | `26.2.0` |
| [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer) | `8.0.0` | `8.0.1` |
| [@types/sshpk](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sshpk) | `1.17.4` | `1.17.5` |
| [esbuild](https://github.com/evanw/esbuild) | `0.28.0` | `0.28.2` |
| [esbuild-node-externals](https://github.com/pradel/esbuild-node-externals) | `1.22.0` | `2.0.0` |
| [eslint](https://github.com/eslint/eslint) | `10.4.0` | `10.9.0` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.2.6` | `16.3.2` |
| [postcss](https://github.com/postcss/postcss) | `8.5.23` | `8.5.26` |
| [prettier](https://github.com/prettier/prettier) | `3.8.3` | `3.9.6` |
| [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `6.5.0` | `6.9.2` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.0` | `4.3.3` |
| [tsc-alias](https://github.com/justkey007/tsc-alias) | `1.8.17` | `1.9.2` |
| [tsx](https://github.com/privatenumber/tsx) | `4.22.3` | `4.23.12` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.3` | `7.0.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.60.0` | `8.67.0` |



Updates `@asteasolutions/zod-to-openapi` from 8.5.0 to 9.1.0
- [Release notes](https://github.com/asteasolutions/zod-to-openapi/releases)
- [Commits](https://github.com/asteasolutions/zod-to-openapi/compare/v8.5.0...v9.1.0)

Updates `@aws-sdk/client-s3` from 3.1056.0 to 3.1116.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1116.0/clients/client-s3)

Updates `@hookform/resolvers` from 5.4.0 to 5.9.1
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v5.4.0...v5.9.1)

Updates `@node-rs/argon2` from 2.0.2 to 2.1.0
- [Release notes](https://github.com/napi-rs/node-rs/releases)
- [Commits](https://github.com/napi-rs/node-rs/compare/@node-rs/argon2@2.0.2...@node-rs/argon2@2.1.0)

Updates `@radix-ui/react-avatar` from 1.1.11 to 1.2.6
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/avatar/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/avatar)

Updates `@radix-ui/react-checkbox` from 1.3.3 to 1.3.11
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/checkbox)

Updates `@radix-ui/react-collapsible` from 1.1.12 to 1.1.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/collapsible/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/collapsible)

Updates `@radix-ui/react-dialog` from 1.1.15 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)

Updates `@radix-ui/react-dropdown-menu` from 2.1.16 to 2.1.24
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu)

Updates `@radix-ui/react-label` from 2.1.8 to 2.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label)

Updates `@radix-ui/react-popover` from 1.1.15 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover)

Updates `@radix-ui/react-progress` from 1.1.8 to 1.1.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/progress/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/progress)

Updates `@radix-ui/react-radio-group` from 1.3.8 to 1.4.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radio-group/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radio-group)

Updates `@radix-ui/react-scroll-area` from 1.2.10 to 1.2.18
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/scroll-area/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/scroll-area)

Updates `@radix-ui/react-select` from 2.2.6 to 2.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select)

Updates `@radix-ui/react-separator` from 1.1.8 to 1.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/separator/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/separator)

Updates `@radix-ui/react-slot` from 1.2.4 to 1.3.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot)

Updates `@radix-ui/react-switch` from 1.2.6 to 1.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch)

Updates `@radix-ui/react-tabs` from 1.1.13 to 1.1.21
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs)

Updates `@radix-ui/react-toast` from 1.2.15 to 1.2.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/toast/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/toast)

Updates `@radix-ui/react-tooltip` from 1.2.8 to 1.2.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip)

Updates `@react-email/render` from 2.0.8 to 2.1.0
- [Release notes](https://github.com/resend/react-email/releases)
- [Changelog](https://github.com/resend/react-email/blob/canary/packages/render/CHANGELOG.md)
- [Commits](https://github.com/resend/react-email/commits/@react-email/render@2.1.0/packages/render)

Updates `@simplewebauthn/server` from 13.3.1 to 13.3.2
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v13.3.2/packages/server)

Updates `@tanstack/react-query` from 5.100.14 to 5.101.4
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.101.4/packages/react-query)

Updates `@tanstack/react-table` from 8.21.3 to 9.1.2
- [Release notes](https://github.com/TanStack/table/releases)
- [Changelog](https://github.com/TanStack/table/blob/main/packages/react-table/CHANGELOG.md)
- [Commits](https://github.com/TanStack/table/commits/@tanstack/react-table@9.1.2/packages/react-table)

Updates `axios` from 1.18.0 to 1.19.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.18.0...v1.19.0)

Updates `express-rate-limit` from 8.5.2 to 8.6.2
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.5.2...v8.6.2)

Updates `gpt-tokenizer` from 3.4.0 to 4.0.0
- [Release notes](https://github.com/niieani/gpt-tokenizer/releases)
- [Commits](https://github.com/niieani/gpt-tokenizer/compare/3.4.0...4.0.0)

Updates `helmet` from 8.2.0 to 8.3.0
- [Changelog](https://github.com/helmetjs/helmet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/helmetjs/helmet/compare/v8.2.0...v8.3.0)

Updates `input-otp` from 1.4.2 to 1.5.0
- [Release notes](https://github.com/guilhermerodz/input-otp/releases)
- [Changelog](https://github.com/guilhermerodz/input-otp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guilhermerodz/input-otp/commits/v1.5.0/packages/input-otp)

Updates `ioredis` from 5.11.0 to 6.0.0
- [Release notes](https://github.com/redis/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/redis/ioredis/compare/v5.11.0...v6.0.0)

Updates `js-yaml` from 4.3.1 to 5.3.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.3.1...5.3.0)

Updates `lucide-react` from 1.17.0 to 1.33.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.33.0/packages/lucide-react)

Updates `maxmind` from 5.0.6 to 5.0.7
- [Release notes](https://github.com/runk/node-maxmind/releases)
- [Commits](https://github.com/runk/node-maxmind/compare/v5.0.6...v5.0.7)

Updates `next` from 16.3.1 to 16.3.2
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.1...v16.3.2)

Updates `next-intl` from 4.13.0 to 4.13.7
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.13.0...v4.13.7)

Updates `nodemailer` from 9.0.1 to 9.0.5
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v9.0.1...v9.0.5)

Updates `pg` from 8.21.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)

Updates `@types/pg` from 8.20.0 to 8.23.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

Updates `posthog-node` from 5.35.6 to 5.50.0
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.50.0/packages/node)

Updates `react` from 19.2.6 to 19.2.8
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.2.8/packages/react)

Updates `@types/react` from 19.2.15 to 19.2.18
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-day-picker` from 9.14.0 to 10.0.1
- [Release notes](https://github.com/gpbl/react-day-picker/releases)
- [Changelog](https://github.com/gpbl/react-day-picker/blob/main/packages/react-day-picker/CHANGELOG.md)
- [Commits](https://github.com/gpbl/react-day-picker/commits/v10.0.1/packages/react-day-picker)

Updates `react-dom` from 19.2.6 to 19.2.8
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.2.8/packages/react-dom)

Updates `@types/react-dom` from 19.2.3 to 19.2.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `react-hook-form` from 7.76.1 to 7.86.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.76.1...v7.86.0)

Updates `react-icons` from 5.6.0 to 5.7.0
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.6.0...v5.7.0)

Updates `recharts` from 3.8.1 to 3.10.1
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v3.8.1...v3.10.1)

Updates `semver` from 7.8.1 to 7.8.5
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.8.1...v7.8.5)

Updates `@types/semver` from 7.7.1 to 7.8.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/semver)

Updates `stripe` from 22.2.0 to 22.5.0
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v22.2.0...v22.5.0)

Updates `uuid` from 14.0.0 to 14.0.2
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v14.0.0...v14.0.2)

Updates `ws` from 8.21.0 to 8.21.3
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.21.0...8.21.3)

Updates `yargs` from 18.0.0 to 18.1.0
- [Release notes](https://github.com/yargs/yargs/releases)
- [Changelog](https://github.com/yargs/yargs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/yargs/yargs/compare/v18.0.0...v18.1.0)

Updates `@dotenvx/dotenvx` from 1.69.1 to 2.21.0
- [Release notes](https://github.com/dotenvx/dotenvx/releases)
- [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotenvx/dotenvx/compare/v1.69.1...v2.21.0)

Updates `@tailwindcss/postcss` from 4.3.0 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-postcss)

Updates `@tanstack/react-query-devtools` from 5.100.14 to 5.101.4
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query-devtools@5.101.4/packages/react-query-devtools)

Updates `@types/node` from 25.9.1 to 26.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/nodemailer` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/nodemailer)

Updates `@types/pg` from 8.20.0 to 8.23.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

Updates `@types/react` from 19.2.15 to 19.2.18
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.2.3 to 19.2.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@types/semver` from 7.7.1 to 7.8.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/semver)

Updates `@types/sshpk` from 1.17.4 to 1.17.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sshpk)

Updates `esbuild` from 0.28.0 to 0.28.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.28.0...v0.28.2)

Updates `esbuild-node-externals` from 1.22.0 to 2.0.0
- [Release notes](https://github.com/pradel/esbuild-node-externals/releases)
- [Commits](https://github.com/pradel/esbuild-node-externals/compare/esbuild-node-externals-v1.22.0...esbuild-node-externals-v2.0.0)

Updates `eslint` from 10.4.0 to 10.9.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.4.0...v10.9.0)

Updates `eslint-config-next` from 16.2.6 to 16.3.2
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.2/packages/eslint-config-next)

Updates `postcss` from 8.5.23 to 8.5.26
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.23...8.5.26)

Updates `prettier` from 3.8.3 to 3.9.6
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.3...3.9.6)

Updates `react-email` from 6.5.0 to 6.9.2
- [Release notes](https://github.com/resend/react-email/releases)
- [Changelog](https://github.com/resend/react-email/blob/canary/packages/react-email/CHANGELOG.md)
- [Commits](https://github.com/resend/react-email/commits/react-email@6.9.2/packages/react-email)

Updates `tailwindcss` from 4.3.0 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss)

Updates `tsc-alias` from 1.8.17 to 1.9.2
- [Release notes](https://github.com/justkey007/tsc-alias/releases)
- [Commits](https://github.com/justkey007/tsc-alias/compare/v1.8.17...v1.9.2)

Updates `tsx` from 4.22.3 to 4.23.12
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.22.3...v4.23.12)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.3...v7.0.2)

Updates `typescript-eslint` from 8.60.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@asteasolutions/zod-to-openapi"
  dependency-version: 9.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1116.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@hookform/resolvers"
  dependency-version: 5.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@node-rs/argon2"
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-avatar"
  dependency-version: 1.2.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-checkbox"
  dependency-version: 1.3.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-collapsible"
  dependency-version: 1.1.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-dialog"
  dependency-version: 1.1.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-version: 2.1.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-label"
  dependency-version: 2.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-popover"
  dependency-version: 1.1.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-progress"
  dependency-version: 1.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-radio-group"
  dependency-version: 1.4.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-version: 1.2.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.3.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-separator"
  dependency-version: 1.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.3.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-switch"
  dependency-version: 1.3.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-tabs"
  dependency-version: 1.1.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-toast"
  dependency-version: 1.2.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@radix-ui/react-tooltip"
  dependency-version: 1.2.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@react-email/render"
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/server"
  dependency-version: 13.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.101.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-table"
  dependency-version: 9.1.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: axios
  dependency-version: 1.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: express-rate-limit
  dependency-version: 8.6.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: gpt-tokenizer
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: helmet
  dependency-version: 8.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: input-otp
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: ioredis
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: js-yaml
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: lucide-react
  dependency-version: 1.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: maxmind
  dependency-version: 5.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next
  dependency-version: 16.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: next-intl
  dependency-version: 4.13.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: nodemailer
  dependency-version: 9.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: pg
  dependency-version: 8.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/pg"
  dependency-version: 8.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: posthog-node
  dependency-version: 5.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react
  dependency-version: 19.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: react-day-picker
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: react-dom
  dependency-version: 19.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: react-hook-form
  dependency-version: 7.86.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-icons
  dependency-version: 5.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: recharts
  dependency-version: 3.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: semver
  dependency-version: 7.8.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/semver"
  dependency-version: 7.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: stripe
  dependency-version: 22.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: uuid
  dependency-version: 14.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: ws
  dependency-version: 8.21.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: yargs
  dependency-version: 18.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 2.21.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.101.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/pg"
  dependency-version: 8.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@types/semver"
  dependency-version: 7.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/sshpk"
  dependency-version: 1.17.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: esbuild
  dependency-version: 0.28.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: esbuild-node-externals
  dependency-version: 2.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: eslint
  dependency-version: 10.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint-config-next
  dependency-version: 16.3.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: postcss
  dependency-version: 8.5.26
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: prettier
  dependency-version: 3.9.6
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: react-email
  dependency-version: 6.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: tailwindcss
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: tsc-alias
  dependency-version: 1.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: tsx
  dependency-version: 4.23.12
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.67.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 01:45:54 +00:00
Owen Schwartz 7319bf84f7 Merge pull request #3481 from ThanatosDi/feat/zh-tw-for-1.21.0
feat: translate zh-tw for 1.21.0
2026-08-24 11:04:08 -04:00
Owen Schwartz 9ec9908ed7 Merge pull request #3509 from shubhamsinnh/codex/fix-zh-tw-language-detection
Fix regional locale detection
2026-08-24 11:03:09 -04:00
Milo Schwartz fd0a0818c1 Merge pull request #3617 from fosrl/dev
Dev
2026-08-19 16:41:48 -04:00
Milo Schwartz 929acc5b1c Merge pull request #3614 from fosrl/dev
Dev
2026-08-19 12:05:12 -04:00
Milo Schwartz 71348f45b2 Merge pull request #3611 from fosrl/dev
1.22.0
2026-08-19 11:26:08 -04:00
Milo Schwartz 21eb4d2876 Merge pull request #3598 from shleeable/patch-12
Fix: ensuring only internal users can request password resets.
2026-08-19 10:35:52 -04:00
Owen Schwartz 18270381c1 Merge pull request #3607 from fosrl/dependabot/npm_and_yarn/multi-ff8097116b
Bump sharp, next and @react-email/ui
2026-08-18 17:21:25 -04:00
dependabot[bot] 8e938a2723 Bump sharp, next and @react-email/ui
Bumps [sharp](https://github.com/lovell/sharp) to 0.35.3 and updates ancestor dependencies [sharp](https://github.com/lovell/sharp), [next](https://github.com/vercel/next.js) and [@react-email/ui](https://github.com/resend/react-email/tree/HEAD/packages/ui). These dependencies need to be updated together.


Updates `sharp` from 0.34.5 to 0.35.3
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.34.5...v0.35.3)

Updates `next` from 16.2.11 to 16.3.1
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.2.11...v16.3.1)

Updates `@react-email/ui` from 6.5.0 to 6.9.2
- [Release notes](https://github.com/resend/react-email/releases)
- [Changelog](https://github.com/resend/react-email/blob/canary/packages/ui/CHANGELOG.md)
- [Commits](https://github.com/resend/react-email/commits/@react-email/ui@6.9.2/packages/ui)

---
updated-dependencies:
- dependency-name: sharp
  dependency-version: 0.35.3
  dependency-type: indirect
- dependency-name: next
  dependency-version: 16.3.1
  dependency-type: direct:production
- dependency-name: "@react-email/ui"
  dependency-version: 6.9.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-18 16:26:28 +00:00
Owen Schwartz 197f8f7ba5 Merge pull request #3503 from fosrl/dependabot/npm_and_yarn/engine.io-6.6.9
Bump engine.io from 6.6.7 to 6.6.9
2026-08-18 12:25:27 -04:00
Owen Schwartz 10b528642d Merge pull request #3517 from fosrl/dependabot/github_actions/github-actions-dependencies-7eb9e48ea9
Bump the github-actions-dependencies group across 1 directory with 2 updates
2026-08-18 12:24:54 -04:00
Owen Schwartz 60d6fff085 Merge pull request #3525 from fosrl/dependabot/npm_and_yarn/ip-address-10.4.0
Bump ip-address from 10.2.0 to 10.4.0
2026-08-18 12:24:30 -04:00
Owen Schwartz c664b3da91 Merge pull request #3526 from fosrl/dependabot/npm_and_yarn/multi-5e81c1b34f
Bump brace-expansion
2026-08-18 12:24:17 -04:00
Owen Schwartz 492282e758 Merge pull request #3530 from fosrl/dependabot/npm_and_yarn/socket.io-parser-4.2.7
Bump socket.io-parser from 4.2.6 to 4.2.7
2026-08-18 12:24:03 -04:00
Owen Schwartz 47f4aefc25 Merge pull request #3531 from fosrl/dependabot/npm_and_yarn/fast-uri-3.1.5
Bump fast-uri from 3.1.4 to 3.1.5
2026-08-18 12:23:43 -04:00
Owen Schwartz 7c0ff9ede7 Merge pull request #3569 from fosrl/dependabot/npm_and_yarn/js-yaml-4.3.1
Bump js-yaml from 4.3.0 to 4.3.1
2026-08-18 12:23:32 -04:00
Owen Schwartz 44e81ea979 Merge pull request #3570 from fosrl/dependabot/npm_and_yarn/nanoid-3.3.18
Bump nanoid from 3.3.12 to 3.3.18
2026-08-18 12:23:17 -04:00
Owen Schwartz 56dc10330a Merge pull request #3572 from fosrl/dependabot/npm_and_yarn/postcss-8.5.23
Bump postcss from 8.5.15 to 8.5.23
2026-08-18 12:23:03 -04:00
dependabot[bot] eb8ad6a181 Bump the github-actions-dependencies group across 1 directory with 2 updates
Bumps the github-actions-dependencies group with 2 updates in the / directory: [docker/login-action](https://github.com/docker/login-action) and [actions/stale](https://github.com/actions/stale).


Updates `docker/login-action` from 4.5.1 to 4.6.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/abd2ef45e78c5afb21d64d4ca52ee8550d9572c7...dbcb813823bdd20940b903addbd779551569679f)

Updates `actions/stale` from 10.4.0 to 11.0.0
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/1e223db275d687790206a7acac4d1a11bd6fe629...4391f3da665fdf50b6810c1a66712fb9ba21aa93)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: 11.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions-dependencies
- dependency-name: docker/login-action
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 01:34:14 +00:00
Shlee 4edd2e4d32 Update requestPasswordReset.ts 2026-08-16 22:21:37 +09:30
Shlee 813c3abe54 Update requestPasswordReset.ts 2026-08-16 22:03:00 +09:30
Owen Schwartz 048e4fc73c Merge pull request #3574 from fosrl/dev
1.21.1-s.5
2026-08-12 16:34:31 -04:00
dependabot[bot] 923371e5b4 Bump postcss from 8.5.15 to 8.5.23
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.15 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-12 02:57:16 +00:00
dependabot[bot] 81430ba3d3 Bump nanoid from 3.3.12 to 3.3.18
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.12 to 3.3.18.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/3.3.18/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.12...3.3.18)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-version: 3.3.18
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 23:54:36 +00:00
dependabot[bot] e4aaadc9f9 Bump js-yaml from 4.3.0 to 4.3.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.0 to 4.3.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.3.0...4.3.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 20:19:18 +00:00
Owen Schwartz d04740fede Merge pull request #3537 from fosrl/dev
1.21.1-s.4
2026-08-06 14:07:42 -04:00
dependabot[bot] 4677a0d501 Bump fast-uri from 3.1.4 to 3.1.5
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.4...v3.1.5)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-05 18:18:41 +00:00
dependabot[bot] b4463f0e1a Bump socket.io-parser from 4.2.6 to 4.2.7
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.6 to 4.2.7.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.6...socket.io-parser@4.2.7)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-version: 4.2.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-05 06:42:16 +00:00
Owen Schwartz b7c0669c38 Merge pull request #3528 from fosrl/dev
1.21.1-s.3
2026-08-04 17:46:49 -04:00
dependabot[bot] 152d2fb1d6 Bump brace-expansion
Bumps  and [brace-expansion](https://github.com/juliangruber/brace-expansion). These dependencies needed to be updated together.

Updates `brace-expansion` from 5.0.6 to 5.0.9
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.9)

Updates `brace-expansion` from 1.1.14 to 1.1.18
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.9)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.9
  dependency-type: indirect
- dependency-name: brace-expansion
  dependency-version: 1.1.18
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 05:33:39 +00:00
dependabot[bot] d374b4f66e Bump ip-address from 10.2.0 to 10.4.0
Bumps [ip-address](https://github.com/beaugunderson/ip-address) from 10.2.0 to 10.4.0.
- [Release notes](https://github.com/beaugunderson/ip-address/releases)
- [Commits](https://github.com/beaugunderson/ip-address/compare/v10.2.0...v10.4.0)

---
updated-dependencies:
- dependency-name: ip-address
  dependency-version: 10.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 01:45:48 +00:00
Shubham Singh cb3f0b49a8 Fix regional locale detection 2026-07-30 14:29:25 +05:30
dependabot[bot] 192542629f Bump engine.io from 6.6.7 to 6.6.9
Bumps [engine.io](https://github.com/socketio/socket.io) from 6.6.7 to 6.6.9.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.7...engine.io@6.6.9)

---
updated-dependencies:
- dependency-name: engine.io
  dependency-version: 6.6.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 21:59:23 +00:00
ThanatosDi 84d5a4b86c feat: translate zh-tw for 1.21.0 2026-07-23 14:40:42 +08:00
107 changed files with 5576 additions and 6578 deletions
-4
View File
@@ -4,10 +4,6 @@ perpetual license to use, modify, and redistribute these contributions under any
choose, including both the AGPLv3 and the Fossorial Commercial license terms. I choose, including both the AGPLv3 and the Fossorial Commercial license terms. I
represent that I have the right to grant this license for all contributed content. represent that I have the right to grant this license for all contributed content.
## AI Disclosure
> Please disclose how AI was used in this pull request. The use of AI does not preclude this from being merged but is an important factor in how we review your request.
## Description ## Description
+4 -4
View File
@@ -77,7 +77,7 @@ jobs:
fi fi
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with: with:
registry: docker.io registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }} username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -149,7 +149,7 @@ jobs:
fi fi
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with: with:
registry: docker.io registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }} username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -204,7 +204,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with: with:
registry: docker.io registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }} username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -407,7 +407,7 @@ jobs:
shell: bash shell: bash
- name: Login to GitHub Container Registry (for cosign) - name: Login to GitHub Container Registry (for cosign)
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0 - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with: with:
days-before-stale: 14 days-before-stale: 14
days-before-close: 14 days-before-close: 14
+2 -67
View File
@@ -7,8 +7,6 @@ inference resource has more than one AI provider.
- Route → capability binding: `server/routers/aiGateway/createAiGatewayRouter.ts` - Route → capability binding: `server/routers/aiGateway/createAiGatewayRouter.ts`
- Request pipeline: `server/routers/aiGateway/pipeline.ts` (`selectProvider`) - Request pipeline: `server/routers/aiGateway/pipeline.ts` (`selectProvider`)
- Model discovery: `server/routers/aiGateway/v1Models.ts` and
`server/lib/aiModelDiscovery.ts`
- Tie-break scoring: `server/lib/aiProviderSelection.ts` - Tie-break scoring: `server/lib/aiProviderSelection.ts`
- Allow/block matching: `server/lib/aiModelKeyMatch.ts` - Allow/block matching: `server/lib/aiModelKeyMatch.ts`
- Model catalog: `server/lib/aiModelCatalog.ts` - Model catalog: `server/lib/aiModelCatalog.ts`
@@ -41,7 +39,6 @@ The incoming path selects a capability before any provider logic runs.
| `POST /v1/chat/completions` | `openai_chat` | | `POST /v1/chat/completions` | `openai_chat` |
| `POST /v1/responses` | `openai_responses` | | `POST /v1/responses` | `openai_responses` |
| `POST /v1/messages` | `anthropic_messages` | | `POST /v1/messages` | `anthropic_messages` |
| `GET /v1/models`, `GET /v1/models/{id}` | `v1_models` |
| Gemini / Vertex / Bedrock routes | their respective capability ids | | Gemini / Vertex / Bedrock routes | their respective capability ids |
Only attached providers that advertise that capability stay in the candidate Only attached providers that advertise that capability stay in the candidate
@@ -50,10 +47,10 @@ set. Default capabilities do not overlap for native OpenAI vs Anthropic:
| Provider type | Default capabilities | | Provider type | Default capabilities |
|---------------|----------------------| |---------------|----------------------|
| `openai` | `openai_chat`, `openai_responses` | | `openai` | `openai_chat`, `openai_responses` |
| `anthropic` | `anthropic_messages`, `v1_models` | | `anthropic` | `anthropic_messages` |
| `openRouter` | `openai_chat` | | `openRouter` | `openai_chat` |
| `vercelAiGateway` | `openai_chat`, `openai_responses` | | `vercelAiGateway` | `openai_chat`, `openai_responses` |
| `microsoftFoundry` | `openai_chat`, `openai_responses`, `anthropic_messages`, `v1_models` | | `microsoftFoundry` | `openai_chat`, `openai_responses`, `anthropic_messages` |
| `custom` | whatever was configured | | `custom` | whatever was configured |
### 2. Allow / Block Lists ### 2. Allow / Block Lists
@@ -131,68 +128,6 @@ Model "<id>" is ambiguous across multiple AI providers on this resource
Typical remaining ties: two OpenAI-type providers both with `*`, or two Typical remaining ties: two OpenAI-type providers both with `*`, or two
customs advertising the same capability for an unknown model. customs advertising the same capability for an unknown model.
## Model Discovery Is Not Selection
`GET /v1/models` and `GET /v1/models/{id}` (`v1_models`) skip steps 3-6
entirely. There is no requested model to disambiguate on, so the gateway does
not pick one provider - it returns the **union** of what every attached
provider advertising `v1_models` would accept, deduplicated by model id
(lowest `providerId` wins a collision).
Discovery is answered from the gateway's own view of the allow/block lists,
never proxied upstream. Providers that expose no `/v1/models` endpoint of their
own still get a working listing, and a model an allow/block list forbids is
never advertised.
Each provider's candidate ids come from two places:
| Source | Contributes |
|--------|-------------|
| Exact (non-wildcard) allow entries | the model key itself |
| The model catalog for the provider's type | every catalog id matching an allow pattern |
Both sources are then filtered through the same
`isAllowedByLists(id, allows, blocks)` check step 2 applies, so a block pattern
hides a model from discovery exactly as it would reject it at request time.
The catalog source is what makes a wildcard allow such as `claude-*`
enumerable. Provider types with no catalog mapping (`openRouter`,
`vercelAiGateway`, `custom`) have nothing to expand against, so a wildcard
allow on those types lists nothing - **add exact allow entries to make their
models discoverable.**
### Where each field comes from
Token limits and capability flags can't be derived from an allow/block list.
They come from the model catalog (`server/lib/aiModelCatalog.ts`), which the
Fossorial API builds from LiteLLM:
| Field | Source |
|-------|--------|
| `max_input_tokens` | catalog `limits.input` |
| `max_tokens` | catalog `limits.output` |
| `capabilities` | catalog flags, mapped to the Models API shape by `capabilitiesFromCatalog` |
| `display_name` | the configured model row's name, else the model id |
| `created_at` | the configured model row's timestamp, else the epoch |
A model the catalog doesn't know (an exact allow entry for a fine-tune, say)
reports `null` for all three metadata fields. The Models API declares them
nullable, so that is a valid answer rather than a broken one.
The catalog's flags are coarser than the Models API describes: it carries a
single `reasoning` flag with no way to distinguish adaptive from
`budget_tokens`-style thinking, and nothing at all for batch, citations, code
execution, PDF input, or context management. Anything it reports as unknown
(`null`) is surfaced as unsupported rather than invented, so `capabilities`
understates rather than overstates what a model can do.
The gateway does **not** query the provider's own `/v1/models`. Discovery is
answered entirely from local state.
Results are ordered newest-first with the id as tie-break, and paginated with
Anthropic's `limit` / `after_id` / `before_id` semantics (default 20, max
1000).
## Examples ## Examples
Assume each provider below is attached and enabled on the same inference Assume each provider below is attached and enabled on the same inference
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Инструментът за кодиране на Anthropic за терминала.", "aiClientConfigDescriptionClaude": "Инструментът за кодиране на Anthropic за терминала.",
"aiClientConfigDescriptionCodex": "Инструментът за кодиране на OpenAI за терминала.", "aiClientConfigDescriptionCodex": "Инструментът за кодиране на OpenAI за терминала.",
"aiClientConfigDescriptionOpencode": "Отворен кодиращ агент за терминал.", "aiClientConfigDescriptionOpencode": "Отворен кодиращ агент за терминал.",
"aiClientConfigDescriptionGemini": "Агентски инструмент на Google за кодиране на терминал.", "aiClientConfigDescriptionCursor": "AI редактор на код, базиран на VS Code.",
"aiClientConfigSetup": "Настройка", "aiClientConfigSetup": "Настройка",
"aiClientConfigTabCli": "Автоматичен (CLI)", "aiClientConfigTabCli": "Автоматичен (CLI)",
"aiClientConfigTabManual": "Ръчна конфигурация", "aiClientConfigTabManual": "Ръчна конфигурация",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Маршрутиране чрез цели на вашите сайтове", "aiProviderRoutingModeTargetDescription": "Маршрутиране чрез цели на вашите сайтове",
"aiProviderRoutingModeTargetNote": "След създаването на този доставчик, конфигурирайте целите на сайта в раздела Настройки на мрежата.", "aiProviderRoutingModeTargetNote": "След създаването на този доставчик, конфигурирайте целите на сайта в раздела Настройки на мрежата.",
"aiProviderTargetNoOne": "Този доставчик няма цели. Добавете цел за маршрутиране на заявки чрез вашите сайтове.", "aiProviderTargetNoOne": "Този доставчик няма цели. Добавете цел за маршрутиране на заявки чрез вашите сайтове.",
"aiProviderRemoteNodeTargetsWarning": "Уебсайтовете, свързани с отдалечени възли, са недостъпни за пренасочване към AI Gateway доставчици.",
"aiProviderSkipTlsVerification": "Пропуснете проверката на TLS", "aiProviderSkipTlsVerification": "Пропуснете проверката на TLS",
"aiProviderSkipTlsVerificationDescription": "Деактивирайте проверката на TLS сертификат за възходящото свързване", "aiProviderSkipTlsVerificationDescription": "Деактивирайте проверката на TLS сертификат за възходящото свързване",
"aiProviderBudget": "Бюджет", "aiProviderBudget": "Бюджет",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Поддържа /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Поддържа /v1/responses",
"aiCapabilityAnthropicMessages": "Anthropic Съобщения", "aiCapabilityAnthropicMessages": "Anthropic Съобщения",
"aiCapabilityAnthropicMessagesDescription": "Поддържа /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Поддържа /v1/messages",
"aiCapabilityV1Models": "Списък на модели",
"aiCapabilityV1ModelsDescription": "Поддържа /v1/models откриване на модели",
"aiCapabilityGeminiGenerateContent": "Gemini Генериране на Съдържание", "aiCapabilityGeminiGenerateContent": "Gemini Генериране на Съдържание",
"aiCapabilityGeminiGenerateContentDescription": "Поддържа директния Gemini API", "aiCapabilityGeminiGenerateContentDescription": "Поддържа директния Gemini API",
"aiCapabilityBedrockModelInvoke": "Бедрок Модел Активирай", "aiCapabilityBedrockModelInvoke": "Бедрок Модел Активирай",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Дневници на действията", "sidebarLogsAction": "Дневници на действията",
"logRetention": "Задържане на логове", "logRetention": "Задържане на логове",
"logRetentionDescription": "Управлявайте времето за задържане на различни видове логове за тази организация или ги деактивирайте", "logRetentionDescription": "Управлявайте времето за задържане на различни видове логове за тази организация или ги деактивирайте",
"logRetentionDisabledWarningTitle": "Деактивирано съхранение на дневници",
"logRetentionDisabledWarningDescription": "{logType} не се съхраняват за тази организация, така че новите дейности няма да се показват тук. Активирайте съхранението в настройките за сигурност, за да започнете събирането на тези дневници.",
"logRetentionDisabledWarningButton": "Отидете на настройки за сигурност",
"requestLogsDescription": "Прегледайте подробни логове на заявки за ресурси в тази организация", "requestLogsDescription": "Прегледайте подробни логове на заявки за ресурси в тази организация",
"aiSessionLogs": "Журнали на AI Портал Сесиите", "aiSessionLogs": "Журнали на AI Портал Сесиите",
"aiSessionLogsDescription": "Прегледайте подканянета и транскрипции на отговори за запитванията към AI портал в тази организация", "aiSessionLogsDescription": "Прегледайте подканянета и транскрипции на отговори за запитванията към AI портал в тази организация",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Събития на свързване и прекъсване на сайта и тунела, включително свръзки и прекъсвания.", "httpDestConnectionLogsDescription": "Събития на свързване и прекъсване на сайта и тунела, включително свръзки и прекъсвания.",
"httpDestRequestLogsTitle": "Логове за HTTP заявки", "httpDestRequestLogsTitle": "Логове за HTTP заявки",
"httpDestRequestLogsDescription": "Регистри за HTTP заявките към проксирани ресурси, включително метод, път и код на отговор.", "httpDestRequestLogsDescription": "Регистри за HTTP заявките към проксирани ресурси, включително метод, път и код на отговор.",
"httpDestAISessionLogsTitle": "Дневници за AI сесии",
"httpDestAISessionLogsDescription": "AI заявки до шлюза и отговори на сесии, включително подканвания, отговори на модели и използване на жетони.",
"httpDestSaveChanges": "Запази промените", "httpDestSaveChanges": "Запази промените",
"httpDestCreateDestination": "Създаване на дестинация", "httpDestCreateDestination": "Създаване на дестинация",
"httpDestUpdatedSuccess": "Дестинацията беше актуализирана успешно", "httpDestUpdatedSuccess": "Дестинацията беше актуализирана успешно",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Antropický agentický kódovací nástroj pro terminál.", "aiClientConfigDescriptionClaude": "Antropický agentický kódovací nástroj pro terminál.",
"aiClientConfigDescriptionCodex": "Agentický kódovací nástroj OpenAI pro terminál.", "aiClientConfigDescriptionCodex": "Agentický kódovací nástroj OpenAI pro terminál.",
"aiClientConfigDescriptionOpencode": "Open source terminální kódovací agent.", "aiClientConfigDescriptionOpencode": "Open source terminální kódovací agent.",
"aiClientConfigDescriptionGemini": "Agentický nástroj Google pro kódování v terminálu.", "aiClientConfigDescriptionCursor": "AI editor kódu postavený na VS Code.",
"aiClientConfigSetup": "Nastavení", "aiClientConfigSetup": "Nastavení",
"aiClientConfigTabCli": "Automatické (CLI)", "aiClientConfigTabCli": "Automatické (CLI)",
"aiClientConfigTabManual": "Ruční konfigurace", "aiClientConfigTabManual": "Ruční konfigurace",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Směrujte přes cíle na svých stránkách", "aiProviderRoutingModeTargetDescription": "Směrujte přes cíle na svých stránkách",
"aiProviderRoutingModeTargetNote": "Po vytvoření tohoto poskytovatele, nakonfigurujte cíle stránek na záložce Nastavení sítě.", "aiProviderRoutingModeTargetNote": "Po vytvoření tohoto poskytovatele, nakonfigurujte cíle stránek na záložce Nastavení sítě.",
"aiProviderTargetNoOne": "Tento poskytovatel nemá žádné cíle. Přidejte cíl pro směrování požadavků přes vaše stránky.", "aiProviderTargetNoOne": "Tento poskytovatel nemá žádné cíle. Přidejte cíl pro směrování požadavků přes vaše stránky.",
"aiProviderRemoteNodeTargetsWarning": "Stránky připojené k vzdáleným uzlům nejsou dostupné pro přesměrování na poskytovatele AI Gateway.",
"aiProviderSkipTlsVerification": "Přeskočit ověření TLS", "aiProviderSkipTlsVerification": "Přeskočit ověření TLS",
"aiProviderSkipTlsVerificationDescription": "Zakázat ověření certifikátu TLS pro upstream připojení", "aiProviderSkipTlsVerificationDescription": "Zakázat ověření certifikátu TLS pro upstream připojení",
"aiProviderBudget": "Rozpočet", "aiProviderBudget": "Rozpočet",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Podporuje /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Podporuje /v1/responses",
"aiCapabilityAnthropicMessages": "Zprávy Anthropic", "aiCapabilityAnthropicMessages": "Zprávy Anthropic",
"aiCapabilityAnthropicMessagesDescription": "Podporuje /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Podporuje /v1/messages",
"aiCapabilityV1Models": "Seznam modelů",
"aiCapabilityV1ModelsDescription": "Podporuje objevování modelů /v1/models",
"aiCapabilityGeminiGenerateContent": "Generování obsahu Gemini", "aiCapabilityGeminiGenerateContent": "Generování obsahu Gemini",
"aiCapabilityGeminiGenerateContentDescription": "Podporuje přímé API Gemini", "aiCapabilityGeminiGenerateContentDescription": "Podporuje přímé API Gemini",
"aiCapabilityBedrockModelInvoke": "Vyvolání modelu Bedrock", "aiCapabilityBedrockModelInvoke": "Vyvolání modelu Bedrock",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Záznamy akcí", "sidebarLogsAction": "Záznamy akcí",
"logRetention": "Zaznamenávání záznamu", "logRetention": "Zaznamenávání záznamu",
"logRetentionDescription": "Spravovat, jak dlouho jsou různé typy logů uloženy pro tuto organizaci nebo je zakázat", "logRetentionDescription": "Spravovat, jak dlouho jsou různé typy logů uloženy pro tuto organizaci nebo je zakázat",
"logRetentionDisabledWarningTitle": "Zakázáno uchování logů",
"logRetentionDisabledWarningDescription": "{logType} nejsou uchovávány pro tuto organizaci, takže nová aktivita se zde neprojeví. Aktivujte uchovávání v nastavení zabezpečení pro zahájení sběru těchto logů.",
"logRetentionDisabledWarningButton": "Přejít na nastavení zabezpečení",
"requestLogsDescription": "Zobrazit podrobné protokoly požadavků pro zdroje v této organizaci", "requestLogsDescription": "Zobrazit podrobné protokoly požadavků pro zdroje v této organizaci",
"aiSessionLogs": "Protokoly AI Gateway Session", "aiSessionLogs": "Protokoly AI Gateway Session",
"aiSessionLogsDescription": "Zobrazit uložené výzvy a odpovědi na žádosti AI brány v této organizaci", "aiSessionLogsDescription": "Zobrazit uložené výzvy a odpovědi na žádosti AI brány v této organizaci",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Události týkající se připojení lokality a tunelu, včetně připojení a odpojení.", "httpDestConnectionLogsDescription": "Události týkající se připojení lokality a tunelu, včetně připojení a odpojení.",
"httpDestRequestLogsTitle": "Záznamy HTTP požadavků", "httpDestRequestLogsTitle": "Záznamy HTTP požadavků",
"httpDestRequestLogsDescription": "HTTP záznamy požadavků pro proxy zdroje, včetně metod, cesty a kódu odpovědi.", "httpDestRequestLogsDescription": "HTTP záznamy požadavků pro proxy zdroje, včetně metod, cesty a kódu odpovědi.",
"httpDestAISessionLogsTitle": "Logy AI sezení",
"httpDestAISessionLogsDescription": "Relace požadavků a odpovědí AI gateway, včetně podnětů, odpovědí modelů a využití tokenů.",
"httpDestSaveChanges": "Uložit změny", "httpDestSaveChanges": "Uložit změny",
"httpDestCreateDestination": "Vytvořit cíl", "httpDestCreateDestination": "Vytvořit cíl",
"httpDestUpdatedSuccess": "Cíl byl úspěšně aktualizován", "httpDestUpdatedSuccess": "Cíl byl úspěšně aktualizován",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Anthropics agentikodningsværktøj til terminalen.", "aiClientConfigDescriptionClaude": "Anthropics agentikodningsværktøj til terminalen.",
"aiClientConfigDescriptionCodex": "OpenAIs agentikodningsværktøj til terminalen.", "aiClientConfigDescriptionCodex": "OpenAIs agentikodningsværktøj til terminalen.",
"aiClientConfigDescriptionOpencode": "Open source terminal kodningsagent.", "aiClientConfigDescriptionOpencode": "Open source terminal kodningsagent.",
"aiClientConfigDescriptionGemini": "Googles agentiske kodningsværktøj til terminalen.", "aiClientConfigDescriptionCursor": "AI-kodeeditor bygget på VS Code.",
"aiClientConfigSetup": "Opsætning", "aiClientConfigSetup": "Opsætning",
"aiClientConfigTabCli": "Automatisk (CLI)", "aiClientConfigTabCli": "Automatisk (CLI)",
"aiClientConfigTabManual": "Manuel Konfiguration", "aiClientConfigTabManual": "Manuel Konfiguration",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Rute gennem mål på dine steder", "aiProviderRoutingModeTargetDescription": "Rute gennem mål på dine steder",
"aiProviderRoutingModeTargetNote": "Efter oprettelse af denne udbyder, konfigurer mål på Netværksindstillinger fanen.", "aiProviderRoutingModeTargetNote": "Efter oprettelse af denne udbyder, konfigurer mål på Netværksindstillinger fanen.",
"aiProviderTargetNoOne": "Denne udbyder har ingen mål. Tilføj et mål for at rute forespørgsler gennem dine steder.", "aiProviderTargetNoOne": "Denne udbyder har ingen mål. Tilføj et mål for at rute forespørgsler gennem dine steder.",
"aiProviderRemoteNodeTargetsWarning": "Websteder, der er forbundet til eksterne noder, kan ikke tilgås for at blive dirigeret til via AI Gateway-udbydere.",
"aiProviderSkipTlsVerification": "Spring TLS-verifikation over", "aiProviderSkipTlsVerification": "Spring TLS-verifikation over",
"aiProviderSkipTlsVerificationDescription": "Deaktiver TLS-certifikat verifikation for opstrømsforbindelsen", "aiProviderSkipTlsVerificationDescription": "Deaktiver TLS-certifikat verifikation for opstrømsforbindelsen",
"aiProviderBudget": "Budget", "aiProviderBudget": "Budget",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Understøtter /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Understøtter /v1/responses",
"aiCapabilityAnthropicMessages": "Anthropic Beskeder", "aiCapabilityAnthropicMessages": "Anthropic Beskeder",
"aiCapabilityAnthropicMessagesDescription": "Understøtter /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Understøtter /v1/messages",
"aiCapabilityV1Models": "Model Liste",
"aiCapabilityV1ModelsDescription": "Understøtter opdagelse af /v1/models modeller",
"aiCapabilityGeminiGenerateContent": "Gemini Generer Indhold", "aiCapabilityGeminiGenerateContent": "Gemini Generer Indhold",
"aiCapabilityGeminiGenerateContentDescription": "Understøtter den direkte Gemini API", "aiCapabilityGeminiGenerateContentDescription": "Understøtter den direkte Gemini API",
"aiCapabilityBedrockModelInvoke": "Bedrock Modeller Invoker", "aiCapabilityBedrockModelInvoke": "Bedrock Modeller Invoker",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Handlingsloger", "sidebarLogsAction": "Handlingsloger",
"logRetention": "Logopbevaring", "logRetention": "Logopbevaring",
"logRetentionDescription": "Håndter hvor længe ulike typer logs beholdes for denne organisation, eller deaktivér dem", "logRetentionDescription": "Håndter hvor længe ulike typer logs beholdes for denne organisation, eller deaktivér dem",
"logRetentionDisabledWarningTitle": "Logbevaring deaktiveret",
"logRetentionDisabledWarningDescription": "{logType} gemmes ikke for denne organisation, så nye aktiviteter vises ikke her. Aktiver logbevaring i sikkerhedsindstillingerne for at begynde at indsamle disse logs.",
"logRetentionDisabledWarningButton": "Gå til sikkerhedsindstillinger",
"requestLogsDescription": "Se detaljerede forespørgselslogs for ressourcer i denne organisation", "requestLogsDescription": "Se detaljerede forespørgselslogs for ressourcer i denne organisation",
"aiSessionLogs": "AI Gateway Øktsprotokoller", "aiSessionLogs": "AI Gateway Øktsprotokoller",
"aiSessionLogsDescription": "Se prompt og svarudskrifter for AI gateway forespørgsler i denne organisation", "aiSessionLogsDescription": "Se prompt og svarudskrifter for AI gateway forespørgsler i denne organisation",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Udstyrs- og tunnelforbindelseshændelser, inklusive forbindelser og frakobling.", "httpDestConnectionLogsDescription": "Udstyrs- og tunnelforbindelseshændelser, inklusive forbindelser og frakobling.",
"httpDestRequestLogsTitle": "HTTP-forespørgselslogs", "httpDestRequestLogsTitle": "HTTP-forespørgselslogs",
"httpDestRequestLogsDescription": "HTTP-forespørgsel logs for bekræftede ressourcer, inklusive metode, sti og responskode.", "httpDestRequestLogsDescription": "HTTP-forespørgsel logs for bekræftede ressourcer, inklusive metode, sti og responskode.",
"httpDestAISessionLogsTitle": "AI-session Logs",
"httpDestAISessionLogsDescription": "AI-gateway anmodninger og respons-sessioner, inklusive prompts, modelresponser og tokenforbrug.",
"httpDestSaveChanges": "Gem ændringer", "httpDestSaveChanges": "Gem ændringer",
"httpDestCreateDestination": "Opret mål", "httpDestCreateDestination": "Opret mål",
"httpDestUpdatedSuccess": "Målet er opdateret", "httpDestUpdatedSuccess": "Målet er opdateret",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Agentisches Codierwerkzeug von Anthropic für das Terminal.", "aiClientConfigDescriptionClaude": "Agentisches Codierwerkzeug von Anthropic für das Terminal.",
"aiClientConfigDescriptionCodex": "Agentisches Codierwerkzeug von OpenAI für das Terminal.", "aiClientConfigDescriptionCodex": "Agentisches Codierwerkzeug von OpenAI für das Terminal.",
"aiClientConfigDescriptionOpencode": "Open-Source-Coding-Agent für das Terminal.", "aiClientConfigDescriptionOpencode": "Open-Source-Coding-Agent für das Terminal.",
"aiClientConfigDescriptionGemini": "Googles agentic Coding-Tool für das Terminal.", "aiClientConfigDescriptionCursor": "KI-Code-Editor basierend auf VS Code.",
"aiClientConfigSetup": "Einrichtung", "aiClientConfigSetup": "Einrichtung",
"aiClientConfigTabCli": "Automatisch (CLI)", "aiClientConfigTabCli": "Automatisch (CLI)",
"aiClientConfigTabManual": "Manuelle Konfiguration", "aiClientConfigTabManual": "Manuelle Konfiguration",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Über Ziele auf Ihren Sites routen", "aiProviderRoutingModeTargetDescription": "Über Ziele auf Ihren Sites routen",
"aiProviderRoutingModeTargetNote": "Konfigurieren Sie nach der Erstellung dieses Anbieters Site-Ziele auf der Registerkarte 'Netzwerkeinstellungen'.", "aiProviderRoutingModeTargetNote": "Konfigurieren Sie nach der Erstellung dieses Anbieters Site-Ziele auf der Registerkarte 'Netzwerkeinstellungen'.",
"aiProviderTargetNoOne": "Dieser Anbieter hat keine Ziele. Fügen Sie ein Ziel hinzu, um Anfragen über Ihre Sites zu leiten.", "aiProviderTargetNoOne": "Dieser Anbieter hat keine Ziele. Fügen Sie ein Ziel hinzu, um Anfragen über Ihre Sites zu leiten.",
"aiProviderRemoteNodeTargetsWarning": "Sites, die mit entfernten Knoten verbunden sind, können bei AI-Gateway-Anbietern nicht weitergeleitet werden.",
"aiProviderSkipTlsVerification": "TLS-Überprüfung überspringen", "aiProviderSkipTlsVerification": "TLS-Überprüfung überspringen",
"aiProviderSkipTlsVerificationDescription": "TLS-Zertifikatsüberprüfung für die Upstream-Verbindung deaktivieren", "aiProviderSkipTlsVerificationDescription": "TLS-Zertifikatsüberprüfung für die Upstream-Verbindung deaktivieren",
"aiProviderBudget": "Budget", "aiProviderBudget": "Budget",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Unterstützt /v1/antworten", "aiCapabilityOpenaiResponsesDescription": "Unterstützt /v1/antworten",
"aiCapabilityAnthropicMessages": "Anthropic Nachrichten", "aiCapabilityAnthropicMessages": "Anthropic Nachrichten",
"aiCapabilityAnthropicMessagesDescription": "Unterstützt /v1/nachrichten", "aiCapabilityAnthropicMessagesDescription": "Unterstützt /v1/nachrichten",
"aiCapabilityV1Models": "Modellliste",
"aiCapabilityV1ModelsDescription": "Unterstützt /v1/models Modellentdeckung",
"aiCapabilityGeminiGenerateContent": "Gemini Inhalt erzeugen", "aiCapabilityGeminiGenerateContent": "Gemini Inhalt erzeugen",
"aiCapabilityGeminiGenerateContentDescription": "Unterstützt die direkte Gemini-API", "aiCapabilityGeminiGenerateContentDescription": "Unterstützt die direkte Gemini-API",
"aiCapabilityBedrockModelInvoke": "Bedrock Modell Aufruf", "aiCapabilityBedrockModelInvoke": "Bedrock Modell Aufruf",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Aktionsprotokolle", "sidebarLogsAction": "Aktionsprotokolle",
"logRetention": "Log-Speicherung", "logRetention": "Log-Speicherung",
"logRetentionDescription": "Verwalten, wie lange verschiedene Logs für diese Organisation gespeichert werden oder deaktivieren", "logRetentionDescription": "Verwalten, wie lange verschiedene Logs für diese Organisation gespeichert werden oder deaktivieren",
"logRetentionDisabledWarningTitle": "Protokoll-Aufbewahrung deaktiviert",
"logRetentionDisabledWarningDescription": "{logType} werden für diese Organisation nicht aufbewahrt, daher erscheinen neue Aktivitäten hier nicht. Aktivieren Sie die Aufbewahrung in den Sicherheitseinstellungen, um diese Protokolle zu sammeln.",
"logRetentionDisabledWarningButton": "Zu den Sicherheitseinstellungen gehen",
"requestLogsDescription": "Detaillierte Request-Logs für Ressourcen in dieser Organisation anzeigen", "requestLogsDescription": "Detaillierte Request-Logs für Ressourcen in dieser Organisation anzeigen",
"aiSessionLogs": "AI-Gateway Sitzungsprotokolle", "aiSessionLogs": "AI-Gateway Sitzungsprotokolle",
"aiSessionLogsDescription": "Zeigen Sie Aufforderungs- und Antwortprotokolle für Anfragen des KI-Gateways in dieser Organisation an", "aiSessionLogsDescription": "Zeigen Sie Aufforderungs- und Antwortprotokolle für Anfragen des KI-Gateways in dieser Organisation an",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Site- und Tunnelverbindungen, einschließlich Verbindungen und Trennungen.", "httpDestConnectionLogsDescription": "Site- und Tunnelverbindungen, einschließlich Verbindungen und Trennungen.",
"httpDestRequestLogsTitle": "HTTP Anforderungsprotokolle", "httpDestRequestLogsTitle": "HTTP Anforderungsprotokolle",
"httpDestRequestLogsDescription": "HTTP-Request-Protokolle für proxiierte Ressourcen, einschließlich Methode, Pfad und Antwort-Code.", "httpDestRequestLogsDescription": "HTTP-Request-Protokolle für proxiierte Ressourcen, einschließlich Methode, Pfad und Antwort-Code.",
"httpDestAISessionLogsTitle": "AI-Sitzungsprotokolle",
"httpDestAISessionLogsDescription": "AI-Gateway-Anfrage- und Antwortsitzungen, einschließlich Eingabeaufforderungen, Modellantworten und Token-Nutzung.",
"httpDestSaveChanges": "Änderungen speichern", "httpDestSaveChanges": "Änderungen speichern",
"httpDestCreateDestination": "Ziel erstellen", "httpDestCreateDestination": "Ziel erstellen",
"httpDestUpdatedSuccess": "Ziel erfolgreich aktualisiert", "httpDestUpdatedSuccess": "Ziel erfolgreich aktualisiert",
-6
View File
@@ -1785,7 +1785,6 @@
"aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.", "aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.", "aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.", "aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
"aiClientConfigDescriptionGemini": "Google's agentic coding tool for the terminal.",
"aiClientConfigSetup": "Setup", "aiClientConfigSetup": "Setup",
"aiClientConfigTabCli": "Automatic (CLI)", "aiClientConfigTabCli": "Automatic (CLI)",
"aiClientConfigTabManual": "Manual Configuration", "aiClientConfigTabManual": "Manual Configuration",
@@ -1891,7 +1890,6 @@
"aiProviderRoutingModeTargetDescription": "Route through targets on your sites", "aiProviderRoutingModeTargetDescription": "Route through targets on your sites",
"aiProviderRoutingModeTargetNote": "After creating this provider, configure site targets on the Network Settings tab.", "aiProviderRoutingModeTargetNote": "After creating this provider, configure site targets on the Network Settings tab.",
"aiProviderTargetNoOne": "This provider doesn't have any targets. Add a target to route requests through your sites.", "aiProviderTargetNoOne": "This provider doesn't have any targets. Add a target to route requests through your sites.",
"aiProviderRemoteNodeTargetsWarning": "Sites connected to remote nodes are inaccessable to be routed to on AI Gateway providers.",
"aiProviderSkipTlsVerification": "Skip TLS Verification", "aiProviderSkipTlsVerification": "Skip TLS Verification",
"aiProviderSkipTlsVerificationDescription": "Disable TLS certificate verification for the upstream connection", "aiProviderSkipTlsVerificationDescription": "Disable TLS certificate verification for the upstream connection",
"aiProviderBudget": "Budget", "aiProviderBudget": "Budget",
@@ -1924,8 +1922,6 @@
"aiCapabilityOpenaiResponsesDescription": "Supports /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Supports /v1/responses",
"aiCapabilityAnthropicMessages": "Anthropic Messages", "aiCapabilityAnthropicMessages": "Anthropic Messages",
"aiCapabilityAnthropicMessagesDescription": "Supports /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Supports /v1/messages",
"aiCapabilityV1Models": "Models List",
"aiCapabilityV1ModelsDescription": "Supports /v1/models model discovery",
"aiCapabilityGeminiGenerateContent": "Gemini Generate Content", "aiCapabilityGeminiGenerateContent": "Gemini Generate Content",
"aiCapabilityGeminiGenerateContentDescription": "Supports the direct Gemini API", "aiCapabilityGeminiGenerateContentDescription": "Supports the direct Gemini API",
"aiCapabilityBedrockModelInvoke": "Bedrock Model Invoke", "aiCapabilityBedrockModelInvoke": "Bedrock Model Invoke",
@@ -4087,8 +4083,6 @@
"httpDestConnectionLogsDescription": "Site and tunnel connection events, including connects and disconnects.", "httpDestConnectionLogsDescription": "Site and tunnel connection events, including connects and disconnects.",
"httpDestRequestLogsTitle": "HTTP Request Logs", "httpDestRequestLogsTitle": "HTTP Request Logs",
"httpDestRequestLogsDescription": "HTTP request logs for proxied resources, including method, path, and response code.", "httpDestRequestLogsDescription": "HTTP request logs for proxied resources, including method, path, and response code.",
"httpDestAISessionLogsTitle": "AI Session Logs",
"httpDestAISessionLogsDescription": "AI gateway request and response sessions, including prompts, model responses, and token usage.",
"httpDestSaveChanges": "Save Changes", "httpDestSaveChanges": "Save Changes",
"httpDestCreateDestination": "Create Destination", "httpDestCreateDestination": "Create Destination",
"httpDestUpdatedSuccess": "Destination updated successfully", "httpDestUpdatedSuccess": "Destination updated successfully",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Herramienta de codificación agentic de Anthropic para el terminal.", "aiClientConfigDescriptionClaude": "Herramienta de codificación agentic de Anthropic para el terminal.",
"aiClientConfigDescriptionCodex": "Herramienta de codificación agentic de OpenAI para el terminal.", "aiClientConfigDescriptionCodex": "Herramienta de codificación agentic de OpenAI para el terminal.",
"aiClientConfigDescriptionOpencode": "Agente de codificación de terminal de código abierto.", "aiClientConfigDescriptionOpencode": "Agente de codificación de terminal de código abierto.",
"aiClientConfigDescriptionGemini": "La herramienta de codificación de agente de Google para el terminal.", "aiClientConfigDescriptionCursor": "Editor de código AI construido sobre VS Code.",
"aiClientConfigSetup": "Configuración", "aiClientConfigSetup": "Configuración",
"aiClientConfigTabCli": "Automático (CLI)", "aiClientConfigTabCli": "Automático (CLI)",
"aiClientConfigTabManual": "Configuración manual", "aiClientConfigTabManual": "Configuración manual",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Ruta a través de objetivos en sus sitios", "aiProviderRoutingModeTargetDescription": "Ruta a través de objetivos en sus sitios",
"aiProviderRoutingModeTargetNote": "Después de crear este proveedor, configure objetivos de sitio en la pestaña de Configuración de Red.", "aiProviderRoutingModeTargetNote": "Después de crear este proveedor, configure objetivos de sitio en la pestaña de Configuración de Red.",
"aiProviderTargetNoOne": "Este proveedor no tiene objetivos. Agregue un objetivo para enrutar solicitudes a través de sus sitios.", "aiProviderTargetNoOne": "Este proveedor no tiene objetivos. Agregue un objetivo para enrutar solicitudes a través de sus sitios.",
"aiProviderRemoteNodeTargetsWarning": "Los sitios conectados a nodos remotos son inaccesibles para ser enrutados a los proveedores de AI Gateway.",
"aiProviderSkipTlsVerification": "Omitir verificación de TLS", "aiProviderSkipTlsVerification": "Omitir verificación de TLS",
"aiProviderSkipTlsVerificationDescription": "Deshabilitar la verificación del certificado TLS para la conexión de upstream", "aiProviderSkipTlsVerificationDescription": "Deshabilitar la verificación del certificado TLS para la conexión de upstream",
"aiProviderBudget": "Presupuesto", "aiProviderBudget": "Presupuesto",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Admite /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Admite /v1/responses",
"aiCapabilityAnthropicMessages": "Mensajes Antropicos", "aiCapabilityAnthropicMessages": "Mensajes Antropicos",
"aiCapabilityAnthropicMessagesDescription": "Admite /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Admite /v1/messages",
"aiCapabilityV1Models": "Lista de Modelos",
"aiCapabilityV1ModelsDescription": "Soporta el descubrimiento de modelos /v1/models",
"aiCapabilityGeminiGenerateContent": "Generar contenido Gemini", "aiCapabilityGeminiGenerateContent": "Generar contenido Gemini",
"aiCapabilityGeminiGenerateContentDescription": "Admite la API directa de Gemini", "aiCapabilityGeminiGenerateContentDescription": "Admite la API directa de Gemini",
"aiCapabilityBedrockModelInvoke": "Invocar modelo de Bedrock", "aiCapabilityBedrockModelInvoke": "Invocar modelo de Bedrock",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Registros de acción", "sidebarLogsAction": "Registros de acción",
"logRetention": "Retención de Log", "logRetention": "Retención de Log",
"logRetentionDescription": "Administrar cuánto tiempo se conservan los diferentes tipos de registros para esta organización o desactivarlos", "logRetentionDescription": "Administrar cuánto tiempo se conservan los diferentes tipos de registros para esta organización o desactivarlos",
"logRetentionDisabledWarningTitle": "Retención de Registros Deshabilitada",
"logRetentionDisabledWarningDescription": "{logType} no se están reteniendo para esta organización, por lo que la nueva actividad no aparecerá aquí. Habilita la retención en la configuración de seguridad para comenzar a recopilar estos registros.",
"logRetentionDisabledWarningButton": "Ir a Configuración de Seguridad",
"requestLogsDescription": "Ver registros de solicitudes detallados para los recursos de esta organización", "requestLogsDescription": "Ver registros de solicitudes detallados para los recursos de esta organización",
"aiSessionLogs": "Registros de Sesiones del Portal de IA", "aiSessionLogs": "Registros de Sesiones del Portal de IA",
"aiSessionLogsDescription": "Ver transcripciones de solicitud y respuesta para solicitudes del portal de IA en esta organización", "aiSessionLogsDescription": "Ver transcripciones de solicitud y respuesta para solicitudes del portal de IA en esta organización",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Eventos de conexión de sitios y túneles, incluyendo conexiones y desconexiones.", "httpDestConnectionLogsDescription": "Eventos de conexión de sitios y túneles, incluyendo conexiones y desconexiones.",
"httpDestRequestLogsTitle": "Registros de Solicitud HTTP", "httpDestRequestLogsTitle": "Registros de Solicitud HTTP",
"httpDestRequestLogsDescription": "Registros de peticiones HTTP para recursos proxyficados, incluyendo método, ruta y código de respuesta.", "httpDestRequestLogsDescription": "Registros de peticiones HTTP para recursos proxyficados, incluyendo método, ruta y código de respuesta.",
"httpDestAISessionLogsTitle": "Registros de Sesión AI",
"httpDestAISessionLogsDescription": "Sesiones de solicitud y respuesta de AI gateway, incluyendo indicaciones, respuestas de modelos, y uso de tokens.",
"httpDestSaveChanges": "Guardar Cambios", "httpDestSaveChanges": "Guardar Cambios",
"httpDestCreateDestination": "Crear destino", "httpDestCreateDestination": "Crear destino",
"httpDestUpdatedSuccess": "Destino actualizado correctamente", "httpDestUpdatedSuccess": "Destino actualizado correctamente",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Outil de codage agentique d'Anthropic pour le terminal.", "aiClientConfigDescriptionClaude": "Outil de codage agentique d'Anthropic pour le terminal.",
"aiClientConfigDescriptionCodex": "Outil de codage agentique d'OpenAI pour le terminal.", "aiClientConfigDescriptionCodex": "Outil de codage agentique d'OpenAI pour le terminal.",
"aiClientConfigDescriptionOpencode": "Agent de codage terminal open source.", "aiClientConfigDescriptionOpencode": "Agent de codage terminal open source.",
"aiClientConfigDescriptionGemini": "Outil de codage agentique de Google pour le terminal.", "aiClientConfigDescriptionCursor": "Éditeur de code IA basé sur VS Code.",
"aiClientConfigSetup": "Configuration", "aiClientConfigSetup": "Configuration",
"aiClientConfigTabCli": "Automatique (CLI)", "aiClientConfigTabCli": "Automatique (CLI)",
"aiClientConfigTabManual": "Configuration manuelle", "aiClientConfigTabManual": "Configuration manuelle",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Route à travers les cibles sur vos sites", "aiProviderRoutingModeTargetDescription": "Route à travers les cibles sur vos sites",
"aiProviderRoutingModeTargetNote": "Après avoir créé ce fournisseur, configurez les cibles du site dans l'onglet Paramètres du réseau.", "aiProviderRoutingModeTargetNote": "Après avoir créé ce fournisseur, configurez les cibles du site dans l'onglet Paramètres du réseau.",
"aiProviderTargetNoOne": "Ce fournisseur n'a aucune cible. Ajoutez une cible pour acheminer les requêtes via vos sites.", "aiProviderTargetNoOne": "Ce fournisseur n'a aucune cible. Ajoutez une cible pour acheminer les requêtes via vos sites.",
"aiProviderRemoteNodeTargetsWarning": "Les sites connectés à des nœuds distants sont inaccessibles pour être routés vers les fournisseurs de passerelles AI.",
"aiProviderSkipTlsVerification": "Ignorer la vérification TLS", "aiProviderSkipTlsVerification": "Ignorer la vérification TLS",
"aiProviderSkipTlsVerificationDescription": "Désactiver la vérification du certificat TLS pour la connexion amont", "aiProviderSkipTlsVerificationDescription": "Désactiver la vérification du certificat TLS pour la connexion amont",
"aiProviderBudget": "Budget", "aiProviderBudget": "Budget",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Prend en charge /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Prend en charge /v1/responses",
"aiCapabilityAnthropicMessages": "Messages Anthropiques", "aiCapabilityAnthropicMessages": "Messages Anthropiques",
"aiCapabilityAnthropicMessagesDescription": "Prend en charge /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Prend en charge /v1/messages",
"aiCapabilityV1Models": "Liste des modèles",
"aiCapabilityV1ModelsDescription": "Prise en charge de la découverte de modèles /v1/models",
"aiCapabilityGeminiGenerateContent": "Générer du Contenu Gemini", "aiCapabilityGeminiGenerateContent": "Générer du Contenu Gemini",
"aiCapabilityGeminiGenerateContentDescription": "Prend en charge l'API directe de Gemini", "aiCapabilityGeminiGenerateContentDescription": "Prend en charge l'API directe de Gemini",
"aiCapabilityBedrockModelInvoke": "Invocation du Modèle Bedrock", "aiCapabilityBedrockModelInvoke": "Invocation du Modèle Bedrock",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Journaux des actions", "sidebarLogsAction": "Journaux des actions",
"logRetention": "Journaliser la rétention", "logRetention": "Journaliser la rétention",
"logRetentionDescription": "Gérer la durée de conservation des différents types de logs pour cette organisation ou les désactiver", "logRetentionDescription": "Gérer la durée de conservation des différents types de logs pour cette organisation ou les désactiver",
"logRetentionDisabledWarningTitle": "Conservation des journaux désactivée",
"logRetentionDisabledWarningDescription": "{logType} ne sont pas conservés pour cette organisation, donc aucune nouvelle activité napparaîtra ici. Activez la conservation dans les paramètres de sécurité pour commencer à collecter ces journaux.",
"logRetentionDisabledWarningButton": "Aller aux paramètres de sécurité",
"requestLogsDescription": "Voir les journaux détaillés des requêtes pour les ressources de cette organisation", "requestLogsDescription": "Voir les journaux détaillés des requêtes pour les ressources de cette organisation",
"aiSessionLogs": "Journaux de Session du Portail AI", "aiSessionLogs": "Journaux de Session du Portail AI",
"aiSessionLogsDescription": "Voir les transcriptions de l'invite et de la réponse pour les requêtes de portail AI dans cette organisation", "aiSessionLogsDescription": "Voir les transcriptions de l'invite et de la réponse pour les requêtes de portail AI dans cette organisation",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Événements de connexion du site et du tunnel, y compris les connexions et les déconnexions.", "httpDestConnectionLogsDescription": "Événements de connexion du site et du tunnel, y compris les connexions et les déconnexions.",
"httpDestRequestLogsTitle": "Journal des Requêtes HTTP", "httpDestRequestLogsTitle": "Journal des Requêtes HTTP",
"httpDestRequestLogsDescription": "Journaux des requêtes HTTP pour les ressources proxiées, y compris la méthode, le chemin et le code de réponse.", "httpDestRequestLogsDescription": "Journaux des requêtes HTTP pour les ressources proxiées, y compris la méthode, le chemin et le code de réponse.",
"httpDestAISessionLogsTitle": "Journaux de session AI",
"httpDestAISessionLogsDescription": "Sessions de requête et de réponse de la passerelle AI, y compris les invites, les réponses du modèle et l'utilisation des jetons.",
"httpDestSaveChanges": "Enregistrer les modifications", "httpDestSaveChanges": "Enregistrer les modifications",
"httpDestCreateDestination": "Créer une destination", "httpDestCreateDestination": "Créer une destination",
"httpDestUpdatedSuccess": "Destination mise à jour avec succès", "httpDestUpdatedSuccess": "Destination mise à jour avec succès",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Strumento di coding agente di Anthropic per il terminale.", "aiClientConfigDescriptionClaude": "Strumento di coding agente di Anthropic per il terminale.",
"aiClientConfigDescriptionCodex": "Strumento di coding agente di OpenAI per il terminale.", "aiClientConfigDescriptionCodex": "Strumento di coding agente di OpenAI per il terminale.",
"aiClientConfigDescriptionOpencode": "Agente di coding open source per il terminale.", "aiClientConfigDescriptionOpencode": "Agente di coding open source per il terminale.",
"aiClientConfigDescriptionGemini": "Lo strumento di codifica agentica di Google per il terminale.", "aiClientConfigDescriptionCursor": "Editor di codice AI basato su VS Code.",
"aiClientConfigSetup": "Impostazione", "aiClientConfigSetup": "Impostazione",
"aiClientConfigTabCli": "Automatico (CLI)", "aiClientConfigTabCli": "Automatico (CLI)",
"aiClientConfigTabManual": "Configurazione Manuale", "aiClientConfigTabManual": "Configurazione Manuale",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Instrada tramite target sui tuoi siti", "aiProviderRoutingModeTargetDescription": "Instrada tramite target sui tuoi siti",
"aiProviderRoutingModeTargetNote": "Dopo aver creato questo provider, configura i target del sito nella scheda Impostazioni di Rete.", "aiProviderRoutingModeTargetNote": "Dopo aver creato questo provider, configura i target del sito nella scheda Impostazioni di Rete.",
"aiProviderTargetNoOne": "Questo provider non ha alcun target. Aggiungi un target per instradare le richieste attraverso i tuoi siti.", "aiProviderTargetNoOne": "Questo provider non ha alcun target. Aggiungi un target per instradare le richieste attraverso i tuoi siti.",
"aiProviderRemoteNodeTargetsWarning": "I siti collegati a nodi remoti non sono accessibili per essere instradati sui fornitori di AI Gateway.",
"aiProviderSkipTlsVerification": "Salta la verifica TLS", "aiProviderSkipTlsVerification": "Salta la verifica TLS",
"aiProviderSkipTlsVerificationDescription": "Disabilita la verifica del certificato TLS per la connessione a monte", "aiProviderSkipTlsVerificationDescription": "Disabilita la verifica del certificato TLS per la connessione a monte",
"aiProviderBudget": "Budget", "aiProviderBudget": "Budget",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Supporta /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Supporta /v1/responses",
"aiCapabilityAnthropicMessages": "Messaggi Anthropic", "aiCapabilityAnthropicMessages": "Messaggi Anthropic",
"aiCapabilityAnthropicMessagesDescription": "Supporta /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Supporta /v1/messages",
"aiCapabilityV1Models": "Elenco dei Modelli",
"aiCapabilityV1ModelsDescription": "Supporta la scoperta del modello /v1/models",
"aiCapabilityGeminiGenerateContent": "Generazione di Contenuti Gemini", "aiCapabilityGeminiGenerateContent": "Generazione di Contenuti Gemini",
"aiCapabilityGeminiGenerateContentDescription": "Supporta l'API diretta di Gemini", "aiCapabilityGeminiGenerateContentDescription": "Supporta l'API diretta di Gemini",
"aiCapabilityBedrockModelInvoke": "Invoca Modello Bedrock", "aiCapabilityBedrockModelInvoke": "Invoca Modello Bedrock",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Log Azioni", "sidebarLogsAction": "Log Azioni",
"logRetention": "Ritenzione Registro", "logRetention": "Ritenzione Registro",
"logRetentionDescription": "Gestisci per quanto tempo i diversi tipi di log sono mantenuti per questa organizzazione o disabilitali", "logRetentionDescription": "Gestisci per quanto tempo i diversi tipi di log sono mantenuti per questa organizzazione o disabilitali",
"logRetentionDisabledWarningTitle": "Conservazione del Log Disabilitata",
"logRetentionDisabledWarningDescription": "{logType} non vengono conservati per questa organizzazione, quindi le nuove attività non appariranno qui. Abilita la conservazione nelle impostazioni di sicurezza per iniziare a raccogliere questi log.",
"logRetentionDisabledWarningButton": "Vai alle Impostazioni di Sicurezza",
"requestLogsDescription": "Visualizza i registri di richiesta dettagliati per le risorse in questa organizzazione", "requestLogsDescription": "Visualizza i registri di richiesta dettagliati per le risorse in questa organizzazione",
"aiSessionLogs": "Log delle Sessioni AI Gateway", "aiSessionLogs": "Log delle Sessioni AI Gateway",
"aiSessionLogsDescription": "Visualizza trascrizioni di prompt e risposte per le richieste del gateway AI in questa organizzazione", "aiSessionLogsDescription": "Visualizza trascrizioni di prompt e risposte per le richieste del gateway AI in questa organizzazione",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Eventi di connessione al sito e al tunnel, inclusi collegamenti e disconnessioni.", "httpDestConnectionLogsDescription": "Eventi di connessione al sito e al tunnel, inclusi collegamenti e disconnessioni.",
"httpDestRequestLogsTitle": "Log Richieste HTTP", "httpDestRequestLogsTitle": "Log Richieste HTTP",
"httpDestRequestLogsDescription": "Registri di richiesta HTTP per le risorse proxy, inclusi metodo, percorso e codice di risposta.", "httpDestRequestLogsDescription": "Registri di richiesta HTTP per le risorse proxy, inclusi metodo, percorso e codice di risposta.",
"httpDestAISessionLogsTitle": "Log di Sessione AI",
"httpDestAISessionLogsDescription": "Sessioni di richiesta e risposta AI gateway, comprese le domande, le risposte del modello e l'utilizzo dei token.",
"httpDestSaveChanges": "Salva Modifiche", "httpDestSaveChanges": "Salva Modifiche",
"httpDestCreateDestination": "Crea Destinazione", "httpDestCreateDestination": "Crea Destinazione",
"httpDestUpdatedSuccess": "Destinazione aggiornata con successo", "httpDestUpdatedSuccess": "Destinazione aggiornata con successo",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Anthropic의 터미널 에이전트 코딩 도구입니다.", "aiClientConfigDescriptionClaude": "Anthropic의 터미널 에이전트 코딩 도구입니다.",
"aiClientConfigDescriptionCodex": "OpenAI의 터미널 에이전트 코딩 도구입니다.", "aiClientConfigDescriptionCodex": "OpenAI의 터미널 에이전트 코딩 도구입니다.",
"aiClientConfigDescriptionOpencode": "오픈 소스 터미널 코딩 에이전트.", "aiClientConfigDescriptionOpencode": "오픈 소스 터미널 코딩 에이전트.",
"aiClientConfigDescriptionGemini": "터미널용 구글의 에이전시 코딩 도구.", "aiClientConfigDescriptionCursor": "VS Code를 기반으로 한 AI 코드 편집기.",
"aiClientConfigSetup": "설정", "aiClientConfigSetup": "설정",
"aiClientConfigTabCli": "자동 (CLI)", "aiClientConfigTabCli": "자동 (CLI)",
"aiClientConfigTabManual": "수동 구성", "aiClientConfigTabManual": "수동 구성",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "사이트의 타겟을 통해 라우트", "aiProviderRoutingModeTargetDescription": "사이트의 타겟을 통해 라우트",
"aiProviderRoutingModeTargetNote": "이 공급자를 생성한 후 네트워크 설정 탭에 사이트 타겟을 구성합니다.", "aiProviderRoutingModeTargetNote": "이 공급자를 생성한 후 네트워크 설정 탭에 사이트 타겟을 구성합니다.",
"aiProviderTargetNoOne": "이 공급자에게 타겟이 없습니다. 사이트를 통해 요청을 라우트하기 위한 타겟을 추가하십시오.", "aiProviderTargetNoOne": "이 공급자에게 타겟이 없습니다. 사이트를 통해 요청을 라우트하기 위한 타겟을 추가하십시오.",
"aiProviderRemoteNodeTargetsWarning": "원격 노드에 연결된 사이트는 AI 게이트웨이 공급자에게 라우팅되지 않습니다.",
"aiProviderSkipTlsVerification": "TLS 검증 건너뛰기", "aiProviderSkipTlsVerification": "TLS 검증 건너뛰기",
"aiProviderSkipTlsVerificationDescription": "상류 연결에 대한 TLS 인증서 검증 비활성화", "aiProviderSkipTlsVerificationDescription": "상류 연결에 대한 TLS 인증서 검증 비활성화",
"aiProviderBudget": "예산", "aiProviderBudget": "예산",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "/v1/responses 지원", "aiCapabilityOpenaiResponsesDescription": "/v1/responses 지원",
"aiCapabilityAnthropicMessages": "Anthropic 메시지", "aiCapabilityAnthropicMessages": "Anthropic 메시지",
"aiCapabilityAnthropicMessagesDescription": "/v1/messages 지원", "aiCapabilityAnthropicMessagesDescription": "/v1/messages 지원",
"aiCapabilityV1Models": "모델 목록",
"aiCapabilityV1ModelsDescription": "/v1/models 모델 검색 지원",
"aiCapabilityGeminiGenerateContent": "Gemini 콘텐츠 생성", "aiCapabilityGeminiGenerateContent": "Gemini 콘텐츠 생성",
"aiCapabilityGeminiGenerateContentDescription": "직접 Gemini API 지원", "aiCapabilityGeminiGenerateContentDescription": "직접 Gemini API 지원",
"aiCapabilityBedrockModelInvoke": "Bedrock 모델 실행", "aiCapabilityBedrockModelInvoke": "Bedrock 모델 실행",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "작업 로그", "sidebarLogsAction": "작업 로그",
"logRetention": "로그 보관", "logRetention": "로그 보관",
"logRetentionDescription": "다양한 유형의 로그를 이 조직에 대해 얼마나 오래 보관할지 관리하거나 비활성화합니다", "logRetentionDescription": "다양한 유형의 로그를 이 조직에 대해 얼마나 오래 보관할지 관리하거나 비활성화합니다",
"logRetentionDisabledWarningTitle": "로그 보존 비활성화",
"logRetentionDisabledWarningDescription": "{logType}이/가 이 조직에 대해 보존되지 않으므로 새로운 활동이 여기에 나타나지 않습니다. 보안을 설정해서 보존을 활성화하여 이러한 로그를 수집하기 시작하세요.",
"logRetentionDisabledWarningButton": "보안 설정으로 이동",
"requestLogsDescription": "이 조직의 자원에 대한 상세한 요청 로그를 봅니다", "requestLogsDescription": "이 조직의 자원에 대한 상세한 요청 로그를 봅니다",
"aiSessionLogs": "AI 게이트웨이 세션 로그", "aiSessionLogs": "AI 게이트웨이 세션 로그",
"aiSessionLogsDescription": "이 조직의 AI 게이트웨이 요청에 대한 프롬프트 및 응답 대본을 봅니다", "aiSessionLogsDescription": "이 조직의 AI 게이트웨이 요청에 대한 프롬프트 및 응답 대본을 봅니다",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "사이트 및 터널 연결 이벤트, 연결 및 연결 끊기를 포함합니다.", "httpDestConnectionLogsDescription": "사이트 및 터널 연결 이벤트, 연결 및 연결 끊기를 포함합니다.",
"httpDestRequestLogsTitle": "HTTP 요청 로그", "httpDestRequestLogsTitle": "HTTP 요청 로그",
"httpDestRequestLogsDescription": "프록시된 리소스에 대한 HTTP 요청 로그, 메서드, 경로 및 응답 코드를 포함합니다.", "httpDestRequestLogsDescription": "프록시된 리소스에 대한 HTTP 요청 로그, 메서드, 경로 및 응답 코드를 포함합니다.",
"httpDestAISessionLogsTitle": "AI 세션 로그",
"httpDestAISessionLogsDescription": "AI 게이트웨이 요청 및 응답 세션, 프롬프트, 모델 응답 및 토큰 사용을 포함합니다.",
"httpDestSaveChanges": "변경 사항 저장", "httpDestSaveChanges": "변경 사항 저장",
"httpDestCreateDestination": "대상지 생성", "httpDestCreateDestination": "대상지 생성",
"httpDestUpdatedSuccess": "대상지가 성공적으로 업데이트되었습니다", "httpDestUpdatedSuccess": "대상지가 성공적으로 업데이트되었습니다",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Anthropics agentiske kodingsverktøy for terminalen.", "aiClientConfigDescriptionClaude": "Anthropics agentiske kodingsverktøy for terminalen.",
"aiClientConfigDescriptionCodex": "OpenAIs agentiske kodingsverktøy for terminalen.", "aiClientConfigDescriptionCodex": "OpenAIs agentiske kodingsverktøy for terminalen.",
"aiClientConfigDescriptionOpencode": "Åpen kildekode terminal kodeagent.", "aiClientConfigDescriptionOpencode": "Åpen kildekode terminal kodeagent.",
"aiClientConfigDescriptionGemini": "Googles agentiske koding verktøy for terminalen.", "aiClientConfigDescriptionCursor": "AI-kodeeditor bygget på VS Code.",
"aiClientConfigSetup": "Oppsett", "aiClientConfigSetup": "Oppsett",
"aiClientConfigTabCli": "Automatisk (CLI)", "aiClientConfigTabCli": "Automatisk (CLI)",
"aiClientConfigTabManual": "Manuell konfigurasjon", "aiClientConfigTabManual": "Manuell konfigurasjon",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Rute gjennom mål på dine nettsteder", "aiProviderRoutingModeTargetDescription": "Rute gjennom mål på dine nettsteder",
"aiProviderRoutingModeTargetNote": "Etter å ha opprettet denne leverandøren, konfigurer områdemål på fanen Nettverksinnstillinger.", "aiProviderRoutingModeTargetNote": "Etter å ha opprettet denne leverandøren, konfigurer områdemål på fanen Nettverksinnstillinger.",
"aiProviderTargetNoOne": "Denne leverandøren har ingen mål. Legg til et mål for å rute forespørsler gjennom dine nettsteder.", "aiProviderTargetNoOne": "Denne leverandøren har ingen mål. Legg til et mål for å rute forespørsler gjennom dine nettsteder.",
"aiProviderRemoteNodeTargetsWarning": "Nettsteder tilkoblet eksterne noder er utilgjengelige for ruting til på AI Gateway leverandører.",
"aiProviderSkipTlsVerification": "Hopp over TLS-verifisering", "aiProviderSkipTlsVerification": "Hopp over TLS-verifisering",
"aiProviderSkipTlsVerificationDescription": "Deaktiver TLS-sertifikatverifisering for oppstrøms tilkobling", "aiProviderSkipTlsVerificationDescription": "Deaktiver TLS-sertifikatverifisering for oppstrøms tilkobling",
"aiProviderBudget": "Budsjett", "aiProviderBudget": "Budsjett",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Støtter /v1/responser", "aiCapabilityOpenaiResponsesDescription": "Støtter /v1/responser",
"aiCapabilityAnthropicMessages": "Anthropic Meldinger", "aiCapabilityAnthropicMessages": "Anthropic Meldinger",
"aiCapabilityAnthropicMessagesDescription": "Støtter /v1/meldinger", "aiCapabilityAnthropicMessagesDescription": "Støtter /v1/meldinger",
"aiCapabilityV1Models": "Modelliste",
"aiCapabilityV1ModelsDescription": "Støtter /v1/modeller modelloppdagelse",
"aiCapabilityGeminiGenerateContent": "Gemini Generer Innhold", "aiCapabilityGeminiGenerateContent": "Gemini Generer Innhold",
"aiCapabilityGeminiGenerateContentDescription": "Støtter direkte Gemini API", "aiCapabilityGeminiGenerateContentDescription": "Støtter direkte Gemini API",
"aiCapabilityBedrockModelInvoke": "Bedrock Modell Påkalling", "aiCapabilityBedrockModelInvoke": "Bedrock Modell Påkalling",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Handlingslogger", "sidebarLogsAction": "Handlingslogger",
"logRetention": "Logg tilbaketrekning", "logRetention": "Logg tilbaketrekning",
"logRetentionDescription": "Håndter hvor lenge ulike typer logger beholdes for denne organisasjonen, eller deaktiver dem", "logRetentionDescription": "Håndter hvor lenge ulike typer logger beholdes for denne organisasjonen, eller deaktiver dem",
"logRetentionDisabledWarningTitle": "Loggbevaring deaktivert",
"logRetentionDisabledWarningDescription": "{logType} blir ikke lagret for denne organisasjonen, så ny aktivitet vises ikke her. Aktiver lagring i sikkerhetsinnstillingene for å begynne å samle inn disse loggene.",
"logRetentionDisabledWarningButton": "Gå til sikkerhetsinnstillinger",
"requestLogsDescription": "Se detaljerte forespørselslogger for ressurser i denne organisasjonen", "requestLogsDescription": "Se detaljerte forespørselslogger for ressurser i denne organisasjonen",
"aiSessionLogs": "AI Portal Sesjonslogger", "aiSessionLogs": "AI Portal Sesjonslogger",
"aiSessionLogsDescription": "Vis stikkord- og responsutskrifter for AI-portal forespørsler i denne organisasjonen", "aiSessionLogsDescription": "Vis stikkord- og responsutskrifter for AI-portal forespørsler i denne organisasjonen",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Utstyrs- og tunneltilkoblingshendelser, inkludert forbindelser og frakobling.", "httpDestConnectionLogsDescription": "Utstyrs- og tunneltilkoblingshendelser, inkludert forbindelser og frakobling.",
"httpDestRequestLogsTitle": "HTTP-forespørselslogger", "httpDestRequestLogsTitle": "HTTP-forespørselslogger",
"httpDestRequestLogsDescription": "HTTP-forespørsel logger for bekreftede ressurser, inkludert metode, bane og responskode.", "httpDestRequestLogsDescription": "HTTP-forespørsel logger for bekreftede ressurser, inkludert metode, bane og responskode.",
"httpDestAISessionLogsTitle": "AI øktlogger",
"httpDestAISessionLogsDescription": "Forespørsels- og svarøkter for AI gateway, inkludert forespørsler, modellresponser og tokenbruk.",
"httpDestSaveChanges": "Lagre endringer", "httpDestSaveChanges": "Lagre endringer",
"httpDestCreateDestination": "Opprett mål", "httpDestCreateDestination": "Opprett mål",
"httpDestUpdatedSuccess": "Målet er oppdatert", "httpDestUpdatedSuccess": "Målet er oppdatert",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Anthropic's agentische coderingstool voor de terminal.", "aiClientConfigDescriptionClaude": "Anthropic's agentische coderingstool voor de terminal.",
"aiClientConfigDescriptionCodex": "OpenAI's agentische coderingstool voor de terminal.", "aiClientConfigDescriptionCodex": "OpenAI's agentische coderingstool voor de terminal.",
"aiClientConfigDescriptionOpencode": "Open source terminal coderingsagent.", "aiClientConfigDescriptionOpencode": "Open source terminal coderingsagent.",
"aiClientConfigDescriptionGemini": "Agentisch coderingstool van Google voor de terminal.", "aiClientConfigDescriptionCursor": "AI-code-editor gebouwd op VS Code.",
"aiClientConfigSetup": "Instellen", "aiClientConfigSetup": "Instellen",
"aiClientConfigTabCli": "Automatisch (CLI)", "aiClientConfigTabCli": "Automatisch (CLI)",
"aiClientConfigTabManual": "Handmatige configuratie", "aiClientConfigTabManual": "Handmatige configuratie",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Routeer door doelen op uw sites", "aiProviderRoutingModeTargetDescription": "Routeer door doelen op uw sites",
"aiProviderRoutingModeTargetNote": "Nadat u deze provider hebt aangemaakt, configureert u site-doelen op het tabblad Netwerkinstellingen.", "aiProviderRoutingModeTargetNote": "Nadat u deze provider hebt aangemaakt, configureert u site-doelen op het tabblad Netwerkinstellingen.",
"aiProviderTargetNoOne": "Deze provider heeft geen doelen. Voeg een doel toe om verzoeken via uw sites te routeren.", "aiProviderTargetNoOne": "Deze provider heeft geen doelen. Voeg een doel toe om verzoeken via uw sites te routeren.",
"aiProviderRemoteNodeTargetsWarning": "Sites die verbonden zijn met externe nodes zijn niet toegankelijk om naar te worden gerouteerd op AI Gateway-providers.",
"aiProviderSkipTlsVerification": "Sla TLS-verificatie over", "aiProviderSkipTlsVerification": "Sla TLS-verificatie over",
"aiProviderSkipTlsVerificationDescription": "Schakel TLS-certificaatverificatie voor de upstream-verbinding uit", "aiProviderSkipTlsVerificationDescription": "Schakel TLS-certificaatverificatie voor de upstream-verbinding uit",
"aiProviderBudget": "Budget", "aiProviderBudget": "Budget",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Ondersteunt /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Ondersteunt /v1/responses",
"aiCapabilityAnthropicMessages": "Anthropic Berichten", "aiCapabilityAnthropicMessages": "Anthropic Berichten",
"aiCapabilityAnthropicMessagesDescription": "Ondersteunt /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Ondersteunt /v1/messages",
"aiCapabilityV1Models": "Modellenlijst",
"aiCapabilityV1ModelsDescription": "Ondersteunt /v1/models modelontdekking",
"aiCapabilityGeminiGenerateContent": "Gemini Inhoud Genereren", "aiCapabilityGeminiGenerateContent": "Gemini Inhoud Genereren",
"aiCapabilityGeminiGenerateContentDescription": "Ondersteunt de directe Gemini API", "aiCapabilityGeminiGenerateContentDescription": "Ondersteunt de directe Gemini API",
"aiCapabilityBedrockModelInvoke": "Bedrock Model Aanroep", "aiCapabilityBedrockModelInvoke": "Bedrock Model Aanroep",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Actie logs", "sidebarLogsAction": "Actie logs",
"logRetention": "Log bewaring", "logRetention": "Log bewaring",
"logRetentionDescription": "Beheren hoe lang verschillende soorten logs bewaard worden voor deze organisatie of schakel ze uit", "logRetentionDescription": "Beheren hoe lang verschillende soorten logs bewaard worden voor deze organisatie of schakel ze uit",
"logRetentionDisabledWarningTitle": "Logboekbewaring Uitgeschakeld",
"logRetentionDisabledWarningDescription": "{logType} worden niet bewaard voor deze organisatie, dus nieuwe activiteiten zullen hier niet verschijnen. Schakel bewaren in beveiligingsinstellingen in om deze logboeken te verzamelen.",
"logRetentionDisabledWarningButton": "Ga naar Beveiligingsinstellingen",
"requestLogsDescription": "Bekijk gedetailleerde verzoeklogboeken voor resources in deze organisatie", "requestLogsDescription": "Bekijk gedetailleerde verzoeklogboeken voor resources in deze organisatie",
"aiSessionLogs": "AI Gateway Sessie Logs", "aiSessionLogs": "AI Gateway Sessie Logs",
"aiSessionLogsDescription": "Bekijk prompt- en reactie-transcripten voor AI-gateway-aanvragen in deze organisatie", "aiSessionLogsDescription": "Bekijk prompt- en reactie-transcripten voor AI-gateway-aanvragen in deze organisatie",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Verbinding met de Site en tunnel maken verbroken, inclusief verbindingen en verbindingen.", "httpDestConnectionLogsDescription": "Verbinding met de Site en tunnel maken verbroken, inclusief verbindingen en verbindingen.",
"httpDestRequestLogsTitle": "HTTP-aanvraaglogboeken", "httpDestRequestLogsTitle": "HTTP-aanvraaglogboeken",
"httpDestRequestLogsDescription": "HTTP request logs voor proxied hulpmiddelen, waaronder methode, pad en response code.", "httpDestRequestLogsDescription": "HTTP request logs voor proxied hulpmiddelen, waaronder methode, pad en response code.",
"httpDestAISessionLogsTitle": "AI Sessielogboeken",
"httpDestAISessionLogsDescription": "AI gateway verzoek- en reactiesessies, inclusief prompts, modelreacties en tokengebruik.",
"httpDestSaveChanges": "Wijzigingen opslaan", "httpDestSaveChanges": "Wijzigingen opslaan",
"httpDestCreateDestination": "Maak bestemming aan", "httpDestCreateDestination": "Maak bestemming aan",
"httpDestUpdatedSuccess": "Bestemming succesvol bijgewerkt", "httpDestUpdatedSuccess": "Bestemming succesvol bijgewerkt",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Agent narzędzia kodującego Anthropic dla terminala.", "aiClientConfigDescriptionClaude": "Agent narzędzia kodującego Anthropic dla terminala.",
"aiClientConfigDescriptionCodex": "Agent narzędzia kodującego OpenAI dla terminala.", "aiClientConfigDescriptionCodex": "Agent narzędzia kodującego OpenAI dla terminala.",
"aiClientConfigDescriptionOpencode": "Agent open source do kodowania w terminalu.", "aiClientConfigDescriptionOpencode": "Agent open source do kodowania w terminalu.",
"aiClientConfigDescriptionGemini": "Agent narzędzi kodowych Google dla terminala.", "aiClientConfigDescriptionCursor": "Edytor kodu AI oparty na VS Code.",
"aiClientConfigSetup": "Ustawienie", "aiClientConfigSetup": "Ustawienie",
"aiClientConfigTabCli": "Automatyczne (CLI)", "aiClientConfigTabCli": "Automatyczne (CLI)",
"aiClientConfigTabManual": "Konfiguracja ręczna", "aiClientConfigTabManual": "Konfiguracja ręczna",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Trasa przez cele na Twoich witrynach", "aiProviderRoutingModeTargetDescription": "Trasa przez cele na Twoich witrynach",
"aiProviderRoutingModeTargetNote": "Po utworzeniu tego dostawcy, skonfiguruj cele witryny na karcie Ustawienia sieci.", "aiProviderRoutingModeTargetNote": "Po utworzeniu tego dostawcy, skonfiguruj cele witryny na karcie Ustawienia sieci.",
"aiProviderTargetNoOne": "Ten dostawca nie ma żadnych celów. Dodaj cel, aby trasować zapytania przez swoje witryny.", "aiProviderTargetNoOne": "Ten dostawca nie ma żadnych celów. Dodaj cel, aby trasować zapytania przez swoje witryny.",
"aiProviderRemoteNodeTargetsWarning": "Witryny podłączone do zdalnych węzłów są niedostępne do trasowania przez dostawców AI Gateway.",
"aiProviderSkipTlsVerification": "Pomiń weryfikację TLS", "aiProviderSkipTlsVerification": "Pomiń weryfikację TLS",
"aiProviderSkipTlsVerificationDescription": "Wyłącz weryfikację certyfikatu TLS dla połączenia w górę", "aiProviderSkipTlsVerificationDescription": "Wyłącz weryfikację certyfikatu TLS dla połączenia w górę",
"aiProviderBudget": "Budżet", "aiProviderBudget": "Budżet",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Obsługuje /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Obsługuje /v1/responses",
"aiCapabilityAnthropicMessages": "Anthropic Wiadomości", "aiCapabilityAnthropicMessages": "Anthropic Wiadomości",
"aiCapabilityAnthropicMessagesDescription": "Obsługuje /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Obsługuje /v1/messages",
"aiCapabilityV1Models": "Lista modeli",
"aiCapabilityV1ModelsDescription": "Obsługuje odkrywanie modeli /v1/models",
"aiCapabilityGeminiGenerateContent": "Gemini Generowanie Treści", "aiCapabilityGeminiGenerateContent": "Gemini Generowanie Treści",
"aiCapabilityGeminiGenerateContentDescription": "Obsługuje bezpośredni Gemini API", "aiCapabilityGeminiGenerateContentDescription": "Obsługuje bezpośredni Gemini API",
"aiCapabilityBedrockModelInvoke": "Model Bedrock Wywołanie", "aiCapabilityBedrockModelInvoke": "Model Bedrock Wywołanie",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Dzienniki działań", "sidebarLogsAction": "Dzienniki działań",
"logRetention": "Zachowanie dziennika", "logRetention": "Zachowanie dziennika",
"logRetentionDescription": "Zarządzaj jak długo różne typy logów są zachowane dla tej organizacji lub wyłącz je", "logRetentionDescription": "Zarządzaj jak długo różne typy logów są zachowane dla tej organizacji lub wyłącz je",
"logRetentionDisabledWarningTitle": "Wyłączone przechowywanie logów",
"logRetentionDisabledWarningDescription": "{logType} nie są przechowywane dla tej organizacji, więc nowe aktywności nie pojawią się tutaj. Włącz przechowywanie w ustawieniach bezpieczeństwa, aby zacząć zbierać te logi.",
"logRetentionDisabledWarningButton": "Przejdź do ustawień bezpieczeństwa",
"requestLogsDescription": "Zobacz szczegółowe dzienniki żądań zasobów w tej organizacji", "requestLogsDescription": "Zobacz szczegółowe dzienniki żądań zasobów w tej organizacji",
"aiSessionLogs": "Dzienniki Sesji Bramy AI", "aiSessionLogs": "Dzienniki Sesji Bramy AI",
"aiSessionLogsDescription": "Zobacz transkrypcje podpowiedzi i odpowiedzi dla żądań bramy AI w tej organizacji", "aiSessionLogsDescription": "Zobacz transkrypcje podpowiedzi i odpowiedzi dla żądań bramy AI w tej organizacji",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Zdarzenia związane z miejscem i tunelem, w tym połączenia i rozłączenia.", "httpDestConnectionLogsDescription": "Zdarzenia związane z miejscem i tunelem, w tym połączenia i rozłączenia.",
"httpDestRequestLogsTitle": "Dzienniki żądań HTTP", "httpDestRequestLogsTitle": "Dzienniki żądań HTTP",
"httpDestRequestLogsDescription": "Logi żądań HTTP dla zasobów proxy, w tym metody, ścieżki i kodu odpowiedzi.", "httpDestRequestLogsDescription": "Logi żądań HTTP dla zasobów proxy, w tym metody, ścieżki i kodu odpowiedzi.",
"httpDestAISessionLogsTitle": "Dzienniki sesji AI",
"httpDestAISessionLogsDescription": "Żądania i sesje odpowiedzi bramki AI, w tym zapytania, odpowiedzi modeli i użycie tokenów.",
"httpDestSaveChanges": "Zapisz zmiany", "httpDestSaveChanges": "Zapisz zmiany",
"httpDestCreateDestination": "Utwórz cel", "httpDestCreateDestination": "Utwórz cel",
"httpDestUpdatedSuccess": "Cel został pomyślnie zaktualizowany", "httpDestUpdatedSuccess": "Cel został pomyślnie zaktualizowany",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Ferramenta de codificação agentic de Anthropic para o terminal.", "aiClientConfigDescriptionClaude": "Ferramenta de codificação agentic de Anthropic para o terminal.",
"aiClientConfigDescriptionCodex": "Ferramenta de codificação agentic da OpenAI para o terminal.", "aiClientConfigDescriptionCodex": "Ferramenta de codificação agentic da OpenAI para o terminal.",
"aiClientConfigDescriptionOpencode": "Agente de codificação de terminal de código aberto.", "aiClientConfigDescriptionOpencode": "Agente de codificação de terminal de código aberto.",
"aiClientConfigDescriptionGemini": "Ferramenta de codificação agêntica do terminal do Google.", "aiClientConfigDescriptionCursor": "Editor de código de IA baseado no VS Code.",
"aiClientConfigSetup": "Configuração", "aiClientConfigSetup": "Configuração",
"aiClientConfigTabCli": "Automático (CLI)", "aiClientConfigTabCli": "Automático (CLI)",
"aiClientConfigTabManual": "Configuração Manual", "aiClientConfigTabManual": "Configuração Manual",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Roteie através de alvos em seus sites", "aiProviderRoutingModeTargetDescription": "Roteie através de alvos em seus sites",
"aiProviderRoutingModeTargetNote": "Após criar este provedor, configure alvos do site na aba Configurações de Rede.", "aiProviderRoutingModeTargetNote": "Após criar este provedor, configure alvos do site na aba Configurações de Rede.",
"aiProviderTargetNoOne": "Este provedor não tem alvos. Adicione um alvo para rotear pedidos pelos seus sites.", "aiProviderTargetNoOne": "Este provedor não tem alvos. Adicione um alvo para rotear pedidos pelos seus sites.",
"aiProviderRemoteNodeTargetsWarning": "Sites conectados a nós remotos estão inacessíveis para serem roteados para os provedores do Gateway de IA.",
"aiProviderSkipTlsVerification": "Pular Verificação TLS", "aiProviderSkipTlsVerification": "Pular Verificação TLS",
"aiProviderSkipTlsVerificationDescription": "Desativar a verificação de certificado TLS para a conexão upstream", "aiProviderSkipTlsVerificationDescription": "Desativar a verificação de certificado TLS para a conexão upstream",
"aiProviderBudget": "Orçamento", "aiProviderBudget": "Orçamento",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Suporta /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Suporta /v1/responses",
"aiCapabilityAnthropicMessages": "Mensagens Antropicas", "aiCapabilityAnthropicMessages": "Mensagens Antropicas",
"aiCapabilityAnthropicMessagesDescription": "Suporta /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Suporta /v1/messages",
"aiCapabilityV1Models": "Lista de Modelos",
"aiCapabilityV1ModelsDescription": "Suporta descoberta de modelos /v1/models",
"aiCapabilityGeminiGenerateContent": "Gêmeos Gerar Conteúdo", "aiCapabilityGeminiGenerateContent": "Gêmeos Gerar Conteúdo",
"aiCapabilityGeminiGenerateContentDescription": "Suporta a API diretta do Gêmeos", "aiCapabilityGeminiGenerateContentDescription": "Suporta a API diretta do Gêmeos",
"aiCapabilityBedrockModelInvoke": "Modelo Bedrock Invocar", "aiCapabilityBedrockModelInvoke": "Modelo Bedrock Invocar",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Logs de Ações", "sidebarLogsAction": "Logs de Ações",
"logRetention": "Retenção de Log", "logRetention": "Retenção de Log",
"logRetentionDescription": "Gerenciar quanto tempo os diferentes tipos de logs são mantidos para esta organização ou desativá-los", "logRetentionDescription": "Gerenciar quanto tempo os diferentes tipos de logs são mantidos para esta organização ou desativá-los",
"logRetentionDisabledWarningTitle": "Retenção de Logs Desativada",
"logRetentionDisabledWarningDescription": "{logType} não estão sendo retidos para esta organização, portanto, novas atividades não aparecerão aqui. Ative a retenção nas configurações de segurança para começar a coletar esses logs.",
"logRetentionDisabledWarningButton": "Ir para Configurações de Segurança",
"requestLogsDescription": "Ver registros de pedidos detalhados de recursos nesta organização", "requestLogsDescription": "Ver registros de pedidos detalhados de recursos nesta organização",
"aiSessionLogs": "Registros de Sessão do Gateway de IA", "aiSessionLogs": "Registros de Sessão do Gateway de IA",
"aiSessionLogsDescription": "Veja as transcrições de prompt e resposta para solicitações de gateway de IA nesta organização", "aiSessionLogsDescription": "Veja as transcrições de prompt e resposta para solicitações de gateway de IA nesta organização",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Eventos de conexão de site e túnel, incluindo conexões e desconexões.", "httpDestConnectionLogsDescription": "Eventos de conexão de site e túnel, incluindo conexões e desconexões.",
"httpDestRequestLogsTitle": "Registros de Pedidos HTTP", "httpDestRequestLogsTitle": "Registros de Pedidos HTTP",
"httpDestRequestLogsDescription": "Logs de solicitação HTTP para recursos proxy incluindo o método, o caminho e o código de resposta.", "httpDestRequestLogsDescription": "Logs de solicitação HTTP para recursos proxy incluindo o método, o caminho e o código de resposta.",
"httpDestAISessionLogsTitle": "Logs de Sessão de IA",
"httpDestAISessionLogsDescription": "Sessões de solicitação e resposta de gateway de IA, incluindo prompts, respostas de modelos e uso de tokens.",
"httpDestSaveChanges": "Salvar as alterações", "httpDestSaveChanges": "Salvar as alterações",
"httpDestCreateDestination": "Criar destino", "httpDestCreateDestination": "Criar destino",
"httpDestUpdatedSuccess": "Destino atualizado com sucesso", "httpDestUpdatedSuccess": "Destino atualizado com sucesso",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Агентивное кодирующее средство Anthropic для терминала.", "aiClientConfigDescriptionClaude": "Агентивное кодирующее средство Anthropic для терминала.",
"aiClientConfigDescriptionCodex": "Агентивное кодирующее средство OpenAI для терминала.", "aiClientConfigDescriptionCodex": "Агентивное кодирующее средство OpenAI для терминала.",
"aiClientConfigDescriptionOpencode": "Открытый исходный агент для кодирования в терминале.", "aiClientConfigDescriptionOpencode": "Открытый исходный агент для кодирования в терминале.",
"aiClientConfigDescriptionGemini": "Инструмент программирования Google для терминала.", "aiClientConfigDescriptionCursor": "AI редактор кода на основе VS Code.",
"aiClientConfigSetup": "Настройка", "aiClientConfigSetup": "Настройка",
"aiClientConfigTabCli": "Автоматическое (CLI)", "aiClientConfigTabCli": "Автоматическое (CLI)",
"aiClientConfigTabManual": "Ручная конфигурация", "aiClientConfigTabManual": "Ручная конфигурация",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Маршрутизация через цели на ваших сайтах", "aiProviderRoutingModeTargetDescription": "Маршрутизация через цели на ваших сайтах",
"aiProviderRoutingModeTargetNote": "После создания этого провайдера настройте целевые сайты на вкладке Сетевые настройки.", "aiProviderRoutingModeTargetNote": "После создания этого провайдера настройте целевые сайты на вкладке Сетевые настройки.",
"aiProviderTargetNoOne": "У этого провайдера нет целей. Добавьте цель для маршрутизации запросов через ваши сайты.", "aiProviderTargetNoOne": "У этого провайдера нет целей. Добавьте цель для маршрутизации запросов через ваши сайты.",
"aiProviderRemoteNodeTargetsWarning": "Сайты, подключенные к удалённым узлам, недоступны для маршрутизации с помощью провайдеров AI Gateway.",
"aiProviderSkipTlsVerification": "Пропустить проверку TLS", "aiProviderSkipTlsVerification": "Пропустить проверку TLS",
"aiProviderSkipTlsVerificationDescription": "Отключить проверку сертификата TLS для исходного соединения", "aiProviderSkipTlsVerificationDescription": "Отключить проверку сертификата TLS для исходного соединения",
"aiProviderBudget": "Бюджет", "aiProviderBudget": "Бюджет",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "Поддерживает /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Поддерживает /v1/responses",
"aiCapabilityAnthropicMessages": "Сообщения Anthropic", "aiCapabilityAnthropicMessages": "Сообщения Anthropic",
"aiCapabilityAnthropicMessagesDescription": "Поддерживает /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Поддерживает /v1/messages",
"aiCapabilityV1Models": "Список моделей",
"aiCapabilityV1ModelsDescription": "Поддерживает обнаружение моделей /v1/models",
"aiCapabilityGeminiGenerateContent": "Gemini Создание контента", "aiCapabilityGeminiGenerateContent": "Gemini Создание контента",
"aiCapabilityGeminiGenerateContentDescription": "Поддерживает прямой API Gemini", "aiCapabilityGeminiGenerateContentDescription": "Поддерживает прямой API Gemini",
"aiCapabilityBedrockModelInvoke": "Вызов модели Bedrock", "aiCapabilityBedrockModelInvoke": "Вызов модели Bedrock",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Журнал действий", "sidebarLogsAction": "Журнал действий",
"logRetention": "Сохранение журнала", "logRetention": "Сохранение журнала",
"logRetentionDescription": "Управление сохранением различных типов журналов для этой организации или отключение их", "logRetentionDescription": "Управление сохранением различных типов журналов для этой организации или отключение их",
"logRetentionDisabledWarningTitle": "Хранение логов отключено",
"logRetentionDisabledWarningDescription": "Логи {logType} не сохраняются для этой организации, поэтому здесь не будет отображаться новая активность. Включите хранение в настройках безопасности, чтобы начать собирать эти логи.",
"logRetentionDisabledWarningButton": "Перейти в настройки безопасности",
"requestLogsDescription": "Просмотреть подробные журналы запроса ресурсов в этой организации", "requestLogsDescription": "Просмотреть подробные журналы запроса ресурсов в этой организации",
"aiSessionLogs": "AI Логи сессии шлюза", "aiSessionLogs": "AI Логи сессии шлюза",
"aiSessionLogsDescription": "Просмотр транскриптов запросов и ответов для шлюзов AI в этой организации", "aiSessionLogsDescription": "Просмотр транскриптов запросов и ответов для шлюзов AI в этой организации",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "События связи с сайтами и туннелями, включая соединения и отключения.", "httpDestConnectionLogsDescription": "События связи с сайтами и туннелями, включая соединения и отключения.",
"httpDestRequestLogsTitle": "HTTP Запросы Логи", "httpDestRequestLogsTitle": "HTTP Запросы Логи",
"httpDestRequestLogsDescription": "Журналы запросов HTTP для проксируемых ресурсов, включая метод, путь и код ответа.", "httpDestRequestLogsDescription": "Журналы запросов HTTP для проксируемых ресурсов, включая метод, путь и код ответа.",
"httpDestAISessionLogsTitle": "Логи AI сессий",
"httpDestAISessionLogsDescription": "Запросы и ответы AI gateway, включая подсказки, ответы моделей и использование токенов.",
"httpDestSaveChanges": "Сохранить изменения", "httpDestSaveChanges": "Сохранить изменения",
"httpDestCreateDestination": "Создать адрес назначения", "httpDestCreateDestination": "Создать адрес назначения",
"httpDestUpdatedSuccess": "Адрес назначения успешно обновлен", "httpDestUpdatedSuccess": "Адрес назначения успешно обновлен",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Anthropic'in terminal için aracılık kodlama aracı.", "aiClientConfigDescriptionClaude": "Anthropic'in terminal için aracılık kodlama aracı.",
"aiClientConfigDescriptionCodex": "OpenAI'nin terminal için aracılık kodlama aracı.", "aiClientConfigDescriptionCodex": "OpenAI'nin terminal için aracılık kodlama aracı.",
"aiClientConfigDescriptionOpencode": "Açık kaynak terminal kodlama aracı.", "aiClientConfigDescriptionOpencode": "Açık kaynak terminal kodlama aracı.",
"aiClientConfigDescriptionGemini": "Google'un terminal için agentik kodlama aracı.", "aiClientConfigDescriptionCursor": "VS Code üzerine kurulu yapay zeka kod editörü.",
"aiClientConfigSetup": "Kurulum", "aiClientConfigSetup": "Kurulum",
"aiClientConfigTabCli": "Otomatik (CLI)", "aiClientConfigTabCli": "Otomatik (CLI)",
"aiClientConfigTabManual": "Manuel Yapılandırma", "aiClientConfigTabManual": "Manuel Yapılandırma",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "Siteniz üzerindeki hedefler üzerinden yönlendirin", "aiProviderRoutingModeTargetDescription": "Siteniz üzerindeki hedefler üzerinden yönlendirin",
"aiProviderRoutingModeTargetNote": "Bu sağlayıcıyı oluşturduktan sonra, site hedeflerini Ağ Ayarları sekmesinde yapılandırın.", "aiProviderRoutingModeTargetNote": "Bu sağlayıcıyı oluşturduktan sonra, site hedeflerini Ağ Ayarları sekmesinde yapılandırın.",
"aiProviderTargetNoOne": "Bu sağlayıcının herhangi bir hedefi yok. Sitemiz üzerinden istekleri yönlendirmek için bir hedef ekleyin.", "aiProviderTargetNoOne": "Bu sağlayıcının herhangi bir hedefi yok. Sitemiz üzerinden istekleri yönlendirmek için bir hedef ekleyin.",
"aiProviderRemoteNodeTargetsWarning": "Uzaktaki düğümlere bağlı siteler, AI Geçidi sağlayıcılarına yönlendirilemez durumda.",
"aiProviderSkipTlsVerification": "TLS Doğrulamayı Atla", "aiProviderSkipTlsVerification": "TLS Doğrulamayı Atla",
"aiProviderSkipTlsVerificationDescription": "Yukarı akış bağlantısı için TLS sertifika doğrulamasını devre dışı bırakın", "aiProviderSkipTlsVerificationDescription": "Yukarı akış bağlantısı için TLS sertifika doğrulamasını devre dışı bırakın",
"aiProviderBudget": "Bütçe", "aiProviderBudget": "Bütçe",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "/v1/yanıtlar desteği sağlar", "aiCapabilityOpenaiResponsesDescription": "/v1/yanıtlar desteği sağlar",
"aiCapabilityAnthropicMessages": "Anthropic Mesajlar", "aiCapabilityAnthropicMessages": "Anthropic Mesajlar",
"aiCapabilityAnthropicMessagesDescription": "/v1/mesajlar desteği sağlar", "aiCapabilityAnthropicMessagesDescription": "/v1/mesajlar desteği sağlar",
"aiCapabilityV1Models": "Modeller Listesi",
"aiCapabilityV1ModelsDescription": "T /v1/models model keşfini destekler",
"aiCapabilityGeminiGenerateContent": "Gemini İçerik Üret", "aiCapabilityGeminiGenerateContent": "Gemini İçerik Üret",
"aiCapabilityGeminiGenerateContentDescription": "Doğrudan Gemini API desteği sağlar", "aiCapabilityGeminiGenerateContentDescription": "Doğrudan Gemini API desteği sağlar",
"aiCapabilityBedrockModelInvoke": "Bedrock Modeli Çağır", "aiCapabilityBedrockModelInvoke": "Bedrock Modeli Çağır",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "Eylem Günlükleri", "sidebarLogsAction": "Eylem Günlükleri",
"logRetention": "Kayıt Saklama", "logRetention": "Kayıt Saklama",
"logRetentionDescription": "Bu organizasyon için farklı türdeki günlüklerin ne kadar süre saklanacağını yönetin veya devre dışı bırakın", "logRetentionDescription": "Bu organizasyon için farklı türdeki günlüklerin ne kadar süre saklanacağını yönetin veya devre dışı bırakın",
"logRetentionDisabledWarningTitle": "Günlük Saklama Devre Dışı Bırakıldı",
"logRetentionDisabledWarningDescription": "{logType} bu organizasyon için saklanmıyor, bu nedenle yeni etkinlikler burada görünmeyecek. Bu günlükleri toplamak için güvenlik ayarlarında saklamayı etkinleştirin.",
"logRetentionDisabledWarningButton": "Güvenlik Ayarlarına Git",
"requestLogsDescription": "Bu organizasyondaki kaynaklar için ayrıntılı istek günlüklerini görüntüleyin", "requestLogsDescription": "Bu organizasyondaki kaynaklar için ayrıntılı istek günlüklerini görüntüleyin",
"aiSessionLogs": "AI Ağ Geçidi Oturum Günlükleri", "aiSessionLogs": "AI Ağ Geçidi Oturum Günlükleri",
"aiSessionLogsDescription": "Bu organizasyondaki AI ağ geçidi isteklerinin istem ve yanıt transkriptlerini görüntüleyin", "aiSessionLogsDescription": "Bu organizasyondaki AI ağ geçidi isteklerinin istem ve yanıt transkriptlerini görüntüleyin",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "Site ve tünel bağlantı olayları, bağlantılar ve bağlantı kesilmeleri dahil.", "httpDestConnectionLogsDescription": "Site ve tünel bağlantı olayları, bağlantılar ve bağlantı kesilmeleri dahil.",
"httpDestRequestLogsTitle": "HTTP İstek Günlükleri", "httpDestRequestLogsTitle": "HTTP İstek Günlükleri",
"httpDestRequestLogsDescription": "Yönlendirilmiş kaynaklar için HTTP istek kayıtları, yöntem, yol ve yanıt kodu dahil.", "httpDestRequestLogsDescription": "Yönlendirilmiş kaynaklar için HTTP istek kayıtları, yöntem, yol ve yanıt kodu dahil.",
"httpDestAISessionLogsTitle": "AI Oturum Günlükleri",
"httpDestAISessionLogsDescription": "AI geçidi istek ve yanıt oturumları, istemler, model yanıtları ve token kullanımı dahil.",
"httpDestSaveChanges": "Değişiklikleri Kaydet", "httpDestSaveChanges": "Değişiklikleri Kaydet",
"httpDestCreateDestination": "Hedef Oluştur", "httpDestCreateDestination": "Hedef Oluştur",
"httpDestUpdatedSuccess": "Hedef başarıyla güncellendi", "httpDestUpdatedSuccess": "Hedef başarıyla güncellendi",
+1 -9
View File
@@ -1785,7 +1785,7 @@
"aiClientConfigDescriptionClaude": "Anthropic 的终端代理编码工具。", "aiClientConfigDescriptionClaude": "Anthropic 的终端代理编码工具。",
"aiClientConfigDescriptionCodex": "OpenAI 的终端代理编码工具。", "aiClientConfigDescriptionCodex": "OpenAI 的终端代理编码工具。",
"aiClientConfigDescriptionOpencode": "开源终端编码代理。", "aiClientConfigDescriptionOpencode": "开源终端编码代理。",
"aiClientConfigDescriptionGemini": "Google的终端代理编码工具。", "aiClientConfigDescriptionCursor": "基于 VS Code 的 AI 代码编辑器。",
"aiClientConfigSetup": "设置", "aiClientConfigSetup": "设置",
"aiClientConfigTabCli": "自动 (CLI)", "aiClientConfigTabCli": "自动 (CLI)",
"aiClientConfigTabManual": "手动配置", "aiClientConfigTabManual": "手动配置",
@@ -1891,7 +1891,6 @@
"aiProviderRoutingModeTargetDescription": "通过您站点上的目标进行路由", "aiProviderRoutingModeTargetDescription": "通过您站点上的目标进行路由",
"aiProviderRoutingModeTargetNote": "创建此提供商后,在“网络设置”选项卡中配置站点目标。", "aiProviderRoutingModeTargetNote": "创建此提供商后,在“网络设置”选项卡中配置站点目标。",
"aiProviderTargetNoOne": "该提供商没有任何目标。 添加目标以通过您的站点路由请求。", "aiProviderTargetNoOne": "该提供商没有任何目标。 添加目标以通过您的站点路由请求。",
"aiProviderRemoteNodeTargetsWarning": "连接到远程节点的站点无法在AI网关供应商上被路由。",
"aiProviderSkipTlsVerification": "跳过TLS验证", "aiProviderSkipTlsVerification": "跳过TLS验证",
"aiProviderSkipTlsVerificationDescription": "禁用上游连接的TLS证书验证", "aiProviderSkipTlsVerificationDescription": "禁用上游连接的TLS证书验证",
"aiProviderBudget": "预算", "aiProviderBudget": "预算",
@@ -1924,8 +1923,6 @@
"aiCapabilityOpenaiResponsesDescription": "支持 /v1/responses", "aiCapabilityOpenaiResponsesDescription": "支持 /v1/responses",
"aiCapabilityAnthropicMessages": "Anthropic 消息", "aiCapabilityAnthropicMessages": "Anthropic 消息",
"aiCapabilityAnthropicMessagesDescription": "支持 /v1/messages", "aiCapabilityAnthropicMessagesDescription": "支持 /v1/messages",
"aiCapabilityV1Models": "模型列表",
"aiCapabilityV1ModelsDescription": "支持/v1/models模型发现",
"aiCapabilityGeminiGenerateContent": "Gemini 生成内容", "aiCapabilityGeminiGenerateContent": "Gemini 生成内容",
"aiCapabilityGeminiGenerateContentDescription": "支持直接Gemini API", "aiCapabilityGeminiGenerateContentDescription": "支持直接Gemini API",
"aiCapabilityBedrockModelInvoke": "Bedrock 模型调用", "aiCapabilityBedrockModelInvoke": "Bedrock 模型调用",
@@ -3551,9 +3548,6 @@
"sidebarLogsAction": "操作日志", "sidebarLogsAction": "操作日志",
"logRetention": "日志保留", "logRetention": "日志保留",
"logRetentionDescription": "管理不同类型的日志为这个机构保留多长时间或禁用这些日志", "logRetentionDescription": "管理不同类型的日志为这个机构保留多长时间或禁用这些日志",
"logRetentionDisabledWarningTitle": "日志保留已禁用",
"logRetentionDisabledWarningDescription": "{logType}未在此组织中被保留,因此新活动不会显示在此处。请在安全设置中启用日志保留以开始收集这些日志。",
"logRetentionDisabledWarningButton": "转到安全设置",
"requestLogsDescription": "查看此机构资源的详细请求日志", "requestLogsDescription": "查看此机构资源的详细请求日志",
"aiSessionLogs": "AI 网关会话日志", "aiSessionLogs": "AI 网关会话日志",
"aiSessionLogsDescription": "查看此组织中AI网关请求的提示和响应记录", "aiSessionLogsDescription": "查看此组织中AI网关请求的提示和响应记录",
@@ -4087,8 +4081,6 @@
"httpDestConnectionLogsDescription": "站点和隧道连接事件,包括连接和断开连接。", "httpDestConnectionLogsDescription": "站点和隧道连接事件,包括连接和断开连接。",
"httpDestRequestLogsTitle": "请求日志", "httpDestRequestLogsTitle": "请求日志",
"httpDestRequestLogsDescription": "HTTP 请求代理资源日志,包括方法、路径和响应代码。", "httpDestRequestLogsDescription": "HTTP 请求代理资源日志,包括方法、路径和响应代码。",
"httpDestAISessionLogsTitle": "AI会话日志",
"httpDestAISessionLogsDescription": "AI网关请求和响应会话,包括提示、模型响应和令牌使用。",
"httpDestSaveChanges": "保存更改", "httpDestSaveChanges": "保存更改",
"httpDestCreateDestination": "创建目标", "httpDestCreateDestination": "创建目标",
"httpDestUpdatedSuccess": "目标已成功更新", "httpDestUpdatedSuccess": "目标已成功更新",
+2213 -788
View File
File diff suppressed because it is too large Load Diff
+2340 -3584
View File
File diff suppressed because it is too large Load Diff
+75 -75
View File
@@ -32,49 +32,49 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@asteasolutions/zod-to-openapi": "8.5.0", "@asteasolutions/zod-to-openapi": "9.1.0",
"@aws-sdk/client-s3": "3.1056.0", "@aws-sdk/client-s3": "3.1116.0",
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz", "@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz", "@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
"@headlessui/react": "2.2.10", "@headlessui/react": "2.2.10",
"@hookform/resolvers": "5.4.0", "@hookform/resolvers": "5.9.1",
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "4.7.0",
"@node-rs/argon2": "2.0.2", "@node-rs/argon2": "2.1.0",
"@novnc/novnc": "^1.7.0", "@novnc/novnc": "^1.7.0",
"@oslojs/crypto": "1.0.1", "@oslojs/crypto": "1.0.1",
"@oslojs/encoding": "1.1.0", "@oslojs/encoding": "1.1.0",
"@radix-ui/react-avatar": "1.1.11", "@radix-ui/react-avatar": "1.2.6",
"@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-checkbox": "1.3.11",
"@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collapsible": "1.1.20",
"@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-dialog": "1.1.23",
"@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-dropdown-menu": "2.1.24",
"@radix-ui/react-icons": "1.3.2", "@radix-ui/react-icons": "1.3.2",
"@radix-ui/react-label": "2.1.8", "@radix-ui/react-label": "2.1.15",
"@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popover": "1.1.23",
"@radix-ui/react-progress": "1.1.8", "@radix-ui/react-progress": "1.1.16",
"@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-radio-group": "1.4.7",
"@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-scroll-area": "1.2.18",
"@radix-ui/react-select": "2.2.6", "@radix-ui/react-select": "2.3.7",
"@radix-ui/react-separator": "1.1.8", "@radix-ui/react-separator": "1.1.15",
"@radix-ui/react-slot": "1.2.4", "@radix-ui/react-slot": "1.3.3",
"@radix-ui/react-switch": "1.2.6", "@radix-ui/react-switch": "1.3.7",
"@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-tabs": "1.1.21",
"@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toast": "1.2.23",
"@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-tooltip": "1.2.16",
"@react-email/body": "0.3.0", "@react-email/body": "0.3.0",
"@react-email/components": "1.0.12", "@react-email/components": "1.0.12",
"@react-email/render": "2.0.8", "@react-email/render": "2.1.0",
"@react-email/tailwind": "2.0.7", "@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0", "@simplewebauthn/browser": "13.3.0",
"@simplewebauthn/server": "13.3.1", "@simplewebauthn/server": "13.3.2",
"@tailwindcss/forms": "0.5.11", "@tailwindcss/forms": "0.5.11",
"@tanstack/react-query": "5.100.14", "@tanstack/react-query": "5.101.4",
"@tanstack/react-table": "8.21.3", "@tanstack/react-table": "9.1.2",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0", "@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0", "@xterm/xterm": "^6.0.0",
"arctic": "3.7.0", "arctic": "3.7.0",
"axios": "1.18.0", "axios": "1.19.0",
"better-sqlite3": "11.9.1", "better-sqlite3": "11.9.1",
"canvas-confetti": "1.9.4", "canvas-confetti": "1.9.4",
"class-variance-authority": "0.7.1", "class-variance-authority": "0.7.1",
@@ -86,62 +86,62 @@
"d3": "7.9.0", "d3": "7.9.0",
"drizzle-orm": "0.45.2", "drizzle-orm": "0.45.2",
"express": "5.2.1", "express": "5.2.1",
"express-rate-limit": "8.5.2", "express-rate-limit": "8.6.2",
"glob": "13.0.6", "glob": "13.0.6",
"gpt-tokenizer": "^3.4.0", "gpt-tokenizer": "^4.0.0",
"helmet": "8.2.0", "helmet": "8.3.0",
"http-errors": "2.0.1", "http-errors": "2.0.1",
"input-otp": "1.4.2", "input-otp": "1.5.0",
"ioredis": "5.11.0", "ioredis": "6.0.0",
"jmespath": "0.16.0", "jmespath": "0.16.0",
"js-yaml": "4.3.0", "js-yaml": "5.3.0",
"jsonwebtoken": "9.0.3", "jsonwebtoken": "9.0.3",
"lucide-react": "1.17.0", "lucide-react": "1.33.0",
"maxmind": "5.0.6", "maxmind": "5.0.7",
"moment": "2.30.1", "moment": "2.30.1",
"next": "16.2.11", "next": "16.3.2",
"next-intl": "4.13.0", "next-intl": "4.13.7",
"next-themes": "0.4.6", "next-themes": "0.4.6",
"nextjs-toploader": "3.9.17", "nextjs-toploader": "3.9.17",
"node-cache": "5.1.2", "node-cache": "5.1.2",
"nodemailer": "9.0.1", "nodemailer": "9.0.5",
"oslo": "1.2.1", "oslo": "1.2.1",
"pg": "8.21.0", "pg": "8.23.0",
"posthog-node": "5.35.6", "posthog-node": "5.50.0",
"qrcode.react": "4.2.0", "qrcode.react": "4.2.0",
"react": "19.2.6", "react": "19.2.8",
"react-day-picker": "9.14.0", "react-day-picker": "10.0.1",
"react-dom": "19.2.6", "react-dom": "19.2.8",
"react-easy-sort": "1.8.0", "react-easy-sort": "1.8.0",
"react-hook-form": "7.76.1", "react-hook-form": "7.86.0",
"react-icons": "5.6.0", "react-icons": "5.7.0",
"recharts": "3.8.1", "recharts": "3.10.1",
"reodotdev": "1.1.0", "reodotdev": "1.1.0",
"semver": "7.8.1", "semver": "7.8.5",
"sshpk": "1.18.0", "sshpk": "1.18.0",
"stripe": "22.2.0", "stripe": "22.5.0",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0", "tailwind-merge": "3.6.0",
"topojson-client": "3.1.0", "topojson-client": "3.1.0",
"tw-animate-css": "1.4.0", "tw-animate-css": "1.4.0",
"use-debounce": "10.1.1", "use-debounce": "10.1.1",
"uuid": "14.0.0", "uuid": "14.0.2",
"vaul": "1.1.2", "vaul": "1.1.2",
"visionscarto-world-atlas": "1.0.0", "visionscarto-world-atlas": "1.0.0",
"winston": "3.19.0", "winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0", "winston-daily-rotate-file": "5.0.0",
"ws": "8.21.0", "ws": "8.21.3",
"yaml": "2.9.0", "yaml": "2.9.0",
"yargs": "18.0.0", "yargs": "18.1.0",
"zod": "4.4.3", "zod": "4.4.3",
"zod-validation-error": "5.0.0" "zod-validation-error": "5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@dotenvx/dotenvx": "1.69.1", "@dotenvx/dotenvx": "2.21.0",
"@esbuild-plugins/tsconfig-paths": "0.1.2", "@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/ui": "^6.5.0", "@react-email/ui": "^6.9.2",
"@tailwindcss/postcss": "4.3.0", "@tailwindcss/postcss": "4.3.3",
"@tanstack/react-query-devtools": "5.100.14", "@tanstack/react-query-devtools": "5.101.4",
"@types/better-sqlite3": "7.6.13", "@types/better-sqlite3": "7.6.13",
"@types/cookie-parser": "1.4.10", "@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19", "@types/cors": "2.8.19",
@@ -152,36 +152,36 @@
"@types/jmespath": "0.15.2", "@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10", "@types/jsonwebtoken": "9.0.10",
"@types/node": "25.9.1", "@types/node": "26.2.0",
"@types/nodemailer": "8.0.0", "@types/nodemailer": "8.0.1",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@types/pg": "8.20.0", "@types/pg": "8.23.1",
"@types/react": "19.2.15", "@types/react": "19.2.18",
"@types/react-dom": "19.2.3", "@types/react-dom": "19.2.4",
"@types/semver": "7.7.1", "@types/semver": "7.8.0",
"@types/sshpk": "1.17.4", "@types/sshpk": "1.17.5",
"@types/swagger-ui-express": "4.1.8", "@types/swagger-ui-express": "4.1.8",
"@types/topojson-client": "3.1.5", "@types/topojson-client": "3.1.5",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@types/yargs": "17.0.35", "@types/yargs": "17.0.35",
"babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-compiler": "1.0.0",
"drizzle-kit": "0.31.10", "drizzle-kit": "0.31.10",
"esbuild": "0.28.0", "esbuild": "0.28.2",
"esbuild-node-externals": "1.22.0", "esbuild-node-externals": "2.0.0",
"eslint": "10.4.0", "eslint": "10.9.0",
"eslint-config-next": "16.2.6", "eslint-config-next": "16.3.2",
"postcss": "8.5.15", "postcss": "8.5.26",
"prettier": "3.8.3", "prettier": "3.9.6",
"react-email": "6.5.0", "react-email": "6.9.2",
"tailwindcss": "4.3.0", "tailwindcss": "4.3.3",
"tsc-alias": "1.8.17", "tsc-alias": "1.9.2",
"tsx": "4.22.3", "tsx": "4.23.12",
"typescript": "6.0.3", "typescript": "7.0.2",
"typescript-eslint": "8.60.0" "typescript-eslint": "8.67.0"
}, },
"overrides": { "overrides": {
"esbuild": "0.28.0", "esbuild": "0.28.2",
"dompurify": "3.4.0", "dompurify": "3.4.0",
"postcss": "8.5.15" "postcss": "8.5.26"
} }
} }
-3
View File
@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 413 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 413 B

-3
View File
@@ -468,9 +468,6 @@ export const eventStreamingDestinations = pgTable(
sendRequestLogs: boolean("sendRequestLogs").notNull().default(false), sendRequestLogs: boolean("sendRequestLogs").notNull().default(false),
sendActionLogs: boolean("sendActionLogs").notNull().default(false), sendActionLogs: boolean("sendActionLogs").notNull().default(false),
sendAccessLogs: boolean("sendAccessLogs").notNull().default(false), sendAccessLogs: boolean("sendAccessLogs").notNull().default(false),
sendAISessionLogs: boolean("sendAISessionLogs")
.notNull()
.default(false),
type: varchar("type", { length: 50 }).notNull(), // e.g. "http", "kafka", etc. type: varchar("type", { length: 50 }).notNull(), // e.g. "http", "kafka", etc.
config: text("config").notNull(), // JSON string with the configuration for the destination config: text("config").notNull(), // JSON string with the configuration for the destination
enabled: boolean("enabled").notNull().default(true), enabled: boolean("enabled").notNull().default(true),
-3
View File
@@ -459,9 +459,6 @@ export const eventStreamingDestinations = sqliteTable(
sendAccessLogs: integer("sendAccessLogs", { mode: "boolean" }) sendAccessLogs: integer("sendAccessLogs", { mode: "boolean" })
.notNull() .notNull()
.default(false), .default(false),
sendAISessionLogs: integer("sendAISessionLogs", { mode: "boolean" })
.notNull()
.default(false),
type: text("type").notNull(), // e.g. "http", "kafka", etc. type: text("type").notNull(), // e.g. "http", "kafka", etc.
config: text("config").notNull(), // JSON string with the configuration for the destination config: text("config").notNull(), // JSON string with the configuration for the destination
enabled: integer("enabled", { mode: "boolean" }) enabled: integer("enabled", { mode: "boolean" })
+1 -16
View File
@@ -4,7 +4,7 @@ import { AI_CAPABILITIES, type AiCapability } from "@app/lib/aiCapabilities";
export { AI_CAPABILITIES, type AiCapability }; export { AI_CAPABILITIES, type AiCapability };
export type AiCapabilityRoute = { export type AiCapabilityRoute = {
method: "GET" | "POST"; method: "POST";
path: string; path: string;
}; };
@@ -135,21 +135,6 @@ export const AI_CAPABILITY_DEFS: Record<AiCapability, AiCapabilityDefinition> =
joinUpstreamUrl(base, pathFromRequest(req)), joinUpstreamUrl(base, pathFromRequest(req)),
isStreaming: isBodyOrSseStreaming isStreaming: isBodyOrSseStreaming
}, },
v1_models: {
id: "v1_models",
protocolFamily: "anthropic",
routes: [
{ method: "GET", path: "/v1/models" },
{ method: "GET", path: "/v1/models/:model" }
],
extractModel: paramModel,
resolveUpstreamUrl: (base, req) =>
joinUpstreamUrl(base, pathFromRequest(req)),
// Model listings are answered from the gateway's own view of the
// provider allow/block lists rather than proxied upstream, so
// there is never a stream to detect.
isStreaming: () => false
},
gemini_generate_content: { gemini_generate_content: {
id: "gemini_generate_content", id: "gemini_generate_content",
protocolFamily: "google", protocolFamily: "google",
-3
View File
@@ -471,8 +471,6 @@ const REQUEST_NORMALIZERS: Record<
openai_chat: normalizeOpenAiChatRequest, openai_chat: normalizeOpenAiChatRequest,
openai_responses: normalizeOpenAiResponsesRequest, openai_responses: normalizeOpenAiResponsesRequest,
anthropic_messages: normalizeAnthropicRequest, anthropic_messages: normalizeAnthropicRequest,
// Model discovery carries no transcript to normalize.
v1_models: () => null,
gemini_generate_content: normalizeGeminiRequest, gemini_generate_content: normalizeGeminiRequest,
google_generate_content: normalizeGeminiRequest, google_generate_content: normalizeGeminiRequest,
google_raw_predict: normalizeBestEffortRequest, google_raw_predict: normalizeBestEffortRequest,
@@ -487,7 +485,6 @@ const RESPONSE_NORMALIZERS: Record<
openai_chat: normalizeOpenAiChatResponse, openai_chat: normalizeOpenAiChatResponse,
openai_responses: normalizeOpenAiResponsesResponse, openai_responses: normalizeOpenAiResponsesResponse,
anthropic_messages: normalizeAnthropicResponse, anthropic_messages: normalizeAnthropicResponse,
v1_models: () => null,
gemini_generate_content: normalizeGeminiResponse, gemini_generate_content: normalizeGeminiResponse,
google_generate_content: normalizeGeminiResponse, google_generate_content: normalizeGeminiResponse,
google_raw_predict: normalizeGoogleRawPredictResponse, google_raw_predict: normalizeGoogleRawPredictResponse,
+24 -88
View File
@@ -44,20 +44,6 @@ export function getCatalogProviderForType(
return PROVIDER_CATALOG_MAP[type]; return PROVIDER_CATALOG_MAP[type];
} }
/**
* Per-model feature flags as reported upstream. `null` means the catalog has
* no data for that model - deliberately distinct from `false`, so consumers
* can tell "unsupported" apart from "unknown".
*/
export type AiModelCapabilityFlags = {
functionCalling: boolean | null;
vision: boolean | null;
promptCaching: boolean | null;
reasoning: boolean | null;
responseSchema: boolean | null;
webSearch: boolean | null;
};
export type AiModelCatalogEntry = { export type AiModelCatalogEntry = {
provider: CatalogProvider; provider: CatalogProvider;
model: string; model: string;
@@ -67,20 +53,8 @@ export type AiModelCatalogEntry = {
cache: number | null; cache: number | null;
reasoning: number | null; reasoning: number | null;
}; };
limits: {
/** Context window. */
input: number | null;
/** Cap on the output/max_tokens request parameter. */
output: number | null;
};
capabilities: AiModelCapabilityFlags;
}; };
const flag = z.boolean().nullable().optional();
// limits/capabilities are optional so a catalog published before they were
// added (or an operator's own merge_file) still parses - those entries just
// report unknown metadata rather than failing the whole payload.
const catalogEntrySchema = z.object({ const catalogEntrySchema = z.object({
model: z.string(), model: z.string(),
provider: z.string(), provider: z.string(),
@@ -91,22 +65,6 @@ const catalogEntrySchema = z.object({
cache: z.number().nullable().optional(), cache: z.number().nullable().optional(),
reasoning: z.number().nullable().optional() reasoning: z.number().nullable().optional()
}) })
.optional(),
limits: z
.object({
input: z.number().nullable().optional(),
output: z.number().nullable().optional()
})
.optional(),
capabilities: z
.object({
functionCalling: flag,
vision: flag,
promptCaching: flag,
reasoning: flag,
responseSchema: flag,
webSearch: flag
})
.optional() .optional()
}); });
@@ -150,18 +108,6 @@ function normalizeEntry(raw: RawCatalogEntry): AiModelCatalogEntry | null {
out: raw.pricing?.out ?? null, out: raw.pricing?.out ?? null,
cache: raw.pricing?.cache ?? null, cache: raw.pricing?.cache ?? null,
reasoning: raw.pricing?.reasoning ?? null reasoning: raw.pricing?.reasoning ?? null
},
limits: {
input: raw.limits?.input ?? null,
output: raw.limits?.output ?? null
},
capabilities: {
functionCalling: raw.capabilities?.functionCalling ?? null,
vision: raw.capabilities?.vision ?? null,
promptCaching: raw.capabilities?.promptCaching ?? null,
reasoning: raw.capabilities?.reasoning ?? null,
responseSchema: raw.capabilities?.responseSchema ?? null,
webSearch: raw.capabilities?.webSearch ?? null
} }
}; };
} }
@@ -338,44 +284,34 @@ export class AiModelCatalog {
export const aiModelCatalog = new AiModelCatalog(); export const aiModelCatalog = new AiModelCatalog();
/**
* Full catalog entries for a provider type, deduplicated by model id and
* sorted by id. Model discovery uses these to report real token limits and
* capability flags; `listCatalogModelsForType` is the id-only view of the
* same list.
*/
export function listCatalogEntriesForType(
type: AiProviderType,
query?: string
): AiModelCatalogEntry[] {
const catalogProvider = getCatalogProviderForType(type);
let entries = catalogProvider ? aiModelCatalog.list(catalogProvider) : [];
if (query) {
const q = query.toLowerCase();
entries = entries.filter((e) => e.model.toLowerCase().includes(q));
}
const seen = new Set<string>();
entries = entries.filter((e) => {
if (seen.has(e.model)) {
return false;
}
seen.add(e.model);
return true;
});
return [...entries].sort((a, b) => a.model.localeCompare(b.model));
}
export function listCatalogModelsForType( export function listCatalogModelsForType(
type: AiProviderType, type: AiProviderType,
query?: string query?: string
): { model: string }[] { ): { model: string }[] {
return listCatalogEntriesForType(type, query).map((entry) => ({ const catalogProvider = getCatalogProviderForType(type);
model: entry.model
})); let models = catalogProvider
? aiModelCatalog.list(catalogProvider).map((entry) => ({
model: entry.model
}))
: [];
if (query) {
const q = query.toLowerCase();
models = models.filter((m) => m.model.toLowerCase().includes(q));
}
const seen = new Set<string>();
models = models.filter((m) => {
if (seen.has(m.model)) {
return false;
}
seen.add(m.model);
return true;
});
models.sort((a, b) => a.model.localeCompare(b.model));
return models;
} }
/** /**
-235
View File
@@ -1,235 +0,0 @@
import {
isAllowedByLists,
isModelKeyPattern
} from "@server/lib/aiModelKeyMatch";
import type { AiModelCapabilityFlags } from "@server/lib/aiModelCatalog";
// Anthropic's Models API pagination: 20 per page by default, 1..1000.
export const MODEL_PAGE_DEFAULT_LIMIT = 20;
export const MODEL_PAGE_MAX_LIMIT = 1000;
// Release dates aren't something we can know for a wildcard allow pattern or a
// catalog entry. The Models API explicitly permits an epoch value when the
// release date is unknown.
const UNKNOWN_CREATED_AT = new Date(0).toISOString();
/**
* One entry of Anthropic's `GET /v1/models` response. Only the identity fields
* can be filled in from a provider's model lists - token limits and
* per-model capability flags aren't derivable from an allow/block list, and the
* API schema declares all three nullable.
*/
export type AnthropicModelInfo = {
type: "model";
id: string;
display_name: string;
created_at: string;
max_input_tokens: number | null;
max_tokens: number | null;
capabilities: Record<string, unknown> | null;
};
/** A model row an administrator configured explicitly on a provider. */
export type ConfiguredModel = { name: string; createdAt: number };
/** What the pricing catalog knows about a model beyond its id. */
export type CatalogModelMetadata = {
maxInputTokens: number | null;
maxOutputTokens: number | null;
capabilities: AiModelCapabilityFlags;
};
/**
* Translates the catalog's flat feature flags into the nested shape
* Anthropic's Models API uses. Best-effort by nature: the catalog carries a
* coarser set of flags than the Models API describes, so anything it reports
* as unknown (`null`) is surfaced as unsupported rather than invented.
*/
export function capabilitiesFromCatalog(
flags: AiModelCapabilityFlags
): Record<string, unknown> {
const supported = (value: boolean | null) => ({
supported: value === true
});
// The catalog has a single `reasoning` flag and no way to distinguish
// adaptive from budget_tokens-style thinking, so both variants follow it.
const reasoning = flags.reasoning === true;
return {
batch: supported(null),
citations: supported(null),
code_execution: supported(null),
context_management: {
supported: false,
clear_thinking_20251015: null,
clear_tool_uses_20250919: null,
compact_20260112: null
},
effort: {
supported: reasoning,
low: supported(flags.reasoning),
medium: supported(flags.reasoning),
high: supported(flags.reasoning),
max: supported(flags.reasoning),
xhigh: null
},
image_input: supported(flags.vision),
pdf_input: supported(null),
structured_outputs: supported(flags.responseSchema),
thinking: {
supported: reasoning,
types: {
adaptive: { supported: reasoning },
enabled: { supported: reasoning }
}
}
};
}
/**
* One attached provider's contribution to a resource's model listing, with the
* allow/block lists already resolved for the attachment's access mode.
*/
export type ModelDiscoveryProvider = {
providerId: number;
allows: string[];
blocks: string[];
/**
* Concrete model ids the provider's type is known to serve, with whatever
* the catalog knows about each. This is what lets a wildcard allow such as
* `claude-*` enumerate into real ids; provider types with no catalog
* (aggregators, custom) pass an empty map and surface only their exact
* allow entries.
*/
catalog: Map<string, CatalogModelMetadata>;
/** Keyed by model key, for display names and creation times. */
configured: Map<string, ConfiguredModel>;
};
export type ModelPage = {
data: AnthropicModelInfo[];
has_more: boolean;
};
/**
* Expands one provider's effective allow/block lists into concrete model ids.
* Two sources feed the candidate set: exact (non-wildcard) allow entries, which
* are already concrete ids, and the catalog for the provider's type, which is
* what makes wildcard allows enumerable. Every candidate is then run back
* through the same allow/block check the inference pipeline applies, so a block
* pattern hides a model here exactly as it would reject it at request time.
*/
export function expandProviderModels(
provider: ModelDiscoveryProvider
): AnthropicModelInfo[] {
const candidates = new Set<string>();
for (const allow of provider.allows) {
if (!isModelKeyPattern(allow)) {
candidates.add(allow);
}
}
for (const modelId of provider.catalog.keys()) {
candidates.add(modelId);
}
const models: AnthropicModelInfo[] = [];
for (const modelKey of candidates) {
if (!isAllowedByLists(modelKey, provider.allows, provider.blocks)) {
continue;
}
const configured = provider.configured.get(modelKey);
const catalog = provider.catalog.get(modelKey);
models.push({
type: "model",
id: modelKey,
display_name: configured?.name || modelKey,
created_at: configured
? new Date(configured.createdAt).toISOString()
: UNKNOWN_CREATED_AT,
max_input_tokens: catalog?.maxInputTokens ?? null,
max_tokens: catalog?.maxOutputTokens ?? null,
capabilities: catalog
? capabilitiesFromCatalog(catalog.capabilities)
: null
});
}
return models;
}
/**
* Aggregates the permitted models across every provider attached to a
* resource. Unlike an inference request there is no requested model to
* disambiguate on, so no provider selection happens - the listing is the union
* of what each provider would accept, deduplicated by model id.
*/
export function listPermittedModels(
providers: ModelDiscoveryProvider[]
): AnthropicModelInfo[] {
const byModelId = new Map<string, AnthropicModelInfo>();
// Sorted so a model offered by two providers always resolves to the same
// entry, which keeps the cursor ordering stable across requests.
const ordered = [...providers].sort((a, b) => a.providerId - b.providerId);
for (const provider of ordered) {
for (const model of expandProviderModels(provider)) {
if (!byModelId.has(model.id)) {
byModelId.set(model.id, model);
}
}
}
// "More recently released models are listed first" per the Models API,
// with the id as a tie-break so the ordering is total - cursor pagination
// needs it to be stable between calls.
return [...byModelId.values()].sort((a, b) => {
const byCreated = b.created_at.localeCompare(a.created_at);
return byCreated !== 0 ? byCreated : a.id.localeCompare(b.id);
});
}
/**
* Applies Anthropic's cursor pagination to an ordered model list. `after_id`
* returns the page immediately after that model, `before_id` the page
* immediately before it. Returns an error message for a caller mistake
* (both cursors, or a cursor naming a model that isn't in the list).
*/
export function paginateModels(
models: AnthropicModelInfo[],
limit: number,
cursor: { afterId?: string; beforeId?: string }
): ModelPage | { error: string } {
if (cursor.afterId && cursor.beforeId) {
return { error: "Only one of after_id and before_id may be provided" };
}
const cursorId = cursor.afterId ?? cursor.beforeId;
if (!cursorId) {
return {
data: models.slice(0, limit),
has_more: models.length > limit
};
}
const index = models.findIndex((model) => model.id === cursorId);
if (index === -1) {
return { error: `Unknown cursor id "${cursorId}"` };
}
if (cursor.afterId) {
const start = index + 1;
return {
data: models.slice(start, start + limit),
has_more: models.length > start + limit
};
}
const start = Math.max(0, index - limit);
return {
data: models.slice(start, index),
has_more: start > 0
};
}
-2
View File
@@ -335,8 +335,6 @@ const EXTRACTORS: Record<
openai_chat: extractOpenAiChat, openai_chat: extractOpenAiChat,
openai_responses: extractOpenAiResponses, openai_responses: extractOpenAiResponses,
anthropic_messages: extractAnthropicMessages, anthropic_messages: extractAnthropicMessages,
// Model discovery never runs a model, so there are no tokens to bill.
v1_models: () => null,
gemini_generate_content: extractGoogleGenerateContent, gemini_generate_content: extractGoogleGenerateContent,
google_generate_content: extractGoogleGenerateContent, google_generate_content: extractGoogleGenerateContent,
// rawPredict is a passthrough to whatever the underlying publisher // rawPredict is a passthrough to whatever the underlying publisher
-2
View File
@@ -9,7 +9,6 @@ export enum TierFeature {
AccessLogs = "accessLogs", // set the retention period to none on downgrade AccessLogs = "accessLogs", // set the retention period to none on downgrade
ActionLogs = "actionLogs", // set the retention period to none on downgrade ActionLogs = "actionLogs", // set the retention period to none on downgrade
ConnectionLogs = "connectionLogs", ConnectionLogs = "connectionLogs",
AISessionLogs = "aiSessionLogs",
RotateCredentials = "rotateCredentials", RotateCredentials = "rotateCredentials",
MaintenancePage = "maintenancePage", // handle downgrade MaintenancePage = "maintenancePage", // handle downgrade
DevicePosture = "devicePosture", DevicePosture = "devicePosture",
@@ -38,7 +37,6 @@ export const tierMatrix: Record<TierFeature, Tier[]> = {
[TierFeature.AccessLogs]: ["tier2", "tier3", "enterprise"], [TierFeature.AccessLogs]: ["tier2", "tier3", "enterprise"],
[TierFeature.ActionLogs]: ["tier2", "tier3", "enterprise"], [TierFeature.ActionLogs]: ["tier2", "tier3", "enterprise"],
[TierFeature.ConnectionLogs]: ["tier2", "tier3", "enterprise"], [TierFeature.ConnectionLogs]: ["tier2", "tier3", "enterprise"],
[TierFeature.AISessionLogs]: ["tier2", "tier3", "enterprise"],
[TierFeature.RotateCredentials]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.RotateCredentials]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.MaintenancePage]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.MaintenancePage]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.DevicePosture]: ["tier2", "tier3", "enterprise"], [TierFeature.DevicePosture]: ["tier2", "tier3", "enterprise"],
+1 -4
View File
@@ -22,10 +22,7 @@ export async function listExitNodes(
// Accepted for parity with the enterprise implementation (used there for // Accepted for parity with the enterprise implementation (used there for
// site-label filtering of remote exit nodes). The OSS build has no remote // site-label filtering of remote exit nodes). The OSS build has no remote
// exit nodes, so it is unused here. // exit nodes, so it is unused here.
siteId?: number, siteId?: number
// Same as above: accepted for parity, unused since the OSS build has no
// remote exit nodes to exclude.
noRemote = false
) { ) {
// TODO: pick which nodes to send and ping better than just all of them that are not remote // TODO: pick which nodes to send and ping better than just all of them that are not remote
const allExitNodes = await db const allExitNodes = await db
+14 -27
View File
@@ -1,26 +1,20 @@
import { db, exitNodes, exitNodeOrgs, Transaction } from "@server/db"; import { db, exitNodes, Transaction } from "@server/db";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { findNextAvailableCidr } from "@server/lib/ip"; import { findNextAvailableCidr } from "@server/lib/ip";
import { lockManager } from "#dynamic/lib/lock"; import { lockManager } from "#dynamic/lib/lock";
import { eq } from "drizzle-orm";
/** /**
* Reserves the next available exit node subnet. * Reserves the next available exit node subnet.
* *
* There isn't enough address space to give every exit node in every org a * Exit node subnets must never overlap with one another - regardless of
* globally unique subnet, so we only guarantee uniqueness among exit nodes * which org(s) they belong to - since HA exit nodes can end up routing for
* that already belong to the same org - that's all that actually matters, * the same org. This acquires a lock that the caller MUST release (via the
* since HA only routes multiple exit nodes for a single org. Pass `orgId` to * returned `release`) only after the chosen address has been durably
* scope the search to that org's existing exit nodes; without it, the search * persisted (e.g. after the enclosing transaction commits), otherwise
* considers every exit node (used by flows with no org context, e.g. the * concurrent callers can race and pick the same subnet.
* initial gerbil exit node bootstrap). This acquires a lock that the caller
* MUST release (via the returned `release`) only after the chosen address
* has been durably persisted (e.g. after the enclosing transaction commits),
* otherwise concurrent callers can race and pick the same subnet.
*/ */
export async function getNextAvailableSubnet( export async function getNextAvailableSubnet(
trx: Transaction | typeof db = db, trx: Transaction | typeof db = db
orgId?: string
): Promise<{ value: string; release: () => Promise<void> }> { ): Promise<{ value: string; release: () => Promise<void> }> {
const lockKey = "exit-node-subnet-allocation"; const lockKey = "exit-node-subnet-allocation";
const acquired = await lockManager.acquireLockWithRetry(lockKey, 6000); const acquired = await lockManager.acquireLockWithRetry(lockKey, 6000);
@@ -30,19 +24,12 @@ export async function getNextAvailableSubnet(
const release = () => lockManager.releaseLock(lockKey, acquired); const release = () => lockManager.releaseLock(lockKey, acquired);
try { try {
// Get existing subnets, scoped to this org's exit nodes when known // Get all existing subnets from routes table
const existingAddresses = orgId const existingAddresses = await trx
? await trx .select({
.select({ address: exitNodes.address }) address: exitNodes.address
.from(exitNodes) })
.innerJoin( .from(exitNodes);
exitNodeOrgs,
eq(exitNodeOrgs.exitNodeId, exitNodes.exitNodeId)
)
.where(eq(exitNodeOrgs.orgId, orgId))
: await trx
.select({ address: exitNodes.address })
.from(exitNodes);
const addresses = existingAddresses.map((a) => a.address); const addresses = existingAddresses.map((a) => a.address);
let subnet = findNextAvailableCidr( let subnet = findNextAvailableCidr(
+1
View File
@@ -348,6 +348,7 @@ export const configSchema = z
.optional() .optional()
.pipe(z.string()) .pipe(z.string())
.transform((url) => url.toLowerCase()), .transform((url) => url.toLowerCase()),
use_subdomain: z.boolean().optional().default(false),
subnet_group: z.string().optional().default("100.89.137.0/20"), subnet_group: z.string().optional().default("100.89.137.0/20"),
block_size: z.number().positive().gt(0).optional().default(24), block_size: z.number().positive().gt(0).optional().default(24),
site_block_size: z site_block_size: z
@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { db, userOrgRoles, users } from "@server/db"; import { db, userOrgRoles, users } from "@server/db";
import logger from "@server/logger"; import logger from "@server/logger";
import type { import type {
+2 -5
View File
@@ -153,8 +153,7 @@ export async function listExitNodes(
orgId: string, orgId: string,
filterOnline = false, filterOnline = false,
noCloud = false, noCloud = false,
siteId?: number, siteId?: number
noRemote = false
) { ) {
const allExitNodes = await db const allExitNodes = await db
.select({ .select({
@@ -243,9 +242,7 @@ export async function listExitNodes(
let remoteExitNodesList = allExitNodes.filter( let remoteExitNodesList = allExitNodes.filter(
(node) => (node) =>
node.type === "remoteExitNode" && node.type === "remoteExitNode" && (!filterOnline || node.online)
!noRemote &&
(!filterOnline || node.online)
); );
const gerbilExitNodes = allExitNodes.filter( const gerbilExitNodes = allExitNodes.filter(
(node) => (node) =>
@@ -19,8 +19,7 @@ import {
requestAuditLog, requestAuditLog,
actionAuditLog, actionAuditLog,
accessAuditLog, accessAuditLog,
connectionAuditLog, connectionAuditLog
aiSessionLog
} from "@server/db"; } from "@server/db";
import logger from "@server/logger"; import logger from "@server/logger";
import { and, eq, gt, desc, max, sql } from "drizzle-orm"; import { and, eq, gt, desc, max, sql } from "drizzle-orm";
@@ -310,7 +309,6 @@ export class LogStreamingManager {
if (dest.sendActionLogs) enabledTypes.push("action"); if (dest.sendActionLogs) enabledTypes.push("action");
if (dest.sendAccessLogs) enabledTypes.push("access"); if (dest.sendAccessLogs) enabledTypes.push("access");
if (dest.sendConnectionLogs) enabledTypes.push("connection"); if (dest.sendConnectionLogs) enabledTypes.push("connection");
if (dest.sendAISessionLogs) enabledTypes.push("aiSession");
if (enabledTypes.length === 0) return; if (enabledTypes.length === 0) return;
@@ -587,13 +585,6 @@ export class LogStreamingManager {
.where(eq(connectionAuditLog.orgId, orgId)); .where(eq(connectionAuditLog.orgId, orgId));
return row?.maxId ?? 0; return row?.maxId ?? 0;
} }
case "aiSession": {
const [row] = await logsDb
.select({ maxId: max(aiSessionLog.id) })
.from(aiSessionLog)
.where(eq(aiSessionLog.orgId, orgId));
return row?.maxId ?? 0;
}
} }
} catch (err) { } catch (err) {
logger.warn( logger.warn(
@@ -679,21 +670,6 @@ export class LogStreamingManager {
.limit(limit)) as Array< .limit(limit)) as Array<
Record<string, unknown> & { id: number } Record<string, unknown> & { id: number }
>; >;
case "aiSession":
return (await logsDb
.select()
.from(aiSessionLog)
.where(
and(
eq(aiSessionLog.orgId, orgId),
gt(aiSessionLog.id, afterId)
)
)
.orderBy(aiSessionLog.id)
.limit(limit)) as Array<
Record<string, unknown> & { id: number }
>;
} }
} }
@@ -718,14 +694,6 @@ export class LogStreamingManager {
timestamp = timestamp =
typeof row.startedAt === "number" ? row.startedAt : 0; typeof row.startedAt === "number" ? row.startedAt : 0;
break; break;
case "aiSession":
// createdAt is stored as epoch milliseconds; normalise to
// epoch seconds to match the other log types.
timestamp =
typeof row.createdAt === "number"
? Math.floor(row.createdAt / 1000)
: 0;
break;
} }
const orgId = typeof row.orgId === "string" ? row.orgId : ""; const orgId = typeof row.orgId === "string" ? row.orgId : "";
+2 -3
View File
@@ -15,14 +15,13 @@
// Log type identifiers // Log type identifiers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export type LogType = "request" | "action" | "access" | "connection" | "aiSession"; export type LogType = "request" | "action" | "access" | "connection";
export const LOG_TYPES: LogType[] = [ export const LOG_TYPES: LogType[] = [
"request", "request",
"action", "action",
"access", "access",
"connection", "connection"
"aiSession"
]; ];
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -1,288 +0,0 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { logsDb, db, orgs, aiSessionLog, type AiProvider } from "@server/db";
import type { InferInsertModel } from "drizzle-orm";
import logger from "@server/logger";
import { and, eq, lt } from "drizzle-orm";
import cache from "#private/lib/cache";
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
import { sanitizeString } from "@server/lib/sanitize";
import type { AiCapability } from "@server/lib/aiCapabilities";
import {
normalizeAiRequest,
normalizeAiResponse
} from "@server/lib/aiMessageNormalization";
// Caps how much of the request/response body we keep per row, so a single
// huge multimodal payload can't blow up buffer memory or storage.
const AI_SESSION_LOG_MAX_BODY_CHARS = 200_000;
type AiSessionLogInsert = InferInsertModel<typeof aiSessionLog>;
// In-memory buffer for batching AI session log inserts, mirroring the
// approach in server/routers/badger/logRequestAudit.ts.
const sessionLogBuffer: AiSessionLogInsert[] = [];
const BATCH_SIZE = 100; // Write to DB every 100 logs
const BATCH_INTERVAL_MS = 5000; // Or every 5 seconds, whichever comes first
const MAX_BUFFER_SIZE = 10000; // Prevent unbounded memory growth
let flushTimer: NodeJS.Timeout | null = null;
let isFlushInProgress = false;
/**
* Flush buffered logs to database
*/
async function flushSessionLogs() {
if (sessionLogBuffer.length === 0 || isFlushInProgress) {
return;
}
isFlushInProgress = true;
// Take all current logs and clear buffer
const logsToWrite = sessionLogBuffer.splice(0, sessionLogBuffer.length);
try {
// Use a transaction to ensure all inserts succeed or fail together
await logsDb.transaction(async (tx) => {
// Batch insert logs in groups of 25 to avoid overwhelming the database
const BATCH_DB_SIZE = 25;
for (let i = 0; i < logsToWrite.length; i += BATCH_DB_SIZE) {
const batch = logsToWrite.slice(i, i + BATCH_DB_SIZE);
await tx.insert(aiSessionLog).values(batch);
}
});
logger.debug(
`Flushed ${logsToWrite.length} AI session logs to database`
);
} catch (error) {
logger.error("Error flushing AI session logs:", error);
// On transaction error, put logs back at the front of the buffer to retry
// but only if buffer isn't too large
if (sessionLogBuffer.length < MAX_BUFFER_SIZE - logsToWrite.length) {
sessionLogBuffer.unshift(...logsToWrite);
logger.info(
`Re-queued ${logsToWrite.length} AI session logs for retry`
);
} else {
logger.error(
`Buffer full, dropped ${logsToWrite.length} AI session logs`
);
}
} finally {
isFlushInProgress = false;
// If buffer filled up while we were flushing, flush again
if (sessionLogBuffer.length >= BATCH_SIZE) {
flushSessionLogs().catch((err) =>
logger.error("Error in follow-up AI session log flush:", err)
);
}
}
}
/**
* Schedule a flush if not already scheduled
*/
function scheduleFlush() {
if (flushTimer === null) {
flushTimer = setTimeout(() => {
flushTimer = null;
flushSessionLogs().catch((err) =>
logger.error("Error in scheduled AI session log flush:", err)
);
}, BATCH_INTERVAL_MS);
}
}
/**
* Gracefully flush all pending logs (call this on shutdown)
*/
export async function shutdownAiSessionLogger() {
if (flushTimer) {
clearTimeout(flushTimer);
flushTimer = null;
}
// Force flush even if one is in progress by waiting and retrying
while (isFlushInProgress) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
await flushSessionLogs();
}
async function getRetentionDays(orgId: string): Promise<number> {
// check cache first
const cached = await cache.get<number>(`org_${orgId}_aiSessionsDays`);
if (cached !== undefined) {
return cached;
}
const [org] = await db
.select({
settingsLogRetentionDaysAISessions:
orgs.settingsLogRetentionDaysAISessions
})
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (!org) {
return 0;
}
// store the result in cache
await cache.set(
`org_${orgId}_aiSessionsDays`,
org.settingsLogRetentionDaysAISessions,
300
);
return org.settingsLogRetentionDaysAISessions;
}
export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
// calculateCutoffTimestamp returns a seconds-epoch cutoff (built for
// requestAuditLog.timestamp), but aiSessionLog.createdAt is ms-epoch to
// match aiUsageRecords - convert before comparing.
const cutoffTimestampMs = calculateCutoffTimestamp(retentionDays) * 1000;
try {
await logsDb
.delete(aiSessionLog)
.where(
and(
lt(aiSessionLog.createdAt, cutoffTimestampMs),
eq(aiSessionLog.orgId, orgId)
)
);
} catch (error) {
logger.error("Error cleaning up old AI session logs:", error);
}
}
function truncateBody(value: string): { value: string; truncated: boolean } {
if (value.length <= AI_SESSION_LOG_MAX_BODY_CHARS) {
return { value, truncated: false };
}
return {
value: value.slice(0, AI_SESSION_LOG_MAX_BODY_CHARS),
truncated: true
};
}
export function logAiSession(data: {
sessionId: string;
capability: AiCapability;
provider: AiProvider;
requestedModel: string | undefined;
requestBody: unknown;
responseText: string;
isStream: boolean;
statusCode: number;
orgId: string | null;
resourceId: number | null;
siteResourceId: number | null;
requestUserId: string | null;
virtualApiKeyId: string | null;
}): void {
(async () => {
try {
// Check retention before buffering any logs
if (data.orgId) {
const retentionDays = await getRetentionDays(data.orgId);
if (retentionDays === 0) {
// do not log
return;
}
} else {
// No org resolved for this request - nothing to govern
// retention with, so don't log it.
return;
}
const requestBodyText = truncateBody(
JSON.stringify(data.requestBody ?? "")
);
const responseBodyText = truncateBody(data.responseText ?? "");
// Uniform, capability-agnostic transcript for search/display -
// computed from the untruncated originals so normalization sees
// the full content; the normalized result gets its own
// (typically much smaller) truncation pass below.
const normalizedRequestMessages = normalizeAiRequest(
data.capability,
data.requestBody
);
const normalizedResponseMessages = normalizeAiResponse(
data.capability,
data.responseText ?? "",
data.isStream
);
const normalizedRequestText = normalizedRequestMessages
? truncateBody(JSON.stringify(normalizedRequestMessages))
: null;
const normalizedResponseText = normalizedResponseMessages
? truncateBody(JSON.stringify(normalizedResponseMessages))
: null;
// Prevent unbounded buffer growth - drop oldest entries if buffer is too large
if (sessionLogBuffer.length >= MAX_BUFFER_SIZE) {
const dropped = sessionLogBuffer.splice(0, BATCH_SIZE);
logger.warn(
`AI session log buffer exceeded max size (${MAX_BUFFER_SIZE}), dropped ${dropped.length} oldest entries`
);
}
sessionLogBuffer.push({
sessionId: data.sessionId,
orgId: sanitizeString(data.orgId),
providerId: data.provider.providerId,
capability: data.capability,
resourceId: data.resourceId ?? undefined,
siteResourceId: data.siteResourceId ?? undefined,
userId: sanitizeString(data.requestUserId ?? undefined),
virtualApiKeyId: sanitizeString(
data.virtualApiKeyId ?? undefined
),
requestedModel: sanitizeString(data.requestedModel),
isStream: data.isStream,
requestBody: sanitizeString(requestBodyText.value),
responseBody: sanitizeString(responseBodyText.value),
normalizedRequest: normalizedRequestText
? sanitizeString(normalizedRequestText.value)
: undefined,
normalizedResponse: normalizedResponseText
? sanitizeString(normalizedResponseText.value)
: undefined,
truncated:
requestBodyText.truncated ||
responseBodyText.truncated ||
(normalizedRequestText?.truncated ?? false) ||
(normalizedResponseText?.truncated ?? false),
statusCode: data.statusCode,
createdAt: Date.now()
});
// Flush immediately if buffer is full, otherwise schedule a flush
if (sessionLogBuffer.length >= BATCH_SIZE) {
flushSessionLogs().catch((err) =>
logger.error("Error flushing AI session logs:", err)
);
} else {
scheduleFlush();
}
} catch (error) {
logger.error("Failed to log AI session", { error });
}
})();
}
@@ -291,10 +291,6 @@ async function disableFeature(
await disableConnectionLogs(orgId); await disableConnectionLogs(orgId);
break; break;
case TierFeature.AISessionLogs:
await disableAISessionLogs(orgId);
break;
case TierFeature.RotateCredentials: case TierFeature.RotateCredentials:
await disableRotateCredentials(orgId); await disableRotateCredentials(orgId);
break; break;
@@ -497,15 +493,6 @@ async function disableConnectionLogs(orgId: string): Promise<void> {
logger.info(`Disabled connection logs for org ${orgId}`); logger.info(`Disabled connection logs for org ${orgId}`);
} }
async function disableAISessionLogs(orgId: string): Promise<void> {
await db
.update(orgs)
.set({ settingsLogRetentionDaysAISessions: 0 })
.where(eq(orgs.orgId, orgId));
logger.info(`Disabled AI session logs for org ${orgId}`);
}
async function disableRotateCredentials(orgId: string): Promise<void> {} async function disableRotateCredentials(orgId: string): Promise<void> {}
async function disablemaintenancePage(orgId: string): Promise<void> { async function disablemaintenancePage(orgId: string): Promise<void> {
@@ -37,8 +37,7 @@ const bodySchema = z.strictObject({
sendConnectionLogs: z.boolean().optional().default(false), sendConnectionLogs: z.boolean().optional().default(false),
sendRequestLogs: z.boolean().optional().default(false), sendRequestLogs: z.boolean().optional().default(false),
sendActionLogs: z.boolean().optional().default(false), sendActionLogs: z.boolean().optional().default(false),
sendAccessLogs: z.boolean().optional().default(false), sendAccessLogs: z.boolean().optional().default(false)
sendAISessionLogs: z.boolean().optional().default(false)
}); });
export type CreateEventStreamingDestinationResponse = { export type CreateEventStreamingDestinationResponse = {
@@ -123,8 +122,7 @@ export async function createEventStreamingDestination(
sendAccessLogs: parsedBody.data.sendAccessLogs, sendAccessLogs: parsedBody.data.sendAccessLogs,
sendActionLogs: parsedBody.data.sendActionLogs, sendActionLogs: parsedBody.data.sendActionLogs,
sendConnectionLogs: parsedBody.data.sendConnectionLogs, sendConnectionLogs: parsedBody.data.sendConnectionLogs,
sendRequestLogs: parsedBody.data.sendRequestLogs, sendRequestLogs: parsedBody.data.sendRequestLogs
sendAISessionLogs: parsedBody.data.sendAISessionLogs
}) })
.returning(); .returning();
@@ -60,7 +60,6 @@ export type ListEventStreamingDestinationsResponse = {
sendRequestLogs: boolean; sendRequestLogs: boolean;
sendActionLogs: boolean; sendActionLogs: boolean;
sendAccessLogs: boolean; sendAccessLogs: boolean;
sendAISessionLogs: boolean;
}[]; }[];
pagination: { pagination: {
total: number; total: number;
@@ -84,8 +83,7 @@ const ListEventStreamingDestinationsResponseDataSchema = z.object({
sendConnectionLogs: z.boolean(), sendConnectionLogs: z.boolean(),
sendRequestLogs: z.boolean(), sendRequestLogs: z.boolean(),
sendActionLogs: z.boolean(), sendActionLogs: z.boolean(),
sendAccessLogs: z.boolean(), sendAccessLogs: z.boolean()
sendAISessionLogs: z.boolean()
}) })
), ),
pagination: z.object({ pagination: z.object({
@@ -40,8 +40,7 @@ const bodySchema = z.strictObject({
sendConnectionLogs: z.boolean().optional(), sendConnectionLogs: z.boolean().optional(),
sendRequestLogs: z.boolean().optional(), sendRequestLogs: z.boolean().optional(),
sendActionLogs: z.boolean().optional(), sendActionLogs: z.boolean().optional(),
sendAccessLogs: z.boolean().optional(), sendAccessLogs: z.boolean().optional()
sendAISessionLogs: z.boolean().optional()
}); });
export type UpdateEventStreamingDestinationResponse = { export type UpdateEventStreamingDestinationResponse = {
@@ -126,7 +125,7 @@ export async function updateEventStreamingDestination(
); );
} }
const { type, config: configToUpdate, enabled, sendAccessLogs, sendActionLogs, sendConnectionLogs, sendRequestLogs, sendAISessionLogs } = parsedBody.data; const { type, config: configToUpdate, enabled, sendAccessLogs, sendActionLogs, sendConnectionLogs, sendRequestLogs } = parsedBody.data;
const updateData: Record<string, unknown> = { const updateData: Record<string, unknown> = {
updatedAt: Date.now() updatedAt: Date.now()
@@ -142,7 +141,6 @@ export async function updateEventStreamingDestination(
if (sendActionLogs !== undefined) updateData.sendActionLogs = sendActionLogs; if (sendActionLogs !== undefined) updateData.sendActionLogs = sendActionLogs;
if (sendConnectionLogs !== undefined) updateData.sendConnectionLogs = sendConnectionLogs; if (sendConnectionLogs !== undefined) updateData.sendConnectionLogs = sendConnectionLogs;
if (sendRequestLogs !== undefined) updateData.sendRequestLogs = sendRequestLogs; if (sendRequestLogs !== undefined) updateData.sendRequestLogs = sendRequestLogs;
if (sendAISessionLogs !== undefined) updateData.sendAISessionLogs = sendAISessionLogs;
await db await db
.update(eventStreamingDestinations) .update(eventStreamingDestinations)
-23
View File
@@ -21,10 +21,6 @@ import * as auth from "#private/routers/auth";
import * as license from "#private/routers/license"; import * as license from "#private/routers/license";
import * as generateLicense from "#private/routers/generatedLicense"; import * as generateLicense from "#private/routers/generatedLicense";
import * as logs from "#private/routers/auditLogs"; import * as logs from "#private/routers/auditLogs";
import {
queryAiSessionLogs,
exportAiSessionLogs
} from "@server/routers/auditLogs";
import * as misc from "#private/routers/misc"; import * as misc from "#private/routers/misc";
import * as reKey from "#private/routers/re-key"; import * as reKey from "#private/routers/re-key";
import * as approval from "#private/routers/approvals"; import * as approval from "#private/routers/approvals";
@@ -595,25 +591,6 @@ authenticated.get(
logs.exportConnectionAuditLogs logs.exportConnectionAuditLogs
); );
authenticated.get(
"/org/:orgId/logs/ai",
verifyValidLicense,
verifyValidSubscription(tierMatrix.aiSessionLogs),
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.viewLogs),
queryAiSessionLogs
);
authenticated.get(
"/org/:orgId/logs/ai/export",
verifyValidLicense,
verifyValidSubscription(tierMatrix.aiSessionLogs),
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
exportAiSessionLogs
);
authenticated.post( authenticated.post(
"/re-key/:clientId/regenerate-client-secret", "/re-key/:clientId/regenerate-client-secret",
verifyClientAccess, // this is first to set the org id verifyClientAccess, // this is first to set the org id
@@ -34,6 +34,10 @@ export async function createExitNode(
// TODO: eventually we will want to get the next available port so that we can multiple exit nodes // TODO: eventually we will want to get the next available port so that we can multiple exit nodes
// const listenPort = await getNextAvailablePort(); // const listenPort = await getNextAvailablePort();
const listenPort = config.getRawConfig().gerbil.start_port; const listenPort = config.getRawConfig().gerbil.start_port;
let subEndpoint = "";
if (config.getRawConfig().gerbil.use_subdomain) {
subEndpoint = await getUniqueExitNodeEndpointName();
}
const exitNodeName = const exitNodeName =
config.getRawConfig().gerbil.exit_node_name || config.getRawConfig().gerbil.exit_node_name ||
@@ -44,7 +48,7 @@ export async function createExitNode(
.insert(exitNodes) .insert(exitNodes)
.values({ .values({
publicKey, publicKey,
endpoint: config.getRawConfig().gerbil.base_endpoint, endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`,
address, address,
listenPort, listenPort,
online: true, online: true,
-23
View File
@@ -43,10 +43,6 @@ import {
unauthenticated as ua, unauthenticated as ua,
authenticated as a authenticated as a
} from "@server/routers/integration"; } from "@server/routers/integration";
import {
queryAiSessionLogs,
exportAiSessionLogs
} from "@server/routers/auditLogs";
import { logActionAudit } from "#private/middlewares"; import { logActionAudit } from "#private/middlewares";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { build } from "@server/build"; import { build } from "@server/build";
@@ -157,25 +153,6 @@ authenticated.get(
logs.exportConnectionAuditLogs logs.exportConnectionAuditLogs
); );
authenticated.get(
"/org/:orgId/logs/ai",
verifyValidLicense,
verifyValidSubscription(tierMatrix.aiSessionLogs),
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.viewLogs),
queryAiSessionLogs
);
authenticated.get(
"/org/:orgId/logs/ai/export",
verifyValidLicense,
verifyValidSubscription(tierMatrix.aiSessionLogs),
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
exportAiSessionLogs
);
authenticated.put( authenticated.put(
"/org/:orgId/idp/oidc", "/org/:orgId/idp/oidc",
verifyValidLicense, verifyValidLicense,
@@ -0,0 +1,238 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { db } from "@server/db";
import { MessageHandler } from "@server/routers/ws";
import { sites, Newt, orgs, clients, clientSitesAssociationsCache } from "@server/db";
import { and, eq, inArray } from "drizzle-orm";
import logger from "@server/logger";
import { inflate } from "zlib";
import { promisify } from "util";
import { logRequestAudit } from "@server/routers/badger/logRequestAudit";
import { getCountryCodeForIp } from "@server/lib/geoip";
export async function flushRequestLogToDb(): Promise<void> {
return;
}
const zlibInflate = promisify(inflate);
interface HTTPRequestLogData {
requestId: string;
resourceId: number; // siteResourceId
timestamp: string; // ISO 8601
method: string;
scheme: string; // "http" or "https"
host: string;
path: string;
rawQuery?: string;
userAgent?: string;
sourceAddr: string; // ip:port
tls: boolean;
}
/**
* Decompress a base64-encoded zlib-compressed string into parsed JSON.
*/
async function decompressRequestLog(
compressed: string
): Promise<HTTPRequestLogData[]> {
const compressedBuffer = Buffer.from(compressed, "base64");
const decompressed = await zlibInflate(compressedBuffer);
const jsonString = decompressed.toString("utf-8");
const parsed = JSON.parse(jsonString);
if (!Array.isArray(parsed)) {
throw new Error("Decompressed request log data is not an array");
}
return parsed;
}
export const handleRequestLogMessage: MessageHandler = async (context) => {
const { message, client } = context;
const newt = client as Newt;
if (!newt) {
logger.warn("Request log received but no newt client in context");
return;
}
if (!newt.siteId) {
logger.warn("Request log received but newt has no siteId");
return;
}
if (!message.data?.compressed) {
logger.warn("Request log message missing compressed data");
return;
}
// Look up the org for this site and check retention settings
const [site] = await db
.select({
orgId: sites.orgId,
orgSubnet: orgs.subnet,
settingsLogRetentionDaysRequest:
orgs.settingsLogRetentionDaysRequest
})
.from(sites)
.innerJoin(orgs, eq(sites.orgId, orgs.orgId))
.where(eq(sites.siteId, newt.siteId));
if (!site) {
logger.warn(
`Request log received but site ${newt.siteId} not found in database`
);
return;
}
const orgId = site.orgId;
if (site.settingsLogRetentionDaysRequest === 0) {
logger.debug(
`Request log retention is disabled for org ${orgId}, skipping`
);
return;
}
let entries: HTTPRequestLogData[];
try {
entries = await decompressRequestLog(message.data.compressed);
} catch (error) {
logger.error("Failed to decompress request log data:", error);
return;
}
if (entries.length === 0) {
return;
}
logger.debug(`Request log entries: ${JSON.stringify(entries)}`);
// Build a map from sourceIp → external endpoint string by joining clients
// with clientSitesAssociationsCache. The endpoint is the real-world IP:port
// of the client device and is used for GeoIP lookup.
const ipToEndpoint = new Map<string, string>();
const cidrSuffix = site.orgSubnet?.includes("/")
? site.orgSubnet.substring(site.orgSubnet.indexOf("/"))
: null;
if (cidrSuffix) {
const uniqueSourceAddrs = new Set<string>();
for (const entry of entries) {
if (entry.sourceAddr) {
uniqueSourceAddrs.add(entry.sourceAddr);
}
}
if (uniqueSourceAddrs.size > 0) {
const subnetQueries = Array.from(uniqueSourceAddrs).map((addr) => {
const ip = addr.includes(":") ? addr.split(":")[0] : addr;
return `${ip}${cidrSuffix}`;
});
const matchedClients = await db
.select({
subnet: clients.subnet,
endpoint: clientSitesAssociationsCache.endpoint
})
.from(clients)
.innerJoin(
clientSitesAssociationsCache,
and(
eq(
clientSitesAssociationsCache.clientId,
clients.clientId
),
eq(clientSitesAssociationsCache.siteId, newt.siteId)
)
)
.where(
and(
eq(clients.orgId, orgId),
inArray(clients.subnet, subnetQueries)
)
);
for (const c of matchedClients) {
if (c.endpoint) {
const ip = c.subnet.split("/")[0];
ipToEndpoint.set(ip, c.endpoint);
}
}
}
}
for (const entry of entries) {
if (
!entry.requestId ||
!entry.resourceId ||
!entry.method ||
!entry.scheme ||
!entry.host ||
!entry.path ||
!entry.sourceAddr
) {
logger.debug(
`Skipping request log entry with missing required fields: ${JSON.stringify(entry)}`
);
continue;
}
const originalRequestURL =
entry.scheme +
"://" +
entry.host +
entry.path +
(entry.rawQuery ? "?" + entry.rawQuery : "");
// Resolve the client's external endpoint for GeoIP lookup.
// sourceAddr is the WireGuard IP (possibly ip:port), so strip the port.
const sourceIp = entry.sourceAddr.includes(":")
? entry.sourceAddr.split(":")[0]
: entry.sourceAddr;
const endpoint = ipToEndpoint.get(sourceIp);
let location: string | undefined;
if (endpoint) {
const endpointIp = endpoint.includes(":")
? endpoint.split(":")[0]
: endpoint;
location = await getCountryCodeForIp(endpointIp);
}
await logRequestAudit(
{
action: true,
reason: 108,
siteResourceId: entry.resourceId,
orgId,
location
},
{
path: entry.path,
originalRequestURL,
scheme: entry.scheme,
host: entry.host,
method: entry.method,
tls: entry.tls,
requestIp: entry.sourceAddr
}
);
}
logger.debug(
`Buffered ${entries.length} request log entry/entries from newt ${newt.newtId} (site ${newt.siteId})`
);
};
+1
View File
@@ -12,3 +12,4 @@
*/ */
export * from "./handleConnectionLogMessage"; export * from "./handleConnectionLogMessage";
export * from "./handleRequestLogMessage";
@@ -191,20 +191,13 @@ export async function createRemoteExitNode(
// If this remote exit node isn't already backing an exit node in // If this remote exit node isn't already backing an exit node in
// another org, we're about to create a brand new one. Reserve a // another org, we're about to create a brand new one. Reserve a
// subnet for it up front, scoped to this org's existing exit nodes, // subnet for it up front so the allocation lock is held across the
// so the allocation lock is held across the whole insert - this // whole insert - this guarantees exit node subnets never overlap,
// guarantees exit node subnets never overlap within the org, even // even under concurrent creation, which matters for HA setups.
// under concurrent creation, which matters for HA setups. Subnets
// may still be reused across different orgs; there isn't enough
// address space to avoid that, and it isn't necessary since HA only
// routes multiple exit nodes for the same org.
let releaseSubnetLock: (() => Promise<void>) | null = null; let releaseSubnetLock: (() => Promise<void>) | null = null;
let newExitNodeAddress: string | null = null; let newExitNodeAddress: string | null = null;
if (!existingExitNode) { if (!existingExitNode) {
const { value, release } = await getNextAvailableSubnet( const { value, release } = await getNextAvailableSubnet();
db,
orgId
);
newExitNodeAddress = value; newExitNodeAddress = value;
releaseSubnetLock = release; releaseSubnetLock = release;
} }
@@ -18,10 +18,12 @@ import {
import { MessageHandler } from "@server/routers/ws"; import { MessageHandler } from "@server/routers/ws";
import { import {
handleConnectionLogMessage, handleConnectionLogMessage,
handleRequestLogMessage
} from "#private/routers/newt"; } from "#private/routers/newt";
export const messageHandlers: Record<string, MessageHandler> = { export const messageHandlers: Record<string, MessageHandler> = {
"remoteExitNode/register": handleRemoteExitNodeRegisterMessage, "remoteExitNode/register": handleRemoteExitNodeRegisterMessage,
"remoteExitNode/ping": handleRemoteExitNodePingMessage, "remoteExitNode/ping": handleRemoteExitNodePingMessage,
"newt/access-log": handleConnectionLogMessage, "newt/access-log": handleConnectionLogMessage,
"newt/request-log": handleRequestLogMessage
}; };
+1 -1
View File
@@ -139,7 +139,7 @@ const processMessage = async (
} }
} }
} catch (error) { } catch (error) {
logger.warn("Message handling error:", error); logger.error("Message handling error:", error);
// ws.send(JSON.stringify({ // ws.send(JSON.stringify({
// type: "error", // type: "error",
// data: { // data: {
@@ -1,37 +1,19 @@
import { Router, type Request, type Response } from "express"; import { Router } from "express";
import { import {
AI_CAPABILITY_DEFS, AI_CAPABILITY_DEFS,
type AiCapability type AiCapability
} from "@server/lib/aiCapabilities"; } from "@server/lib/aiCapabilities";
import { handleAiGatewayProxy } from "@server/routers/aiGateway/pipeline"; import { handleAiGatewayProxy } from "@server/routers/aiGateway/pipeline";
import { handleV1Models } from "@server/routers/aiGateway";
type CapabilityHandler = (
req: Request,
res: Response,
capability: AiCapability
) => Promise<any>;
// Capabilities the gateway answers itself instead of proxying upstream.
// Everything else goes through the inference pipeline.
const LOCAL_HANDLERS: Partial<Record<AiCapability, CapabilityHandler>> = {
v1_models: handleV1Models
};
export function createAiGatewayRouter() { export function createAiGatewayRouter() {
const router = Router(); const router = Router();
for (const def of Object.values(AI_CAPABILITY_DEFS)) { for (const def of Object.values(AI_CAPABILITY_DEFS)) {
const capability = def.id as AiCapability; const capability = def.id as AiCapability;
const handler = LOCAL_HANDLERS[capability] ?? handleAiGatewayProxy;
for (const route of def.routes) { for (const route of def.routes) {
const bind = (req: Request, res: Response) => router.post(route.path, (req, res) =>
handler(req, res, capability); handleAiGatewayProxy(req, res, capability)
if (route.method === "GET") { );
router.get(route.path, bind);
} else {
router.post(route.path, bind);
}
} }
} }
-1
View File
@@ -1,3 +1,2 @@
export { handleAiGatewayProxy } from "./pipeline"; export { handleAiGatewayProxy } from "./pipeline";
export { handleV1Models } from "./v1Models";
export { createAiGatewayRouter } from "./createAiGatewayRouter"; export { createAiGatewayRouter } from "./createAiGatewayRouter";
+255 -5
View File
@@ -1,12 +1,171 @@
import { AiCapability } from "@app/lib/aiCapabilities"; import { logsDb, db, orgs, aiSessionLog, type AiProvider } from "@server/db";
import { AiProvider } from "@server/db"; import type { InferInsertModel } from "drizzle-orm";
import logger from "@server/logger";
import { and, eq, lt } from "drizzle-orm";
import cache from "#dynamic/lib/cache";
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
import { sanitizeString } from "@server/lib/sanitize";
import type { AiCapability } from "@server/lib/aiCapabilities";
import {
normalizeAiRequest,
normalizeAiResponse
} from "@server/lib/aiMessageNormalization";
// Caps how much of the request/response body we keep per row, so a single
// huge multimodal payload can't blow up buffer memory or storage.
const AI_SESSION_LOG_MAX_BODY_CHARS = 200_000;
type AiSessionLogInsert = InferInsertModel<typeof aiSessionLog>;
// In-memory buffer for batching AI session log inserts, mirroring the
// approach in server/routers/badger/logRequestAudit.ts.
const sessionLogBuffer: AiSessionLogInsert[] = [];
const BATCH_SIZE = 100; // Write to DB every 100 logs
const BATCH_INTERVAL_MS = 5000; // Or every 5 seconds, whichever comes first
const MAX_BUFFER_SIZE = 10000; // Prevent unbounded memory growth
let flushTimer: NodeJS.Timeout | null = null;
let isFlushInProgress = false;
/**
* Flush buffered logs to database
*/
async function flushSessionLogs() {
if (sessionLogBuffer.length === 0 || isFlushInProgress) {
return;
}
isFlushInProgress = true;
// Take all current logs and clear buffer
const logsToWrite = sessionLogBuffer.splice(0, sessionLogBuffer.length);
try {
// Use a transaction to ensure all inserts succeed or fail together
await logsDb.transaction(async (tx) => {
// Batch insert logs in groups of 25 to avoid overwhelming the database
const BATCH_DB_SIZE = 25;
for (let i = 0; i < logsToWrite.length; i += BATCH_DB_SIZE) {
const batch = logsToWrite.slice(i, i + BATCH_DB_SIZE);
await tx.insert(aiSessionLog).values(batch);
}
});
logger.debug(
`Flushed ${logsToWrite.length} AI session logs to database`
);
} catch (error) {
logger.error("Error flushing AI session logs:", error);
// On transaction error, put logs back at the front of the buffer to retry
// but only if buffer isn't too large
if (sessionLogBuffer.length < MAX_BUFFER_SIZE - logsToWrite.length) {
sessionLogBuffer.unshift(...logsToWrite);
logger.info(
`Re-queued ${logsToWrite.length} AI session logs for retry`
);
} else {
logger.error(
`Buffer full, dropped ${logsToWrite.length} AI session logs`
);
}
} finally {
isFlushInProgress = false;
// If buffer filled up while we were flushing, flush again
if (sessionLogBuffer.length >= BATCH_SIZE) {
flushSessionLogs().catch((err) =>
logger.error("Error in follow-up AI session log flush:", err)
);
}
}
}
/**
* Schedule a flush if not already scheduled
*/
function scheduleFlush() {
if (flushTimer === null) {
flushTimer = setTimeout(() => {
flushTimer = null;
flushSessionLogs().catch((err) =>
logger.error("Error in scheduled AI session log flush:", err)
);
}, BATCH_INTERVAL_MS);
}
}
/** /**
* Gracefully flush all pending logs (call this on shutdown) * Gracefully flush all pending logs (call this on shutdown)
*/ */
export async function shutdownAiSessionLogger() {} export async function shutdownAiSessionLogger() {
if (flushTimer) {
clearTimeout(flushTimer);
flushTimer = null;
}
// Force flush even if one is in progress by waiting and retrying
while (isFlushInProgress) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
await flushSessionLogs();
}
export async function cleanUpOldLogs(orgId: string, retentionDays: number) {} async function getRetentionDays(orgId: string): Promise<number> {
// check cache first
const cached = await cache.get<number>(`org_${orgId}_aiSessionsDays`);
if (cached !== undefined) {
return cached;
}
const [org] = await db
.select({
settingsLogRetentionDaysAISessions:
orgs.settingsLogRetentionDaysAISessions
})
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (!org) {
return 0;
}
// store the result in cache
await cache.set(
`org_${orgId}_aiSessionsDays`,
org.settingsLogRetentionDaysAISessions,
300
);
return org.settingsLogRetentionDaysAISessions;
}
export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
// calculateCutoffTimestamp returns a seconds-epoch cutoff (built for
// requestAuditLog.timestamp), but aiSessionLog.createdAt is ms-epoch to
// match aiUsageRecords - convert before comparing.
const cutoffTimestampMs = calculateCutoffTimestamp(retentionDays) * 1000;
try {
await logsDb
.delete(aiSessionLog)
.where(
and(
lt(aiSessionLog.createdAt, cutoffTimestampMs),
eq(aiSessionLog.orgId, orgId)
)
);
} catch (error) {
logger.error("Error cleaning up old AI session logs:", error);
}
}
function truncateBody(value: string): { value: string; truncated: boolean } {
if (value.length <= AI_SESSION_LOG_MAX_BODY_CHARS) {
return { value, truncated: false };
}
return {
value: value.slice(0, AI_SESSION_LOG_MAX_BODY_CHARS),
truncated: true
};
}
export function logAiSession(data: { export function logAiSession(data: {
sessionId: string; sessionId: string;
@@ -22,4 +181,95 @@ export function logAiSession(data: {
siteResourceId: number | null; siteResourceId: number | null;
requestUserId: string | null; requestUserId: string | null;
virtualApiKeyId: string | null; virtualApiKeyId: string | null;
}): void {} }): void {
(async () => {
try {
// Check retention before buffering any logs
if (data.orgId) {
const retentionDays = await getRetentionDays(data.orgId);
if (retentionDays === 0) {
// do not log
return;
}
} else {
// No org resolved for this request - nothing to govern
// retention with, so don't log it.
return;
}
const requestBodyText = truncateBody(
JSON.stringify(data.requestBody ?? "")
);
const responseBodyText = truncateBody(data.responseText ?? "");
// Uniform, capability-agnostic transcript for search/display -
// computed from the untruncated originals so normalization sees
// the full content; the normalized result gets its own
// (typically much smaller) truncation pass below.
const normalizedRequestMessages = normalizeAiRequest(
data.capability,
data.requestBody
);
const normalizedResponseMessages = normalizeAiResponse(
data.capability,
data.responseText ?? "",
data.isStream
);
const normalizedRequestText = normalizedRequestMessages
? truncateBody(JSON.stringify(normalizedRequestMessages))
: null;
const normalizedResponseText = normalizedResponseMessages
? truncateBody(JSON.stringify(normalizedResponseMessages))
: null;
// Prevent unbounded buffer growth - drop oldest entries if buffer is too large
if (sessionLogBuffer.length >= MAX_BUFFER_SIZE) {
const dropped = sessionLogBuffer.splice(0, BATCH_SIZE);
logger.warn(
`AI session log buffer exceeded max size (${MAX_BUFFER_SIZE}), dropped ${dropped.length} oldest entries`
);
}
sessionLogBuffer.push({
sessionId: data.sessionId,
orgId: sanitizeString(data.orgId),
providerId: data.provider.providerId,
capability: data.capability,
resourceId: data.resourceId ?? undefined,
siteResourceId: data.siteResourceId ?? undefined,
userId: sanitizeString(data.requestUserId ?? undefined),
virtualApiKeyId: sanitizeString(
data.virtualApiKeyId ?? undefined
),
requestedModel: sanitizeString(data.requestedModel),
isStream: data.isStream,
requestBody: sanitizeString(requestBodyText.value),
responseBody: sanitizeString(responseBodyText.value),
normalizedRequest: normalizedRequestText
? sanitizeString(normalizedRequestText.value)
: undefined,
normalizedResponse: normalizedResponseText
? sanitizeString(normalizedResponseText.value)
: undefined,
truncated:
requestBodyText.truncated ||
responseBodyText.truncated ||
(normalizedRequestText?.truncated ?? false) ||
(normalizedResponseText?.truncated ?? false),
statusCode: data.statusCode,
createdAt: Date.now()
});
// Flush immediately if buffer is full, otherwise schedule a flush
if (sessionLogBuffer.length >= BATCH_SIZE) {
flushSessionLogs().catch((err) =>
logger.error("Error flushing AI session logs:", err)
);
} else {
scheduleFlush();
}
} catch (error) {
logger.error("Failed to log AI session", { error });
}
})();
}
+11 -20
View File
@@ -86,7 +86,7 @@ import {
type AiUsage type AiUsage
} from "@server/lib/aiUsageExtraction"; } from "@server/lib/aiUsageExtraction";
import { streamAiGatewayResponse } from "@server/routers/aiGateway/streamAiGatewayResponse"; import { streamAiGatewayResponse } from "@server/routers/aiGateway/streamAiGatewayResponse";
import { logAiSession } from "#dynamic/routers/aiGateway/logAiSession"; import { logAiSession } from "@server/routers/aiGateway/logAiSession";
const EXIT_NODE_RANGES_CACHE_KEY = "aiGateway:exitNodeRanges"; const EXIT_NODE_RANGES_CACHE_KEY = "aiGateway:exitNodeRanges";
const EXIT_NODE_RANGES_TTL_SEC = 6000; const EXIT_NODE_RANGES_TTL_SEC = 6000;
@@ -137,7 +137,7 @@ async function findClientByIp(ip: string): Promise<CachedClient> {
return result; return result;
} }
export type ProviderAttachment = { type ProviderAttachment = {
provider: AiProvider; provider: AiProvider;
accessMode: AccessMode; accessMode: AccessMode;
}; };
@@ -149,12 +149,12 @@ type ResourceModelPattern = {
enabled: boolean; enabled: boolean;
}; };
export type ProviderPatternLists = { type ProviderPatternLists = {
allows: string[]; allows: string[];
blocks: string[]; blocks: string[];
}; };
export type ResolvedTarget = { type ResolvedTarget = {
resourceId: number | null; resourceId: number | null;
siteResourceId: number | null; siteResourceId: number | null;
orgId: string | null; orgId: string | null;
@@ -362,7 +362,7 @@ function getRequestHeader(req: Request, name: string): string | undefined {
// request came through, per the trust middleware's resource-type header - // request came through, per the trust middleware's resource-type header -
// falls back to checking both (public preferred on overlap) only when that // falls back to checking both (public preferred on overlap) only when that
// header is absent, e.g. a request that reached the gateway outside Traefik. // header is absent, e.g. a request that reached the gateway outside Traefik.
export async function resolveTarget( async function resolveTarget(
host: string, host: string,
resourceType: AiGatewayResourceType | null resourceType: AiGatewayResourceType | null
): Promise<ResolvedTarget | null> { ): Promise<ResolvedTarget | null> {
@@ -728,9 +728,7 @@ export function recordAiGatewayCompletion(args: {
let cost: ReturnType<typeof calculateAiCost> = null; let cost: ReturnType<typeof calculateAiCost> = null;
if (upstreamSucceeded) { if (upstreamSucceeded) {
usage = usage = extractUsage(capability, responseText, isStream, headers) ?? emptyUsage();
extractUsage(capability, responseText, isStream, headers) ??
emptyUsage();
if (isUsageEmpty(usage)) { if (isUsageEmpty(usage)) {
usage = estimateUsage( usage = estimateUsage(
JSON.stringify(requestBody ?? ""), JSON.stringify(requestBody ?? ""),
@@ -812,17 +810,6 @@ export function recordAiGatewayCompletion(args: {
}); });
} }
// p-host is only used sometimes when overriding the host header for some
// middleware proxy. Shared with the model-discovery endpoint so both resolve
// the inference resource off the same hostname.
export function resolveGatewayHost(req: Request): string {
return (
(req.headers["p-host"] as string | undefined) ||
req.headers.host ||
""
).split(":")[0];
}
export async function handleAiGatewayProxy( export async function handleAiGatewayProxy(
req: Request, req: Request,
res: Response, res: Response,
@@ -831,7 +818,11 @@ export async function handleAiGatewayProxy(
try { try {
const def = AI_CAPABILITY_DEFS[capability]; const def = AI_CAPABILITY_DEFS[capability];
const host = resolveGatewayHost(req); const host = (
(req.headers["p-host"] as string | undefined) || // p-host is only used sometimes when overriding the host header for some middleware proxy
req.headers.host ||
""
).split(":")[0];
if (!host) { if (!host) {
return res return res
.status(HttpCode.BAD_REQUEST) .status(HttpCode.BAD_REQUEST)
@@ -99,7 +99,6 @@ async function fetchProviderTargets(
method: targets.method, method: targets.method,
exitNodeSubnet: sites.exitNodeSubnet, exitNodeSubnet: sites.exitNodeSubnet,
reachableAt: exitNodes.reachableAt, reachableAt: exitNodes.reachableAt,
exitNodeType: exitNodes.type,
hcHealth: targetHealthCheck.hcHealth hcHealth: targetHealthCheck.hcHealth
}) })
.from(targets) .from(targets)
@@ -120,12 +119,6 @@ async function fetchProviderTargets(
if (!row.exitNodeSubnet || !row.reachableAt) { if (!row.exitNodeSubnet || !row.reachableAt) {
continue; continue;
} }
// Sites connected to a remote exit node aren't reachable via a
// gerbil sidecar's /router/* proxy - only "gerbil" type exit nodes
// run that endpoint.
if (row.exitNodeType !== "gerbil") {
continue;
}
// A target with an active health check that's currently failing is // A target with an active health check that's currently failing is
// taken out of rotation. No health check (null) or "unknown" (check // taken out of rotation. No health check (null) or "unknown" (check
// hasn't run yet / hcEnabled is off) still routes normally, matching // hasn't run yet / hcEnabled is off) still routes normally, matching
-308
View File
@@ -1,308 +0,0 @@
import { Request, Response } from "express";
import { inArray } from "drizzle-orm";
import { z } from "zod";
import { aiModels, db } from "@server/db";
import {
providerHasCapability,
type AiCapability
} from "@server/lib/aiCapabilities";
import {
buildAiCapabilityErrorBody,
type AiCapabilityErrorKind
} from "@server/lib/aiGatewayAuthError";
import {
getAiGatewayResourceType,
isAiGatewayTrustHeaderValid
} from "@server/lib/aiGatewayTrust";
import { resolveEffectiveLists } from "@server/lib/aiInferenceResource";
import { listCatalogEntriesForType } from "@server/lib/aiModelCatalog";
import {
listPermittedModels,
paginateModels,
MODEL_PAGE_DEFAULT_LIMIT,
MODEL_PAGE_MAX_LIMIT,
type CatalogModelMetadata,
type ConfiguredModel,
type ModelDiscoveryProvider
} from "@server/lib/aiModelDiscovery";
import type { AiProviderType } from "@server/lib/aiProviderDefaults";
import {
resolveGatewayHost,
resolveTarget,
type ProviderAttachment,
type ProviderPatternLists
} from "@server/routers/aiGateway/pipeline";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
const CAPABILITY: AiCapability = "v1_models";
const querySchema = z.object({
limit: z.coerce.number().int().min(1).max(MODEL_PAGE_MAX_LIMIT).optional(),
after_id: z.string().min(1).optional(),
before_id: z.string().min(1).optional()
});
type ProviderModelLists = {
allowsByProvider: Map<number, string[]>;
blocksByProvider: Map<number, string[]>;
configuredByProvider: Map<number, Map<string, ConfiguredModel>>;
};
function errorResponse(
res: Response,
status: number,
kind: AiCapabilityErrorKind,
message: string
) {
return res
.status(status)
.json(buildAiCapabilityErrorBody(CAPABILITY, kind, message, status));
}
// Provider-level allow/block lists, plus the display name and creation time of
// every catalog row, so explicitly configured models are reported with the name
// the administrator gave them rather than a bare model id.
async function loadProviderModelLists(
providerIds: number[]
): Promise<ProviderModelLists> {
const lists: ProviderModelLists = {
allowsByProvider: new Map(),
blocksByProvider: new Map(),
configuredByProvider: new Map()
};
if (providerIds.length === 0) {
return lists;
}
const rows = await db
.select({
providerId: aiModels.providerId,
modelKey: aiModels.modelKey,
name: aiModels.name,
listType: aiModels.listType,
enabled: aiModels.enabled,
createdAt: aiModels.createdAt
})
.from(aiModels)
.where(inArray(aiModels.providerId, providerIds));
for (const row of rows) {
if (!row.enabled) {
continue;
}
const targetMap =
row.listType === "allow"
? lists.allowsByProvider
: lists.blocksByProvider;
const existing = targetMap.get(row.providerId) ?? [];
existing.push(row.modelKey);
targetMap.set(row.providerId, existing);
let configured = lists.configuredByProvider.get(row.providerId);
if (!configured) {
configured = new Map();
lists.configuredByProvider.set(row.providerId, configured);
}
configured.set(row.modelKey, {
name: row.name,
createdAt: row.createdAt
});
}
return lists;
}
function catalogMetadataForType(
type: AiProviderType
): Map<string, CatalogModelMetadata> {
const metadata = new Map<string, CatalogModelMetadata>();
for (const entry of listCatalogEntriesForType(type)) {
metadata.set(entry.model, {
maxInputTokens: entry.limits.input,
maxOutputTokens: entry.limits.output,
capabilities: entry.capabilities
});
}
return metadata;
}
function buildDiscoveryProviders(
attachments: ProviderAttachment[],
resourceListsByProvider: Map<number, ProviderPatternLists>,
lists: ProviderModelLists
): ModelDiscoveryProvider[] {
return attachments.map((attachment) => {
const providerId = attachment.provider.providerId;
const resourceLists = resourceListsByProvider.get(providerId);
const { allows, blocks } = resolveEffectiveLists({
accessMode: attachment.accessMode,
providerAllows: lists.allowsByProvider.get(providerId) ?? [],
providerBlocks: lists.blocksByProvider.get(providerId) ?? [],
resourceAllows: resourceLists?.allows ?? [],
resourceBlocks: resourceLists?.blocks ?? []
});
return {
providerId,
allows,
blocks,
catalog: catalogMetadataForType(
attachment.provider.type as AiProviderType
),
configured: lists.configuredByProvider.get(providerId) ?? new Map()
};
});
}
/**
* Serves Anthropic's model-discovery endpoints (`GET /v1/models` and
* `GET /v1/models/{id}`) for an inference resource. The gateway answers these
* itself rather than proxying: upstream providers either don't expose a model
* list at all or would expose models the resource's allow/block lists forbid,
* so the response is built from the same effective lists that gate inference.
*/
export async function handleV1Models(
req: Request,
res: Response
): Promise<any> {
try {
const host = resolveGatewayHost(req);
if (!host) {
return errorResponse(
res,
HttpCode.BAD_REQUEST,
"invalid_request",
"Missing Host header"
);
}
const resourceType = getAiGatewayResourceType(
req.headers as Record<string, string>
);
const target = await resolveTarget(host, resourceType);
if (!target) {
return errorResponse(
res,
HttpCode.NOT_FOUND,
"not_found",
"No inference resource found for this host"
);
}
// Same gate as the inference pipeline: public inference must pass
// Badger verify-session first, which is what stamps the trust header.
if (
target.resourceId != null &&
!isAiGatewayTrustHeaderValid(req.headers as Record<string, string>)
) {
return errorResponse(
res,
HttpCode.UNAUTHORIZED,
"authentication",
"Request must be authenticated via the inference resource"
);
}
if (target.attachments.length === 0) {
return errorResponse(
res,
HttpCode.FORBIDDEN,
"permission",
"No AI providers configured for this resource"
);
}
const capableAttachments = target.attachments.filter((a) =>
providerHasCapability(a.provider.capabilities, CAPABILITY)
);
if (capableAttachments.length === 0) {
return errorResponse(
res,
HttpCode.FORBIDDEN,
"permission",
`No AI provider on this resource supports ${CAPABILITY}`
);
}
const lists = await loadProviderModelLists(
capableAttachments.map((a) => a.provider.providerId)
);
const models = listPermittedModels(
buildDiscoveryProviders(
capableAttachments,
target.resourceListsByProvider,
lists
)
);
// `GET /v1/models/{id}` - a single model, 404 when this resource
// doesn't permit it.
const requestedModel = req.params?.model;
if (typeof requestedModel === "string" && requestedModel.length > 0) {
const model = models.find((m) => m.id === requestedModel);
if (!model) {
return errorResponse(
res,
HttpCode.NOT_FOUND,
"not_found",
`Model "${requestedModel}" is not available on this resource`
);
}
return res.status(HttpCode.OK).json(model);
}
const parsedQuery = querySchema.safeParse(req.query);
if (!parsedQuery.success) {
return errorResponse(
res,
HttpCode.BAD_REQUEST,
"invalid_request",
parsedQuery.error.issues[0]?.message ??
"Invalid pagination parameters"
);
}
const page = paginateModels(
models,
parsedQuery.data.limit ?? MODEL_PAGE_DEFAULT_LIMIT,
{
afterId: parsedQuery.data.after_id,
beforeId: parsedQuery.data.before_id
}
);
if ("error" in page) {
return errorResponse(
res,
HttpCode.BAD_REQUEST,
"invalid_request",
page.error
);
}
logger.debug("AI gateway model discovery", {
host,
resourceId: target.resourceId,
siteResourceId: target.siteResourceId,
providers: capableAttachments.length,
total: models.length,
returned: page.data.length
});
return res.status(HttpCode.OK).json({
data: page.data,
has_more: page.has_more,
first_id: page.data[0]?.id ?? null,
last_id: page.data[page.data.length - 1]?.id ?? null
});
} catch (error) {
logger.error(error);
return errorResponse(
res,
HttpCode.INTERNAL_SERVER_ERROR,
"internal",
"Failed to list models"
);
}
}
+7 -2
View File
@@ -6,7 +6,7 @@ import HttpCode from "@server/types/HttpCode";
import { response } from "@server/lib/response"; import { response } from "@server/lib/response";
import { db } from "@server/db"; import { db } from "@server/db";
import { passwordResetTokens, users } from "@server/db"; import { passwordResetTokens, users } from "@server/db";
import { eq } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import { alphabet, generateRandomString, sha256 } from "oslo/crypto"; import { alphabet, generateRandomString, sha256 } from "oslo/crypto";
import { createDate } from "oslo"; import { createDate } from "oslo";
import logger from "@server/logger"; import logger from "@server/logger";
@@ -49,7 +49,12 @@ export async function requestPasswordReset(
const existingUser = await db const existingUser = await db
.select() .select()
.from(users) .from(users)
.where(eq(users.email, email)); .where(
and(
eq(users.email, email),
eq(users.type, UserType.Internal)
)
);
if (!existingUser || !existingUser.length) { if (!existingUser || !existingUser.length) {
await randomDelay(2000); await randomDelay(2000);
@@ -23,12 +23,6 @@ export async function createCertificate(
throw new Error(`Domain with ID ${domainId} not found`); throw new Error(`Domain with ID ${domainId} not found`);
} }
// Note: certificates.domain has a global UNIQUE constraint (it is not
// scoped per-domainId), so existence must be checked by domain value
// alone. Filtering on domainId here as well can cause this check to
// miss an existing cert (e.g. if it was stored under a different but
// still-valid domainId), leading to an INSERT that then fails on the
// unique constraint.
let existing: Certificate[] = []; let existing: Certificate[] = [];
if (domainRecord.type == "ns" || domainRecord.type == "wildcard") { if (domainRecord.type == "ns" || domainRecord.type == "wildcard") {
const domainLevelDown = domain.split(".").slice(1).join("."); const domainLevelDown = domain.split(".").slice(1).join(".");
@@ -38,13 +32,16 @@ export async function createCertificate(
.select() .select()
.from(certificates) .from(certificates)
.where( .where(
or( and(
eq(certificates.domain, domain), eq(certificates.domainId, domainId),
and( or(
eq(certificates.wildcard, true), eq(certificates.domain, domain),
or( and(
eq(certificates.domain, domainLevelDown), eq(certificates.wildcard, true),
eq(certificates.domain, wildcardPrefixed) or(
eq(certificates.domain, domainLevelDown),
eq(certificates.domain, wildcardPrefixed)
)
) )
) )
) )
@@ -54,7 +51,12 @@ export async function createCertificate(
existing = await trx existing = await trx
.select() .select()
.from(certificates) .from(certificates)
.where(eq(certificates.domain, domain)); // exact match for non-NS domains .where(
and(
eq(certificates.domainId, domainId),
eq(certificates.domain, domain) // exact match for non-NS domains
)
);
} }
if (existing.length > 0) { if (existing.length > 0) {
@@ -85,22 +87,16 @@ export async function createCertificate(
} }
} }
// No cert found, create a new one in pending state. onConflictDoNothing // No cert found, create a new one in pending state
// guards against the domain having been inserted concurrently (or under await trx.insert(certificates).values({
// a different domainId) between the existence check above and this domain: domainToWrite,
// insert, since certificates.domain is globally unique. domainId,
await trx wildcard:
.insert(certificates) domainRecord.type == "ns" ||
.values({ (domainRecord.type == "wildcard" &&
domain: domainToWrite, domainRecord.preferWildcardCert), // we can only create wildcard certs for NS domains
domainId, status: "pending",
wildcard: updatedAt: Math.floor(Date.now() / 1000),
domainRecord.type == "ns" || createdAt: Math.floor(Date.now() / 1000)
(domainRecord.type == "wildcard" && });
domainRecord.preferWildcardCert), // we can only create wildcard certs for NS domains
status: "pending",
updatedAt: Math.floor(Date.now() / 1000),
createdAt: Math.floor(Date.now() / 1000)
})
.onConflictDoNothing();
} }
+15
View File
@@ -1490,6 +1490,21 @@ authenticated.get(
logs.exportRequestAuditLogs logs.exportRequestAuditLogs
); );
authenticated.get(
"/org/:orgId/logs/ai",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.viewLogs),
logs.queryAiSessionLogs
);
authenticated.get(
"/org/:orgId/logs/ai/export",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
logs.exportAiSessionLogs
);
authenticated.get( authenticated.get(
"/org/:orgId/logs/ai/usage/filters", "/org/:orgId/logs/ai/usage/filters",
verifyOrgAccess, verifyOrgAccess,
+7 -1
View File
@@ -15,7 +15,13 @@ export async function createExitNode(
if (!exitNodeQuery) { if (!exitNodeQuery) {
const { value: address, release } = await getNextAvailableSubnet(); const { value: address, release } = await getNextAvailableSubnet();
try { try {
// TODO: eventually we will want to get the next available port so that we can multiple exit nodes
// const listenPort = await getNextAvailablePort();
const listenPort = config.getRawConfig().gerbil.start_port; const listenPort = config.getRawConfig().gerbil.start_port;
let subEndpoint = "";
if (config.getRawConfig().gerbil.use_subdomain) {
subEndpoint = await getUniqueExitNodeEndpointName();
}
const exitNodeName = const exitNodeName =
config.getRawConfig().gerbil.exit_node_name || config.getRawConfig().gerbil.exit_node_name ||
@@ -26,7 +32,7 @@ export async function createExitNode(
.insert(exitNodes) .insert(exitNodes)
.values({ .values({
publicKey, publicKey,
endpoint: config.getRawConfig().gerbil.base_endpoint, endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`,
address, address,
online: true, online: true,
listenPort, listenPort,
+15
View File
@@ -1532,6 +1532,21 @@ authenticated.get(
logs.exportRequestAuditLogs logs.exportRequestAuditLogs
); );
authenticated.get(
"/org/:orgId/logs/ai",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.viewLogs),
logs.queryAiSessionLogs
);
authenticated.get(
"/org/:orgId/logs/ai/export",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.exportLogs),
logActionAudit(ActionsEnum.exportLogs),
logs.exportAiSessionLogs
);
authenticated.get( authenticated.get(
"/org/:orgId/logs/ai/usage/filters", "/org/:orgId/logs/ai/usage/filters",
verifyApiKeyOrgAccess, verifyApiKeyOrgAccess,
+2 -241
View File
@@ -1,248 +1,9 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { db } from "@server/db";
import { MessageHandler } from "@server/routers/ws"; import { MessageHandler } from "@server/routers/ws";
import { sites, Newt, orgs, clients, clientSitesAssociationsCache, users } from "@server/db";
import { and, eq, inArray } from "drizzle-orm";
import logger from "@server/logger";
import { inflate } from "zlib";
import { promisify } from "util";
import { logRequestAudit } from "@server/routers/badger/logRequestAudit";
import { getCountryCodeForIp } from "@server/lib/geoip";
export async function flushRequestLogToDb(): Promise<void> { export async function flushRequestLogToDb(): Promise<void> {
return; return;
} }
const zlibInflate = promisify(inflate);
interface HTTPRequestLogData {
requestId: string;
resourceId: number; // siteResourceId
timestamp: string; // ISO 8601
method: string;
scheme: string; // "http" or "https"
host: string;
path: string;
rawQuery?: string;
userAgent?: string;
sourceAddr: string; // ip:port
tls: boolean;
}
/**
* Decompress a base64-encoded zlib-compressed string into parsed JSON.
*/
async function decompressRequestLog(
compressed: string
): Promise<HTTPRequestLogData[]> {
const compressedBuffer = Buffer.from(compressed, "base64");
const decompressed = await zlibInflate(compressedBuffer);
const jsonString = decompressed.toString("utf-8");
const parsed = JSON.parse(jsonString);
if (!Array.isArray(parsed)) {
throw new Error("Decompressed request log data is not an array");
}
return parsed;
}
export const handleRequestLogMessage: MessageHandler = async (context) => { export const handleRequestLogMessage: MessageHandler = async (context) => {
const { message, client } = context; return;
const newt = client as Newt; };
if (!newt) {
logger.warn("Request log received but no newt client in context");
return;
}
if (!newt.siteId) {
logger.warn("Request log received but newt has no siteId");
return;
}
if (!message.data?.compressed) {
logger.warn("Request log message missing compressed data");
return;
}
// Look up the org for this site and check retention settings
const [site] = await db
.select({
orgId: sites.orgId,
orgSubnet: orgs.subnet,
settingsLogRetentionDaysRequest:
orgs.settingsLogRetentionDaysRequest
})
.from(sites)
.innerJoin(orgs, eq(sites.orgId, orgs.orgId))
.where(eq(sites.siteId, newt.siteId));
if (!site) {
logger.warn(
`Request log received but site ${newt.siteId} not found in database`
);
return;
}
const orgId = site.orgId;
if (site.settingsLogRetentionDaysRequest === 0) {
logger.debug(
`Request log retention is disabled for org ${orgId}, skipping`
);
return;
}
let entries: HTTPRequestLogData[];
try {
entries = await decompressRequestLog(message.data.compressed);
} catch (error) {
logger.error("Failed to decompress request log data:", error);
return;
}
if (entries.length === 0) {
return;
}
logger.debug(`Request log entries: ${JSON.stringify(entries)}`);
// Build a map from sourceIp → external endpoint string by joining clients
// with clientSitesAssociationsCache. The endpoint is the real-world IP:port
// of the client device and is used for GeoIP lookup.
const ipToEndpoint = new Map<string, string>();
// Build a map from sourceIp → the user associated with the client (if any)
const ipToUser = new Map<string, { username: string; userId: string }>();
const cidrSuffix = site.orgSubnet?.includes("/")
? site.orgSubnet.substring(site.orgSubnet.indexOf("/"))
: null;
if (cidrSuffix) {
const uniqueSourceAddrs = new Set<string>();
for (const entry of entries) {
if (entry.sourceAddr) {
uniqueSourceAddrs.add(entry.sourceAddr);
}
}
if (uniqueSourceAddrs.size > 0) {
const subnetQueries = Array.from(uniqueSourceAddrs).map((addr) => {
const ip = addr.includes(":") ? addr.split(":")[0] : addr;
return `${ip}${cidrSuffix}`;
});
const matchedClients = await db
.select({
subnet: clients.subnet,
endpoint: clientSitesAssociationsCache.endpoint,
username: users.username,
userId: users.userId
})
.from(clients)
.innerJoin(
clientSitesAssociationsCache,
and(
eq(
clientSitesAssociationsCache.clientId,
clients.clientId
),
eq(clientSitesAssociationsCache.siteId, newt.siteId)
)
)
.leftJoin(users, eq(clients.userId, users.userId))
.where(
and(
eq(clients.orgId, orgId),
inArray(clients.subnet, subnetQueries)
)
);
for (const c of matchedClients) {
const ip = c.subnet.split("/")[0];
if (c.endpoint) {
ipToEndpoint.set(ip, c.endpoint);
}
if (c.userId && c.username) {
ipToUser.set(ip, { userId: c.userId, username: c.username });
}
}
}
}
for (const entry of entries) {
if (
!entry.requestId ||
!entry.resourceId ||
!entry.method ||
!entry.scheme ||
!entry.host ||
!entry.path ||
!entry.sourceAddr
) {
logger.debug(
`Skipping request log entry with missing required fields: ${JSON.stringify(entry)}`
);
continue;
}
const originalRequestURL =
entry.scheme +
"://" +
entry.host +
entry.path +
(entry.rawQuery ? "?" + entry.rawQuery : "");
// Resolve the client's external endpoint for GeoIP lookup.
// sourceAddr is the WireGuard IP (possibly ip:port), so strip the port.
const sourceIp = entry.sourceAddr.includes(":")
? entry.sourceAddr.split(":")[0]
: entry.sourceAddr;
const endpoint = ipToEndpoint.get(sourceIp);
let location: string | undefined;
if (endpoint) {
const endpointIp = endpoint.includes(":")
? endpoint.split(":")[0]
: endpoint;
location = await getCountryCodeForIp(endpointIp);
}
const user = ipToUser.get(sourceIp);
await logRequestAudit(
{
action: true,
reason: 108,
siteResourceId: entry.resourceId,
orgId,
location,
user
},
{
path: entry.path,
originalRequestURL,
scheme: entry.scheme,
host: entry.host,
method: entry.method,
tls: entry.tls,
requestIp: entry.sourceAddr
}
);
}
logger.debug(
`Buffered ${entries.length} request log entry/entries from newt ${newt.newtId} (site ${newt.siteId})`
);
};
@@ -42,8 +42,7 @@ export const handleOlmExitNodesRequestMessage: MessageHandler = async (
client.orgId, client.orgId,
true, true,
noCloud || false, noCloud || false,
olm.clientId, olm.clientId
true // don't select remote exit nodes for clients
); // filter for only the online ones ); // filter for only the online ones
let lastExitNodeId = null; let lastExitNodeId = null;
-36
View File
@@ -147,42 +147,6 @@ export async function updateOrg(
parsedBody.data.settingsEnableGlobalNewtAutoUpdate = false; // force it off parsedBody.data.settingsEnableGlobalNewtAutoUpdate = false; // force it off
} }
// Check access logs feature
const hasAccessLogsFeature = await isLicensedOrSubscribed(
orgId,
tierMatrix[TierFeature.AccessLogs]
);
if (!hasAccessLogsFeature) {
parsedBody.data.settingsLogRetentionDaysAccess = undefined;
}
// Check action logs feature
const hasActionLogsFeature = await isLicensedOrSubscribed(
orgId,
tierMatrix[TierFeature.ActionLogs]
);
if (!hasActionLogsFeature) {
parsedBody.data.settingsLogRetentionDaysAction = undefined;
}
// Check connection logs feature
const hasConnectionLogsFeature = await isLicensedOrSubscribed(
orgId,
tierMatrix[TierFeature.ConnectionLogs]
);
if (!hasConnectionLogsFeature) {
parsedBody.data.settingsLogRetentionDaysConnection = undefined;
}
// Check AI session logs feature
const hasAISessionLogsFeature = await isLicensedOrSubscribed(
orgId,
tierMatrix[TierFeature.AISessionLogs]
);
if (!hasAISessionLogsFeature) {
parsedBody.data.settingsLogRetentionDaysAISessions = undefined;
}
if (build == "saas") { if (build == "saas") {
const { tier } = await getOrgTierData(orgId); const { tier } = await getOrgTierData(orgId);
+3 -38
View File
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db } from "@server/db"; import { db } from "@server/db";
import { idp, resources, userPolicies, userResources, users } from "@server/db"; // Assuming these are the correct tables import { idp, userResources, users } from "@server/db"; // Assuming these are the correct tables
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -14,23 +14,7 @@ const listResourceUsersSchema = z.strictObject({
resourceId: z.coerce.number().int().positive() resourceId: z.coerce.number().int().positive()
}); });
async function queryUsers(resourceId: number, policyId: number | null) { async function queryUsers(resourceId: number) {
if (policyId !== null) {
return await db
.select({
userId: userPolicies.userId,
username: users.username,
type: users.type,
idpName: idp.name,
idpId: users.idpId,
email: users.email
})
.from(userPolicies)
.innerJoin(users, eq(userPolicies.userId, users.userId))
.leftJoin(idp, eq(users.idpId, idp.idpId))
.where(eq(userPolicies.resourcePolicyId, policyId));
}
return await db return await db
.select({ .select({
userId: userResources.userId, userId: userResources.userId,
@@ -120,26 +104,7 @@ export async function listResourceUsers(
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
const [resource] = await db const resourceUsersList = await queryUsers(resourceId);
.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
const isInlinePolicy =
resource.resourcePolicyId === null &&
resource.defaultResourcePolicyId !== null;
const resourceUsersList = await queryUsers(
resourceId,
isInlinePolicy ? resource.defaultResourcePolicyId! : null
);
return response<ListResourceUsersResponse>(res, { return response<ListResourceUsersResponse>(res, {
data: { data: {
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/clients/add", path: "/private-resource/{siteResourceId}/clients/add",
description: description:
"Add a single client to a site resource. Clients with a userId cannot be added.", "Add a single client to a site resource. Clients with a userId cannot be added.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/roles/add", path: "/private-resource/{siteResourceId}/roles/add",
description: "Add a single role to a site resource.", description: "Add a single role to a site resource.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], tags: [OpenAPITags.PrivateResource, OpenAPITags.Role],
request: { request: {
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/users/add", path: "/private-resource/{siteResourceId}/users/add",
description: "Add a single user to a site resource.", description: "Add a single user to a site resource.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.User], tags: [OpenAPITags.PrivateResource, OpenAPITags.User],
request: { request: {
@@ -63,7 +63,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "get", method: "get",
path: "/private-resource/{resourceId}/clients", path: "/private-resource/{siteResourceId}/clients",
description: "List all clients for a site resource.", description: "List all clients for a site resource.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
request: { request: {
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "get", method: "get",
path: "/private-resource/{resourceId}/roles", path: "/private-resource/{siteResourceId}/roles",
description: "List all roles for a site resource.", description: "List all roles for a site resource.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], tags: [OpenAPITags.PrivateResource, OpenAPITags.Role],
request: { request: {
@@ -67,7 +67,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "get", method: "get",
path: "/private-resource/{resourceId}/users", path: "/private-resource/{siteResourceId}/users",
description: "List all users for a site resource.", description: "List all users for a site resource.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.User], tags: [OpenAPITags.PrivateResource, OpenAPITags.User],
request: { request: {
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/clients/remove", path: "/private-resource/{siteResourceId}/clients/remove",
description: description:
"Remove a single client from a site resource. Clients with a userId cannot be removed.", "Remove a single client from a site resource. Clients with a userId cannot be removed.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/roles/remove", path: "/private-resource/{siteResourceId}/roles/remove",
description: "Remove a single role from a site resource.", description: "Remove a single role from a site resource.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], tags: [OpenAPITags.PrivateResource, OpenAPITags.Role],
request: { request: {
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/users/remove", path: "/private-resource/{siteResourceId}/users/remove",
description: "Remove a single user from a site resource.", description: "Remove a single user from a site resource.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.User], tags: [OpenAPITags.PrivateResource, OpenAPITags.User],
request: { request: {
@@ -64,7 +64,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/clients", path: "/private-resource/{siteResourceId}/clients",
description: description:
"Set clients for a site resource. This will replace all existing clients. Clients with a userId cannot be added.", "Set clients for a site resource. This will replace all existing clients. Clients with a userId cannot be added.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
@@ -65,7 +65,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/roles", path: "/private-resource/{siteResourceId}/roles",
description: description:
"Set roles for a site resource. This will replace all existing roles.", "Set roles for a site resource. This will replace all existing roles.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], tags: [OpenAPITags.PrivateResource, OpenAPITags.Role],
@@ -66,7 +66,7 @@ registry.registerPath({
registry.registerPath({ registry.registerPath({
method: "post", method: "post",
path: "/private-resource/{resourceId}/users", path: "/private-resource/{siteResourceId}/users",
description: description:
"Set users for a site resource. This will replace all existing users.", "Set users for a site resource. This will replace all existing users.",
tags: [OpenAPITags.PrivateResource, OpenAPITags.User], tags: [OpenAPITags.PrivateResource, OpenAPITags.User],
+1 -3
View File
@@ -7,8 +7,7 @@ import {
handleNewtExitNodesRequestMessage, handleNewtExitNodesRequestMessage,
handleApplyBlueprintMessage, handleApplyBlueprintMessage,
handleNewtPingMessage, handleNewtPingMessage,
handleNewtDisconnectingMessage, handleNewtDisconnectingMessage
handleRequestLogMessage
} from "../newt"; } from "../newt";
import { import {
handleOlmRegisterMessage, handleOlmRegisterMessage,
@@ -47,6 +46,5 @@ export const messageHandlers: Record<string, MessageHandler> = {
"newt/ping/request": handleNewtExitNodesRequestMessage, "newt/ping/request": handleNewtExitNodesRequestMessage,
"newt/blueprint/apply": handleApplyBlueprintMessage, "newt/blueprint/apply": handleApplyBlueprintMessage,
"newt/healthcheck/status": handleHealthcheckStatusMessage, "newt/healthcheck/status": handleHealthcheckStatusMessage,
"newt/request-log": handleRequestLogMessage,
"ws/round-trip/complete": handleRoundTripMessage "ws/round-trip/complete": handleRoundTripMessage
}; };
+1 -1
View File
@@ -388,7 +388,7 @@ const setupConnection = async (
} }
} }
} catch (error) { } catch (error) {
logger.warn("Message handling error:", error); logger.error("Message handling error:", error);
ws.send( ws.send(
JSON.stringify({ JSON.stringify({
type: "error", type: "error",
@@ -8,8 +8,6 @@ import ExitNodesTable, {
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { build } from "@server/build";
import { redirect } from "next/navigation";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Remote Exit Nodes" title: "Remote Exit Nodes"
@@ -24,10 +22,6 @@ export const dynamic = "force-dynamic";
export default async function RemoteExitNodesPage( export default async function RemoteExitNodesPage(
props: RemoteExitNodesPageProps props: RemoteExitNodesPageProps
) { ) {
if (build != "saas") {
redirect("/");
}
const params = await props.params; const params = await props.params;
let remoteExitNodes: ListRemoteExitNodesResponse["remoteExitNodes"] = []; let remoteExitNodes: ListRemoteExitNodesResponse["remoteExitNodes"] = [];
try { try {
@@ -345,7 +345,6 @@ export default function AiProviderNetworkPage() {
ref={targetsFormRef} ref={targetsFormRef}
orgId={orgId} orgId={orgId}
isHttp isHttp
isAiProvider
providerId={provider.providerId} providerId={provider.providerId}
initialTargets={ initialTargets={
isTargetModeSaved ? remoteTargets : [] isTargetModeSaved ? remoteTargets : []
@@ -682,7 +682,6 @@ export default function CreateAiProviderPage() {
<ProxyResourceTargetsForm <ProxyResourceTargetsForm
orgId={orgId} orgId={orgId}
isHttp isHttp
isAiProvider
onChange={(nextTargets) => { onChange={(nextTargets) => {
targetsRef.current = nextTargets; targetsRef.current = nextTargets;
}} }}
@@ -298,6 +298,101 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
)} )}
/> />
<FormField
control={form.control}
name="settingsLogRetentionDaysAISessions"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("logRetentionAISessionsLabel")}
</FormLabel>
<FormControl>
<Select
value={field.value.toString()}
onValueChange={(value) =>
field.onChange(
parseInt(value, 10)
)
}
>
<SelectTrigger>
<SelectValue
placeholder={t(
"selectLogRetention"
)}
/>
</SelectTrigger>
<SelectContent>
{LOG_RETENTION_OPTIONS.filter(
(option) => {
if (
build != "saas"
) {
return true;
}
let maxDays: number;
if (
!subscriptionTier
) {
// No tier
maxDays = 3;
} else if (
subscriptionTier ==
"enterprise"
) {
// Enterprise - no limit
return true;
} else if (
subscriptionTier ==
"tier3"
) {
maxDays = 90;
} else if (
subscriptionTier ==
"tier2"
) {
maxDays = 30;
} else if (
subscriptionTier ==
"tier1"
) {
maxDays = 7;
} else {
// Default to most restrictive
maxDays = 3;
}
// Filter out options that exceed the max
// Special values: -1 (forever) and 9001 (end of year) should be filtered
if (
option.value <
0 ||
option.value >
maxDays
) {
return false;
}
return true;
}
).map((option) => (
<SelectItem
key={option.value}
value={option.value.toString()}
>
{t(option.label)}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{!env.flags.disableEnterpriseFeatures && ( {!env.flags.disableEnterpriseFeatures && (
<> <>
<PaidFeaturesAlert <PaidFeaturesAlert
@@ -679,131 +774,6 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
); );
}} }}
/> />
<FormField
control={form.control}
name="settingsLogRetentionDaysAISessions"
render={({ field }) => {
const isDisabled = !isPaidUser(
tierMatrix.aiSessionLogs
);
return (
<FormItem>
<FormLabel>
{t(
"logRetentionAISessionsLabel"
)}
</FormLabel>
<FormControl>
<Select
value={field.value.toString()}
onValueChange={(
value
) => {
if (
!isDisabled
) {
field.onChange(
parseInt(
value,
10
)
);
}
}}
disabled={
isDisabled
}
>
<SelectTrigger>
<SelectValue
placeholder={t(
"selectLogRetention"
)}
/>
</SelectTrigger>
<SelectContent>
{LOG_RETENTION_OPTIONS.filter(
(
option
) => {
if (
build !=
"saas"
) {
return true;
}
let maxDays: number;
if (
!subscriptionTier
) {
// No tier
maxDays = 3;
} else if (
subscriptionTier ==
"enterprise"
) {
// Enterprise - no limit
return true;
} else if (
subscriptionTier ==
"tier3"
) {
maxDays = 90;
} else if (
subscriptionTier ==
"tier2"
) {
maxDays = 30;
} else if (
subscriptionTier ==
"tier1"
) {
maxDays = 7;
} else {
// Default to most restrictive
maxDays = 3;
}
// Filter out options that exceed the max
// Special values: -1 (forever) and 9001 (end of year) should be filtered
if (
option.value <
0 ||
option.value >
maxDays
) {
return false;
}
return true;
}
).map(
(
option
) => (
<SelectItem
key={
option.value
}
value={option.value.toString()}
>
{t(
option.label
)}
</SelectItem>
)
)}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</> </>
)} )}
</form> </form>
+1 -13
View File
@@ -3,13 +3,11 @@ import { ColumnFilterButton } from "@app/components/ColumnFilterButton";
import { DateTimeValue } from "@app/components/DateTimePicker"; import { DateTimeValue } from "@app/components/DateTimePicker";
import { LogDataTable } from "@app/components/LogDataTable"; import { LogDataTable } from "@app/components/LogDataTable";
import { AiSessionChatView } from "@app/components/AiSessionChatView"; import { AiSessionChatView } from "@app/components/AiSessionChatView";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import LogRetentionWarning from "@app/components/LogRetentionWarning"; import LogRetentionWarning from "@app/components/LogRetentionWarning";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { useOrgContext } from "@app/hooks/useOrgContext"; import { useOrgContext } from "@app/hooks/useOrgContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@@ -17,8 +15,6 @@ import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
import { getPrivateResourceSettingsHref } from "@app/lib/launcherResourceAdminHref"; import { getPrivateResourceSettingsHref } from "@app/lib/launcherResourceAdminHref";
import { logQueries } from "@app/lib/queries"; import { logQueries } from "@app/lib/queries";
import { formatVirtualApiKeyPreview } from "@app/lib/virtualApiKeyFormat"; import { formatVirtualApiKeyPreview } from "@app/lib/virtualApiKeyFormat";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
@@ -33,7 +29,6 @@ const capabilityLabels: Record<string, string> = {
openai_chat: "OpenAI Chat Completions", openai_chat: "OpenAI Chat Completions",
openai_responses: "OpenAI Responses", openai_responses: "OpenAI Responses",
anthropic_messages: "Anthropic Messages", anthropic_messages: "Anthropic Messages",
v1_models: "Models List",
gemini_generate_content: "Gemini", gemini_generate_content: "Gemini",
google_generate_content: "Vertex AI (Generate Content)", google_generate_content: "Vertex AI (Generate Content)",
google_raw_predict: "Vertex AI (Raw Predict)", google_raw_predict: "Vertex AI (Raw Predict)",
@@ -49,7 +44,6 @@ export default function AiSessionLogsPage() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const { org } = useOrgContext(); const { org } = useOrgContext();
const { isPaidUser } = usePaidStatus();
const [isExporting, startTransition] = useTransition(); const [isExporting, startTransition] = useTransition();
@@ -139,8 +133,7 @@ export default function AiSessionLogsPage() {
...logQueries.aiSessions({ ...logQueries.aiSessions({
orgId: orgId as string, orgId: orgId as string,
filters: queryFilters filters: queryFilters
}), })
enabled: isPaidUser(tierMatrix.aiSessionLogs) && build !== "oss"
}); });
const rows = isLoading ? generateSampleAiSessionLogs() : (data?.log ?? []); const rows = isLoading ? generateSampleAiSessionLogs() : (data?.log ?? []);
@@ -652,8 +645,6 @@ export default function AiSessionLogsPage() {
description={t("aiSessionLogsDescription")} description={t("aiSessionLogsDescription")}
/> />
<PaidFeaturesAlert tiers={tierMatrix.aiSessionLogs} />
{org.org.settingsLogRetentionDaysAISessions === 0 && ( {org.org.settingsLogRetentionDaysAISessions === 0 && (
<LogRetentionWarning <LogRetentionWarning
orgId={orgId as string} orgId={orgId as string}
@@ -688,9 +679,6 @@ export default function AiSessionLogsPage() {
pageSize={pageSize} pageSize={pageSize}
expandable={true} expandable={true}
renderExpandedRow={renderExpandedRow} renderExpandedRow={renderExpandedRow}
disabled={
!isPaidUser(tierMatrix.aiSessionLogs) || build === "oss"
}
/> />
</> </>
); );
@@ -88,8 +88,8 @@ export default async function ClientResourcesPage(
siteNiceIds: siteResource.siteNiceIds, siteNiceIds: siteResource.siteNiceIds,
niceId: siteResource.niceId, niceId: siteResource.niceId,
enabled: siteResource.enabled, enabled: siteResource.enabled,
tcpPortRangeString: siteResource.tcpPortRangeString ?? null, tcpPortRangeString: siteResource.tcpPortRangeString || null,
udpPortRangeString: siteResource.udpPortRangeString ?? null, udpPortRangeString: siteResource.udpPortRangeString || null,
disableIcmp: siteResource.disableIcmp || false, disableIcmp: siteResource.disableIcmp || false,
authDaemonMode: siteResource.authDaemonMode ?? null, authDaemonMode: siteResource.authDaemonMode ?? null,
authDaemonPort: siteResource.authDaemonPort ?? null, authDaemonPort: siteResource.authDaemonPort ?? null,
@@ -113,8 +113,6 @@ type ProxyResourceTargetsFormProps = {
hideSaveButton?: boolean; hideSaveButton?: boolean;
/** Hide the advanced mode toggle and always use non-advanced mode (e.g. AI providers) */ /** Hide the advanced mode toggle and always use non-advanced mode (e.g. AI providers) */
disableAdvancedMode?: boolean; disableAdvancedMode?: boolean;
/** Targets picker is for an AI provider (changes which routing warnings are shown) */
isAiProvider?: boolean;
}; };
export const ProxyResourceTargetsForm = forwardRef< export const ProxyResourceTargetsForm = forwardRef<
@@ -133,8 +131,7 @@ export const ProxyResourceTargetsForm = forwardRef<
emptyMessage, emptyMessage,
embedded = false, embedded = false,
hideSaveButton = false, hideSaveButton = false,
disableAdvancedMode = false, disableAdvancedMode = false
isAiProvider = false
}, },
ref ref
) { ) {
@@ -262,14 +259,6 @@ export const ProxyResourceTargetsForm = forwardRef<
}) })
); );
const { data: remoteExitNodes = [] } = useQuery({
...orgQueries.remoteExitNodes({ orgId }),
enabled: build === "saas" && isAiProvider
});
const hasRemoteExitNodes = remoteExitNodes.some(
(node) => node.exitNodeId !== null
);
const updateTarget = useCallback( const updateTarget = useCallback(
(targetId: number, data: Partial<LocalTarget>) => { (targetId: number, data: Partial<LocalTarget>) => {
setTargets((prevTargets) => { setTargets((prevTargets) => {
@@ -983,7 +972,6 @@ export const ProxyResourceTargetsForm = forwardRef<
</div> </div>
)} )}
{build === "saas" && {build === "saas" &&
!isAiProvider &&
targets.length > 1 && targets.length > 1 &&
new Set(targets.map((t) => t.siteId)).size > 1 && ( new Set(targets.map((t) => t.siteId)).size > 1 && (
<p className="text-sm text-muted-foreground mt-3"> <p className="text-sm text-muted-foreground mt-3">
@@ -1000,11 +988,6 @@ export const ProxyResourceTargetsForm = forwardRef<
. .
</p> </p>
)} )}
{build === "saas" && isAiProvider && hasRemoteExitNodes && (
<p className="text-sm text-muted-foreground mt-3">
{t("aiProviderRemoteNodeTargetsWarning")}
</p>
)}
</> </>
); );
+1 -1
View File
@@ -100,7 +100,7 @@ export default async function Page(props: {
loginIdps = idpsRes.data.data.idps.map((idp) => ({ loginIdps = idpsRes.data.data.idps.map((idp) => ({
idpId: idp.idpId, idpId: idp.idpId,
name: idp.name, name: idp.name,
variant: idp.variant ?? idp.type variant: idp.type
})) as LoginFormIDP[]; })) as LoginFormIDP[];
} }
} else { } else {
@@ -20,7 +20,6 @@ const CAPABILITY_LABEL_KEYS: Record<AiCapability, string> = {
openai_chat: "aiCapabilityOpenaiChat", openai_chat: "aiCapabilityOpenaiChat",
openai_responses: "aiCapabilityOpenaiResponses", openai_responses: "aiCapabilityOpenaiResponses",
anthropic_messages: "aiCapabilityAnthropicMessages", anthropic_messages: "aiCapabilityAnthropicMessages",
v1_models: "aiCapabilityV1Models",
gemini_generate_content: "aiCapabilityGeminiGenerateContent", gemini_generate_content: "aiCapabilityGeminiGenerateContent",
bedrock_model_invoke: "aiCapabilityBedrockModelInvoke", bedrock_model_invoke: "aiCapabilityBedrockModelInvoke",
google_generate_content: "aiCapabilityGoogleGenerateContent", google_generate_content: "aiCapabilityGoogleGenerateContent",
+5 -5
View File
@@ -79,7 +79,7 @@ function MessageBubble({ message }: { message: NormalizedAiMessage }) {
)} )}
</div> </div>
<div <div
className={`min-w-0 max-w-[80%] rounded-lg px-3 py-2 text-sm whitespace-pre-wrap break-words ${ className={`max-w-[80%] rounded-lg px-3 py-2 text-sm whitespace-pre-wrap break-words ${
isUser isUser
? "bg-primary text-primary-foreground" ? "bg-primary text-primary-foreground"
: isTool : isTool
@@ -108,7 +108,7 @@ function RawFallbackBlock({
}) { }) {
const pretty = prettyRaw(raw); const pretty = prettyRaw(raw);
return ( return (
<div className="min-w-0 rounded-md border bg-muted/30 p-3"> <div className="rounded-md border bg-muted/30 p-3">
<div className="mb-1 text-xs font-medium text-muted-foreground"> <div className="mb-1 text-xs font-medium text-muted-foreground">
{label} {label}
{pretty && unparsedLabel && ( {pretty && unparsedLabel && (
@@ -117,7 +117,7 @@ function RawFallbackBlock({
</span> </span>
)} )}
</div> </div>
<pre className="max-h-64 overflow-auto whitespace-pre-wrap break-all text-xs text-muted-foreground"> <pre className="max-h-64 overflow-auto whitespace-pre-wrap break-words text-xs text-muted-foreground">
{pretty ?? noDataLabel} {pretty ?? noDataLabel}
</pre> </pre>
</div> </div>
@@ -172,7 +172,7 @@ export function AiSessionChatView({
</Button> </Button>
</div> </div>
{rawMode ? ( {rawMode ? (
<div className="flex min-w-0 max-h-[32rem] flex-col gap-3 overflow-y-auto rounded-md border bg-background p-4"> <div className="flex max-h-[32rem] flex-col gap-3 overflow-y-auto rounded-md border bg-background p-4">
<RawFallbackBlock <RawFallbackBlock
label={t("aiSessionRequest")} label={t("aiSessionRequest")}
raw={normalizedRequest} raw={normalizedRequest}
@@ -185,7 +185,7 @@ export function AiSessionChatView({
/> />
</div> </div>
) : ( ) : (
<div className="flex min-w-0 max-h-[32rem] flex-col gap-3 overflow-y-auto rounded-md border bg-background p-4"> <div className="flex max-h-[32rem] flex-col gap-3 overflow-y-auto rounded-md border bg-background p-4">
{hasRequestMessages ? ( {hasRequestMessages ? (
requestMessages!.map((message, i) => ( requestMessages!.map((message, i) => (
<MessageBubble key={`req-${i}`} message={message} /> <MessageBubble key={`req-${i}`} message={message} />
+1 -29
View File
@@ -57,7 +57,6 @@ export interface Destination {
sendActionLogs: boolean; sendActionLogs: boolean;
sendConnectionLogs: boolean; sendConnectionLogs: boolean;
sendRequestLogs: boolean; sendRequestLogs: boolean;
sendAISessionLogs: boolean;
lastError: string | null; lastError: string | null;
lastErrorAt: number | null; lastErrorAt: number | null;
createdAt: number; createdAt: number;
@@ -181,7 +180,6 @@ export function HttpDestinationCredenza({
const [sendActionLogs, setSendActionLogs] = useState(false); const [sendActionLogs, setSendActionLogs] = useState(false);
const [sendConnectionLogs, setSendConnectionLogs] = useState(false); const [sendConnectionLogs, setSendConnectionLogs] = useState(false);
const [sendRequestLogs, setSendRequestLogs] = useState(false); const [sendRequestLogs, setSendRequestLogs] = useState(false);
const [sendAISessionLogs, setSendAISessionLogs] = useState(false);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@@ -192,7 +190,6 @@ export function HttpDestinationCredenza({
setSendActionLogs(editing?.sendActionLogs ?? false); setSendActionLogs(editing?.sendActionLogs ?? false);
setSendConnectionLogs(editing?.sendConnectionLogs ?? false); setSendConnectionLogs(editing?.sendConnectionLogs ?? false);
setSendRequestLogs(editing?.sendRequestLogs ?? false); setSendRequestLogs(editing?.sendRequestLogs ?? false);
setSendAISessionLogs(editing?.sendAISessionLogs ?? false);
} }
}, [open, editing]); }, [open, editing]);
@@ -229,8 +226,7 @@ export function HttpDestinationCredenza({
sendAccessLogs, sendAccessLogs,
sendActionLogs, sendActionLogs,
sendConnectionLogs, sendConnectionLogs,
sendRequestLogs, sendRequestLogs
sendAISessionLogs
}; };
if (editing) { if (editing) {
await api.post( await api.post(
@@ -782,30 +778,6 @@ export function HttpDestinationCredenza({
</p> </p>
</div> </div>
</div> </div>
<div className="flex items-start gap-3 rounded-md border p-3">
<Checkbox
id="log-ai-session"
checked={sendAISessionLogs}
onCheckedChange={(v) =>
setSendAISessionLogs(v === true)
}
className="mt-0.5"
/>
<div>
<label
htmlFor="log-ai-session"
className="text-sm font-medium cursor-pointer"
>
{t("httpDestAISessionLogsTitle")}
</label>
<p className="text-xs text-muted-foreground mt-0.5">
{t(
"httpDestAISessionLogsDescription"
)}
</p>
</div>
</div>
</div> </div>
</div> </div>
</HorizontalTabs> </HorizontalTabs>
+3 -16
View File
@@ -313,15 +313,6 @@ export function LogDataTable<TData, TValue>({
} }
}, [currentPage, table, isServerPagination]); }, [currentPage, table, isServerPagination]);
// Collapse any expanded rows whenever the page changes, since row ids
// are reused across pages and would otherwise show the wrong content
// in the same expanded position.
const pageIndex = table.getState().pagination.pageIndex;
useEffect(() => {
setExpandedRows(new Set());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageIndex]);
const handleTabChange = (value: string) => { const handleTabChange = (value: string) => {
if (disabled) return; if (disabled) return;
@@ -524,13 +515,9 @@ export function LogDataTable<TData, TValue>({
} }
className="p-4 bg-muted/50" className="p-4 bg-muted/50"
> >
{/* w-0 min-w-full keeps this cell's content from */} {renderExpandedRow(
{/* blowing out the table's auto column widths */} row.original
<div className="w-0 min-w-full"> )}
{renderExpandedRow(
row.original
)}
</div>
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
+1 -28
View File
@@ -90,7 +90,6 @@ export function S3DestinationCredenza({
const [sendActionLogs, setSendActionLogs] = useState(false); const [sendActionLogs, setSendActionLogs] = useState(false);
const [sendConnectionLogs, setSendConnectionLogs] = useState(false); const [sendConnectionLogs, setSendConnectionLogs] = useState(false);
const [sendRequestLogs, setSendRequestLogs] = useState(false); const [sendRequestLogs, setSendRequestLogs] = useState(false);
const [sendAISessionLogs, setSendAISessionLogs] = useState(false);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@@ -99,7 +98,6 @@ export function S3DestinationCredenza({
setSendActionLogs(editing?.sendActionLogs ?? false); setSendActionLogs(editing?.sendActionLogs ?? false);
setSendConnectionLogs(editing?.sendConnectionLogs ?? false); setSendConnectionLogs(editing?.sendConnectionLogs ?? false);
setSendRequestLogs(editing?.sendRequestLogs ?? false); setSendRequestLogs(editing?.sendRequestLogs ?? false);
setSendAISessionLogs(editing?.sendAISessionLogs ?? false);
} }
}, [open, editing]); }, [open, editing]);
@@ -123,8 +121,7 @@ export function S3DestinationCredenza({
sendAccessLogs, sendAccessLogs,
sendActionLogs, sendActionLogs,
sendConnectionLogs, sendConnectionLogs,
sendRequestLogs, sendRequestLogs
sendAISessionLogs
}; };
if (editing) { if (editing) {
await api.post( await api.post(
@@ -513,30 +510,6 @@ export function S3DestinationCredenza({
</p> </p>
</div> </div>
</div> </div>
<div className="flex items-start gap-3 rounded-md border p-3">
<Checkbox
id="s3-log-ai-session"
checked={sendAISessionLogs}
onCheckedChange={(v) =>
setSendAISessionLogs(v === true)
}
className="mt-0.5"
/>
<div>
<Label
htmlFor="s3-log-ai-session"
className="cursor-pointer font-medium"
>
{t("httpDestAISessionLogsTitle")}
</Label>
<p className="text-xs text-muted-foreground mt-0.5">
{t(
"httpDestAISessionLogsDescription"
)}
</p>
</div>
</div>
</div> </div>
</div> </div>
</HorizontalTabs> </HorizontalTabs>
@@ -49,10 +49,6 @@ const CLIENT_LOGOS = {
opencode: { opencode: {
light: "/third-party/opencode-dark.svg", light: "/third-party/opencode-dark.svg",
dark: "/third-party/opencode-light.svg" dark: "/third-party/opencode-light.svg"
},
gemini: {
light: "/third-party/gemini-dark.svg",
dark: "/third-party/gemini-light.svg"
} }
} as const; } as const;
@@ -69,8 +65,7 @@ export function AiClientConfigSection({
const descriptions: Record<string, string> = { const descriptions: Record<string, string> = {
claude: t("aiClientConfigDescriptionClaude"), claude: t("aiClientConfigDescriptionClaude"),
codex: t("aiClientConfigDescriptionCodex"), codex: t("aiClientConfigDescriptionCodex"),
opencode: t("aiClientConfigDescriptionOpencode"), opencode: t("aiClientConfigDescriptionOpencode")
gemini: t("aiClientConfigDescriptionGemini")
}; };
return ( return (
@@ -129,7 +129,6 @@ export function ToggleableTrendChart(props: ToggleableTrendChartProps) {
payload?.[0]?.payload?.day payload?.[0]?.payload?.day
) )
} }
valueFormatter={valueFormatter}
/> />
} }
/> />
@@ -173,7 +172,6 @@ export function ToggleableTrendChart(props: ToggleableTrendChartProps) {
payload?.[0]?.payload?.day payload?.[0]?.payload?.day
) )
} }
valueFormatter={valueFormatter}
/> />
} }
/> />
+1 -2
View File
@@ -47,8 +47,7 @@ export function buildSeriesFromData(
export const currencyFormatter = new Intl.NumberFormat(undefined, { export const currencyFormatter = new Intl.NumberFormat(undefined, {
style: "currency", style: "currency",
currency: "USD", currency: "USD",
minimumFractionDigits: 2, maximumFractionDigits: 2
maximumFractionDigits: 4
}); });
export const compactNumberFormatter = new Intl.NumberFormat(undefined, { export const compactNumberFormatter = new Intl.NumberFormat(undefined, {
+10 -16
View File
@@ -135,7 +135,6 @@ type ChartTooltipContentProps = React.ComponentProps<"div"> & {
labelKey?: string; labelKey?: string;
color?: string; color?: string;
labelClassName?: string; labelClassName?: string;
valueFormatter?: (value: number) => string;
}; };
const ChartTooltipContent = React.forwardRef< const ChartTooltipContent = React.forwardRef<
@@ -156,8 +155,7 @@ const ChartTooltipContent = React.forwardRef<
formatter, formatter,
color, color,
nameKey, nameKey,
labelKey, labelKey
valueFormatter
}, },
ref ref
) => { ) => {
@@ -304,23 +302,19 @@ const ChartTooltipContent = React.forwardRef<
item.name} item.name}
</span> </span>
</div> </div>
{item.value !== undefined && ( {item.value && (
<span className="font-mono font-medium tabular-nums text-foreground"> <span className="font-mono font-medium tabular-nums text-foreground">
{!isNaN( {!isNaN(
item.value as number item.value as number
) )
? valueFormatter ? new Intl.NumberFormat(
? valueFormatter( navigator.language,
item.value as number {
) maximumFractionDigits: 0
: new Intl.NumberFormat( }
navigator.language, ).format(
{ item.value as number
maximumFractionDigits: 0 )
}
).format(
item.value as number
)
: item.value.toLocaleString()} : item.value.toLocaleString()}
</span> </span>
)} )}
+74
View File
@@ -0,0 +1,74 @@
import { assertEquals } from "@test/assert";
import { detectLocale } from "./detectLocale";
function runTests() {
assertEquals(
detectLocale("zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6"),
"zh-TW",
"An exact regional match should take precedence over a language fallback"
);
assertEquals(
detectLocale("ZH-tw"),
"zh-TW",
"Locale matching should be case-insensitive"
);
assertEquals(
detectLocale(" zh-TW ; q=1 , zh-CN;q=0.8 "),
"zh-TW",
"Whitespace and quality parameters should not prevent an exact match"
);
assertEquals(
detectLocale("zh-CN,zh-TW;q=0.9"),
"zh-CN",
"Simplified Chinese should still match exactly"
);
assertEquals(
detectLocale("zh"),
"zh-CN",
"A generic Chinese preference should retain the existing fallback"
);
assertEquals(
detectLocale("en-GB,en;q=0.9"),
"en-US",
"An unsupported region should fall back to a supported locale for the language"
);
assertEquals(
detectLocale("ja-JP,zh-TW;q=0.9"),
"zh-TW",
"The next preference should be used when a language is unsupported"
);
assertEquals(
detectLocale("zh-CN;q=0.5,zh-TW;q=0.9"),
"zh-TW",
"Preferences should be evaluated by quality"
);
assertEquals(
detectLocale("zh-TW;q=0,fr-FR;q=0.8"),
"fr-FR",
"Locales with zero quality should be excluded"
);
assertEquals(
detectLocale("*,de-DE;q=0.8"),
"de-DE",
"A wildcard should not obscure a supported preference"
);
assertEquals(
detectLocale("ja-JP"),
undefined,
"An unsupported language should not match"
);
assertEquals(
detectLocale(""),
undefined,
"An empty Accept-Language header should not match"
);
console.log("All locale detection tests passed!");
}
try {
runTests();
} catch (error) {
console.error("Locale detection test failed:", error);
process.exit(1);
}

Some files were not shown because too many files have changed in this diff Show More