Files
2026-07-13 13:39:12 +08:00

161 lines
4.7 KiB
TypeScript

import {
buildBedrockNativeInferenceProfilesUrl,
buildBedrockNativeModelsUrl,
normalizeBedrockDiscoveredModels,
resolveBedrockRegion,
type BedrockDiscoveredModel,
} from "../config/bedrock.ts";
export type BedrockNativeFetch = (url: string, init: RequestInit) => Promise<Response>;
export type BedrockNativeDiscoveryResult = {
region: string;
models: BedrockDiscoveredModel[];
foundationModelsResponse: unknown;
inferenceProfilesResponse: unknown;
warnings: string[];
};
export class BedrockNativeApiError extends Error {
readonly status: number | null;
readonly url: string;
readonly body: unknown;
constructor(message: string, options: { status?: number | null; url: string; body?: unknown }) {
super(message);
this.name = "BedrockNativeApiError";
this.status = typeof options.status === "number" ? options.status : null;
this.url = options.url;
this.body = options.body ?? null;
}
}
export function isBedrockNativeApiError(error: unknown): error is BedrockNativeApiError {
return error instanceof BedrockNativeApiError;
}
export function isBedrockNativeAuthError(error: unknown): boolean {
return isBedrockNativeApiError(error) && (error.status === 401 || error.status === 403);
}
export function buildBedrockNativeHeaders(
apiKey: string | null | undefined,
extraHeaders: Record<string, string> = {}
): Record<string, string> {
return {
Accept: "application/json",
"Content-Type": "application/json",
...(apiKey ? { Authorization: "Bearer " + apiKey } : {}),
...extraHeaders,
};
}
async function readJsonOrText(response: Response): Promise<unknown> {
const text = await response.text();
if (!text) return null;
try {
return JSON.parse(text);
} catch {
return text;
}
}
function getErrorMessage(body: unknown, fallback: string): string {
if (body && typeof body === "object") {
const record = body as Record<string, unknown>;
const message = record.message || record.Message || record.error || record.errorMessage;
if (typeof message === "string" && message.trim()) return message.trim();
}
if (typeof body === "string" && body.trim()) return body.trim();
return fallback;
}
async function fetchBedrockJson(
fetcher: BedrockNativeFetch,
url: string,
apiKey: string,
init: RequestInit = {}
): Promise<unknown> {
const headers = buildBedrockNativeHeaders(apiKey, {
...((init.headers as Record<string, string> | undefined) || {}),
});
const response = await fetcher(url, {
...init,
method: init.method || "GET",
headers,
});
const body = await readJsonOrText(response);
if (!response.ok) {
throw new BedrockNativeApiError(
getErrorMessage(body, "Bedrock API request failed with " + response.status),
{ status: response.status, url, body }
);
}
return body;
}
async function fetchInferenceProfiles(
fetcher: BedrockNativeFetch,
region: string,
apiKey: string
): Promise<{ inferenceProfileSummaries: unknown[] }> {
const summaries: unknown[] = [];
let nextToken: string | null = null;
do {
const data = await fetchBedrockJson(
fetcher,
buildBedrockNativeInferenceProfilesUrl(region, { nextToken }),
apiKey
);
const record = data && typeof data === "object" ? (data as Record<string, unknown>) : {};
const pageSummaries = Array.isArray(record.inferenceProfileSummaries)
? record.inferenceProfileSummaries
: [];
summaries.push(...pageSummaries);
nextToken = typeof record.nextToken === "string" && record.nextToken ? record.nextToken : null;
} while (nextToken);
return { inferenceProfileSummaries: summaries };
}
export async function discoverBedrockNativeModels({
apiKey,
providerSpecificData,
fetcher = fetch,
}: {
apiKey: string;
providerSpecificData?: unknown;
fetcher?: BedrockNativeFetch;
}): Promise<BedrockNativeDiscoveryResult> {
const region = resolveBedrockRegion(providerSpecificData);
const foundationModelsResponse = await fetchBedrockJson(
fetcher,
buildBedrockNativeModelsUrl(region),
apiKey
);
let inferenceProfilesResponse: unknown = { inferenceProfileSummaries: [] };
const warnings: string[] = [];
try {
inferenceProfilesResponse = await fetchInferenceProfiles(fetcher, region, apiKey);
} catch (error) {
if (isBedrockNativeAuthError(error)) {
throw error;
}
const message = error instanceof Error ? error.message : String(error || "unknown error");
warnings.push("Bedrock inference profiles unavailable: " + message);
}
return {
region,
foundationModelsResponse,
inferenceProfilesResponse,
models: normalizeBedrockDiscoveredModels(foundationModelsResponse, inferenceProfilesResponse),
warnings,
};
}