pi-cliproxyapi-autoconnect 1.2.0 → 1.2.1

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.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/index.ts +68 -68
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -289,6 +289,11 @@ CLIProxyAPI 官方建议调大 nginx 的 `proxy_read_timeout`,Cloudflare 免
289
289
 
290
290
  ## 更新日志
291
291
 
292
+ ### 1.2.1
293
+
294
+ - pi TUI 内的登录提示、地址选择、错误信息和 `/cliproxy` 输出全部改为英文。
295
+ - provider 显示名仍为 `CLIProxy`,`/login` 入口、`/cliproxy` 命令和模型前缀 `cliproxy/` 不变。
296
+
292
297
  ### 1.2.0
293
298
 
294
299
  - 包名从 `pi-cliproxy` 改为 `pi-cliproxyapi-autoconnect`;provider ID、`cliproxy.json`、`/cliproxy` 命令和模型前缀 `cliproxy/` 保持不变。
package/index.ts CHANGED
@@ -51,7 +51,7 @@ import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent"
51
51
 
52
52
  const PROVIDER_ID = "cliproxy";
53
53
  const PROVIDER_NAME = "CLIProxy";
54
- const AUTH_NAME = "CLIProxy(域名 + API Key)";
54
+ const AUTH_NAME = "CLIProxy (domain + API key)";
55
55
  const CONFIG_FILE_NAME = "cliproxy.json";
56
56
  const CONFIG_VERSION = 2;
57
57
  const REQUEST_TIMEOUT_MS = 10_000;
@@ -65,9 +65,9 @@ let lastRefreshError: string | undefined;
65
65
  type SupportedApi = "openai-completions" | "anthropic-messages" | "google-generative-ai";
66
66
 
67
67
  const PROVIDER_APIS: Readonly<Record<SupportedApi, string>> = {
68
- "openai-completions": "OpenAI 兼容",
69
- "anthropic-messages": "Anthropic 兼容",
70
- "google-generative-ai": "Gemini 兼容",
68
+ "openai-completions": "OpenAI compatible",
69
+ "anthropic-messages": "Anthropic compatible",
70
+ "google-generative-ai": "Gemini compatible",
71
71
  };
72
72
 
73
73
  // =============================================================================
