dsh-sub2api-sync 0.2.7 → 0.2.9

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/lib/index.js CHANGED
@@ -190,6 +190,7 @@ function panelRefreshRef(baseURL) {
190
190
  function panelPasswordRef(baseURL) {
191
191
  return `DSH_SUB2API_PASSWORD_${hashString(instanceKey(baseURL))}`;
192
192
  }
193
+ const NATIVE_PANEL_PASSWORD_REF = "DSH_SUB2API_PANEL_PASSWORD";
193
194
  function oauthTokenRef(baseURL) {
194
195
  return `DSH_SUB2API_OAUTH_TOKEN_${hashString(instanceKey(baseURL))}`;
195
196
  }
@@ -376,14 +377,30 @@ const apiProtocolSchema = z.union([
376
377
  z.const("openai-responses"),
377
378
  z.const("anthropic-messages")
378
379
  ]);
380
+ /**
381
+ * dsh-Go native settings: a fixed-ref credential slot for the panel
382
+ * password (the official dsh-media shape). The `credential-ref` role marks
383
+ * the binding — the App reads it, shows 已设置/未设置 and writes the secret
384
+ * through its credentials transaction; the `secret` sentinel refuses any
385
+ * value on the ordinary config write path, so a password can never land in
386
+ * the profile document. Empty default keeps the form write a no-op.
387
+ */
388
+ const secretSentinel = z.transform(z.any(), (value) => {
389
+ if (value !== "") throw new Error("面板密码只能通过凭据服务写入(本字段仅作占位)。");
390
+ return "";
391
+ }, true).default("").role("secret");
392
+ const panelCredentialSlot = z.object({
393
+ credentialRef: z.const(NATIVE_PANEL_PASSWORD_REF).default(NATIVE_PANEL_PASSWORD_REF).role("credential-ref"),
394
+ password: secretSentinel.description("面板密码(写入凭据库,手机原生设置可填) / Panel password (stored in the credential store)")
395
+ }).description("面板密码凭据(免输登录用,与网页卡共用) / Panel password credential").collapse();
379
396
  const Config = z.object({
380
- baseURL: z.string().volatile(),
381
- email: z.string().volatile(),
382
- providerNameTemplate: z.string().default(DEFAULT_PROVIDER_NAME_TEMPLATE).volatile(),
383
- selectedKeys: z.array(z.number().step(1)).default([]).volatile(),
384
- modelListOverrides: z.dict(z.array(z.string())).default({}).volatile(),
385
- protocolOverrides: z.dict(apiProtocolSchema).default({}).volatile(),
386
- routeKeyIds: z.dict(z.number().step(1)).default({}).volatile(),
397
+ baseURL: z.string().description("sub2api 实例地址(含或不含 /v1) / Instance URL (with or without /v1)").volatile(),
398
+ email: z.string().description("面板账号邮箱(密码登录用) / Panel account email (password login)").volatile(),
399
+ providerNameTemplate: z.string().default(DEFAULT_PROVIDER_NAME_TEMPLATE).description("供应商显示名模板,须含 {keyName} 或 {siteName} / Provider display-name template (must keep {keyName} or {siteName})").volatile(),
400
+ selectedKeys: z.array(z.number().step(1)).default([]).description("待同步的中转 key 列表(网页卡勾选写入) / Relay keys selected for sync (card-managed)").collapse().volatile(),
401
+ modelListOverrides: z.dict(z.array(z.string())).default({}).description("分组手动模型列表(/v1/models 不可用时) / Manual model lists when /v1/models is unavailable").collapse().volatile(),
402
+ protocolOverrides: z.dict(apiProtocolSchema).default({}).description("分组线协议覆盖 / Per-group wire-protocol override").collapse().volatile(),
403
+ routeKeyIds: z.dict(z.number().step(1)).default({}).description("(引擎维护,请勿手改)路由 → 面板 key id 映射 / Engine-maintained route→keyId map — do not edit").hidden(true).volatile(),
387
404
  compatTemplate: z.object({
388
405
  compat: z.dict(z.any()),
389
406
  reasoningEfforts: z.union([z.const(false), z.dict(z.union([z.string(), z.const(null)]))]),
@@ -396,12 +413,12 @@ const Config = z.object({
396
413
  z.const("xhigh"),
397
414
  z.const("max")
398
415
  ])
399
- }).volatile(),
400
- enrichModelMeta: z.boolean().default(true).volatile(),
401
- keyQuota: z.number().volatile(),
402
- keyExpiresInDays: z.number().step(1).volatile(),
403
- deleteOrphanKeys: z.boolean().default(false).volatile(),
404
- syncIntervalMs: z.number().step(1).volatile(),
416
+ }).description("同步模型继承的兼容模板 / Per-model template inherited by synced models").collapse().volatile(),
417
+ enrichModelMeta: z.boolean().default(true).description("models.dev 元数据增强(显示名/上下文窗口) / models.dev metadata enrichment").volatile(),
418
+ keyQuota: z.number().description("新建 key 的额度(USD,可选) / Optional quota (USD) for created keys").volatile(),
419
+ keyExpiresInDays: z.number().step(1).description("新建 key 的有效天数(可选) / Optional expiry in days for created keys").volatile(),
420
+ deleteOrphanKeys: z.boolean().default(false).description("移除路由时同时吊销面板 key / Also revoke the panel key when removing a route").volatile(),
421
+ syncIntervalMs: z.number().step(1).description("定时同步间隔 ms(删除字段即关闭,下限 60000) / Scheduled resync interval ms (unset = off; floor 60000)").volatile(),
405
422
  retryPolicy: z.object({
406
423
  mode: z.union([z.const("normal"), z.const("always")]),
407
424
  maxRetries: z.number().step(1),
@@ -411,10 +428,11 @@ const Config = z.object({
411
428
  maxDelayMs: z.number(),
412
429
  jitterRatio: z.number()
413
430
  })
414
- }).volatile(),
415
- defaultContextWindow: z.number().step(1).volatile(),
416
- defaultMaxTokens: z.number().step(1).volatile(),
417
- allowRemoteActions: z.boolean().default(false).volatile()
431
+ }).description("同步路由的重试策略覆盖(缺省继承内置宽容策略) / Retry-policy overrides (defaults to the built-in lenient policy)").collapse().volatile(),
432
+ defaultContextWindow: z.number().step(1).description("元数据无法匹配时的默认上下文窗口 / Default context window when enrichment cannot match").volatile(),
433
+ defaultMaxTokens: z.number().step(1).description("元数据无法匹配时的默认最大输出 / Default max output tokens when enrichment cannot match").volatile(),
434
+ allowRemoteActions: z.boolean().default(false).description("允许私网对端调用动作路由(逃生门,默认关,开启有安全暴露) / Allow private-network peers on action routes (escape hatch, default off)").volatile(),
435
+ panelCredential: panelCredentialSlot.volatile()
418
436
  });
