@mangoc/dsh-sub2api-sync 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +149 -2
- package/README.zh.md +120 -0
- package/cordis.patch.yml +10 -0
- package/data/models-dev.snapshot.json +1 -0
- package/lib/client.cjs +2416 -0
- package/lib/client.d.ts +49 -0
- package/lib/client.js +2425 -0
- package/lib/config.d.ts +111 -0
- package/lib/engine.d.ts +128 -0
- package/lib/enrich.d.ts +104 -0
- package/lib/icon.d.ts +50 -0
- package/lib/index.d.ts +31 -0
- package/lib/index.js +2872 -0
- package/lib/oauth.d.ts +128 -0
- package/lib/panel.d.ts +203 -0
- package/lib/protocols.d.ts +125 -0
- package/lib/routes.d.ts +256 -0
- package/lib/types.d.ts +151 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +124 -3
package/lib/routes.d.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP routes between the settings card (browser half) and this plugin's
|
|
3
|
+
* host half, registered on the harness web server.
|
|
4
|
+
*
|
|
5
|
+
* rev.4 split: configuration reads/writes ride the platform's
|
|
6
|
+
* `configForms`/settings seam — the old whole-section `/config` route is
|
|
7
|
+
* gone. These routes carry only **actions, secrets, and state projection**:
|
|
8
|
+
* login (password/2FA), panel group/key listing, paste/clear of a
|
|
9
|
+
* relay key, sync, model discovery, and the status snapshot.
|
|
10
|
+
*
|
|
11
|
+
* Requests are restricted to trusted local origins — the same
|
|
12
|
+
* `trustedRequest` posture the community plugins use — with one explicit
|
|
13
|
+
* escape hatch (`allowRemoteActions`, §11): when the user opts in, peers
|
|
14
|
+
* from private-network ranges are accepted too. Secrets never ride these
|
|
15
|
+
* responses: a relay key is written once via `POST /key`, a panel token
|
|
16
|
+
* never leaves the credentials store, and `/group-keys` answers metadata
|
|
17
|
+
* only. The webserver rejects duplicate (kind, path) registrations, so each
|
|
18
|
+
* path dispatches by method inside one handler.
|
|
19
|
+
*
|
|
20
|
+
* @module sub2api-sync/routes
|
|
21
|
+
*/
|
|
22
|
+
import type { IncomingMessage } from 'node:http';
|
|
23
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
24
|
+
import type { SyncReport } from './engin./engine.jsrt type { IconPayload } from './icon../icon.jsrt type { AuthKind } from './panel./panel.jsrt type { CallbackLang } from './oauth./oauth.jsThe hand-back page's locale from the callback request's Accept-Language:
|
|
25
|
+
* the first tag decides (`en*` → en, anything else → zh — matching the
|
|
26
|
+
* browser's preference order without full q-value negotiation). */
|
|
27
|
+
export declare function callbackLangOf(acceptLanguage: string | undefined): CallbackLang;
|
|
28
|
+
export declare const ROUTES: {
|
|
29
|
+
readonly sync: "/plugins/dsh-sub2api-sync/sync";
|
|
30
|
+
readonly status: "/plugins/dsh-sub2api-sync/status";
|
|
31
|
+
readonly login: "/plugins/dsh-sub2api-sync/login";
|
|
32
|
+
readonly login2fa: "/plugins/dsh-sub2api-sync/login/2fa";
|
|
33
|
+
readonly logout: "/plugins/dsh-sub2api-sync/logout";
|
|
34
|
+
readonly groups: "/plugins/dsh-sub2api-sync/groups";
|
|
35
|
+
readonly keys: "/plugins/dsh-sub2api-sync/keys";
|
|
36
|
+
readonly icon: "/plugins/dsh-sub2api-sync/icon";
|
|
37
|
+
readonly site: "/plugins/dsh-sub2api-sync/site";
|
|
38
|
+
readonly account: "/plugins/dsh-sub2api-sync/account";
|
|
39
|
+
readonly resetSync: "/plugins/dsh-sub2api-sync/reset-sync";
|
|
40
|
+
readonly removeKey: "/plugins/dsh-sub2api-sync/remove-key";
|
|
41
|
+
readonly oauthStart: "/plugins/dsh-sub2api-sync/oauth/start";
|
|
42
|
+
readonly oauthProbe: "/plugins/dsh-sub2api-sync/oauth/probe";
|
|
43
|
+
readonly oauthCallback: "/plugins/dsh-sub2api-sync/oauth/callback";
|
|
44
|
+
readonly oauthStatus: "/plugins/dsh-sub2api-sync/oauth/status";
|
|
45
|
+
readonly oauthCancel: "/plugins/dsh-sub2api-sync/oauth/cancel";
|
|
46
|
+
};
|
|
47
|
+
/** One relay key as the keys card renders it (metadata only, no plaintext). */
|
|
48
|
+
export interface PanelKeyMeta {
|
|
49
|
+
id: number;
|
|
50
|
+
name: string;
|
|
51
|
+
groupId: number;
|
|
52
|
+
status?: string;
|
|
53
|
+
}
|
|
54
|
+
/** `GET /keys` — one page of the aggregated key list (newest first). */
|
|
55
|
+
export interface KeysResponse {
|
|
56
|
+
ok: true;
|
|
57
|
+
keys: PanelKeyMeta[];
|
|
58
|
+
total: number;
|
|
59
|
+
pages: number;
|
|
60
|
+
}
|
|
61
|
+
/** One dep-provided key list page (host seam between client and panel). */
|
|
62
|
+
export interface KeysListPage {
|
|
63
|
+
keys: PanelKeyMeta[];
|
|
64
|
+
total: number;
|
|
65
|
+
pages: number;
|
|
66
|
+
}
|
|
67
|
+
/** `POST /keys` — create one relay key (name defaults to the group name). */
|
|
68
|
+
export interface CreateKeyResponse {
|
|
69
|
+
ok: true;
|
|
70
|
+
key: PanelKeyMeta;
|
|
71
|
+
}
|
|
72
|
+
/** Auth snapshot the card renders its account block from. */
|
|
73
|
+
export interface AuthView {
|
|
74
|
+
signedIn: boolean;
|
|
75
|
+
kind: AuthKind;
|
|
76
|
+
email?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface StatusRoute {
|
|
79
|
+
route: string;
|
|
80
|
+
displayName?: string;
|
|
81
|
+
api?: string;
|
|
82
|
+
models: number;
|
|
83
|
+
modelsPreview: string[];
|
|
84
|
+
/** The panel key id behind this route (from the engine's route→id map);
|
|
85
|
+
* absent for routes synced before the map existed. */
|
|
86
|
+
keyId?: number;
|
|
87
|
+
}
|
|
88
|
+
export interface StatusResponse {
|
|
89
|
+
ok: true;
|
|
90
|
+
auth: AuthView;
|
|
91
|
+
instance: string;
|
|
92
|
+
syncRunning: boolean;
|
|
93
|
+
routes: StatusRoute[];
|
|
94
|
+
lastReport?: SyncReport;
|
|
95
|
+
/** Wall-clock ms of the last accepted sync (client renders it relatively). */
|
|
96
|
+
lastReportAt?: number;
|
|
97
|
+
}
|
|
98
|
+
export interface GroupsView {
|
|
99
|
+
id: number;
|
|
100
|
+
name: string;
|
|
101
|
+
platform: string;
|
|
102
|
+
status?: string;
|
|
103
|
+
rateMultiplier?: number;
|
|
104
|
+
isExclusive?: boolean;
|
|
105
|
+
claudeCodeOnly?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface GroupsResponse {
|
|
108
|
+
ok: true;
|
|
109
|
+
groups: GroupsView[];
|
|
110
|
+
}
|
|
111
|
+
/** `POST /reset-sync` — purge the plugin's sync state (instance switch). */
|
|
112
|
+
export interface ResetSyncResponse {
|
|
113
|
+
ok: true;
|
|
114
|
+
}
|
|
115
|
+
/** `POST /remove-key` — remove one key's synced provider (report entry button). */
|
|
116
|
+
export interface RemoveKeyResponse {
|
|
117
|
+
ok: true;
|
|
118
|
+
}
|
|
119
|
+
/** `GET /site` — the instance's configured site name (undefined when unknown). */
|
|
120
|
+
export interface SiteResponse {
|
|
121
|
+
ok: true;
|
|
122
|
+
name?: string;
|
|
123
|
+
}
|
|
124
|
+
/** `GET /account` — the signed-in panel account's identity (header rendering). */
|
|
125
|
+
export interface AccountResponse {
|
|
126
|
+
ok: true;
|
|
127
|
+
/** The account's set username, when one exists. */
|
|
128
|
+
username?: string;
|
|
129
|
+
/** The account's email (raw — the card masks it before display). */
|
|
130
|
+
email?: string;
|
|
131
|
+
/** The account's balance in USD (header pill; absent when not numeric). */
|
|
132
|
+
balance?: number;
|
|
133
|
+
/** The upstream failure reason when the profile could not be fetched
|
|
134
|
+
* (the header is cosmetic and falls back, but the cause must be visible). */
|
|
135
|
+
error?: string;
|
|
136
|
+
}
|
|
137
|
+
export interface LoginResponse {
|
|
138
|
+
ok: true;
|
|
139
|
+
kind: 'ok' | '2fa';
|
|
140
|
+
requires2FA?: boolean;
|
|
141
|
+
}
|
|
142
|
+
/** `GET /oauth/probe?address=…` — whether the instance publishes OAuth
|
|
143
|
+
* discovery metadata (the sign-in dialog auto-selects its channel from it). */
|
|
144
|
+
export interface OAuthProbeResponse {
|
|
145
|
+
ok: true;
|
|
146
|
+
supported: boolean;
|
|
147
|
+
}
|
|
148
|
+
export interface SyncResponse {
|
|
149
|
+
ok: true;
|
|
150
|
+
report: SyncReport;
|
|
151
|
+
}
|
|
152
|
+
/** `POST /oauth/start` — one authorization attempt, opened in a new tab. */
|
|
153
|
+
export interface OAuthStartResponse {
|
|
154
|
+
ok: true;
|
|
155
|
+
attemptId: string;
|
|
156
|
+
authorizeUrl: string;
|
|
157
|
+
}
|
|
158
|
+
/** `GET /oauth/status?attempt=<id>` — dialog poll for the web channel. */
|
|
159
|
+
export interface OAuthAttemptStatusResponse {
|
|
160
|
+
ok: true;
|
|
161
|
+
attempt: {
|
|
162
|
+
phase: 'pending' | 'completed' | 'failed';
|
|
163
|
+
error?: string;
|
|
164
|
+
} | undefined;
|
|
165
|
+
auth: AuthView;
|
|
166
|
+
}
|
|
167
|
+
/** `POST /oauth/cancel` — drop a pending attempt. */
|
|
168
|
+
export interface OAuthCancelResponse {
|
|
169
|
+
ok: true;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Accept only trusted peers: a loopback peer that is not a cross-site
|
|
173
|
+
* request and whose Origin (when present) matches the Host. With
|
|
174
|
+
* `allowRemote` (the user's cross-device escape hatch, §11) private-network
|
|
175
|
+
* peers are accepted under the same origin rules — public peers never are.
|
|
176
|
+
*/
|
|
177
|
+
export declare function trustedRequest(req: IncomingMessage, allowRemote?: boolean): boolean;
|
|
178
|
+
export interface RouteDeps {
|
|
179
|
+
/** Instance address as configured (echoed to the card for the header). */
|
|
180
|
+
instance(): string;
|
|
181
|
+
/** Current auth snapshot (credential-derived; email rides the config). */
|
|
182
|
+
authState(): Promise<AuthView>;
|
|
183
|
+
/** Password login; stores email/password and the token pair on success. */
|
|
184
|
+
login(email: string, password: string | undefined): Promise<LoginResponse>;
|
|
185
|
+
/** Complete the pending 2FA login. */
|
|
186
|
+
login2FA(code: string): Promise<void>;
|
|
187
|
+
/** Drop panel tokens. */
|
|
188
|
+
logout(): Promise<void>;
|
|
189
|
+
/** Live panel groups as the panel reports them (the card's checked state
|
|
190
|
+
* rides the config's `selectedKeys`, not this response). */
|
|
191
|
+
listGroups(): Promise<GroupsView[]>;
|
|
192
|
+
/** Run one sync; the implementation serializes and records the report. */
|
|
193
|
+
sync(keyIds?: number[]): Promise<SyncReport>;
|
|
194
|
+
lastReport(): SyncReport | undefined;
|
|
195
|
+
lastReportAt(): number | undefined;
|
|
196
|
+
/** One page of the panel's keys (newest first; page is 1-based). */
|
|
197
|
+
listKeys(page: number, pageSize: number): Promise<KeysListPage>;
|
|
198
|
+
/** Create one relay key (name defaults to the group name upstream of this seam). */
|
|
199
|
+
createKey(input: {
|
|
200
|
+
groupId: number;
|
|
201
|
+
name: string;
|
|
202
|
+
}): Promise<PanelKeyMeta>;
|
|
203
|
+
/** The instance's icon bytes (declared `<link rel=icon>` → /favicon.ico), or undefined. */
|
|
204
|
+
icon(): Promise<IconPayload | undefined>;
|
|
205
|
+
/** The instance's configured site name (parsed from its HTML title), or undefined. */
|
|
206
|
+
siteName(): Promise<string | undefined>;
|
|
207
|
+
/** The signed-in panel account's identity (username/email/balance), or undefined when unavailable. */
|
|
208
|
+
account(): Promise<{
|
|
209
|
+
username?: string;
|
|
210
|
+
email?: string;
|
|
211
|
+
balance?: number;
|
|
212
|
+
} | undefined>;
|
|
213
|
+
/**
|
|
214
|
+
* Purge this plugin's sync state: remove every owned provider route and
|
|
215
|
+
* its relay-key credential, plus (when `previousBaseURL` is given) the
|
|
216
|
+
* old instance's unreachable panel secrets. The card calls it on instance
|
|
217
|
+
* switch; logout and auth-loss purge inside the host.
|
|
218
|
+
*/
|
|
219
|
+
resetSync(previousBaseURL?: string): Promise<void>;
|
|
220
|
+
/**
|
|
221
|
+
* Remove one key's synced provider: the route derived from the current
|
|
222
|
+
* instance + `keyId` leaves the llm-pi-ai section and its relay-key
|
|
223
|
+
* credential is dropped. Local state only — no panel calls, and the
|
|
224
|
+
* selection drop happens card-side so the next sync cannot resurrect it.
|
|
225
|
+
*/
|
|
226
|
+
removeSyncedKey(keyId: number): Promise<void>;
|
|
227
|
+
isSyncRunning(): boolean;
|
|
228
|
+
/** Owned routes currently registered in the llm-pi-ai section. */
|
|
229
|
+
ownedRoutes(): StatusRoute[];
|
|
230
|
+
/** Whether the cross-device escape hatch is on (Config, default false). */
|
|
231
|
+
allowRemote(): boolean;
|
|
232
|
+
/** Begin one OAuth authorization attempt bound to this harness origin. */
|
|
233
|
+
oauthStart(callbackOrigin: string): Promise<{
|
|
234
|
+
attemptId: string;
|
|
235
|
+
authorizeUrl: string;
|
|
236
|
+
}>;
|
|
237
|
+
/** Whether the given instance address publishes OAuth discovery metadata
|
|
238
|
+
* (host-side fetch — the browser cannot probe cross-origin). */
|
|
239
|
+
oauthProbe(address: string): Promise<boolean>;
|
|
240
|
+
/** Consume the provider's browser redirect: match state, exchange the code.
|
|
241
|
+
* `lang` is the callback request's Accept-Language locale for the page. */
|
|
242
|
+
oauthCallback(query: URLSearchParams, lang: CallbackLang): Promise<{
|
|
243
|
+
html: string;
|
|
244
|
+
}>;
|
|
245
|
+
/** Project one attempt's phase plus the current auth snapshot. */
|
|
246
|
+
oauthStatus(attemptId: string | undefined): Promise<OAuthAttemptStatusResponse>;
|
|
247
|
+
/** Drop a pending attempt (user cancelled the dialog). */
|
|
248
|
+
oauthCancel(attemptId: string): Promise<void>;
|
|
249
|
+
}
|
|
250
|
+
/** Register all card routes on the web server (no-op without one). */
|
|
251
|
+
export declare function registerRoutes(ctx: Context, deps: RouteDeps): void;
|
|
252
|
+
/**
|
|
253
|
+
* Assemble the `ownedRoutes` view for the status endpoint from the live
|
|
254
|
+
* `llm-pi-ai` section value (same-process caller, no redaction).
|
|
255
|
+
*/
|
|
256
|
+
export declare function describeOwnedRoutes(section: unknown): StatusRoute[];
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared vocabulary of the sub2api sync plugin: platforms, wire protocols,
|
|
3
|
+
* route/credential naming, service interfaces, and the ownership boundary
|
|
4
|
+
* this plugin enforces inside `llm-pi-ai.providers`.
|
|
5
|
+
*
|
|
6
|
+
* rev.5: routes are opaque — `sub2api-<10-hex>` derived from SHA-256 over
|
|
7
|
+
* (instance baseURL, panel key id). The panel's sequential key id never
|
|
8
|
+
* appears in route or credential names, so a shared config screenshot cannot
|
|
9
|
+
* reveal how many keys the site has handed out. Legacy `sub2api-<digits>`
|
|
10
|
+
* routes stay recognized (owned + cleaned up) so existing deployments
|
|
11
|
+
* migrate on the next sync; the plaintext credential gains a legacy-ref
|
|
12
|
+
* fallback read for the same reason.
|
|
13
|
+
*
|
|
14
|
+
* @module sub2api-sync/types
|
|
15
|
+
*/
|
|
16
|
+
/** Platforms sub2api assigns to groups (verified: `backend/internal/domain/constants.go`; live instance 2026-09-30). */
|
|
17
|
+
export declare const PLATFORMS: readonly ["anthropic", "openai", "gemini", "antigravity", "grok", "kimi", "zhipu", "deepseek", "composite"];
|
|
18
|
+
export type Sub2apiPlatform = (typeof PLATFORMS)[number];
|
|
19
|
+
/** Human labels for the group types, matching the platform picker's wording. */
|
|
20
|
+
export declare const PLATFORM_LABELS: Record<Sub2apiPlatform, string>;
|
|
21
|
+
/** The display label for a group type; unknown values pass through raw so a
|
|
22
|
+
* future sub2api platform still shows something truthful. */
|
|
23
|
+
export declare function platformLabel(platform: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* The default provider-name template (rev.4 decision). Lives here — not in
|
|
26
|
+
* `config.ts` — so the browser half can value-import it without pulling the
|
|
27
|
+
* schemastery-backed schema (the client bundle must only request baseline
|
|
28
|
+
* externals; see test/client-bundle.test.ts).
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_PROVIDER_NAME_TEMPLATE = "{siteName}({keyName})";
|
|
31
|
+
/**
|
|
32
|
+
* Wire protocols llm-pi-ai can speak; must stay inside its
|
|
33
|
+
* `supportedProtocols()` set or the write is refused with `settings-rejected`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const API_PROTOCOLS: readonly ["openai-completions", "openai-responses", "anthropic-messages"];
|
|
36
|
+
export type ApiProtocol = (typeof API_PROTOCOLS)[number];
|
|
37
|
+
/** Route prefix this plugin owns inside `llm-pi-ai.providers`. */
|
|
38
|
+
export declare const ROUTE_PREFIX = "sub2api-";
|
|
39
|
+
/**
|
|
40
|
+
* Ownership boundary: only `sub2api-<10-hex>` (rev.5 opaque slug) and the
|
|
41
|
+
* legacy `sub2api-<digits>` routes belong to this plugin. Anything else —
|
|
42
|
+
* including the community plugin's `sub2api-openai` style routes — stays out
|
|
43
|
+
* of this plugin's write and cleanup set.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isOwnedRoute(route: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* FIPS 180-4 SHA-256 over UTF-8 input, pure JS so both the host half and the
|
|
48
|
+
* browser bundle can use it without pulling `node:crypto`. Only used for
|
|
49
|
+
* non-secret route slugs; tests pin the standard vectors.
|
|
50
|
+
*/
|
|
51
|
+
export declare function sha256Hex(input: string): string;
|
|
52
|
+
/** Route slug for one relay key: SHA-256 over (instance, key id), truncated. */
|
|
53
|
+
export declare function keyRouteSlug(baseURL: string, keyId: number): string;
|
|
54
|
+
/**
|
|
55
|
+
* The llm-pi-ai route name for one selected relay key. The slug is derived
|
|
56
|
+
* from the instance address and the panel key id — stable across syncs and
|
|
57
|
+
* key renames, collision-free across instances, and it carries no hint about
|
|
58
|
+
* the panel's sequential key numbering.
|
|
59
|
+
*/
|
|
60
|
+
export declare function routeForKey(baseURL: string, keyId: number): string;
|
|
61
|
+
/** The route's suffix (slug or legacy digits), when the shape is owned. */
|
|
62
|
+
export declare function suffixOfRoute(route: string): string | undefined;
|
|
63
|
+
/** The panel key id encoded in a LEGACY digit route, when the shape matches. */
|
|
64
|
+
export declare function legacyKeyIdOfRoute(route: string): number | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* FNV-1a 32-bit string hash. Used to derive per-instance credential
|
|
67
|
+
* reference suffixes (tokens/password are secrets of one panel instance;
|
|
68
|
+
* hashing the baseURL keeps refs stable yet instance-scoped).
|
|
69
|
+
*/
|
|
70
|
+
export declare function hashString(input: string): number;
|
|
71
|
+
/** Normalized instance key the credential suffixes derive from. */
|
|
72
|
+
export declare function instanceKey(baseURL: string): string;
|
|
73
|
+
/** Panel access-token credential reference for one instance. */
|
|
74
|
+
export declare function panelTokenRef(baseURL: string): string;
|
|
75
|
+
/** Panel refresh-token credential reference for one instance. */
|
|
76
|
+
export declare function panelRefreshRef(baseURL: string): string;
|
|
77
|
+
/** Panel password credential reference for one instance (Mode A, C3). */
|
|
78
|
+
export declare function panelPasswordRef(baseURL: string): string;
|
|
79
|
+
/** OAuth access-token credential reference for one instance (C2). */
|
|
80
|
+
export declare function oauthTokenRef(baseURL: string): string;
|
|
81
|
+
/** OAuth refresh-token credential reference for one instance (C2). */
|
|
82
|
+
export declare function oauthRefreshRef(baseURL: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Credential reference carrying one route's relay key. Derived from the
|
|
85
|
+
* route suffix, so the lifecycle follows the route exactly: removal unsets
|
|
86
|
+
* it without needing the panel key id. New routes hash-scope the ref per
|
|
87
|
+
* instance; legacy digit routes keep their historical ref shape.
|
|
88
|
+
*/
|
|
89
|
+
export declare function routeKeyRef(route: string): string;
|
|
90
|
+
/**
|
|
91
|
+
* Legacy per-key-id credential reference (pre-rev.5). Kept as a READ
|
|
92
|
+
* fallback during migration: deployments that stored plaintext under
|
|
93
|
+
* `DSH_SUB2API_KEY_<id>` keep syncing without re-entering the key. Never
|
|
94
|
+
* written anymore; only unset when the matching legacy route is removed.
|
|
95
|
+
*/
|
|
96
|
+
export declare function legacyKeyRef(keyId: number): string;
|
|
97
|
+
/**
|
|
98
|
+
* Parse a user/config-supplied group id: decimal digits within the safe
|
|
99
|
+
* integer range. Group ids are int64 at the sub2api side; anything beyond
|
|
100
|
+
* `Number.MAX_SAFE_INTEGER` fails loudly instead of being silently mangled.
|
|
101
|
+
*/
|
|
102
|
+
export declare function parseGroupId(input: unknown): number | undefined;
|
|
103
|
+
/** Minimal structural view of the settings service the engine needs. */
|
|
104
|
+
export interface SettingsLike {
|
|
105
|
+
describe(options?: {
|
|
106
|
+
redactSecrets?: boolean;
|
|
107
|
+
}): Array<{
|
|
108
|
+
ns: string;
|
|
109
|
+
revision: number;
|
|
110
|
+
value?: unknown;
|
|
111
|
+
}>;
|
|
112
|
+
mutate(ns: string, ops: SettingsPathOp[], expectedRevision?: number): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
/** One settings path operation (shared by llm-pi-ai writes and own-ns writes). */
|
|
115
|
+
export type SettingsPathOp = {
|
|
116
|
+
op: 'set';
|
|
117
|
+
path: string[];
|
|
118
|
+
value: unknown;
|
|
119
|
+
} | {
|
|
120
|
+
op: 'unset';
|
|
121
|
+
path: string[];
|
|
122
|
+
};
|
|
123
|
+
/** Minimal structural view of the credentials service the engine needs. */
|
|
124
|
+
export interface CredentialsLike {
|
|
125
|
+
resolve(ref: string): Promise<{
|
|
126
|
+
value: string;
|
|
127
|
+
} | undefined>;
|
|
128
|
+
set(ref: string, value: string): Promise<void>;
|
|
129
|
+
unset(ref: string): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The settings service's concurrent-writer error shape (`code` or `name`).
|
|
133
|
+
* One predicate for every conflict surface: the shared mutate helper below,
|
|
134
|
+
* the engine's plan loop, and the route layer's 409 projection.
|
|
135
|
+
*/
|
|
136
|
+
export declare function isSettingsConflict(error: unknown): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Conflict-retried write into one settings namespace: build the ops, read
|
|
139
|
+
* the namespace's live revision, mutate, and on a concurrent-writer conflict
|
|
140
|
+
* rebuild + retry (bounded — 3 attempts total, matching the engine's policy).
|
|
141
|
+
*
|
|
142
|
+
* `buildOps` runs per attempt so a retry can recompute the plan against the
|
|
143
|
+
* just-re-read state; it may close over outer holders to surface the final
|
|
144
|
+
* ops/actions (the engine does exactly that for its sync report). An empty
|
|
145
|
+
* ops list skips the wire round trip entirely — but the namespace must still
|
|
146
|
+
* exist (the same loud failure the engine's revision read used to produce).
|
|
147
|
+
*/
|
|
148
|
+
export declare function mutateWithRetry(settings: SettingsLike, ns: string, buildOps: () => SettingsPathOp[] | Promise<SettingsPathOp[]>, options?: {
|
|
149
|
+
missingError?: string;
|
|
150
|
+
attempts?: number;
|
|
151
|
+
}): Promise<void>;
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,127 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mangoc/dsh-sub2api-sync",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Sync one sub2api instance's groups, keys, and models into dsh-llm-pi-ai provider routes: panel-driven login (password / OAuth) first, one group one provider.",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Shuai-S",
|
|
7
|
+
"homepage": "https://github.com/Shuai-S/dsh-sub2api-sync#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Shuai-S/dsh-sub2api-sync.git",
|
|
11
|
+
"directory": "packages/llm/sub2api-sync"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/Shuai-S/dsh-sub2api-sync/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dsh",
|
|
16
|
+
"dsh-plugin",
|
|
17
|
+
"sub2api",
|
|
18
|
+
"provider-sync",
|
|
19
|
+
"llm"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org/"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "lib/index.js",
|
|
27
|
+
"types": "lib/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./lib/index.d.ts",
|
|
31
|
+
"default": "./lib/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./client": "./lib/client.js",
|
|
34
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
35
|
+
"./package.json": "./package.json",
|
|
36
|
+
"./locale/*.json": "./locale/*.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"lib",
|
|
40
|
+
"data",
|
|
41
|
+
"locale",
|
|
42
|
+
"cordis.patch.yml",
|
|
43
|
+
"README.md",
|
|
44
|
+
"README.zh.md"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dsh": {
|
|
50
|
+
"bundle": {
|
|
51
|
+
"patch": "./cordis.patch.yml"
|
|
52
|
+
},
|
|
53
|
+
"client": {
|
|
54
|
+
"inject": [
|
|
55
|
+
"@deepseek-ai/dsh-client-locale",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
57
|
+
],
|
|
58
|
+
"platform": "web"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-locale": ">=0.2.0-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.2.0-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.2.0-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-credentials": "*",
|
|
70
|
+
"@deepseek-ai/dsh-host-webserver": "*",
|
|
71
|
+
"@deepseek-ai/dsh-settings": "*",
|
|
72
|
+
"react": "^18.2.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"@deepseek-ai/cordis": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-primitives": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@deepseek-ai/dsh-client-ui-settings": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"@deepseek-ai/dsh-credentials": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"@deepseek-ai/dsh-host-webserver": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"@deepseek-ai/dsh-settings": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"react": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"devDependencies": {
|
|
101
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
102
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
103
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.0-rc.2",
|
|
104
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
|
|
105
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
|
|
106
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
|
|
107
|
+
"@deepseek-ai/dsh-client-ui-settings-models": "0.2.0-rc.2",
|
|
108
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
|
|
109
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
110
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
|
|
111
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.2",
|
|
112
|
+
"@types/node": "^22.20.0",
|
|
113
|
+
"@types/react": "~18.3.1",
|
|
114
|
+
"react": "^18.2.0",
|
|
115
|
+
"tsdown": "^0.4.0",
|
|
116
|
+
"typescript": "^5.7.0",
|
|
117
|
+
"vitest": "^3.2.4"
|
|
118
|
+
},
|
|
119
|
+
"scripts": {
|
|
120
|
+
"build": "tsdown && node scripts/wrap-client.mjs",
|
|
121
|
+
"pack:all": "node scripts/pack-all.mjs",
|
|
122
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
|
|
123
|
+
"test": "vitest run",
|
|
124
|
+
"models:refresh": "node scripts/fetch-models-dev.mjs",
|
|
125
|
+
"check": "pnpm typecheck && pnpm test && pnpm build"
|
|
126
|
+
}
|
|
6
127
|
}
|