larkin 0.2.87 → 0.2.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -65,20 +65,30 @@ function validatePiBaseUrl(raw) {
|
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* 查询 OpenAI 兼容 provider 的 /models 列表(setup 时校验 --model 是否真实可用)。
|
|
68
|
-
*
|
|
68
|
+
* 网络超时/不可达返回 null(不阻塞 setup——运行时对账会严格兜底);
|
|
69
|
+
* 列表拿到但模型不在其中时抛错。
|
|
69
70
|
*/
|
|
70
71
|
async function listProviderModels(baseUrl, apiKey, fetchImpl = fetch) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
try {
|
|
73
|
+
const url = `${validatePiBaseUrl(baseUrl)}/models`;
|
|
74
|
+
const response = await fetchImpl(url, {
|
|
75
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
76
|
+
signal: AbortSignal.timeout(30_000),
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok)
|
|
79
|
+
throw new Error(`provider /models 查询失败:HTTP ${response.status}`);
|
|
80
|
+
const payload = await response.json();
|
|
81
|
+
if (!payload || !Array.isArray(payload.data))
|
|
82
|
+
throw new Error("provider /models 响应格式非法");
|
|
83
|
+
return payload.data.map((entry) => String(entry.id ?? "")).filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError"
|
|
87
|
+
|| /fetch failed|network|ETIMEDOUT|ECONNREFUSED|ConnectionRefused|Unable to connect/i.test(error.message))) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
82
92
|
}
|
|
83
93
|
function resolveBuiltinPiProviderSetupSelection(input) {
|
|
84
94
|
if (input.distribution !== "builtin")
|
|
@@ -51,20 +51,30 @@ export function validatePiBaseUrl(raw) {
|
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* 查询 OpenAI 兼容 provider 的 /models 列表(setup 时校验 --model 是否真实可用)。
|
|
54
|
-
*
|
|
54
|
+
* 网络超时/不可达返回 null(不阻塞 setup——运行时对账会严格兜底);
|
|
55
|
+
* 列表拿到但模型不在其中时抛错。
|
|
55
56
|
*/
|
|
56
57
|
export async function listProviderModels(baseUrl, apiKey, fetchImpl = fetch) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
try {
|
|
59
|
+
const url = `${validatePiBaseUrl(baseUrl)}/models`;
|
|
60
|
+
const response = await fetchImpl(url, {
|
|
61
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
62
|
+
signal: AbortSignal.timeout(30_000),
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok)
|
|
65
|
+
throw new Error(`provider /models 查询失败:HTTP ${response.status}`);
|
|
66
|
+
const payload = await response.json();
|
|
67
|
+
if (!payload || !Array.isArray(payload.data))
|
|
68
|
+
throw new Error("provider /models 响应格式非法");
|
|
69
|
+
return payload.data.map((entry) => String(entry.id ?? "")).filter(Boolean);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError"
|
|
73
|
+
|| /fetch failed|network|ETIMEDOUT|ECONNREFUSED|ConnectionRefused|Unable to connect/i.test(error.message))) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
68
78
|
}
|
|
69
79
|
export function resolveBuiltinPiProviderSetupSelection(input) {
|
|
70
80
|
if (input.distribution !== "builtin")
|
|
@@ -608,15 +608,22 @@ export async function main() {
|
|
|
608
608
|
distribution: "builtin",
|
|
609
609
|
preset: presetId,
|
|
610
610
|
model: setupModel ?? presetDef?.defaultModel ?? "",
|
|
611
|
-
|
|
611
|
+
// 显式 --base-url 优先于预设默认值(自定义网关场景,如 opencode)。
|
|
612
|
+
...(setupBaseUrl ? { baseUrl: validatePiBaseUrl(setupBaseUrl) } : presetDef ? { baseUrl: presetDef.baseUrl } : {}),
|
|
612
613
|
};
|
|
613
614
|
if (setupModel && raw.baseUrl) {
|
|
614
615
|
// Owner 决策:模型名必须来自 provider 的权威可用列表,输错即报错,不做运行时猜测。
|
|
616
|
+
// 网络超时/不可达时不阻塞 setup(null),运行时对账会严格兜底。
|
|
615
617
|
const availableIds = await listProviderModels(raw.baseUrl, setupApiKey);
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
618
|
+
if (availableIds !== null) {
|
|
619
|
+
const matched = availableIds.some((id) => id === setupModel || id.endsWith(`/${setupModel}`));
|
|
620
|
+
if (!matched) {
|
|
621
|
+
const preview = availableIds.slice(0, 12).join(", ") + (availableIds.length > 12 ? ", …" : "");
|
|
622
|
+
throw new Error(`未知模型 ${setupModel};provider 可用模型:[${preview || "无"}]`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
say("[setup] 网络暂不可达,跳过 /models 预校验;运行时将对模型严格对账");
|
|
620
627
|
}
|
|
621
628
|
}
|
|
622
629
|
stageBuiltinPiProvider(CFG_DIR, id, { ...raw, apiKey: setupApiKey });
|