Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fb72b973a | |||
| 7b1c945324 | |||
| 1a7c363413 | |||
| 107a572174 | |||
| 89bd31c670 | |||
| 2e29e45a3a |
@@ -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
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user