197 lines
7.2 KiB
TypeScript
197 lines
7.2 KiB
TypeScript
import { getModelSpec } from "../../src/shared/constants/modelSpecs.ts";
|
|
|
|
export const BEDROCK_DEFAULT_REGION = "us-east-1";
|
|
export const BEDROCK_DASHBOARD_DEFAULT_REGION = "eu-west-2";
|
|
|
|
const BEDROCK_REGION_PATTERN = /^[a-z]{2}(?:-gov)?-[a-z]+-\d+$/i;
|
|
|
|
export function normalizeBedrockRegion(value: unknown, fallback = BEDROCK_DEFAULT_REGION): string {
|
|
if (typeof value !== "string") return fallback;
|
|
const trimmed = value.trim().toLowerCase();
|
|
return BEDROCK_REGION_PATTERN.test(trimmed) ? trimmed : fallback;
|
|
}
|
|
|
|
export function extractBedrockRegionFromBaseUrl(value: string | null | undefined): string | null {
|
|
if (!value) return null;
|
|
try {
|
|
const hostname = new URL(value).hostname;
|
|
const match = hostname.match(/^bedrock(?:-runtime|-mantle)?\.([a-z0-9-]+)\./i);
|
|
return match?.[1] ? normalizeBedrockRegion(match[1], "") || null : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function resolveBedrockRegion(providerSpecificData: unknown): string {
|
|
const data =
|
|
providerSpecificData && typeof providerSpecificData === "object"
|
|
? (providerSpecificData as Record<string, unknown>)
|
|
: {};
|
|
const explicit = normalizeBedrockRegion(data.region, "");
|
|
if (explicit) return explicit;
|
|
|
|
const baseUrl = typeof data.baseUrl === "string" ? data.baseUrl : null;
|
|
return extractBedrockRegionFromBaseUrl(baseUrl) || BEDROCK_DEFAULT_REGION;
|
|
}
|
|
|
|
export function buildBedrockControlBaseUrl(region: string): string {
|
|
return `https://bedrock.${normalizeBedrockRegion(region)}.amazonaws.com`;
|
|
}
|
|
|
|
export function buildBedrockRuntimeBaseUrl(region: string): string {
|
|
return `https://bedrock-runtime.${normalizeBedrockRegion(region)}.amazonaws.com`;
|
|
}
|
|
|
|
export function buildBedrockNativeModelsUrl(region: string): string {
|
|
return `${buildBedrockControlBaseUrl(region)}/foundation-models?byOutputModality=TEXT`;
|
|
}
|
|
|
|
export function buildBedrockNativeInferenceProfilesUrl(
|
|
region: string,
|
|
options: { nextToken?: string | null; typeEquals?: "SYSTEM_DEFINED" | "APPLICATION" } = {}
|
|
): string {
|
|
const url = new URL(`${buildBedrockControlBaseUrl(region)}/inference-profiles`);
|
|
url.searchParams.set("maxResults", "100");
|
|
url.searchParams.set("typeEquals", options.typeEquals || "SYSTEM_DEFINED");
|
|
if (options.nextToken) url.searchParams.set("nextToken", options.nextToken);
|
|
return url.toString();
|
|
}
|
|
|
|
export function buildBedrockNativeConverseUrl(region: string, modelId: string, stream = false) {
|
|
const encodedModel = encodeURIComponent(modelId);
|
|
return `${buildBedrockRuntimeBaseUrl(region)}/model/${encodedModel}/${stream ? "converse-stream" : "converse"}`;
|
|
}
|
|
|
|
function modelIdFromArn(value: unknown): string | null {
|
|
if (typeof value !== "string") return null;
|
|
const marker = ":foundation-model/";
|
|
const idx = value.indexOf(marker);
|
|
if (idx < 0) return null;
|
|
const id = value.slice(idx + marker.length).trim();
|
|
return id || null;
|
|
}
|
|
|
|
export type BedrockDiscoveredModel = {
|
|
id: string;
|
|
name: string;
|
|
source: "foundation" | "inference_profile";
|
|
provider?: string | null;
|
|
supportsStreaming?: boolean;
|
|
supportsVision?: boolean;
|
|
inputTokenLimit?: number;
|
|
outputTokenLimit?: number;
|
|
};
|
|
|
|
export function getBedrockKnownModelLimits(modelId: string): {
|
|
inputTokenLimit?: number;
|
|
outputTokenLimit?: number;
|
|
} | null {
|
|
const trimmed = typeof modelId === "string" ? modelId.trim() : "";
|
|
if (!trimmed) return null;
|
|
|
|
const unqualified = trimmed.includes("/") ? trimmed.slice(trimmed.indexOf("/") + 1) : trimmed;
|
|
const withoutProfilePrefix = unqualified.replace(/^(?:eu|us|global)\./i, "");
|
|
const withoutProviderPrefix = withoutProfilePrefix.replace(/^anthropic\./i, "");
|
|
const spec =
|
|
getModelSpec(trimmed) ||
|
|
getModelSpec(unqualified) ||
|
|
getModelSpec(withoutProfilePrefix) ||
|
|
getModelSpec(withoutProviderPrefix);
|
|
|
|
if (!spec?.contextWindow && !spec?.maxOutputTokens) return null;
|
|
return {
|
|
...(typeof spec.contextWindow === "number" ? { inputTokenLimit: spec.contextWindow } : {}),
|
|
...(typeof spec.maxOutputTokens === "number" ? { outputTokenLimit: spec.maxOutputTokens } : {}),
|
|
};
|
|
}
|
|
|
|
function withKnownBedrockLimits(model: BedrockDiscoveredModel): BedrockDiscoveredModel {
|
|
return {
|
|
...model,
|
|
...(getBedrockKnownModelLimits(model.id) || {}),
|
|
};
|
|
}
|
|
|
|
export function normalizeBedrockDiscoveredModels(
|
|
foundationModelsResponse: unknown,
|
|
inferenceProfilesResponse: unknown = null
|
|
): BedrockDiscoveredModel[] {
|
|
const byId = new Map<string, BedrockDiscoveredModel>();
|
|
const add = (model: BedrockDiscoveredModel) => {
|
|
if (!model.id || byId.has(model.id)) return;
|
|
byId.set(model.id, model);
|
|
};
|
|
|
|
const foundationModels =
|
|
foundationModelsResponse && typeof foundationModelsResponse === "object"
|
|
? (foundationModelsResponse as Record<string, unknown>).modelSummaries
|
|
: null;
|
|
if (Array.isArray(foundationModels)) {
|
|
for (const item of foundationModels) {
|
|
const model = item && typeof item === "object" ? (item as Record<string, unknown>) : {};
|
|
const id = typeof model.modelId === "string" ? model.modelId.trim() : "";
|
|
if (!id) continue;
|
|
const outputModalities = Array.isArray(model.outputModalities) ? model.outputModalities : [];
|
|
const inputModalities = Array.isArray(model.inputModalities) ? model.inputModalities : [];
|
|
add(
|
|
withKnownBedrockLimits({
|
|
id,
|
|
name:
|
|
typeof model.modelName === "string" && model.modelName.trim() ? model.modelName : id,
|
|
source: "foundation",
|
|
provider: typeof model.providerName === "string" ? model.providerName : null,
|
|
supportsStreaming: model.responseStreamingSupported === true,
|
|
supportsVision: inputModalities.includes("IMAGE") || outputModalities.includes("IMAGE"),
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
const profiles =
|
|
inferenceProfilesResponse && typeof inferenceProfilesResponse === "object"
|
|
? (inferenceProfilesResponse as Record<string, unknown>).inferenceProfileSummaries
|
|
: null;
|
|
if (Array.isArray(profiles)) {
|
|
for (const item of profiles) {
|
|
const profile = item && typeof item === "object" ? (item as Record<string, unknown>) : {};
|
|
const id =
|
|
typeof profile.inferenceProfileId === "string" ? profile.inferenceProfileId.trim() : "";
|
|
if (id) {
|
|
add(
|
|
withKnownBedrockLimits({
|
|
id,
|
|
name:
|
|
typeof profile.inferenceProfileName === "string" &&
|
|
profile.inferenceProfileName.trim()
|
|
? profile.inferenceProfileName
|
|
: id,
|
|
source: "inference_profile",
|
|
supportsStreaming: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
const models = Array.isArray(profile.models) ? profile.models : [];
|
|
for (const profileModel of models) {
|
|
const modelRecord =
|
|
profileModel && typeof profileModel === "object"
|
|
? (profileModel as Record<string, unknown>)
|
|
: {};
|
|
const modelId = modelIdFromArn(modelRecord.modelArn);
|
|
if (modelId) {
|
|
add(
|
|
withKnownBedrockLimits({
|
|
id: modelId,
|
|
name: modelId,
|
|
source: "foundation",
|
|
supportsStreaming: true,
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
}
|