refactor(ui): Phase 4 complete — provider-pool adapter eliminates DRY violation

Create adapters/provider-pool.adapter.ts with 6 pure functions that
replace duplicated CodexPoolEntry construction in 3 files:
- provider-overview.tsx (inline poolEntries useMemo)
- agent-codex-pool-page.tsx (35-line buildEntries callback)
- provider-pool-activity-section.tsx (30-line entries useMemo)

Also centralizes resolveProviderAvailability (was copied in 3 places).
This commit is contained in:
viettranx committed 2026-04-03 22:59:25 +07:00
1 parent 1313f02cf4
commit 2ed810b854
4 files changed
+173 -100

No files matched your search

@@ -0,0 +1,156 @@
import type { CodexPoolEntry } from "@/pages/agents/agent-detail/codex-pool-entry-types";
import type { CodexPoolProviderCount } from "@/pages/agents/agent-detail/hooks/use-codex-pool-activity";
import type { ChatGPTOAuthAvailability } from "@/pages/providers/hooks/use-chatgpt-oauth-provider-statuses";
import type { ChatGPTOAuthProviderQuota } from "@/pages/providers/hooks/use-chatgpt-oauth-provider-quotas";
/** Minimal provider shape needed for pool entry construction. */
export interface PoolProviderInfo {
id?: string;
display_name?: string;
enabled?: boolean;
}
/** Resolve OAuth availability from status map with enabled fallback. */
export function resolveProviderAvailability(
providerName: string,
statusByName: Map<string, { availability: ChatGPTOAuthAvailability }>,
enabled?: boolean,
): ChatGPTOAuthAvailability {
return (
statusByName.get(providerName)?.availability ??
(enabled === false ? "disabled" : "needs_sign_in")
);
}
/**
* Build a CodexPoolEntry for a provider name with live activity counts.
* Used when CodexPoolProviderCount data is available (activity views).
*/
export function toPoolEntryWithCounts(
providerName: string,
count: CodexPoolProviderCount,
preferredProviderName: string,
providerByName: Map<string, PoolProviderInfo>,
statusByName: Map<string, { availability: ChatGPTOAuthAvailability }>,
quotaByName: Map<string, ChatGPTOAuthProviderQuota | null>,
): CodexPoolEntry {
const provider = providerByName.get(providerName);
return {
name: providerName,
label: provider?.display_name || providerName,
availability: resolveProviderAvailability(providerName, statusByName, provider?.enabled),
role: providerName === preferredProviderName ? "preferred" : "extra",
requestCount: count.request_count,
directSelectionCount: count.direct_selection_count,
failoverServeCount: count.failover_serve_count,
successCount: count.success_count,
failureCount: count.failure_count,
consecutiveFailures: count.consecutive_failures,
successRate: count.success_rate,
healthScore: count.health_score,
healthState: count.health_state ?? "idle",
lastSelectedAt: count.last_selected_at,
lastFailoverAt: count.last_failover_at,
lastUsedAt: count.last_used_at,
lastSuccessAt: count.last_success_at,
lastFailureAt: count.last_failure_at,
providerHref: provider?.id ? `/providers/${provider.id}` : undefined,
quota: quotaByName.get(providerName),
};
}
/**
* Build a CodexPoolEntry for a provider name with zeroed counts.
* Used when no activity data is available (pool configuration views).
*/
export function toPoolEntry(
providerName: string,
preferredProviderName: string,
providerByName: Map<string, PoolProviderInfo>,
statusByName: Map<string, { availability: ChatGPTOAuthAvailability }>,
quotaByName: Map<string, ChatGPTOAuthProviderQuota | null>,
): CodexPoolEntry {
const provider = providerByName.get(providerName);
return {
name: providerName,
label: provider?.display_name || providerName,
availability: resolveProviderAvailability(providerName, statusByName, provider?.enabled),
role: providerName === preferredProviderName ? "preferred" : "extra",
requestCount: 0,
directSelectionCount: 0,
failoverServeCount: 0,
successCount: 0,
failureCount: 0,
consecutiveFailures: 0,
successRate: 0,
healthScore: 0,
healthState: "idle",
providerHref: provider?.id ? `/providers/${provider.id}` : undefined,
quota: quotaByName.get(providerName),
};
}
/**
* Batch-transform a list of provider names into CodexPoolEntry[] with zeroed counts.
* Used in pool configuration views (no activity data).
*/
export function toPoolEntries(
poolNames: string[],
preferredProviderName: string,
providerByName: Map<string, PoolProviderInfo>,
statusByName: Map<string, { availability: ChatGPTOAuthAvailability }>,
quotaByName: Map<string, ChatGPTOAuthProviderQuota | null>,
): CodexPoolEntry[] {
return poolNames.map((name) =>
toPoolEntry(name, preferredProviderName, providerByName, statusByName, quotaByName),
);
}
/**
* Batch-transform CodexPoolProviderCount[] into CodexPoolEntry[] with live counts.
* Used in pool activity views where counts drive the entry list (provider-pool-activity-section).
*/
export function toPoolEntriesWithCounts(
counts: CodexPoolProviderCount[],
preferredProviderName: string,
providerByName: Map<string, PoolProviderInfo>,
statusByName: Map<string, { availability: ChatGPTOAuthAvailability }>,
quotaByName: Map<string, ChatGPTOAuthProviderQuota | null>,
): CodexPoolEntry[] {
return counts.map((c) =>
toPoolEntryWithCounts(
c.provider_name,
c,
preferredProviderName,
providerByName,
statusByName,
quotaByName,
),
);
}
/**
* Build CodexPoolEntry[] for a list of pool provider names, merging in any available
* activity counts by name. Names with no count data get zeroed fields.
* directSelectionCount falls back to request_count when direct_selection_count is absent.
* Used in agent codex pool page where pool names drive the list, not counts.
*/
export function toPoolEntriesMerged(
poolNames: string[],
counts: CodexPoolProviderCount[],
preferredProviderName: string,
providerByName: Map<string, PoolProviderInfo>,
statusByName: Map<string, { availability: ChatGPTOAuthAvailability }>,
quotaByName: Map<string, ChatGPTOAuthProviderQuota | null>,
): CodexPoolEntry[] {
const countsByName = new Map(counts.map((c) => [c.provider_name, c]));
return poolNames.map((providerName) => {
const count = countsByName.get(providerName);
if (!count) return toPoolEntry(providerName, preferredProviderName, providerByName, statusByName, quotaByName);
// directSelectionCount falls back to request_count when field is absent (older API responses)
return {
...toPoolEntryWithCounts(providerName, count, preferredProviderName, providerByName, statusByName, quotaByName),
directSelectionCount: count.direct_selection_count ?? count.request_count ?? 0,
};
});
}
@@ -1,12 +1,9 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router";
import { useTranslation } from "react-i18next";
import { DetailPageSkeleton } from "@/components/shared/loading-skeleton";
import { useProviders } from "@/pages/providers/hooks/use-providers";
import {
useChatGPTOAuthProviderStatuses,
type ChatGPTOAuthAvailability,
} from "@/pages/providers/hooks/use-chatgpt-oauth-provider-statuses";
import { useChatGPTOAuthProviderStatuses } from "@/pages/providers/hooks/use-chatgpt-oauth-provider-statuses";
import { useChatGPTOAuthProviderQuotas } from "@/pages/providers/hooks/use-chatgpt-oauth-provider-quotas";
import { useAuthStore } from "@/stores/use-auth-store";
import type { ChatGPTOAuthRoutingConfig } from "@/types/agent";
@@ -24,27 +21,14 @@ import {
} from "./agent-display-utils";
import { getRouteReadiness } from "./chatgpt-oauth-quota-utils";
import { ChatGPTOAuthRoutingSection } from "./config-sections";
import {
CodexPoolActivityPanel,
type CodexPoolEntry,
} from "./codex-pool-activity-panel";
import { CodexPoolActivityPanel } from "./codex-pool-activity-panel";
import { CodexPoolPageHeader } from "./codex-pool-page-header";
import {
buildDraftRouting,
routingDraftSignature,
} from "./codex-pool-routing-draft-utils";
import { useCodexPoolActivity } from "./hooks/use-codex-pool-activity";
function providerStatus(
providerName: string,
statusByName: Map<string, { availability: ChatGPTOAuthAvailability }>,
enabled?: boolean,
): ChatGPTOAuthAvailability {
return (
statusByName.get(providerName)?.availability ??
(enabled === false ? "disabled" : "needs_sign_in")
);
}
import { toPoolEntriesMerged } from "@/adapters/provider-pool.adapter";
export function AgentCodexPoolPage() {
const { id = "" } = useParams<{ id: string }>();
@@ -171,50 +155,13 @@ export function AgentCodexPoolPage() {
refetch: refreshActivity,
} = useCodexPoolActivity(agent?.id ?? id, 8, Boolean(agent && isEligible));
const buildEntries = useCallback(
(poolNames: string[]): CodexPoolEntry[] => {
if (!agent) return [];
const countsByName = new Map(
activity.provider_counts.map((item) => [item.provider_name, item]),
);
return poolNames.map((providerName) => {
const provider = providerByName.get(providerName);
const count = countsByName.get(providerName);
return {
name: providerName,
label: provider?.display_name || providerName,
availability: providerStatus(providerName, statusByName, provider?.enabled),
role: providerName === agent.provider ? "preferred" : "extra",
requestCount: count?.request_count ?? 0,
directSelectionCount:
count?.direct_selection_count ?? count?.request_count ?? 0,
failoverServeCount: count?.failover_serve_count ?? 0,
successCount: count?.success_count ?? 0,
failureCount: count?.failure_count ?? 0,
consecutiveFailures: count?.consecutive_failures ?? 0,
successRate: count?.success_rate ?? 0,
healthScore: count?.health_score ?? 0,
healthState: count?.health_state ?? "idle",
lastSelectedAt: count?.last_selected_at,
lastFailoverAt: count?.last_failover_at,
lastUsedAt: count?.last_used_at,
lastSuccessAt: count?.last_success_at,
lastFailureAt: count?.last_failure_at,
providerHref: provider?.id ? `/providers/${provider.id}` : undefined,
quota: quotaByName.get(providerName),
};
});
},
[activity.provider_counts, agent, providerByName, quotaByName, statusByName],
);
const liveEntries = useMemo(
() => buildEntries(savedEffectiveRouting.poolProviderNames),
[buildEntries, savedEffectiveRouting.poolProviderNames],
() => !agent ? [] : toPoolEntriesMerged(savedEffectiveRouting.poolProviderNames, activity.provider_counts, agent.provider, providerByName, statusByName, quotaByName),
[activity.provider_counts, agent, providerByName, quotaByName, savedEffectiveRouting.poolProviderNames, statusByName],
);
const draftEntries = useMemo(
() => buildEntries(draftEffectiveRouting.poolProviderNames),
[buildEntries, draftEffectiveRouting.poolProviderNames],
() => !agent ? [] : toPoolEntriesMerged(draftEffectiveRouting.poolProviderNames, activity.provider_counts, agent.provider, providerByName, statusByName, quotaByName),
[activity.provider_counts, agent, draftEffectiveRouting.poolProviderNames, providerByName, quotaByName, statusByName],
);
const routeEntries = useMemo(
@@ -29,8 +29,8 @@ import type { ChatGPTOAuthRoutingConfig } from "@/types/agent";
import { useChatGPTOAuthProviderStatuses } from "../hooks/use-chatgpt-oauth-provider-statuses";
import { useChatGPTOAuthProviderQuotas } from "../hooks/use-chatgpt-oauth-provider-quotas";
import { ChatGPTOAuthRoutingSection } from "@/pages/agents/agent-detail/config-sections";
import type { CodexPoolEntry } from "@/pages/agents/agent-detail/codex-pool-activity-panel";
import { useProviderCodexPoolActivity } from "../hooks/use-provider-codex-pool-activity";
import { toPoolEntries } from "@/adapters/provider-pool.adapter";
import {
NO_API_KEY_TYPES,
NO_EMBEDDING_TYPES,
@@ -169,12 +169,9 @@ export function ProviderOverview({ provider, onUpdate }: ProviderOverviewProps)
return Array.from(new Set(selectedPoolProviderNames.filter((n) => { if (!n) return false; const item = providerByName.get(n); return providerStatus(n, statusByName, item?.enabled) === "ready"; })));
}, [isOAuth, providerByName, selectedPoolProviderNames, statusByName]);
const { quotaByName, isLoading: quotasLoading, isFetching: quotasFetching } = useChatGPTOAuthProviderQuotas(quotaProviderNames, isOAuth);
const poolEntries = useMemo<CodexPoolEntry[]>(() => {
const poolEntries = useMemo(() => {
if (!canEditPoolRouting) return [];
return selectedPoolProviderNames.map((n) => {
const item = providerByName.get(n);
return { name: n, label: item?.display_name || n, availability: providerStatus(n, statusByName, item?.enabled), role: n === provider.name ? "preferred" : "extra", requestCount: 0, directSelectionCount: 0, failoverServeCount: 0, successCount: 0, failureCount: 0, consecutiveFailures: 0, successRate: 0, healthScore: 0, healthState: "idle" as const, providerHref: item?.id ? `/providers/${item.id}` : undefined, quota: quotaByName.get(n) };
});
return toPoolEntries(selectedPoolProviderNames, provider.name, providerByName, statusByName, quotaByName);
}, [canEditPoolRouting, provider.name, providerByName, quotaByName, selectedPoolProviderNames, statusByName]);
const isPoolOwner = canEditPoolRouting && selectedPoolProviderNames.length > 1;
const { data: poolActivity, isFetching: poolActivityFetching, refetch: refreshPoolActivity } = useProviderCodexPoolActivity(provider.id, 8, isPoolOwner);
@@ -6,15 +6,14 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { EmptyState } from "@/components/shared/empty-state";
import { cn } from "@/lib/utils";
import type { CodexPoolProviderCount } from "@/pages/agents/agent-detail/hooks/use-codex-pool-activity";
import type { CodexPoolProviderCount, CodexPoolRecentRequest } from "@/pages/agents/agent-detail/hooks/use-codex-pool-activity";
import { CodexPoolMemberCard } from "@/pages/agents/agent-detail/codex-pool-member-card";
import { CodexPoolRecentRequestsList } from "@/pages/agents/agent-detail/codex-pool-recent-requests-list";
import type { CodexPoolEntry } from "@/pages/agents/agent-detail/codex-pool-entry-types";
import type { ChatGPTOAuthAvailability } from "../hooks/use-chatgpt-oauth-provider-statuses";
import type { ChatGPTOAuthProviderQuota } from "../hooks/use-chatgpt-oauth-provider-quotas";
import type { ProviderData } from "../hooks/use-providers";
import type { ProviderCodexPoolAgentCount } from "../hooks/use-provider-codex-pool-activity";
import type { CodexPoolRecentRequest } from "@/pages/agents/agent-detail/hooks/use-codex-pool-activity";
import { toPoolEntriesWithCounts } from "@/adapters/provider-pool.adapter";
function MonitorStat({ label, value }: { label: string; value: string }) {
return (
@@ -57,36 +56,10 @@ export function ProviderPoolActivitySection({
const { t } = useTranslation("providers");
const { t: ta } = useTranslation("agents");
const entries = useMemo<CodexPoolEntry[]>(() => {
return providerCounts.map((c) => {
const p = providerByName.get(c.provider_name);
const availability: ChatGPTOAuthAvailability =
statusByName.get(c.provider_name)?.availability ??
(p?.enabled === false ? "disabled" : "needs_sign_in");
return {
name: c.provider_name,
label: p?.display_name || c.provider_name,
availability,
role: c.provider_name === provider.name ? "preferred" as const : "extra" as const,
requestCount: c.request_count,
directSelectionCount: c.direct_selection_count,
failoverServeCount: c.failover_serve_count,
successCount: c.success_count,
failureCount: c.failure_count,
consecutiveFailures: c.consecutive_failures,
successRate: c.success_rate,
healthScore: c.health_score,
healthState: c.health_state ?? "idle",
lastSelectedAt: c.last_selected_at,
lastFailoverAt: c.last_failover_at,
lastUsedAt: c.last_used_at,
lastSuccessAt: c.last_success_at,
lastFailureAt: c.last_failure_at,
providerHref: p?.id ? `/providers/${p.id}` : undefined,
quota: quotaByName.get(c.provider_name),
};
});
}, [provider.name, providerByName, providerCounts, quotaByName, statusByName]);
const entries = useMemo(
() => toPoolEntriesWithCounts(providerCounts, provider.name, providerByName, statusByName, quotaByName),
[provider.name, providerByName, providerCounts, quotaByName, statusByName],
);
const failoverCount = recentRequests.filter((r) => r.used_failover).length;