fix: deduplicate workbench requests and remove unused map paths
Generic Container CI/CD / test-build-publish (push) Successful in 2m7s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m7s

This commit is contained in:
2026-11-09 18:49:32 +08:00
parent 038d3458bc
commit d7eb01652a
13 changed files with 403 additions and 321 deletions
+9 -49
View File
@@ -220,24 +220,16 @@ export type AgentApiClientOptions = {
getProjectId?: () => Promise<string | undefined>;
};
const AGENT_API_BASE_URLS = [env.TJWATER_AGENT_URL.replace(/\/$/, "")];
const API_PATH = "/api/v1";
export function createAgentApiClient(
baseUrls: string | string[] = AGENT_API_BASE_URLS,
baseUrl: string = env.TJWATER_AGENT_URL,
options: AgentApiClientOptions = {}
): AgentApiClient {
const candidates = (Array.isArray(baseUrls) ? baseUrls : [baseUrls]).map((item) => item.replace(/\/$/, ""));
let activeBaseUrl = candidates[0] ?? "";
const setActiveBaseUrl = (baseUrl: string) => {
activeBaseUrl = baseUrl;
};
const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
const request = (path: string, init?: RequestInit) =>
fetchWithFallback(
candidates,
activeBaseUrl,
setActiveBaseUrl,
fetchAgent(
normalizedBaseUrl,
path,
init,
options.getAccessToken,
@@ -265,7 +257,7 @@ export function createAgentApiClient(
return token;
} : undefined;
const answerStateRequest = async (path: string, init?: RequestInit) => {
const response = await fetchWithFallback(candidates, activeBaseUrl, setActiveBaseUrl, path, init,
const response = await fetchAgent(normalizedBaseUrl, path, init,
checkedAnswerStateToken, options.getProjectId);
// An identity change while awaiting the network also invalidates the result.
await checkedAnswerStateToken?.();
@@ -275,7 +267,7 @@ export function createAgentApiClient(
return {
...createInteractionRequestApi(answerStateRequest),
async getHealth(signal) {
const response = await fetchWithFallback(candidates, activeBaseUrl, setActiveBaseUrl, "/health", { signal, cache: "no-store" }, undefined, undefined, "");
const response = await fetchAgent(normalizedBaseUrl, "/health", { signal, cache: "no-store" }, undefined, undefined, "");
if (!response.ok) throw new Error("Agent 服务暂不可用");
const data: unknown = await response.json();
if (!isRecord(data) || data.ok !== true || data.ready !== true) throw new Error("Agent 服务尚未就绪");
@@ -579,19 +571,14 @@ export function createAgentApiClient(
};
}
async function fetchWithFallback(
baseUrls: string[],
activeBaseUrl: string,
setActiveBaseUrl: (baseUrl: string) => void,
async function fetchAgent(
baseUrl: string,
path: string,
init?: RequestInit,
getAccessToken?: AccessTokenProvider,
getProjectId?: () => Promise<string | undefined>,
prefix = API_PATH
) {
const orderedBaseUrls = [activeBaseUrl, ...baseUrls.filter((item) => item !== activeBaseUrl)];
let lastError: unknown;
let lastResponse: Response | null = null;
const accessToken = await getAccessToken?.();
const projectId = await getProjectId?.();
const hasContextHeaders = Boolean(accessToken || projectId);
@@ -600,34 +587,7 @@ async function fetchWithFallback(
if (projectId) headers?.set("X-Project-Id", projectId);
const requestInit = hasContextHeaders ? { ...init, headers } : init;
for (const baseUrl of orderedBaseUrls) {
try {
const response = await fetch(`${baseUrl}${prefix}${path}`, requestInit);
if (response.ok) {
setActiveBaseUrl(baseUrl);
return response;
}
lastResponse = response;
if (shouldFallbackOnHttpStatus(response.status) && baseUrl !== orderedBaseUrls[orderedBaseUrls.length - 1]) {
await response.body?.cancel();
continue;
}
return response;
} catch (error) {
lastError = error;
}
}
if (lastResponse) {
return lastResponse;
}
throw lastError instanceof Error ? lastError : new Error("Agent API unavailable");
}
function shouldFallbackOnHttpStatus(status: number) {
return status === 404 || status === 405 || status === 502 || status === 503 || status === 504;
return fetch(`${baseUrl}${prefix}${path}`, requestInit);
}
function isRecord(value: unknown): value is Record<string, unknown> {