Spaces:
Running
Running
Sync from felladrin/MiniSearch@fd8be33
Browse files- .env.example +9 -3
- agents.md +1 -1
- changelog.md +5 -0
- docs/api.md +27 -11
- docs/configuration.md +14 -3
- docs/failure-injection.md +3 -0
- docs/overview.md +2 -1
- docs/security.md +4 -4
- package-lock.json +144 -64
.env.example
CHANGED
|
@@ -26,8 +26,14 @@ INTERNAL_OPENAI_COMPATIBLE_API_NAME="Internal API"
|
|
| 26 |
# "internal" -> $INTERNAL_OPENAI_COMPATIBLE_API_NAME
|
| 27 |
DEFAULT_INFERENCE_TYPE="browser"
|
| 28 |
|
| 29 |
-
# Whether a text search that failed on SearXNG is retried against
|
| 30 |
SEARCH_FALLBACK_ENABLED="false"
|
| 31 |
|
| 32 |
-
#
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
# "internal" -> $INTERNAL_OPENAI_COMPATIBLE_API_NAME
|
| 27 |
DEFAULT_INFERENCE_TYPE="browser"
|
| 28 |
|
| 29 |
+
# Whether a text search that failed on SearXNG is retried against the second search providers (search.parallel.ai and You.com, tried one after the other in random order until one returns results). Only "true" or "1" turn it on.
|
| 30 |
SEARCH_FALLBACK_ENABLED="false"
|
| 31 |
|
| 32 |
+
# Comma-separated list of the second search providers to try: "parallel", "youcom", or both. Empty means all of them. With the fallback enabled, an unknown name stops the server at start.
|
| 33 |
+
SEARCH_FALLBACK_PROVIDERS=""
|
| 34 |
+
|
| 35 |
+
# The API key for Parallel (search.parallel.ai). Optional; without it the keyless tier is used, which Parallel describes as for exploration and light use.
|
| 36 |
+
SEARCH_FALLBACK_PARALLEL_API_KEY=""
|
| 37 |
+
|
| 38 |
+
# The API key for You.com. Optional; without it the keyless "profile=free" endpoint is used, which allows 100 queries per day.
|
| 39 |
+
SEARCH_FALLBACK_YOUCOM_API_KEY=""
|
agents.md
CHANGED
|
@@ -154,7 +154,7 @@ Need to:
|
|
| 154 |
- `server/dictationModelServerHook.ts` - `/dictation-models/` endpoint: serves the pinned speech-to-text model files from the instance
|
| 155 |
- `server/cacheServerHook.ts` - Cache-Control headers (preview server only)
|
| 156 |
- `server/webSearchService.ts` - SearXNG integration with circuit breaker and retry logic
|
| 157 |
-
- `server/fallbackSearchService.ts` -
|
| 158 |
- `server/pageContentService.ts` - Reads result pages and extracts query-relevant passages
|
| 159 |
- `server/pageReadHostBreaker.ts` - Per-host circuit breaker for page reads: skips a host that refused the last three reads, then probes it once
|
| 160 |
- `server/utils/publicUrl.ts` - Blocks private and reserved addresses before a server-side fetch
|
|
|
|
| 154 |
- `server/dictationModelServerHook.ts` - `/dictation-models/` endpoint: serves the pinned speech-to-text model files from the instance
|
| 155 |
- `server/cacheServerHook.ts` - Cache-Control headers (preview server only)
|
| 156 |
- `server/webSearchService.ts` - SearXNG integration with circuit breaker and retry logic
|
| 157 |
+
- `server/fallbackSearchService.ts` - Fallback text-search cascade over Parallel and You.com for when SearXNG fails and `SEARCH_FALLBACK_ENABLED` is on, with a per-provider pause after a 429
|
| 158 |
- `server/pageContentService.ts` - Reads result pages and extracts query-relevant passages
|
| 159 |
- `server/pageReadHostBreaker.ts` - Per-host circuit breaker for page reads: skips a host that refused the last three reads, then probes it once
|
| 160 |
- `server/utils/publicUrl.ts` - Blocks private and reserved addresses before a server-side fetch
|
changelog.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
| 3 |
Notable changes, grouped by date, most recent first.
|
| 4 |
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
## 2026-10-08
|
| 7 |
|
| 8 |
- `/status` now reports `authorization.distinctRejectedTokens`, the number of distinct dead tokens the server has cached and keeps refusing. A steady stream of invalid-token rejections with a value of 1 there is one client replaying one stale token, not many old search links, which the existing counters could not tell apart.
|
|
|
|
| 3 |
Notable changes, grouped by date, most recent first.
|
| 4 |
|
| 5 |
|
| 6 |
+
## 2026-10-10
|
| 7 |
+
|
| 8 |
+
- The text-search fallback now cascades over Parallel and You.com instead of asking a single provider. On each search the configured providers are tried in a random order, one after the other, until one returns results, under the same 15-second budget, which is split across the providers still untried and not paused so a hanging one cannot starve the rest. A provider that answers HTTP 429 is paused until its `Retry-After` and skipped meanwhile, as You.com's acceptable use policy asks. A single search can now reach more than one provider; each sees the query text and this server's IP address, never the user's. `SEARCH_FALLBACK_PROVIDERS` picks the providers (all of them when empty), and `SEARCH_FALLBACK_PARALLEL_API_KEY` and `SEARCH_FALLBACK_YOUCOM_API_KEY` hold the optional keys. `SEARCH_FALLBACK_PROVIDER` and `SEARCH_FALLBACK_API_KEY` are removed: if either is still set while the fallback is enabled, the server stops at start with an error naming the replacement, and so does a provider key with a control character inside it. `/status` gained per-provider counters under `searches.fallbackProviders`.
|
| 9 |
+
|
| 10 |
+
|
| 11 |
## 2026-10-08
|
| 12 |
|
| 13 |
- `/status` now reports `authorization.distinctRejectedTokens`, the number of distinct dead tokens the server has cached and keeps refusing. A steady stream of invalid-token rejections with a value of 1 there is one client replaying one stale token, not many old search links, which the existing counters could not tell apart.
|
docs/api.md
CHANGED
|
@@ -175,16 +175,23 @@ Failures:
|
|
| 175 |
| `500` | `{"error":"Internal server error"}` | Anything else |
|
| 176 |
|
| 177 |
With `SEARCH_FALLBACK_ENABLED` on (see `docs/configuration.md`), a text search
|
| 178 |
-
whose SearXNG attempt failed is retried against
|
| 179 |
-
endpoint gives up.
|
| 180 |
-
|
| 181 |
-
answers
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
### `GET /search/images`
|
| 190 |
|
|
@@ -360,9 +367,18 @@ counts searches answered with fallback results, and `searchesFailedOnFallback`
|
|
| 360 |
counts the other attempts: those the fallback itself failed, and those where
|
| 361 |
SearXNG's retries had left so little of the 25-second search deadline that the
|
| 362 |
fallback was never asked. A rising `searchesFailedOnFallback` is therefore not
|
| 363 |
-
by itself a sign that the fallback
|
| 364 |
and neither carries a query.
|
| 365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
Nothing in the response is per-user: queries, URLs and client addresses are
|
| 367 |
never recorded, only aggregate outcomes
|
| 368 |
(see the privacy section of `docs/security.md`).
|
|
|
|
| 175 |
| `500` | `{"error":"Internal server error"}` | Anything else |
|
| 176 |
|
| 177 |
With `SEARCH_FALLBACK_ENABLED` on (see `docs/configuration.md`), a text search
|
| 178 |
+
whose SearXNG attempt failed is retried against the configured fallback
|
| 179 |
+
providers before this endpoint gives up. They are tried one after the other, in
|
| 180 |
+
a random order on every search, until one returns results; a provider that
|
| 181 |
+
answers with nothing usable or fails hands the search to the next. The whole
|
| 182 |
+
cascade runs under one 15-second budget, or what is left of the search's own
|
| 183 |
+
deadline when that is shorter, and each attempt gets the remaining budget
|
| 184 |
+
divided by the number of providers still untried and not paused. A provider
|
| 185 |
+
paused after a `429` is skipped without a request. The cascade runs only after SearXNG has
|
| 186 |
+
failed: never while SearXNG answers, and never on an image search. It is
|
| 187 |
+
skipped when less than two seconds of the search's own 25-second deadline
|
| 188 |
+
remain, since an answer that late cannot reach the client before its 30-second
|
| 189 |
+
timeout, and that skip answers the `502` above and counts as a fallback that
|
| 190 |
+
failed. If at least one provider answers with nothing usable and none returns
|
| 191 |
+
results, this endpoint responds `200` with `[]`, so the client shows the
|
| 192 |
+
"No results found" alert rather than the "Text search unavailable" one; only
|
| 193 |
+
when every provider tried fails, or none could be tried at all, does the
|
| 194 |
+
search fall through to the same `502`.
|
| 195 |
|
| 196 |
### `GET /search/images`
|
| 197 |
|
|
|
|
| 367 |
counts the other attempts: those the fallback itself failed, and those where
|
| 368 |
SearXNG's retries had left so little of the 25-second search deadline that the
|
| 369 |
fallback was never asked. A rising `searchesFailedOnFallback` is therefore not
|
| 370 |
+
by itself a sign that the fallback providers are failing. Both are plain counts,
|
| 371 |
and neither carries a query.
|
| 372 |
|
| 373 |
+
`searches.fallbackProviders` breaks the cascade down per provider name. Each
|
| 374 |
+
entry counts the attempts that provider `served` with results, answered
|
| 375 |
+
`empty`, or `failed`, the subset of those failures that were a `429`
|
| 376 |
+
(`rateLimited`, also counted in `failed`), and the searches that skipped it
|
| 377 |
+
while it was paused (`skippedWhilePaused`); `pausedForMs` is
|
| 378 |
+
the time left on its current pause, or `0` when it is not paused. The names are
|
| 379 |
+
configured infrastructure, not anything a user typed, and no field carries a
|
| 380 |
+
query or a provider's error text.
|
| 381 |
+
|
| 382 |
Nothing in the response is per-user: queries, URLs and client addresses are
|
| 383 |
never recorded, only aggregate outcomes
|
| 384 |
(see the privacy section of `docs/security.md`).
|
docs/configuration.md
CHANGED
|
@@ -91,12 +91,23 @@ These defaults are provided by `docker-compose.yml`/`docker-compose.production.y
|
|
| 91 |
|
| 92 |
### Search Fallback
|
| 93 |
|
| 94 |
-
When SearXNG fails on a text search, the server can retry that search against
|
| 95 |
|
| 96 |
| Variable | Default | Description |
|
| 97 |
|----------|---------|-------------|
|
| 98 |
-
| `SEARCH_FALLBACK_ENABLED` | `false` | Serve text results from the fallback when SearXNG has failed (an HTTP error, a network error, every engine unresponsive, or an open circuit), under one 15-second budget for the whole
|
| 99 |
-
| `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
## Application Settings
|
| 102 |
|
|
|
|
| 91 |
|
| 92 |
### Search Fallback
|
| 93 |
|
| 94 |
+
When SearXNG fails on a text search, the server can retry that search against second providers: `search.parallel.ai` (Parallel) and You.com's MCP endpoint (`https://api.you.com/mcp`). Every configured provider takes part. On each search they are put in a random order, and each is tried in turn until one returns results; a provider that answers with nothing usable, or fails, hands the search to the next one. The whole cascade shares one 15-second budget, and each attempt gets the remaining budget divided by the number of providers still untried and not paused, so a provider that hangs cannot use up the time the others need. A single search can therefore reach more than one provider, one after the other. `docker-compose.yml` passes only `HOST`, `PORT`, `BASIC_SSL` and `HMR_PORT` into the development container, so these variables are not carried by the compose `environment` list; set them in `.env`, which `vite.config.ts` loads, inside the container as well as outside it. `vite.config.ts` reads `.env.example` after `.env`, so anything still set there acts as a default: the real API keys belong in `.env`, never in `.env.example`, which is committed.
|
| 95 |
|
| 96 |
| Variable | Default | Description |
|
| 97 |
|----------|---------|-------------|
|
| 98 |
+
| `SEARCH_FALLBACK_ENABLED` | `false` | Serve text results from the fallback providers when SearXNG has failed (an HTTP error, a network error, every engine unresponsive, or an open circuit), under one 15-second budget for the whole cascade. Only `true` or `1` turns it on, and changing it takes a restart, since a process keeps the environment it was started with. Image searches never use it, and a fallback that returns nothing usable is still an HTTP 200 with no results rather than an outage |
|
| 99 |
+
| `SEARCH_FALLBACK_PROVIDERS` | `''` (all providers) | Comma-separated list of the providers to try: `parallel`, `youcom`, or both. Names are trimmed and lowercased, and empty items are ignored. Unset or empty means every built-in provider. With `SEARCH_FALLBACK_ENABLED` on, an unknown name, or a list with no names at all (such as `,`), stops the server at start with an error that names the valid values |
|
| 100 |
+
| `SEARCH_FALLBACK_PARALLEL_API_KEY` | `''` | Optional API key for Parallel, sent as a Bearer token. Without it the fallback calls Parallel's keyless tier. With `SEARCH_FALLBACK_ENABLED` on, a key with a control character inside it (such as a pasted line break) stops the server at start, since the HTTP client would quote it in its error |
|
| 101 |
+
| `SEARCH_FALLBACK_YOUCOM_API_KEY` | `''` | Optional API key for You.com, sent as a Bearer token. Without it the fallback calls the keyless `https://api.you.com/mcp?profile=free` endpoint. A key with a control character inside it stops the server at start, like the Parallel key |
|
| 102 |
+
|
| 103 |
+
`SEARCH_FALLBACK_PROVIDER` and `SEARCH_FALLBACK_API_KEY` are gone. If either is still set to a non-empty value while `SEARCH_FALLBACK_ENABLED` is on, the server stops at start with an error that names the variable to use instead, so an upgrade cannot silently drop a key: move the key to `SEARCH_FALLBACK_PARALLEL_API_KEY` or `SEARCH_FALLBACK_YOUCOM_API_KEY`, and the provider choice to `SEARCH_FALLBACK_PROVIDERS`.
|
| 104 |
+
|
| 105 |
+
Each provider has its own terms, and each operator is responsible for following them:
|
| 106 |
+
|
| 107 |
+
- **Parallel**: Parallel describes its keyless tier as for exploration and light use, and asks for an account and an API key for production use ([docs.parallel.ai](https://docs.parallel.ai/integrations/mcp/search-mcp)).
|
| 108 |
+
- **You.com**: the keyless `profile=free` endpoint allows 100 queries per day ([you.com](https://you.com/docs/build-with-agents/mcp-server)). Its API acceptable use policy asks integrations to honor 429 responses ([you.com](https://you.com/acceptable-use-policy-may-2026)).
|
| 109 |
+
|
| 110 |
+
That is why a provider that answers HTTP 429 is paused: the cascade skips it, without a network call, until its `Retry-After` has passed (60 seconds when the header is missing or invalid, and never more than an hour or less than a second). A public instance should set a key for each provider.
|
| 111 |
|
| 112 |
## Application Settings
|
| 113 |
|
docs/failure-injection.md
CHANGED
|
@@ -37,6 +37,9 @@ needed.
|
|
| 37 |
| SearXNG is down while the fallback is switched off | HTTP 502; the fallback is never consulted and neither fallback counter moves | `server/searchEndpointServerHook.test.ts` โบ graceful degradation โบ text search fallback |
|
| 38 |
| SearXNG is down on an image search while the fallback is on | HTTP 502; the fallback is text-only, so it is never consulted and neither fallback counter moves | `server/searchEndpointServerHook.test.ts` โบ graceful degradation โบ text search fallback |
|
| 39 |
| SearXNG answers while the fallback is on | HTTP 200 from SearXNG's results; the fallback is never consulted and neither fallback counter moves | `server/searchEndpointServerHook.test.ts` โบ graceful degradation โบ text search fallback |
|
|
|
|
|
|
|
|
|
|
| 40 |
| Thumbnail host never answers | Request aborted after the timeout, `/thumbnail` answers HTTP 502 and the tile shows the host name | `server/thumbnailEndpointServerHook.test.ts` |
|
| 41 |
| DNS lookup for a thumbnail never settles | Bounded by the same deadline as the fetch, `/thumbnail` answers HTTP 502 | `server/thumbnailEndpointServerHook.test.ts` |
|
| 42 |
| Thumbnail URL resolves into a private range | Refused before any request, `/thumbnail` answers HTTP 403 | `server/thumbnailEndpointServerHook.test.ts` |
|
|
|
|
| 37 |
| SearXNG is down while the fallback is switched off | HTTP 502; the fallback is never consulted and neither fallback counter moves | `server/searchEndpointServerHook.test.ts` โบ graceful degradation โบ text search fallback |
|
| 38 |
| SearXNG is down on an image search while the fallback is on | HTTP 502; the fallback is text-only, so it is never consulted and neither fallback counter moves | `server/searchEndpointServerHook.test.ts` โบ graceful degradation โบ text search fallback |
|
| 39 |
| SearXNG answers while the fallback is on | HTTP 200 from SearXNG's results; the fallback is never consulted and neither fallback counter moves | `server/searchEndpointServerHook.test.ts` โบ graceful degradation โบ text search fallback |
|
| 40 |
+
| The first fallback provider fails and the next one answers | The cascade moves on to the next provider within what is left of the shared budget, and that provider's results are served; the failure is logged with the provider name and a status code, never the query | `server/fallbackSearchService.test.ts` โบ the cascade |
|
| 41 |
+
| A fallback provider answers HTTP 429 | That provider is paused until its `Retry-After` (60 s when missing or invalid, at most an hour), the next provider serves the search, and later searches skip the paused one without a request until the pause ends | `server/fallbackSearchService.test.ts` โบ pausing a rate-limited provider |
|
| 42 |
+
| Every fallback provider is paused | No provider is called, the cascade throws, and `/search/text` answers HTTP 502 as for a fallback that failed | `server/fallbackSearchService.test.ts` โบ pausing a rate-limited provider |
|
| 43 |
| Thumbnail host never answers | Request aborted after the timeout, `/thumbnail` answers HTTP 502 and the tile shows the host name | `server/thumbnailEndpointServerHook.test.ts` |
|
| 44 |
| DNS lookup for a thumbnail never settles | Bounded by the same deadline as the fetch, `/thumbnail` answers HTTP 502 | `server/thumbnailEndpointServerHook.test.ts` |
|
| 45 |
| Thumbnail URL resolves into a private range | Refused before any request, `/thumbnail` answers HTTP 403 | `server/thumbnailEndpointServerHook.test.ts` |
|
docs/overview.md
CHANGED
|
@@ -248,7 +248,7 @@ The `/status` endpoint returns a JSON object:
|
|
| 248 |
| `searchesWithUnresponsiveEngines` | number | Searches, text and image together, that came back with zero results and unresponsive engines, whether the retries were spent or an all-suspended set failed fast; one per search, not per attempt |
|
| 249 |
| `searchesWithAllResultsDiscarded` | number | Text searches whose results were all dropped during processing |
|
| 250 |
| `searchesServedByFallback` | number | Text searches answered by the fallback source after SearXNG failed |
|
| 251 |
-
| `searchesFailedOnFallback` | number | Fallback searches that failed, plus searches where SearXNG's own retries had left too little of the search deadline for the fallback to be asked at all. A rising count is therefore not by itself a sign that the fallback
|
| 252 |
| `biEncoderServiceStatus` | string | `"healthy"` or `"unhealthy"` |
|
| 253 |
| `rerankerServiceStatus` | string | `"healthy"` or `"unhealthy"` |
|
| 254 |
| `webSearchServiceStatus` | string | `"healthy"`, `"degraded"` or `"unhealthy"`, see below |
|
|
@@ -422,6 +422,7 @@ way `pageReads` does:
|
|
| 422 |
| `searches.degradedSearchTypes` | array | Nothing; which of `text` and `images` is currently flagged, so a `degraded` verdict says which engine pool it is about. Empty when the half-open circuit is what triggered the verdict, which `circuitState` in the same object names |
|
| 423 |
| `searches.circuitOpens` | number | `failureThreshold` and `resetTimeout` on the SearXNG breaker: each opening is a minute of serving no searches at all |
|
| 424 |
| `searches.unresponsiveEngines` | object | Nothing; `failures` and `lastFailure` per engine name, which is how one engine timing out is told apart from every engine being CAPTCHA-blocked. `lastFailure` is `blocked` (a CAPTCHA, a rate limit or an access denial), `timeout` or `other`, never SearXNG's own wording, which is free-form text from an upstream engine and does not belong on an endpoint that needs no token. Counted on the searches `searchesWithUnresponsiveEngines` counts, so a search that recovered on a retry is not in it |
|
|
|
|
| 425 |
| `reranker.reranks` | number | Nothing; the denominator for the rest |
|
| 426 |
| `reranker.averageMs` | number | Whether reranking or SearXNG is what users wait for, and so whether to rerank a shortlist instead of all 30 results. Over `reranks`, covering the filtering and sorting as well as the model |
|
| 427 |
| `reranker.keptRate` | number | `kStandardDeviationFactor`: near 100% means the filter is not filtering. Text and image reranks are pooled, and the two run through different paths, so this moves with the traffic mix as well as with the threshold |
|
|
|
|
| 248 |
| `searchesWithUnresponsiveEngines` | number | Searches, text and image together, that came back with zero results and unresponsive engines, whether the retries were spent or an all-suspended set failed fast; one per search, not per attempt |
|
| 249 |
| `searchesWithAllResultsDiscarded` | number | Text searches whose results were all dropped during processing |
|
| 250 |
| `searchesServedByFallback` | number | Text searches answered by the fallback source after SearXNG failed |
|
| 251 |
+
| `searchesFailedOnFallback` | number | Fallback searches that failed, plus searches where SearXNG's own retries had left too little of the search deadline for the fallback to be asked at all. A rising count is therefore not by itself a sign that the fallback providers are failing |
|
| 252 |
| `biEncoderServiceStatus` | string | `"healthy"` or `"unhealthy"` |
|
| 253 |
| `rerankerServiceStatus` | string | `"healthy"` or `"unhealthy"` |
|
| 254 |
| `webSearchServiceStatus` | string | `"healthy"`, `"degraded"` or `"unhealthy"`, see below |
|
|
|
|
| 422 |
| `searches.degradedSearchTypes` | array | Nothing; which of `text` and `images` is currently flagged, so a `degraded` verdict says which engine pool it is about. Empty when the half-open circuit is what triggered the verdict, which `circuitState` in the same object names |
|
| 423 |
| `searches.circuitOpens` | number | `failureThreshold` and `resetTimeout` on the SearXNG breaker: each opening is a minute of serving no searches at all |
|
| 424 |
| `searches.unresponsiveEngines` | object | Nothing; `failures` and `lastFailure` per engine name, which is how one engine timing out is told apart from every engine being CAPTCHA-blocked. `lastFailure` is `blocked` (a CAPTCHA, a rate limit or an access denial), `timeout` or `other`, never SearXNG's own wording, which is free-form text from an upstream engine and does not belong on an endpoint that needs no token. Counted on the searches `searchesWithUnresponsiveEngines` counts, so a search that recovered on a retry is not in it |
|
| 425 |
+
| `searches.fallbackProviders` | object | Whether each fallback provider earns its place, and whether one needs an API key: per provider name, `served`, `empty`, `failed`, `rateLimited` (answered `429`, and also counted in `failed`) and `skippedWhilePaused` counts, plus `pausedForMs`, the time left on its current `429` pause (`0` when not paused). A steady `rateLimited` count on a keyless provider is the cue to set its key (see `docs/configuration.md`). Provider names are configured infrastructure, not user input |
|
| 426 |
| `reranker.reranks` | number | Nothing; the denominator for the rest |
|
| 427 |
| `reranker.averageMs` | number | Whether reranking or SearXNG is what users wait for, and so whether to rerank a shortlist instead of all 30 results. Over `reranks`, covering the filtering and sorting as well as the model |
|
| 428 |
| `reranker.keptRate` | number | `kStandardDeviationFactor`: near 100% means the filter is not filtering. Text and image reranks are pooled, and the two run through different paths, so this moves with the traffic mix as well as with the threshold |
|
docs/security.md
CHANGED
|
@@ -44,8 +44,8 @@ Every HTTP request from client to backend carries a `token` query parameter for
|
|
| 44 |
|
| 45 |
- **Local-First Storage**: All data stored in IndexedDB, no cloud sync
|
| 46 |
- **No Tracking**: No telemetry, analytics, or user tracking
|
| 47 |
-
- **SearXNG Integration**: All web searches routed through privacy-focused metasearch, which holds for every search as long as `SEARCH_FALLBACK_ENABLED` is off (the default); with it on, a text search that found SearXNG failed goes to the fallback
|
| 48 |
-
- **The Fallback Sends The Query To A Third Party When It Is On**: With `SEARCH_FALLBACK_ENABLED` on (off by default), a text query whose SearXNG search failed is sent to `search.parallel.ai` (Parallel),
|
| 49 |
- **Browser-Only Mode Still Downloads Models From HuggingFace**: The instance sends no telemetry and no analytics. But browser-only mode is not fully offline: both wllama and the local text-to-speech engine fetch their models from `huggingface.co` (served from `us.aws.cdn.hf.co`), so that host sees the user's IP. The text-to-speech engine's full host list is in the Reading Answers Aloud bullet below.
|
| 50 |
- **Page Reading Is On By Default, And Reversible**: `enablePageContentFetch` ships on and the user can turn it off under AI Settings; nothing is read until AI responses are enabled, and the server (never the browser) requests the top result pages, so those sites see the instance and no user cookies or IP
|
| 51 |
- **Search And Page Reading Leave No Query In The Log**: The search query is never written to the server log, and neither is the URL of a page read for grounding; how often searches come back empty, fail on unresponsive engines, are fully discarded, are served by the fallback or lose the fallback too, and how page reads ended, are counted instead and reported on `/status` (`searchesWithoutResults`, `searchesWithUnresponsiveEngines`, `searchesWithAllResultsDiscarded`, `searchesServedByFallback`, `searchesFailedOnFallback`, `pageReads`). `searches.unresponsiveEngines` names the engines behind those failures, which is configured infrastructure rather than anything a user typed, the same basis on which `inference.byModel` names model ids. What each failed with is published as one of `blocked`, `timeout` or `other`, never SearXNG's own wording: `/status` needs no token, and an upstream engine is free to build its error text out of the request URL, and so out of the query. The raw string stays in the server log and in the thrown error the search endpoint catches; the 502 it answers with carries a fixed message, so the string never leaves the server. The page reader's per-host circuit breaker (`server/pageReadHostBreaker.ts`) holds the hosts it has boxed in memory, since it needs them to know which host to skip; a page-read host comes from the search results, downstream of the query, so unlike an engine name it is never published: `/status` reports how many reads were skipped and how often a host was boxed, as counts only. Anything in front of the instance keeps its own access log, where `?q=` appears in full
|
|
@@ -96,7 +96,7 @@ The full trust model lives in [`.github/SECURITY.md`](../.github/SECURITY.md) ("
|
|
| 96 |
| `server/pageReadsSinceLastRestart.ts` | In-memory aggregate counters for pages read for grounding (outcomes, durations, passage ratios), reported on `/status`; records no query, URL, host, or per-read timestamp |
|
| 97 |
| `server/pageReadHostBreaker.ts` | Per-host circuit breaker for page reads: a host that refused the last three reads is skipped for five minutes, then probed with one read; holds host names in memory only, reset on restart, and reports counts alone |
|
| 98 |
| `server/searchEndpointServerHook.ts` | Proxies text/image search to SearXNG after token verification (via `handleTokenVerification`), and serves fallback text results when SearXNG has failed and `SEARCH_FALLBACK_ENABLED` is on |
|
| 99 |
-
| `server/fallbackSearchService.ts` | Second text-search source, consulted only after SearXNG has failed: reads `SEARCH_FALLBACK_ENABLED`
|
| 100 |
| `server/verifyTokenAndRateLimit.ts` | Verifies the Argon2 token hash and enforces rate limiting (10 requests per 10 seconds, shared by the search, page-content and inference endpoints; a separate 60-per-10-seconds budget for `/thumbnail`) |
|
| 101 |
| `server/handleTokenVerification.ts` | Middleware bridge that calls `verifyTokenAndRateLimit` and writes 400/401/429 error responses for the search, page-content, thumbnail and inference endpoints |
|
| 102 |
| `server/configEndpointServerHook.ts` | Serves the non-secret runtime config at `/api/config`, including whether access keys are enabled |
|
|
@@ -171,7 +171,7 @@ pin and keeps the old rebinding residual.
|
|
| 171 |
## Threat Model
|
| 172 |
|
| 173 |
- **Local Environment**: Assumes trusted local execution
|
| 174 |
-
- **Network Requests**: All external requests go through SearXNG proxy, except a text search whose SearXNG search failed, which goes to `search.parallel.ai`, and only when `SEARCH_FALLBACK_ENABLED` is on (off by default)
|
| 175 |
- **AI Models**: Models run locally or through trusted providers
|
| 176 |
- **Data Exfiltration**: Prevented by local-first architecture
|
| 177 |
|
|
|
|
| 44 |
|
| 45 |
- **Local-First Storage**: All data stored in IndexedDB, no cloud sync
|
| 46 |
- **No Tracking**: No telemetry, analytics, or user tracking
|
| 47 |
+
- **SearXNG Integration**: All web searches routed through privacy-focused metasearch, which holds for every search as long as `SEARCH_FALLBACK_ENABLED` is off (the default); with it on, a text search that found SearXNG failed goes to the fallback providers instead, and an image search never does
|
| 48 |
+
- **The Fallback Sends The Query To A Third Party When It Is On**: With `SEARCH_FALLBACK_ENABLED` on (off by default), a text query whose SearXNG search failed is sent to the providers `SEARCH_FALLBACK_PROVIDERS` lists (all of them when it is empty): `search.parallel.ai` (Parallel) and You.com's MCP endpoint (`https://api.you.com/mcp`). They are tried one after the other, in a random order, until one returns results, so a single search can reach more than one provider. Each provider that is tried sees the query text and the server's IP address. The user's IP address is never sent, because the request leaves from the server rather than from the browser. The query is still never written to the server log, and neither is any provider's error text; what `/status` publishes for this feature is the two counts `searchesServedByFallback` and `searchesFailedOnFallback` and the per-provider counters under `searches.fallbackProviders`, none of which carries a query. The provider names there are configured infrastructure, not anything a user typed
|
| 49 |
- **Browser-Only Mode Still Downloads Models From HuggingFace**: The instance sends no telemetry and no analytics. But browser-only mode is not fully offline: both wllama and the local text-to-speech engine fetch their models from `huggingface.co` (served from `us.aws.cdn.hf.co`), so that host sees the user's IP. The text-to-speech engine's full host list is in the Reading Answers Aloud bullet below.
|
| 50 |
- **Page Reading Is On By Default, And Reversible**: `enablePageContentFetch` ships on and the user can turn it off under AI Settings; nothing is read until AI responses are enabled, and the server (never the browser) requests the top result pages, so those sites see the instance and no user cookies or IP
|
| 51 |
- **Search And Page Reading Leave No Query In The Log**: The search query is never written to the server log, and neither is the URL of a page read for grounding; how often searches come back empty, fail on unresponsive engines, are fully discarded, are served by the fallback or lose the fallback too, and how page reads ended, are counted instead and reported on `/status` (`searchesWithoutResults`, `searchesWithUnresponsiveEngines`, `searchesWithAllResultsDiscarded`, `searchesServedByFallback`, `searchesFailedOnFallback`, `pageReads`). `searches.unresponsiveEngines` names the engines behind those failures, which is configured infrastructure rather than anything a user typed, the same basis on which `inference.byModel` names model ids. What each failed with is published as one of `blocked`, `timeout` or `other`, never SearXNG's own wording: `/status` needs no token, and an upstream engine is free to build its error text out of the request URL, and so out of the query. The raw string stays in the server log and in the thrown error the search endpoint catches; the 502 it answers with carries a fixed message, so the string never leaves the server. The page reader's per-host circuit breaker (`server/pageReadHostBreaker.ts`) holds the hosts it has boxed in memory, since it needs them to know which host to skip; a page-read host comes from the search results, downstream of the query, so unlike an engine name it is never published: `/status` reports how many reads were skipped and how often a host was boxed, as counts only. Anything in front of the instance keeps its own access log, where `?q=` appears in full
|
|
|
|
| 96 |
| `server/pageReadsSinceLastRestart.ts` | In-memory aggregate counters for pages read for grounding (outcomes, durations, passage ratios), reported on `/status`; records no query, URL, host, or per-read timestamp |
|
| 97 |
| `server/pageReadHostBreaker.ts` | Per-host circuit breaker for page reads: a host that refused the last three reads is skipped for five minutes, then probed with one read; holds host names in memory only, reset on restart, and reports counts alone |
|
| 98 |
| `server/searchEndpointServerHook.ts` | Proxies text/image search to SearXNG after token verification (via `handleTokenVerification`), and serves fallback text results when SearXNG has failed and `SEARCH_FALLBACK_ENABLED` is on |
|
| 99 |
+
| `server/fallbackSearchService.ts` | Second text-search source, consulted only after SearXNG has failed: reads `SEARCH_FALLBACK_ENABLED`, `SEARCH_FALLBACK_PROVIDERS` and the per-provider keys at call time, and refuses to start on an unknown provider name or a removed variable; runs the JSON-RPC exchange against `search.parallel.ai` and You.com's MCP endpoint in a random order per search, one after the other until one returns results, under one shared 15-second budget; pauses a provider that answered 429 until its `Retry-After`; and maps the reply into the tuples the SearXNG path produces; records no query |
|
| 100 |
| `server/verifyTokenAndRateLimit.ts` | Verifies the Argon2 token hash and enforces rate limiting (10 requests per 10 seconds, shared by the search, page-content and inference endpoints; a separate 60-per-10-seconds budget for `/thumbnail`) |
|
| 101 |
| `server/handleTokenVerification.ts` | Middleware bridge that calls `verifyTokenAndRateLimit` and writes 400/401/429 error responses for the search, page-content, thumbnail and inference endpoints |
|
| 102 |
| `server/configEndpointServerHook.ts` | Serves the non-secret runtime config at `/api/config`, including whether access keys are enabled |
|
|
|
|
| 171 |
## Threat Model
|
| 172 |
|
| 173 |
- **Local Environment**: Assumes trusted local execution
|
| 174 |
+
- **Network Requests**: All external requests go through SearXNG proxy, except a text search whose SearXNG search failed, which goes to the fallback providers `SEARCH_FALLBACK_PROVIDERS` lists (`search.parallel.ai` and You.com's MCP endpoint, one after the other until one returns results, so possibly more than one per search), and only when `SEARCH_FALLBACK_ENABLED` is on (off by default)
|
| 175 |
- **AI Models**: Models run locally or through trusted providers
|
| 176 |
- **Data Exfiltration**: Prevented by local-first architecture
|
| 177 |
|
package-lock.json
CHANGED
|
@@ -95,13 +95,13 @@
|
|
| 95 |
"license": "MIT"
|
| 96 |
},
|
| 97 |
"node_modules/@ai-sdk/gateway": {
|
| 98 |
-
"version": "4.0.
|
| 99 |
-
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.
|
| 100 |
-
"integrity": "sha512-
|
| 101 |
"license": "Apache-2.0",
|
| 102 |
"dependencies": {
|
| 103 |
-
"@ai-sdk/provider": "4.0.
|
| 104 |
-
"@ai-sdk/provider-utils": "5.0.
|
| 105 |
"@vercel/oidc": "3.2.0"
|
| 106 |
},
|
| 107 |
"engines": {
|
|
@@ -111,14 +111,54 @@
|
|
| 111 |
"zod": "^3.25.76 || ^4.1.8"
|
| 112 |
}
|
| 113 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
"node_modules/@ai-sdk/openai-compatible": {
|
| 115 |
-
"version": "3.0.
|
| 116 |
-
"resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.
|
| 117 |
-
"integrity": "sha512-
|
| 118 |
"license": "Apache-2.0",
|
| 119 |
"dependencies": {
|
| 120 |
-
"@ai-sdk/provider": "4.0.
|
| 121 |
-
"@ai-sdk/provider-utils": "5.0.
|
| 122 |
},
|
| 123 |
"engines": {
|
| 124 |
"node": ">=22"
|
|
@@ -127,10 +167,10 @@
|
|
| 127 |
"zod": "^3.25.76 || ^4.1.8"
|
| 128 |
}
|
| 129 |
},
|
| 130 |
-
"node_modules/@ai-sdk/provider": {
|
| 131 |
-
"version": "4.0.
|
| 132 |
-
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.
|
| 133 |
-
"integrity": "sha512-
|
| 134 |
"license": "Apache-2.0",
|
| 135 |
"dependencies": {
|
| 136 |
"json-schema": "^0.4.0"
|
|
@@ -139,13 +179,13 @@
|
|
| 139 |
"node": ">=22"
|
| 140 |
}
|
| 141 |
},
|
| 142 |
-
"node_modules/@ai-sdk/provider-utils": {
|
| 143 |
-
"version": "5.0.
|
| 144 |
-
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.
|
| 145 |
-
"integrity": "sha512-
|
| 146 |
"license": "Apache-2.0",
|
| 147 |
"dependencies": {
|
| 148 |
-
"@ai-sdk/provider": "4.0.
|
| 149 |
"@standard-schema/spec": "^1.1.0",
|
| 150 |
"@workflow/serde": "4.1.0",
|
| 151 |
"eventsource-parser": "^3.0.8",
|
|
@@ -158,7 +198,7 @@
|
|
| 158 |
"zod": "^3.25.76 || ^4.1.8"
|
| 159 |
}
|
| 160 |
},
|
| 161 |
-
"node_modules/@ai-sdk/
|
| 162 |
"version": "7.30.0",
|
| 163 |
"resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz",
|
| 164 |
"integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==",
|
|
@@ -785,13 +825,13 @@
|
|
| 785 |
"license": "MIT"
|
| 786 |
},
|
| 787 |
"node_modules/@mantine/carousel": {
|
| 788 |
-
"version": "9.7.
|
| 789 |
-
"resolved": "https://registry.npmjs.org/@mantine/carousel/-/carousel-9.7.
|
| 790 |
-
"integrity": "sha512-
|
| 791 |
"license": "MIT",
|
| 792 |
"peerDependencies": {
|
| 793 |
-
"@mantine/core": "9.7.
|
| 794 |
-
"@mantine/hooks": "9.7.
|
| 795 |
"embla-carousel": ">=8.0.0",
|
| 796 |
"embla-carousel-react": ">=8.0.0",
|
| 797 |
"react": "^19.2.0",
|
|
@@ -799,24 +839,24 @@
|
|
| 799 |
}
|
| 800 |
},
|
| 801 |
"node_modules/@mantine/code-highlight": {
|
| 802 |
-
"version": "9.7.
|
| 803 |
-
"resolved": "https://registry.npmjs.org/@mantine/code-highlight/-/code-highlight-9.7.
|
| 804 |
-
"integrity": "sha512-
|
| 805 |
"license": "MIT",
|
| 806 |
"dependencies": {
|
| 807 |
"clsx": "^2.1.1"
|
| 808 |
},
|
| 809 |
"peerDependencies": {
|
| 810 |
-
"@mantine/core": "9.7.
|
| 811 |
-
"@mantine/hooks": "9.7.
|
| 812 |
"react": "^19.2.0",
|
| 813 |
"react-dom": "^19.2.0"
|
| 814 |
}
|
| 815 |
},
|
| 816 |
"node_modules/@mantine/core": {
|
| 817 |
-
"version": "9.7.
|
| 818 |
-
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-9.7.
|
| 819 |
-
"integrity": "sha512-
|
| 820 |
"license": "MIT",
|
| 821 |
"dependencies": {
|
| 822 |
"@floating-ui/react": "^0.27.20",
|
|
@@ -826,15 +866,15 @@
|
|
| 826 |
"type-fest": "^5.10.0"
|
| 827 |
},
|
| 828 |
"peerDependencies": {
|
| 829 |
-
"@mantine/hooks": "9.7.
|
| 830 |
"react": "^19.2.0",
|
| 831 |
"react-dom": "^19.2.0"
|
| 832 |
}
|
| 833 |
},
|
| 834 |
"node_modules/@mantine/form": {
|
| 835 |
-
"version": "9.7.
|
| 836 |
-
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-9.7.
|
| 837 |
-
"integrity": "sha512-
|
| 838 |
"license": "MIT",
|
| 839 |
"dependencies": {
|
| 840 |
"@standard-schema/spec": "^1.1.0",
|
|
@@ -846,34 +886,34 @@
|
|
| 846 |
}
|
| 847 |
},
|
| 848 |
"node_modules/@mantine/hooks": {
|
| 849 |
-
"version": "9.7.
|
| 850 |
-
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-9.7.
|
| 851 |
-
"integrity": "sha512-
|
| 852 |
"license": "MIT",
|
| 853 |
"peerDependencies": {
|
| 854 |
"react": "^19.2.0"
|
| 855 |
}
|
| 856 |
},
|
| 857 |
"node_modules/@mantine/notifications": {
|
| 858 |
-
"version": "9.7.
|
| 859 |
-
"resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-9.7.
|
| 860 |
-
"integrity": "sha512-
|
| 861 |
"license": "MIT",
|
| 862 |
"dependencies": {
|
| 863 |
-
"@mantine/store": "9.7.
|
| 864 |
"react-transition-group": "4.4.5"
|
| 865 |
},
|
| 866 |
"peerDependencies": {
|
| 867 |
-
"@mantine/core": "9.7.
|
| 868 |
-
"@mantine/hooks": "9.7.
|
| 869 |
"react": "^19.2.0",
|
| 870 |
"react-dom": "^19.2.0"
|
| 871 |
}
|
| 872 |
},
|
| 873 |
"node_modules/@mantine/store": {
|
| 874 |
-
"version": "9.7.
|
| 875 |
-
"resolved": "https://registry.npmjs.org/@mantine/store/-/store-9.7.
|
| 876 |
-
"integrity": "sha512-
|
| 877 |
"license": "MIT",
|
| 878 |
"peerDependencies": {
|
| 879 |
"react": "^19.2.0"
|
|
@@ -2988,14 +3028,14 @@
|
|
| 2988 |
}
|
| 2989 |
},
|
| 2990 |
"node_modules/ai": {
|
| 2991 |
-
"version": "7.0.
|
| 2992 |
-
"resolved": "https://registry.npmjs.org/ai/-/ai-7.0.
|
| 2993 |
-
"integrity": "sha512-
|
| 2994 |
"license": "Apache-2.0",
|
| 2995 |
"dependencies": {
|
| 2996 |
-
"@ai-sdk/gateway": "4.0.
|
| 2997 |
-
"@ai-sdk/provider": "4.0.
|
| 2998 |
-
"@ai-sdk/provider-utils": "5.0.
|
| 2999 |
},
|
| 3000 |
"engines": {
|
| 3001 |
"node": ">=22"
|
|
@@ -3004,6 +3044,46 @@
|
|
| 3004 |
"zod": "^3.25.76 || ^4.1.8"
|
| 3005 |
}
|
| 3006 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3007 |
"node_modules/ansi-regex": {
|
| 3008 |
"version": "5.0.1",
|
| 3009 |
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
|
@@ -3617,9 +3697,9 @@
|
|
| 3617 |
}
|
| 3618 |
},
|
| 3619 |
"node_modules/dotenv": {
|
| 3620 |
-
"version": "18.0.
|
| 3621 |
-
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.
|
| 3622 |
-
"integrity": "sha512-
|
| 3623 |
"license": "BSD-2-Clause",
|
| 3624 |
"bin": {
|
| 3625 |
"dotenv": "dist/index.cjs"
|
|
@@ -4623,9 +4703,9 @@
|
|
| 4623 |
}
|
| 4624 |
},
|
| 4625 |
"node_modules/knip": {
|
| 4626 |
-
"version": "6.
|
| 4627 |
-
"resolved": "https://registry.npmjs.org/knip/-/knip-6.
|
| 4628 |
-
"integrity": "sha512-
|
| 4629 |
"dev": true,
|
| 4630 |
"funding": [
|
| 4631 |
{
|
|
@@ -7827,9 +7907,9 @@
|
|
| 7827 |
}
|
| 7828 |
},
|
| 7829 |
"node_modules/vite": {
|
| 7830 |
-
"version": "8.3.
|
| 7831 |
-
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.
|
| 7832 |
-
"integrity": "sha512-
|
| 7833 |
"license": "MIT",
|
| 7834 |
"dependencies": {
|
| 7835 |
"lightningcss": "^1.33.0",
|
|
|
|
| 95 |
"license": "MIT"
|
| 96 |
},
|
| 97 |
"node_modules/@ai-sdk/gateway": {
|
| 98 |
+
"version": "4.0.106",
|
| 99 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.106.tgz",
|
| 100 |
+
"integrity": "sha512-NprSdFzyVnbEp2SDWtIgsCFIUHFCehOD9S6FQkF/dLrCmnke8w4SOK3KcmUUiEbwtVzlUF8prkaELp+ohcQaQw==",
|
| 101 |
"license": "Apache-2.0",
|
| 102 |
"dependencies": {
|
| 103 |
+
"@ai-sdk/provider": "4.0.24",
|
| 104 |
+
"@ai-sdk/provider-utils": "5.0.56",
|
| 105 |
"@vercel/oidc": "3.2.0"
|
| 106 |
},
|
| 107 |
"engines": {
|
|
|
|
| 111 |
"zod": "^3.25.76 || ^4.1.8"
|
| 112 |
}
|
| 113 |
},
|
| 114 |
+
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": {
|
| 115 |
+
"version": "4.0.24",
|
| 116 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.24.tgz",
|
| 117 |
+
"integrity": "sha512-iztFqJTdxNN0ZzqZAAnVPMhRMaL1bfQFa3GIPAffWQ0+dP2FXIkyMD8kHeEI7EpB/C6TR45cqOkrS7YZZ1sATA==",
|
| 118 |
+
"license": "Apache-2.0",
|
| 119 |
+
"dependencies": {
|
| 120 |
+
"json-schema": "^0.4.0"
|
| 121 |
+
},
|
| 122 |
+
"engines": {
|
| 123 |
+
"node": ">=22"
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": {
|
| 127 |
+
"version": "5.0.56",
|
| 128 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.56.tgz",
|
| 129 |
+
"integrity": "sha512-F7/U1YpVXEfSxRHbxScm0PVdr9lN6GaUWHnt8ws2UjuGeuxWHqCkV25dGpHAkpDHkuIXoC+aak/OqSylKSJmHw==",
|
| 130 |
+
"license": "Apache-2.0",
|
| 131 |
+
"dependencies": {
|
| 132 |
+
"@ai-sdk/provider": "4.0.24",
|
| 133 |
+
"@standard-schema/spec": "^1.1.0",
|
| 134 |
+
"@workflow/serde": "4.1.0",
|
| 135 |
+
"eventsource-parser": "^3.0.8",
|
| 136 |
+
"undici": "^7.29.0"
|
| 137 |
+
},
|
| 138 |
+
"engines": {
|
| 139 |
+
"node": ">=22"
|
| 140 |
+
},
|
| 141 |
+
"peerDependencies": {
|
| 142 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 143 |
+
}
|
| 144 |
+
},
|
| 145 |
+
"node_modules/@ai-sdk/gateway/node_modules/undici": {
|
| 146 |
+
"version": "7.30.0",
|
| 147 |
+
"resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz",
|
| 148 |
+
"integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==",
|
| 149 |
+
"license": "MIT",
|
| 150 |
+
"engines": {
|
| 151 |
+
"node": ">=20.18.1"
|
| 152 |
+
}
|
| 153 |
+
},
|
| 154 |
"node_modules/@ai-sdk/openai-compatible": {
|
| 155 |
+
"version": "3.0.65",
|
| 156 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-3.0.65.tgz",
|
| 157 |
+
"integrity": "sha512-TBfQk1hf9hOioOwdeDvuXdbmFDp+uLvYf5lfu/gCKVFy6rzjpEqTekEln2ebYonrnq2lIglsuj9S9IovaGhDZA==",
|
| 158 |
"license": "Apache-2.0",
|
| 159 |
"dependencies": {
|
| 160 |
+
"@ai-sdk/provider": "4.0.24",
|
| 161 |
+
"@ai-sdk/provider-utils": "5.0.56"
|
| 162 |
},
|
| 163 |
"engines": {
|
| 164 |
"node": ">=22"
|
|
|
|
| 167 |
"zod": "^3.25.76 || ^4.1.8"
|
| 168 |
}
|
| 169 |
},
|
| 170 |
+
"node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider": {
|
| 171 |
+
"version": "4.0.24",
|
| 172 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.24.tgz",
|
| 173 |
+
"integrity": "sha512-iztFqJTdxNN0ZzqZAAnVPMhRMaL1bfQFa3GIPAffWQ0+dP2FXIkyMD8kHeEI7EpB/C6TR45cqOkrS7YZZ1sATA==",
|
| 174 |
"license": "Apache-2.0",
|
| 175 |
"dependencies": {
|
| 176 |
"json-schema": "^0.4.0"
|
|
|
|
| 179 |
"node": ">=22"
|
| 180 |
}
|
| 181 |
},
|
| 182 |
+
"node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": {
|
| 183 |
+
"version": "5.0.56",
|
| 184 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.56.tgz",
|
| 185 |
+
"integrity": "sha512-F7/U1YpVXEfSxRHbxScm0PVdr9lN6GaUWHnt8ws2UjuGeuxWHqCkV25dGpHAkpDHkuIXoC+aak/OqSylKSJmHw==",
|
| 186 |
"license": "Apache-2.0",
|
| 187 |
"dependencies": {
|
| 188 |
+
"@ai-sdk/provider": "4.0.24",
|
| 189 |
"@standard-schema/spec": "^1.1.0",
|
| 190 |
"@workflow/serde": "4.1.0",
|
| 191 |
"eventsource-parser": "^3.0.8",
|
|
|
|
| 198 |
"zod": "^3.25.76 || ^4.1.8"
|
| 199 |
}
|
| 200 |
},
|
| 201 |
+
"node_modules/@ai-sdk/openai-compatible/node_modules/undici": {
|
| 202 |
"version": "7.30.0",
|
| 203 |
"resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz",
|
| 204 |
"integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==",
|
|
|
|
| 825 |
"license": "MIT"
|
| 826 |
},
|
| 827 |
"node_modules/@mantine/carousel": {
|
| 828 |
+
"version": "9.7.1",
|
| 829 |
+
"resolved": "https://registry.npmjs.org/@mantine/carousel/-/carousel-9.7.1.tgz",
|
| 830 |
+
"integrity": "sha512-lEa4BjqsrCUp4xJ4pJyaOCvcXGyqRfSYUumUvY/Q1KHrkqTdReiZCdURcsX+OHjqrju1KruddWH/0qNzAYJ9Ew==",
|
| 831 |
"license": "MIT",
|
| 832 |
"peerDependencies": {
|
| 833 |
+
"@mantine/core": "9.7.1",
|
| 834 |
+
"@mantine/hooks": "9.7.1",
|
| 835 |
"embla-carousel": ">=8.0.0",
|
| 836 |
"embla-carousel-react": ">=8.0.0",
|
| 837 |
"react": "^19.2.0",
|
|
|
|
| 839 |
}
|
| 840 |
},
|
| 841 |
"node_modules/@mantine/code-highlight": {
|
| 842 |
+
"version": "9.7.1",
|
| 843 |
+
"resolved": "https://registry.npmjs.org/@mantine/code-highlight/-/code-highlight-9.7.1.tgz",
|
| 844 |
+
"integrity": "sha512-iKr+gUauwvW5yrdB6rSNAeKCZqwuzXpb5pwopgymCB9JJs/UI7mfR5vyRkR5NkDgZOUHJU4O1QqzzG3bkroWPA==",
|
| 845 |
"license": "MIT",
|
| 846 |
"dependencies": {
|
| 847 |
"clsx": "^2.1.1"
|
| 848 |
},
|
| 849 |
"peerDependencies": {
|
| 850 |
+
"@mantine/core": "9.7.1",
|
| 851 |
+
"@mantine/hooks": "9.7.1",
|
| 852 |
"react": "^19.2.0",
|
| 853 |
"react-dom": "^19.2.0"
|
| 854 |
}
|
| 855 |
},
|
| 856 |
"node_modules/@mantine/core": {
|
| 857 |
+
"version": "9.7.1",
|
| 858 |
+
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-9.7.1.tgz",
|
| 859 |
+
"integrity": "sha512-SU1xklOfdEDzpPhw35ti9rF4QRE9HUongCe37C+xCO2Ea8MLJkAzreCrMxb8qcCij+gnF7SQbtTCBa1r+pKxYA==",
|
| 860 |
"license": "MIT",
|
| 861 |
"dependencies": {
|
| 862 |
"@floating-ui/react": "^0.27.20",
|
|
|
|
| 866 |
"type-fest": "^5.10.0"
|
| 867 |
},
|
| 868 |
"peerDependencies": {
|
| 869 |
+
"@mantine/hooks": "9.7.1",
|
| 870 |
"react": "^19.2.0",
|
| 871 |
"react-dom": "^19.2.0"
|
| 872 |
}
|
| 873 |
},
|
| 874 |
"node_modules/@mantine/form": {
|
| 875 |
+
"version": "9.7.1",
|
| 876 |
+
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-9.7.1.tgz",
|
| 877 |
+
"integrity": "sha512-fVjWYdiMBw21z/bqUkFghyV1MsANHoqCZaXji0k4KJaRCHh7cD3qhBuLIVhc2hUCKXQ3cHiJTHLz3ayoE/mRZg==",
|
| 878 |
"license": "MIT",
|
| 879 |
"dependencies": {
|
| 880 |
"@standard-schema/spec": "^1.1.0",
|
|
|
|
| 886 |
}
|
| 887 |
},
|
| 888 |
"node_modules/@mantine/hooks": {
|
| 889 |
+
"version": "9.7.1",
|
| 890 |
+
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-9.7.1.tgz",
|
| 891 |
+
"integrity": "sha512-C1xD4A3bfB87nUsLHr42WqOabnK4G+fCyEf8FN14EEiZAcKjdcWJbK63qxhIXFKuHCc3acLbpm2ts9UqmLASTw==",
|
| 892 |
"license": "MIT",
|
| 893 |
"peerDependencies": {
|
| 894 |
"react": "^19.2.0"
|
| 895 |
}
|
| 896 |
},
|
| 897 |
"node_modules/@mantine/notifications": {
|
| 898 |
+
"version": "9.7.1",
|
| 899 |
+
"resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-9.7.1.tgz",
|
| 900 |
+
"integrity": "sha512-gmN+rTrjwYFJqaIqDQtQfoTJ/OR0wSxqxIihOHD5/S/sYUycVi9wDqK4tub5y8x0HeQH4kNkehPMN4l1Uk0A6A==",
|
| 901 |
"license": "MIT",
|
| 902 |
"dependencies": {
|
| 903 |
+
"@mantine/store": "9.7.1",
|
| 904 |
"react-transition-group": "4.4.5"
|
| 905 |
},
|
| 906 |
"peerDependencies": {
|
| 907 |
+
"@mantine/core": "9.7.1",
|
| 908 |
+
"@mantine/hooks": "9.7.1",
|
| 909 |
"react": "^19.2.0",
|
| 910 |
"react-dom": "^19.2.0"
|
| 911 |
}
|
| 912 |
},
|
| 913 |
"node_modules/@mantine/store": {
|
| 914 |
+
"version": "9.7.1",
|
| 915 |
+
"resolved": "https://registry.npmjs.org/@mantine/store/-/store-9.7.1.tgz",
|
| 916 |
+
"integrity": "sha512-CYCQHWt7zFrUe2kVQmFAWnQaZbiDUoN2VeMJBI/vf1XybGj+itTEB6cnvoX4C8yOrln3sXe4BIoIf46pUhnEhA==",
|
| 917 |
"license": "MIT",
|
| 918 |
"peerDependencies": {
|
| 919 |
"react": "^19.2.0"
|
|
|
|
| 3028 |
}
|
| 3029 |
},
|
| 3030 |
"node_modules/ai": {
|
| 3031 |
+
"version": "7.0.130",
|
| 3032 |
+
"resolved": "https://registry.npmjs.org/ai/-/ai-7.0.130.tgz",
|
| 3033 |
+
"integrity": "sha512-QkBkCogl46jXUg0SrSsdqQlOI2ZVMCjfnbBQS8t3gE21U/SO17oNMVFyjmcC9z20wOoYUA+2O2vsso0maoS7dw==",
|
| 3034 |
"license": "Apache-2.0",
|
| 3035 |
"dependencies": {
|
| 3036 |
+
"@ai-sdk/gateway": "4.0.106",
|
| 3037 |
+
"@ai-sdk/provider": "4.0.24",
|
| 3038 |
+
"@ai-sdk/provider-utils": "5.0.56"
|
| 3039 |
},
|
| 3040 |
"engines": {
|
| 3041 |
"node": ">=22"
|
|
|
|
| 3044 |
"zod": "^3.25.76 || ^4.1.8"
|
| 3045 |
}
|
| 3046 |
},
|
| 3047 |
+
"node_modules/ai/node_modules/@ai-sdk/provider": {
|
| 3048 |
+
"version": "4.0.24",
|
| 3049 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.24.tgz",
|
| 3050 |
+
"integrity": "sha512-iztFqJTdxNN0ZzqZAAnVPMhRMaL1bfQFa3GIPAffWQ0+dP2FXIkyMD8kHeEI7EpB/C6TR45cqOkrS7YZZ1sATA==",
|
| 3051 |
+
"license": "Apache-2.0",
|
| 3052 |
+
"dependencies": {
|
| 3053 |
+
"json-schema": "^0.4.0"
|
| 3054 |
+
},
|
| 3055 |
+
"engines": {
|
| 3056 |
+
"node": ">=22"
|
| 3057 |
+
}
|
| 3058 |
+
},
|
| 3059 |
+
"node_modules/ai/node_modules/@ai-sdk/provider-utils": {
|
| 3060 |
+
"version": "5.0.56",
|
| 3061 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.56.tgz",
|
| 3062 |
+
"integrity": "sha512-F7/U1YpVXEfSxRHbxScm0PVdr9lN6GaUWHnt8ws2UjuGeuxWHqCkV25dGpHAkpDHkuIXoC+aak/OqSylKSJmHw==",
|
| 3063 |
+
"license": "Apache-2.0",
|
| 3064 |
+
"dependencies": {
|
| 3065 |
+
"@ai-sdk/provider": "4.0.24",
|
| 3066 |
+
"@standard-schema/spec": "^1.1.0",
|
| 3067 |
+
"@workflow/serde": "4.1.0",
|
| 3068 |
+
"eventsource-parser": "^3.0.8",
|
| 3069 |
+
"undici": "^7.29.0"
|
| 3070 |
+
},
|
| 3071 |
+
"engines": {
|
| 3072 |
+
"node": ">=22"
|
| 3073 |
+
},
|
| 3074 |
+
"peerDependencies": {
|
| 3075 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 3076 |
+
}
|
| 3077 |
+
},
|
| 3078 |
+
"node_modules/ai/node_modules/undici": {
|
| 3079 |
+
"version": "7.30.0",
|
| 3080 |
+
"resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz",
|
| 3081 |
+
"integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==",
|
| 3082 |
+
"license": "MIT",
|
| 3083 |
+
"engines": {
|
| 3084 |
+
"node": ">=20.18.1"
|
| 3085 |
+
}
|
| 3086 |
+
},
|
| 3087 |
"node_modules/ansi-regex": {
|
| 3088 |
"version": "5.0.1",
|
| 3089 |
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
|
|
|
| 3697 |
}
|
| 3698 |
},
|
| 3699 |
"node_modules/dotenv": {
|
| 3700 |
+
"version": "18.0.6",
|
| 3701 |
+
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.6.tgz",
|
| 3702 |
+
"integrity": "sha512-ZMVmohV6CvAJtj++pZNyggluUt6hbRdQAxc2+Z635RL6/AG/ruXv5lHnybUFoxMPL/yrTL/tsF4paZu2l7RA6Q==",
|
| 3703 |
"license": "BSD-2-Clause",
|
| 3704 |
"bin": {
|
| 3705 |
"dotenv": "dist/index.cjs"
|
|
|
|
| 4703 |
}
|
| 4704 |
},
|
| 4705 |
"node_modules/knip": {
|
| 4706 |
+
"version": "6.40.0",
|
| 4707 |
+
"resolved": "https://registry.npmjs.org/knip/-/knip-6.40.0.tgz",
|
| 4708 |
+
"integrity": "sha512-5Lp/ANJu3AkW5f+J+2QG14z7xKZGi/ybb5MU1EWjeGdt6MpuIaLpLFiyBi0ZKryTiuTHiJqo/WKJRaWYimvavw==",
|
| 4709 |
"dev": true,
|
| 4710 |
"funding": [
|
| 4711 |
{
|
|
|
|
| 7907 |
}
|
| 7908 |
},
|
| 7909 |
"node_modules/vite": {
|
| 7910 |
+
"version": "8.3.3",
|
| 7911 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.3.tgz",
|
| 7912 |
+
"integrity": "sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==",
|
| 7913 |
"license": "MIT",
|
| 7914 |
"dependencies": {
|
| 7915 |
"lightningcss": "^1.33.0",
|