custom-provider-pi 0.1.3 → 0.1.4

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.
package/README.md CHANGED
@@ -217,13 +217,19 @@ pi install local:/path/to/custom-provider
217
217
 
218
218
  ### 多模态(图片输入)
219
219
 
220
- 所有模型默认 `input: ["text", "image"]`,pi 会将图片附件发送给模型。纯文本模型不受影响(用户不发图片时不触发任何副作用)。
220
+ 默认 `input: ["text"]`(纯文本)。pi 会自动识别已知的多模态模型并开启图片输入:
221
221
 
222
- 如需某个模型只支持纯文本,通过 `--overrides` 显式设置:
222
+ 1. **OpenRouter 目录**:`architecture.input_modalities` 含 `image` 时自动标记
223
+ 2. **内置模式匹配**:`gpt-4o` / `claude-*` / `gemini-*` / `grok-4+` / `glm-4v` / `qwen-vl` / `minimax-m*` 及 ID 含 `vision` 的模型
224
+ 3. **显式覆盖**:模型对象写 `input: ["text", "image"]` 或 `["text"]`
225
+
226
+ **为什么默认不开**:向不支持图片的模型发图会收到上游 404(如 deepseek-v4-flash-0731 非 vision 版)。未知模型保守处理为纯文本;已知视觉模型自动开启,无需手动配置。
227
+
228
+ **强制指定**:
223
229
 
224
230
  ```bash
225
- /custom-provider add pure-text --base-url ... \
226
- --overrides '{"mymodel":{"input":["text"]}}'
231
+ /custom-provider add my --base-url ... \
232
+ --overrides '{"mymodel":{"input":["text","image"]}}'
227
233
  ```
228
234
 
229
235
  ### 上下文窗口与规格推断
@@ -233,7 +233,7 @@ async function refreshRemoteSpecs(): Promise<void> {
233
233
  }
234
234
 
235
235
  // 查询模型规格:远程实时表 → 本地已知预设 → undefined
