import { u as readConfigFileSnapshot } from "./io-CYCDSQdU.js";
import { r as replaceConfigFile } from "./mutate-CKevtGmS.js";
import "./config-ClYraoTC.js";
import { r as fetchWithTimeout } from "./fetch-timeout-CboMtRsl.js";
//#region src/gateway/server-startup-sealseek-models.ts
const SEALSEEK_PROVIDER_ID = "sealseek";
const MODELS_FETCH_TIMEOUT_MS = 1e4;
const DEFAULT_CONTEXT_WINDOW = 2e5;
const DEFAULT_MAX_TOKENS = 8192;
const DEFAULT_INPUT = ["text", "image"];
/**
* 把 sealseek 的 protocol 字段映射为模型的 api / reasoning。
* 复刻前端 ui/src/ui/sealseek-model-api.ts 的 resolveModelApi：
* - protocol="responses" → Responses API 且开启 reasoning（GPT/o 系列需走 /v1/responses 才能正常调技能）；
* - 其它（chat / 缺失 / 未识别）→ Chat Completions 且关闭 reasoning。
*/
function mapProtocolToApi(protocol) {
	return protocol === "responses" ? {
		api: "openai-responses",
		reasoning: true
	} : {
		api: "openai-completions",
		reasoning: false
	};
}
/**
* 用远端 protocol 重建单个模型条目：
* 已存在的条目只覆盖 api/reasoning，保留其 cost/contextWindow/maxTokens/input/name 等字段；
* 新增条目补默认字段。id 非法时返回 null。
*/
function buildModelEntry(remote, existing) {
	const id = typeof remote.id === "string" ? remote.id.trim() : "";
	if (!id) return null;
	const { api, reasoning } = mapProtocolToApi(remote.protocol);
	if (existing) return {
		...existing,
		api,
		reasoning
	};
	return {
		id,
		name: id,
		api,
		reasoning,
		input: [...DEFAULT_INPUT],
		cost: {
			input: 0,
			output: 0,
			cacheRead: 0,
			cacheWrite: 0
		},
		contextWindow: DEFAULT_CONTEXT_WINDOW,
		maxTokens: DEFAULT_MAX_TOKENS
	};
}
/**
* 拉取远端模型列表。任何网络/解析/鉴权失败都返回 null，由调用方降级跳过。
* 兼容两种响应体：OpenAI 兼容的 { object, data } 与业务包装的 { code, message, data }。
*/
async function fetchRemoteModels(baseUrl, apiKey, log) {
	const url = `${baseUrl.replace(/\/+$/, "")}/models`;
	let body;
	let httpStatus = 0;
	try {
		const res = await fetchWithTimeout(url, { headers: { Authorization: `Bearer ${apiKey}` } }, MODELS_FETCH_TIMEOUT_MS);
		httpStatus = res.status;
		body = await res.json();
	} catch (err) {
		log.warn(`sealseek models fetch failed: ${String(err)}`);
		return null;
	}
	const data = body.data;
	if (!Array.isArray(data)) {
		const code = body.code;
		const message = body.message;
		log.warn(`sealseek models response has no data array (http ${httpStatus}, code=${String(code)}, message=${String(message)})`);
		return null;
	}
	return data;
}
function modelsEqual(a, b) {
	return JSON.stringify(a) === JSON.stringify(b);
}
/**
* 网关启动后刷新 sealseek provider 的模型清单。
*
* 用配置中已存的 apiKey 拉取后端 /models，按每个模型的 protocol 重写其 api/reasoning，
* 再原子写回配置文件。这样重启网关即可让 gpt-5 系列等模型按后端最新协议路由，
* 不再依赖有人手动刷新 Control UI 页面。
*
* 任何一步失败（配置无效 / 缺 key / 网络或鉴权失败 / 写冲突）都降级为 warn，
* 保留现有配置，绝不阻断网关启动。
*/
async function refreshSealseekModelsOnStartup(params) {
	const { log } = params;
	const snapshot = await readConfigFileSnapshot();
	if (!snapshot.valid) {
		log.warn("sealseek models refresh skipped: config invalid");
		return;
	}
	const sourceConfig = snapshot.sourceConfig ?? snapshot.config;
	const provider = sourceConfig.models?.providers?.[SEALSEEK_PROVIDER_ID];
	if (!provider) return;
	const apiKey = typeof provider.apiKey === "string" ? provider.apiKey.trim() : "";
	if (!provider.baseUrl || !apiKey) {
		log.warn("sealseek models refresh skipped: missing baseUrl or apiKey");
		return;
	}
	const remoteModels = await fetchRemoteModels(provider.baseUrl, apiKey, log);
	if (!remoteModels) return;
	if (remoteModels.length === 0) {
		log.warn("sealseek models refresh skipped: remote returned empty list");
		return;
	}
	const existingById = /* @__PURE__ */ new Map();
	for (const model of provider.models ?? []) existingById.set(model.id, model);
	const nextModels = [];
	for (const remote of remoteModels) {
		const entry = buildModelEntry(remote, existingById.get(String(remote.id)));
		if (entry) nextModels.push(entry);
	}
	if (nextModels.length === 0) {
		log.warn("sealseek models refresh skipped: no valid models after mapping");
		return;
	}
	if (modelsEqual(provider.models ?? [], nextModels)) {
		log.info?.("sealseek models already up to date");
		return;
	}
	const nextConfig = structuredClone(sourceConfig);
	const nextProvider = nextConfig.models?.providers?.[SEALSEEK_PROVIDER_ID];
	if (!nextProvider) return;
	nextProvider.models = structuredClone(nextModels);
	try {
		await replaceConfigFile({
			nextConfig,
			baseHash: snapshot.hash
		});
		log.info?.(`sealseek models refreshed: ${nextModels.length} model(s)`);
	} catch (err) {
		log.warn(`sealseek models config write failed: ${String(err)}`);
	}
}
//#endregion
export { refreshSealseekModelsOnStartup };
