dsh-sub2api-sync 0.0.0-stage → 0.1.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/README.md +155 -2
- package/README.zh.md +124 -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 +89 -0
- package/lib/engine.d.ts +129 -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 +2836 -0
- package/lib/oauth.d.ts +128 -0
- package/lib/panel.d.ts +203 -0
- package/lib/protocols.d.ts +119 -0
- package/lib/routes.d.ts +263 -0
- package/lib/types.d.ts +130 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +124 -3
package/lib/oauth.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Authorization Code + PKCE (S256) client for the sub2api provider.
|
|
3
|
+
*
|
|
4
|
+
* Implements the plugin-side half of the 2026-10-01 design:
|
|
5
|
+
* - endpoint discovery from `/.well-known/oauth-authorization-server`
|
|
6
|
+
* (RFC 8414) with the `/api/v1/oauth/.well-known` compatibility alias as
|
|
7
|
+
* the fallback — discovery failure means OAuth is unavailable;
|
|
8
|
+
* - PKCE: 64-char base64url verifier, S256 challenge only (never `plain`);
|
|
9
|
+
* - one-shot authorization attempts: `state` is single-use and expires with
|
|
10
|
+
* the attempt, the verifier never leaves the host process, and the final
|
|
11
|
+
* token exchange always re-submits the exact redirect_uri + verifier;
|
|
12
|
+
* - loopback-only redirect derivation: the receiver is this harness's
|
|
13
|
+
* webserver, so the origin must be 127.0.0.1 / [::1] (RFC 8252 §7.3 —
|
|
14
|
+
* the port may vary, the path is the plugin's fixed callback route).
|
|
15
|
+
*
|
|
16
|
+
* @module sub2api-sync/oauth
|
|
17
|
+
*/
|
|
18
|
+
/** The public desktop client the sub2api provider seeds for this plugin. */
|
|
19
|
+
export declare const OAUTH_CLIENT_ID = "dsh-sub2api-sync";
|
|
20
|
+
/** The callback path this plugin registers on the harness webserver. */
|
|
21
|
+
export declare const OAUTH_CALLBACK_PATH = "/plugins/dsh-sub2api-sync/oauth/callback";
|
|
22
|
+
/** An authorization attempt lives exactly this long, state included. */
|
|
23
|
+
export declare const OAUTH_ATTEMPT_TTL_MS: number;
|
|
24
|
+
/** Scopes the plugin requests; `keys:revoke` only when orphan revocation is on. */
|
|
25
|
+
export declare function oauthScopesFor(deleteOrphanKeys: boolean | undefined): string;
|
|
26
|
+
/** Failure with the provider's OAuth error code attached (when present). */
|
|
27
|
+
export declare class OAuthError extends Error {
|
|
28
|
+
readonly code: string;
|
|
29
|
+
constructor(code: string, message: string);
|
|
30
|
+
}
|
|
31
|
+
/** The provider endpoints the plugin talks to (discovered, never hardcoded). */
|
|
32
|
+
export interface OAuthEndpoints {
|
|
33
|
+
authorizationEndpoint: string;
|
|
34
|
+
tokenEndpoint: string;
|
|
35
|
+
revocationEndpoint?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the provider metadata: the standard RFC 8414 location first, the
|
|
39
|
+
* versioned compatibility alias second. A failed or incomplete document is a
|
|
40
|
+
* loud failure — the caller hides the OAuth channel instead of guessing URLs.
|
|
41
|
+
*/
|
|
42
|
+
export declare function discoverOAuthEndpoints(instance: string, fetchImpl?: typeof fetch, now?: number): Promise<OAuthEndpoints>;
|
|
43
|
+
/** Test seam: drop the process-wide discovery cache. */
|
|
44
|
+
export declare function resetDiscoveryCache(): void;
|
|
45
|
+
/** One PKCE pair: base64url verifier (64 chars, RFC 7636 charset subset) + S256 challenge. */
|
|
46
|
+
export declare function createPkcePair(): Promise<{
|
|
47
|
+
verifier: string;
|
|
48
|
+
challenge: string;
|
|
49
|
+
}>;
|
|
50
|
+
/** The exact loopback redirect URI for this harness session, or a loud reason. */
|
|
51
|
+
export declare function loopbackCallbackOrigin(origin: string): string;
|
|
52
|
+
export declare function buildAuthorizeUrl(endpoints: OAuthEndpoints, input: {
|
|
53
|
+
redirectUri: string;
|
|
54
|
+
scope: string;
|
|
55
|
+
state: string;
|
|
56
|
+
challenge: string;
|
|
57
|
+
}): string;
|
|
58
|
+
export interface OAuthTokenPair {
|
|
59
|
+
accessToken: string;
|
|
60
|
+
refreshToken?: string;
|
|
61
|
+
expiresIn?: number;
|
|
62
|
+
scope?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Exchange the one-time code: verifier + exact redirect_uri ride again. */
|
|
65
|
+
export declare function exchangeAuthorizationCode(endpoints: OAuthEndpoints, input: {
|
|
66
|
+
code: string;
|
|
67
|
+
redirectUri: string;
|
|
68
|
+
verifier: string;
|
|
69
|
+
}, fetchImpl?: typeof fetch): Promise<OAuthTokenPair>;
|
|
70
|
+
/** Rotating refresh: the provider answers a fresh access+refresh pair. */
|
|
71
|
+
export declare function refreshAccessToken(endpoints: OAuthEndpoints, refreshToken: string, fetchImpl?: typeof fetch): Promise<OAuthTokenPair>;
|
|
72
|
+
/** RFC 7009 revocation; best-effort by contract — failures resolve normally. */
|
|
73
|
+
export declare function revokeToken(endpoints: OAuthEndpoints, token: string, fetchImpl?: typeof fetch): Promise<void>;
|
|
74
|
+
export type OAuthAttemptPhase = 'pending' | 'completed' | 'failed';
|
|
75
|
+
export interface OAuthAttempt {
|
|
76
|
+
id: string;
|
|
77
|
+
state: string;
|
|
78
|
+
verifier: string;
|
|
79
|
+
redirectUri: string;
|
|
80
|
+
scope: string;
|
|
81
|
+
phase: OAuthAttemptPhase;
|
|
82
|
+
error?: string;
|
|
83
|
+
createdAt: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Host-side attempt registry: `state` is the single-use binding between the
|
|
87
|
+
* browser redirect and the locally held verifier. An attempt is consumed
|
|
88
|
+
* exactly once — by its state — and expires with the TTL.
|
|
89
|
+
*/
|
|
90
|
+
export declare class OAuthAttemptStore {
|
|
91
|
+
private readonly ttlMs;
|
|
92
|
+
private readonly attempts;
|
|
93
|
+
private readonly byState;
|
|
94
|
+
constructor(ttlMs?: number);
|
|
95
|
+
create(input: {
|
|
96
|
+
verifier: string;
|
|
97
|
+
redirectUri: string;
|
|
98
|
+
scope: string;
|
|
99
|
+
}, now?: number): OAuthAttempt;
|
|
100
|
+
private expired;
|
|
101
|
+
/**
|
|
102
|
+
* Take the attempt for one browser redirect — single-use: the `state`
|
|
103
|
+
* binding is consumed by this call, so a replayed redirect finds nothing.
|
|
104
|
+
* The attempt itself stays in the registry for status polls by id until
|
|
105
|
+
* it settles or expires.
|
|
106
|
+
*/
|
|
107
|
+
takeByState(state: string, now?: number): OAuthAttempt | undefined;
|
|
108
|
+
/** Mark a consumed attempt terminal (keeps it readable for status polls). */
|
|
109
|
+
settle(id: string, phase: 'completed' | 'failed', error?: string): void;
|
|
110
|
+
/** Read-only projection for the dialog's poll; unknown id → undefined. */
|
|
111
|
+
peek(id: string, now?: number): {
|
|
112
|
+
phase: OAuthAttemptPhase;
|
|
113
|
+
error?: string;
|
|
114
|
+
} | undefined;
|
|
115
|
+
cancel(id: string): boolean;
|
|
116
|
+
private drop;
|
|
117
|
+
}
|
|
118
|
+
/** Browser language for the hand-back page: the callback request's
|
|
119
|
+
* Accept-Language picks one locale; fixed UI copy renders in that language
|
|
120
|
+
* only (never the bilingual `zh · en` compound). */
|
|
121
|
+
export type CallbackLang = 'zh' | 'en';
|
|
122
|
+
/** Small HTML hand-back page for the browser redirect (§11.9 of the design).
|
|
123
|
+
* The success variant also attempts to pull the desktop app forward: DSH
|
|
124
|
+
* registers `dsh://` as an OS protocol and its shell focuses the primary
|
|
125
|
+
* window on `dsh://open`. The auto-attempt rides a hidden iframe (fails
|
|
126
|
+
* silently when the scheme is not registered, e.g. plain web UI), and an
|
|
127
|
+
* explicit button keeps a user-gesture fallback. */
|
|
128
|
+
export declare function callbackPageHtml(outcome: 'success' | 'failure', message: string, lang?: CallbackLang): string;
|
package/lib/panel.d.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel (控制台) API client and credential gateway for one sub2api instance.
|
|
3
|
+
*
|
|
4
|
+
* Shapes verified against a live instance (2026-09-30, NexaRelay):
|
|
5
|
+
* - envelope `{code, message, data}` on success; errors carry code/message;
|
|
6
|
+
* - `GET /keys` answers a paginated envelope whose items embed the relay-key
|
|
7
|
+
* **plaintext** and the nested group object;
|
|
8
|
+
* - `GET /groups/available` answers `data` as a **bare array** (not paginated)
|
|
9
|
+
* with `platform` on every element — Mode A reads the platform directly.
|
|
10
|
+
*
|
|
11
|
+
* Reading discipline: take the known fields, ignore the rest (the deployment
|
|
12
|
+
* is a fork whose DTO is a superset of upstream).
|
|
13
|
+
*
|
|
14
|
+
* @module sub2api-sync/panel
|
|
15
|
+
*/
|
|
16
|
+
import type { CredentialsLike } from './types../types.jst { panelPasswordRef } from './types../types.jst { type OAuthEndpoints, type OAuthTokenPair } from './oauth../oauth.jsoud panel failure with the HTTP status and envelope message attached. */
|
|
17
|
+
export declare class PanelError extends Error {
|
|
18
|
+
readonly status: number;
|
|
19
|
+
readonly code?: unknown;
|
|
20
|
+
constructor(status: number, message: string, code?: unknown);
|
|
21
|
+
}
|
|
22
|
+
/** One panel group as the plugin consumes it (tolerant subset). */
|
|
23
|
+
export interface PanelGroup {
|
|
24
|
+
id: number;
|
|
25
|
+
name: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
platform: string;
|
|
28
|
+
rateMultiplier?: number;
|
|
29
|
+
status?: string;
|
|
30
|
+
isExclusive?: boolean;
|
|
31
|
+
claudeCodeOnly?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** One panel relay key (plaintext present only when the API returns it). */
|
|
34
|
+
export interface PanelKey {
|
|
35
|
+
id: number;
|
|
36
|
+
name: string;
|
|
37
|
+
groupId: number;
|
|
38
|
+
status?: string;
|
|
39
|
+
/** Plaintext relay key; absent when the panel masked it. */
|
|
40
|
+
key?: string;
|
|
41
|
+
quota?: number;
|
|
42
|
+
expiresAt?: string;
|
|
43
|
+
}
|
|
44
|
+
/** The signed-in account's panel identity (card header rendering). */
|
|
45
|
+
export interface PanelProfile {
|
|
46
|
+
/** The account's set username, when one exists. */
|
|
47
|
+
username?: string;
|
|
48
|
+
/** The account's email (raw — the caller masks it before display). */
|
|
49
|
+
email?: string;
|
|
50
|
+
/** The account's balance in USD (profile `balance`; absent when not numeric). */
|
|
51
|
+
balance?: number;
|
|
52
|
+
}
|
|
53
|
+
/** One page of the panel's native key listing. */
|
|
54
|
+
export interface PanelKeyPage {
|
|
55
|
+
items: PanelKey[];
|
|
56
|
+
total: number;
|
|
57
|
+
pages: number;
|
|
58
|
+
}
|
|
59
|
+
export type LoginOutcome = {
|
|
60
|
+
kind: 'ok';
|
|
61
|
+
accessToken: string;
|
|
62
|
+
refreshToken?: string;
|
|
63
|
+
} | {
|
|
64
|
+
kind: '2fa';
|
|
65
|
+
tempToken: string;
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'captcha';
|
|
68
|
+
message: string;
|
|
69
|
+
};
|
|
70
|
+
export interface PanelClientOptions {
|
|
71
|
+
baseURL: string;
|
|
72
|
+
fetchImpl?: typeof fetch;
|
|
73
|
+
}
|
|
74
|
+
/** Raw panel HTTP client; every authed method takes the access token explicitly. */
|
|
75
|
+
export declare class PanelClient {
|
|
76
|
+
readonly baseURL: string;
|
|
77
|
+
private readonly fetchImpl;
|
|
78
|
+
constructor(options: PanelClientOptions);
|
|
79
|
+
private call;
|
|
80
|
+
/** Password login; classifies 2FA and captcha outcomes. */
|
|
81
|
+
login(email: string, password: string): Promise<LoginOutcome>;
|
|
82
|
+
/** Second factor for the pending login. */
|
|
83
|
+
login2FA(tempToken: string, totpCode: string): Promise<{
|
|
84
|
+
accessToken: string;
|
|
85
|
+
refreshToken?: string;
|
|
86
|
+
}>;
|
|
87
|
+
/** Exchange a refresh token for a fresh pair. */
|
|
88
|
+
refresh(refreshToken: string): Promise<{
|
|
89
|
+
accessToken: string;
|
|
90
|
+
refreshToken?: string;
|
|
91
|
+
}>;
|
|
92
|
+
/** Groups the account can bind; `data` is a bare array. */
|
|
93
|
+
listAvailableGroups(token: string): Promise<PanelGroup[]>;
|
|
94
|
+
/**
|
|
95
|
+
* One page of the panel's native key listing (newest first upstream:
|
|
96
|
+
* `sort_by=created_at&sort_order=desc`, mirroring the console call shape).
|
|
97
|
+
* Callers that need every key walk `pages` themselves.
|
|
98
|
+
*/
|
|
99
|
+
listKeys(token: string, options?: {
|
|
100
|
+
groupId?: number;
|
|
101
|
+
page?: number;
|
|
102
|
+
pageSize?: number;
|
|
103
|
+
sortBy?: string;
|
|
104
|
+
sortOrder?: string;
|
|
105
|
+
}): Promise<PanelKeyPage>;
|
|
106
|
+
/** Create one relay key. */
|
|
107
|
+
createKey(token: string, input: {
|
|
108
|
+
name: string;
|
|
109
|
+
groupId: number;
|
|
110
|
+
quota?: number;
|
|
111
|
+
expiresinDays?: number;
|
|
112
|
+
}): Promise<PanelKey>;
|
|
113
|
+
/** Revoke one relay key. */
|
|
114
|
+
deleteKey(token: string, id: number): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Controlled plaintext reveal for one client-owned relay key (OAuth only):
|
|
117
|
+
* the regular OAuth list/get answers are masked, this endpoint is the
|
|
118
|
+
* documented exception — same ownership checks server-side, audited.
|
|
119
|
+
*/
|
|
120
|
+
revealKey(token: string, id: number): Promise<PanelKey>;
|
|
121
|
+
/**
|
|
122
|
+
* The signed-in account's panel identity via the OAuth userinfo endpoint
|
|
123
|
+
* (`GET /auth/me`, scope `profile` — the plugin always requests it). The
|
|
124
|
+
* richer `/user/profile` sits OUTSIDE the OAuth token's path whitelist
|
|
125
|
+
* (`requiredOAuthScope`: only /auth/me, /groups/*, /keys/*) and answers
|
|
126
|
+
* 403 INSUFFICIENT_SCOPE for OAuth sessions. Fields: username, email,
|
|
127
|
+
* balance — all optional.
|
|
128
|
+
*/
|
|
129
|
+
getProfile(token: string): Promise<PanelProfile>;
|
|
130
|
+
/** OAuth token exchange against the discovered provider (host-side only). */
|
|
131
|
+
oauthTokenExchange(endpoints: OAuthEndpoints, input: {
|
|
132
|
+
code: string;
|
|
133
|
+
redirectUri: string;
|
|
134
|
+
verifier: string;
|
|
135
|
+
}): Promise<OAuthTokenPair>;
|
|
136
|
+
/** OAuth discovery passthrough for route handlers that need endpoints. */
|
|
137
|
+
discoverOAuth(): Promise<OAuthEndpoints>;
|
|
138
|
+
}
|
|
139
|
+
export type AuthKind = 'none' | 'panel' | 'oauth';
|
|
140
|
+
export interface AuthState {
|
|
141
|
+
signedIn: boolean;
|
|
142
|
+
kind: AuthKind;
|
|
143
|
+
email?: string;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Credential-backed gateway over {@link PanelClient}: stores login
|
|
147
|
+
* tokens, classifies the auth kind, and wraps authed calls with a
|
|
148
|
+
* single-flight 401 → refresh → retry.
|
|
149
|
+
*/
|
|
150
|
+
export declare class PanelGateway {
|
|
151
|
+
private readonly baseURLSource;
|
|
152
|
+
private readonly credentials;
|
|
153
|
+
private readonly fetchImpl?;
|
|
154
|
+
private pending2FA?;
|
|
155
|
+
private refreshInFlight?;
|
|
156
|
+
private oauthRefreshInFlight?;
|
|
157
|
+
constructor(options: {
|
|
158
|
+
baseURL: string | (() => string);
|
|
159
|
+
credentials: CredentialsLike;
|
|
160
|
+
fetchImpl?: typeof fetch;
|
|
161
|
+
onAuthLost?: () => void;
|
|
162
|
+
});
|
|
163
|
+
/**
|
|
164
|
+
* Fired when the panel session is definitively gone — no refresh token, or
|
|
165
|
+
* the refresh call itself was refused. Never on network failures. The host
|
|
166
|
+
* hooks this to purge the site's sync state (logout/expiry policy).
|
|
167
|
+
*/
|
|
168
|
+
private readonly onAuthLost?;
|
|
169
|
+
/** The instance address, re-read per call so a baseURL edit takes effect. */
|
|
170
|
+
private get baseURL();
|
|
171
|
+
private client;
|
|
172
|
+
private tokenRef;
|
|
173
|
+
private refreshRef;
|
|
174
|
+
private oauthAccessRef;
|
|
175
|
+
private oauthRefresh;
|
|
176
|
+
/** Current auth state; email rides the caller's config, not credentials. */
|
|
177
|
+
authState(email?: string): Promise<AuthState>;
|
|
178
|
+
/** Password login; stores the pair and the password for silent re-login. */
|
|
179
|
+
login(email: string, password: string): Promise<LoginOutcome>;
|
|
180
|
+
/** Complete the pending 2FA login. */
|
|
181
|
+
login2FA(code: string): Promise<void>;
|
|
182
|
+
/** Sign out: revoke OAuth tokens server-side when present, then drop both channels' secrets. */
|
|
183
|
+
logout(): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Run one authed panel call. Tokens resolve per channel — OAuth access
|
|
186
|
+
* token first, then the panel JWT — and a 401 triggers a single-flight
|
|
187
|
+
* refresh (channel-appropriate) and exactly one retry before failing loud.
|
|
188
|
+
*/
|
|
189
|
+
use<T>(fn: (panel: PanelClient, token: string) => Promise<T>): Promise<T>;
|
|
190
|
+
/** Store the pair from a completed browser authorization (C2). */
|
|
191
|
+
storeOAuthTokens(pair: OAuthTokenPair): Promise<void>;
|
|
192
|
+
/** Revoke OAuth tokens at the provider (best effort) and drop the stored secrets. */
|
|
193
|
+
oauthSignOut(): Promise<void>;
|
|
194
|
+
/** Single-flight OAuth refresh (rotating); concurrent 401s share one wire call. */
|
|
195
|
+
private refreshOAuthSingleFlight;
|
|
196
|
+
private refreshOAuth;
|
|
197
|
+
/** Single-flight refresh; concurrent 401s share one wire call. */
|
|
198
|
+
private refreshSingleFlight;
|
|
199
|
+
private doRefresh;
|
|
200
|
+
private storeTokens;
|
|
201
|
+
}
|
|
202
|
+
/** Convenience export: the password credential ref for one instance. */
|
|
203
|
+
export { panelPasswordRef };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform → wire-protocol mapping and provider-profile construction.
|
|
3
|
+
*
|
|
4
|
+
* The mapping follows the sub2api gateway source (verified:
|
|
5
|
+
* `routes/gateway.go`, `isOpenAIResponsesCompatibleGatewayPlatform`): the
|
|
6
|
+
* five OpenAI-family platforms are forwarded through the gateway's OpenAI
|
|
7
|
+
* Responses path natively, anthropic through Messages, and the remaining
|
|
8
|
+
* platforms only exist behind gateway bridging — chat/completions there.
|
|
9
|
+
* Speaking the gateway's native path avoids its chat/completions ↔ native
|
|
10
|
+
* conversion, which drops or misaligns parallel tool calls.
|
|
11
|
+
*
|
|
12
|
+
* @module sub2api-sync/protocols
|
|
13
|
+
*/
|
|
14
|
+
import type { ApiProtocol } from './type./types.js One model entry inside an llm-pi-ai provider profile. */
|
|
15
|
+
export interface ProviderProfileModel {
|
|
16
|
+
id: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
contextWindow?: number;
|
|
19
|
+
maxTokens?: number;
|
|
20
|
+
input?: Array<'text' | 'image'>;
|
|
21
|
+
reasoningEfforts?: false | Record<string, string | null>;
|
|
22
|
+
compat?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface PiAiRetryPolicy {
|
|
25
|
+
mode: 'normal' | 'always';
|
|
26
|
+
maxRetries?: number;
|
|
27
|
+
retryableCodes?: string[];
|
|
28
|
+
backoff?: {
|
|
29
|
+
initialDelayMs?: number;
|
|
30
|
+
maxDelayMs?: number;
|
|
31
|
+
jitterRatio?: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** One provider route inside the `llm-pi-ai` settings section. */
|
|
35
|
+
export interface PiAiProviderProfile {
|
|
36
|
+
apiKeyEnv?: string;
|
|
37
|
+
displayName?: string;
|
|
38
|
+
api?: ApiProtocol;
|
|
39
|
+
baseURL?: string;
|
|
40
|
+
models?: ProviderProfileModel[];
|
|
41
|
+
defaultContextWindow?: number;
|
|
42
|
+
defaultMaxTokens?: number;
|
|
43
|
+
defaultInput?: Array<'text' | 'image'>;
|
|
44
|
+
retryPolicy?: PiAiRetryPolicy;
|
|
45
|
+
/** Provider-level default reasoning effort (pi-ai `reasoning`). */
|
|
46
|
+
reasoning?: ThinkingLevel;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Lenient retry policy for proxied upstreams: sub2api sits in front of
|
|
50
|
+
* provider subscriptions that enforce their own HTTP 429 windows, and
|
|
51
|
+
* llm-pi-ai's default normal policy (2 retries / 10s) cannot outlast them.
|
|
52
|
+
*/
|
|
53
|
+
export declare const SUB2API_RETRY_POLICY: Required<PiAiRetryPolicy>;
|
|
54
|
+
/**
|
|
55
|
+
* Guess one sub2api platform from discovered model ids. The gateway never
|
|
56
|
+
* answers a relay key with the group platform, but model families map
|
|
57
|
+
* cleanly: one consistent family names its platform, a mix of families is
|
|
58
|
+
* the signature of a composite group. Returns `undefined` when nothing
|
|
59
|
+
* matches — the caller keeps the manual choice authoritative.
|
|
60
|
+
*/
|
|
61
|
+
export declare function guessPlatformFromModels(modelIds: readonly string[]): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Map one sub2api platform to the wire protocol the gateway serves on its
|
|
64
|
+
* native path. Returns `undefined` for platforms outside the known set —
|
|
65
|
+
* callers fail the group loudly instead of guessing.
|
|
66
|
+
*/
|
|
67
|
+
export declare function protocolForPlatform(platform: string): ApiProtocol | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Render the provider displayName from the relay key's name. The template is
|
|
70
|
+
* a Config field (C5: must contain `{keyName}`); callers that receive an
|
|
71
|
+
* invalid template still get a sane name instead of a broken one.
|
|
72
|
+
*/
|
|
73
|
+
export declare function renderProviderName(template: string | undefined, keyName: string, siteName?: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Derive the profile baseURL for one protocol. OpenAI-family SDKs append
|
|
76
|
+
* their endpoint to a `/v1`-rooted URL; the Anthropic SDK treats the URL as
|
|
77
|
+
* a bare host and appends `/v1/messages` itself, so a `/v1` root there would
|
|
78
|
+
* produce `/v1/v1/messages` (404).
|
|
79
|
+
*/
|
|
80
|
+
export declare function baseURLForProtocol(instance: string, protocol: ApiProtocol): string;
|
|
81
|
+
/** pi-ai thinking levels a provider-level default effort may name. */
|
|
82
|
+
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
83
|
+
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
84
|
+
/** Per-model template inherited by every synced model entry (Config field). */
|
|
85
|
+
export interface ModelTemplate {
|
|
86
|
+
compat?: Record<string, unknown>;
|
|
87
|
+
reasoningEfforts?: false | Record<string, string | null>;
|
|
88
|
+
/**
|
|
89
|
+
* Provider-level default reasoning effort (pi-ai profile `reasoning`): the
|
|
90
|
+
* effort preselected for every synced model and sent when the user has not
|
|
91
|
+
* picked one. models.dev publishes no default-effort data, so this is the
|
|
92
|
+
* user's explicit choice rather than catalog-derived.
|
|
93
|
+
*/
|
|
94
|
+
reasoning?: ThinkingLevel;
|
|
95
|
+
}
|
|
96
|
+
/** One discovered model with its enrichment facts (from the models.dev index). */
|
|
97
|
+
export interface ProfileModelInput {
|
|
98
|
+
id: string;
|
|
99
|
+
/** Display name for selectors (models.dev `name`); absent keeps the id. */
|
|
100
|
+
name?: string;
|
|
101
|
+
contextWindow?: number;
|
|
102
|
+
maxTokens?: number;
|
|
103
|
+
imageInput?: boolean;
|
|
104
|
+
/** Per-model auto-filled effort map (models.dev `reasoning_options`). */
|
|
105
|
+
reasoningEfforts?: Record<string, string | null>;
|
|
106
|
+
}
|
|
107
|
+
export interface ProfileInput {
|
|
108
|
+
groupId: number;
|
|
109
|
+
/** Rendered provider display name (`renderProviderName` over the key name). */
|
|
110
|
+
displayName: string;
|
|
111
|
+
protocol: ApiProtocol;
|
|
112
|
+
/** sub2api instance address, with or without a trailing `/v1`. */
|
|
113
|
+
instance: string;
|
|
114
|
+
models: readonly ProfileModelInput[];
|
|
115
|
+
apiKeyEnv: string;
|
|
116
|
+
template?: ModelTemplate;
|
|
117
|
+
}
|
|
118
|
+
/** Build one hand-declared llm-pi-ai provider profile for a group. */
|
|
119
|
+
export declare function buildProviderProfile(input: ProfileInput): PiAiProviderProfile;
|