mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 21:52:21 +03:00
Merge remote-tracking branch 'origin/release/v3.8.47' into tmp/implement-prs-6697-b
This commit is contained in:
@@ -182,7 +182,8 @@
|
||||
"open-sse/services/compression/strategySelector.ts": 1043,
|
||||
"open-sse/services/rateLimitManager.ts": 1035,
|
||||
"_rebaseline_2026_06_29_4038_cas_guard": "PR (#4038) own growth: tokenRefresh.ts 2103->2181 (+78 = the compare-and-swap guard on the refresh persist — runWithCasGuard/getActiveCasGuard AsyncLocalStorage pair mirroring runWithOnPersist, casGuardShouldSkipPersist that rereads the row right before persisting and skips the write when a concurrent writer already rotated the refresh_token past the one presented, plus getCasGuardStats counters). Fixes the sibling-rotation-revert → token-family-revocation storm. Gated behind an active guard (opt-in; no guard => byte-identical). Wiring lives at the two persist chokepoints inside getAccessToken; the comparison reuses wasRefreshTokenRotated from refreshSerializer. Not extractable without splitting the refresh hot path.",
|
||||
"open-sse/services/tokenRefresh.ts": 2181,
|
||||
"_rebaseline_2026_07_09_6126_clinepass_dual_auth": "PR #6126 (@hajilok, dual-auth ClinePass) own growth: tokenRefresh.ts 2181->2182 (+1 = a single `case \"clinepass\":` fallthrough label added to the existing `case \"cline\":` in _getAccessTokenInternal's provider switch, so clinepass token refresh dispatches to the already-shared refreshClineToken() instead of silently falling through to the generic OAuth refresh). Irreducible 1-line switch-case wiring at the existing chokepoint; the header-building logic for the same feature was extracted to a new leaf src/shared/utils/clineAuth.ts::buildClinepassHeaders() (well under cap) to avoid growing open-sse/executors/default.ts. Covered by tests/unit/clinepass-provider.test.ts.",
|
||||
"open-sse/services/tokenRefresh.ts": 2182,
|
||||
"open-sse/services/usage.ts": 3454,
|
||||
"open-sse/translator/request/openai-to-gemini.ts": 906,
|
||||
"open-sse/translator/request/openai-to-kiro.ts": 890,
|
||||
@@ -276,6 +277,7 @@
|
||||
},
|
||||
"testCap": 800,
|
||||
"testFrozen": {
|
||||
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
|
||||
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
|
||||
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
|
||||
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
|
||||
@@ -305,7 +307,7 @@
|
||||
"tests/unit/image-generation-handler.test.ts": 2019,
|
||||
"tests/unit/model-sync-route.test.ts": 1016,
|
||||
"tests/unit/models-catalog-route.test.ts": 1605,
|
||||
"tests/unit/oauth-providers-config.test.ts": 842,
|
||||
"tests/unit/oauth-providers-config.test.ts": 845,
|
||||
"tests/unit/perplexity-web.test.ts": 999,
|
||||
"tests/unit/provider-models-route.test.ts": 1752,
|
||||
"tests/unit/provider-validation-specialty.test.ts": 2856,
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
|
||||
// ClinePass — Cline's $9.99/mo BYOK API-key gateway (https://cline.bot). Distinct
|
||||
// from the OAuth `cline` provider: same host (api.cline.bot) but a plain Bearer
|
||||
// API key and the `cline-pass/*` model namespace. Responses are wrapped in a
|
||||
// {success, data} envelope — unwrapped by open-sse/utils/clinepassEnvelope.ts.
|
||||
// ClinePass — Cline's $9.99/mo gateway (https://cline.bot). Dual-auth: sign in
|
||||
// with a Cline account (OAuth, reusing the `cline` WorkOS flow) OR paste a direct
|
||||
// BYOK API key. Same host (api.cline.bot) as the OAuth `cline` provider; the
|
||||
// `cline-pass/*` model namespace. Responses are wrapped in a {success, data}
|
||||
// envelope — unwrapped by open-sse/utils/clinepassEnvelope.ts.
|
||||
export const clinepassProvider: RegistryEntry = {
|
||||
id: "clinepass",
|
||||
alias: "clinepass",
|
||||
// MUST match the OAUTH_PROVIDERS catalog alias (src/shared/constants/providers/oauth.ts).
|
||||
// The dashboard sends models as `<catalogAlias>/<modelId>` (e.g. "cp/cline-pass/glm-5.2"),
|
||||
// and routing resolves the prefix via ALIAS_TO_PROVIDER_ID (built from this field). If the
|
||||
// registry alias drifts from the catalog alias, the prefix won't resolve, the executor falls
|
||||
// back to PROVIDERS.openai, and requests hit api.openai.com with the ClinePass key → 401.
|
||||
alias: "cp",
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
// ClinePass shares Cline's streaming-only API — a non-streaming request returns
|
||||
// "generateText is not implemented" / an empty body. Force upstream streaming;
|
||||
// chatCore accumulates the SSE and converts it back to JSON for stream:false
|
||||
// clients. (Same as the sibling `cline` provider.)
|
||||
// clients. (Same as the sibling `cline` provider. #6165.)
|
||||
forceStream: true,
|
||||
baseUrl: "https://api.cline.bot/api/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authType: "oauth",
|
||||
authHeader: "bearer",
|
||||
oauth: {
|
||||
tokenUrl: "https://api.cline.bot/api/v1/auth/token",
|
||||
refreshUrl: "https://api.cline.bot/api/v1/auth/refresh",
|
||||
authUrl: "https://api.cline.bot/api/v1/auth/authorize",
|
||||
},
|
||||
extraHeaders: {
|
||||
"HTTP-Referer": "https://cline.bot",
|
||||
"X-Title": "Cline",
|
||||
|
||||
@@ -44,7 +44,7 @@ import { buildMaritalkChatUrl } from "../config/maritalk.ts";
|
||||
import { LOCAL_PROVIDERS } from "@/shared/constants/providers";
|
||||
import { isForbiddenCustomHeaderName } from "@/shared/constants/upstreamHeaders";
|
||||
import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults";
|
||||
import { buildClineHeaders } from "@/shared/utils/clineAuth";
|
||||
import { buildClineHeaders, buildClinepassHeaders } from "@/shared/utils/clineAuth";
|
||||
import {
|
||||
normalizeHerokuChatUrl,
|
||||
normalizeDatabricksChatUrl,
|
||||
@@ -369,6 +369,9 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
case "glm-coding-apikey":
|
||||
headers["x-api-key"] = effectiveKey || credentials.accessToken;
|
||||
break;
|
||||
case "clinepass": // dual-auth (OAuth or BYOK) — see buildClinepassHeaders()
|
||||
Object.assign(headers, buildClinepassHeaders(credentials, effectiveKey));
|
||||
break;
|
||||
case "cline":
|
||||
// Cline's API requires the bearer token prefixed with `workos:` plus a
|
||||
// set of Cline client-identification headers; plain `Bearer <token>`
|
||||
|
||||
@@ -1602,6 +1602,7 @@ async function _getAccessTokenInternal(provider, credentials, log, proxyConfig:
|
||||
);
|
||||
|
||||
case "cline":
|
||||
case "clinepass": // reuses the Cline WorkOS refresh flow (clinepass: cline)
|
||||
return await refreshClineToken(credentials.refreshToken, log, proxyConfig);
|
||||
|
||||
case "kimi-coding":
|
||||
|
||||
@@ -247,17 +247,22 @@ export default function ConnectionsHeaderToolbar({
|
||||
)}
|
||||
{!isCompatible ? (
|
||||
<>
|
||||
{isCommandCode ? (
|
||||
{isCommandCode || providerId === "clinepass" ? (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
icon="open_in_new"
|
||||
loading={
|
||||
commandCodeAuthState.phase === "starting" ||
|
||||
commandCodeAuthState.phase === "polling" ||
|
||||
commandCodeAuthState.phase === "applying"
|
||||
isCommandCode &&
|
||||
(commandCodeAuthState.phase === "starting" ||
|
||||
commandCodeAuthState.phase === "polling" ||
|
||||
commandCodeAuthState.phase === "applying")
|
||||
}
|
||||
onClick={() =>
|
||||
gateConnectionFlow(
|
||||
isCommandCode ? handleOpenCommandCodeConnect : openPrimaryAddFlow
|
||||
)
|
||||
}
|
||||
onClick={() => gateConnectionFlow(handleOpenCommandCodeConnect)}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
|
||||
@@ -55,16 +55,21 @@ export default function EmptyConnectionsPlaceholder({
|
||||
<p className="text-sm text-text-muted mb-4">{t("addFirstConnectionHint")}</p>
|
||||
{!isCompatible && (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{isCommandCode ? (
|
||||
{isCommandCode || providerId === "clinepass" ? (
|
||||
<>
|
||||
<Button
|
||||
icon="open_in_new"
|
||||
loading={
|
||||
commandCodeAuthState.phase === "starting" ||
|
||||
commandCodeAuthState.phase === "polling" ||
|
||||
commandCodeAuthState.phase === "applying"
|
||||
isCommandCode &&
|
||||
(commandCodeAuthState.phase === "starting" ||
|
||||
commandCodeAuthState.phase === "polling" ||
|
||||
commandCodeAuthState.phase === "applying")
|
||||
}
|
||||
onClick={() =>
|
||||
gateConnectionFlow(
|
||||
isCommandCode ? handleOpenCommandCodeConnect : openPrimaryAddFlow
|
||||
)
|
||||
}
|
||||
onClick={() => gateConnectionFlow(handleOpenCommandCodeConnect)}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
|
||||
@@ -484,6 +484,7 @@ export const PROVIDERS = {
|
||||
CURSOR: "cursor",
|
||||
KILOCODE: "kilocode",
|
||||
CLINE: "cline",
|
||||
CLINEPASS: "clinepass",
|
||||
WINDSURF: "windsurf",
|
||||
DEVIN_CLI: "devin-cli",
|
||||
TRAE: "trae",
|
||||
|
||||
@@ -46,6 +46,10 @@ export const PROVIDERS = {
|
||||
trae,
|
||||
kilocode,
|
||||
cline,
|
||||
// clinepass reuses the Cline WorkOS OAuth flow 1:1 (same api.cline.bot host, same token
|
||||
// type) — it is a separate catalog entry advertising the cline-pass/* (ClinePass
|
||||
// subscription) models. See registry/clinepass/index.ts.
|
||||
clinepass: cline,
|
||||
windsurf,
|
||||
// devin-cli shares the same token format as windsurf (WINDSURF_API_KEY / devin auth login)
|
||||
"devin-cli": windsurf,
|
||||
|
||||
@@ -195,8 +195,22 @@ export function resolveStaticProviderCatalogEntry(
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth-primary providers that ALSO accept a direct BYOK API key (dual-auth),
|
||||
* admitted through the managed-connection API-key gate independent of the OAuth
|
||||
* catalog. These are deliberately kept OUT of `FREE_APIKEY_PROVIDER_IDS`: that
|
||||
* set flips `providerSupportsPat` true, which turns `isOAuth` false and would
|
||||
* make the dashboard's primary "Connect" button route to the API-key modal
|
||||
* instead of the OAuth flow. Admitting them here lets POST /api/providers
|
||||
* persist an `apikey` connection (the reliable BYOK path) while the provider
|
||||
* stays OAuth-primary (isOAuth=true). clinepass is the dual-auth case: sign in
|
||||
* with a Cline account OR paste a ClinePass API key.
|
||||
*/
|
||||
const DUAL_AUTH_APIKEY_PROVIDER_IDS = new Set<string>(["clinepass"]);
|
||||
|
||||
export function isManagedProviderConnectionId(providerId: string): boolean {
|
||||
if (supportsApiKeyOnFreeProvider(providerId)) return true;
|
||||
if (DUAL_AUTH_APIKEY_PROVIDER_IDS.has(providerId)) return true;
|
||||
|
||||
const entry = resolveStaticProviderCatalogEntry(providerId);
|
||||
return !!(entry && MANAGED_PROVIDER_CONNECTION_CATEGORIES.has(entry.category));
|
||||
|
||||
@@ -41,20 +41,6 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
|
||||
"Use a Command Code API key. Requests are sent to Command Code's /alpha/generate endpoint.",
|
||||
apiHint: "Create or copy an API key from Command Code, then paste it here as a Bearer token.",
|
||||
},
|
||||
clinepass: {
|
||||
id: "clinepass",
|
||||
alias: "clinepass",
|
||||
name: "ClinePass",
|
||||
icon: "vpn_key",
|
||||
color: "#5B9BD5",
|
||||
textIcon: "CP",
|
||||
passthroughModels: true,
|
||||
website: "https://cline.bot",
|
||||
notice: {
|
||||
text: "ClinePass is Cline's paid BYOK gateway ($9.99/mo). Bring your own Cline API key; requests hit api.cline.bot with the cline-pass/* model namespace.",
|
||||
apiKeyUrl: "https://app.cline.bot/settings/api-keys",
|
||||
},
|
||||
},
|
||||
openrouter: {
|
||||
id: "openrouter",
|
||||
alias: "openrouter",
|
||||
|
||||
@@ -191,6 +191,19 @@ export const OAUTH_PROVIDERS = {
|
||||
subscriptionRisk: true,
|
||||
riskNoticeVariant: "oauth",
|
||||
},
|
||||
clinepass: {
|
||||
id: "clinepass",
|
||||
alias: "cp",
|
||||
name: "ClinePass",
|
||||
icon: "smart_toy",
|
||||
color: "#9D4EDD",
|
||||
textIcon: "CP",
|
||||
website: "https://cline.bot/clinepass",
|
||||
subscriptionRisk: true,
|
||||
riskNoticeVariant: "oauth",
|
||||
authHint:
|
||||
"ClinePass is Cline's $9.99/mo subscription bundling 10 open coding models. Sign in with your Cline account (same login as the Cline CLI/IDE), or paste a direct ClinePass API key (app.cline.bot → Settings → API Keys). A ClinePass subscription unlocks the cline-pass/* models. Reuses the Cline WorkOS OAuth flow.",
|
||||
},
|
||||
windsurf: {
|
||||
id: "windsurf",
|
||||
alias: "ws",
|
||||
|
||||
@@ -60,3 +60,26 @@ export function buildClineHeaders(
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build headers for a ClinePass request. ClinePass is dual-auth: an OAuth
|
||||
* connection (workos:-prefixed token in `accessToken`) needs the full Cline
|
||||
* client header set from `buildClineHeaders()`; a BYOK API-key connection
|
||||
* (`sk_...` key, #5942) sends the key as a plain Bearer token — no `workos:`
|
||||
* prefix — alongside the Cline identification headers.
|
||||
*/
|
||||
export function buildClinepassHeaders(
|
||||
credentials: { accessToken?: unknown; apiKey?: unknown } | null | undefined,
|
||||
effectiveKey?: string
|
||||
): Record<string, string> {
|
||||
if (credentials?.accessToken) {
|
||||
return buildClineHeaders(credentials.accessToken);
|
||||
}
|
||||
const headers: Record<string, string> = {
|
||||
"HTTP-Referer": "https://cline.bot",
|
||||
"X-Title": "Cline",
|
||||
};
|
||||
const byokKey = effectiveKey || (credentials?.apiKey as string | undefined);
|
||||
if (byokKey) headers.Authorization = `Bearer ${byokKey}`;
|
||||
return headers;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
|
||||
const { APIKEY_PROVIDERS, OAUTH_PROVIDERS, supportsApiKeyOnFreeProvider } =
|
||||
await import("../../src/shared/constants/providers.ts");
|
||||
const { isManagedProviderConnectionId } = await import("../../src/lib/providers/catalog.ts");
|
||||
const { PROVIDERS: oauthFlows } = await import("../../src/lib/oauth/providers/index.ts");
|
||||
const { REGISTRY: providerRegistry } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { unwrapClinepassEnvelope } = await import("../../open-sse/utils/clinepassEnvelope.ts");
|
||||
const { filterClinepassModels } = await import("../../open-sse/services/clinepassModels.ts");
|
||||
const { parseUpstreamError, buildErrorBody } = await import("../../open-sse/utils/error.ts");
|
||||
|
||||
// ── Provider metadata (Zod-validated APIKEY catalog) ─────────────────────────
|
||||
test("ClinePass is registered as an API-key provider with the canonical identity", () => {
|
||||
const cp = APIKEY_PROVIDERS.clinepass;
|
||||
assert.ok(cp, "APIKEY_PROVIDERS.clinepass must be defined");
|
||||
// ── Provider metadata (oauth-primary catalog; single provider) ──────────────
|
||||
test("ClinePass is registered as an OAuth-primary provider with the canonical identity", () => {
|
||||
const cp = OAUTH_PROVIDERS.clinepass;
|
||||
assert.ok(cp, "OAUTH_PROVIDERS.clinepass must be defined (oauth-primary catalog)");
|
||||
assert.equal(cp.id, "clinepass");
|
||||
assert.equal(cp.alias, "clinepass");
|
||||
assert.equal(cp.name, "ClinePass");
|
||||
assert.equal(cp.website, "https://cline.bot");
|
||||
assert.equal(
|
||||
(cp as { notice?: { apiKeyUrl?: string } }).notice?.apiKeyUrl,
|
||||
"https://app.cline.bot/settings/api-keys"
|
||||
// Single provider — NO duplicate APIKEY_PROVIDERS entry. Dual-auth (OAuth sign-in
|
||||
// + Manual API key) is rendered by the dashboard's isOAuth branch (same as
|
||||
// cline/claude), not via FREE_APIKEY_PROVIDER_IDS (which would flip isOAuth off).
|
||||
assert.ok(
|
||||
!APIKEY_PROVIDERS.clinepass,
|
||||
"clinepass must NOT be in APIKEY_PROVIDERS (single provider)"
|
||||
);
|
||||
});
|
||||
|
||||
test("ClinePass registry entry uses OpenAI format with bearer apikey auth + Cline headers", () => {
|
||||
test("ClinePass registry entry is oauth-primary (dual-auth) with Cline headers", () => {
|
||||
const entry = providerRegistry.clinepass;
|
||||
assert.ok(entry, "providerRegistry.clinepass must be defined");
|
||||
assert.equal(entry.id, "clinepass");
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authType, "oauth");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.ok(entry.oauth, "must carry the Cline OAuth urls (sign-in path)");
|
||||
assert.equal(entry.baseUrl, "https://api.cline.bot/api/v1/chat/completions");
|
||||
assert.equal(entry.extraHeaders?.["HTTP-Referer"], "https://cline.bot");
|
||||
assert.equal(entry.extraHeaders?.["X-Title"], "Cline");
|
||||
@@ -115,3 +120,61 @@ test("parseUpstreamError unwraps clinepass envelope error without leaking a stac
|
||||
const body = buildErrorBody(502, parsed.message) as { error: { message: string } };
|
||||
assert.ok(!body.error.message.includes("at /"), "sanitized error must not include a stack frame");
|
||||
});
|
||||
|
||||
// ── Dual-auth: clinepass accepts BOTH an API key (#5942) AND OAuth login ─────
|
||||
test("ClinePass is also in the OAuth catalog (dual-auth: API-key + OAuth login)", () => {
|
||||
const cp = OAUTH_PROVIDERS.clinepass;
|
||||
assert.ok(cp, "OAUTH_PROVIDERS.clinepass must be defined for the OAuth login path");
|
||||
assert.equal(cp.id, "clinepass");
|
||||
assert.equal(cp.name, "ClinePass");
|
||||
});
|
||||
|
||||
test("ClinePass reuses the Cline WorkOS OAuth flow (clinepass -> cline)", () => {
|
||||
assert.ok(oauthFlows.clinepass, "clinepass must map to an OAuth flow");
|
||||
assert.equal(
|
||||
oauthFlows.clinepass,
|
||||
oauthFlows.cline,
|
||||
"clinepass must reuse the cline OAuth flow 1:1 (same api.cline.bot host/token)"
|
||||
);
|
||||
});
|
||||
|
||||
test("ClinePass is a single OAuth-primary provider (no duplicate catalog entry)", () => {
|
||||
assert.ok(OAUTH_PROVIDERS.clinepass, "OAuth catalog entry");
|
||||
assert.ok(!APIKEY_PROVIDERS.clinepass, "no duplicate APIKEY_PROVIDERS entry");
|
||||
});
|
||||
|
||||
// ── Dual-auth API-key admission (POST /api/providers gate) ───────────────────
|
||||
// clinepass is OAuth-primary (isOAuth=true → "Connect" opens the OAuth flow) but
|
||||
// ALSO accepts a pasted BYOK API key. The API-key path must pass the managed-
|
||||
// connection gate (isManagedProviderConnectionId) WITHOUT flipping isOAuth off.
|
||||
// That means admitting it through the dedicated DUAL_AUTH set, NOT through
|
||||
// FREE_APIKEY_PROVIDER_IDS (which would set providerSupportsPat=true → isOAuth=false
|
||||
// and break the primary Connect→OAuth routing). Regression guard for the layout.
|
||||
test("ClinePass API-key connections pass the managed gate while staying OAuth-primary", () => {
|
||||
assert.ok(
|
||||
isManagedProviderConnectionId("clinepass"),
|
||||
"POST /api/providers must accept a clinepass apikey connection (dual-auth BYOK path)"
|
||||
);
|
||||
assert.ok(
|
||||
!supportsApiKeyOnFreeProvider("clinepass"),
|
||||
"clinepass must NOT be in FREE_APIKEY_PROVIDER_IDS — that would flip isOAuth false"
|
||||
);
|
||||
});
|
||||
|
||||
// ── Catalog ↔ registry alias consistency (routing prefix) ───────────────────
|
||||
// The dashboard sends models as `<catalogAlias>/<modelId>` (e.g. "cp/cline-pass/glm-5.2").
|
||||
// Routing resolves that prefix via ALIAS_TO_PROVIDER_ID, which is built from the REGISTRY
|
||||
// alias (generateAliasMap). If the registry alias drifts from the catalog alias, the prefix
|
||||
// won't resolve → executor falls back to PROVIDERS.openai → requests hit api.openai.com
|
||||
// with the ClinePass key → a misleading OpenAI 401. cline keeps these in sync (both "cl");
|
||||
// clinepass must too. Regression guard for the cp/cline-pass/* OpenAI-401 incident.
|
||||
test("ClinePass registry alias matches the OAUTH_PROVIDERS catalog alias (routing prefix)", () => {
|
||||
const cp = OAUTH_PROVIDERS.clinepass;
|
||||
assert.ok(cp?.alias, "catalog alias must be defined");
|
||||
assert.equal(
|
||||
providerRegistry.clinepass.alias,
|
||||
cp.alias,
|
||||
"registry alias must equal catalog alias so <alias>/<model> resolves to clinepass"
|
||||
);
|
||||
assert.equal(providerRegistry.clinepass.alias, "cp");
|
||||
});
|
||||
|
||||
@@ -64,6 +64,7 @@ const EXPECTED_PROVIDER_KEYS = [
|
||||
"trae",
|
||||
"kilocode",
|
||||
"cline",
|
||||
"clinepass",
|
||||
"windsurf",
|
||||
"devin-cli",
|
||||
"grok-cli",
|
||||
@@ -87,6 +88,7 @@ const EXPECTED_CONFIG_BY_PROVIDER = {
|
||||
cursor: CURSOR_CONFIG,
|
||||
kilocode: KILOCODE_CONFIG,
|
||||
cline: CLINE_CONFIG,
|
||||
clinepass: CLINE_CONFIG, // reuses the Cline WorkOS flow (clinepass: cline in providers/index.ts)
|
||||
windsurf: WINDSURF_CONFIG,
|
||||
"devin-cli": WINDSURF_CONFIG,
|
||||
trae: TRAE_CONFIG,
|
||||
@@ -138,6 +140,7 @@ const REQUIRED_FIELDS_BY_PROVIDER = {
|
||||
cursor: ["apiEndpoint", "api3Endpoint", "agentEndpoint", "agentNonPrivacyEndpoint", "dbKeys"],
|
||||
kilocode: ["apiBaseUrl", "initiateUrl", "pollUrlBase"],
|
||||
cline: ["appBaseUrl", "apiBaseUrl", "authorizeUrl", "tokenExchangeUrl", "refreshUrl"],
|
||||
clinepass: ["appBaseUrl", "apiBaseUrl", "authorizeUrl", "tokenExchangeUrl", "refreshUrl"],
|
||||
windsurf: ["authorizeUrl", "apiServerUrl", "exchangePath", "inferenceUrl"],
|
||||
"devin-cli": ["authorizeUrl", "apiServerUrl", "exchangePath", "inferenceUrl"],
|
||||
trae: ["apiEndpoint", "chatEndpoint", "webUrl"],
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// Characterization of the providers.ts catalog split (god-file decomposition): the host became a
|
||||
// barrel that re-exports 10 data catalogs now living under constants/providers/*, and APIKEY is
|
||||
// merged from 6 semantic family files (apikey/<family>.ts). Locks: the public surface (every catalog
|
||||
// + helpers still exported), the spread-merge integrity (167 APIKEY entries, no loss/dup), and that
|
||||
// + helpers still exported), the spread-merge integrity (166 APIKEY entries, no loss/dup), and that
|
||||
// load-time Zod validation still runs. Pure-data move → behavior must be identical.
|
||||
// Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc.).
|
||||
// Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc.,
|
||||
// 171->167) plus #6126 (ClinePass dual-auth): the API-key-only APIKEY_PROVIDERS_GATEWAYS entry was
|
||||
// removed as a duplicate now that clinepass is OAuth-primary (OAUTH_PROVIDERS.clinepass) with its
|
||||
// BYOK path admitted through the DUAL_AUTH_APIKEY_PROVIDER_IDS gate instead (167->166).
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
@@ -32,12 +35,12 @@ test("barrel still exports every catalog + key helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("APIKEY_PROVIDERS merges the 6 family files into 167 entries (no loss / no dup)", async () => {
|
||||
test("APIKEY_PROVIDERS merges the 6 family files into 166 entries (no loss / no dup)", async () => {
|
||||
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
|
||||
assert.equal(keys.length, 167);
|
||||
assert.equal(new Set(keys).size, 167, "duplicate keys after spread-merge");
|
||||
assert.equal(keys.length, 166);
|
||||
assert.equal(new Set(keys).size, 166, "duplicate keys after spread-merge");
|
||||
// the merged object's entry-count equals the sum of the 6 semantic family files; families are a
|
||||
// strict partition (every provider in exactly one), so the sum must be exactly 167.
|
||||
// strict partition (every provider in exactly one), so the sum must be exactly 166.
|
||||
const families: [string, string][] = [
|
||||
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
|
||||
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
|
||||
@@ -57,7 +60,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 167 entries (no loss / no
|
||||
seen.add(k);
|
||||
}
|
||||
}
|
||||
assert.equal(famTotal, 167, "families must partition all 167 providers");
|
||||
assert.equal(famTotal, 166, "families must partition all 166 providers");
|
||||
});
|
||||
|
||||
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {
|
||||
|
||||
Reference in New Issue
Block a user