419
437
  /** Unwrap one live cell into its current snapshot; plain values pass through. */
420
438
  function unwrap$1(field) {
@@ -820,7 +838,7 @@ var Sub2apiSyncError = class extends Error {
820
838
  this.name = "Sub2apiSyncError";
821
839
  }
822
840
  };
823
- function messageOf(error) {
841
+ function messageOf$1(error) {
824
842
  return error instanceof Error ? error.message : String(error);
825
843
  }
826
844
  /** Order-insensitive JSON comparison — profiles are plain JSON values. */
@@ -966,7 +984,7 @@ var Sub2apiSyncEngine = class {
966
984
  route,
967
985
  keyId: key.id,
968
986
  action: "failed",
969
- error: messageOf(error)
987
+ error: messageOf$1(error)
970
988
  });
971
989
  }
972
990
  }
@@ -994,13 +1012,13 @@ var Sub2apiSyncEngine = class {
994
1012
  try {
995
1013
  await this.credentials.unset(routeKeyRef(route));
996
1014
  } catch (error) {
997
- warnings.push(`key ${route} 的凭据清理失败:${messageOf(error)}`);
1015
+ warnings.push(`key ${route} 的凭据清理失败:${messageOf$1(error)}`);
998
1016
  }
999
1017
  const keyId = keyIdByRoute.get(route) ?? legacyKeyIdOfRoute(route);
1000
1018
  if (config.deleteOrphanKeys === true && keyId !== undefined) try {
1001
1019
  await this.gateway.use((panel, token) => panel.deleteKey(token, keyId));
1002
1020
  } catch (error) {
1003
- warnings.push(`key ${route} 的远端撤销失败:${messageOf(error)}`);
1021
+ warnings.push(`key ${route} 的远端撤销失败:${messageOf$1(error)}`);
1004
1022
  }
1005
1023
  }
