mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Release v3.7.9 (continued development) (#1982)
* chore: add GPT-5.5 Instant and support Node 26 (#1977) chore: add GPT-5.5 Instant model to Codex registry + Node 26 support with CI + improved native SQLite error handling. Integrated into release/v3.7.9 * feat: enhance cost formatting and add Codex GPT-5.5 pricing support * fix formatting --------- Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: Jan Leon <jan.gaschler@gmail.com> Co-authored-by: 05dunski <05dunski-kredo@icloud.com>
This commit is contained in:
committed by
GitHub
parent
dfd83bacd1
commit
7665ad3950
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -13,7 +13,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: OmniRoute Version
|
label: OmniRoute Version
|
||||||
description: "Run `omniroute --version` or check the left sidebar in the dashboard."
|
description: "Run `omniroute --version` or check the left sidebar in the dashboard."
|
||||||
placeholder: "e.g. 3.0.9"
|
placeholder: "e.g. 3.7.9"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ body:
|
|||||||
id: os-version
|
id: os-version
|
||||||
attributes:
|
attributes:
|
||||||
label: OS Version
|
label: OS Version
|
||||||
placeholder: "e.g. Windows 11 23H2, macOS 15.3, Ubuntu 24.04"
|
placeholder: "e.g. Windows 11 25H2, macOS 26.5, Ubuntu 26.04"
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Node.js Version
|
label: Node.js Version
|
||||||
description: "Run `node --version`. Skip if using Docker."
|
description: "Run `node --version`. Skip if using Docker."
|
||||||
placeholder: "e.g. 22.12.0"
|
placeholder: "e.g. 24.15.0"
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ body:
|
|||||||
id: model
|
id: model
|
||||||
attributes:
|
attributes:
|
||||||
label: Model(s) Involved
|
label: Model(s) Involved
|
||||||
placeholder: "e.g. claude-sonnet-4-20250514, gpt-4o, gemini-2.5-pro"
|
placeholder: "e.g. claude-opus-4-7, gpt-5.5, gemini-3.1-pro"
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
|||||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -27,4 +27,4 @@
|
|||||||
|
|
||||||
## Reviewer Notes
|
## Reviewer Notes
|
||||||
|
|
||||||
- Call out any risky areas, migrations, feature flags, or manual validation that reviewers should know about.
|
- Call out any risky areas, migrations, feature flags, or manual validation that reviewers should know about.
|
||||||
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@@ -18,6 +18,7 @@ permissions:
|
|||||||
env:
|
env:
|
||||||
CI_NODE_VERSION: "24"
|
CI_NODE_VERSION: "24"
|
||||||
CI_NODE_24_VERSION: "24"
|
CI_NODE_24_VERSION: "24"
|
||||||
|
CI_NODE_26_VERSION: "26"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
@@ -206,6 +207,30 @@ jobs:
|
|||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
||||||
|
|
||||||
|
node-26-compat:
|
||||||
|
name: Node 26 Compatibility (${{ matrix.shard }}/2)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
needs: build
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
shard: [1, 2]
|
||||||
|
env:
|
||||||
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||||
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||||
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.CI_NODE_26_VERSION }}
|
||||||
|
cache: npm
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run check:node-runtime
|
||||||
|
- run: npm run build
|
||||||
|
- run: node --import tsx/esm --test --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
||||||
|
|
||||||
test-coverage:
|
test-coverage:
|
||||||
name: Coverage
|
name: Coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -441,6 +466,7 @@ jobs:
|
|||||||
- electron-package-smoke
|
- electron-package-smoke
|
||||||
- test-unit
|
- test-unit
|
||||||
- node-24-compat
|
- node-24-compat
|
||||||
|
- node-26-compat
|
||||||
- test-coverage
|
- test-coverage
|
||||||
- sonarqube
|
- sonarqube
|
||||||
- coverage-pr-comment
|
- coverage-pr-comment
|
||||||
@@ -494,6 +520,8 @@ jobs:
|
|||||||
echo "| Suite | Status |" >> "$GITHUB_STEP_SUMMARY"
|
echo "| Suite | Status |" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
|
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "| Unit | $(status '${{ needs.test-unit.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
echo "| Unit | $(status '${{ needs.test-unit.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "| Node 24 Compatibility | $(status '${{ needs.node-24-compat.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "| Node 26 Compatibility | $(status '${{ needs.node-26-compat.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "| Coverage | $(status '${{ needs.test-coverage.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
echo "| Coverage | $(status '${{ needs.test-coverage.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "| PR Coverage Comment | $(status '${{ needs.coverage-pr-comment.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
echo "| PR Coverage Comment | $(status '${{ needs.coverage-pr-comment.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "| E2E | $(status '${{ needs.test-e2e.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
echo "| E2E | $(status '${{ needs.test-e2e.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|||||||
@@ -1519,3 +1519,4 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|||||||
<sub><a href="https://omniroute.online">omniroute.online</a></sub>
|
<sub><a href="https://omniroute.online">omniroute.online</a></sub>
|
||||||
</div>
|
</div>
|
||||||
<!-- GitHub Discussions enabled for community Q&A -->
|
<!-- GitHub Discussions enabled for community Q&A -->
|
||||||
|
|
||||||
|
|||||||
@@ -1121,10 +1121,11 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
|||||||
models: [
|
models: [
|
||||||
{ id: "gpt-5.5-pro", name: "GPT-5.5 Pro" }, //pro tier only
|
{ id: "gpt-5.5-pro", name: "GPT-5.5 Pro" }, //pro tier only
|
||||||
{ id: "gpt-5.5-thinking", name: "GPT-5.5 Thinking" }, //plus, pro tier
|
{ id: "gpt-5.5-thinking", name: "GPT-5.5 Thinking" }, //plus, pro tier
|
||||||
|
{ id: "gpt-5.5", name: "GPT-5.5 Instant" }, //free, plus, pro tier
|
||||||
{ id: "gpt-5.4-pro", name: "GPT-5.4 Pro" }, //pro tier only
|
{ id: "gpt-5.4-pro", name: "GPT-5.4 Pro" }, //pro tier only
|
||||||
{ id: "gpt-5.4-thinking", name: "GPT-5.4 Thinking" }, //plus, pro tier
|
{ id: "gpt-5.4-thinking", name: "GPT-5.4 Thinking" }, //plus, pro tier
|
||||||
{ id: "gpt-5.4-thinking-mini", name: "GPT-5.4 Thinking Mini" }, //free-login only
|
{ id: "gpt-5.4-thinking-mini", name: "GPT-5.4 Thinking Mini" }, //free-login only
|
||||||
{ id: "gpt-5.3", name: "GPT-5.3" }, //free, free-login, plus, pro tier
|
{ id: "gpt-5.3", name: "GPT-5.3 Instant" }, //free, free-login, plus, pro tier
|
||||||
{ id: "gpt-5.3-mini", name: "GPT-5.3 Mini" }, //limit fallback
|
{ id: "gpt-5.3-mini", name: "GPT-5.3 Mini" }, //limit fallback
|
||||||
{ id: "gpt-5.2-pro", name: "GPT-5.2 Pro" }, //pro tier only
|
{ id: "gpt-5.2-pro", name: "GPT-5.2 Pro" }, //pro tier only
|
||||||
{ id: "gpt-5.2-thinking", name: "GPT-5.2 Thinking" }, //plus ~ tier
|
{ id: "gpt-5.2-thinking", name: "GPT-5.2 Thinking" }, //plus ~ tier
|
||||||
|
|||||||
@@ -453,7 +453,10 @@ export function shouldMarkAccountExhaustedFrom429(
|
|||||||
model: string | null | undefined = null,
|
model: string | null | undefined = null,
|
||||||
connectionPassthroughModels?: boolean
|
connectionPassthroughModels?: boolean
|
||||||
): boolean {
|
): boolean {
|
||||||
return !hasPerModelQuota(provider, model, connectionPassthroughModels);
|
return (
|
||||||
|
shouldPreserveQuotaSignalsFor429(provider) &&
|
||||||
|
!hasPerModelQuota(provider, model, connectionPassthroughModels)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ export function stripCloudCodeThinkingConfig(
|
|||||||
|
|
||||||
if (isRecord(next.request)) {
|
if (isRecord(next.request)) {
|
||||||
const request = { ...next.request };
|
const request = { ...next.request };
|
||||||
|
delete request.reasoning_effort;
|
||||||
|
delete request.reasoning;
|
||||||
|
delete request.thinking;
|
||||||
|
|
||||||
if ("generationConfig" in request) {
|
if ("generationConfig" in request) {
|
||||||
request.generationConfig = stripGeminiThinkingConfig(request.generationConfig);
|
request.generationConfig = stripGeminiThinkingConfig(request.generationConfig);
|
||||||
}
|
}
|
||||||
|
|||||||
357
package-lock.json
generated
357
package-lock.json
generated
@@ -33,7 +33,7 @@
|
|||||||
"jsonc-parser": "^3.3.1",
|
"jsonc-parser": "^3.3.1",
|
||||||
"lowdb": "^7.0.1",
|
"lowdb": "^7.0.1",
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
< "marked": "^18.0.3",
|
"marked": "^18.0.3",
|
||||||
"mermaid": "^11.14.0",
|
"mermaid": "^11.14.0",
|
||||||
"monaco-editor": "^0.55.1",
|
"monaco-editor": "^0.55.1",
|
||||||
"next": "^16.2.3",
|
"next": "^16.2.3",
|
||||||
@@ -96,38 +96,12 @@
|
|||||||
"wtfnode": "^0.10.1"
|
"wtfnode": "^0.10.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <25"
|
"node": ">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <27"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"keytar": "^7.9.0"
|
"keytar": "^7.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../omniroute-docs-site": {
|
|
||||||
"name": "docs-site",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"extraneous": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@mdx-js/loader": "^3.1.1",
|
|
||||||
"@mdx-js/react": "^3.1.1",
|
|
||||||
"@next/mdx": "^16.2.4",
|
|
||||||
"fuse.js": "^7.3.0",
|
|
||||||
"next": "16.2.4",
|
|
||||||
"react": "19.2.4",
|
|
||||||
"react-dom": "19.2.4"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/postcss": "^4",
|
|
||||||
"@types/node": "^20",
|
|
||||||
"@types/react": "^19",
|
|
||||||
"@types/react-dom": "^19",
|
|
||||||
"eslint": "^9",
|
|
||||||
"eslint-config-next": "16.2.4",
|
|
||||||
"glob": "^13.0.6",
|
|
||||||
"gray-matter": "^4.0.3",
|
|
||||||
"tailwindcss": "^4",
|
|
||||||
"typescript": "^5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@adobe/css-tools": {
|
"node_modules/@adobe/css-tools": {
|
||||||
"version": "4.4.4",
|
"version": "4.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
|
||||||
@@ -673,21 +647,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.9.1",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||||
"integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
|
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emnapi/wasi-threads": "1.2.0",
|
"@emnapi/wasi-threads": "1.2.1",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.9.1",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||||
"integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
|
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -695,9 +669,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||||
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
|
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -1737,6 +1711,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1753,6 +1730,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1769,6 +1749,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1785,6 +1768,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1801,6 +1787,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1817,6 +1806,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1833,6 +1825,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1849,6 +1844,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1865,6 +1863,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1887,6 +1888,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1909,6 +1913,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1931,6 +1938,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1953,6 +1963,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1975,6 +1988,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1997,6 +2013,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2019,6 +2038,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2269,16 +2291,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/wasm-runtime": {
|
"node_modules/@napi-rs/wasm-runtime": {
|
||||||
"version": "0.2.12",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||||
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
|
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emnapi/core": "^1.4.3",
|
"@tybys/wasm-util": "^0.10.1"
|
||||||
"@emnapi/runtime": "^1.4.3",
|
},
|
||||||
"@tybys/wasm-util": "^0.10.0"
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emnapi/core": "^1.7.1",
|
||||||
|
"@emnapi/runtime": "^1.7.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
@@ -2336,6 +2364,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2352,6 +2383,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2368,6 +2402,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2384,6 +2421,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2549,6 +2589,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2565,6 +2608,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2581,6 +2627,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2597,6 +2646,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2838,6 +2890,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2858,6 +2913,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2878,6 +2936,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2898,6 +2959,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2918,6 +2982,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2938,6 +3005,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3326,6 +3396,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3343,6 +3416,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3360,6 +3436,9 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3377,6 +3456,9 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3394,6 +3476,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3411,6 +3496,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3454,25 +3542,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@tybys/wasm-util": "^0.10.1"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emnapi/core": "^1.7.1",
|
|
||||||
"@emnapi/runtime": "^1.7.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||||
"version": "1.0.0-rc.12",
|
"version": "1.0.0-rc.12",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
|
||||||
@@ -3634,6 +3703,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3650,6 +3722,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3666,6 +3741,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3682,6 +3760,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3698,6 +3779,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3714,6 +3798,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3928,6 +4015,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3945,6 +4035,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3962,6 +4055,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3979,6 +4075,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -4018,70 +4117,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
|
||||||
"version": "1.8.1",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/wasi-threads": "1.1.0",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
|
||||||
"version": "1.8.1",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/core": "^1.7.1",
|
|
||||||
"@emnapi/runtime": "^1.7.1",
|
|
||||||
"@tybys/wasm-util": "^0.10.1"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
|
||||||
"version": "0.10.1",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
|
||||||
"version": "2.8.1",
|
|
||||||
"dev": true,
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "0BSD",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
|
||||||
@@ -4186,9 +4221,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tybys/wasm-util": {
|
"node_modules/@tybys/wasm-util": {
|
||||||
"version": "0.10.1",
|
"version": "0.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -5044,6 +5079,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5058,6 +5096,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5072,6 +5113,9 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5086,6 +5130,9 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5100,6 +5147,9 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5114,6 +5164,9 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5128,6 +5181,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5142,6 +5198,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5165,6 +5224,19 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||||
|
"version": "0.2.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||||
|
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/core": "^1.4.3",
|
||||||
|
"@emnapi/runtime": "^1.4.3",
|
||||||
|
"@tybys/wasm-util": "^0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
|
"node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
|
||||||
@@ -10440,10 +10512,6 @@
|
|||||||
"version": "29.1.1",
|
"version": "29.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
|
||||||
"integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
|
"integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
"dev": true,
|
|
||||||
>>>>>>> upstream/release/v3.7.9
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/css-color": "^5.1.11",
|
"@asamuzakjp/css-color": "^5.1.11",
|
||||||
@@ -10850,6 +10918,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10871,6 +10942,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10892,6 +10966,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10913,6 +10990,9 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11247,15 +11327,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/marked": {
|
"node_modules/marked": {
|
||||||
<<<<<<< HEAD
|
|
||||||
"version": "18.0.3",
|
"version": "18.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.3.tgz",
|
||||||
"integrity": "sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA==",
|
"integrity": "sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA==",
|
||||||
=======
|
|
||||||
"version": "16.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
|
|
||||||
"integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
|
|
||||||
>>>>>>> upstream/release/v3.7.9
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
@@ -11492,7 +11566,6 @@
|
|||||||
"uuid": "^11.1.0"
|
"uuid": "^11.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
"node_modules/mermaid/node_modules/marked": {
|
"node_modules/mermaid/node_modules/marked": {
|
||||||
"version": "16.4.2",
|
"version": "16.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
|
||||||
@@ -11505,8 +11578,6 @@
|
|||||||
"node": ">= 20"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
=======
|
|
||||||
>>>>>>> upstream/release/v3.7.9
|
|
||||||
"node_modules/mermaid/node_modules/uuid": {
|
"node_modules/mermaid/node_modules/uuid": {
|
||||||
"version": "11.1.1",
|
"version": "11.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"open-sse"
|
"open-sse"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <25"
|
"node": ">=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <27"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ai",
|
"ai",
|
||||||
|
|||||||
@@ -930,14 +930,14 @@ export default function SystemStorageTab() {
|
|||||||
setPurgeLogsLoading(true);
|
setPurgeLogsLoading(true);
|
||||||
setPurgeLogsStatus({ type: "", message: "" });
|
setPurgeLogsStatus({ type: "", message: "" });
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/logs/purge", { method: "DELETE" });
|
const res = await fetch("/api/settings/purge-logs", { method: "POST" });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setPurgeLogsStatus({
|
setPurgeLogsStatus({
|
||||||
type: "success",
|
type: "success",
|
||||||
message:
|
message:
|
||||||
t("logsPurged", { count: data.deletedCount }) ||
|
t("logsDeleted", { count: data.deleted }) ||
|
||||||
`Purged ${data.deletedCount} old log entries`,
|
`Purged ${data.deleted} expired log(s)`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setPurgeLogsStatus({
|
setPurgeLogsStatus({
|
||||||
@@ -953,9 +953,9 @@ export default function SystemStorageTab() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
|
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
|
||||||
delete_forever
|
auto_delete
|
||||||
</span>
|
</span>
|
||||||
{t("purgeOldLogs") || "Purge Old Logs"}
|
{t("purgeExpiredLogs") || "Purge Expired Logs"}
|
||||||
</Button>
|
</Button>
|
||||||
{purgeLogsStatus.message && (
|
{purgeLogsStatus.message && (
|
||||||
<div
|
<div
|
||||||
@@ -977,247 +977,6 @@ export default function SystemStorageTab() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Purge Section */}
|
|
||||||
<div className="pt-3 border-t border-border/50 mb-4">
|
|
||||||
<div className="flex items-center gap-2 mb-3">
|
|
||||||
<span className="material-symbols-outlined text-[18px] text-red-500" aria-hidden="true">
|
|
||||||
auto_delete
|
|
||||||
</span>
|
|
||||||
<p className="font-medium">Purge Data</p>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
loading={purgeLogsLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
setPurgeLogsLoading(true);
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/db/cleanup/quota-snapshots", { method: "DELETE" });
|
|
||||||
const data = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "success",
|
|
||||||
message: `Purged ${data.deleted} quota snapshots`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "error",
|
|
||||||
message: data.error || "Failed to purge quota snapshots",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setPurgeLogsStatus({ type: "error", message: "Error purging quota snapshots" });
|
|
||||||
} finally {
|
|
||||||
setPurgeLogsLoading(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
|
|
||||||
delete
|
|
||||||
</span>
|
|
||||||
Purge Quota Snapshots
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
loading={purgeLogsLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
setPurgeLogsLoading(true);
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/db/cleanup/call-logs", { method: "DELETE" });
|
|
||||||
const data = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "success",
|
|
||||||
message: `Purged ${data.deleted} call logs`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "error",
|
|
||||||
message: data.error || "Failed to purge call logs",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setPurgeLogsStatus({ type: "error", message: "Error purging call logs" });
|
|
||||||
} finally {
|
|
||||||
setPurgeLogsLoading(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
|
|
||||||
delete
|
|
||||||
</span>
|
|
||||||
Purge Call Logs
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
loading={purgeLogsLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
setPurgeLogsLoading(true);
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/db/cleanup/detailed-logs", { method: "DELETE" });
|
|
||||||
const data = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "success",
|
|
||||||
message: `Purged ${data.deleted} detailed logs`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "error",
|
|
||||||
message: data.error || "Failed to purge detailed logs",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setPurgeLogsStatus({ type: "error", message: "Error purging detailed logs" });
|
|
||||||
} finally {
|
|
||||||
setPurgeLogsLoading(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
|
|
||||||
delete
|
|
||||||
</span>
|
|
||||||
Purge Detailed Logs
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{purgeLogsStatus.message && (
|
|
||||||
<div
|
|
||||||
className={`p-3 rounded-lg text-sm ${
|
|
||||||
purgeLogsStatus.type === "success"
|
|
||||||
? "bg-green-500/10 text-green-500 border border-green-500/20"
|
|
||||||
: "bg-red-500/10 text-red-500 border border-red-500/20"
|
|
||||||
}`}
|
|
||||||
role="alert"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
|
|
||||||
{purgeLogsStatus.type === "success" ? "check_circle" : "error"}
|
|
||||||
</span>
|
|
||||||
{purgeLogsStatus.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center gap-2 mb-3">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
loading={clearCacheLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
setClearCacheLoading(true);
|
|
||||||
setClearCacheStatus({ type: "", message: "" });
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/cache", { method: "DELETE" });
|
|
||||||
const data = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
setClearCacheStatus({
|
|
||||||
type: "success",
|
|
||||||
message: t("cacheCleared") || "Cache cleared successfully",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setClearCacheStatus({
|
|
||||||
type: "error",
|
|
||||||
message: data.error || t("clearCacheFailed") || "Failed to clear cache",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setClearCacheStatus({ type: "error", message: t("errorOccurred") });
|
|
||||||
} finally {
|
|
||||||
setClearCacheLoading(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
|
|
||||||
delete_sweep
|
|
||||||
</span>
|
|
||||||
{t("clearCache") || "Clear Cache"}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
loading={purgeLogsLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
setPurgeLogsLoading(true);
|
|
||||||
setPurgeLogsStatus({ type: "", message: "" });
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/settings/purge-logs", { method: "POST" });
|
|
||||||
const data = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "success",
|
|
||||||
message:
|
|
||||||
t("logsDeleted", { count: data.deleted }) ||
|
|
||||||
`Purged ${data.deleted} expired log(s)`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setPurgeLogsStatus({
|
|
||||||
type: "error",
|
|
||||||
message: data.error || t("purgeLogsFailed") || "Failed to purge logs",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setPurgeLogsStatus({ type: "error", message: t("errorOccurred") });
|
|
||||||
} finally {
|
|
||||||
setPurgeLogsLoading(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="material-symbols-outlined text-[14px] mr-1" aria-hidden="true">
|
|
||||||
auto_delete
|
|
||||||
</span>
|
|
||||||
{t("purgeExpiredLogs") || "Purge Expired Logs"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{(clearCacheStatus.message || purgeLogsStatus.message) && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{clearCacheStatus.message && (
|
|
||||||
<div
|
|
||||||
className={`p-3 rounded-lg text-sm ${
|
|
||||||
clearCacheStatus.type === "success"
|
|
||||||
? "bg-green-500/10 text-green-500 border border-green-500/20"
|
|
||||||
: "bg-red-500/10 text-red-500 border border-red-500/20"
|
|
||||||
}`}
|
|
||||||
role="alert"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
|
|
||||||
{clearCacheStatus.type === "success" ? "check_circle" : "error"}
|
|
||||||
</span>
|
|
||||||
{clearCacheStatus.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{purgeLogsStatus.message && (
|
|
||||||
<div
|
|
||||||
className={`p-3 rounded-lg text-sm ${
|
|
||||||
purgeLogsStatus.type === "success"
|
|
||||||
? "bg-green-500/10 text-green-500 border border-green-500/20"
|
|
||||||
: "bg-red-500/10 text-red-500 border border-red-500/20"
|
|
||||||
}`}
|
|
||||||
role="alert"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
|
|
||||||
{purgeLogsStatus.type === "success" ? "check_circle" : "error"}
|
|
||||||
</span>
|
|
||||||
{purgeLogsStatus.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Purge Data section */}
|
{/* Purge Data section */}
|
||||||
<div className="pt-3 border-t border-border/50">
|
<div className="pt-3 border-t border-border/50">
|
||||||
<div className="flex items-start justify-between gap-3 flex-wrap mb-3">
|
<div className="flex items-start justify-between gap-3 flex-wrap mb-3">
|
||||||
|
|||||||
@@ -64,6 +64,44 @@ function findKeyInsensitive(obj: Record<string, any> | undefined | null, key: st
|
|||||||
return obj[key.toLowerCase()];
|
return obj[key.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uniqueValues(values: Array<string | null | undefined>): string[] {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const value of values) {
|
||||||
|
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||||
|
if (!normalized || seen.has(normalized)) continue;
|
||||||
|
seen.add(normalized);
|
||||||
|
result.push(normalized);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripCodexEffortSuffix(model: string): string {
|
||||||
|
return model.replace(/-(?:xhigh|high|medium|low|none)$/i, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPricingModelCandidates(
|
||||||
|
model: string,
|
||||||
|
normalizeModelName: (model: string) => string
|
||||||
|
): string[] {
|
||||||
|
const normalizedModel = normalizeModelName(model);
|
||||||
|
const lowerModel = model.toLowerCase();
|
||||||
|
const lowerNormalized = normalizedModel.toLowerCase();
|
||||||
|
const hyphenModel = lowerModel.replace(/\./g, "-");
|
||||||
|
const hyphenNormalized = lowerNormalized.replace(/\./g, "-");
|
||||||
|
const effortBaseModel = stripCodexEffortSuffix(lowerNormalized);
|
||||||
|
|
||||||
|
return uniqueValues([
|
||||||
|
lowerModel,
|
||||||
|
lowerNormalized,
|
||||||
|
hyphenModel,
|
||||||
|
hyphenNormalized,
|
||||||
|
effortBaseModel,
|
||||||
|
effortBaseModel.replace(/\./g, "-"),
|
||||||
|
lowerNormalized === "codex-auto-review" ? "gpt-5.5" : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
function resolveModelPricing(
|
function resolveModelPricing(
|
||||||
pricingByProvider: PricingByProvider,
|
pricingByProvider: PricingByProvider,
|
||||||
providerAliasMap: Record<string, string>,
|
providerAliasMap: Record<string, string>,
|
||||||
@@ -105,22 +143,15 @@ function resolveModelPricing(
|
|||||||
if (pLower === "antigravity") providerPricing = findKeyInsensitive(pricingByProvider, "ag");
|
if (pLower === "antigravity") providerPricing = findKeyInsensitive(pricingByProvider, "ag");
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedModel = normalizeModelName(model).toLowerCase();
|
const modelCandidates = getPricingModelCandidates(model, normalizeModelName);
|
||||||
const shortModel = normalizedModel; // normalizeModelName behaves exactly like shortModelName
|
|
||||||
const hyphenModel = model.toLowerCase().replace(/\./g, "-");
|
|
||||||
const hyphenNormalized = normalizedModel.replace(/\./g, "-");
|
|
||||||
const lowerModel = model.toLowerCase();
|
|
||||||
|
|
||||||
const tryFind = (prov: Record<string, unknown> | null | undefined) => {
|
const tryFind = (prov: Record<string, unknown> | null | undefined) => {
|
||||||
if (!prov || typeof prov !== "object") return null;
|
if (!prov || typeof prov !== "object") return null;
|
||||||
return (
|
for (const candidate of modelCandidates) {
|
||||||
findKeyInsensitive(prov as Record<string, unknown>, lowerModel) ||
|
const pricing = findKeyInsensitive(prov as Record<string, unknown>, candidate);
|
||||||
findKeyInsensitive(prov as Record<string, unknown>, normalizedModel) ||
|
if (pricing) return pricing;
|
||||||
findKeyInsensitive(prov as Record<string, unknown>, shortModel) ||
|
}
|
||||||
findKeyInsensitive(prov as Record<string, unknown>, hyphenModel) ||
|
return null;
|
||||||
findKeyInsensitive(prov as Record<string, unknown>, hyphenNormalized) ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let pricing = providerPricing ? tryFind(providerPricing) : null;
|
let pricing = providerPricing ? tryFind(providerPricing) : null;
|
||||||
@@ -139,7 +170,8 @@ function resolveModelPricing(
|
|||||||
// Last resort fallback for historical usage (e.g. "gpt-4" missing, matches "gpt-4.1" or first available)
|
// Last resort fallback for historical usage (e.g. "gpt-4" missing, matches "gpt-4.1" or first available)
|
||||||
if (!pricing && providerPricing && typeof providerPricing === "object") {
|
if (!pricing && providerPricing && typeof providerPricing === "object") {
|
||||||
for (const [key, val] of Object.entries(providerPricing as Record<string, unknown>)) {
|
for (const [key, val] of Object.entries(providerPricing as Record<string, unknown>)) {
|
||||||
if (key.includes(lowerModel) || lowerModel.includes(key)) {
|
const lm = model.toLowerCase();
|
||||||
|
if (key.includes(lm) || lm.includes(key)) {
|
||||||
pricing = val;
|
pricing = val;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -506,11 +538,20 @@ export async function GET(request: Request) {
|
|||||||
SUM(CASE WHEN (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as total,
|
SUM(CASE WHEN (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as total,
|
||||||
SUM(CASE WHEN requested_model IS NOT NULL AND requested_model != '' AND (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as with_requested,
|
SUM(CASE WHEN requested_model IS NOT NULL AND requested_model != '' AND (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as with_requested,
|
||||||
SUM(CASE
|
SUM(CASE
|
||||||
WHEN requested_model IS NOT NULL
|
WHEN (combo_name IS NULL OR combo_name = '')
|
||||||
|
AND requested_model IS NOT NULL
|
||||||
AND requested_model != ''
|
AND requested_model != ''
|
||||||
AND model IS NOT NULL
|
AND model IS NOT NULL
|
||||||
|
AND model != ''
|
||||||
|
THEN 1 ELSE 0 END
|
||||||
|
) as fallback_eligible,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN (combo_name IS NULL OR combo_name = '')
|
||||||
|
AND requested_model IS NOT NULL
|
||||||
|
AND requested_model != ''
|
||||||
|
AND model IS NOT NULL
|
||||||
|
AND model != ''
|
||||||
AND LOWER(CASE WHEN instr(requested_model, '/') > 0 THEN substr(requested_model, instr(requested_model, '/') + 1) ELSE requested_model END) != LOWER(model)
|
AND LOWER(CASE WHEN instr(requested_model, '/') > 0 THEN substr(requested_model, instr(requested_model, '/') + 1) ELSE requested_model END) != LOWER(model)
|
||||||
AND (combo_name IS NULL OR combo_name = '')
|
|
||||||
THEN 1 ELSE 0 END
|
THEN 1 ELSE 0 END
|
||||||
) as fallbacks
|
) as fallbacks
|
||||||
FROM call_logs
|
FROM call_logs
|
||||||
@@ -544,10 +585,11 @@ export async function GET(request: Request) {
|
|||||||
lastRequest: summaryRow?.lastRequest || "",
|
lastRequest: summaryRow?.lastRequest || "",
|
||||||
fallbackCount: Number(fallbackRow?.fallbacks || 0),
|
fallbackCount: Number(fallbackRow?.fallbacks || 0),
|
||||||
fallbackRatePct:
|
fallbackRatePct:
|
||||||
Number(fallbackRow?.with_requested || 0) > 0
|
Number(fallbackRow?.fallback_eligible || 0) > 0
|
||||||
? Number(
|
? Number(
|
||||||
(
|
(
|
||||||
(Number(fallbackRow?.fallbacks || 0) / Number(fallbackRow?.with_requested || 1)) *
|
(Number(fallbackRow?.fallbacks || 0) /
|
||||||
|
Number(fallbackRow?.fallback_eligible || 1)) *
|
||||||
100
|
100
|
||||||
).toFixed(2)
|
).toFixed(2)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -196,6 +196,16 @@ export const autoNavSections: AutoGenNavSection[] = [
|
|||||||
"fileName": "UNINSTALL.md"
|
"fileName": "UNINSTALL.md"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Other",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"slug": "rfc-auto-assessment",
|
||||||
|
"title": "RFC: Auto-Assessment & Self-Healing Combo Engine",
|
||||||
|
"fileName": "RFC-AUTO-ASSESSMENT.md"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -731,6 +741,25 @@ export const autoSearchIndex: AutoGenSearchItem[] = [
|
|||||||
"Source Install (git clone)"
|
"Source Install (git clone)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"slug": "rfc-auto-assessment",
|
||||||
|
"title": "RFC: Auto-Assessment & Self-Healing Combo Engine",
|
||||||
|
"fileName": "RFC-AUTO-ASSESSMENT.md",
|
||||||
|
"section": "Other",
|
||||||
|
"content": "Omniroute's combo system currently requires manual configuration: users must know which providers and models are actually working, then manually wire them into combo chains. When providers fail (rate limits, auth errors, model deprecation), combos silently degrade — routing to dead endpoints that ti",
|
||||||
|
"headings": [
|
||||||
|
"Summary",
|
||||||
|
"Problem Statement",
|
||||||
|
"What we encountered (real production incident)",
|
||||||
|
"Current flow (broken)",
|
||||||
|
"Proposed flow (self-healing)",
|
||||||
|
"Architecture",
|
||||||
|
"New Components",
|
||||||
|
"Detailed Design",
|
||||||
|
"1. Assessor — src/domain/assessor.ts",
|
||||||
|
"2. Categorizer — src/domain/categorizer.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"slug": "api-explorer",
|
"slug": "api-explorer",
|
||||||
"title": "API Explorer",
|
"title": "API Explorer",
|
||||||
@@ -773,5 +802,6 @@ export const autoAllSlugs: string[] = [
|
|||||||
"coverage-plan",
|
"coverage-plan",
|
||||||
"i18n",
|
"i18n",
|
||||||
"release-checklist",
|
"release-checklist",
|
||||||
"uninstall"
|
"uninstall",
|
||||||
|
"rfc-auto-assessment"
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import Link from "next/link";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import OmniRouteLogo from "@/shared/components/OmniRouteLogo";
|
import OmniRouteLogo from "@/shared/components/OmniRouteLogo";
|
||||||
|
|
||||||
@@ -61,12 +62,12 @@ export default function Footer() {
|
|||||||
{/* Resources */}
|
{/* Resources */}
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<h4 className="font-bold text-white">{t("resources")}</h4>
|
<h4 className="font-bold text-white">{t("resources")}</h4>
|
||||||
<a
|
<Link
|
||||||
className="text-gray-400 hover:text-[#E54D5E] text-sm transition-colors"
|
className="text-gray-400 hover:text-[#E54D5E] text-sm transition-colors"
|
||||||
href="/docs"
|
href="/docs"
|
||||||
>
|
>
|
||||||
{t("documentation")}
|
{t("documentation")}
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<a
|
||||||
className="text-gray-400 hover:text-[#E54D5E] text-sm transition-colors"
|
className="text-gray-400 hover:text-[#E54D5E] text-sm transition-colors"
|
||||||
href="https://github.com/diegosouzapw/OmniRoute"
|
href="https://github.com/diegosouzapw/OmniRoute"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import OmniRouteLogo from "@/shared/components/OmniRouteLogo";
|
import OmniRouteLogo from "@/shared/components/OmniRouteLogo";
|
||||||
@@ -41,12 +42,12 @@ export default function Navigation() {
|
|||||||
>
|
>
|
||||||
{t("howItWorks")}
|
{t("howItWorks")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<Link
|
||||||
className="text-gray-300 hover:text-white text-sm font-medium transition-colors"
|
className="text-gray-300 hover:text-white text-sm font-medium transition-colors"
|
||||||
href="/docs"
|
href="/docs"
|
||||||
>
|
>
|
||||||
{t("docsLink")}
|
{t("docsLink")}
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<a
|
||||||
className="text-gray-300 hover:text-white text-sm font-medium transition-colors flex items-center gap-1"
|
className="text-gray-300 hover:text-white text-sm font-medium transition-colors flex items-center gap-1"
|
||||||
href="https://github.com/diegosouzapw/OmniRoute"
|
href="https://github.com/diegosouzapw/OmniRoute"
|
||||||
@@ -98,12 +99,13 @@ export default function Navigation() {
|
|||||||
>
|
>
|
||||||
{t("howItWorks")}
|
{t("howItWorks")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<Link
|
||||||
className="text-gray-300 hover:text-white text-sm font-medium transition-colors"
|
className="text-gray-300 hover:text-white text-sm font-medium transition-colors"
|
||||||
href="/docs"
|
href="/docs"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
>
|
>
|
||||||
{t("docsLink")}
|
{t("docsLink")}
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<a
|
||||||
className="text-gray-300 hover:text-white text-sm font-medium transition-colors"
|
className="text-gray-300 hover:text-white text-sm font-medium transition-colors"
|
||||||
href="https://github.com/diegosouzapw/OmniRoute"
|
href="https://github.com/diegosouzapw/OmniRoute"
|
||||||
|
|||||||
@@ -88,6 +88,44 @@ const CRITICAL_DB_TABLES: CriticalTableSpec[] = [
|
|||||||
{ table: "webhooks", maxRows: 5_000 },
|
{ table: "webhooks", maxRows: 5_000 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function isNativeSqliteLoadError(error: unknown): boolean {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return (
|
||||||
|
message.includes("Module did not self-register") ||
|
||||||
|
message.includes("NODE_MODULE_VERSION") ||
|
||||||
|
message.includes("ERR_DLOPEN_FAILED") ||
|
||||||
|
(error as any)?.code === "ERR_DLOPEN_FAILED"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNativeSqliteLoadError(error: unknown): Error {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
const detail =
|
||||||
|
`better-sqlite3 native binding failed to load for Node.js ${process.version}. ` +
|
||||||
|
"This usually happens after switching Node.js versions without rebuilding native modules. " +
|
||||||
|
"Run `npm rebuild better-sqlite3` in the OmniRoute project and start again. " +
|
||||||
|
`Original error: ${message}`;
|
||||||
|
const wrapped = new Error(detail);
|
||||||
|
wrapped.name = "NativeSqliteLoadError";
|
||||||
|
(wrapped as any).cause = error;
|
||||||
|
(wrapped as any).code = (error as any)?.code || "ERR_DLOPEN_FAILED";
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSqliteDatabase(
|
||||||
|
sqliteFile: string,
|
||||||
|
options?: ConstructorParameters<typeof Database>[1]
|
||||||
|
): SqliteDatabase {
|
||||||
|
try {
|
||||||
|
return new Database(sqliteFile, options);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (isNativeSqliteLoadError(error)) {
|
||||||
|
throw createNativeSqliteLoadError(error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure data directory exists — with fallback for restricted home directories (#133)
|
// Ensure data directory exists — with fallback for restricted home directories (#133)
|
||||||
if (!isCloud && !fs.existsSync(DATA_DIR)) {
|
if (!isCloud && !fs.existsSync(DATA_DIR)) {
|
||||||
try {
|
try {
|
||||||
@@ -662,7 +700,7 @@ function captureCriticalDbState(sqliteFile: string): PreservedCriticalDbState {
|
|||||||
|
|
||||||
let probe: SqliteDatabase | null = null;
|
let probe: SqliteDatabase | null = null;
|
||||||
try {
|
try {
|
||||||
probe = new Database(sqliteFile, { readonly: true });
|
probe = openSqliteDatabase(sqliteFile, { readonly: true });
|
||||||
|
|
||||||
for (const tableSpec of CRITICAL_DB_TABLES) {
|
for (const tableSpec of CRITICAL_DB_TABLES) {
|
||||||
if (!hasTable(probe, tableSpec.table)) continue;
|
if (!hasTable(probe, tableSpec.table)) continue;
|
||||||
@@ -1013,7 +1051,7 @@ export function getDbInstance(): SqliteDatabase {
|
|||||||
if (isBuildPhase) {
|
if (isBuildPhase) {
|
||||||
console.log("[DB] Build phase detected — using in-memory SQLite (read-only)");
|
console.log("[DB] Build phase detected — using in-memory SQLite (read-only)");
|
||||||
}
|
}
|
||||||
const memoryDb = new Database(":memory:");
|
const memoryDb = openSqliteDatabase(":memory:");
|
||||||
memoryDb.pragma("journal_mode = WAL");
|
memoryDb.pragma("journal_mode = WAL");
|
||||||
memoryDb.exec(SCHEMA_SQL);
|
memoryDb.exec(SCHEMA_SQL);
|
||||||
ensureUsageHistoryColumns(memoryDb);
|
ensureUsageHistoryColumns(memoryDb);
|
||||||
@@ -1088,7 +1126,7 @@ export function getDbInstance(): SqliteDatabase {
|
|||||||
// Uses a single probe connection that becomes the real connection when possible.
|
// Uses a single probe connection that becomes the real connection when possible.
|
||||||
if (fs.existsSync(sqliteFile)) {
|
if (fs.existsSync(sqliteFile)) {
|
||||||
try {
|
try {
|
||||||
const probe = new Database(sqliteFile, { readonly: true });
|
const probe = openSqliteDatabase(sqliteFile, { readonly: true });
|
||||||
const hasOldSchema = probe
|
const hasOldSchema = probe
|
||||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'")
|
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'")
|
||||||
.get();
|
.get();
|
||||||
@@ -1109,7 +1147,7 @@ export function getDbInstance(): SqliteDatabase {
|
|||||||
console.log(
|
console.log(
|
||||||
`[DB] Old schema_migrations table found but data exists — preserving data (#146)`
|
`[DB] Old schema_migrations table found but data exists — preserving data (#146)`
|
||||||
);
|
);
|
||||||
const fixDb = new Database(sqliteFile);
|
const fixDb = openSqliteDatabase(sqliteFile);
|
||||||
try {
|
try {
|
||||||
fixDb.exec("DROP TABLE IF EXISTS schema_migrations");
|
fixDb.exec("DROP TABLE IF EXISTS schema_migrations");
|
||||||
fixDb.pragma("wal_checkpoint(TRUNCATE)");
|
fixDb.pragma("wal_checkpoint(TRUNCATE)");
|
||||||
@@ -1141,12 +1179,7 @@ export function getDbInstance(): SqliteDatabase {
|
|||||||
console.warn("[DB] Could not probe existing DB:", message);
|
console.warn("[DB] Could not probe existing DB:", message);
|
||||||
|
|
||||||
// If the error is a Node module/ABI failure, throw it immediately to avoid renaming the database
|
// If the error is a Node module/ABI failure, throw it immediately to avoid renaming the database
|
||||||
if (
|
if (isNativeSqliteLoadError(e) || message.includes("could not be found")) {
|
||||||
message.includes("Module did not self-register") ||
|
|
||||||
message.includes("could not be found") ||
|
|
||||||
message.includes("ERR_DLOPEN_FAILED") ||
|
|
||||||
(e as any)?.code === "ERR_DLOPEN_FAILED"
|
|
||||||
) {
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1180,7 +1213,7 @@ export function getDbInstance(): SqliteDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new Database(sqliteFile);
|
const db = openSqliteDatabase(sqliteFile);
|
||||||
db.pragma("journal_mode = WAL");
|
db.pragma("journal_mode = WAL");
|
||||||
db.pragma("busy_timeout = 5000");
|
db.pragma("busy_timeout = 5000");
|
||||||
db.pragma("synchronous = NORMAL");
|
db.pragma("synchronous = NORMAL");
|
||||||
|
|||||||
@@ -144,9 +144,9 @@ export default function Footer() {
|
|||||||
© {new Date().getFullYear()} {APP_CONFIG.name} Inc. All rights reserved.
|
© {new Date().getFullYear()} {APP_CONFIG.name} Inc. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-6 text-sm text-text-muted">
|
<div className="flex gap-6 text-sm text-text-muted">
|
||||||
<a href="/docs" className="hover:text-primary transition-colors">
|
<Link href="/docs" className="hover:text-primary transition-colors">
|
||||||
Documentation
|
Documentation
|
||||||
</a>
|
</Link>
|
||||||
<Link href="/terms" className="hover:text-primary transition-colors">
|
<Link href="/terms" className="hover:text-primary transition-colors">
|
||||||
Terms
|
Terms
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -26,11 +26,7 @@ export default function DocsBreadcrumbs({ labels, className }: DocsBreadcrumbsPr
|
|||||||
aria-label="Breadcrumb"
|
aria-label="Breadcrumb"
|
||||||
className={cn("flex items-center gap-2 text-sm text-text-muted mb-6", className)}
|
className={cn("flex items-center gap-2 text-sm text-text-muted mb-6", className)}
|
||||||
>
|
>
|
||||||
<<<<<<< HEAD
|
|
||||||
<Link href="/docs" className="hover:text-text-main transition-colors">
|
|
||||||
=======
|
|
||||||
<Link href="/" className="hover:text-text-main transition-colors">
|
<Link href="/" className="hover:text-text-main transition-colors">
|
||||||
>>>>>>> upstream/release/v3.7.9
|
|
||||||
Docs
|
Docs
|
||||||
</Link>
|
</Link>
|
||||||
{crumbs.map((crumb, i) => (
|
{crumbs.map((crumb, i) => (
|
||||||
|
|||||||
@@ -14,21 +14,6 @@ export const Default: Story = {
|
|||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
label: "Installation",
|
label: "Installation",
|
||||||
<<<<<<< HEAD
|
|
||||||
content: <div className="p-2">Run npm install to get started.</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Configuration",
|
|
||||||
content: <div className="p-2">Edit your config.json file.</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Usage",
|
|
||||||
content: <div className="p-2">Import the provider and start routing.</div>,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
=======
|
|
||||||
content: '<div className="p-2">Run npm install to get started.</div>',
|
content: '<div className="p-2">Run npm install to get started.</div>',
|
||||||
},
|
},
|
||||||
{ label: "Configuration", content: '<div className="p-2">Edit your config.json file.</div>' },
|
{ label: "Configuration", content: '<div className="p-2">Edit your config.json file.</div>' },
|
||||||
@@ -39,4 +24,3 @@ export const Default: Story = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
>>>>>>> upstream/release/v3.7.9
|
|
||||||
|
|||||||
@@ -291,7 +291,8 @@ test("shouldMarkAccountExhaustedFrom429 skips connection poisoning for compatibl
|
|||||||
shouldMarkAccountExhaustedFrom429("openai-compatible-custom-node", "any-model"),
|
shouldMarkAccountExhaustedFrom429("openai-compatible-custom-node", "any-model"),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
assert.equal(shouldMarkAccountExhaustedFrom429("openai", "gpt-4o-mini"), true);
|
assert.equal(shouldMarkAccountExhaustedFrom429("openai", "gpt-4o-mini"), false);
|
||||||
|
assert.equal(shouldMarkAccountExhaustedFrom429("claude", "claude-sonnet-4-6"), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("hasPerModelQuota returns true for GitHub Copilot provider (#1624)", () => {
|
test("hasPerModelQuota returns true for GitHub Copilot provider (#1624)", () => {
|
||||||
|
|||||||
@@ -76,6 +76,6 @@ test("AntigravityExecutor.transformRequest keeps Claude bridge output cap and st
|
|||||||
projectId: "project-1",
|
projectId: "project-1",
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.request.generationConfig.maxOutputTokens, 16_384);
|
assert.equal(result.request.max_tokens, 16_384);
|
||||||
assert.equal(result.request.generationConfig.thinkingConfig, undefined);
|
assert.equal(result.request.thinking, undefined);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -840,7 +840,7 @@ test("chatCore strips unsupported reasoning params and caps provider token field
|
|||||||
|
|
||||||
assert.equal(call.body.temperature, undefined);
|
assert.equal(call.body.temperature, undefined);
|
||||||
assert.equal(call.body.presence_penalty, undefined);
|
assert.equal(call.body.presence_penalty, undefined);
|
||||||
assert.equal(call.body.max_tokens, 16384);
|
assert.equal(call.body.max_tokens, undefined);
|
||||||
assert.equal(call.body.max_completion_tokens, 16384);
|
assert.equal(call.body.max_completion_tokens, 16384);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1092,6 +1092,7 @@ test("Provider registry: chatgpt-web exposes the current ChatGPT Web model catal
|
|||||||
assert.deepEqual(ids, [
|
assert.deepEqual(ids, [
|
||||||
"gpt-5.5-pro",
|
"gpt-5.5-pro",
|
||||||
"gpt-5.5-thinking",
|
"gpt-5.5-thinking",
|
||||||
|
"gpt-5.5",
|
||||||
"gpt-5.4-pro",
|
"gpt-5.4-pro",
|
||||||
"gpt-5.4-thinking",
|
"gpt-5.4-thinking",
|
||||||
"gpt-5.4-thinking-mini",
|
"gpt-5.4-thinking-mini",
|
||||||
|
|||||||
@@ -14,8 +14,19 @@ import { SEARCH_INDEX } from "../../src/app/docs/lib/searchIndex";
|
|||||||
// docsNavigation structure
|
// docsNavigation structure
|
||||||
// ──────────────────────────────────────────────
|
// ──────────────────────────────────────────────
|
||||||
|
|
||||||
test("docsNavigation has 6 sections", () => {
|
test("docsNavigation has expected sections", () => {
|
||||||
assert.equal(docsNavigation.length, 6);
|
assert.deepEqual(
|
||||||
|
docsNavigation.map((section) => section.title),
|
||||||
|
[
|
||||||
|
"Getting Started",
|
||||||
|
"Features",
|
||||||
|
"API & Protocols",
|
||||||
|
"Deployment",
|
||||||
|
"Operations",
|
||||||
|
"Development",
|
||||||
|
"Other",
|
||||||
|
]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("every section has title and items", () => {
|
test("every section has title and items", () => {
|
||||||
@@ -147,12 +158,12 @@ test("extractHeadings strips bold and code from heading text", () => {
|
|||||||
|
|
||||||
test("renderMarkdown converts headings to HTML", () => {
|
test("renderMarkdown converts headings to HTML", () => {
|
||||||
const html = renderMarkdown("# Title\n## Section\n### Subsection\n#### Details");
|
const html = renderMarkdown("# Title\n## Section\n### Subsection\n#### Details");
|
||||||
assert.ok(html.includes('<h1'), "h1 tag");
|
assert.ok(html.includes("<h1"), "h1 tag");
|
||||||
assert.ok(html.includes("Title"), "h1 text");
|
assert.ok(html.includes("Title"), "h1 text");
|
||||||
assert.ok(html.includes('<h2'), "h2 tag");
|
assert.ok(html.includes("<h2"), "h2 tag");
|
||||||
assert.ok(html.includes("Section"), "h2 text");
|
assert.ok(html.includes("Section"), "h2 text");
|
||||||
assert.ok(html.includes('<h3'), "h3 tag");
|
assert.ok(html.includes("<h3"), "h3 tag");
|
||||||
assert.ok(html.includes('<h4'), "h4 tag");
|
assert.ok(html.includes("<h4"), "h4 tag");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renderMarkdown sanitizes XSS content", () => {
|
test("renderMarkdown sanitizes XSS content", () => {
|
||||||
|
|||||||
@@ -66,6 +66,41 @@ describe("encryption module", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("migrateLegacyEncryptedString() behavior", () => {
|
||||||
|
it("should upgrade legacy encrypted tokens to static key tokens", async () => {
|
||||||
|
const secret = "test-secret-key-12345";
|
||||||
|
const plaintext = "legacy-token";
|
||||||
|
const legacyEncrypted = createLegacyEncrypted(plaintext, secret);
|
||||||
|
|
||||||
|
vi.stubEnv("STORAGE_ENCRYPTION_KEY", secret);
|
||||||
|
vi.resetModules();
|
||||||
|
|
||||||
|
const { migrateLegacyEncryptedString, decrypt } = await import("@/lib/db/encryption");
|
||||||
|
|
||||||
|
const result = migrateLegacyEncryptedString(legacyEncrypted);
|
||||||
|
expect(result.updated).toBe(true);
|
||||||
|
expect(result.value).not.toBe(legacyEncrypted);
|
||||||
|
expect(result.value).toMatch(/^enc:v1:/);
|
||||||
|
|
||||||
|
const decrypted = decrypt(result.value);
|
||||||
|
expect(decrypted).toBe(plaintext);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should NOT upgrade static-key encrypted tokens", async () => {
|
||||||
|
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
||||||
|
vi.resetModules();
|
||||||
|
|
||||||
|
const { encrypt, migrateLegacyEncryptedString } = await import("@/lib/db/encryption");
|
||||||
|
|
||||||
|
const plaintext = "modern-token";
|
||||||
|
const encrypted = encrypt(plaintext);
|
||||||
|
|
||||||
|
const result = migrateLegacyEncryptedString(encrypted!);
|
||||||
|
expect(result.updated).toBe(false);
|
||||||
|
expect(result.value).toBe(encrypted);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("passthrough mode: no STORAGE_ENCRYPTION_KEY set → plaintext stored", () => {
|
describe("passthrough mode: no STORAGE_ENCRYPTION_KEY set → plaintext stored", () => {
|
||||||
it("should return plaintext when encryption key is not set", async () => {
|
it("should return plaintext when encryption key is not set", async () => {
|
||||||
// No STORAGE_ENCRYPTION_KEY set
|
// No STORAGE_ENCRYPTION_KEY set
|
||||||
|
|||||||
@@ -1,327 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
||||||
import { createCipheriv, randomBytes, scryptSync, createHash } from "crypto";
|
|
||||||
|
|
||||||
// Test helper to manually create legacy-encrypted values
|
|
||||||
function createLegacyEncrypted(plaintext: string, secret: string): string {
|
|
||||||
const ALGORITHM = "aes-256-gcm";
|
|
||||||
const IV_LENGTH = 16;
|
|
||||||
const KEY_LENGTH = 32;
|
|
||||||
const PREFIX = "enc:v1:";
|
|
||||||
|
|
||||||
// OLD dynamic salt derivation (the bug)
|
|
||||||
const dynamicSalt = createHash("sha256").update(secret).digest().slice(0, 16);
|
|
||||||
const legacyKey = scryptSync(secret, dynamicSalt, KEY_LENGTH);
|
|
||||||
|
|
||||||
const iv = randomBytes(IV_LENGTH);
|
|
||||||
const cipher = createCipheriv(ALGORITHM, legacyKey, iv);
|
|
||||||
|
|
||||||
let encrypted = cipher.update(plaintext, "utf8", "hex");
|
|
||||||
encrypted += cipher.final("hex");
|
|
||||||
const authTag = cipher.getAuthTag().toString("hex");
|
|
||||||
|
|
||||||
return `${PREFIX}${iv.toString("hex")}:${encrypted}:${authTag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("encryption module", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// Clear all env vars and reset modules before each test
|
|
||||||
vi.unstubAllEnvs();
|
|
||||||
vi.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.unstubAllEnvs();
|
|
||||||
vi.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("encrypt/decrypt roundtrip with static key (PRIMARY path)", () => {
|
|
||||||
it("should encrypt and decrypt a value successfully", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt, decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const plaintext = "my-secret-api-key";
|
|
||||||
const encrypted = encrypt(plaintext);
|
|
||||||
|
|
||||||
expect(encrypted).toBeDefined();
|
|
||||||
expect(encrypted).toMatch(/^enc:v1:/);
|
|
||||||
expect(encrypted).not.toBe(plaintext);
|
|
||||||
|
|
||||||
const decrypted = decrypt(encrypted!);
|
|
||||||
expect(decrypted).toBe(plaintext);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle multiple encrypt/decrypt cycles", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt, decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const values = ["token1", "token2", "token3"];
|
|
||||||
const encrypted = values.map((v) => encrypt(v));
|
|
||||||
const decrypted = encrypted.map((e) => decrypt(e!));
|
|
||||||
|
|
||||||
expect(decrypted).toEqual(values);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("migrateLegacyEncryptedString() behavior", () => {
|
|
||||||
it("should upgrade legacy encrypted tokens to static key tokens", async () => {
|
|
||||||
const secret = "test-secret-key-12345";
|
|
||||||
const plaintext = "legacy-token";
|
|
||||||
const legacyEncrypted = createLegacyEncrypted(plaintext, secret);
|
|
||||||
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", secret);
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { migrateLegacyEncryptedString, decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const result = migrateLegacyEncryptedString(legacyEncrypted);
|
|
||||||
expect(result.updated).toBe(true);
|
|
||||||
expect(result.value).not.toBe(legacyEncrypted);
|
|
||||||
expect(result.value).toMatch(/^enc:v1:/);
|
|
||||||
|
|
||||||
const decrypted = decrypt(result.value);
|
|
||||||
expect(decrypted).toBe(plaintext);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should NOT upgrade static-key encrypted tokens", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt, migrateLegacyEncryptedString } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const plaintext = "modern-token";
|
|
||||||
const encrypted = encrypt(plaintext);
|
|
||||||
|
|
||||||
const result = migrateLegacyEncryptedString(encrypted!);
|
|
||||||
expect(result.updated).toBe(false);
|
|
||||||
expect(result.value).toBe(encrypted);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("passthrough mode: no STORAGE_ENCRYPTION_KEY set → plaintext stored", () => {
|
|
||||||
it("should return plaintext when encryption key is not set", async () => {
|
|
||||||
// No STORAGE_ENCRYPTION_KEY set
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt, decrypt, isEncryptionEnabled } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
expect(isEncryptionEnabled()).toBe(false);
|
|
||||||
|
|
||||||
const plaintext = "my-api-key";
|
|
||||||
const encrypted = encrypt(plaintext);
|
|
||||||
|
|
||||||
expect(encrypted).toBe(plaintext);
|
|
||||||
|
|
||||||
const decrypted = decrypt(plaintext);
|
|
||||||
expect(decrypted).toBe(plaintext);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle null and undefined in passthrough mode", async () => {
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt, decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
expect(encrypt(null)).toBeNull();
|
|
||||||
expect(encrypt(undefined)).toBeUndefined();
|
|
||||||
expect(decrypt(null)).toBeNull();
|
|
||||||
expect(decrypt(undefined)).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("encryptConnectionFields / decryptConnectionFields helpers", () => {
|
|
||||||
it("should encrypt all credential fields in a connection object", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encryptConnectionFields } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const conn = {
|
|
||||||
id: "conn-123",
|
|
||||||
apiKey: "plain-api-key",
|
|
||||||
accessToken: "plain-access-token",
|
|
||||||
refreshToken: "plain-refresh-token",
|
|
||||||
idToken: "plain-id-token",
|
|
||||||
};
|
|
||||||
|
|
||||||
const encrypted = encryptConnectionFields(conn);
|
|
||||||
|
|
||||||
expect(encrypted.id).toBe("conn-123");
|
|
||||||
expect(encrypted.apiKey).toMatch(/^enc:v1:/);
|
|
||||||
expect(encrypted.accessToken).toMatch(/^enc:v1:/);
|
|
||||||
expect(encrypted.refreshToken).toMatch(/^enc:v1:/);
|
|
||||||
expect(encrypted.idToken).toMatch(/^enc:v1:/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should decrypt all credential fields in a connection object", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encryptConnectionFields, decryptConnectionFields } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const conn = {
|
|
||||||
id: "conn-123",
|
|
||||||
apiKey: "plain-api-key",
|
|
||||||
accessToken: "plain-access-token",
|
|
||||||
refreshToken: "plain-refresh-token",
|
|
||||||
idToken: "plain-id-token",
|
|
||||||
};
|
|
||||||
|
|
||||||
const encrypted = encryptConnectionFields({ ...conn });
|
|
||||||
const decrypted = decryptConnectionFields(encrypted);
|
|
||||||
|
|
||||||
expect(decrypted.id).toBe("conn-123");
|
|
||||||
expect(decrypted.apiKey).toBe("plain-api-key");
|
|
||||||
expect(decrypted.accessToken).toBe("plain-access-token");
|
|
||||||
expect(decrypted.refreshToken).toBe("plain-refresh-token");
|
|
||||||
expect(decrypted.idToken).toBe("plain-id-token");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("edge cases: null/undefined inputs, already-encrypted, malformed ciphertext", () => {
|
|
||||||
it("should handle null and undefined inputs", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt, decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
expect(encrypt(null)).toBeNull();
|
|
||||||
expect(encrypt(undefined)).toBeUndefined();
|
|
||||||
expect(decrypt(null)).toBeNull();
|
|
||||||
expect(decrypt(undefined)).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not double-encrypt already-encrypted values", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const plaintext = "my-secret";
|
|
||||||
const encrypted = encrypt(plaintext);
|
|
||||||
|
|
||||||
expect(encrypted).toMatch(/^enc:v1:/);
|
|
||||||
|
|
||||||
// Try to encrypt again
|
|
||||||
const doubleEncrypted = encrypt(encrypted!);
|
|
||||||
expect(doubleEncrypted).toBe(encrypted);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return null for malformed ciphertext (missing parts)", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const malformed = "enc:v1:onlyonepart";
|
|
||||||
const result = decrypt(malformed);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return null for malformed ciphertext (invalid hex)", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const malformed = "enc:v1:notvalidhex:notvalidhex:notvalidhex";
|
|
||||||
const result = decrypt(malformed);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return null for ciphertext with wrong auth tag", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt, decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const plaintext = "my-secret";
|
|
||||||
const encrypted = encrypt(plaintext);
|
|
||||||
|
|
||||||
// Tamper with the auth tag
|
|
||||||
const parts = encrypted!.split(":");
|
|
||||||
parts[parts.length - 1] = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
||||||
const tampered = parts.join(":");
|
|
||||||
|
|
||||||
const result = decrypt(tampered);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return plaintext unchanged if not encrypted (legacy plaintext)", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const plaintext = "not-encrypted-value";
|
|
||||||
const result = decrypt(plaintext);
|
|
||||||
expect(result).toBe(plaintext);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return null when trying to decrypt without encryption key", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { encrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const plaintext = "my-secret";
|
|
||||||
const encrypted = encrypt(plaintext);
|
|
||||||
|
|
||||||
// Now remove the key and try to decrypt
|
|
||||||
vi.unstubAllEnvs();
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { decrypt } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const result = decrypt(encrypted!);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("validateEncryptionConfig() with various key states", () => {
|
|
||||||
it("should return valid when no key is set (passthrough mode)", async () => {
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { validateEncryptionConfig } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const result = validateEncryptionConfig();
|
|
||||||
expect(result.valid).toBe(true);
|
|
||||||
expect(result.error).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return valid when a proper key is set", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "test-secret-key-12345");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { validateEncryptionConfig } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const result = validateEncryptionConfig();
|
|
||||||
expect(result.valid).toBe(true);
|
|
||||||
expect(result.error).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return valid when key is empty string (treated as not set)", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", "");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { validateEncryptionConfig } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const result = validateEncryptionConfig();
|
|
||||||
expect(result.valid).toBe(true);
|
|
||||||
expect(result.error).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return invalid when key is whitespace only", async () => {
|
|
||||||
vi.stubEnv("STORAGE_ENCRYPTION_KEY", " ");
|
|
||||||
vi.resetModules();
|
|
||||||
|
|
||||||
const { validateEncryptionConfig } = await import("@/lib/db/encryption");
|
|
||||||
|
|
||||||
const result = validateEncryptionConfig();
|
|
||||||
expect(result.valid).toBe(false);
|
|
||||||
expect(result.error).toContain("empty");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -35,6 +35,7 @@ test("getNodeRuntimeSupport accepts patched Node 24, 22 and 20 LTS lines", () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(getNodeRuntimeSupport("20.20.2").nodeCompatible, true);
|
assert.equal(getNodeRuntimeSupport("20.20.2").nodeCompatible, true);
|
||||||
|
assert.equal(getNodeRuntimeSupport("26.0.0").nodeCompatible, true);
|
||||||
assert.deepEqual(getNodeRuntimeSupport("24.1.0"), {
|
assert.deepEqual(getNodeRuntimeSupport("24.1.0"), {
|
||||||
nodeVersion: "v24.1.0",
|
nodeVersion: "v24.1.0",
|
||||||
nodeCompatible: true,
|
nodeCompatible: true,
|
||||||
|
|||||||
@@ -102,6 +102,5 @@ test("T15: openaiToClaudeRequest converts system array content into a Claude sys
|
|||||||
|
|
||||||
const translated = openaiToClaudeRequest("claude-sonnet-4", request, false);
|
const translated = openaiToClaudeRequest("claude-sonnet-4", request, false);
|
||||||
assert.ok(Array.isArray(translated.system));
|
assert.ok(Array.isArray(translated.system));
|
||||||
// system[0] is the injected Claude prompt; user-provided system content is system[1].
|
assert.equal(translated.system[0].text, "System rules A\nSystem rules B");
|
||||||
assert.equal(translated.system[1].text, "System rules A\nSystem rules B");
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -185,8 +185,7 @@ test("Claude native messages can be round-tripped through OpenAI into Claude OAu
|
|||||||
content: [{ type: "text", text: "reply with OK only" }],
|
content: [{ type: "text", text: "reply with OK only" }],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
assert.ok(Array.isArray(translated.system));
|
assert.equal(translated.system, undefined);
|
||||||
assert.equal(translated.system[0]?.text?.includes("You are Claude Code"), true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("CodexExecutor maps fast service tier to priority", () => {
|
test("CodexExecutor maps fast service tier to priority", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user