@@ -480,7 +480,7 @@ function isLocalHostname(hostname: string): boolean {
480
480
  */
481
481
  function normalizeDomain(input: string): NormalizedDomain {
482
482
  const original = input.trim();
483
- if (!original) throw new Error("未输入绑定域名");
483
+ if (!original) throw new Error("Domain is required.");
484
484
  const implicitScheme = !/^[a-z][a-z\d+.-]*:\/\//iu.test(original);
485
485
  const withScheme = implicitScheme
486
486
  ? `${isLocalHostname(original.split("/")[0]?.split(":")[0] ?? "") ? "http" : "https"}://${original}`
@@ -489,10 +489,10 @@ function normalizeDomain(input: string): NormalizedDomain {
489
489
  try {
490
490
  parsed = new URL(withScheme);
491
491
  } catch {
492
- throw new Error(`无法解析域名:"${input}"。示例:proxy.example.com 或 https://proxy.example.com`);
492
+ throw new Error(`Could not parse domain: "${input}". Example: proxy.example.com or https://proxy.example.com`);
493
493
  }
494
494
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
495
- throw new Error(`仅支持 http/https 地址,收到:${parsed.protocol}`);
495
+ throw new Error(`Only http/https URLs are supported; received: ${parsed.protocol}`);
496
496
  }
497
497
  parsed.username = "";
498
498
  parsed.password = "";
@@ -527,7 +527,7 @@ function geminiBaseUrl(rootUrl: string): string {
527
527
 
528
528
  function abortSignalWithTimeout(signal: AbortSignal, timeoutMs: number): { signal: AbortSignal; cleanup: () => void } {
529
529
  const controller = new AbortController();
530
- const timer = setTimeout(() => controller.abort(new Error("请求超时")), timeoutMs);
530
+ const timer = setTimeout(() => controller.abort(new Error("Request timed out")), timeoutMs);
531
531
  const onAbort = () => controller.abort(signal.reason);
532
532
  if (signal.aborted) controller.abort(signal.reason);
533
533
  else signal.addEventListener("abort", onAbort, { once: true });
@@ -549,12 +549,12 @@ async function safeText(response: Response): Promise<string> {
549
549
  }
550
550
 
551
551
  function describeFetchError(error: unknown, ownSignal: AbortSignal, outerSignal: AbortSignal): string {
552
- if (outerSignal.aborted) return "已取消";
553
- if (ownSignal.aborted) return "请求超时";
552
+ if (outerSignal.aborted) return "Cancelled";
553
+ if (ownSignal.aborted) return "Request timed out";
554
554
  if (error instanceof Error) {
555
555
  const cause = error.cause;
556
556
  if (cause instanceof Error && cause.message && cause.message !== error.message) {
557
- return `${error.message}(${cause.message})`;
557
+ return `${error.message} (${cause.message})`;
558
558
  }
559
559
  return error.message;
560
560
  }
@@ -572,7 +572,7 @@ function failureKindForStatus(status: number): FetchFailure["kind"] {
572
572
  function makeHttpFailure(status: number, body: string): FetchFailure {
573
573
  return {
574
574
  ok: false,
575
- error: `HTTP ${status}${body ? `:${body}` : ""}`,
575
+ error: `HTTP ${status}${body ? `: ${body}` : ""}`,
576
576
  kind: failureKindForStatus(status),
577
577
  status,
578
578
  };
@@ -587,8 +587,8 @@ async function probeCliproxy(rootUrl: string, signal: AbortSignal): Promise<stri
587
587
  });
588
588
  if (!response.ok) return undefined;
589
589
  const body = (await response.json().catch(() => undefined)) as { status?: unknown } | undefined;
590
- if (body?.status === "ok") return "已探测到 CLIProxyAPI(/healthz 正常)";
591
- return "已探测到 /healthz,但响应不是 CLIProxyAPI 格式";
590
+ if (body?.status === "ok") return "Detected CLIProxyAPI (/healthz OK)";
591
+ return "Detected /healthz, but the response is not CLIProxyAPI format";
592
592
  } catch {
593
593
  return undefined;
594
594
  } finally {
@@ -638,7 +638,7 @@ async function fetchOpenAIModels(baseUrl: string, apiKey: string, signal: AbortS
638
638
  headers: {
639
639
  Accept: "application/json",
640
640
  Authorization: `Bearer ${apiKey}`,
641
- "User-Agent": "pi-cliproxyapi-autoconnect/1.2",
641
+ "User-Agent": "pi-cliproxyapi-autoconnect/1.2.1",
642
642
  },
643
643
  signal: requestSignal,
644
644
  });
@@ -648,7 +648,7 @@ async function fetchOpenAIModels(baseUrl: string, apiKey: string, signal: AbortS
648
648
  }
649
649
  const payload = await response.json().catch(() => undefined);
650
650
  const models = parseOpenAIModelList(payload);
651
- if (!models) return { ok: false, error: "响应不是 OpenAI 兼容的模型列表(缺少 data 数组)", kind: "format" };
651
+ if (!models) return { ok: false, error: "Response is not an OpenAI-compatible model list (missing data array)", kind: "format" };
652
652
  return { ok: true, models };
653
653
  } catch (error) {
654
654
  return { ok: false, error: describeFetchError(error, requestSignal, signal), kind: "network" };
@@ -662,7 +662,7 @@ async function fetchGeminiModels(baseUrl: string, apiKey: string, signal: AbortS
662
662
  const { signal: requestSignal, cleanup } = abortSignalWithTimeout(signal, REQUEST_TIMEOUT_MS);
663
663
  try {
664
664
  const response = await fetch(url, {
665
- headers: { Accept: "application/json", "User-Agent": "pi-cliproxyapi-autoconnect/1.2" },
665
+ headers: { Accept: "application/json", "User-Agent": "pi-cliproxyapi-autoconnect/1.2.1" },
666
666
  signal: requestSignal,
667
667
  });
668
668
  if (!response.ok) {
@@ -671,7 +671,7 @@ async function fetchGeminiModels(baseUrl: string, apiKey: string, signal: AbortS
671
671
  }
672
672
  const payload = await response.json().catch(() => undefined);
673
673
  const models = parseOpenAIModelList(payload);
674
- if (!models) return { ok: false, error: "响应不是 Gemini 兼容的模型列表(缺少 models 数组)", kind: "format" };
674
+ if (!models) return { ok: false, error: "Response is not a Gemini-compatible model list (missing models array)", kind: "format" };
675
675
  return { ok: true, models };
676
676
  } catch (error) {
677
677
  return { ok: false, error: describeFetchError(error, requestSignal, signal), kind: "network" };
@@ -703,7 +703,7 @@ async function fetchAnthropicModels(baseUrl: string, apiKey: string, signal: Abo
703
703
  // Anthropic's x-api-key header; sending both is harmless upstream.
704
704
  Authorization: `Bearer ${apiKey}`,
705
705
  "anthropic-version": "2023-06-01",
706
- "User-Agent": "pi-cliproxyapi-autoconnect/1.2",
706
+ "User-Agent": "pi-cliproxyapi-autoconnect/1.2.1",
707
707
  },
708
708
  signal: requestSignal,
709
709
  });
@@ -712,7 +712,7 @@ async function fetchAnthropicModels(baseUrl: string, apiKey: string, signal: Abo
712
712
  if (response.status === 404 || response.status === 405 || response.status === 501) {
713
713
  return {
714
714
  ok: false,
715
- error: `Anthropic 兼容接口没有可用的模型列表(HTTP ${response.status})`,
715
+ error: `The Anthropic-compatible endpoint does not expose a usable model list (HTTP ${response.status})`,
716
716
  kind: "unsupported",
717
717
  status: response.status,
718
718
  };
@@ -721,7 +721,7 @@ async function fetchAnthropicModels(baseUrl: string, apiKey: string, signal: Abo
721
721
  }
722
722
  const payload = await response.json().catch(() => undefined);
723
723
  const models = parseOpenAIModelList(payload);
724
- if (!models) return { ok: false, error: "响应不是 Anthropic 兼容的模型列表(缺少 data 数组)", kind: "format" };
724
+ if (!models) return { ok: false, error: "Response is not an Anthropic-compatible model list (missing data array)", kind: "format" };
725
725
  return { ok: true, models };
726
726
  } catch (error) {
727
727
  return { ok: false, error: describeFetchError(error, requestSignal, signal), kind: "network" };
@@ -833,9 +833,9 @@ function isAuthFailure(failure: FetchFailure | undefined): boolean {
833
833
 
834
834
  function authFailureMessage(failure: FetchFailure): string {
835
835
  if (failure.error.includes("unsafe_example_api_key")) {
836
- return "CLIProxy 服务端仍在使用示例 API Key(unsafe_example_api_key),请先修改服务端 access.api-keys 并重启 CLIProxyAPI。";
836
+ return "CLIProxyAPI is still using the example API key (unsafe_example_api_key). Update access.api-keys on the server and restart CLIProxyAPI.";
837
837
  }
838
- return `API Key 验证失败:${failure.error}`;
838
+ return `API key verification failed: ${failure.error}`;
839
839
  }
840
840
 
841
841
  function normalizeApiKey(value: string): string {
@@ -844,7 +844,7 @@ function normalizeApiKey(value: string): string {
844
844
 
845
845
  function compactMessage(value: string, maxLength = 180): string {
846
846
  const singleLine = value.replace(/\s+/gu, " ").trim();
847
- return singleLine.length > maxLength ? `${singleLine.slice(0, maxLength)}…` : singleLine;
847
+ return singleLine.length > maxLength ? `${singleLine.slice(0, maxLength)}...` : singleLine;
848
848
  }
849
849
 
850
850
  /**
@@ -870,7 +870,7 @@ function summarizeDiscoveryAttempts(attempts: DiscoveryAttempt[]): string {
870
870
  return attempts
871
871
  .map(
872
872
  ({ candidate, failure }) =>
873
- `${PROVIDER_APIS[candidate.api]} ${candidate.baseUrl}:${compactMessage(failure.error)}`,
873
+ `${PROVIDER_APIS[candidate.api]} ${candidate.baseUrl}: ${compactMessage(failure.error)}`,
874
874
  )
875
875
  .join("\n");
876
876
  }
@@ -905,10 +905,10 @@ async function autoDiscoverModels(
905
905
  async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCredential> {
906
906
  const domainInput = await interaction.prompt({
907
907
  type: "text",
908
- message: "CLIProxy 绑定域名(例如 proxy.example.com 或 https://proxy.example.com)",
908
+ message: "CLIProxy bound domain (for example proxy.example.com or https://proxy.example.com)",
909
909
  placeholder: "proxy.example.com",
910
910
  });
911
- if (!domainInput.trim()) throw new Error("未输入绑定域名");
911
+ if (!domainInput.trim()) throw new Error("Domain is required.");
912
912
 
913
913
  const { rootUrl: initialRootUrl, implicitScheme } = normalizeDomain(domainInput);
914
914
  const promptForApiKey = async (message: string): Promise<string> => {
@@ -919,10 +919,10 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
919
919
  placeholder: "sk-...",
920
920
  }),
921
921
  );
922
- if (!value) throw new Error("未输入 API Key");
922
+ if (!value) throw new Error("API key is required.");
923
923
  return value;
924
924
  };
925
- let apiKey = await promptForApiKey("CLIProxy API Key(保存在 pi 的 auth.json)");
925
+ let apiKey = await promptForApiKey("CLIProxy API key (stored in pi's auth.json)");
926
926
 
927
927
  // A bare host may be either http or https; only try the alternate scheme
928
928
  // automatically when the user did not spell one out.
@@ -944,7 +944,7 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
944
944
  autoDiscoverModels(candidates, apiKey, interaction.signal, (candidate, index, total) => {
945
945
  interaction.notify({
946
946
  type: "progress",
947
- message: `正在尝试 ${PROVIDER_APIS[candidate.api]}:${candidate.baseUrl}(${index + 1}/${total})…`,
947
+ message: `Trying ${PROVIDER_APIS[candidate.api]}: ${candidate.baseUrl} (${index + 1}/${total})...`,
948
948
  });
949
949
  });
950
950
 
@@ -962,9 +962,9 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
962
962
  if (authFailure) {
963
963
  interaction.notify({
964
964
  type: "info",
965
- message: `所有兼容地址都未能通过鉴权:${compactMessage(authFailure.error)}\n请重新输入一次 API Key。`,
965
+ message: `All compatible endpoints failed authentication: ${compactMessage(authFailure.error)}\nEnter the API key again.`,
966
966
  });
967
- apiKey = await promptForApiKey("重新输入 CLIProxy API Key");
967
+ apiKey = await promptForApiKey("Re-enter the CLIProxy API key");
968
968
  outcome = await discover();
969
969
  authFailure = outcome.ok ? undefined : firstAuthFailure(outcome.attempts);
970
970
  if (authFailure) throw new Error(authFailureMessage(authFailure));
@@ -979,7 +979,7 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
979
979
  lastError = undefined;
980
980
  interaction.notify({
981
981
  type: "info",
982
- message: `自动选择 ${PROVIDER_APIS[api]}:${baseUrl}(${models.length} 个模型)。`,
982
+ message: `Selected ${PROVIDER_APIS[api]}: ${baseUrl} (${models.length} models).`,
983
983
  });
984
984
  } else if (outcome.empty) {
985
985
  // Every address answered, but no upstream credentials are available.
@@ -988,26 +988,26 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
988
988
  baseUrl = outcome.empty.baseUrl;
989
989
  api = outcome.empty.api;
990
990
  models = [];
991
- lastError = "所有兼容地址均返回空模型列表";
991
+ lastError = "All compatible endpoints returned an empty model list";
992
992
  } else {
993
993
  const responding = outcome.attempts.find(({ failure }) => failure.kind !== "network");
994
994
  activeRoot = responding?.candidate.rootUrl ?? initialRootUrl;
995
- lastError = summarizeDiscoveryAttempts(outcome.attempts) || "未知错误";
995
+ lastError = summarizeDiscoveryAttempts(outcome.attempts) || "Unknown error";
996
996
 
997
997
  const diagnostic = await probeCliproxy(activeRoot, interaction.signal);
998
998
  interaction.notify({
999
999
  type: "info",
1000
- message: `已自动尝试 ${outcome.attempts.length} 个兼容地址,均未获取到模型列表:\n${lastError}${diagnostic ? `\n${diagnostic}` : ""}`,
1000
+ message: `Tried ${outcome.attempts.length} compatible endpoints, but none returned a model list:\n${lastError}${diagnostic ? `\n${diagnostic}` : ""}`,
1001
1001
  });
1002
1002
 
1003
1003
  const choice = await interaction.prompt({
1004
1004
  type: "select",
1005
- message: "请选择 CLIProxy 兼容地址",
1005
+ message: "Select a CLIProxy-compatible endpoint",
1006
1006
  options: [
1007
- { id: "retry-openai", label: `重试 OpenAI 兼容地址:${openAIBaseUrl(activeRoot)}` },
1008
- { id: "anthropic", label: `Anthropic 兼容地址:${anthropicBaseUrl(activeRoot)}` },
1009
- { id: "gemini", label: `Gemini 兼容地址:${geminiBaseUrl(activeRoot)}` },
1010
- { id: "custom", label: "自定义兼容地址…" },
1007
+ { id: "retry-openai", label: `Retry OpenAI compatible: ${openAIBaseUrl(activeRoot)}` },
1008
+ { id: "anthropic", label: `Anthropic compatible: ${anthropicBaseUrl(activeRoot)}` },
1009
+ { id: "gemini", label: `Gemini compatible: ${geminiBaseUrl(activeRoot)}` },
1010
+ { id: "custom", label: "Custom compatible endpoint..." },
1011
1011
  ],
1012
1012
  });
1013
1013
 
@@ -1035,7 +1035,7 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
1035
1035
  if (anthropic.kind === "unsupported") {
1036
1036
  interaction.notify({
1037
1037
  type: "info",
1038
- message: "该 Anthropic 兼容地址没有模型列表端点,请在下一步手动输入模型 ID。",
1038
+ message: "This Anthropic-compatible endpoint has no model list endpoint. Enter model IDs manually in the next step.",
1039
1039
  });
1040
1040
  }
1041
1041
  }
@@ -1054,18 +1054,18 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
1054
1054
  const customInput = (
1055
1055
  await interaction.prompt({
1056
1056
  type: "text",
1057
- message: "自定义兼容地址(完整 base URL,例如 https://proxy.example.com/v1)",
1057
+ message: "Custom compatible endpoint (full base URL, for example https://proxy.example.com/v1)",
1058
1058
  placeholder: "https://proxy.example.com/v1",
1059
1059
  })
1060
1060
  ).trim();
1061
- if (!customInput) throw new Error("未输入自定义地址");
1061
+ if (!customInput) throw new Error("A custom endpoint is required.");
1062
1062
  const protocolChoice = await interaction.prompt({
1063
1063
  type: "select",
1064
- message: "该地址使用的兼容协议",
1064
+ message: "Compatible protocol for this endpoint",
1065
1065
  options: [
1066
- { id: "openai", label: "OpenAI 兼容(/v1/chat/completions)" },
1067
- { id: "anthropic", label: "Anthropic 兼容(/v1/messages)" },
1068
- { id: "gemini", label: "Gemini 兼容(/v1beta)" },
1066
+ { id: "openai", label: "OpenAI compatible (/v1/chat/completions)" },
1067
+ { id: "anthropic", label: "Anthropic compatible (/v1/messages)" },
1068
+ { id: "gemini", label: "Gemini compatible (/v1beta)" },
1069
1069
  ],
1070
1070
  });
1071
1071
  api =
@@ -1089,7 +1089,7 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
1089
1089
  if (models.length === 0) {
1090
1090
  const manual = await interaction.prompt({
1091
1091
  type: "text",
1092
- message: "未获取到模型列表。可手动输入模型 ID(逗号或空格分隔,留空则暂不添加模型)",
1092
+ message: "No model list was retrieved. Enter model IDs manually (comma or space separated; leave empty to add none for now)",
1093
1093
  placeholder: "claude-sonnet-4-5, gemini-2.5-pro",
1094
1094
  });
1095
1095
  models = parseManualModelIds(manual);
@@ -1120,19 +1120,19 @@ async function runLogin(interaction: ProviderAuthInteraction): Promise<ApiKeyCre
1120
1120
  writeConfig(config);
1121
1121
  } catch (error) {
1122
1122
  throw new Error(
1123
- `CLIProxy 配置写入失败:${error instanceof Error ? error.message : String(error)}(${configPath()})`,
1123
+ `Failed to write CLIProxy config: ${error instanceof Error ? error.message : String(error)} (${configPath()})`,
1124
1124
  );
1125
1125
  }
1126
1126
 
1127
1127
  if (models.length > 0) {
1128
1128
  interaction.notify({
1129
1129
  type: "info",
1130
- message: `CLIProxy 配置已保存:${baseUrl}(${models.length} 个模型,${PROVIDER_APIS[api]})。`,
1130
+ message: `CLIProxy config saved: ${baseUrl} (${models.length} models, ${PROVIDER_APIS[api]}).`,
1131
1131
  });
1132
1132
  } else {
1133
1133
  interaction.notify({
1134
1134
  type: "info",
1135
- message: `CLIProxy 配置已保存:${baseUrl},但暂未导入任何模型。可使用 /cliproxy refresh 重试。`,
1135
+ message: `CLIProxy config saved: ${baseUrl}, but no models were imported. Use /cliproxy refresh to retry.`,
1136
1136
  });
1137
1137
  }
1138
1138
 
@@ -1183,7 +1183,7 @@ async function refreshProviderModels(context: RefreshModelsContext): Promise<rea
1183
1183
  writeConfig({
1184
1184
  ...config,
1185
1185
  updatedAt: Date.now(),
1186
- lastError: "上游返回的模型列表为空(保留手动模型)",
1186
+ lastError: "Upstream returned an empty model list (manual models kept)",
1187
1187
  });
1188
1188
  } catch {
1189
1189
  // Best effort.
@@ -1197,7 +1197,7 @@ async function refreshProviderModels(context: RefreshModelsContext): Promise<rea
1197
1197
  models: enriched,
1198
1198
  source: "auto",
1199
1199
  updatedAt: Date.now(),
1200
- ...(enriched.length === 0 ? { lastError: "上游返回的模型列表为空" } : {}),
1200
+ ...(enriched.length === 0 ? { lastError: "Upstream returned an empty model list" } : {}),
1201
1201
  };
1202
1202
  if (enriched.length > 0) delete updated.lastError;
1203
1203
  try {
@@ -1254,14 +1254,14 @@ export default function (pi: ExtensionAPI): void {
1254
1254
  pi.registerProvider(buildProvider(readConfig()));
1255
1255
 
1256
1256
  pi.registerCommand("cliproxy", {
1257
- description: "查看 CLIProxy 配置,或执行 /cliproxy refresh 刷新模型列表",
1257
+ description: "Show CLIProxy config, or run /cliproxy refresh to refresh models",
1258
1258
  handler: async (args, ctx) => {
1259
1259
  const subcommand = args.trim().toLowerCase();
1260
1260
  const config = readConfig();
1261
1261
 
1262
1262
  if (subcommand === "refresh") {
1263
1263
  if (!config) {
1264
- ctx.ui.notify("尚未配置 CLIProxy。请运行 /login 并选择“Sign in with an API key” → CLIProxy。", "warning");
1264
+ ctx.ui.notify("CLIProxy is not configured. Run /login and choose \"Sign in with an API key\" -> CLIProxy.", "warning");
1265
1265
  return;
1266
1266
  }
1267
1267
  let refreshError: string | undefined;
@@ -1277,20 +1277,20 @@ export default function (pi: ExtensionAPI): void {
1277
1277
  }
1278
1278
  const count = ctx.modelRegistry.getAll().filter((model) => model.provider === PROVIDER_ID).length;
1279
1279
  if (refreshError) {
1280
- ctx.ui.notify(`CLIProxy 模型刷新失败:${refreshError}`, "error");
1280
+ ctx.ui.notify(`CLIProxy model refresh failed: ${refreshError}`, "error");
1281
1281
  } else if (lastRefreshError) {
1282
- ctx.ui.notify(`CLIProxy 模型刷新失败,继续使用缓存(共 ${count} 个):${lastRefreshError}`, "warning");
1282
+ ctx.ui.notify(`CLIProxy model refresh failed; using cached models (${count}): ${lastRefreshError}`, "warning");
1283
1283
  } else if (count === 0) {
1284
- ctx.ui.notify("CLIProxy 已刷新,但仍未获取到模型。请检查 API Key 或运行 /login 重新配置。", "warning");
1284
+ ctx.ui.notify("CLIProxy refreshed, but no models were found. Check the API key or run /login again.", "warning");
1285
1285
  } else {
1286
- ctx.ui.notify(`CLIProxy 模型已刷新,共 ${count} 个。`, "info");
1286
+ ctx.ui.notify(`CLIProxy models refreshed: ${count}.`, "info");
1287
1287
  }
1288
1288
  return;
1289
1289
  }
1290
1290
 
1291
1291
  if (!config) {
1292
1292
  ctx.ui.notify(
1293
- `尚未配置 CLIProxy。\n请运行 /login → “Sign in with an API key” → CLIProxy,输入绑定域名和 API Key。\n配置将写入:${configPath()}`,
1293
+ `CLIProxy is not configured.\nRun /login -> "Sign in with an API key" -> CLIProxy and enter the bound domain and API key.\nConfig will be written to: ${configPath()}`,
1294
1294
  "info",
1295
1295
  );
1296
1296
  return;
@@ -1299,13 +1299,13 @@ export default function (pi: ExtensionAPI): void {
1299
1299
  const count = ctx.modelRegistry.getAll().filter((model) => model.provider === PROVIDER_ID).length;
1300
1300
  const lastError = lastRefreshError ?? config.lastError;
1301
1301
  const lines = [
1302
- `CLIProxy:${config.baseUrl}`,
1303
- `协议:${PROVIDER_APIS[config.api]}(${config.api})`,
1304
- `模型:${count} 个${config.models.length !== count ? `(缓存 ${config.models.length} 个)` : ""}`,
1305
- `配置:${configPath()}`,
1306
- lastError ? `最近错误:${lastError}` : undefined,
1307
- "重新配置:/login → “Sign in with an API key” → CLIProxy",
1308
- "刷新模型:/cliproxy refresh",
1302
+ `CLIProxy: ${config.baseUrl}`,
1303
+ `Protocol: ${PROVIDER_APIS[config.api]} (${config.api})`,
1304
+ `Models: ${count}${config.models.length !== count ? ` (${config.models.length} cached)` : ""}`,
1305
+ `Config: ${configPath()}`,
1306
+ lastError ? `Last error: ${lastError}` : undefined,
1307
+ "Reconfigure: /login -> \"Sign in with an API key\" -> CLIProxy",
1308
+ "Refresh models: /cliproxy refresh",
1309
1309
  ].filter((line): line is string => Boolean(line));
1310
1310
  ctx.ui.notify(lines.join("\n"), "info");
1311
1311
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cliproxyapi-autoconnect",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLIProxyAPI 自动连接插件 for pi:/login 输入绑定域名 + API Key,自动探测 OpenAI/Gemini/Anthropic 兼容地址并注册模型。",
5
5
  "type": "module",
6
6
  "license": "MIT",