1006
1024
  try {
@@ -1055,7 +1073,7 @@ var Sub2apiSyncEngine = class {
1055
1073
  plaintext = !usable(revealed.key) ? undefined : revealed.key;
1056
1074
  if (plaintext === undefined) revealFailure = "reveal 未返回可用明文";
1057
1075
  } catch (error) {
1058
- revealFailure = messageOf(error);
1076
+ revealFailure = messageOf$1(error);
1059
1077
  }
1060
1078
  if (!usable(plaintext)) {
1061
1079
  const revealNote = revealFailure !== undefined ? `;reveal 接口也失败:${revealFailure}` : "";
@@ -1065,19 +1083,23 @@ var Sub2apiSyncEngine = class {
1065
1083
  try {
1066
1084
  models = await this.discoverModels(config, group.id, instance, plaintext);
1067
1085
  } catch (error) {
1068
- if (source !== "stored" && source !== "legacy" || !/HTTP 401(?![0-9])/.test(messageOf(error))) throw error;
1086
+ if (source !== "stored" && source !== "legacy" || !/HTTP 401(?![0-9])/.test(messageOf$1(error))) throw error;
1069
1087
  await this.credentials.unset(ref).catch(() => undefined);
1070
1088
  if (ref !== legacyKeyRef(key.id)) await this.credentials.unset(legacyKeyRef(key.id)).catch(() => undefined);
1071
1089
  let revealed;
1072
1090
  try {
1073
1091
  revealed = (await this.gateway.use((panel, token) => panel.revealKey(token, key.id)))?.key;
1074
1092
  } catch (revealError) {
1075
- throw new Sub2apiSyncError(`${messageOf(error)};本地存底已作废,但 reveal 也失败:${messageOf(revealError)}——请在面板删除这把 key 后用插件「创建 key」重建`);
1093
+ throw new Sub2apiSyncError(`${messageOf$1(error)};本地存底已作废,但 reveal 也失败:${messageOf$1(revealError)}——请在面板删除这把 key 后用插件「创建 key」重建`);
1076
1094
  }
1077
- if (revealed === undefined || revealed.length === 0 || revealed.includes("****")) throw new Sub2apiSyncError(`${messageOf(error)};本地存底已作废,但 reveal 未返回明文——请在面板删除这把 key 后用插件「创建 key」重建`);
1095
+ if (revealed === undefined || revealed.length === 0 || revealed.includes("****")) throw new Sub2apiSyncError(`${messageOf$1(error)};本地存底已作废,但 reveal 未返回明文——请在面板删除这把 key 后用插件「创建 key」重建`);
1078
1096
  plaintext = revealed;
1079
1097
  models = await this.discoverModels(config, group.id, instance, plaintext);
1080
1098
  }
1099
+ if (models.length === 0) {
1100
+ const groupLabel = group.name.length > 0 ? `${group.name}(#${group.id})` : `#${group.id}`;
1101
+ throw new Sub2apiSyncError(`key #${key.id}(${key.name})经网关 /v1/models 发现到 0 个模型(分组 ${groupLabel})——中转站没有向这把 key 暴露任何模型;请在中转站确认该分组的模型配置与这把 key 的权限,或在卡片用「模型清单覆盖」显式指定模型。这把 key 已跳过,其余 key 照常同步`);
1102
+ }
1081
1103
  if (this.credentials.resolve !== undefined) {
1082
1104
  const stored = await this.credentials.resolve(ref);
1083
1105
  if (stored?.value !== plaintext) await this.credentials.set(ref, plaintext);
@@ -1132,7 +1154,7 @@ var Sub2apiSyncEngine = class {
1132
1154
  });
1133
1155
  return await client.listModels(relayKey);
1134
1156
  } catch (error) {
1135
- throw new Sub2apiSyncError(`分组 ${groupId} 模型发现失败:${messageOf(error)};且未配置 modelListOverrides 兜底`);
1157
+ throw new Sub2apiSyncError(`分组 ${groupId} 模型发现失败:${messageOf$1(error)};且未配置 modelListOverrides 兜底`);
1136
1158
  }
1137
1159
  }
1138
1160
  readCurrentProviders() {
@@ -2168,6 +2190,252 @@ else await this.credentials.unset(this.refreshRef());
2168
2190
  }
2169
2191
  };
