fix: improve provider routing and assignments
This commit is contained in:
1 parent
d00205511a
commit
df8c4ccb81
17 files changed
+471
-51
No files matched your search
@@ -6,7 +6,7 @@ import PropTypes from "prop-types";
|
||||
import { Badge, Toggle, Tooltip } from "@/shared/components";
|
||||
import CooldownTimer from "./CooldownTimer";
|
||||
|
||||
export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst, isLast, onMoveUp, onMoveDown, onToggleActive, onUpdateProxy, onEdit, onDelete, oneByOneStatus = null, autoPing = null }) {
|
||||
export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst, isLast, onMoveUp, onMoveDown, onToggleActive, onUpdateProxy, onEdit, onDelete, oneByOneStatus = null, autoPing = null, modelAssignmentOptions = null, onModelAssignmentChange = null, strictModelAssignment = false }) {
|
||||
const [showProxyDropdown, setShowProxyDropdown] = useState(false);
|
||||
const [updatingProxy, setUpdatingProxy] = useState(false);
|
||||
const [selectedProxyIds, setSelectedProxyIds] = useState([]);
|
||||
@@ -19,13 +19,14 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst
|
||||
const legacyProxyPoolId = connection.providerSpecificData?.proxyPoolId;
|
||||
|
||||
// Migrate legacy single proxy to array format
|
||||
if (legacyProxyPoolId && proxyPoolIds.length === 0) {
|
||||
setSelectedProxyIds([legacyProxyPoolId]);
|
||||
} else {
|
||||
setSelectedProxyIds(proxyPoolIds);
|
||||
}
|
||||
|
||||
setRotationStrategy(connection.providerSpecificData?.proxyRotationStrategy || "none");
|
||||
queueMicrotask(() => {
|
||||
if (legacyProxyPoolId && proxyPoolIds.length === 0) {
|
||||
setSelectedProxyIds([legacyProxyPoolId]);
|
||||
} else {
|
||||
setSelectedProxyIds(proxyPoolIds);
|
||||
}
|
||||
setRotationStrategy(connection.providerSpecificData?.proxyRotationStrategy || "none");
|
||||
});
|
||||
}, [connection]);
|
||||
|
||||
const proxyPoolMap = new Map((proxyPools || []).map((pool) => [pool.id, pool]));
|
||||
@@ -201,7 +202,7 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst
|
||||
return () => {
|
||||
if (interval) clearInterval(interval);
|
||||
};
|
||||
}, [modelLockUntil]);
|
||||
}, [connection, modelLockUntil]);
|
||||
|
||||
// Determine effective status (override unavailable if cooldown expired)
|
||||
const effectiveStatus = (connection.testStatus === "unavailable" && !isCooldown)
|
||||
@@ -283,6 +284,20 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{modelAssignmentOptions && onModelAssignmentChange && (
|
||||
<select
|
||||
value={connection.providerSpecificData?.assignedModel || connection.providerSpecificData?.freebuffModel || ""}
|
||||
onChange={(e) => onModelAssignmentChange(e.target.value)}
|
||||
disabled={!strictModelAssignment}
|
||||
className="mt-2 max-w-full rounded-md border border-border bg-background px-2 py-1 text-[11px] text-text-main"
|
||||
title="Model assignment"
|
||||
>
|
||||
<option value="">Unassigned</option>
|
||||
{modelAssignmentOptions.map((model) => (
|
||||
<option key={model.id} value={model.id}>{model.name || model.id}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
{hasAnyProxy && (
|
||||
<div className="mt-1 flex items-center gap-2 flex-wrap">
|
||||
<span className="max-w-full truncate text-[11px] text-text-muted sm:max-w-[420px]" title={proxyDisplayText}>
|
||||
@@ -495,6 +510,9 @@ ConnectionRow.propTypes = {
|
||||
onUpdateProxy: PropTypes.func,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
modelAssignmentOptions: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string })),
|
||||
onModelAssignmentChange: PropTypes.func,
|
||||
strictModelAssignment: PropTypes.bool,
|
||||
oneByOneStatus: PropTypes.shape({
|
||||
state: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
|
||||
@@ -64,6 +64,7 @@ export default function ProviderDetailPage() {
|
||||
const [bulkUpdatingProxy, setBulkUpdatingProxy] = useState(false);
|
||||
const [providerStrategy, setProviderStrategy] = useState(null);
|
||||
const [providerStickyLimit, setProviderStickyLimit] = useState("");
|
||||
const [strictModelAssignment, setStrictModelAssignment] = useState(false);
|
||||
const [thinkingMode, setThinkingMode] = useState("auto");
|
||||
const [autoPing, setAutoPing] = useState({ enabled: false, connections: {} });
|
||||
const [suggestedModels, setSuggestedModels] = useState([]);
|
||||
@@ -170,6 +171,21 @@ export default function ProviderDetailPage() {
|
||||
return levels && levels.includes(thinkingMode) ? thinkingMode : null;
|
||||
};
|
||||
const providerStorageAlias = isCompatible ? providerId : providerAlias;
|
||||
const assignmentModels = (() => {
|
||||
const byId = new Map();
|
||||
const add = (model) => {
|
||||
if (model?.id && !byId.has(model.id)) byId.set(model.id, model);
|
||||
};
|
||||
models.forEach(add);
|
||||
kiloFreeModels.forEach(add);
|
||||
customModels.forEach((model) => {
|
||||
if (model.providerAlias === providerStorageAlias && (model.kind || model.type || "llm") === "llm") {
|
||||
add(model);
|
||||
}
|
||||
});
|
||||
const disabled = new Set(disabledModelIds);
|
||||
return [...byId.values()].filter((model) => !disabled.has(model.id));
|
||||
})();
|
||||
// Union of levels across this provider's reasoning models — drives the level picker options.
|
||||
// Include custom models too (e.g. manually added gpt-5.6-sol → max).
|
||||
const providerThinkingLevels = (() => {
|
||||
@@ -313,6 +329,7 @@ export default function ProviderDetailPage() {
|
||||
const override = (settingsData.providerStrategies || {})[providerId] || {};
|
||||
setProviderStrategy(override.fallbackStrategy || null);
|
||||
setProviderStickyLimit(override.stickyRoundRobinLimit != null ? String(override.stickyRoundRobinLimit) : "1");
|
||||
setStrictModelAssignment(override.strictModelAssignment === true);
|
||||
// Load per-provider thinking config
|
||||
const thinkingCfg = (settingsData.providerThinking || {})[providerId] || {};
|
||||
setThinkingMode(thinkingCfg.mode || "auto");
|
||||
@@ -368,9 +385,13 @@ export default function ProviderDetailPage() {
|
||||
const settingsData = settingsRes.ok ? await settingsRes.json() : {};
|
||||
const current = settingsData.providerStrategies || {};
|
||||
|
||||
// Build override: null strategy means remove override, use global
|
||||
const override = {};
|
||||
// Preserve Freebuff-only settings while changing the shared strategy.
|
||||
const override = { ...(current[providerId] || {}) };
|
||||
if (strategy) override.fallbackStrategy = strategy;
|
||||
else {
|
||||
delete override.fallbackStrategy;
|
||||
delete override.stickyRoundRobinLimit;
|
||||
}
|
||||
if (strategy === "round-robin" && stickyLimit !== "") {
|
||||
override.stickyRoundRobinLimit = Number(stickyLimit) || 3;
|
||||
}
|
||||
@@ -392,6 +413,44 @@ export default function ProviderDetailPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleStrictAssignmentToggle = async (enabled) => {
|
||||
setStrictModelAssignment(enabled);
|
||||
try {
|
||||
const settingsRes = await fetch("/api/settings", { cache: "no-store" });
|
||||
const settingsData = settingsRes.ok ? await settingsRes.json() : {};
|
||||
const current = settingsData.providerStrategies || {};
|
||||
await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
providerStrategies: {
|
||||
...current,
|
||||
[providerId]: { ...(current[providerId] || {}), strictModelAssignment: enabled },
|
||||
},
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error saving Freebuff strict assignment:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelAssignment = async (connectionId, assignedModel) => {
|
||||
try {
|
||||
const res = await fetch(`/api/providers/${connectionId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ providerSpecificData: { assignedModel: assignedModel || null } }),
|
||||
});
|
||||
if (res.ok) {
|
||||
setConnections((prev) => prev.map((c) => c.id === connectionId
|
||||
? { ...c, providerSpecificData: { ...(c.providerSpecificData || {}), assignedModel: assignedModel || null } }
|
||||
: c));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error saving Freebuff model assignment:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRoundRobinToggle = (enabled) => {
|
||||
const strategy = enabled ? "round-robin" : null;
|
||||
const sticky = enabled ? (providerStickyLimit || "1") : providerStickyLimit;
|
||||
@@ -452,10 +511,12 @@ export default function ProviderDetailPage() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchConnections();
|
||||
fetchAliases();
|
||||
fetchCustomModels();
|
||||
fetchDisabledModels();
|
||||
Promise.resolve().then(() => {
|
||||
fetchConnections();
|
||||
fetchAliases();
|
||||
fetchCustomModels();
|
||||
fetchDisabledModels();
|
||||
});
|
||||
}, [fetchConnections, fetchAliases, fetchCustomModels, fetchDisabledModels]);
|
||||
|
||||
// Cursor's model availability is account-specific and changes frequently.
|
||||
@@ -463,13 +524,13 @@ export default function ProviderDetailPage() {
|
||||
// registry remains the fallback while the request is pending or unavailable.
|
||||
useEffect(() => {
|
||||
if (providerId !== "cursor") {
|
||||
setLiveModels([]);
|
||||
queueMicrotask(() => setLiveModels([]));
|
||||
return;
|
||||
}
|
||||
|
||||
const connection = connections.find((item) => item.isActive !== false);
|
||||
if (!connection?.id) {
|
||||
setLiveModels([]);
|
||||
queueMicrotask(() => setLiveModels([]));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -867,7 +928,9 @@ export default function ProviderDetailPage() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedConnectionIds((prev) => prev.filter((id) => connections.some((conn) => conn.id === id)));
|
||||
queueMicrotask(() => {
|
||||
setSelectedConnectionIds((prev) => prev.filter((id) => connections.some((conn) => conn.id === id)));
|
||||
});
|
||||
}, [connections]);
|
||||
|
||||
const selectedProxySummary = (() => {
|
||||
@@ -1014,6 +1077,9 @@ export default function ProviderDetailPage() {
|
||||
}}
|
||||
onDelete={() => handleDelete(conn.id)}
|
||||
oneByOneStatus={oneByOneResults[conn.id] || null}
|
||||
modelAssignmentOptions={assignmentModels}
|
||||
onModelAssignmentChange={(model) => handleModelAssignment(conn.id, model)}
|
||||
strictModelAssignment={strictModelAssignment}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1511,6 +1577,13 @@ export default function ProviderDetailPage() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 border-t border-black/[0.03] pt-2 dark:border-white/[0.03]">
|
||||
<div>
|
||||
<span className="text-xs text-text-muted font-medium">Strict Model Assignment</span>
|
||||
<p className="text-[10px] text-text-muted">Only assigned accounts can serve each model for this provider.</p>
|
||||
</div>
|
||||
<Toggle checked={strictModelAssignment} onChange={handleStrictAssignmentToggle} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -522,6 +522,23 @@ export function parseQuotaData(provider, data) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "freebuff":
|
||||
// Session quotas keyed by model id — label rows with the friendly
|
||||
// displayName (from the registry) and keep modelKey for ordering.
|
||||
if (data.quotas) {
|
||||
Object.entries(data.quotas).forEach(([modelKey, quota]) => {
|
||||
normalizedQuotas.push({
|
||||
name: quota.displayName || modelKey,
|
||||
modelKey,
|
||||
used: quota.used || 0,
|
||||
total: quota.total || 0,
|
||||
resetAt: quota.resetAt || null,
|
||||
recurring: quota.recurring !== false,
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "ollama":
|
||||
// Session (5h) / Weekly (7d) usage % from ollama.com/api/usage.
|
||||
// remainingPercentage only — no absolute remaining (UI treats remaining as %).
|
||||
|
||||
@@ -102,9 +102,24 @@ const OAUTH_TEST_CONFIG = {
|
||||
},
|
||||
refreshable: false,
|
||||
},
|
||||
freebuff: {
|
||||
// The session endpoint doubles as the auth probe: GET never claims a
|
||||
// session (POST would burn 1.0 unit of the daily quota). Mirrors the usage
|
||||
// handler: 401 = bad token, 403 = region/account gate (token still valid),
|
||||
// 404 = no session row yet (pre-join, token valid). No refresh path —
|
||||
// when the authToken dies the user re-logs in.
|
||||
url: "https://www.codebuff.com/api/v1/freebuff/session",
|
||||
method: "GET",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer ",
|
||||
extraHeaders: { Accept: "application/json", "User-Agent": "codebuff-cli/0.0.138" },
|
||||
acceptStatuses: [403, 404],
|
||||
softFailMessage: {
|
||||
403: "Connected, but Freebuff is gated (403) — country blocked or account banned.",
|
||||
},
|
||||
},
|
||||
// Grok CLI / Grok Build — probe /v1/user (no inference quota). Headers mirror official CLI.
|
||||
"grok-cli": {
|
||||
url: PROVIDERS["grok-cli"]?.userUrl || "https://cli-chat-proxy.grok.com/v1/user",
|
||||
"grok-cli": { url: PROVIDERS["grok-cli"]?.userUrl || "https://cli-chat-proxy.grok.com/v1/user",
|
||||
method: "GET",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer ",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { clearPoolUnfit } from "open-sse/services/proxyPoolFitness.js";
|
||||
import { clearPoolUnfit, ensurePoolFitnessHydrated } from "open-sse/services/proxyPoolFitness.js";
|
||||
|
||||
// POST /api/proxy-pools/[id]/fitness/clear
|
||||
// Body: { scope: "provider::model" } — clears the mark for this pool + scope.
|
||||
export async function POST(request, { params }) {
|
||||
try {
|
||||
await ensurePoolFitnessHydrated();
|
||||
const { id } = await params;
|
||||
let body = {};
|
||||
try {
|
||||
@@ -22,4 +23,4 @@ export async function POST(request, { params }) {
|
||||
console.log("Error clearing pool fitness:", error);
|
||||
return NextResponse.json({ error: "Failed to clear pool fitness" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { clearAllPoolUnfit } from "open-sse/services/proxyPoolFitness.js";
|
||||
import { clearAllPoolUnfit, ensurePoolFitnessHydrated } from "open-sse/services/proxyPoolFitness.js";
|
||||
|
||||
// POST /api/proxy-pools/fitness/clear-all
|
||||
// Body: { provider?: string } — clears every mark, or only marks scoped to the
|
||||
// given provider ("provider::*") when provided.
|
||||
export async function POST(request) {
|
||||
try {
|
||||
await ensurePoolFitnessHydrated();
|
||||
let body = {};
|
||||
try {
|
||||
body = await request.json();
|
||||
@@ -19,4 +20,4 @@ export async function POST(request) {
|
||||
console.log("Error clearing proxy fitness:", error);
|
||||
return NextResponse.json({ error: "Failed to clear proxy fitness" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { poolFitnessSnapshot } from "open-sse/services/proxyPoolFitness.js";
|
||||
import { ensurePoolFitnessHydrated, poolFitnessSnapshot } from "open-sse/services/proxyPoolFitness.js";
|
||||
|
||||
// GET /api/proxy-pools/fitness — in-memory snapshot of pool fitness marks.
|
||||
// Returns { pools: { [poolId]: { [scope]: { until, reason } } } }.
|
||||
export async function GET() {
|
||||
try {
|
||||
await ensurePoolFitnessHydrated();
|
||||
return NextResponse.json({ pools: poolFitnessSnapshot() });
|
||||
} catch (error) {
|
||||
console.log("Error reading proxy fitness:", error);
|
||||
return NextResponse.json({ error: "Failed to read proxy fitness" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getProxyPoolById } from "@/models";
|
||||
import { fitPoolIds } from "open-sse/services/proxyPoolFitness.js";
|
||||
import { ensurePoolFitnessHydrated, fitPoolIds } from "open-sse/services/proxyPoolFitness.js";
|
||||
|
||||
// Safely normalize any value into a trimmed string.
|
||||
function normalizeString(value) {
|
||||
@@ -30,8 +30,10 @@ export function pickProxyPoolId(poolIds, strategy, providerId, opts = {}) {
|
||||
if (strategy === "smart" && scope) eligible = fitPoolIds(eligible, scope);
|
||||
|
||||
if (eligible.length === 0) {
|
||||
// Every candidate is unfit/excluded — fall back to the first non-excluded
|
||||
// pool rather than deadlocking; its egress may have recovered.
|
||||
// Freebuff must never reuse a limited-IP egress. Other providers retain
|
||||
// the previous fail-open behavior when every smart candidate is marked
|
||||
// unfit; their executors may have their own pool fallback semantics.
|
||||
if (providerId === "freebuff" && strategy === "smart") return null;
|
||||
eligible = poolIds.filter((id) => !(excludeIds || []).includes(id));
|
||||
if (eligible.length === 0) return null;
|
||||
}
|
||||
@@ -88,6 +90,7 @@ export async function resolveConnectionProxyConfig(
|
||||
excludePoolIds = null
|
||||
) {
|
||||
try {
|
||||
await ensurePoolFitnessHydrated();
|
||||
// Handle new multi-proxy format
|
||||
const proxyPoolIds = providerSpecificData?.proxyPoolIds || [];
|
||||
const proxyRotationStrategy = providerSpecificData?.proxyRotationStrategy || "none";
|
||||
@@ -97,6 +100,8 @@ export async function resolveConnectionProxyConfig(
|
||||
const proxyPoolIdRaw = legacyProxyPoolId === "__none__" ? "" : legacyProxyPoolId;
|
||||
|
||||
const legacy = normalizeLegacyProxy(providerSpecificData);
|
||||
const multiPoolScope = providerSpecificData?.proxyPoolScope || null;
|
||||
let selectedPoolId = null;
|
||||
|
||||
/**
|
||||
* -----------------------------
|
||||
@@ -104,10 +109,9 @@ export async function resolveConnectionProxyConfig(
|
||||
* -----------------------------
|
||||
*/
|
||||
if (proxyPoolIds.length > 0) {
|
||||
const scope = providerSpecificData?.proxyPoolScope || null;
|
||||
const selectedPoolId = pickProxyPoolId(proxyPoolIds, proxyRotationStrategy, connectionId, { scope, excludeIds: excludePoolIds });
|
||||
selectedPoolId = pickProxyPoolId(proxyPoolIds, proxyRotationStrategy, connectionId, { scope: multiPoolScope, excludeIds: excludePoolIds });
|
||||
|
||||
if (selectedPoolId) {
|
||||
if (selectedPoolId) {
|
||||
const proxyPool = await getProxyPoolById(selectedPoolId);
|
||||
const proxyUrl = normalizeString(proxyPool?.proxyUrl);
|
||||
const noProxy = normalizeString(proxyPool?.noProxy);
|
||||
@@ -147,6 +151,22 @@ export async function resolveConnectionProxyConfig(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
!selectedPoolId &&
|
||||
proxyRotationStrategy === "smart" &&
|
||||
multiPoolScope?.startsWith("freebuff::")
|
||||
) {
|
||||
return {
|
||||
source: "pool",
|
||||
proxyPoolId: null,
|
||||
proxyPool: null,
|
||||
noFitPool: true,
|
||||
connectionProxyEnabled: false,
|
||||
connectionProxyUrl: "",
|
||||
connectionNoProxy: "",
|
||||
strictProxy: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------
|
||||
|
||||
@@ -8,6 +8,18 @@ import * as log from "../utils/logger.js";
|
||||
// Mutex to prevent race conditions during account selection
|
||||
let selectionMutex = Promise.resolve();
|
||||
|
||||
export function filterConnectionsForModel(providerId, connections, model, settings = {}) {
|
||||
const override = (settings.providerStrategies || {})[providerId] || {};
|
||||
if (override.strictModelAssignment !== true || !model) {
|
||||
return connections;
|
||||
}
|
||||
return connections.filter((connection) => {
|
||||
const assignedModel = connection.providerSpecificData?.assignedModel
|
||||
|| (providerId === "freebuff" ? connection.providerSpecificData?.freebuffModel : null);
|
||||
return assignedModel === model;
|
||||
});
|
||||
}
|
||||
|
||||
const GITHUB_MONTHLY_USAGE_LIMIT = "you've reached your additional usage limit for your plan";
|
||||
|
||||
function githubMonthlyResetMs(status, errorText, provider) {
|
||||
@@ -81,7 +93,10 @@ export async function getProviderCredentials(provider, excludeConnectionIds = nu
|
||||
};
|
||||
}
|
||||
|
||||
const connections = await getProviderConnections({ provider: providerId, isActive: true });
|
||||
let connections = await getProviderConnections({ provider: providerId, isActive: true });
|
||||
const settings = await getSettings();
|
||||
const providerOverride = (settings.providerStrategies || {})[providerId] || {};
|
||||
connections = filterConnectionsForModel(providerId, connections, model, settings);
|
||||
log.debug("AUTH", `${provider} | total connections: ${connections.length}, excludeIds: ${excludeSet.size > 0 ? [...excludeSet].join(",") : "none"}, model: ${model || "any"}`);
|
||||
|
||||
if (connections.length === 0) {
|
||||
@@ -126,9 +141,7 @@ export async function getProviderCredentials(provider, excludeConnectionIds = nu
|
||||
return null;
|
||||
}
|
||||
|
||||
const settings = await getSettings();
|
||||
// Per-provider strategy overrides global setting
|
||||
const providerOverride = (settings.providerStrategies || {})[providerId] || {};
|
||||
const strategy = providerOverride.fallbackStrategy || settings.fallbackStrategy || "fill-first";
|
||||
|
||||
let connection;
|
||||
@@ -211,6 +224,7 @@ export async function getProviderCredentials(provider, excludeConnectionIds = nu
|
||||
connectionProxyPoolId: resolvedProxy.proxyPoolId || null,
|
||||
vercelRelayUrl: resolvedProxy.vercelRelayUrl || "",
|
||||
proxyPoolId: resolvedProxy.proxyPoolId || null,
|
||||
noFitPool: resolvedProxy.noFitPool === true,
|
||||
strictProxy: resolvedProxy.strictProxy === true,
|
||||
},
|
||||
connectionId: connection.id,
|
||||
|
||||
Reference in new issue
Block a user