MagicCode/packages/providers/src/sapaicore/deployments.ts

130 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-10-11 00:11:20 +00:00
import type { BtpAiConfig } from "./config";
import { safeReadText } from "./http";
export const DEFAULT_RESOURCE_GROUP = "default";
/** Famille de modèle : détermine l'API à appeler sur le déploiement. */
export type ModelType = "claude" | "openai" | "other";
export interface Deployment {
id: string;
name: string;
version: string | null;
deploymentUrl: string;
type: ModelType;
}
/**
* Hypothèse (la fonction n'était pas dans les samples) :
* - anthropic--* / claude → Claude (AWS Bedrock, API Converse)
* - gpt*, o1/o3/o4… → OpenAI (Azure OpenAI, chat/completions)
* - le reste (gemini, embeddings, mistral…) → non pris en charge pour l'instant
*/
export function detectModelType(name: string): ModelType {
const n = name.toLowerCase();
if (n.startsWith("anthropic--") || n.includes("claude")) return "claude";
if (/^(gpt|chatgpt|o\d)/.test(n)) return "openai";
return "other";
}
/**
* Liste les modèles/déploiements disponibles.
*
* GET {AI_API_URL}/v2/lm/deployments
* Headers: Authorization: Bearer {token}, AI-Resource-Group
*/
export async function listDeployments(config: BtpAiConfig, token: string): Promise<Deployment[]> {
const url = `${config.serviceurls.AI_API_URL.replace(/\/+$/, "")}/v2/lm/deployments`;
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"AI-Resource-Group": config.resourceGroup ?? DEFAULT_RESOURCE_GROUP,
},
});
if (!response.ok) {
const body = await safeReadText(response);
throw new Error(
`Échec de récupération de la liste des modèles (HTTP ${response.status} ${response.statusText}). ${body}`,
);
}
const data = (await response.json()) as { resources?: RawDeployment[] };
const resources = Array.isArray(data.resources) ? data.resources : [];
const deployments = resources
.filter((r) => r.deploymentUrl)
.map((r): Deployment => {
const model = extractModelInfo(r);
const name = model?.name ?? r.configurationName ?? r.id ?? "(nom inconnu)";
return {
id: r.id ?? r.deploymentUrl!,
name,
version: model?.version ?? null,
deploymentUrl: r.deploymentUrl!,
type: detectModelType(name),
};
});
if (deployments.length === 0) {
throw new Error(
"Aucun modèle déployé (avec deploymentUrl) n'a été trouvé pour ce service/resource group.",
);
}
return deployments;
}
/**
* Choisit le déploiement : nom/id exact, sinon nom partiel. Vide ou "auto" → premier
* déploiement de chat pris en charge.
*/
export function findDeployment(deployments: Deployment[], wanted: string): Deployment {
const w = wanted.trim().toLowerCase();
const usable = deployments.filter((d) => d.type !== "other");
if (!w || w === "auto") {
const first = usable[0];
if (!first) {
throw new Error("Aucun déploiement de chat pris en charge (Claude ou GPT) n'a été trouvé.");
}
return first;
}
const found =
usable.find((d) => d.name.toLowerCase() === w || d.id.toLowerCase() === w) ??
usable.find((d) => d.name.toLowerCase().includes(w));
if (!found) {
throw new Error(
`Modèle "${wanted}" introuvable. Disponibles : ${usable.map((d) => d.name).join(", ") || "(aucun)"}`,
);
}
return found;
}
interface RawModel {
name?: string;
version?: string | null;
}
interface RawDeployment {
id?: string;
deploymentUrl?: string;
configurationName?: string;
details?: { resources?: { backend_details?: { model?: RawModel }; backendDetails?: { model?: RawModel } } };
backend_details?: { model?: RawModel };
backendDetails?: { model?: RawModel };
}
/** Chemin confirmé "details.resources.backend_details.model", avec variantes de repli. */
function extractModelInfo(r: RawDeployment): { name: string; version: string | null } | null {
const candidates = [
r.details?.resources?.backend_details?.model,
r.details?.resources?.backendDetails?.model,
r.backend_details?.model,
r.backendDetails?.model,
];
const found = candidates.find((c) => c?.name);
return found?.name ? { name: found.name, version: found.version ?? null } : null;
}