2170
2192
 
2193
+ //#endregion
2194
+ //#region src/dshgo.ts
2195
+ const ZH = {
2196
+ signedInPrefix: "已登录",
2197
+ signedOut: "未登录:在设置里填写实例地址与邮箱,凭据槽写入面板密码",
2198
+ noEmail: "请先在设置里填写面板邮箱",
2199
+ loginAction: "登录面板",
2200
+ logoutAction: "退出登录",
2201
+ syncAction: "立即同步",
2202
+ syncing: "同步中",
2203
+ idle: "空闲",
2204
+ instanceLabel: "实例",
2205
+ stateLabel: "状态",
2206
+ routesLabel: "路由数",
2207
+ lastSyncLabel: "上次同步",
2208
+ never: "尚未同步",
2209
+ loginStarted: "正在登录…",
2210
+ logoutStarted: "正在退出…",
2211
+ twoFaHint: "该账号开启了两步验证:请在网页版设置卡完成第二因子",
2212
+ hint: "勾选 key、两步验证与逐路由移除请在网页版设置卡操作",
2213
+ justNow: "刚刚",
2214
+ minutesAgo: (n) => n + " 分钟前",
2215
+ hoursAgo: (n) => n + " 小时前",
2216
+ daysAgo: (n) => n + " 天前"
2217
+ };
2218
+ const EN = {
2219
+ signedInPrefix: "Signed in",
2220
+ signedOut: "Signed out: fill the instance URL and email in settings, write the panel password into the credential slot",
2221
+ noEmail: "Fill the panel email in settings first",
2222
+ loginAction: "Sign in",
2223
+ logoutAction: "Sign out",
2224
+ syncAction: "Sync now",
2225
+ syncing: "Syncing",
2226
+ idle: "Idle",
2227
+ instanceLabel: "Instance",
2228
+ stateLabel: "State",
2229
+ routesLabel: "Routes",
2230
+ lastSyncLabel: "Last sync",
2231
+ never: "Never",
2232
+ loginStarted: "Signing in…",
2233
+ logoutStarted: "Signing out…",
2234
+ twoFaHint: "Two-factor is enabled: finish the second factor in the web settings card",
2235
+ hint: "Key selection, two-factor and per-route removal live in the web settings card",
2236
+ justNow: "just now",
2237
+ minutesAgo: (n) => n + " min ago",
2238
+ hoursAgo: (n) => n + " h ago",
2239
+ daysAgo: (n) => n + " d ago"
2240
+ };
2241
+ const textOf = (locale) => typeof locale === "string" && locale.toLowerCase().startsWith("en") ? EN : ZH;
2242
+ function messageOf(error) {
2243
+ return error instanceof Error ? error.message : String(error);
2244
+ }
2245
+ function relativeTime(at, t) {
2246
+ const elapsed = Date.now() - at;
2247
+ if (elapsed < 6e4) return t.justNow;
2248
+ if (elapsed < 36e5) return t.minutesAgo(Math.floor(elapsed / 6e4));
2249
+ if (elapsed < 864e5) return t.hoursAgo(Math.floor(elapsed / 36e5));
2250
+ return t.daysAgo(Math.floor(elapsed / 864e5));
2251
+ }
2252
+ function registerDshGo(ctx, deps, hooks) {
2253
+ ctx.inject(["dshGo"], (host) => {
2254
+ const dshGo = host.dshGo;
2255
+ if (dshGo === undefined) return;
2256
+ /** Transient feedback from background actions; cleared by the next success. */
2257
+ let lastError;
2258
+ let hint;
2259
+ let activeLocale = "zh-CN";
2260
+ const buildData = async () => {
2261
+ const t = textOf(activeLocale);
2262
+ let signedIn = false;
2263
+ let email;
2264
+ try {
2265
+ const auth = await deps.authState();
2266
+ signedIn = auth.signedIn;
2267
+ email = auth.email;
2268
+ } catch {
2269
+ signedIn = false;
2270
+ }
2271
+ const routes = deps.ownedRoutes();
2272
+ const lastSyncAt = deps.lastReportAt();
2273
+ const statusLine = lastError !== undefined ? {
2274
+ text: lastError,
2275
+ tone: "bad"
2276
+ } : hint !== undefined ? {
2277
+ text: hint,
2278
+ tone: "info"
2279
+ } : signedIn ? {
2280
+ text: t.signedInPrefix + (email !== undefined && email.length > 0 ? " · " + email : ""),
2281
+ tone: "good"
2282
+ } : {
2283
+ text: t.signedOut,
2284
+ tone: "warn"
2285
+ };
2286
+ return {
2287
+ statusText: statusLine.text,
2288
+ statusTone: statusLine.tone,
2289
+ signedIn,
2290
+ instance: deps.instance(),
2291
+ stateText: deps.isSyncRunning() ? t.syncing : signedIn ? t.idle : t.signedOut,
2292
+ routeCount: routes.length,
2293
+ lastSyncText: lastSyncAt === undefined ? t.never : relativeTime(lastSyncAt, t),
2294
+ routes: routes.map((route) => ({
2295
+ title: route.displayName ?? route.route,
2296
+ ...route.api !== undefined ? { subtitle: route.api } : {},
2297
+ trailing: String(route.models)
2298
+ })),
2299
+ actions: signedIn ? [{
2300
+ label: t.syncAction,
2301
+ style: "primary",
2302
+ action: "sync"
2303
+ }, {
2304
+ label: t.logoutAction,
2305
+ action: "logout"
2306
+ }] : [{
2307
+ label: t.loginAction,
2308
+ style: "primary",
2309
+ action: "login"
2310
+ }]
2311
+ };
2312
+ };
2313
+ /** Broadcast one fresh model to every live data/strip source. */
2314
+ const sinks = new Set();
2315
+ const publish = (data) => {
2316
+ for (const sink of [...sinks]) sink(data);
2317
+ };
2318
+ const schedule = () => {
2319
+ void buildData().then(publish).catch(() => undefined);
2320
+ };
2321
+ /** Emit now, on every settle, and on a bounded tick until cancelled. */
2322
+ const watch = (sink) => {
2323
+ sinks.add(sink);
2324
+ schedule();
2325
+ const timer = setInterval(schedule, 15e3);
2326
+ return () => {
2327
+ sinks.delete(sink);
2328
+ clearInterval(timer);
2329
+ };
2330
+ };
2331
+ const noteError = (cause) => {
2332
+ lastError = messageOf(cause);
2333
+ schedule();
2334
+ };
2335
+ dshGo.panel("sync-status", {
2336
+ view: (_scope, caps) => {
2337
+ const t = textOf(caps?.locale);
2338
+ return {
2339
+ type: "stack",
2340
+ children: [
2341
+ {
2342
+ type: "callout",
2343
+ text: { $: "data.statusText" },
2344
+ tone: { $: "data.statusTone" }
2345
+ },
2346
+ {
2347
+ type: "row",
2348
+ children: [{
2349
+ type: "metric",
2350
+ label: t.routesLabel,
2351
+ value: { $: "data.routeCount" },
2352
+ tone: "info"
2353
+ }, {
2354
+ type: "metric",
2355
+ label: t.lastSyncLabel,
2356
+ value: { $: "data.lastSyncText" },
2357
+ tone: "info"
2358
+ }]
2359
+ },
2360
+ {
2361
+ type: "keyValue",
2362
+ items: [[t.instanceLabel, { $: "data.instance" }], [t.stateLabel, { $: "data.stateText" }]]
2363
+ },
2364
+ {
2365
+ type: "list",
2366
+ items: { $: "data.routes" }
2367
+ },
2368
+ {
2369
+ type: "actions",
2370
+ items: { $: "data.actions" }
2371
+ },
2372
+ {
2373
+ type: "text",
2374
+ tone: "muted",
2375
+ text: t.hint
2376
+ }
2377
+ ]
2378
+ };
2379
+ },
2380
+ data: (_scope, emit, caps) => {
2381
+ activeLocale = caps?.locale ?? activeLocale;
2382
+ let alive = true;
2383
+ const cancel = watch((data) => {
2384
+ if (alive) emit(data);
2385
+ });
2386
+ return () => {
2387
+ alive = false;
2388
+ cancel();
2389
+ };
2390
+ },
2391
+ actions: {
2392
+ sync: () => {
2393
+ const t = textOf(activeLocale);
2394
+ if (deps.isSyncRunning()) return { toast: t.syncing + "…" };
2395
+ lastError = undefined;
2396
+ void deps.sync().then(schedule).catch(noteError);
2397
+ return { toast: t.syncAction + " · " + t.syncing + "…" };
2398
+ },
2399
+ login: () => {
2400
+ const t = textOf(activeLocale);
2401
+ const email = hooks.email()?.trim() ?? "";
2402
+ if (email.length === 0) return { toast: t.noEmail };
2403
+ hint = undefined;
2404
+ lastError = undefined;
2405
+ void deps.login(email, undefined).then((response) => {
2406
+ hint = response.kind === "2fa" ? t.twoFaHint : undefined;
2407
+ schedule();
2408
+ }).catch(noteError);
2409
+ return { toast: t.loginStarted };
2410
+ },
2411
+ logout: () => {
2412
+ const t = textOf(activeLocale);
2413
+ lastError = undefined;
2414
+ void deps.logout().then(schedule).catch(noteError);
2415
+ return { toast: t.logoutStarted };
2416
+ }
2417
+ }
2418
+ });
2419
+ dshGo.strip("sync-strip", (_scope, emit, caps) => {
2420
+ activeLocale = caps?.locale ?? activeLocale;
2421
+ let alive = true;
2422
+ const cancel = watch((data) => {
2423
+ if (!alive) return;
2424
+ const t = textOf(activeLocale);
2425
+ emit({
2426
+ text: data.signedIn ? data.routeCount + " · " + data.lastSyncText : "Sub2API · " + t.signedOut,
2427
+ icon: "chart",
2428
+ ...data.statusTone === "bad" ? { tone: "bad" } : {}
2429
+ });
2430
+ });
2431
+ return () => {
2432
+ alive = false;
2433
+ cancel();
2434
+ };
2435
+ });
2436
+ });
2437
+ }
2438
+
2171
2439
  //#endregion
