forked from boos/BoosAPI
feat(proxy): propagate account-level proxy across all surfaces
- chatSurface/openAiResponsesSurface: extract channelProxyUrl once and pass to withSiteRecordProxyRequestInit - geminiSurface: switch from withSiteProxyRequestInit to withSiteRecordProxyRequestInit with channelProxyUrl - balanceService: pass accountProxyUrl to sub2api token refresh requests - siteProxy: fix isAccountOverride to only be true when account proxy differs from site proxy - Accounts.tsx: use parseAccountExtraConfig helper for proxy badge display
This commit is contained in:
@@ -204,6 +204,7 @@ export async function handleChatSurfaceRequest(
|
|||||||
runtime: endpointRequest.runtime,
|
runtime: endpointRequest.runtime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
const channelProxyUrl = resolveChannelProxyUrl(selected.site, selected.account.extraConfig);
|
||||||
const dispatchRequest = (
|
const dispatchRequest = (
|
||||||
compatibilityRequest: BuiltEndpointRequest,
|
compatibilityRequest: BuiltEndpointRequest,
|
||||||
targetUrl?: string,
|
targetUrl?: string,
|
||||||
@@ -216,7 +217,7 @@ export async function handleChatSurfaceRequest(
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: requestForFetch.headers,
|
headers: requestForFetch.headers,
|
||||||
body: JSON.stringify(requestForFetch.body),
|
body: JSON.stringify(requestForFetch.body),
|
||||||
}),
|
}, channelProxyUrl),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const endpointStrategy = downstreamTransformer.compatibility.createEndpointStrategy({
|
const endpointStrategy = downstreamTransformer.compatibility.createEndpointStrategy({
|
||||||
@@ -265,7 +266,6 @@ export async function handleChatSurfaceRequest(
|
|||||||
try {
|
try {
|
||||||
const endpointResult = await executeEndpointFlow({
|
const endpointResult = await executeEndpointFlow({
|
||||||
siteUrl: selected.site.url,
|
siteUrl: selected.site.url,
|
||||||
proxyUrl: resolveChannelProxyUrl(selected.site, selected.account.extraConfig),
|
|
||||||
endpointCandidates,
|
endpointCandidates,
|
||||||
buildRequest: (endpoint) => buildEndpointRequest(endpoint),
|
buildRequest: (endpoint) => buildEndpointRequest(endpoint),
|
||||||
dispatchRequest,
|
dispatchRequest,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { isModelAllowedByPolicyOrAllowedRoutes } from '../../services/downstream
|
|||||||
import { tokenRouter } from '../../services/tokenRouter.js';
|
import { tokenRouter } from '../../services/tokenRouter.js';
|
||||||
import { buildOauthProviderHeaders } from '../../services/oauth/service.js';
|
import { buildOauthProviderHeaders } from '../../services/oauth/service.js';
|
||||||
import { getOauthInfoFromExtraConfig } from '../../services/oauth/oauthAccount.js';
|
import { getOauthInfoFromExtraConfig } from '../../services/oauth/oauthAccount.js';
|
||||||
import { withSiteProxyRequestInit } from '../../services/siteProxy.js';
|
import { resolveChannelProxyUrl, withSiteRecordProxyRequestInit } from '../../services/siteProxy.js';
|
||||||
import { refreshModelsAndRebuildRoutes } from '../../services/modelService.js';
|
import { refreshModelsAndRebuildRoutes } from '../../services/modelService.js';
|
||||||
import { getDownstreamRoutingPolicy } from '../../routes/proxy/downstreamPolicy.js';
|
import { getDownstreamRoutingPolicy } from '../../routes/proxy/downstreamPolicy.js';
|
||||||
import { executeEndpointFlow, type BuiltEndpointRequest } from '../../routes/proxy/endpointFlow.js';
|
import { executeEndpointFlow, type BuiltEndpointRequest } from '../../routes/proxy/endpointFlow.js';
|
||||||
@@ -470,6 +470,7 @@ export async function geminiProxyRoute(app: FastifyInstance) {
|
|||||||
selected.tokenValue,
|
selected.tokenValue,
|
||||||
query,
|
query,
|
||||||
);
|
);
|
||||||
|
const channelProxyUrl = resolveChannelProxyUrl(selected.site, selected.account.extraConfig);
|
||||||
const upstream = isInternalGemini
|
const upstream = isInternalGemini
|
||||||
? await dispatchRuntimeRequest({
|
? await dispatchRuntimeRequest({
|
||||||
siteUrl: selected.site.url,
|
siteUrl: selected.site.url,
|
||||||
@@ -489,11 +490,11 @@ export async function geminiProxyRoute(app: FastifyInstance) {
|
|||||||
: (isStreamAction ? 'streamGenerateContent' : 'generateContent'),
|
: (isStreamAction ? 'streamGenerateContent' : 'generateContent'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
buildInit: async (requestUrl, requestForFetch) => withSiteProxyRequestInit(requestUrl, {
|
buildInit: async (_requestUrl, requestForFetch) => withSiteRecordProxyRequestInit(selected.site, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: requestForFetch.headers,
|
headers: requestForFetch.headers,
|
||||||
body: JSON.stringify(requestForFetch.body),
|
body: JSON.stringify(requestForFetch.body),
|
||||||
}),
|
}, channelProxyUrl),
|
||||||
})
|
})
|
||||||
: await fetch(targetUrl, {
|
: await fetch(targetUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -741,16 +742,17 @@ export async function geminiProxyRoute(app: FastifyInstance) {
|
|||||||
runtime: endpointRequest.runtime,
|
runtime: endpointRequest.runtime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
const channelProxyUrl = resolveChannelProxyUrl(selected.site, selected.account.extraConfig);
|
||||||
const dispatchRequest = (compatibilityRequest: BuiltEndpointRequest, targetUrl?: string) => (
|
const dispatchRequest = (compatibilityRequest: BuiltEndpointRequest, targetUrl?: string) => (
|
||||||
dispatchRuntimeRequest({
|
dispatchRuntimeRequest({
|
||||||
siteUrl: selected.site.url,
|
siteUrl: selected.site.url,
|
||||||
targetUrl,
|
targetUrl,
|
||||||
request: compatibilityRequest,
|
request: compatibilityRequest,
|
||||||
buildInit: async (requestUrl, requestForFetch) => withSiteProxyRequestInit(requestUrl, {
|
buildInit: async (_requestUrl, requestForFetch) => withSiteRecordProxyRequestInit(selected.site, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: requestForFetch.headers,
|
headers: requestForFetch.headers,
|
||||||
body: JSON.stringify(requestForFetch.body),
|
body: JSON.stringify(requestForFetch.body),
|
||||||
}),
|
}, channelProxyUrl),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const endpointStrategy = createChatEndpointStrategy({
|
const endpointStrategy = createChatEndpointStrategy({
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ export async function handleOpenAiResponsesSurfaceRequest(
|
|||||||
runtime: endpointRequest.runtime,
|
runtime: endpointRequest.runtime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
const channelProxyUrl = resolveChannelProxyUrl(selected.site, selected.account.extraConfig);
|
||||||
const dispatchRequest = (compatibilityRequest: BuiltEndpointRequest, targetUrl?: string) => (
|
const dispatchRequest = (compatibilityRequest: BuiltEndpointRequest, targetUrl?: string) => (
|
||||||
dispatchRuntimeRequest({
|
dispatchRuntimeRequest({
|
||||||
siteUrl: selected.site.url,
|
siteUrl: selected.site.url,
|
||||||
@@ -313,7 +314,7 @@ export async function handleOpenAiResponsesSurfaceRequest(
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: requestForFetch.headers,
|
headers: requestForFetch.headers,
|
||||||
body: JSON.stringify(requestForFetch.body),
|
body: JSON.stringify(requestForFetch.body),
|
||||||
}),
|
}, channelProxyUrl),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const endpointStrategy = openAiResponsesTransformer.compatibility.createEndpointStrategy({
|
const endpointStrategy = openAiResponsesTransformer.compatibility.createEndpointStrategy({
|
||||||
@@ -355,7 +356,6 @@ export async function handleOpenAiResponsesSurfaceRequest(
|
|||||||
try {
|
try {
|
||||||
const endpointResult = await executeEndpointFlow({
|
const endpointResult = await executeEndpointFlow({
|
||||||
siteUrl: selected.site.url,
|
siteUrl: selected.site.url,
|
||||||
proxyUrl: resolveChannelProxyUrl(selected.site, selected.account.extraConfig),
|
|
||||||
endpointCandidates,
|
endpointCandidates,
|
||||||
buildRequest: (endpoint) => buildEndpointRequest(endpoint),
|
buildRequest: (endpoint) => buildEndpointRequest(endpoint),
|
||||||
dispatchRequest,
|
dispatchRequest,
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ async function refreshSub2ApiManagedSession(params: {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||||
}));
|
}, getProxyUrlFromExtraConfig(params.account.extraConfig)));
|
||||||
payload = await response.json().catch(() => null);
|
payload = await response.json().catch(() => null);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw new Error(err?.message || 'sub2api token refresh request failed');
|
throw new Error(err?.message || 'sub2api token refresh request failed');
|
||||||
@@ -382,23 +382,23 @@ export async function refreshBalance(accountId: number) {
|
|||||||
let activeExtraConfig = account.extraConfig;
|
let activeExtraConfig = account.extraConfig;
|
||||||
let balanceInfo: BalanceInfo | null = null;
|
let balanceInfo: BalanceInfo | null = null;
|
||||||
|
|
||||||
|
const accountProxyUrl = getProxyUrlFromExtraConfig(account.extraConfig);
|
||||||
|
|
||||||
if (isSub2ApiPlatform(site.platform)) {
|
if (isSub2ApiPlatform(site.platform)) {
|
||||||
const managedAuth = getSub2ApiAuthFromExtraConfig(activeExtraConfig);
|
const managedAuth = getSub2ApiAuthFromExtraConfig(activeExtraConfig);
|
||||||
if (managedAuth?.refreshToken && isNearTokenExpiry(managedAuth.tokenExpiresAt)) {
|
if (managedAuth?.refreshToken && isNearTokenExpiry(managedAuth.tokenExpiresAt)) {
|
||||||
try {
|
try {
|
||||||
const refreshed = await refreshSub2ApiManagedSession({
|
const refreshed = await withAccountProxyOverride(accountProxyUrl, () => refreshSub2ApiManagedSession({
|
||||||
account,
|
account,
|
||||||
site,
|
site,
|
||||||
currentAccessToken: activeAccessToken,
|
currentAccessToken: activeAccessToken,
|
||||||
currentExtraConfig: activeExtraConfig,
|
currentExtraConfig: activeExtraConfig,
|
||||||
});
|
}));
|
||||||
activeAccessToken = refreshed.accessToken;
|
activeAccessToken = refreshed.accessToken;
|
||||||
activeExtraConfig = refreshed.extraConfig;
|
activeExtraConfig = refreshed.extraConfig;
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountProxyUrl = getProxyUrlFromExtraConfig(account.extraConfig);
|
|
||||||
const readBalance = async (token: string) => withAccountProxyOverride(accountProxyUrl,
|
const readBalance = async (token: string) => withAccountProxyOverride(accountProxyUrl,
|
||||||
() => adapter.getBalance(site.url, token, platformUserId));
|
() => adapter.getBalance(site.url, token, platformUserId));
|
||||||
const handleBalanceError = async (err: any) => {
|
const handleBalanceError = async (err: any) => {
|
||||||
|
|||||||
@@ -470,8 +470,10 @@ export function withSiteRecordProxyRequestInit(
|
|||||||
nextOptions.headers = mergedHeaders;
|
nextOptions.headers = mergedHeaders;
|
||||||
}
|
}
|
||||||
const accountNormalized = normalizeSiteProxyUrl(accountProxyUrl);
|
const accountNormalized = normalizeSiteProxyUrl(accountProxyUrl);
|
||||||
const proxyUrl = accountNormalized || resolveProxyUrlForSite(site);
|
const siteProxyUrl = resolveProxyUrlForSite(site);
|
||||||
return withExplicitProxyRequestInit(proxyUrl, nextOptions, !!accountNormalized);
|
const proxyUrl = accountNormalized || siteProxyUrl;
|
||||||
|
const isAccountOverride = !!accountNormalized && accountNormalized !== siteProxyUrl;
|
||||||
|
return withExplicitProxyRequestInit(proxyUrl, nextOptions, isAccountOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveChannelProxyUrl(
|
export function resolveChannelProxyUrl(
|
||||||
|
|||||||
+18
-22
@@ -653,25 +653,23 @@ export default function Accounts() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseAccountExtraConfig = (account: any): Record<string, any> => {
|
||||||
|
try { return JSON.parse(account?.extraConfig || '{}') || {}; }
|
||||||
|
catch { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
const extractManagedSub2ApiAuth = (account: any) => {
|
const extractManagedSub2ApiAuth = (account: any) => {
|
||||||
try {
|
const parsed = parseAccountExtraConfig(account);
|
||||||
const parsed = JSON.parse(account?.extraConfig || '{}');
|
const auth = parsed?.sub2apiAuth || {};
|
||||||
const auth = parsed?.sub2apiAuth || {};
|
return {
|
||||||
return {
|
refreshToken: typeof auth.refreshToken === 'string' ? auth.refreshToken : '',
|
||||||
refreshToken: typeof auth.refreshToken === 'string' ? auth.refreshToken : '',
|
tokenExpiresAt: auth.tokenExpiresAt ? String(auth.tokenExpiresAt) : '',
|
||||||
tokenExpiresAt: auth.tokenExpiresAt ? String(auth.tokenExpiresAt) : '',
|
};
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return { refreshToken: '', tokenExpiresAt: '' };
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const openEditPanel = (account: any) => {
|
const openEditPanel = (account: any) => {
|
||||||
const managedAuth = extractManagedSub2ApiAuth(account);
|
const managedAuth = extractManagedSub2ApiAuth(account);
|
||||||
const proxyUrl = (() => {
|
const proxyUrl = parseAccountExtraConfig(account)?.proxyUrl || '';
|
||||||
try { return JSON.parse(account?.extraConfig || '{}')?.proxyUrl || ''; }
|
|
||||||
catch { return ''; }
|
|
||||||
})();
|
|
||||||
closeAddPanel();
|
closeAddPanel();
|
||||||
setRebindTarget(null);
|
setRebindTarget(null);
|
||||||
setEditingAccount(account);
|
setEditingAccount(account);
|
||||||
@@ -793,12 +791,10 @@ export default function Accounts() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const extractPlatformUserId = (account: any): string => {
|
const extractPlatformUserId = (account: any): string => {
|
||||||
try {
|
const parsed = parseAccountExtraConfig(account);
|
||||||
const parsed = JSON.parse(account?.extraConfig || '{}');
|
const raw = parsed?.platformUserId;
|
||||||
const raw = parsed?.platformUserId;
|
const value = Number.parseInt(String(raw ?? ''), 10);
|
||||||
const value = Number.parseInt(String(raw ?? ''), 10);
|
if (Number.isFinite(value) && value > 0) return String(value);
|
||||||
if (Number.isFinite(value) && value > 0) return String(value);
|
|
||||||
} catch { }
|
|
||||||
const guessed = Number.parseInt(String(account?.username || '').match(/(\d{3,8})$/)?.[1] || '', 10);
|
const guessed = Number.parseInt(String(account?.username || '').match(/(\d{3,8})$/)?.[1] || '', 10);
|
||||||
return Number.isFinite(guessed) && guessed > 0 ? String(guessed) : '';
|
return Number.isFinite(guessed) && guessed > 0 ? String(guessed) : '';
|
||||||
};
|
};
|
||||||
@@ -1605,7 +1601,7 @@ export default function Accounts() {
|
|||||||
<span className={`badge ${connectionMode === 'apikey' ? 'badge-warning' : 'badge-info'}`} style={{ fontSize: 10 }}>
|
<span className={`badge ${connectionMode === 'apikey' ? 'badge-warning' : 'badge-info'}`} style={{ fontSize: 10 }}>
|
||||||
{connectionMode === 'apikey' ? 'API Key' : 'Session'}
|
{connectionMode === 'apikey' ? 'API Key' : 'Session'}
|
||||||
</span>
|
</span>
|
||||||
{(() => { try { return JSON.parse(a.extraConfig || '{}')?.proxyUrl; } catch { return null; } })() && (
|
{parseAccountExtraConfig(a)?.proxyUrl && (
|
||||||
<span className="badge badge-purple" style={{ fontSize: 10 }}>代理</span>
|
<span className="badge badge-purple" style={{ fontSize: 10 }}>代理</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1818,7 +1814,7 @@ export default function Accounts() {
|
|||||||
<span className={`badge ${connectionMode === 'apikey' ? 'badge-warning' : 'badge-info'}`} style={{ fontSize: 10 }}>
|
<span className={`badge ${connectionMode === 'apikey' ? 'badge-warning' : 'badge-info'}`} style={{ fontSize: 10 }}>
|
||||||
{connectionMode === 'apikey' ? 'API Key' : 'Session'}
|
{connectionMode === 'apikey' ? 'API Key' : 'Session'}
|
||||||
</span>
|
</span>
|
||||||
{(() => { try { return JSON.parse(a.extraConfig || '{}')?.proxyUrl; } catch { return null; } })() && (
|
{parseAccountExtraConfig(a)?.proxyUrl && (
|
||||||
<span className="badge badge-purple" style={{ fontSize: 10 }}>代理</span>
|
<span className="badge badge-purple" style={{ fontSize: 10 }}>代理</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user