dsh-coding-subscription-oauth 0.5.3 → 0.5.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.
@@ -0,0 +1,206 @@
1
+ /** Static tables for the Coding OAuth settings UI. */
2
+
3
+ import type { GrokBuildSettingsKey } from "./locales.ts";
4
+ import type {
5
+ CapabilityFlagKey,
6
+ CapabilityLimitKey,
7
+ ProviderCardDefinition,
8
+ SettingsTabId,
9
+ SourceCommitAction,
10
+ SourceConflict,
11
+ SourceKind,
12
+ SourcePreviewAction,
13
+ SourceReason,
14
+ } from "./types.ts";
15
+
16
+ export const STATUS_PATH = "/plugins/dsh-grok-build/oauth/status";
17
+ export const LOGIN_PATH = "/plugins/dsh-grok-build/oauth/login";
18
+ export const LOGIN_CODE_PATH = "/plugins/dsh-grok-build/oauth/code";
19
+ export const LOGIN_CANCEL_PATH = "/plugins/dsh-grok-build/oauth/cancel";
20
+ export const LOGOUT_PATH = "/plugins/dsh-grok-build/oauth/logout";
21
+ export const MODELS_PATH = "/plugins/dsh-grok-build/oauth/models";
22
+ export const SOURCES_PATH = "/plugins/dsh-grok-build/oauth/sources";
23
+ export const SOURCES_PREVIEW_PATH = "/plugins/dsh-grok-build/oauth/sources/preview";
24
+ export const SOURCES_COMMIT_PATH = "/plugins/dsh-grok-build/oauth/sources/commit";
25
+ export const SOURCES_CANCEL_PATH = "/plugins/dsh-grok-build/oauth/sources/cancel";
26
+ export const CAPABILITIES_PATH = "/plugins/dsh-grok-build/capabilities";
27
+ export const CODEX_USAGE_PATH = "/plugins/dsh-grok-build/codex/usage";
28
+ export const IMAGINE_CREDENTIAL_PATH = "/plugins/dsh-grok-build/imagine/credential-status";
29
+ export const GATEWAY_PATH = "/plugins/dsh-grok-build/gateway";
30
+ export const GATEWAY_REVEAL_PATH = "/plugins/dsh-grok-build/gateway/reveal";
31
+ export const GATEWAY_ROTATE_PATH = "/plugins/dsh-grok-build/gateway/rotate";
32
+ export const POLL_INTERVAL_MS = 1_000;
33
+ export const HOUR_MS = 60 * 60 * 1000;
34
+
35
+ export const SOURCE_KINDS: readonly SourceKind[] = ["grok", "codex", "kimi", "claude"];
36
+ export const SOURCE_REASONS: readonly SourceReason[] = ["missing", "unsafe", "invalid", "too_large"];
37
+ export const SOURCE_CONFLICTS: readonly SourceConflict[] = [
38
+ "none",
39
+ "same_credential",
40
+ "same_account",
41
+ "different_account",
42
+ "unknown_account",
43
+ "unreadable_destination",
44
+ "unsafe_destination",
45
+ ];
46
+ export const SOURCE_PREVIEW_ACTIONS: readonly SourcePreviewAction[] = ["import", "reuse", "overwrite", "blocked"];
47
+ export const SOURCE_COMMIT_ACTIONS: readonly SourceCommitAction[] = ["imported", "unchanged", "overwritten"];
48
+
49
+ export const SOURCE_DEFAULT_PATH: { readonly [K in SourceKind]: string } = {
50
+ grok: "~/.grok/auth.json",
51
+ codex: "~/.codex/auth.json",
52
+ kimi: "~/.kimi/credentials/kimi-code.json",
53
+ claude: "~/.claude/.credentials.json",
54
+ };
55
+
56
+ export const SOURCE_KIND_KEY: { readonly [K in SourceKind]: GrokBuildSettingsKey } = {
57
+ grok: "sourceKindGrok",
58
+ codex: "sourceKindCodex",
59
+ kimi: "sourceKindKimi",
60
+ claude: "sourceKindClaude",
61
+ };
62
+
63
+ export const SOURCE_REASON_KEY: { readonly [K in SourceReason]: GrokBuildSettingsKey } = {
64
+ missing: "sourceReasonMissing",
65
+ unsafe: "sourceReasonUnsafe",
66
+ invalid: "sourceReasonInvalid",
67
+ too_large: "sourceReasonTooLarge",
68
+ };
69
+
70
+ export const SOURCE_CONFLICT_KEY: { readonly [K in SourceConflict]: GrokBuildSettingsKey } = {
71
+ none: "sourceConflictNone",
72
+ same_credential: "sourceConflictSameCredential",
73
+ same_account: "sourceConflictSameAccount",
74
+ different_account: "sourceConflictDifferentAccount",
75
+ unknown_account: "sourceConflictUnknownAccount",
76
+ unreadable_destination: "sourceConflictUnreadableDestination",
77
+ unsafe_destination: "sourceConflictUnsafeDestination",
78
+ };
79
+
80
+ export const SOURCE_PREVIEW_ACTION_KEY: { readonly [K in SourcePreviewAction]: GrokBuildSettingsKey } = {
81
+ import: "sourceActionImport",
82
+ reuse: "sourceActionReuse",
83
+ overwrite: "sourceActionOverwrite",
84
+ blocked: "sourceActionBlocked",
85
+ };
86
+
87
+ export const SOURCE_COMMIT_ACTION_KEY: { readonly [K in SourceCommitAction]: GrokBuildSettingsKey } = {
88
+ imported: "sourceCommitImported",
89
+ unchanged: "sourceCommitUnchanged",
90
+ overwritten: "sourceCommitOverwritten",
91
+ };
92
+
93
+ export const CAPABILITY_TOGGLES: readonly {
94
+ key: CapabilityFlagKey;
95
+ label: GrokBuildSettingsKey;
96
+ hint: GrokBuildSettingsKey;
97
+ requiresImages?: true;
98
+ }[] = [
99
+ { key: "codexSearch", label: "capCodexSearch", hint: "capCodexSearchHint" },
100
+ { key: "codexImages", label: "capCodexImages", hint: "capCodexImagesHint" },
101
+ { key: "codexImageEdits", label: "capCodexImageEdits", hint: "capCodexImageEditsHint", requiresImages: true },
102
+ { key: "codexUsage", label: "capCodexUsage", hint: "capCodexUsageHint" },
103
+ { key: "codexFast", label: "capCodexFast", hint: "capCodexFastHint" },
104
+ { key: "grokImagineImage", label: "capGrokImagineImage", hint: "capGrokImagineImageHint" },
105
+ { key: "grokImagineVideo", label: "capGrokImagineVideo", hint: "capGrokImagineVideoHint" },
106
+ ];
107
+
108
+ export const CAPABILITY_LIMITS: readonly {
109
+ key: CapabilityLimitKey;
110
+ label: GrokBuildSettingsKey;
111
+ hint: GrokBuildSettingsKey;
112
+ min: number;
113
+ max: number;
114
+ scale: number;
115
+ }[] = [
116
+ { key: "searchResults", label: "capSearchResults", hint: "capSearchResultsHint", min: 1, max: 20, scale: 1 },
117
+ { key: "imageCount", label: "capImageCount", hint: "capImageCountHint", min: 1, max: 4, scale: 1 },
118
+ {
119
+ key: "videoArtifactTtlMs",
120
+ label: "capVideoTtlHours",
121
+ hint: "capVideoTtlHoursHint",
122
+ min: 1,
123
+ max: 168,
124
+ scale: HOUR_MS,
125
+ },
126
+ ];
127
+
128
+ export const IMAGINE_SOURCE_KEY: { readonly [source: string]: GrokBuildSettingsKey } = {
129
+ none: "imagineSourceNone",
130
+ env: "imagineSourceEnv",
131
+ environment: "imagineSourceEnv",
132
+ "xai-api-key": "imagineSourceEnv",
133
+ xai_api_key: "imagineSourceEnv",
134
+ "api-key": "imagineSourceApiKey",
135
+ api_key: "imagineSourceApiKey",
136
+ apikey: "imagineSourceApiKey",
137
+ key: "imagineSourceApiKey",
138
+ settings: "imagineSourceApiKey",
139
+ oauth: "imagineSourceOAuth",
140
+ "oauth-access": "imagineSourceOAuth",
141
+ "grok-cli-key": "imagineSourceCliKey",
142
+ "cli-key": "imagineSourceCliKey",
143
+ };
144
+
145
+ export const PROVIDERS: readonly ProviderCardDefinition[] = [
146
+ {
147
+ slug: "grok",
148
+ route: "grok-build",
149
+ titleKey: "grokTitle",
150
+ descriptionKey: "grokDescription",
151
+ methods: ["pkce", "device"],
152
+ recommended: "pkce",
153
+ remoteRecommended: "device",
154
+ },
155
+ {
156
+ slug: "codex",
157
+ route: "codex-oauth",
158
+ titleKey: "codexTitle",
159
+ descriptionKey: "codexDescription",
160
+ methods: ["device", "browser"],
161
+ recommended: "device",
162
+ remoteRecommended: "device",
163
+ },
164
+ {
165
+ slug: "kimi",
166
+ route: "kimi-code-oauth",
167
+ titleKey: "kimiTitle",
168
+ descriptionKey: "kimiDescription",
169
+ methods: ["device"],
170
+ recommended: "device",
171
+ remoteRecommended: "device",
172
+ },
173
+ {
174
+ slug: "claude",
175
+ route: "claude-code-oauth",
176
+ titleKey: "claudeTitle",
177
+ descriptionKey: "claudeDescription",
178
+ methods: ["browser"],
179
+ recommended: "browser",
180
+ remoteRecommended: "browser",
181
+ },
182
+ ];
183
+
184
+ export const SETTINGS_TABS: readonly { id: SettingsTabId; label: GrokBuildSettingsKey }[] = [
185
+ { id: "accounts", label: "tabAccounts" },
186
+ { id: "gateway", label: "tabGateway" },
187
+ { id: "capabilities", label: "tabCapabilities" },
188
+ { id: "about", label: "tabAbout" },
189
+ ];
190
+
191
+ export const GATEWAY_PORT_MIN = 1024;
192
+ export const GATEWAY_PORT_MAX = 65_535;
193
+ export const GATEWAY_RANDOM_PORT_MIN = 18_100;
194
+ export const GATEWAY_RANDOM_PORT_MAX = 18_999;
195
+ export const GATEWAY_RANDOM_RESERVED = new Set([22, 53, 3080, 7890, 9090, 18_080]);
196
+
197
+ export const CONSUMED_PREVIEW_CODES = new Set([
198
+ "preview_invalid",
199
+ "preview_expired",
200
+ "source_changed",
201
+ "destination_changed",
202
+ "confirm_required",
203
+ "unsafe_destination",
204
+ ]);
205
+
206
+ export const PLUGIN_VERSION = "0.5.4";
@@ -0,0 +1,61 @@
1
+ /** Pure display helpers for remote UX and CLI pull noise control. */
2
+
3
+ import type { GrokBuildSettingsInjected, LoginMethod, ProviderCardDefinition, SourceStatus } from "./types.ts";
4
+
5
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
6
+
7
+ /** True when the Settings page is served from a non-loopback host (typical remote DSH). */
8
+ export function isLikelyRemoteHost(hostname: string): boolean {
9
+ const normalized = hostname.trim().toLowerCase();
10
+ if (normalized.length === 0) return false;
11
+ if (LOOPBACK_HOSTS.has(normalized)) return false;
12
+ if (normalized.endsWith(".localhost")) return false;
13
+ return true;
14
+ }
15
+
16
+ export function preferredLoginMethod(definition: ProviderCardDefinition, remote: boolean): LoginMethod {
17
+ if (
18
+ remote &&
19
+ definition.remoteRecommended !== undefined &&
20
+ definition.methods.includes(definition.remoteRecommended)
21
+ ) {
22
+ return definition.remoteRecommended;
23
+ }
24
+ return definition.recommended;
25
+ }
26
+
27
+ /** Put the recommended method first so the primary CTA is unambiguous. */
28
+ export function orderedLoginMethods(definition: ProviderCardDefinition, remote: boolean): LoginMethod[] {
29
+ const preferred = preferredLoginMethod(definition, remote);
30
+ const rest = definition.methods.filter((method) => method !== preferred);
31
+ return definition.methods.includes(preferred) ? [preferred, ...rest] : [...definition.methods];
32
+ }
33
+
34
+ /** Per-card CLI “missing” copy is noisy on remote hosts; keep only actionable reasons. */
35
+ export function shouldShowPerCardSourceReason(source: SourceStatus | undefined): boolean {
36
+ if (source === undefined || source.available) return false;
37
+ if (source.reason === undefined) return false;
38
+ return source.reason !== "missing";
39
+ }
40
+
41
+ export function allOfficialCliMissing(sources: readonly SourceStatus[] | undefined): boolean {
42
+ if (sources === undefined || sources.length === 0) return false;
43
+ return sources.every((source) => !source.available && (source.reason === undefined || source.reason === "missing"));
44
+ }
45
+
46
+ export function anyOfficialCliAvailable(sources: readonly SourceStatus[] | undefined): boolean {
47
+ return sources?.some((source) => source.available) === true;
48
+ }
49
+
50
+ export function methodLabel(
51
+ method: LoginMethod,
52
+ t: GrokBuildSettingsInjected["t"],
53
+ options?: { remote?: boolean; primary?: boolean },
54
+ ): string {
55
+ if (method === "device") {
56
+ if (options?.remote === true && options.primary === true) return t("deviceLoginRemote");
57
+ return t("deviceLogin");
58
+ }
59
+ if (method === "browser") return t("browserLogin");
60
+ return t("pkceLogin");
61
+ }
@@ -15,6 +15,7 @@ export const en = {
15
15
  working: "Working…",
16
16
  cancelLogin: "Cancel sign-in",
17
17
  deviceLogin: "Sign in with device code",
18
+ deviceLoginRemote: "Sign in with device code (best for remote)",
18
19
  browserLogin: "Sign in in browser",
19
20
  pkceLogin: "Sign in with authorization code",
20
21
  userCode: "Enter this code when the provider asks:",
@@ -60,7 +61,14 @@ export const en = {
60
61
  sourcesCheckAgain: "Check again",
61
62
  sourcesAvailable: "A readable copy is available",
62
63
  sourcesUnavailable: "No usable copy found",
64
+ sourcesAllMissingHint:
65
+ "No official CLI login files were found on this host. That is normal on a remote DSH machine — use device-code sign-in instead, or check again after logging in on this same host.",
66
+ sourcesAvailableHint:
67
+ "An official CLI login was found on this host. You can pull a one-way copy into dsh from a provider card.",
63
68
  sourcesPullCopy: "Pull a copy",
69
+ remoteAccountsTip:
70
+ "This Settings page looks remote (not loopback). Prefer device-code sign-in; browser pop-ups and local PKCE callbacks often fail from another machine.",
71
+ remoteTipDismiss: "Dismiss",
64
72
  sourcesPreviewTitle: "Review this copy before saving it in dsh",
65
73
  sourcesPreviewExpires: "CLI credential expiry: {time}",
66
74
  sourcesTicketExpires: "This preview expires at {time}",
@@ -120,20 +128,19 @@ export const en = {
120
128
  "Completed Imagine video artifacts are kept for 1–168 hours. Lowering retention cleans older artifacts immediately; raising it applies to new artifacts.",
121
129
  capabilityLimitInvalid: "Enter a whole number from {min} through {max}.",
122
130
  capCodexSearch: "Codex subscription search",
123
- capCodexSearchHint: "Optional Codex search. It does not become the default search provider.",
131
+ capCodexSearchHint: "Optional search using Codex quota.",
124
132
  capCodexImages: "Codex images",
125
- capCodexImagesHint: "Optional Codex image generation. Uses ChatGPT/Codex quota.",
133
+ capCodexImagesHint: "Generate images with ChatGPT/Codex quota.",
126
134
  capCodexImageEdits: "Codex image edits",
127
- capCodexImageEditsHint: "Optional Codex image edits. Available only when Codex images is on.",
135
+ capCodexImageEditsHint: "Edit images; requires Codex images.",
128
136
  capCodexUsage: "Show Codex quota",
129
- capCodexUsageHint: "Best-effort remaining-quota card. Hidden if the vendor does not return usable numbers.",
137
+ capCodexUsageHint: "Show remaining quota on the Codex account card when available.",
130
138
  capCodexFast: "Fast requested",
131
- capCodexFastHint:
132
- "Publishes the codex-oauth-fast route only after a fresh private model catalog confirms a priority-eligible model; failures withdraw it.",
139
+ capCodexFastHint: "Publish the Fast route when the live catalog confirms priority eligibility.",
133
140
  capGrokImagineImage: "Grok Imagine images",
134
- capGrokImagineImageHint: "Optional official xAI Imagine images. Requires an Imagine credential, not only Grok OAuth.",
141
+ capGrokImagineImageHint: "Needs a separate Imagine credential (not Grok OAuth alone).",
135
142
  capGrokImagineVideo: "Grok Imagine video",
136
- capGrokImagineVideoHint: "Optional official xAI Imagine video. Requires an Imagine credential, not only Grok OAuth.",
143
+ capGrokImagineVideoHint: "Needs a separate Imagine credential (not Grok OAuth alone).",
137
144
  usageTitle: "Codex quota",
138
145
  usageLoading: "Loading Codex quota…",
139
146
  usageUnavailable: "Codex quota is not available right now.",
@@ -184,6 +191,11 @@ export const en = {
184
191
  gatewayTitle: "Local API gateway",
185
192
  gatewayIntro: "Loopback OpenAI/Anthropic-compatible server for your own tools. Off until you enable it.",
186
193
  gatewayWarning: "Exposes signed-in subscriptions as a local API. Can violate provider terms and uses quota.",
194
+ gatewayLoopbackHint: "Binds to loopback only. Do not port-forward or expose this gateway on a public interface.",
195
+ gatewayEnableConfirm:
196
+ "Enable the local API gateway? It exposes your signed-in subscriptions to tools on this host and consumes provider quota.",
197
+ gatewayEnableConfirmAction: "Confirm",
198
+ gatewayEnableCancel: "Cancel",
187
199
  gatewayEnabled: "Enable local gateway",
188
200
  gatewayBind: "Bind",
189
201
  gatewayPort: "Port",
@@ -211,6 +223,11 @@ export const en = {
211
223
  gatewayLoadFailed: "Could not load gateway settings.",
212
224
  gatewaySaveFailed: "Could not update the gateway.",
213
225
  gatewayLoading: "Loading gateway settings…",
226
+ remoteLoginHelp:
227
+ "On a remote DSH host, open Settings here and use device-code sign-in for each provider. Complete the code on any browser that can reach the provider, then return to chat and pick the provider route model.",
228
+ pluginVersion: "Plugin version {version}",
229
+ aboutDocsHint:
230
+ "See the plugin README and INSTALL docs in the repository for setup, supported providers, and gateway safety notes.",
214
231
  };
215
232
 
216
233
  export type GrokBuildSettingsKey = keyof typeof en;
@@ -230,6 +247,7 @@ export const zh: { [Key in GrokBuildSettingsKey]: string } = {
230
247
  working: "处理中…",
231
248
  cancelLogin: "取消登录",
232
249
  deviceLogin: "使用设备码登录",
250
+ deviceLoginRemote: "使用设备码登录(适合远程)",
233
251
  browserLogin: "在浏览器中登录",
234
252
  pkceLogin: "使用授权码登录",
235
253
  userCode: "供应商要求输入代码时,请输入:",
@@ -271,7 +289,13 @@ export const zh: { [Key in GrokBuildSettingsKey]: string } = {
271
289
  sourcesCheckAgain: "再检查一次",
272
290
  sourcesAvailable: "可以读取一份副本",
273
291
  sourcesUnavailable: "没有可用的副本",
292
+ sourcesAllMissingHint:
293
+ "这台主机上没有找到官方 CLI 登录文件。远程 DSH 上这很常见——请改用设备码登录;若已在本机 CLI 登录,可再检查一次。",
294
+ sourcesAvailableHint: "本机发现了官方 CLI 登录。可在对应账户卡片里单向拉取一份副本到 dsh。",
274
295
  sourcesPullCopy: "拉取副本",
296
+ remoteAccountsTip:
297
+ "当前 Settings 页面看起来是远程访问(非本机回环)。请优先使用设备码登录;从另一台机器打开时,浏览器弹窗和本地 PKCE 回调经常会失败。",
298
+ remoteTipDismiss: "知道了",
275
299
  sourcesPreviewTitle: "先核对这份副本,再写入 dsh",
276
300
  sourcesPreviewExpires: "CLI 凭据到期时间:{time}",
277
301
  sourcesTicketExpires: "此次预览将于 {time} 失效",
@@ -328,19 +352,19 @@ export const zh: { [Key in GrokBuildSettingsKey]: string } = {
328
352
  capVideoTtlHoursHint: "Imagine 视频产物保留 1–168 小时;降低保留时间会立即清理过期旧产物,提高只影响新产物。",
329
353
  capabilityLimitInvalid: "请输入 {min} 到 {max} 之间的整数。",
330
354
  capCodexSearch: "Codex 订阅搜索",
331
- capCodexSearchHint: "可选的 Codex 搜索。不会自动变成默认搜索提供方。",
355
+ capCodexSearchHint: "可选搜索,消耗 Codex 配额。",
332
356
  capCodexImages: "Codex 出图",
333
- capCodexImagesHint: "可选的 Codex 出图。消耗 ChatGPT/Codex 配额。",
357
+ capCodexImagesHint: " ChatGPT/Codex 配额出图。",
334
358
  capCodexImageEdits: "Codex 改图",
335
- capCodexImageEditsHint: "可选的 Codex 改图。仅在打开 Codex 出图后可编辑。",
359
+ capCodexImageEditsHint: "改图;需先打开 Codex 出图。",
336
360
  capCodexUsage: "显示 Codex 额度",
337
- capCodexUsageHint: "尽力展示剩余额度。供应商未返回可用数字时会隐藏对应项。",
361
+ capCodexUsageHint: "有数据时在 Codex 账户卡片上显示剩余额度。",
338
362
  capCodexFast: "已请求 Fast",
339
- capCodexFastHint: "仅在最新私有模型目录确认存在可用优先模型后发布 codex-oauth-fast 路由;刷新失败会撤回该路由。",
363
+ capCodexFastHint: "在线目录确认优先资格后发布 Fast 路由。",
340
364
  capGrokImagineImage: "Grok Imagine 出图",
341
- capGrokImagineImageHint: "可选的官方 xAI Imagine 出图。需要 Imagine 凭据,仅有 Grok OAuth 不够。",
365
+ capGrokImagineImageHint: "需要单独的 Imagine 凭据,仅有 Grok OAuth 不够。",
342
366
  capGrokImagineVideo: "Grok Imagine 出视频",
343
- capGrokImagineVideoHint: "可选的官方 xAI Imagine 出视频。需要 Imagine 凭据,仅有 Grok OAuth 不够。",
367
+ capGrokImagineVideoHint: "需要单独的 Imagine 凭据,仅有 Grok OAuth 不够。",
344
368
  usageTitle: "Codex 额度",
345
369
  usageLoading: "正在加载 Codex 额度…",
346
370
  usageUnavailable: "当前无法读取 Codex 额度。",
@@ -391,6 +415,10 @@ export const zh: { [Key in GrokBuildSettingsKey]: string } = {
391
415
  gatewayTitle: "本地 API 网关",
392
416
  gatewayIntro: "本机 loopback 上的 OpenAI/Anthropic 兼容服务,供你自己的工具使用。默认关闭。",
393
417
  gatewayWarning: "打开后会把已登录订阅暴露成本地 API。可能违反供应商条款并消耗配额。",
418
+ gatewayLoopbackHint: "仅绑定本机回环地址。请勿做端口转发,也不要把它暴露到公网。",
419
+ gatewayEnableConfirm: "要启用本地 API 网关吗?它会把已登录的订阅暴露给本机工具,并消耗供应商配额。",
420
+ gatewayEnableConfirmAction: "确认启用",
421
+ gatewayEnableCancel: "取消",
394
422
  gatewayEnabled: "启用本地网关",
395
423
  gatewayBind: "绑定地址",
396
424
  gatewayPort: "端口",
@@ -418,4 +446,8 @@ export const zh: { [Key in GrokBuildSettingsKey]: string } = {
418
446
  gatewayLoadFailed: "无法加载网关设置。",
419
447
  gatewaySaveFailed: "无法更新网关。",
420
448
  gatewayLoading: "正在加载网关设置…",
449
+ remoteLoginHelp:
450
+ "在远程 DSH 主机上,打开此处 Settings,对每个供应商使用设备码登录。在能访问供应商的任意浏览器完成验证码,再回到聊天选择对应路由模型。",
451
+ pluginVersion: "插件版本 {version}",
452
+ aboutDocsHint: "仓库中的 README 与 INSTALL 文档介绍了安装步骤、支持的供应商以及网关安全注意事项。",
421
453
  };