UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

323 lines (322 loc) 11 kB
import { c as loadManifestMetadataSnapshot } from "./manifest-contract-eligibility-BbV7X6pV.js"; import { r as fetchWithTimeout } from "./fetch-timeout-DGxOi-Tg.js"; import { n as normalizeSecretInput } from "./normalize-secret-input-Df_qhWv_.js"; import { t as ensureApiKeyFromEnvOrPrompt } from "./provider-auth-input-B4MRIGbK.js"; import { n as t } from "./i18n-hynzGFbD.js"; import { a as buildOpenAiVerificationProbeRequest, d as resolveCustomProviderId, i as buildEndpointIdFromUrl, l as resolveCustomModelAliasError, n as applyCustomApiConfig, o as normalizeEndpointId, r as buildAnthropicVerificationProbeRequest, s as normalizeOptionalProviderApiKey, u as resolveCustomModelImageInputInference } from "./onboard-custom-config-DpIUL0Vo.js"; //#region src/commands/onboard-custom.ts const VERIFY_TIMEOUT_MS = 3e4; const COMPATIBILITY_OPTIONS = [ { value: "openai", labelKey: "wizard.customProvider.compatibilityOpenAi", hintKey: "wizard.customProvider.compatibilityOpenAiHint" }, { value: "openai-responses", labelKey: "wizard.customProvider.compatibilityOpenAiResponses", hintKey: "wizard.customProvider.compatibilityOpenAiResponsesHint" }, { value: "anthropic", labelKey: "wizard.customProvider.compatibilityAnthropic", hintKey: "wizard.customProvider.compatibilityAnthropicHint" }, { value: "unknown", labelKey: "wizard.customProvider.compatibilityUnknown", hintKey: "wizard.customProvider.compatibilityUnknownHint" } ]; function formatVerificationError(error) { if (!error) return "unknown error"; if (error instanceof Error) return error.message; if (typeof error === "string") return error; try { return JSON.stringify(error); } catch { return "unknown error"; } } function isJsonVerificationResponse(res) { const contentType = typeof res.headers?.get === "function" ? res.headers.get("content-type") ?? "" : ""; if (!contentType.trim()) return true; const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase(); return mediaType === "application/json" || mediaType !== void 0 && mediaType.endsWith("+json"); } async function requestVerification(params) { let res; try { res = await fetchWithTimeout(params.endpoint, { method: "POST", headers: { "Content-Type": "application/json", ...params.headers }, body: JSON.stringify(params.body) }, VERIFY_TIMEOUT_MS); if (res.ok && !isJsonVerificationResponse(res)) return { ok: false, error: `Verification returned ${res.headers.get("content-type") || "missing content-type"} instead of JSON. Check the provider base URL; OpenAI-compatible endpoints usually need a /v1 path prefix.` }; return { ok: res.ok, status: res.status }; } catch (error) { return { ok: false, error }; } finally { await res?.body?.cancel().catch(() => void 0); } } async function requestOpenAiVerification(params) { return await requestVerification(buildOpenAiVerificationProbeRequest(params)); } async function requestAnthropicVerification(params) { return await requestVerification(buildAnthropicVerificationProbeRequest(params)); } async function promptBaseUrlAndKey(params) { const baseUrl = (await params.prompter.text({ message: t("wizard.customProvider.apiBaseUrl"), initialValue: params.initialBaseUrl, placeholder: "https://api.example.com/v1", validate: (val) => { return URL.canParse(val) ? void 0 : t("wizard.customProvider.validUrl"); } })).trim(); const providerHint = buildEndpointIdFromUrl(baseUrl) || "custom"; let apiKeyInput; const resolvedApiKey = await ensureApiKeyFromEnvOrPrompt({ config: params.config, provider: providerHint, envLabel: "CUSTOM_API_KEY", promptMessage: t("wizard.customProvider.apiKeyPrompt"), normalize: normalizeSecretInput, validate: () => void 0, prompter: params.prompter, secretInputMode: params.secretInputMode, setCredential: async (apiKey) => { apiKeyInput = apiKey; } }); return { baseUrl, apiKey: normalizeOptionalProviderApiKey(apiKeyInput), resolvedApiKey: normalizeSecretInput(resolvedApiKey) }; } async function promptCustomApiRetryChoice(prompter) { return await prompter.select({ message: t("wizard.customProvider.retryChoice"), options: [ { value: "baseUrl", label: t("wizard.customProvider.changeBaseUrl") }, { value: "model", label: t("wizard.customProvider.changeModel") }, { value: "both", label: t("wizard.customProvider.changeBaseUrlAndModel") } ] }); } async function promptCustomApiModelId(prompter) { return (await prompter.text({ message: t("wizard.customProvider.modelId"), placeholder: t("wizard.customProvider.modelIdPlaceholder"), validate: (val) => val.trim() ? void 0 : t("wizard.customProvider.modelIdRequired") })).trim(); } async function applyCustomApiRetryChoice(params) { let { baseUrl, apiKey, resolvedApiKey, modelId } = params.current; if (params.retryChoice === "baseUrl" || params.retryChoice === "both") { const retryInput = await promptBaseUrlAndKey({ prompter: params.prompter, config: params.config, secretInputMode: params.secretInputMode, initialBaseUrl: baseUrl }); baseUrl = retryInput.baseUrl; apiKey = retryInput.apiKey; resolvedApiKey = retryInput.resolvedApiKey; } if (params.retryChoice === "model" || params.retryChoice === "both") modelId = await promptCustomApiModelId(params.prompter); return { baseUrl, apiKey, resolvedApiKey, modelId }; } /** Prompts for a custom API provider and prepares its verified endpoint config without writing it. */ async function promptCustomApiConfig(params) { const { prompter, runtime, config } = params; const manifestPlugins = loadManifestMetadataSnapshot({ config, workspaceDir: params.target?.workspaceDir, env: process.env }).plugins; const baseInput = await promptBaseUrlAndKey({ prompter, config, secretInputMode: params.secretInputMode }); let baseUrl = baseInput.baseUrl; let apiKey = baseInput.apiKey; let resolvedApiKey = baseInput.resolvedApiKey; const compatibilityChoice = await prompter.select({ message: t("wizard.customProvider.compatibility"), options: COMPATIBILITY_OPTIONS.map((option) => ({ value: option.value, label: t(option.labelKey), hint: t(option.hintKey) })) }); let modelId = await promptCustomApiModelId(prompter); let compatibility = compatibilityChoice === "unknown" ? null : compatibilityChoice; while (true) { let verifiedFromProbe = false; if (!compatibility) { const probeSpinner = prompter.progress(t("wizard.customProvider.detectionProgress")); if ((await requestOpenAiVerification({ baseUrl, apiKey: resolvedApiKey, modelId })).ok) { probeSpinner.stop(t("wizard.customProvider.detectedOpenAi")); compatibility = "openai"; verifiedFromProbe = true; } else if ((await requestOpenAiVerification({ baseUrl, apiKey: resolvedApiKey, modelId, responsesApi: true })).ok) { probeSpinner.stop(t("wizard.customProvider.detectedOpenAiResponses")); compatibility = "openai-responses"; verifiedFromProbe = true; } else if ((await requestAnthropicVerification({ baseUrl, apiKey: resolvedApiKey, modelId })).ok) { probeSpinner.stop(t("wizard.customProvider.detectedAnthropic")); compatibility = "anthropic"; verifiedFromProbe = true; } else { probeSpinner.stop(t("wizard.customProvider.detectionFailed")); await prompter.note(t("wizard.customProvider.detectionFailedNote"), t("wizard.customProvider.detectionNoteTitle")); const retryChoice = await promptCustomApiRetryChoice(prompter); ({baseUrl, apiKey, resolvedApiKey, modelId} = await applyCustomApiRetryChoice({ prompter, config, secretInputMode: params.secretInputMode, retryChoice, current: { baseUrl, apiKey, resolvedApiKey, modelId } })); continue; } } if (verifiedFromProbe) break; const verifySpinner = prompter.progress(t("wizard.customProvider.verifying")); const result = compatibility === "anthropic" ? await requestAnthropicVerification({ baseUrl, apiKey: resolvedApiKey, modelId }) : await requestOpenAiVerification({ baseUrl, apiKey: resolvedApiKey, modelId, responsesApi: compatibility === "openai-responses" }); if (result.ok) { verifySpinner.stop(t("wizard.customProvider.verificationSuccessful")); break; } if (result.error !== void 0) verifySpinner.stop(t("wizard.customProvider.verificationFailedError", { error: formatVerificationError(result.error) })); else verifySpinner.stop(t("wizard.customProvider.verificationFailedStatus", { status: result.status })); const retryChoice = await promptCustomApiRetryChoice(prompter); ({baseUrl, apiKey, resolvedApiKey, modelId} = await applyCustomApiRetryChoice({ prompter, config, secretInputMode: params.secretInputMode, retryChoice, current: { baseUrl, apiKey, resolvedApiKey, modelId } })); if (compatibilityChoice === "unknown") compatibility = null; } const suggestedId = buildEndpointIdFromUrl(baseUrl); const providerIdInput = await prompter.text({ message: t("wizard.customProvider.endpointId"), initialValue: suggestedId, placeholder: "custom", validate: (value) => { if (!normalizeEndpointId(value)) return t("wizard.customProvider.endpointIdRequired"); } }); const aliasInput = await prompter.text({ message: t("wizard.customProvider.modelAlias"), placeholder: t("wizard.customProvider.modelAliasPlaceholder"), initialValue: "", validate: (value) => { const resolvedProvider = resolveCustomProviderId({ config, baseUrl, providerId: providerIdInput }); return resolveCustomModelAliasError({ raw: value, cfg: config, modelRef: { provider: resolvedProvider.providerId, model: modelId }, manifestPlugins, agentId: params.target?.agentId }); } }); const imageInputInference = resolveCustomModelImageInputInference(modelId); const supportsImageInput = imageInputInference.confidence === "known" ? imageInputInference.supportsImageInput : await prompter.confirm({ message: t("wizard.customProvider.imageInput"), initialValue: imageInputInference.supportsImageInput }); const result = applyCustomApiConfig({ config, baseUrl, modelId, compatibility: compatibility ?? "openai", apiKey, providerId: providerIdInput, alias: aliasInput, manifestPlugins, supportsImageInput, ...params.target ? { target: params.target } : {}, ...params.setAsPrimary === false ? { setAsPrimary: false } : {} }); if (result.providerIdRenamedFrom) await prompter.note(t("wizard.customProvider.endpointIdRenamed", { from: result.providerIdRenamedFrom, to: result.providerId }), t("wizard.customProvider.endpointIdTitle")); runtime.log(`Prepared custom provider: ${result.providerId}/${result.modelId}`); return result; } //#endregion export { promptCustomApiConfig as t };