2172
2440
  //#region src/routes.ts
2173
2441
  function callbackLangOf(acceptLanguage) {
@@ -2717,7 +2985,7 @@ function apply(ctx, config) {
2717
2985
  authState: () => gateway.authState(source().email),
2718
2986
  login: async (email, password) => {
2719
2987
  const typed = password !== undefined && password.length > 0 ? password : undefined;
2720
- let pwd = typed ?? (await credentials.resolve(panelPasswordRef(instance())))?.value;
2988
+ let pwd = typed ?? (await credentials.resolve(panelPasswordRef(instance())))?.value ?? (await credentials.resolve(NATIVE_PANEL_PASSWORD_REF))?.value;
2721
2989
  if (pwd === undefined || pwd.length === 0) throw new Error("请输入面板密码(首次登录后密码会保存到凭据存储,之后可免输入)");
2722
2990
  await mutateOwn([{
2723
2991
  op: "set",
@@ -2926,6 +3194,7 @@ function apply(ctx, config) {
2926
3194
  }
2927
3195
  };
2928
3196
  registerRoutes(ctx, deps);
3197
+ registerDshGo(ctx, deps, { email: () => source().email });
2929
3198
  let timer;
2930
3199
  let armedFor;
2931
3200
  const disarm = () => {
package/lib/types.d.ts CHANGED
@@ -76,6 +76,13 @@ export declare function panelTokenRef(baseURL: string): string;
76
76
  export declare function panelRefreshRef(baseURL: string): string;
77
77
  /** Panel password credential reference for one instance (Mode A, C3). */
78
78
  export declare function panelPasswordRef(baseURL: string): string;
79
+ /**
80
+ * Fixed credential slot behind the dsh-Go native settings page: a password
81
+ * set on the phone lands here. The per-instance ref above remains the
82
+ * gateway's own storage for proven-good passwords; login resolution reads
83
+ * the instance ref first and this slot as the final fallback.
84
+ */
85
+ export declare const NATIVE_PANEL_PASSWORD_REF: "DSH_SUB2API_PANEL_PASSWORD";
79
86
  /** OAuth access-token credential reference for one instance (C2). */
80
87
  export declare function oauthTokenRef(baseURL: string): string;
81
88
  /** OAuth refresh-token credential reference for one instance (C2). */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-sub2api-sync",
3
- "description": "Sync one sub2api instance's groups, keys, and models into dsh-llm-pi-ai provider routes: panel-driven login (password / OAuth), one relay key one provider.",
4
- "version": "0.2.7",
3
+ "description": "Sync one sub2api instance's groups, keys, and models into dsh-llm-pi-ai provider routes: panel-driven login (password / OAuth), one provider per relay key.",
4
+ "version": "0.2.9",
5
5
  "license": "MIT",
6
6
  "author": "Shuai-S",
7
7
  "homepage": "https://github.com/Shuai-S/dsh-sub2api-sync#readme",
@@ -58,7 +58,39 @@
58
58
  "platform": "web"
59
59
  }
60
60
  },
61
+ "dshGo": {
62
+ "v": 1,
63
+ "title": {
64
+ "zh": "Sub2API 同步",
65
+ "en": "Sub2API Sync"
66
+ },
67
+ "icon": "chart",
68
+ "contributes": {
69
+ "composer.strip": [
70
+ {
71
+ "id": "sync-strip",
72
+ "icon": "chart",
73
+ "panel": "sync-status"
74
+ }
75
+ ],
76
+ "panels": [
77
+ {
78
+ "id": "sync-status",
79
+ "title": {
80
+ "zh": "同步状态",
81
+ "en": "Sync status"
82
+ },
83
+ "icon": "chart",
84
+ "placement": [
85
+ "session.tab",
86
+ "features"
87
+ ]
88
+ }
89
+ ]
90
+ }
91
+ },
61
92
  "dependencies": {
93
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
62
94
  "@deepseek-ai/schemastery": "^3.18.4"
63
95
  },
64
96
  "peerDependencies": {
@@ -66,7 +98,6 @@
66
98
  "@deepseek-ai/dsh-client-locale": ">=0.2.0-rc.2",
67
99
  "@deepseek-ai/dsh-client-ui-primitives": ">=0.2.0-rc.2",
68
100
  "@deepseek-ai/dsh-client-ui-settings": ">=0.2.0-rc.2",
69
- "@deepseek-ai/dsh-credentials": "*",
70
101
  "@deepseek-ai/dsh-host-webserver": "*",
71
102
  "@deepseek-ai/dsh-settings": "*",
72
103
  "react": "^18.2.0"
@@ -84,9 +115,6 @@
84
115
  "@deepseek-ai/dsh-client-ui-settings": {
85
116
  "optional": true
86
117
  },
87
- "@deepseek-ai/dsh-credentials": {
88
- "optional": true
89
- },
90
118
  "@deepseek-ai/dsh-host-webserver": {
91
119
  "optional": true
92
120
  },
@@ -106,7 +134,6 @@
106
134
  "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
107
135
  "@deepseek-ai/dsh-client-ui-settings-models": "0.2.0-rc.2",
108
136
  "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
109
- "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
110
137
  "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
111
138
  "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
112
139
  "@types/node": "^22.20.0",