Compare commits

...

6 Commits

Author SHA1 Message Date
diegosouzapw 3fb72b973a fix(build): revert middleware.ts rename to proxy.ts because of Next.js Edge constraints
Build Electron Desktop App / Validate version (push) Failing after 29s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
2026-03-29 12:53:50 -03:00
diegosouzapw 7b1c945324 fix(build): remove legacy proxy.ts causing Next.js build collision 2026-03-29 12:52:04 -03:00
diegosouzapw 1a7c363413 fix(auth): redirect and unconditional 401 on disabled requireLogin + fix test cases 2026-03-29 12:49:34 -03:00
Gorchakov-Pressure 107a572174 fix: handle allRateLimited credentials & forward extra body keys in embeddings/images routes (#757)
* fix: handle allRateLimited credentials in embeddings and images routes

When getProviderCredentials() returns an allRateLimited object (truthy,
but without apiKey/accessToken), the embeddings and images routes
incorrectly passed it to handlers as valid credentials. The handlers
then sent upstream requests without Authorization headers, causing
401 errors from providers (e.g. NVIDIA NIM).

This only manifested under concurrent requests: a chat/completions
call could trigger rate limiting on a provider account, and a
simultaneous embeddings request would receive the allRateLimited
sentinel — but treat it as valid credentials.

The chat pipeline already handled this case correctly. This commit
adds the same allRateLimited guard to all affected routes:
- POST /v1/embeddings
- POST /v1/providers/{provider}/embeddings
- POST /v1/images/generations
- POST /v1/providers/{provider}/images/generations

Also adds a defense-in-depth guard in the embeddings handler itself:
if no auth token is available for a non-local provider, return 401
immediately instead of sending an unauthenticated request upstream.

Made-with: Cursor

* fix(embeddings): forward extra body keys to upstream providers

The embeddings handler only forwarded model, input, dimensions, and
encoding_format to upstream providers, silently dropping any additional
fields. This broke asymmetric embedding APIs (e.g. NVIDIA NIM
nv-embedqa-e5-v5) that require input_type, and other providers
expecting user or truncate parameters.

Add a KNOWN_FIELDS exclusion set and forward all unrecognized body
keys to the upstream request, matching the passthrough pattern used
by the chat pipeline's DefaultExecutor.transformRequest().

Made-with: Cursor
2026-03-29 12:48:52 -03:00
tombii 89bd31c670 fix(sse): remove race condition in cache metrics tracking (#758)
- Remove in-memory metrics tracking (currentMetrics, trackCacheMetrics, updateCacheMetrics)
- Cache metrics now computed on-the-fly from usage_history table (single source of truth)
- Fixes CRITICAL issue from code review: concurrent requests overwriting metrics
- Fixes WARNING: duplicate metric tracking logic in streaming/non-streaming paths

Ref: PR #752 (merged before this fix was included)
2026-03-29 12:48:33 -03:00
diegosouzapw 2e29e45a3a chore(release): v3.2.8 — Docker auto-update UI and cache analytics fixes 2026-03-29 11:31:36 -03:00
16 changed files with 336 additions and 70 deletions
+19
View File
@@ -2,6 +2,25 @@
## [Unreleased]
---
## [3.2.8] - 2026-03-29
### ✨ Enhancements & Refactoring
- **Docker Auto-Update UI** — Integrated a detached background update process for Docker Compose deployments. The Dashboard UI now seamlessly tracks update lifecycle events combining JSON REST responses with SSE streaming progress overlays for robust cross-environment reliability.
- **Cache Analytics** — Repaired zero-metrics visualization mapping by migrating Semantic Cache telemetry logs directly into the centralized tracking SQLite module.
### 🐛 Bug Fixes
- **Authentication Logic** — Fixed a bug where saving dashboard settings or adding models failed with a 401 Unauthorized error when `requireLogin` was disabled. API endpoints now correctly evaluate the global authentication toggle. Resolved global redirection by reactivating `src/middleware.ts`.
- **CLI Tool Detection (Windows)** — Prevented fatal initialization exceptions during CLI environment detection by catching `cross-spawn` ENOENT errors correctly. Adds explicit detection paths for `\AppData\Local\droid\droid.exe`.
- **Codex Native Passthrough** — Normalized model translation parameters preventing context poisoning in proxy pass-through mode, enforcing generic `store: false` constraints explicitly for all Codex-originated requests.
- **SSE Token Reporting** — Normalized provider tool-call chunk `finish_reason` detection, fixing 0% Usage analytics for stream-only responses missing strict `<DONE>` indicators.
- **DeepSeek <think> Tags** — Implemented an explicit `<think>` extraction mapping inside `responsesHandler.ts`, ensuring DeepSeek reasoning streams map equivalently to native Anthropic `<thinking>` structures.
---
## [3.2.7] - 2026-03-29
### Fixed
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.2.7
version: 3.2.8
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,
+18 -56
View File
@@ -43,13 +43,8 @@ import {
} from "@/lib/localDb";
import { getExecutor } from "../executors/index.ts";
import { getCacheControlSettings } from "@/lib/cacheControlSettings";
import {
shouldPreserveCacheControl,
trackCacheMetrics,
recordCacheHit,
type CacheControlMetrics,
} from "../utils/cacheControlPolicy.ts";
import { getCacheMetrics, updateCacheMetrics } from "@/lib/db/settings.ts";
import { shouldPreserveCacheControl, recordCacheHit } from "../utils/cacheControlPolicy.ts";
import { getCacheMetrics } from "@/lib/db/settings.ts";
import {
parseCodexQuotaHeaders,
@@ -701,27 +696,6 @@ export async function handleChatCore({
settings: { alwaysPreserveClientCache: cacheControlMode },
});
// Track cache metrics for this request
let currentMetrics = await getCacheMetrics().catch(() => ({
totalRequests: 0,
requestsWithCacheControl: 0,
totalInputTokens: 0,
totalCachedTokens: 0,
totalCacheCreationTokens: 0,
tokensSaved: 0,
estimatedCostSaved: 0,
byProvider: {},
byStrategy: {},
lastUpdated: new Date().toISOString(),
}));
currentMetrics = trackCacheMetrics({
preserved: preserveCacheControl,
provider,
strategy: comboStrategy,
metrics: currentMetrics,
});
if (preserveCacheControl) {
log?.debug?.(
"CACHE",
@@ -1473,18 +1447,6 @@ export async function handleChatCore({
(usage as any).prompt_tokens_details?.cache_creation_tokens
);
if (cachedTokens > 0 || cacheCreationTokens > 0) {
currentMetrics = updateCacheTokenMetrics({
metrics: currentMetrics,
provider,
strategy: comboStrategy,
inputTokens,
cachedTokens,
cacheCreationTokens,
costSaved: 0, // Will be calculated based on pricing
});
}
saveRequestUsage({
provider: provider || "unknown",
model: model || "unknown",
@@ -1649,17 +1611,22 @@ export async function handleChatCore({
(streamUsage as any).prompt_tokens_details?.cache_creation_tokens
);
if (cachedTokens > 0 || cacheCreationTokens > 0) {
currentMetrics = updateCacheTokenMetrics({
metrics: currentMetrics,
provider,
strategy: comboStrategy,
inputTokens,
cachedTokens,
cacheCreationTokens,
costSaved: 0,
});
}
saveRequestUsage({
provider: provider || "unknown",
model: model || "unknown",
tokens: streamUsage,
status: String(streamStatus || 200),
success: streamStatus === 200,
latencyMs: Date.now() - startTime,
timeToFirstTokenMs: ttft,
errorCode: null,
timestamp: new Date().toISOString(),
connectionId: connectionId || undefined,
apiKeyId: apiKeyInfo?.id || undefined,
apiKeyName: apiKeyInfo?.name || undefined,
}).catch((err) => {
console.error("Failed to save usage stats:", err.message);
});
}
persistAttemptLogs({
@@ -1673,11 +1640,6 @@ export async function handleChatCore({
claudeCacheUsageMeta: cacheUsageLogMeta,
});
// Persist cache metrics to database
updateCacheMetrics(currentMetrics).catch((err) => {
log?.debug?.("CACHE", `Failed to persist cache metrics: ${err?.message || "unknown"}`);
});
if (apiKeyInfo?.id && streamUsage) {
calculateCost(provider, model, streamUsage)
.then((estimatedCost) => {
+16 -2
View File
@@ -80,16 +80,24 @@ export async function handleEmbedding({
};
}
// Build upstream request
// Build upstream request — start with standard fields, then forward any extras
// the client sent (e.g. input_type, user, truncate for NVIDIA NIM asymmetric models).
const KNOWN_FIELDS = new Set(["model", "input", "dimensions", "encoding_format"]);
const upstreamBody: Record<string, unknown> = {
model: model,
input: body.input,
};
// Pass optional parameters
if (body.dimensions !== undefined) upstreamBody.dimensions = body.dimensions;
if (body.encoding_format !== undefined) upstreamBody.encoding_format = body.encoding_format;
for (const [key, value] of Object.entries(body)) {
if (!KNOWN_FIELDS.has(key) && value !== undefined) {
upstreamBody[key] = value;
}
}
// Build headers
const headers = {
"Content-Type": "application/json",
@@ -104,6 +112,12 @@ export async function handleEmbedding({
} else if (providerConfig.authHeader === "x-api-key") {
headers["x-api-key"] = token;
}
} else if (providerConfig.authType !== "none") {
return {
success: false,
status: 401,
error: `No valid authentication token for provider ${provider}. Check provider credentials.`,
};
}
if (log) {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.2.6",
"version": "3.2.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.2.6",
"version": "3.2.8",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "3.2.7",
"version": "3.2.8",
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
"type": "module",
"bin": {
+17
View File
@@ -0,0 +1,17 @@
## [3.2.8] - 2026-03-29
### ✨ Enhancements & Refactoring
- **Docker Auto-Update UI** — Integrated a detached background update process for Docker Compose deployments. The Dashboard UI now seamlessly tracks update lifecycle events combining JSON REST responses with SSE streaming progress overlays for robust cross-environment reliability.
- **Cache Analytics** — Repaired zero-metrics visualization mapping by migrating Semantic Cache telemetry logs directly into the centralized tracking SQLite module.
### 🐛 Bug Fixes
- **Authentication Logic** — Fixed a bug where saving dashboard settings or adding models failed with a 401 Unauthorized error when `requireLogin` was disabled. API endpoints now correctly evaluate the global authentication toggle. Resolved global redirection by reactivating `src/middleware.ts`.
- **CLI Tool Detection (Windows)** — Prevented fatal initialization exceptions during CLI environment detection by catching `cross-spawn` ENOENT errors correctly. Adds explicit detection paths for `\AppData\Local\droid\droid.exe`.
- **Codex Native Passthrough** — Normalized model translation parameters preventing context poisoning in proxy pass-through mode, enforcing generic `store: false` constraints explicitly for all Codex-originated requests.
- **SSE Token Reporting** — Normalized provider tool-call chunk `finish_reason` detection, fixing 0% Usage analytics for stream-only responses missing strict `<DONE>` indicators.
- **DeepSeek <think> Tags** — Implemented an explicit `<think>` extraction mapping inside `responsesHandler.ts`, ensuring DeepSeek reasoning streams map equivalently to native Anthropic `<thinking>` structures.
---
+9 -1
View File
@@ -14,7 +14,7 @@ import {
type EmbeddingProviderNodeRow,
type EmbeddingProvider,
} from "@omniroute/open-sse/config/embeddingRegistry.ts";
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
import { errorResponse, unavailableResponse } from "@omniroute/open-sse/utils/error.ts";
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import * as log from "@/sse/utils/logger";
import { toJsonErrorPayload } from "@/shared/utils/upstreamError";
@@ -209,6 +209,14 @@ export async function POST(request) {
`No credentials for embedding provider: ${provider}`
);
}
if (credentials.allRateLimited) {
return unavailableResponse(
HTTP_STATUS.RATE_LIMITED,
`[${provider}] All accounts rate limited`,
credentials.retryAfter,
credentials.retryAfterHuman
);
}
}
const result = await handleEmbedding({
+17 -2
View File
@@ -11,7 +11,7 @@ import {
getAllImageModels,
getImageProvider,
} from "@omniroute/open-sse/config/imageRegistry.ts";
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
import { errorResponse, unavailableResponse } from "@omniroute/open-sse/utils/error.ts";
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import * as log from "@/sse/utils/logger";
import { toJsonErrorPayload } from "@/shared/utils/upstreamError";
@@ -156,8 +156,15 @@ export async function POST(request) {
`No credentials for image provider: ${provider}`
);
}
if (credentials.allRateLimited) {
return unavailableResponse(
HTTP_STATUS.RATE_LIMITED,
`[${provider}] All accounts rate limited`,
credentials.retryAfter,
credentials.retryAfterHuman
);
}
} else if (isCustomModel) {
// Custom models need credentials from the provider connection
credentials = await getProviderCredentials(provider);
if (!credentials) {
return errorResponse(
@@ -165,6 +172,14 @@ export async function POST(request) {
`No credentials for custom image provider: ${provider}`
);
}
if (credentials.allRateLimited) {
return unavailableResponse(
HTTP_STATUS.RATE_LIMITED,
`[${provider}] All accounts rate limited`,
credentials.retryAfter,
credentials.retryAfterHuman
);
}
}
const result = await handleImageGeneration({
@@ -1,5 +1,5 @@
import { CORS_ORIGIN } from "@/shared/utils/cors";
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
import { errorResponse, unavailableResponse } from "@omniroute/open-sse/utils/error.ts";
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts";
import {
@@ -85,6 +85,14 @@ export async function POST(request, { params }) {
if (!credentials) {
return errorResponse(HTTP_STATUS.BAD_REQUEST, `No credentials for provider: ${rawProvider}`);
}
if (credentials.allRateLimited) {
return unavailableResponse(
HTTP_STATUS.RATE_LIMITED,
`[${rawProvider}] All accounts rate limited`,
credentials.retryAfter,
credentials.retryAfterHuman
);
}
const result = await handleEmbedding({ body, credentials, log });
@@ -1,6 +1,6 @@
import { CORS_ORIGIN } from "@/shared/utils/cors";
import { handleImageGeneration } from "@omniroute/open-sse/handlers/imageGeneration.ts";
import { errorResponse } from "@omniroute/open-sse/utils/error.ts";
import { errorResponse, unavailableResponse } from "@omniroute/open-sse/utils/error.ts";
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
import {
getProviderCredentials,
@@ -85,6 +85,14 @@ export async function POST(request, { params }) {
`No credentials for image provider: ${rawProvider}`
);
}
if (credentials.allRateLimited) {
return unavailableResponse(
HTTP_STATUS.RATE_LIMITED,
`[${rawProvider}] All accounts rate limited`,
credentials.retryAfter,
credentials.retryAfterHuman
);
}
const result = await handleImageGeneration({ body, credentials, log });
+1 -1
View File
@@ -9,7 +9,7 @@ import { isModelSyncInternalRequest } from "./shared/services/modelSyncScheduler
const SECRET = new TextEncoder().encode(process.env.JWT_SECRET || "");
export async function proxy(request) {
export async function proxy(request: any) {
const { pathname } = request.nextUrl;
// Pipeline: Add request ID header for end-to-end tracing
+4
View File
@@ -89,6 +89,10 @@ export async function verifyAuth(request: any): Promise<string | null> {
* need to conditionally skip auth should check that separately.
*/
export async function isAuthenticated(request: Request): Promise<boolean> {
// If settings say login/auth is disabled, treat all requests as authenticated
if (!(await isAuthRequired())) {
return true;
}
// 1. Check API key (for external clients)
const authHeader = request.headers.get("authorization");
if (authHeader?.startsWith("Bearer ")) {
+207
View File
@@ -0,0 +1,207 @@
[CREDENTIALS] No external credentials file found, using defaults.
[DB] SQLite database ready: /home/diegosouzapw/.omniroute/storage.sqlite
[MODEL] Ambiguous model 'claude-haiku-4.5'. Use provider/model prefix (ex: gh/claude-haiku-4.5 or kr/claude-haiku-4.5). Candidates: gh, kr, anthropic
TAP version 13
# Subtest: getModelInfoCore resolves unique non-openai unprefixed model
ok 1 - getModelInfoCore resolves unique non-openai unprefixed model
---
duration_ms: 3.403766
type: 'test'
...
# Subtest: getModelInfoCore keeps openai fallback for gpt-4o
ok 2 - getModelInfoCore keeps openai fallback for gpt-4o
---
duration_ms: 0.535726
type: 'test'
...
# Subtest: getModelInfoCore resolves gpt-5.4 to codex
ok 3 - getModelInfoCore resolves gpt-5.4 to codex
---
duration_ms: 0.321781
type: 'test'
...
# Subtest: getModelInfoCore returns explicit ambiguity metadata for ambiguous unprefixed model
ok 4 - getModelInfoCore returns explicit ambiguity metadata for ambiguous unprefixed model
---
duration_ms: 1.079896
type: 'test'
...
# Subtest: getModelInfoCore canonicalizes github legacy alias with explicit provider prefix
ok 5 - getModelInfoCore canonicalizes github legacy alias with explicit provider prefix
---
duration_ms: 0.370547
type: 'test'
...
# Subtest: GithubExecutor routes codex-family model to /responses
ok 6 - GithubExecutor routes codex-family model to /responses
---
duration_ms: 0.47113
type: 'test'
...
# Subtest: GithubExecutor keeps non-codex model on /chat/completions
ok 7 - GithubExecutor keeps non-codex model on /chat/completions
---
duration_ms: 0.38457
type: 'test'
...
# Subtest: DefaultExecutor uses x-api-key for kimi-coding-apikey
ok 8 - DefaultExecutor uses x-api-key for kimi-coding-apikey
---
duration_ms: 0.451443
type: 'test'
...
# Subtest: CodexExecutor forces stream=true for upstream compatibility
ok 9 - CodexExecutor forces stream=true for upstream compatibility
---
duration_ms: 1.203259
type: 'test'
...
# Subtest: Claude native messages can be round-tripped through OpenAI into Claude OAuth format
ok 10 - Claude native messages can be round-tripped through OpenAI into Claude OAuth format
---
duration_ms: 7.232512
type: 'test'
...
# Subtest: CodexExecutor maps fast service tier to priority
ok 11 - CodexExecutor maps fast service tier to priority
---
duration_ms: 0.489993
type: 'test'
...
# Subtest: shouldUseNativeCodexPassthrough only enables responses-native Codex requests
ok 12 - shouldUseNativeCodexPassthrough only enables responses-native Codex requests
---
duration_ms: 0.441911
type: 'test'
...
# Subtest: CodexExecutor can force fast service tier from settings
ok 13 - CodexExecutor can force fast service tier from settings
---
duration_ms: 0.299575
type: 'test'
...
# Subtest: CodexExecutor always requests SSE accept header
ok 14 - CodexExecutor always requests SSE accept header
---
duration_ms: 0.602914
type: 'test'
...
# Subtest: CodexExecutor does not request SSE accept header for compact requests
ok 15 - CodexExecutor does not request SSE accept header for compact requests
---
duration_ms: 0.322611
type: 'test'
...
# Subtest: CodexExecutor preserves native responses payloads for Codex passthrough
not ok 16 - CodexExecutor preserves native responses payloads for Codex passthrough
---
duration_ms: 1.856261
type: 'test'
location: '/home/diegosouzapw/dev/proxys/9router/tests/unit/plan3-p0.test.mjs:221:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
false !== true
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: true
actual: false
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/diegosouzapw/dev/proxys/9router/tests/unit/plan3-p0.test.mjs:242:10)
Test.runInAsyncScope (node:async_hooks:214:14)
Test.run (node:internal/test_runner/test:1047:25)
Test.processPendingSubtests (node:internal/test_runner/test:744:18)
Test.postRun (node:internal/test_runner/test:1173:19)
Test.run (node:internal/test_runner/test:1101:12)
async Test.processPendingSubtests (node:internal/test_runner/test:744:7)
...
# Subtest: CodexExecutor strips streaming fields for compact passthrough
ok 17 - CodexExecutor strips streaming fields for compact passthrough
---
duration_ms: 0.296176
type: 'test'
...
# Subtest: CodexExecutor routes responses subpaths to matching upstream paths
ok 18 - CodexExecutor routes responses subpaths to matching upstream paths
---
duration_ms: 0.546657
type: 'test'
...
# Subtest: translateNonStreamingResponse converts Responses API payload to OpenAI chat.completion
ok 19 - translateNonStreamingResponse converts Responses API payload to OpenAI chat.completion
---
duration_ms: 1.483788
type: 'test'
...
# Subtest: extractUsageFromResponse reads usage from Responses API payload
ok 20 - extractUsageFromResponse reads usage from Responses API payload
---
duration_ms: 0.398039
type: 'test'
...
# Subtest: detectFormat identifies OpenAI Responses when input is string
ok 21 - detectFormat identifies OpenAI Responses when input is string
---
duration_ms: 0.359174
type: 'test'
...
# Subtest: detectFormat identifies OpenAI Responses by max_output_tokens without input array
ok 22 - detectFormat identifies OpenAI Responses by max_output_tokens without input array
---
duration_ms: 0.271215
type: 'test'
...
# Subtest: detectFormatFromEndpoint forces OpenAI for /v1/chat/completions
ok 23 - detectFormatFromEndpoint forces OpenAI for /v1/chat/completions
---
duration_ms: 0.52054
type: 'test'
...
# Subtest: detectFormatFromEndpoint forces Claude for /v1/messages
ok 24 - detectFormatFromEndpoint forces Claude for /v1/messages
---
duration_ms: 0.433035
type: 'test'
...
# Subtest: translateRequest normalizes openai-responses input string into list payload
ok 25 - translateRequest normalizes openai-responses input string into list payload
---
duration_ms: 0.358109
type: 'test'
...
# Subtest: translateRequest preserves service_tier when converting openai to openai-responses
ok 26 - translateRequest preserves service_tier when converting openai to openai-responses
---
duration_ms: 1.10454
type: 'test'
...
# Subtest: parseSSEToResponsesOutput parses completed response from SSE payload
ok 27 - parseSSEToResponsesOutput parses completed response from SSE payload
---
duration_ms: 0.575476
type: 'test'
...
# Subtest: parseSSEToResponsesOutput returns null for invalid payload
ok 28 - parseSSEToResponsesOutput returns null for invalid payload
---
duration_ms: 0.302714
type: 'test'
...
# Subtest: parseSSEToOpenAIResponse merges split tool call chunks by id without duplication
ok 29 - parseSSEToOpenAIResponse merges split tool call chunks by id without duplication
---
duration_ms: 0.916032
type: 'test'
...
1..29
# tests 29
# suites 0
# pass 28
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 65.394285
+5 -1
View File
@@ -120,7 +120,11 @@ test("isAuthenticated accepts bearer API keys", async () => {
assert.equal(result, true);
});
test("isAuthenticated returns false without valid credentials", async () => {
test("isAuthenticated returns false when auth is required without valid credentials", async () => {
// Force requireLogin to be active
process.env.INITIAL_PASSWORD = "bootstrap-password";
await localDb.updateSettings({ requireLogin: true, password: "" });
const request = new Request("https://example.com/api/providers");
const result = await apiAuth.isAuthenticated(request);
+1 -1
View File
@@ -239,7 +239,7 @@ test("CodexExecutor preserves native responses payloads for Codex passthrough",
assert.equal(transformed.stream, true);
assert.equal(transformed.service_tier, "priority");
assert.equal(transformed.instructions, "custom system prompt");
assert.equal(transformed.store, true);
assert.equal(transformed.store, false);
assert.deepEqual(transformed.metadata, { source: "codex-client" });
assert.equal(transformed.reasoning_effort, "high");
assert.ok(!("_nativeCodexPassthrough" in transformed));