236
- function lookupModelSpec(modelId: string): { contextWindow: number; maxTokens: number } | undefined {
236
+ function lookupModelSpec(modelId: string): { contextWindow: number; maxTokens: number; vision?: boolean } | undefined {
237
237
  if (remoteSpecStore && remoteSpecStore.size > 0) {
238
238
  // 候选链逐形态命中:deepseek-v4-flash-free → deepseek-v4-flash → deepseek-v4 …
239
239
  for (const candidate of normalizeModelIdCandidates(modelId)) {
@@ -416,16 +416,17 @@ function stripBasePath(baseUrl: string): string {
416
416
  function normalizeBaseUrl(baseUrl: string, api: string): string {
417
417
  const root = stripBasePath(baseUrl);
418
418
 
419
+ // 已经包含版本号后缀(/v1、/v4、/v1beta 等)或特殊版本路径段 → 不再追加
420
+ // 覆盖智谱 /api/paas/v4、Google /v1beta 等风格
421
+ if (/\/v\d+(?:beta)?(?:\/|$)/i.test(root)) return root;
422
+
419
423
  switch (api) {
420
424
  case "openai-completions":
421
425
  case "openai-responses":
422
426
  case "anthropic-messages":
423
- // OpenAI/Anthropic 兼容协议要求 /v1 前缀;只在"不以 /v1 结尾"时补,
424
- // 避免路径中段含 /v1/ 的历史误判
425
427
  return /\/v1$/i.test(root) ? root : `${root}/v1`;
426
428
 
427
429
  case "google-generative-ai":
428
- // Google Generative AI 需要 /v1beta 或 /v1 前缀
429
430
  if (!/(\/v1|\/v1beta)(\/|$)/i.test(root)) return `${root}/v1`;
430
431
  return root;
431
432
 
@@ -562,12 +563,19 @@ async function fetchModels(
562
563
  headers?: Record<string, string>
563
564
  ): Promise<string[]> {
564
565
  const cleanBase = stripBasePath(baseUrl);
565
- const v1Base = /\/v1$/i.test(cleanBase) ? cleanBase : `${cleanBase}/v1`;
566
+ // 已包含版本段(/v1、/v4、/api/paas/v4)则不追加 /v1
567
+ const hasVersion = /\/v\d+(?:beta)?(?:\/|$)/i.test(cleanBase);
568
+ const v1Base = hasVersion ? cleanBase : `${cleanBase}/v1`;
566
569
 
567
- // 尝试多种端点路径(google / anthropic 也走标准 /models 列表端点)
570
+ // 尝试多种端点路径
568
571
  const endpoints = api === "google-generative-ai"
569
572
  ? [`${v1Base}/models`]
570
- : [ `${v1Base}/models`, `${cleanBase}/models`, `${cleanBase}/api/models` ];
573
+ : [
574
+ `${v1Base}/models`, // 标准 /v1/models
575
+ `${cleanBase}/models`, // 已含版本或原始路径
576
+ `${cleanBase}/v4/models`, // 智谱 /api/paas/v4 后追加
577
+ `${cleanBase}/api/models`,
578
+ ];
571
579
 
572
580
  // 按协议组装认证头:anthropic 用 x-api-key,google 用 x-goog-api-key,其余 Bearer
573
581
  const requestHeaders: Record<string, string> = { ...resolveHeaders(headers) };
@@ -667,10 +675,11 @@ async function probeEndpoint(
667
675
  const base = url.replace(/\/+$/, "");
668
676
  const resolvedKey = apiKey ? resolveValue(apiKey) : "";
669
677
 
670
- // 按常见端点路径尝试(OpenAI 兼容最多,其次 Anthropic)
678
+ // 按常见端点路径尝试(OpenAI 兼容最多,其次 Anthropic/Google
671
679
  const attempts = [
672
680
  { ep: `${base}/v1/models`, api: "openai-completions" },
673
681
  { ep: `${base}/models`, api: "openai-completions" },
682
+ { ep: `${base}/v4/models`, api: "openai-completions" }, // 智谱风格 /api/paas/v4
674
683
  { ep: `${base}/v1/models`, api: "anthropic-messages", useXApiKey: true },
675
684
  ];
676
685
 
@@ -1162,7 +1171,7 @@ export default function customProviderExtension(pi: ExtensionAPI) {
1162
1171
  );
1163
1172
  if (!addMode) return;
1164
1173
 
1165
- // ================= 简单添加路径(3 步完成)=================
1174
+ // ================= 简单添加路径(4 步完成)=================
1166
1175
  if (addMode.startsWith("简单")) {
1167
1176
  // 1. URL
1168
1177
  const simpleUrl = await ctx.ui.input(
@@ -1171,9 +1180,15 @@ export default function customProviderExtension(pi: ExtensionAPI) {
1171
1180
  );
1172
1181
  if (!simpleUrl) return;
1173
1182
 
1174
- // 2. 探测
1183
+ // 2. API Key(留空 = local 无认证)
1184
+ const simpleKeyInput = await ctx.ui.input(
1185
+ "API Key(留空 = 无认证本地服务,支持 $ENV 环境变量)",
1186
+ "$DEEPSEEK_API_KEY"
1187
+ );
1188
+ const simpleApiKey = simpleKeyInput?.trim() || "local";
1189
+
1190
+ // 3. 探测
1175
1191
  ctx.ui.notify("正在探测端点…", "info");
1176
- let simpleApiKey = "local";
1177
1192
  const probeResult = await probeEndpoint(simpleUrl, simpleApiKey);
1178
1193
 
1179
1194
  let simpleModels: (string | IModel)[] = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "custom-provider-pi",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "pi 扩展:统一管理第三方模型 Provider。子命令体系 /custom-provider add|remove|refresh|list|test|config|enable|disable|prune,支持交互向导、flags/JSON 非交互添加、双协议混用(OpenAI + Anthropic)、模型关键字过滤与修剪",
5
5
  "keywords": [
6
6
  "pi-package",