grok2api / frontend /src /features /settings /settings-api.ts
fromozuzhouzzz
Deploy grok2api v3.0.11 to HF Spaces
f1dd159
Raw
History Blame Contribute Delete
24 kB
import { apiRequest } from "@/shared/api/client";
import { createObjectDecoder, decodeBooleanResult, hasShape, isArrayOf, isBoolean, isNumber, isOneOf, isOptional, isRecordOf, isString } from "@/shared/api/decoder";
import type { SortOrder } from "@/shared/lib/table-sort";
export type SettingsConfigDTO = {
server: { maxConcurrentRequests: number };
providerBuild: { baseURL: string; fallbackBaseURL: string; clientVersion: string; clientIdentifier: string; tokenAuth: string; tokenAuthConfigured: boolean; userAgent: string; responseHeaderTimeout: string };
providerWeb: {
baseURL: string; quotaTimeout: string; chatTimeout: string; imageTimeout: string; videoTimeout: string;
statsigMode: "manual" | "url"; statsigManualValue?: string; statsigManualConfigured: boolean; statsigSignerURL: string;
clearanceMode: "manual" | "flaresolverr"; flareSolverrURL: string; clearanceTimeout: string; clearanceRefresh: string;
mediaConcurrency: number; allowNSFW: boolean;
recoveryBackoffBase: string; recoveryBackoffMax: string;
};
providerConsole: { baseURL: string; chatTimeout: string };
batch: { importConcurrency: number; conversionConcurrency: number; syncConcurrency: number; refreshConcurrency: number; randomDelay: string };
media: {
maxImageBytes: number; maxTotalBytes: number; cleanupThresholdPercent: number;
cleanupInterval: string;
};
frontend: { publicApiBaseURL: string };
routing: {
stickyTTL: string; cooldownBase: string; cooldownMax: string; capacityWait: string; maxAttempts: number; preferFreeBuild: boolean; markBuildChatDeniedAsReauth: boolean;
segmentedSelector: { enabled: boolean; minCandidates: number; windowSize: number };
};
audit: { bufferSize: number; batchSize: number; flushInterval: string; commitDelayMS: number };
clientKeyDefaults: { rpmLimit: number; maxConcurrent: number };
accounts: {
markBuildForbiddenReauth: boolean;
buildForbiddenReauthCodes: string[];
autoCleanReauthEnabled: boolean;
autoCleanReauthInterval: string;
autoCleanReauthMinAge: string;
autoCleanIncludeDisabled: boolean;
};
};
export type EgressNodeDTO = {
id: string; name: string; scope: EgressScope; enabled: boolean;
proxyConfigured: boolean; userAgent: string; cookieConfigured: boolean;
accountBoundProxy: boolean; proxyPool: boolean;
sourceId?: string; accountCapacity: number; assignedAccountCount: number;
health: number; failureCount: number; cooldownUntil?: string; lastError?: string;
probeStatus: "unknown" | "healthy" | "unhealthy"; lastProbedAt?: string; probeLatencyMs: number; exitIp?: string; probeError?: string;
probeProvider?: "ipinfo" | "cloudflare";
ipv4Probe: EgressIPProbeDTO; ipv6Probe: EgressIPProbeDTO;
};
export type EgressNodeInput = {
name: string; scope: EgressScope; enabled: boolean; proxyPool: boolean; proxyURL?: string;
accountCapacity: number; clearProxyURL?: boolean; userAgent: string; cloudflareCookies?: string; clearCookies?: boolean;
};
export type EgressScope = "grok_build" | "grok_web" | "grok_console" | "grok_web_asset";
export type EgressFallbackMode = "none" | "direct" | "fixed";
export type EgressFallbackConfigDTO = { mode: EgressFallbackMode; nodeId?: string };
export type EgressNodeListDTO = {
items: EgressNodeDTO[];
page: number;
pageSize: number;
total: number;
defaultUserAgents: Record<EgressScope, string>;
};
export type EgressSourceDTO = {
id: string; name: string; scope: EgressScope; enabled: boolean; urlConfigured: boolean;
refreshIntervalSeconds: number; defaultAccountCapacity: number;
lastSyncedAt?: string; nextSyncAt?: string; lastSyncImported: number; lastSyncError?: string;
};
export type EgressSourceListDTO = {
items: EgressSourceDTO[];
page: number;
pageSize: number;
total: number;
};
export type EgressSourceInput = {
name: string; scope: EgressScope; enabled: boolean; url?: string; clearUrl?: boolean;
refreshIntervalSeconds: number; defaultAccountCapacity: number;
};
export type EgressOperationsConfigDTO = {
probeProvider: "ipinfo" | "cloudflare"; probeIntervalSeconds: number; autoAssignEnabled: boolean; autoBalanceEnabled: boolean;
assignmentIntervalSeconds: number; fallbacks: Record<EgressScope, EgressFallbackConfigDTO>; updatedAt: string;
};
export type EgressImportResultDTO = { imported: number; skipped: number };
export type EgressIPProbeDTO = { status: "unknown" | "healthy" | "unhealthy"; testedAt?: string; latencyMs: number; exitIp?: string; error?: string };
export type EgressProbeResultDTO = { status: "unknown" | "healthy" | "unhealthy"; testedAt: string; latencyMs: number; exitIp?: string; error?: string; probeProvider?: "ipinfo" | "cloudflare"; ipv4: EgressIPProbeDTO; ipv6: EgressIPProbeDTO };
export type EgressProbeBatchResultDTO = { requested: number; healthy: number; unhealthy: number };
export type EgressRebalanceResultDTO = { assigned: number; rebalanced: number; unplaced: number };
export type EgressUnhealthyCleanupPreviewDTO = { nodes: number; boundAccounts: number; subscriptionManaged: number };
export type SettingsSnapshotDTO = {
config: SettingsConfigDTO;
recommendedProviderBuild: { clientVersion: string; userAgent: string };
updatedAt: string;
revision: string;
restartRequired: string[];
};
const settingsConfigValidator = hasShape({
server: hasShape({ maxConcurrentRequests: isNumber }),
providerBuild: hasShape({ baseURL: isString, fallbackBaseURL: isString, clientVersion: isString, clientIdentifier: isString, tokenAuth: isString, tokenAuthConfigured: isBoolean, userAgent: isString, responseHeaderTimeout: isString }),
providerWeb: hasShape({
baseURL: isString, quotaTimeout: isString, chatTimeout: isString, imageTimeout: isString, videoTimeout: isString,
statsigMode: isOneOf("manual", "url"), statsigManualValue: isOptional(isString), statsigManualConfigured: isBoolean,
statsigSignerURL: isString, clearanceMode: isOneOf("manual", "flaresolverr"), flareSolverrURL: isString,
clearanceTimeout: isString, clearanceRefresh: isString, mediaConcurrency: isNumber, allowNSFW: isBoolean, recoveryBackoffBase: isString, recoveryBackoffMax: isString,
}),
providerConsole: hasShape({ baseURL: isString, chatTimeout: isString }),
batch: hasShape({ importConcurrency: isNumber, conversionConcurrency: isNumber, syncConcurrency: isNumber, refreshConcurrency: isNumber, randomDelay: isString }),
media: hasShape({ maxImageBytes: isNumber, maxTotalBytes: isNumber, cleanupThresholdPercent: isNumber, cleanupInterval: isString }),
frontend: hasShape({ publicApiBaseURL: isString }),
routing: hasShape({
stickyTTL: isString, cooldownBase: isString, cooldownMax: isString, capacityWait: isString, maxAttempts: isNumber, preferFreeBuild: isBoolean, markBuildChatDeniedAsReauth: isBoolean,
segmentedSelector: isOptional(hasShape({ enabled: isBoolean, minCandidates: isNumber, windowSize: isNumber })),
}),
audit: hasShape({ bufferSize: isNumber, batchSize: isNumber, flushInterval: isString, commitDelayMS: isOptional(isNumber) }),
clientKeyDefaults: hasShape({ rpmLimit: isNumber, maxConcurrent: isNumber }),
// Older backends may omit accounts; withSettingsDefaults supplies a safe local default.
accounts: isOptional(hasShape({
markBuildForbiddenReauth: isOptional(isBoolean),
buildForbiddenReauthCodes: isOptional(isArrayOf(isString)),
autoCleanReauthEnabled: isBoolean,
autoCleanReauthInterval: isString,
autoCleanReauthMinAge: isString,
autoCleanIncludeDisabled: isBoolean,
})),
});
const defaultAccountsConfig = (): SettingsConfigDTO["accounts"] => ({
markBuildForbiddenReauth: false,
buildForbiddenReauthCodes: ["permission-denied"],
autoCleanReauthEnabled: false,
autoCleanReauthInterval: "10m",
autoCleanReauthMinAge: "1h",
autoCleanIncludeDisabled: false,
});
function withSettingsDefaults(snapshot: SettingsSnapshotDTO): SettingsSnapshotDTO {
const accounts = snapshot.config.accounts ?? defaultAccountsConfig();
const segmentedSelector = snapshot.config.routing.segmentedSelector ?? { enabled: false, minCandidates: 3000, windowSize: 64 };
return {
...snapshot,
config: {
...snapshot.config,
audit: {
...snapshot.config.audit,
commitDelayMS: snapshot.config.audit.commitDelayMS ?? 5,
},
routing: {
...snapshot.config.routing,
markBuildChatDeniedAsReauth: snapshot.config.routing.markBuildChatDeniedAsReauth ?? false,
segmentedSelector: {
enabled: segmentedSelector.enabled ?? false,
minCandidates: segmentedSelector.minCandidates || 3000,
windowSize: segmentedSelector.windowSize || 64,
},
},
accounts: {
markBuildForbiddenReauth: accounts.markBuildForbiddenReauth ?? false,
buildForbiddenReauthCodes: accounts.buildForbiddenReauthCodes ?? ["permission-denied"],
autoCleanReauthEnabled: accounts.autoCleanReauthEnabled ?? false,
autoCleanReauthInterval: accounts.autoCleanReauthInterval || "10m",
autoCleanReauthMinAge: accounts.autoCleanReauthMinAge || "1h",
autoCleanIncludeDisabled: accounts.autoCleanIncludeDisabled ?? false,
},
},
};
}
const decodeSettingsSnapshotRaw = createObjectDecoder<SettingsSnapshotDTO>("settings", {
config: settingsConfigValidator,
recommendedProviderBuild: hasShape({ clientVersion: isString, userAgent: isString }),
updatedAt: isString,
revision: isString,
restartRequired: isArrayOf(isString),
});
const decodeSettingsSnapshot = (value: unknown) => withSettingsDefaults(decodeSettingsSnapshotRaw(value));
const egressIPProbeValidator = hasShape({
status: isOneOf("unknown", "healthy", "unhealthy"), testedAt: isOptional(isString), latencyMs: isNumber, exitIp: isOptional(isString), error: isOptional(isString),
});
type EgressNodeWireDTO = Omit<EgressNodeDTO, "ipv4Probe" | "ipv6Probe"> & { ipv4Probe?: EgressIPProbeDTO; ipv6Probe?: EgressIPProbeDTO };
type EgressOperationsConfigWireDTO = Omit<EgressOperationsConfigDTO, "probeProvider"> & { probeProvider?: "ipinfo" | "cloudflare" };
type EgressProbeResultWireDTO = Omit<EgressProbeResultDTO, "ipv4" | "ipv6"> & { ipv4?: EgressIPProbeDTO; ipv6?: EgressIPProbeDTO };
const unknownEgressIPProbe = (): EgressIPProbeDTO => ({ status: "unknown", latencyMs: 0 });
const withEgressNodeProbeDefaults = (value: EgressNodeWireDTO): EgressNodeDTO => ({
...value,
ipv4Probe: value.ipv4Probe ?? unknownEgressIPProbe(),
ipv6Probe: value.ipv6Probe ?? unknownEgressIPProbe(),
});
const egressNodeValidator = hasShape({
id: isString, name: isString, scope: isOneOf("grok_build", "grok_web", "grok_console", "grok_web_asset"), enabled: isBoolean,
proxyConfigured: isBoolean, userAgent: isString, cookieConfigured: isBoolean, accountBoundProxy: isBoolean, proxyPool: isBoolean, health: isNumber, failureCount: isNumber,
sourceId: isOptional(isString), accountCapacity: isNumber, assignedAccountCount: isNumber,
probeStatus: isOneOf("unknown", "healthy", "unhealthy"), lastProbedAt: isOptional(isString), probeLatencyMs: isNumber, exitIp: isOptional(isString), probeError: isOptional(isString), probeProvider: isOptional(isOneOf("ipinfo", "cloudflare")),
ipv4Probe: isOptional(egressIPProbeValidator), ipv6Probe: isOptional(egressIPProbeValidator),
cooldownUntil: isOptional(isString), lastError: isOptional(isString),
});
const decodeEgressNodeRaw = createObjectDecoder<EgressNodeWireDTO>("egress node", {
id: isString, name: isString, scope: isOneOf("grok_build", "grok_web", "grok_console", "grok_web_asset"), enabled: isBoolean,
proxyConfigured: isBoolean, userAgent: isString, cookieConfigured: isBoolean, accountBoundProxy: isBoolean, proxyPool: isBoolean, health: isNumber, failureCount: isNumber,
sourceId: isOptional(isString), accountCapacity: isNumber, assignedAccountCount: isNumber,
probeStatus: isOneOf("unknown", "healthy", "unhealthy"), lastProbedAt: isOptional(isString), probeLatencyMs: isNumber, exitIp: isOptional(isString), probeError: isOptional(isString), probeProvider: isOptional(isOneOf("ipinfo", "cloudflare")),
ipv4Probe: isOptional(egressIPProbeValidator), ipv6Probe: isOptional(egressIPProbeValidator),
cooldownUntil: isOptional(isString), lastError: isOptional(isString),
});
const decodeEgressNode = (value: unknown) => withEgressNodeProbeDefaults(decodeEgressNodeRaw(value));
type EgressNodeListWireDTO = {
items: EgressNodeWireDTO[];
page?: number;
pageSize?: number;
total?: number;
defaultUserAgents: Record<EgressScope, string>;
};
const decodeEgressNodeListRaw = createObjectDecoder<EgressNodeListWireDTO>("egress node list", {
items: isArrayOf(egressNodeValidator),
page: isOptional(isNumber),
pageSize: isOptional(isNumber),
total: isOptional(isNumber),
defaultUserAgents: hasShape({ grok_build: isString, grok_web: isString, grok_console: isString, grok_web_asset: isString }),
});
const decodeEgressNodeList = (value: unknown): EgressNodeListDTO => {
const decoded = decodeEgressNodeListRaw(value);
return {
...decoded,
items: decoded.items.map(withEgressNodeProbeDefaults),
page: decoded.page ?? 1,
pageSize: decoded.pageSize ?? Math.max(20, decoded.items.length),
total: decoded.total ?? decoded.items.length,
};
};
const egressSourceValidator = hasShape({
id: isString, name: isString, scope: isOneOf("grok_build", "grok_web", "grok_console", "grok_web_asset"), enabled: isBoolean, urlConfigured: isBoolean,
refreshIntervalSeconds: isNumber, defaultAccountCapacity: isNumber, lastSyncedAt: isOptional(isString), nextSyncAt: isOptional(isString),
lastSyncImported: isNumber, lastSyncError: isOptional(isString),
});
const decodeEgressSource = createObjectDecoder<EgressSourceDTO>("egress source", {
id: isString, name: isString, scope: isOneOf("grok_build", "grok_web", "grok_console", "grok_web_asset"), enabled: isBoolean, urlConfigured: isBoolean,
refreshIntervalSeconds: isNumber, defaultAccountCapacity: isNumber, lastSyncedAt: isOptional(isString), nextSyncAt: isOptional(isString),
lastSyncImported: isNumber, lastSyncError: isOptional(isString),
});
type EgressSourceListWireDTO = {
items: EgressSourceDTO[];
page?: number;
pageSize?: number;
total?: number;
};
const decodeEgressSourceListRaw = createObjectDecoder<EgressSourceListWireDTO>("egress source list", {
items: isArrayOf(egressSourceValidator), page: isOptional(isNumber), pageSize: isOptional(isNumber), total: isOptional(isNumber),
});
const decodeEgressSourceList = (value: unknown): EgressSourceListDTO => {
const decoded = decodeEgressSourceListRaw(value);
return {
...decoded,
page: decoded.page ?? 1,
pageSize: decoded.pageSize ?? Math.max(20, decoded.items.length),
total: decoded.total ?? decoded.items.length,
};
};
const decodeEgressImportResult = createObjectDecoder<EgressImportResultDTO>("egress import result", { imported: isNumber, skipped: isNumber });
const decodeEgressProbeBatchResult = createObjectDecoder<EgressProbeBatchResultDTO>("egress probe result", { requested: isNumber, healthy: isNumber, unhealthy: isNumber });
const decodeEgressRebalanceResult = createObjectDecoder<EgressRebalanceResultDTO>("egress rebalance result", { assigned: isNumber, rebalanced: isNumber, unplaced: isNumber });
const egressFallbackConfigValidator = hasShape({ mode: isOneOf("none", "direct", "fixed"), nodeId: isOptional(isString) });
const decodeEgressOperationsConfigRaw = createObjectDecoder<EgressOperationsConfigWireDTO>("egress operations config", {
probeProvider: isOptional(isOneOf("ipinfo", "cloudflare")), probeIntervalSeconds: isNumber, autoAssignEnabled: isBoolean, autoBalanceEnabled: isBoolean, assignmentIntervalSeconds: isNumber,
fallbacks: isRecordOf(egressFallbackConfigValidator), updatedAt: isString,
});
const decodeEgressOperationsConfig = (value: unknown): EgressOperationsConfigDTO => {
const decoded = decodeEgressOperationsConfigRaw(value);
return { ...decoded, probeProvider: decoded.probeProvider ?? "cloudflare" };
};
const decodeEgressProbeResultRaw = createObjectDecoder<EgressProbeResultWireDTO>("egress probe", {
status: isOneOf("unknown", "healthy", "unhealthy"), testedAt: isString, latencyMs: isNumber, exitIp: isOptional(isString), error: isOptional(isString), probeProvider: isOptional(isOneOf("ipinfo", "cloudflare")),
ipv4: isOptional(egressIPProbeValidator), ipv6: isOptional(egressIPProbeValidator),
});
const decodeEgressProbeResult = (value: unknown): EgressProbeResultDTO => {
const decoded = decodeEgressProbeResultRaw(value);
return { ...decoded, ipv4: decoded.ipv4 ?? unknownEgressIPProbe(), ipv6: decoded.ipv6 ?? unknownEgressIPProbe() };
};
export function getSettings(): Promise<SettingsSnapshotDTO> {
return apiRequest("/api/admin/v1/settings", {}, decodeSettingsSnapshot);
}
export function updateSettings(revision: string, config: SettingsConfigDTO): Promise<SettingsSnapshotDTO> {
return apiRequest("/api/admin/v1/settings", { method: "PUT", body: { revision, config } }, decodeSettingsSnapshot);
}
type ListEgressNodesInput = {
page?: number;
pageSize?: number;
search?: string;
scope?: EgressScope | "";
enabled?: string;
probe?: string;
assignment?: string;
sortBy?: string;
sortOrder?: SortOrder;
};
export function listEgressNodes(input: ListEgressNodesInput = {}): Promise<EgressNodeListDTO> {
const query = new URLSearchParams({ page: String(input.page ?? 1), pageSize: String(input.pageSize ?? 20) });
if (input.search) query.set("search", input.search);
if (input.scope) query.set("scope", input.scope);
if (input.enabled) query.set("enabled", input.enabled);
if (input.probe) query.set("probe", input.probe);
if (input.assignment) query.set("assignment", input.assignment);
if (input.sortBy && input.sortOrder) {
query.set("sortBy", input.sortBy);
query.set("sortOrder", input.sortOrder);
}
return apiRequest(`/api/admin/v1/egress-nodes?${query}`, {}, decodeEgressNodeList);
}
export async function listAllEgressNodes(input: Omit<ListEgressNodesInput, "page" | "pageSize"> = {}): Promise<EgressNodeListDTO> {
const pageSize = 2000;
const first = await listEgressNodes({ ...input, page: 1, pageSize });
const items = [...first.items];
for (let page = 2; items.length < first.total; page += 1) {
const next = await listEgressNodes({ ...input, page, pageSize });
if (next.items.length === 0) break;
items.push(...next.items);
}
return { ...first, items, page: 1, pageSize, total: items.length };
}
export function createEgressNode(input: EgressNodeInput): Promise<EgressNodeDTO> {
return apiRequest("/api/admin/v1/egress-nodes", { method: "POST", body: input }, decodeEgressNode);
}
export function updateEgressNode(id: string, input: EgressNodeInput): Promise<EgressNodeDTO> {
return apiRequest(`/api/admin/v1/egress-nodes/${id}`, { method: "PUT", body: input }, decodeEgressNode);
}
export function deleteEgressNode(id: string): Promise<{ deleted: boolean }> {
return apiRequest(`/api/admin/v1/egress-nodes/${id}`, { method: "DELETE" }, decodeBooleanResult<{ deleted: boolean }>("deleted"));
}
export function deleteEgressNodes(ids: string[]): Promise<{ deleted: number }> {
return apiRequest("/api/admin/v1/egress-nodes", { method: "DELETE", body: { ids } }, createObjectDecoder<{ deleted: number }>("egress node batch delete", { deleted: isNumber }));
}
export function updateEgressNodesEnabled(ids: string[], enabled: boolean): Promise<{ updated: number }> {
return apiRequest("/api/admin/v1/egress-nodes/batch", { method: "PATCH", body: { ids, enabled } }, createObjectDecoder<{ updated: number }>("egress node batch update", { updated: isNumber }));
}
export function previewUnhealthyEgressNodes(): Promise<EgressUnhealthyCleanupPreviewDTO> {
return apiRequest("/api/admin/v1/egress-nodes/cleanup-preview", {}, createObjectDecoder<EgressUnhealthyCleanupPreviewDTO>("egress node cleanup preview", {
nodes: isNumber, boundAccounts: isNumber, subscriptionManaged: isNumber,
}));
}
export function cleanupUnhealthyEgressNodes(): Promise<{ deleted: number }> {
return apiRequest("/api/admin/v1/egress-nodes/cleanup", { method: "POST" }, createObjectDecoder<{ deleted: number }>("egress node cleanup", { deleted: isNumber }));
}
export function refreshEgressClearance(id: string): Promise<{ refreshed: boolean }> {
return apiRequest(`/api/admin/v1/egress-nodes/${id}/refresh-clearance`, { method: "POST" }, decodeBooleanResult<{ refreshed: boolean }>("refreshed"));
}
export function testEgressNode(id: string): Promise<EgressProbeResultDTO> {
return apiRequest(`/api/admin/v1/egress-nodes/${id}/test`, { method: "POST" }, decodeEgressProbeResult);
}
export function testEgressNodes(ids?: string[]): Promise<EgressProbeBatchResultDTO> {
return apiRequest("/api/admin/v1/egress-nodes/test", { method: "POST", body: { ids: ids ?? [] } }, decodeEgressProbeBatchResult);
}
type ListEgressSourcesInput = {
page?: number;
pageSize?: number;
search?: string;
scope?: EgressScope;
};
export function listEgressSources(input?: ListEgressSourcesInput): Promise<EgressSourceListDTO> {
if (!input) return apiRequest("/api/admin/v1/egress-sources", {}, decodeEgressSourceList);
const query = new URLSearchParams({ page: String(input.page ?? 1), pageSize: String(input.pageSize ?? 20) });
if (input.search) query.set("search", input.search);
if (input.scope) query.set("scope", input.scope);
return apiRequest(`/api/admin/v1/egress-sources?${query}`, {}, decodeEgressSourceList);
}
export function createEgressSource(input: EgressSourceInput): Promise<EgressSourceDTO> {
return apiRequest("/api/admin/v1/egress-sources", { method: "POST", body: input }, decodeEgressSource);
}
export function updateEgressSource(id: string, input: EgressSourceInput): Promise<EgressSourceDTO> {
return apiRequest(`/api/admin/v1/egress-sources/${id}`, { method: "PUT", body: input }, decodeEgressSource);
}
export function deleteEgressSource(id: string): Promise<{ deleted: boolean }> {
return apiRequest(`/api/admin/v1/egress-sources/${id}`, { method: "DELETE" }, decodeBooleanResult<{ deleted: boolean }>("deleted"));
}
export function syncEgressSource(id: string): Promise<EgressImportResultDTO> {
return apiRequest(`/api/admin/v1/egress-sources/${id}/sync`, { method: "POST" }, decodeEgressImportResult);
}
export function importEgressText(input: { name: string; scope: EgressScope; accountCapacity: number; content: string }): Promise<EgressImportResultDTO> {
return apiRequest("/api/admin/v1/egress-imports", { method: "POST", body: input }, decodeEgressImportResult);
}
export function getEgressOperationsConfig(): Promise<EgressOperationsConfigDTO> {
return apiRequest("/api/admin/v1/egress-operations", {}, decodeEgressOperationsConfig);
}
export function updateEgressOperationsConfig(input: Omit<EgressOperationsConfigDTO, "updatedAt">): Promise<EgressOperationsConfigDTO> {
return apiRequest("/api/admin/v1/egress-operations", { method: "PUT", body: input }, decodeEgressOperationsConfig);
}
export function rebalanceEgressAccounts(): Promise<EgressRebalanceResultDTO> {
return apiRequest("/api/admin/v1/egress-operations/rebalance", { method: "POST" }, decodeEgressRebalanceResult);
}
export function assignEgressAccounts(nodeID: string, provider: "grok_build" | "grok_web" | "grok_console", ids: string[], mode: "manual" | "auto" = "manual"): Promise<{ assigned: number }> {
return apiRequest(`/api/admin/v1/egress-nodes/${nodeID}/accounts`, { method: "POST", body: { provider, ids, mode } }, createObjectDecoder<{ assigned: number }>("egress account assignment", { assigned: isNumber }));
}
export function unassignEgressAccounts(provider: "grok_build" | "grok_web" | "grok_console", ids: string[]): Promise<{ assigned: number }> {
return apiRequest("/api/admin/v1/egress-nodes/accounts", { method: "DELETE", body: { provider, ids } }, createObjectDecoder<{ assigned: number }>("egress account assignment", { assigned: isNumber }));
}