@eddyskywalker/dsh-chatgpt-subscription 0.42.0 → 0.43.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/CHANGELOG.md +53 -0
- package/README.md +1 -0
- package/lib/client.js +180 -144
- package/lib/client.js.map +1 -1
- package/lib/index.js +620 -216
- package/lib/types/client/command-code/CommandCodeComposerQuota.d.ts.map +1 -1
- package/lib/types/client/common/format.d.ts +16 -0
- package/lib/types/client/common/format.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiCodeComposerQuota.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiQuotaCard.d.ts.map +1 -1
- package/lib/types/client/kimi-code/styles.d.ts.map +1 -1
- package/lib/types/client/locales-ru/opendesign.d.ts.map +1 -1
- package/lib/types/client/opendesign/OpenDesignSection.d.ts.map +1 -1
- package/lib/types/client/opendesign/locales.d.ts +12 -0
- package/lib/types/client/opendesign/locales.d.ts.map +1 -1
- package/lib/types/compat.d.ts +1 -1
- package/lib/types/host/claude/types.d.ts +7 -1
- package/lib/types/host/claude/types.d.ts.map +1 -1
- package/lib/types/host/glm-coding-plan/types.d.ts +5 -1
- package/lib/types/host/glm-coding-plan/types.d.ts.map +1 -1
- package/lib/types/host/kimi-code/account-pool.d.ts +26 -1
- package/lib/types/host/kimi-code/account-pool.d.ts.map +1 -1
- package/lib/types/host/kimi-code/client.d.ts +24 -0
- package/lib/types/host/kimi-code/client.d.ts.map +1 -1
- package/lib/types/host/kimi-code/oauth.d.ts +16 -0
- package/lib/types/host/kimi-code/oauth.d.ts.map +1 -1
- package/lib/types/host/kimi-code/routes.d.ts.map +1 -1
- package/lib/types/host/opendesign/adapter.d.ts +11 -13
- package/lib/types/host/opendesign/adapter.d.ts.map +1 -1
- package/lib/types/host/opendesign/catalog.d.ts +14 -0
- package/lib/types/host/opendesign/catalog.d.ts.map +1 -1
- package/lib/types/host/opendesign/line.d.ts +17 -6
- package/lib/types/host/opendesign/line.d.ts.map +1 -1
- package/lib/types/host/opendesign/routes.d.ts.map +1 -1
- package/lib/types/host/opendesign/token-store.d.ts +91 -4
- package/lib/types/host/opendesign/token-store.d.ts.map +1 -1
- package/lib/types/host/opendesign/types.d.ts.map +1 -1
- package/lib/types/host/qoder/types.d.ts +6 -1
- package/lib/types/host/qoder/types.d.ts.map +1 -1
- package/lib/types/host/step-code/types.d.ts +5 -1
- package/lib/types/host/step-code/types.d.ts.map +1 -1
- package/lib/types/shared/opendesign-contracts.d.ts +24 -1
- package/lib/types/shared/opendesign-contracts.d.ts.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -141,7 +141,7 @@ const ROUTE_PREFIX$4 = "/api/dsh-chatgpt-subscription";
|
|
|
141
141
|
* `test/responses-client.test.ts` asserts the two agree, so a release that
|
|
142
142
|
* forgets this constant fails the suite instead of shipping quietly.
|
|
143
143
|
*/
|
|
144
|
-
const PLUGIN_VERSION = "0.
|
|
144
|
+
const PLUGIN_VERSION = "0.43.0";
|
|
145
145
|
const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
|
|
146
146
|
const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
|
|
147
147
|
const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
|
|
@@ -26310,9 +26310,11 @@ function identityFromTokens(accessToken, refreshToken) {
|
|
|
26310
26310
|
const refresh = refreshToken === void 0 ? void 0 : decodeJwtPayload(refreshToken);
|
|
26311
26311
|
const userId = claimString(access?.user_id) ?? claimString(refresh?.user_id) ?? claimString(access?.sub) ?? claimString(refresh?.sub);
|
|
26312
26312
|
const email = (claimString(access?.email) ?? claimString(refresh?.email))?.toLowerCase();
|
|
26313
|
+
const nickname = claimString(access?.nickname) ?? claimString(refresh?.nickname) ?? claimString(access?.name) ?? claimString(refresh?.name) ?? claimString(access?.preferred_username) ?? claimString(refresh?.preferred_username);
|
|
26313
26314
|
return {
|
|
26314
26315
|
...userId === void 0 ? {} : { userId },
|
|
26315
|
-
...email === void 0 ? {} : { email }
|
|
26316
|
+
...email === void 0 ? {} : { email },
|
|
26317
|
+
...nickname === void 0 ? {} : { nickname }
|
|
26316
26318
|
};
|
|
26317
26319
|
}
|
|
26318
26320
|
function parseTokenResponse$2(record) {
|
|
@@ -26511,7 +26513,7 @@ async function runDeviceLogin(options) {
|
|
|
26511
26513
|
});
|
|
26512
26514
|
if (outcome.kind === "success") {
|
|
26513
26515
|
const identity = identityFromTokens(outcome.token.accessToken, outcome.token.refreshToken);
|
|
26514
|
-
const
|
|
26516
|
+
const signedIn = {
|
|
26515
26517
|
accessToken: outcome.token.accessToken,
|
|
26516
26518
|
refreshToken: outcome.token.refreshToken,
|
|
26517
26519
|
expiresAt: outcome.token.expiresAt,
|
|
@@ -26523,8 +26525,10 @@ async function runDeviceLogin(options) {
|
|
|
26523
26525
|
baseUrl: codingBaseUrl(options.region),
|
|
26524
26526
|
authenticatedAt: Date.now(),
|
|
26525
26527
|
...identity.userId === void 0 ? {} : { userId: identity.userId },
|
|
26526
|
-
...identity.email === void 0 ? {} : { email: identity.email }
|
|
26528
|
+
...identity.email === void 0 ? {} : { email: identity.email },
|
|
26529
|
+
...identity.nickname === void 0 ? {} : { nickname: identity.nickname }
|
|
26527
26530
|
};
|
|
26531
|
+
const credentials = options.enrich === void 0 ? signedIn : await options.enrich(signedIn).catch(() => signedIn);
|
|
26528
26532
|
await options.store.write(credentials);
|
|
26529
26533
|
if (options.onSave) await options.onSave(credentials).catch(() => void 0);
|
|
26530
26534
|
await persistRegion(options.region);
|
|
@@ -26573,7 +26577,8 @@ async function beginWebLogin(store, options = {}) {
|
|
|
26573
26577
|
region,
|
|
26574
26578
|
signal: controller.signal,
|
|
26575
26579
|
open,
|
|
26576
|
-
...options.onSave === void 0 ? {} : { onSave: options.onSave }
|
|
26580
|
+
...options.onSave === void 0 ? {} : { onSave: options.onSave },
|
|
26581
|
+
...options.enrich === void 0 ? {} : { enrich: options.enrich }
|
|
26577
26582
|
});
|
|
26578
26583
|
webLoginFlow = {
|
|
26579
26584
|
...webLoginFlow,
|
|
@@ -27365,6 +27370,92 @@ function parsePlanLevel(payload) {
|
|
|
27365
27370
|
return asString$18(membership?.level ?? membership?.levelId ?? membership?.level_id) ?? null;
|
|
27366
27371
|
}
|
|
27367
27372
|
/**
|
|
27373
|
+
* Field names the service uses for the account's own name.
|
|
27374
|
+
*
|
|
27375
|
+
* `/me` documents a nickname; the platform also echoes a display name, and a
|
|
27376
|
+
* gateway envelope adds nothing but depth. All of them are the same fact: what
|
|
27377
|
+
* to title the account's card with, and what a `账号 N` placeholder should
|
|
27378
|
+
* become.
|
|
27379
|
+
*/
|
|
27380
|
+
const PROFILE_NAME_KEYS = [
|
|
27381
|
+
"nickname",
|
|
27382
|
+
"name",
|
|
27383
|
+
"username",
|
|
27384
|
+
"display_name",
|
|
27385
|
+
"displayName",
|
|
27386
|
+
"preferred_username"
|
|
27387
|
+
];
|
|
27388
|
+
/**
|
|
27389
|
+
* Every record a profile payload may keep the identity in, outermost first.
|
|
27390
|
+
*
|
|
27391
|
+
* The endpoint has answered flat (`{ nickname, email, ... }`) and nested
|
|
27392
|
+
* (`{ data: { userInfo: { ... } } }`) on different gateway versions, and a
|
|
27393
|
+
* nested envelope may hold credentials-adjacent siblings at the top level too,
|
|
27394
|
+
* so the search never commits to one shape: each container is tried in turn and
|
|
27395
|
+
* the first one that names a field wins.
|
|
27396
|
+
*/
|
|
27397
|
+
function profileRecords(payload) {
|
|
27398
|
+
const root = asRecord$13(payload);
|
|
27399
|
+
if (root === void 0) return [];
|
|
27400
|
+
const data = asRecord$13(root.data);
|
|
27401
|
+
return [
|
|
27402
|
+
root,
|
|
27403
|
+
data,
|
|
27404
|
+
asRecord$13(root.userInfo) ?? asRecord$13(data?.userInfo),
|
|
27405
|
+
asRecord$13(root.user) ?? asRecord$13(data?.user)
|
|
27406
|
+
].filter((record) => record !== void 0);
|
|
27407
|
+
}
|
|
27408
|
+
/** First value among `keys` found in any profile container, outermost first. */
|
|
27409
|
+
function profileString(payload, keys) {
|
|
27410
|
+
for (const record of profileRecords(payload)) {
|
|
27411
|
+
const found = firstString$1(record, keys);
|
|
27412
|
+
if (found !== void 0) return found;
|
|
27413
|
+
}
|
|
27414
|
+
}
|
|
27415
|
+
/**
|
|
27416
|
+
* Fold the facts a `/me` read established into a credential.
|
|
27417
|
+
*
|
|
27418
|
+
* Sign-in only names the account through its token, which carries an id and —
|
|
27419
|
+
* for an account with a mailbox — an address. The nickname lives on `/me` and
|
|
27420
|
+
* nowhere else, so the caller reads it once and stores it here; without this,
|
|
27421
|
+
* a phone-signed-in account could never be called anything but `账号 N`.
|
|
27422
|
+
*
|
|
27423
|
+
* Only fields the profile actually names are written: a gateway that drops the
|
|
27424
|
+
* nickname must not blank one learned earlier, and the token pair is copied
|
|
27425
|
+
* through untouched. Returns the same object when nothing new was learned, so a
|
|
27426
|
+
* caller can tell "nothing to store" from "store this".
|
|
27427
|
+
*/
|
|
27428
|
+
function credentialsWithProfile(credentials, profile) {
|
|
27429
|
+
const nickname = profileString(profile, PROFILE_NAME_KEYS);
|
|
27430
|
+
const email = profileString(profile, ["email"])?.toLowerCase();
|
|
27431
|
+
const userId = profileString(profile, [
|
|
27432
|
+
"user_id",
|
|
27433
|
+
"userId",
|
|
27434
|
+
"global_id",
|
|
27435
|
+
"globalId"
|
|
27436
|
+
]);
|
|
27437
|
+
const planName = parsePlanName(profile) ?? void 0;
|
|
27438
|
+
const next = { ...credentials };
|
|
27439
|
+
let changed = false;
|
|
27440
|
+
if (nickname !== void 0 && next.nickname !== nickname) {
|
|
27441
|
+
next.nickname = nickname;
|
|
27442
|
+
changed = true;
|
|
27443
|
+
}
|
|
27444
|
+
if (email !== void 0 && next.email !== email) {
|
|
27445
|
+
next.email = email;
|
|
27446
|
+
changed = true;
|
|
27447
|
+
}
|
|
27448
|
+
if (userId !== void 0 && next.userId !== userId) {
|
|
27449
|
+
next.userId = userId;
|
|
27450
|
+
changed = true;
|
|
27451
|
+
}
|
|
27452
|
+
if (planName !== void 0 && next.planName !== planName) {
|
|
27453
|
+
next.planName = planName;
|
|
27454
|
+
changed = true;
|
|
27455
|
+
}
|
|
27456
|
+
return changed ? next : credentials;
|
|
27457
|
+
}
|
|
27458
|
+
/**
|
|
27368
27459
|
* Fetch and cache the account's quota snapshot.
|
|
27369
27460
|
*
|
|
27370
27461
|
* A 401 is surfaced as a rejection so the caller can invalidate the stored
|
|
@@ -27415,6 +27506,9 @@ async function fetchAccountQuota$3(store, options = {}) {
|
|
|
27415
27506
|
...credentials,
|
|
27416
27507
|
...learned
|
|
27417
27508
|
}).catch(() => void 0);
|
|
27509
|
+
if (options.onAccount !== void 0) try {
|
|
27510
|
+
await options.onAccount(account, credentials);
|
|
27511
|
+
} catch {}
|
|
27418
27512
|
const snapshot = {
|
|
27419
27513
|
account,
|
|
27420
27514
|
planName: account.planName,
|
|
@@ -27468,12 +27562,8 @@ function accountFromCredentials$1(credentials, payload, profile) {
|
|
|
27468
27562
|
const planLevel = (profile === void 0 ? null : parsePlanLevel(profile)) ?? (payload === void 0 ? null : parsePlanLevel(payload));
|
|
27469
27563
|
return {
|
|
27470
27564
|
userId: credentials.userId ?? identity.userId ?? null,
|
|
27471
|
-
nickname: (profile === void 0 ?
|
|
27472
|
-
|
|
27473
|
-
"username",
|
|
27474
|
-
"name"
|
|
27475
|
-
])) ?? credentials.nickname ?? null,
|
|
27476
|
-
email: credentials.email ?? identity.email ?? null,
|
|
27565
|
+
nickname: (profile === void 0 ? void 0 : profileString(profile, PROFILE_NAME_KEYS)) ?? credentials.nickname ?? null,
|
|
27566
|
+
email: credentials.email ?? identity.email ?? (profile === void 0 ? void 0 : profileString(profile, ["email"])?.toLowerCase()) ?? null,
|
|
27477
27567
|
planName,
|
|
27478
27568
|
planLevel,
|
|
27479
27569
|
region: credentials.region,
|
|
@@ -31991,6 +32081,46 @@ function processLine(line, state, wire) {
|
|
|
31991
32081
|
function kimiCodePoolPath() {
|
|
31992
32082
|
return path.join(dshHomeDir(), "storages", "kimi-code-pool.json");
|
|
31993
32083
|
}
|
|
32084
|
+
/**
|
|
32085
|
+
* Aliases that name nobody: the placeholder this pool mints at sign-in.
|
|
32086
|
+
*
|
|
32087
|
+
* `defaultAlias` writes `账号 N` when the credential carried neither an address
|
|
32088
|
+
* nor a name (a phone-signed-in account carries neither), and the legacy
|
|
32089
|
+
* single-credential mirror wrote `主账号`. Both mean "no name learned yet", so
|
|
32090
|
+
* both may be upgraded once the profile reports one — while anything else is a
|
|
32091
|
+
* name someone chose or an address already on the card, and stays put.
|
|
32092
|
+
*/
|
|
32093
|
+
const PLACEHOLDER_ALIAS = /^(?:账号|帐号|account)\s*\d*$|^主账号$|^primary$/i;
|
|
32094
|
+
/**
|
|
32095
|
+
* Whether an alias is still the placeholder a sign-in mints, rather than a name
|
|
32096
|
+
* somebody recognises.
|
|
32097
|
+
*
|
|
32098
|
+
* Exported because the routes decide whether one more profile read is worth it:
|
|
32099
|
+
* a row still titled `账号 N` is exactly the row whose name nobody has learned.
|
|
32100
|
+
*/
|
|
32101
|
+
function isPlaceholderAlias(alias) {
|
|
32102
|
+
return PLACEHOLDER_ALIAS.test(alias.trim());
|
|
32103
|
+
}
|
|
32104
|
+
/** A field worth storing, or undefined when the service left it blank. */
|
|
32105
|
+
function learnedField(value) {
|
|
32106
|
+
const text = (value ?? "").trim();
|
|
32107
|
+
return text === "" ? void 0 : text;
|
|
32108
|
+
}
|
|
32109
|
+
/**
|
|
32110
|
+
* The alias one sign-in should leave the row with.
|
|
32111
|
+
*
|
|
32112
|
+
* A fresh sign-in computes one from the credential it just produced, and that is
|
|
32113
|
+
* the truth when it names something (an address, or a nickname in the token). It
|
|
32114
|
+
* names nothing for a phone account, though, while the row may already carry the
|
|
32115
|
+
* nickname a profile read learned or a title the user typed — dropping those
|
|
32116
|
+
* back to `账号 N` on a re-sign-in would undo both. So a minted placeholder only
|
|
32117
|
+
* beats another placeholder; anything that names somebody is kept.
|
|
32118
|
+
*/
|
|
32119
|
+
function aliasForSignIn(minted, previous) {
|
|
32120
|
+
if (!isPlaceholderAlias(minted)) return minted;
|
|
32121
|
+
if (previous !== void 0 && !isPlaceholderAlias(previous)) return previous;
|
|
32122
|
+
return minted;
|
|
32123
|
+
}
|
|
31994
32124
|
function optionalString$11(record, key) {
|
|
31995
32125
|
const value = record[key];
|
|
31996
32126
|
if (value === void 0 || value === null) return void 0;
|
|
@@ -32089,9 +32219,9 @@ var KimiCodeAccountPool = class extends AccountPoolCore {
|
|
|
32089
32219
|
parsePoolData: parseKimiCodePoolData,
|
|
32090
32220
|
dedupeKey: (credentials) => credentials.userId ?? credentials.email,
|
|
32091
32221
|
defaultAlias: (credentials, position) => credentials.email ?? credentials.nickname ?? `账号 ${position}`,
|
|
32092
|
-
createAccount: ({ id, alias, credentials, addedAt, isPrimary }) => ({
|
|
32222
|
+
createAccount: ({ id, alias, credentials, addedAt, isPrimary, existing }) => ({
|
|
32093
32223
|
id,
|
|
32094
|
-
alias,
|
|
32224
|
+
alias: aliasForSignIn(alias, existing?.alias),
|
|
32095
32225
|
credentials,
|
|
32096
32226
|
addedAt,
|
|
32097
32227
|
isPrimary,
|
|
@@ -32183,6 +32313,47 @@ var KimiCodeAccountPool = class extends AccountPoolCore {
|
|
|
32183
32313
|
mirrorStore() {
|
|
32184
32314
|
return this.store;
|
|
32185
32315
|
}
|
|
32316
|
+
/**
|
|
32317
|
+
* Remember who the service says one pooled account is.
|
|
32318
|
+
*
|
|
32319
|
+
* Sign-in can only name an account through its token, which carries an id and
|
|
32320
|
+
* — for an account with a mailbox — an address. That is why a phone-signed-in
|
|
32321
|
+
* row is titled `账号 N` and never changes on its own: the nickname lives on
|
|
32322
|
+
* `/me`, which only the quota read asks, and nothing used to carry the answer
|
|
32323
|
+
* back to the row. Every reading now calls this with what the profile said, so
|
|
32324
|
+
* the card can name its own account without a re-sign-in.
|
|
32325
|
+
*
|
|
32326
|
+
* Only the placeholder yields: a name someone chose stays, because silently
|
|
32327
|
+
* renaming a row a user named is worse than leaving a default in place. The
|
|
32328
|
+
* learned facts also go into the mirrored credential, so a later re-sign-in
|
|
32329
|
+
* (which mints the alias from the credential) keeps the name, and so do the
|
|
32330
|
+
* single-account fallbacks that read that copy.
|
|
32331
|
+
*/
|
|
32332
|
+
async learnAccount(accountId, account) {
|
|
32333
|
+
const nickname = learnedField(account.nickname);
|
|
32334
|
+
const email = learnedField(account.email)?.toLowerCase();
|
|
32335
|
+
const userId = learnedField(account.userId);
|
|
32336
|
+
const planName = learnedField(account.planName);
|
|
32337
|
+
if (nickname === void 0 && email === void 0 && userId === void 0 && planName === void 0) return;
|
|
32338
|
+
await this.updatePool((data) => {
|
|
32339
|
+
const target = data.accounts.find((entry) => entry.id === accountId);
|
|
32340
|
+
if (target === void 0) return;
|
|
32341
|
+
if (nickname !== void 0) target.nickname = nickname;
|
|
32342
|
+
if (email !== void 0) target.email = email;
|
|
32343
|
+
if (userId !== void 0) target.userId = userId;
|
|
32344
|
+
if (planName !== void 0) target.planName = planName;
|
|
32345
|
+
const next = { ...target.credentials };
|
|
32346
|
+
if (nickname !== void 0) next.nickname = nickname;
|
|
32347
|
+
if (email !== void 0) next.email = email;
|
|
32348
|
+
if (userId !== void 0) next.userId = userId;
|
|
32349
|
+
if (planName !== void 0) next.planName = planName;
|
|
32350
|
+
target.credentials = next;
|
|
32351
|
+
if (isPlaceholderAlias(target.alias)) {
|
|
32352
|
+
const name = nickname ?? email;
|
|
32353
|
+
if (name !== void 0) target.alias = name;
|
|
32354
|
+
}
|
|
32355
|
+
});
|
|
32356
|
+
}
|
|
32186
32357
|
};
|
|
32187
32358
|
//#endregion
|
|
32188
32359
|
//#region src/host/kimi-code/routes.ts
|
|
@@ -32426,6 +32597,57 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
|
|
|
32426
32597
|
return (_credentials) => pool.renewCredential(accountId, fetchFn);
|
|
32427
32598
|
};
|
|
32428
32599
|
/**
|
|
32600
|
+
* Learning seam for the quota fetch, bound to one pool account.
|
|
32601
|
+
*
|
|
32602
|
+
* The reading is the only thing that asks `/me`, so it is also the only place
|
|
32603
|
+
* the account's nickname can be found. The pool owns the row, so the facts go
|
|
32604
|
+
* there: without this the card stayed titled `账号 N` forever, because nothing
|
|
32605
|
+
* ever carried the profile back to the account it describes.
|
|
32606
|
+
*/
|
|
32607
|
+
const learnFor = (accountId) => {
|
|
32608
|
+
const pool = accountPool;
|
|
32609
|
+
if (pool === void 0 || accountId === void 0) return void 0;
|
|
32610
|
+
return (account) => pool.learnAccount(accountId, account);
|
|
32611
|
+
};
|
|
32612
|
+
/**
|
|
32613
|
+
* Accounts whose name has already been asked for, once per process.
|
|
32614
|
+
*
|
|
32615
|
+
* A row titled `账号 N` needs exactly one `/me`; retrying on every status poll
|
|
32616
|
+
* (the card asks every minute) would spend a request a minute on an account
|
|
32617
|
+
* whose profile simply has no nickname to give.
|
|
32618
|
+
*/
|
|
32619
|
+
const nameProbes = /* @__PURE__ */ new Set();
|
|
32620
|
+
/**
|
|
32621
|
+
* One profile read for a row still carrying the sign-in placeholder.
|
|
32622
|
+
*
|
|
32623
|
+
* The quota read is the natural place to learn a name, but it happens only
|
|
32624
|
+
* when the snapshot is stale — and not at all while the usage call is failing
|
|
32625
|
+
* — so a fresh cache could hold the card at `账号 N` for as long as the TTL
|
|
32626
|
+
* lasts. This reads `/me` with the token the status call already holds, once
|
|
32627
|
+
* per account, and reports whether the row was renamed.
|
|
32628
|
+
*/
|
|
32629
|
+
const probeName = async (target, rows) => {
|
|
32630
|
+
const pool = accountPool;
|
|
32631
|
+
if (pool === void 0 || target.id === void 0) return false;
|
|
32632
|
+
const row = rows.find((entry) => entry.id === target.id);
|
|
32633
|
+
if (row === void 0 || !isPlaceholderAlias(row.alias)) return false;
|
|
32634
|
+
if (nameProbes.has(target.id)) return false;
|
|
32635
|
+
nameProbes.add(target.id);
|
|
32636
|
+
try {
|
|
32637
|
+
const credentials = await effectiveCredentials(target);
|
|
32638
|
+
if (credentials === void 0) return false;
|
|
32639
|
+
const profile = await fetchProfile(store, {
|
|
32640
|
+
fetchFn,
|
|
32641
|
+
credentials
|
|
32642
|
+
});
|
|
32643
|
+
if (profile === null) return false;
|
|
32644
|
+
await pool.learnAccount(target.id, accountFromCredentials$1(credentials, void 0, profile));
|
|
32645
|
+
return true;
|
|
32646
|
+
} catch {
|
|
32647
|
+
return false;
|
|
32648
|
+
}
|
|
32649
|
+
};
|
|
32650
|
+
/**
|
|
32429
32651
|
* The credential the active account should be used with right now.
|
|
32430
32652
|
*
|
|
32431
32653
|
* Every card path goes through the pool here. A pool owns both the rotation
|
|
@@ -32467,17 +32689,20 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
|
|
|
32467
32689
|
...accountPool === void 0 ? {} : {
|
|
32468
32690
|
credentials,
|
|
32469
32691
|
accountId: poolActive.id,
|
|
32470
|
-
renewFn: renewFor(poolActive.id)
|
|
32692
|
+
renewFn: renewFor(poolActive.id),
|
|
32693
|
+
onAccount: learnFor(poolActive.id)
|
|
32471
32694
|
}
|
|
32472
32695
|
});
|
|
32473
32696
|
};
|
|
32474
32697
|
if (cached === null) await quotaRefresh.run(refresh);
|
|
32475
32698
|
else quotaRefresh.start(refresh);
|
|
32476
32699
|
}
|
|
32700
|
+
let value = await readStatus(url.searchParams.get("sessionId") ?? void 0);
|
|
32701
|
+
if (await probeName(poolActive, value.accounts ?? [])) value = await readStatus(url.searchParams.get("sessionId") ?? void 0);
|
|
32477
32702
|
return sendJson$8(response, 200, {
|
|
32478
32703
|
ok: true,
|
|
32479
32704
|
value: {
|
|
32480
|
-
...
|
|
32705
|
+
...value,
|
|
32481
32706
|
quotaError: quotaRefresh.lastError(),
|
|
32482
32707
|
quotaRefreshing: quotaRefresh.refreshing
|
|
32483
32708
|
}
|
|
@@ -32497,6 +32722,13 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
|
|
|
32497
32722
|
fetchFn,
|
|
32498
32723
|
region: requested,
|
|
32499
32724
|
openBrowser: options.openBrowser,
|
|
32725
|
+
enrich: async (credentials) => {
|
|
32726
|
+
const profile = await fetchProfile(store, {
|
|
32727
|
+
fetchFn,
|
|
32728
|
+
credentials
|
|
32729
|
+
}).catch(() => null);
|
|
32730
|
+
return profile === null ? credentials : credentialsWithProfile(credentials, profile);
|
|
32731
|
+
},
|
|
32500
32732
|
...accountPool === void 0 ? {} : { onSave: (credentials) => accountPool.addAccount(credentials) }
|
|
32501
32733
|
})
|
|
32502
32734
|
});
|
|
@@ -32536,7 +32768,8 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
|
|
|
32536
32768
|
...accountPool === void 0 ? {} : {
|
|
32537
32769
|
credentials: await effectiveCredentials(poolActive),
|
|
32538
32770
|
accountId: poolActive.id,
|
|
32539
|
-
renewFn: renewFor(poolActive.id)
|
|
32771
|
+
renewFn: renewFor(poolActive.id),
|
|
32772
|
+
onAccount: learnFor(poolActive.id)
|
|
32540
32773
|
}
|
|
32541
32774
|
});
|
|
32542
32775
|
return sendJson$8(response, 200, {
|
|
@@ -32566,7 +32799,8 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
|
|
|
32566
32799
|
...accountPool === void 0 ? {} : {
|
|
32567
32800
|
credentials: await effectiveCredentials(quotaActive),
|
|
32568
32801
|
accountId: quotaActive.id,
|
|
32569
|
-
renewFn: renewFor(quotaActive.id)
|
|
32802
|
+
renewFn: renewFor(quotaActive.id),
|
|
32803
|
+
onAccount: learnFor(quotaActive.id)
|
|
32570
32804
|
}
|
|
32571
32805
|
}) === null) return sendJson$8(response, 502, {
|
|
32572
32806
|
ok: false,
|
|
@@ -65296,14 +65530,14 @@ function publishedCatalogMeta(store, id) {
|
|
|
65296
65530
|
function formatModelHint$1(contextWindow, input, reasoning) {
|
|
65297
65531
|
return formatModelCapacity$1(contextWindow) + " · " + input.join("+") + (reasoning ? " · reasoning" : "");
|
|
65298
65532
|
}
|
|
65299
|
-
function uniqueModels
|
|
65533
|
+
function uniqueModels(settings) {
|
|
65300
65534
|
const ids = new Set(DEFAULT_MODELS);
|
|
65301
65535
|
for (const model of listedClinePassCatalogModels(settings)) ids.add(model.id);
|
|
65302
65536
|
return [...ids];
|
|
65303
65537
|
}
|
|
65304
65538
|
/** Every model the card should render, whether or not it is advertised. */
|
|
65305
65539
|
function projectModels$1(settings, store) {
|
|
65306
|
-
const known = uniqueModels
|
|
65540
|
+
const known = uniqueModels(settings);
|
|
65307
65541
|
const hidden = new Set(settings.hiddenModels);
|
|
65308
65542
|
const enabledSet = settings.enabledModelIds === null ? null : new Set(settings.enabledModelIds);
|
|
65309
65543
|
return known.map((id) => {
|
|
@@ -65959,7 +66193,7 @@ function registerClinePassRoutes(ctx, options) {
|
|
|
65959
66193
|
});
|
|
65960
66194
|
const body = await readRequestJson$2(request).catch(() => ({}));
|
|
65961
66195
|
const current = await settingsStore.read();
|
|
65962
|
-
const known = uniqueModels
|
|
66196
|
+
const known = uniqueModels(current);
|
|
65963
66197
|
const hidden = new Set(current.hiddenModels);
|
|
65964
66198
|
if (path === "models/visibility") {
|
|
65965
66199
|
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
@@ -73021,8 +73255,7 @@ const MODEL_CATALOG$1 = Object.freeze({
|
|
|
73021
73255
|
output: .28
|
|
73022
73256
|
},
|
|
73023
73257
|
contextWindow: 1048576,
|
|
73024
|
-
input: ["text", "image"]
|
|
73025
|
-
reasoning: false
|
|
73258
|
+
input: ["text", "image"]
|
|
73026
73259
|
},
|
|
73027
73260
|
"mimo-v2.6-pro": {
|
|
73028
73261
|
name: "MiMo V2.6 Pro",
|
|
@@ -73031,8 +73264,7 @@ const MODEL_CATALOG$1 = Object.freeze({
|
|
|
73031
73264
|
output: .87
|
|
73032
73265
|
},
|
|
73033
73266
|
contextWindow: 1048576,
|
|
73034
|
-
input: ["text", "image"]
|
|
73035
|
-
reasoning: false
|
|
73267
|
+
input: ["text", "image"]
|
|
73036
73268
|
},
|
|
73037
73269
|
"minimax-m3.1-flash": {
|
|
73038
73270
|
name: "MiniMax M3.1 Flash",
|
|
@@ -73076,8 +73308,7 @@ const MODEL_CATALOG$1 = Object.freeze({
|
|
|
73076
73308
|
},
|
|
73077
73309
|
contextWindow: 1e6,
|
|
73078
73310
|
maxTokens: 65536,
|
|
73079
|
-
input: ["text"]
|
|
73080
|
-
reasoning: false
|
|
73311
|
+
input: ["text"]
|
|
73081
73312
|
}
|
|
73082
73313
|
});
|
|
73083
73314
|
/** Subscription models current at release time; the live catalog extends this. */
|
|
@@ -73099,6 +73330,96 @@ function catalogEntry$1(model) {
|
|
|
73099
73330
|
return MODEL_CATALOG$1[model];
|
|
73100
73331
|
}
|
|
73101
73332
|
//#endregion
|
|
73333
|
+
//#region src/host/opendesign/catalog.ts
|
|
73334
|
+
/**
|
|
73335
|
+
* Merge the shipped table, the last gateway listing and the user's per-model
|
|
73336
|
+
* override into the descriptor the harness validates.
|
|
73337
|
+
*
|
|
73338
|
+
* Context window and output cap are provider facts, not preferences: the harness
|
|
73339
|
+
* sizes its prompt budget against `context.contextWindow`, so an understated
|
|
73340
|
+
* window silently truncates history. This line ships no per-model windows (the
|
|
73341
|
+
* gateway publishes none to an API key), so the window is the line default until
|
|
73342
|
+
* a live listing or a user override states one.
|
|
73343
|
+
*
|
|
73344
|
+
* @module dsh-chatgpt-subscription/opendesign-catalog
|
|
73345
|
+
*/
|
|
73346
|
+
/** The wire's whole modality vocabulary; a listing may claim more than it accepts. */
|
|
73347
|
+
const SEAM_MODALITIES = /* @__PURE__ */ new Set(["text", "image"]);
|
|
73348
|
+
function definedOnly(value) {
|
|
73349
|
+
if (value === void 0) return {};
|
|
73350
|
+
const result = {};
|
|
73351
|
+
for (const [key, entry] of Object.entries(value)) if (entry !== void 0 && entry !== null) result[key] = entry;
|
|
73352
|
+
return result;
|
|
73353
|
+
}
|
|
73354
|
+
/**
|
|
73355
|
+
* The output budget to advertise for one model.
|
|
73356
|
+
*
|
|
73357
|
+
* `defaultMaxTokens` is what a request asks for, not what the model can emit,
|
|
73358
|
+
* and the harness spends it twice: it fills a request's `max_tokens` from it
|
|
73359
|
+
* and reserves the same number as completion room before deciding to compact.
|
|
73360
|
+
* So a published ceiling clamps the configured budget rather than becoming it,
|
|
73361
|
+
* and a budget is never taken past half the window.
|
|
73362
|
+
*/
|
|
73363
|
+
function resolveOutputBudget(budget, ceiling, contextWindow) {
|
|
73364
|
+
const wanted = Number.isFinite(budget) && (budget ?? 0) > 0 ? budget : void 0;
|
|
73365
|
+
const most = Number.isFinite(ceiling) && (ceiling ?? 0) > 0 ? ceiling : void 0;
|
|
73366
|
+
const chosen = wanted === void 0 ? most : most === void 0 ? wanted : Math.min(wanted, most);
|
|
73367
|
+
if (chosen === void 0) return void 0;
|
|
73368
|
+
const half = Number.isFinite(contextWindow) && contextWindow > 0 ? Math.floor(contextWindow / 2) : void 0;
|
|
73369
|
+
return half === void 0 ? chosen : Math.min(chosen, half);
|
|
73370
|
+
}
|
|
73371
|
+
/**
|
|
73372
|
+
* Whether the subscription is known to serve one model.
|
|
73373
|
+
*
|
|
73374
|
+
* `servedModelIds === null` is "nobody has ever measured this plan": the absence
|
|
73375
|
+
* of a fact is not a denial, so nothing is hidden on its strength and the shipped
|
|
73376
|
+
* table stands in exactly as it did before this fact existed. An array is a
|
|
73377
|
+
* measurement taken from a successful scan, and an id outside it is a model this
|
|
73378
|
+
* plan does not serve — it stays in the editor as a described model, it just stops
|
|
73379
|
+
* being advertised.
|
|
73380
|
+
*
|
|
73381
|
+
* The user's own override lives one layer up (`forceVisibleModels`): this function
|
|
73382
|
+
* answers what the plan said, never what the user decided.
|
|
73383
|
+
*/
|
|
73384
|
+
function isModelServed(model, servedModelIds) {
|
|
73385
|
+
return servedModelIds === null || servedModelIds.includes(model);
|
|
73386
|
+
}
|
|
73387
|
+
/**
|
|
73388
|
+
* Resolve the metadata one model is advertised with.
|
|
73389
|
+
*
|
|
73390
|
+
* Precedence per field: the user's override, then the last gateway listing,
|
|
73391
|
+
* then the shipped table, then the route-wide fallback. The listing sits above
|
|
73392
|
+
* the shipped table for the fields the table cannot carry (window, modalities):
|
|
73393
|
+
* the table's windows do not exist to be beaten, and its names/prices are
|
|
73394
|
+
* curated, so a listing that states no name keeps the table's.
|
|
73395
|
+
*/
|
|
73396
|
+
function resolveModelInfo(model, options) {
|
|
73397
|
+
const synced = options.synced.find((entry) => entry.id === model);
|
|
73398
|
+
const shipped = catalogEntry$1(model);
|
|
73399
|
+
const published = {
|
|
73400
|
+
...definedOnly(shipped),
|
|
73401
|
+
...definedOnly(synced)
|
|
73402
|
+
};
|
|
73403
|
+
const fallbackWindow = options.defaultContextWindow ?? 128e3;
|
|
73404
|
+
const contextWindow = options.contextWindowOverride ?? published.contextWindow ?? fallbackWindow;
|
|
73405
|
+
const maxTokens = resolveOutputBudget(options.defaultMaxTokens ?? 64e3, published.maxTokens, contextWindow);
|
|
73406
|
+
const reasoning = published.reasoning ?? shipped?.reasoning ?? true;
|
|
73407
|
+
const efforts = (Array.isArray(published.efforts) && published.efforts.length > 0 ? published.efforts : void 0) ?? (shipped?.efforts !== void 0 && shipped.efforts.length > 0 ? [...shipped.efforts] : DEFAULT_EFFORTS);
|
|
73408
|
+
const defaultEffort = reasoning && shipped?.defaultEffort !== void 0 && efforts.includes(shipped.defaultEffort) ? shipped.defaultEffort : void 0;
|
|
73409
|
+
const input = (Array.isArray(published.input) && published.input.length > 0 ? published.input : shipped?.input ?? ["text"]).filter((entry) => SEAM_MODALITIES.has(entry));
|
|
73410
|
+
return {
|
|
73411
|
+
id: model,
|
|
73412
|
+
displayName: String(published.name ?? "").trim() || model,
|
|
73413
|
+
contextWindow,
|
|
73414
|
+
maxTokens: maxTokens ?? options.defaultMaxTokens ?? 64e3,
|
|
73415
|
+
reasoning,
|
|
73416
|
+
...reasoning ? { efforts } : { efforts: [] },
|
|
73417
|
+
...defaultEffort === void 0 ? {} : { defaultEffort },
|
|
73418
|
+
input: input.length > 0 ? input : ["text"],
|
|
73419
|
+
...shipped?.pricing === void 0 ? {} : { pricing: shipped.pricing }
|
|
73420
|
+
};
|
|
73421
|
+
}
|
|
73422
|
+
//#endregion
|
|
73102
73423
|
//#region src/host/opendesign/token-store.ts
|
|
73103
73424
|
/** Whether one credential is a device-flow session rather than a pasted key. */
|
|
73104
73425
|
function isOpenDesignOAuthCredentials(credentials) {
|
|
@@ -73254,6 +73575,24 @@ function readCatalogModels(value) {
|
|
|
73254
73575
|
}
|
|
73255
73576
|
return models;
|
|
73256
73577
|
}
|
|
73578
|
+
/**
|
|
73579
|
+
* One stored id list, normalized: non-empty strings only, deduplicated, and
|
|
73580
|
+
* `null` when the value is not a list at all.
|
|
73581
|
+
*
|
|
73582
|
+
* `null` and `[]` stay distinct here; see the tri-state note on
|
|
73583
|
+
* {@link OpenDesignModelSettings.servedModelIds}.
|
|
73584
|
+
*/
|
|
73585
|
+
function readIdList(value) {
|
|
73586
|
+
if (!Array.isArray(value)) return null;
|
|
73587
|
+
const ids = [];
|
|
73588
|
+
for (const entry of value) {
|
|
73589
|
+
if (typeof entry !== "string") continue;
|
|
73590
|
+
const id = entry.trim();
|
|
73591
|
+
if (id === "" || ids.includes(id)) continue;
|
|
73592
|
+
ids.push(id);
|
|
73593
|
+
}
|
|
73594
|
+
return ids;
|
|
73595
|
+
}
|
|
73257
73596
|
function positiveInteger(value, fallback, allowZero = false) {
|
|
73258
73597
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < (allowZero ? 0 : 1)) return fallback;
|
|
73259
73598
|
return value;
|
|
@@ -73268,6 +73607,8 @@ function parseOpenDesignModelSettings(value) {
|
|
|
73268
73607
|
enabledModelIds,
|
|
73269
73608
|
catalogModels: readCatalogModels(record.catalogModels),
|
|
73270
73609
|
hiddenModels: Array.isArray(record.hiddenModels) ? record.hiddenModels.filter((id) => typeof id === "string") : [],
|
|
73610
|
+
servedModelIds: readIdList(record.servedModelIds),
|
|
73611
|
+
forceVisibleModels: readIdList(record.forceVisibleModels) ?? [],
|
|
73271
73612
|
contextWindowOverrides: readNumberOverrides(record.contextWindowOverrides),
|
|
73272
73613
|
defaultContextWindow: positiveInteger(record.defaultContextWindow, DEFAULT_CONTEXT_WINDOW),
|
|
73273
73614
|
defaultMaxTokens: positiveInteger(record.defaultMaxTokens, DEFAULT_MAX_TOKENS),
|
|
@@ -73285,12 +73626,30 @@ function defaultOpenDesignModelSettings() {
|
|
|
73285
73626
|
*
|
|
73286
73627
|
* The file is written atomically (temp + rename) so a crash mid-write leaves the
|
|
73287
73628
|
* previous document intact rather than a truncated one that reads as "reset all
|
|
73288
|
-
* settings"
|
|
73629
|
+
* settings", and writes are SERIALIZED behind one queue so two read→compute→write
|
|
73630
|
+
* cycles can never interleave and erase each other's fields. Reads do not queue:
|
|
73631
|
+
* see {@link OpenDesignModelSettingsStore.pending}.
|
|
73289
73632
|
*/
|
|
73290
73633
|
var OpenDesignModelSettingsStore = class {
|
|
73291
73634
|
filePath;
|
|
73292
73635
|
cached = null;
|
|
73293
73636
|
loadPromise = null;
|
|
73637
|
+
/**
|
|
73638
|
+
* Tail of the write queue.
|
|
73639
|
+
*
|
|
73640
|
+
* Every mutator derives a WHOLE document from the one it read, so two of them in
|
|
73641
|
+
* flight at once each erase the other's fields. That is not hypothetical here:
|
|
73642
|
+
* the catalog sync is fire-and-forget (`void syncCatalog()` on account add and on
|
|
73643
|
+
* a completed sign-in), so it really can land between a selection change's read
|
|
73644
|
+
* and its write. Each mutation therefore runs its entire read → compute → write
|
|
73645
|
+
* inside one turn of this queue.
|
|
73646
|
+
*
|
|
73647
|
+
* `read()` stays OUTSIDE the queue on purpose: it is the hot path (every status
|
|
73648
|
+
* request, every keystroke-driven checkbox), and waiting behind a write would buy
|
|
73649
|
+
* nothing — by the time a queued operation runs, every write queued before it has
|
|
73650
|
+
* already landed, so the plain read it performs IS the in-lock read.
|
|
73651
|
+
*/
|
|
73652
|
+
pending = Promise.resolve();
|
|
73294
73653
|
constructor(filePath = modelSettingsPath$7()) {
|
|
73295
73654
|
this.filePath = filePath;
|
|
73296
73655
|
}
|
|
@@ -73300,7 +73659,14 @@ var OpenDesignModelSettingsStore = class {
|
|
|
73300
73659
|
status() {
|
|
73301
73660
|
return this.cached ?? defaultOpenDesignModelSettings();
|
|
73302
73661
|
}
|
|
73303
|
-
/**
|
|
73662
|
+
/**
|
|
73663
|
+
* Read the document at activation time, synchronously.
|
|
73664
|
+
*
|
|
73665
|
+
* Synchronous by contract, so it cannot join the write queue — and it does not
|
|
73666
|
+
* need to: it only READS, a rename makes the file either the old or the new
|
|
73667
|
+
* document (never a half-written one), and a queued write assigns `cached` after
|
|
73668
|
+
* that rename, so a reading taken mid-write is superseded by the write's own.
|
|
73669
|
+
*/
|
|
73304
73670
|
readSync() {
|
|
73305
73671
|
if (this.cached !== null) return this.cached;
|
|
73306
73672
|
try {
|
|
@@ -73330,7 +73696,30 @@ var OpenDesignModelSettingsStore = class {
|
|
|
73330
73696
|
this.cached = defaultOpenDesignModelSettings();
|
|
73331
73697
|
return this.cached;
|
|
73332
73698
|
}
|
|
73699
|
+
/**
|
|
73700
|
+
* Run one operation with exclusive access to the document.
|
|
73701
|
+
*
|
|
73702
|
+
* The queue's tail never carries a rejection: an operation that throws rejects
|
|
73703
|
+
* its OWN caller only, and the tail is flattened to a fulfilled promise right
|
|
73704
|
+
* afterwards, so a failed write cannot poison the writes queued behind it.
|
|
73705
|
+
*/
|
|
73706
|
+
async exclusive(operation) {
|
|
73707
|
+
const result = this.pending.then(operation);
|
|
73708
|
+
this.pending = result.then(() => void 0, () => void 0);
|
|
73709
|
+
return await result;
|
|
73710
|
+
}
|
|
73711
|
+
/** Replace the whole document, queued behind any write already in flight. */
|
|
73333
73712
|
async write(settings) {
|
|
73713
|
+
return await this.exclusive(() => this.writeUnlocked(settings));
|
|
73714
|
+
}
|
|
73715
|
+
/**
|
|
73716
|
+
* The file half of a write, for callers that ALREADY hold the queue.
|
|
73717
|
+
*
|
|
73718
|
+
* No public mutator may call another one: awaiting a queued mutator from inside
|
|
73719
|
+
* `exclusive` would wait for a queue position behind itself and deadlock. That is
|
|
73720
|
+
* why every mutator below composes `read()` with THIS, never with `write()`.
|
|
73721
|
+
*/
|
|
73722
|
+
async writeUnlocked(settings) {
|
|
73334
73723
|
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
73335
73724
|
const tmp = `${this.filePath}.tmp.${Date.now()}`;
|
|
73336
73725
|
await fs.writeFile(tmp, JSON.stringify({
|
|
@@ -73342,32 +73731,75 @@ var OpenDesignModelSettingsStore = class {
|
|
|
73342
73731
|
return settings;
|
|
73343
73732
|
}
|
|
73344
73733
|
async update(patch) {
|
|
73345
|
-
|
|
73346
|
-
|
|
73347
|
-
|
|
73348
|
-
|
|
73349
|
-
|
|
73350
|
-
|
|
73351
|
-
|
|
73352
|
-
|
|
73734
|
+
return await this.exclusive(async () => {
|
|
73735
|
+
const current = await this.read();
|
|
73736
|
+
const next = {
|
|
73737
|
+
...current,
|
|
73738
|
+
...patch,
|
|
73739
|
+
contextWindowOverrides: patch.contextWindowOverrides === void 0 ? current.contextWindowOverrides : patch.contextWindowOverrides
|
|
73740
|
+
};
|
|
73741
|
+
return await this.writeUnlocked(next);
|
|
73742
|
+
});
|
|
73353
73743
|
}
|
|
73354
|
-
/**
|
|
73355
|
-
|
|
73356
|
-
|
|
73357
|
-
|
|
73358
|
-
|
|
73359
|
-
|
|
73744
|
+
/**
|
|
73745
|
+
* Record a catalog sync without touching the user's selection.
|
|
73746
|
+
*
|
|
73747
|
+
* `servedModelIds` is written only when the caller actually measured it: a sync
|
|
73748
|
+
* in which every scan failed must leave the previous measurement standing rather
|
|
73749
|
+
* than clearing the filter, so `undefined` keeps the stored value while an array
|
|
73750
|
+
* (including an empty one) replaces it.
|
|
73751
|
+
*/
|
|
73752
|
+
async storeCatalog(models, servedModelIds) {
|
|
73753
|
+
return await this.exclusive(async () => {
|
|
73754
|
+
const current = await this.read();
|
|
73755
|
+
return await this.writeUnlocked(servedModelIds === void 0 ? {
|
|
73756
|
+
...current,
|
|
73757
|
+
catalogModels: models
|
|
73758
|
+
} : {
|
|
73759
|
+
...current,
|
|
73760
|
+
catalogModels: models,
|
|
73761
|
+
servedModelIds
|
|
73762
|
+
});
|
|
73763
|
+
});
|
|
73764
|
+
}
|
|
73765
|
+
/**
|
|
73766
|
+
* Replace the model selection, dropping any forced reveal it excludes.
|
|
73767
|
+
*
|
|
73768
|
+
* One `read` for both the prune input and the write baseline, so the two cannot
|
|
73769
|
+
* disagree: computing the prune outside and then patching would read the document
|
|
73770
|
+
* twice, and a forced reveal added in that gap would be recorded and then erased
|
|
73771
|
+
* by the second read's snapshot.
|
|
73772
|
+
*
|
|
73773
|
+
* The prune belongs to the selection rather than to `models/visibility` because
|
|
73774
|
+
* the card's only un-tick path is the selection patch: it removes the id from the
|
|
73775
|
+
* whitelist and never posts `{visible:false}`, so a forced reveal would otherwise
|
|
73776
|
+
* outlive the tick that asked for it. `null` is "no filter" — not a "keep only
|
|
73777
|
+
* these" statement — so it prunes nothing.
|
|
73778
|
+
*/
|
|
73779
|
+
async selectModels(ids) {
|
|
73780
|
+
return await this.exclusive(async () => {
|
|
73781
|
+
const current = await this.read();
|
|
73782
|
+
return await this.writeUnlocked(ids === null ? {
|
|
73783
|
+
...current,
|
|
73784
|
+
enabledModelIds: null
|
|
73785
|
+
} : {
|
|
73786
|
+
...current,
|
|
73787
|
+
enabledModelIds: ids,
|
|
73788
|
+
forceVisibleModels: current.forceVisibleModels.filter((id) => ids.includes(id))
|
|
73789
|
+
});
|
|
73360
73790
|
});
|
|
73361
73791
|
}
|
|
73362
73792
|
/** Store or clear (null) one model's context-window override. */
|
|
73363
73793
|
async setContextWindowOverride(model, contextWindow) {
|
|
73364
|
-
|
|
73365
|
-
|
|
73366
|
-
|
|
73367
|
-
|
|
73368
|
-
|
|
73369
|
-
|
|
73370
|
-
|
|
73794
|
+
return await this.exclusive(async () => {
|
|
73795
|
+
const current = await this.read();
|
|
73796
|
+
const overrides = { ...current.contextWindowOverrides };
|
|
73797
|
+
if (contextWindow === null) delete overrides[model];
|
|
73798
|
+
else overrides[model] = contextWindow;
|
|
73799
|
+
return await this.writeUnlocked({
|
|
73800
|
+
...current,
|
|
73801
|
+
contextWindowOverrides: overrides
|
|
73802
|
+
});
|
|
73371
73803
|
});
|
|
73372
73804
|
}
|
|
73373
73805
|
};
|
|
@@ -73521,80 +73953,6 @@ var OpenDesignAccountPool = class extends AccountPoolCore {
|
|
|
73521
73953
|
}
|
|
73522
73954
|
};
|
|
73523
73955
|
//#endregion
|
|
73524
|
-
//#region src/host/opendesign/catalog.ts
|
|
73525
|
-
/**
|
|
73526
|
-
* Merge the shipped table, the last gateway listing and the user's per-model
|
|
73527
|
-
* override into the descriptor the harness validates.
|
|
73528
|
-
*
|
|
73529
|
-
* Context window and output cap are provider facts, not preferences: the harness
|
|
73530
|
-
* sizes its prompt budget against `context.contextWindow`, so an understated
|
|
73531
|
-
* window silently truncates history. This line ships no per-model windows (the
|
|
73532
|
-
* gateway publishes none to an API key), so the window is the line default until
|
|
73533
|
-
* a live listing or a user override states one.
|
|
73534
|
-
*
|
|
73535
|
-
* @module dsh-chatgpt-subscription/opendesign-catalog
|
|
73536
|
-
*/
|
|
73537
|
-
/** The wire's whole modality vocabulary; a listing may claim more than it accepts. */
|
|
73538
|
-
const SEAM_MODALITIES = /* @__PURE__ */ new Set(["text", "image"]);
|
|
73539
|
-
function definedOnly(value) {
|
|
73540
|
-
if (value === void 0) return {};
|
|
73541
|
-
const result = {};
|
|
73542
|
-
for (const [key, entry] of Object.entries(value)) if (entry !== void 0 && entry !== null) result[key] = entry;
|
|
73543
|
-
return result;
|
|
73544
|
-
}
|
|
73545
|
-
/**
|
|
73546
|
-
* The output budget to advertise for one model.
|
|
73547
|
-
*
|
|
73548
|
-
* `defaultMaxTokens` is what a request asks for, not what the model can emit,
|
|
73549
|
-
* and the harness spends it twice: it fills a request's `max_tokens` from it
|
|
73550
|
-
* and reserves the same number as completion room before deciding to compact.
|
|
73551
|
-
* So a published ceiling clamps the configured budget rather than becoming it,
|
|
73552
|
-
* and a budget is never taken past half the window.
|
|
73553
|
-
*/
|
|
73554
|
-
function resolveOutputBudget(budget, ceiling, contextWindow) {
|
|
73555
|
-
const wanted = Number.isFinite(budget) && (budget ?? 0) > 0 ? budget : void 0;
|
|
73556
|
-
const most = Number.isFinite(ceiling) && (ceiling ?? 0) > 0 ? ceiling : void 0;
|
|
73557
|
-
const chosen = wanted === void 0 ? most : most === void 0 ? wanted : Math.min(wanted, most);
|
|
73558
|
-
if (chosen === void 0) return void 0;
|
|
73559
|
-
const half = Number.isFinite(contextWindow) && contextWindow > 0 ? Math.floor(contextWindow / 2) : void 0;
|
|
73560
|
-
return half === void 0 ? chosen : Math.min(chosen, half);
|
|
73561
|
-
}
|
|
73562
|
-
/**
|
|
73563
|
-
* Resolve the metadata one model is advertised with.
|
|
73564
|
-
*
|
|
73565
|
-
* Precedence per field: the user's override, then the last gateway listing,
|
|
73566
|
-
* then the shipped table, then the route-wide fallback. The listing sits above
|
|
73567
|
-
* the shipped table for the fields the table cannot carry (window, modalities):
|
|
73568
|
-
* the table's windows do not exist to be beaten, and its names/prices are
|
|
73569
|
-
* curated, so a listing that states no name keeps the table's.
|
|
73570
|
-
*/
|
|
73571
|
-
function resolveModelInfo(model, options) {
|
|
73572
|
-
const synced = options.synced.find((entry) => entry.id === model);
|
|
73573
|
-
const shipped = catalogEntry$1(model);
|
|
73574
|
-
const published = {
|
|
73575
|
-
...definedOnly(shipped),
|
|
73576
|
-
...definedOnly(synced)
|
|
73577
|
-
};
|
|
73578
|
-
const fallbackWindow = options.defaultContextWindow ?? 128e3;
|
|
73579
|
-
const contextWindow = options.contextWindowOverride ?? published.contextWindow ?? fallbackWindow;
|
|
73580
|
-
const maxTokens = resolveOutputBudget(options.defaultMaxTokens ?? 64e3, published.maxTokens, contextWindow);
|
|
73581
|
-
const reasoning = published.reasoning ?? shipped?.reasoning ?? true;
|
|
73582
|
-
const efforts = (Array.isArray(published.efforts) && published.efforts.length > 0 ? published.efforts : void 0) ?? (shipped?.efforts !== void 0 && shipped.efforts.length > 0 ? [...shipped.efforts] : DEFAULT_EFFORTS);
|
|
73583
|
-
const defaultEffort = reasoning && shipped?.defaultEffort !== void 0 && efforts.includes(shipped.defaultEffort) ? shipped.defaultEffort : void 0;
|
|
73584
|
-
const input = (Array.isArray(published.input) && published.input.length > 0 ? published.input : shipped?.input ?? ["text"]).filter((entry) => SEAM_MODALITIES.has(entry));
|
|
73585
|
-
return {
|
|
73586
|
-
id: model,
|
|
73587
|
-
displayName: String(published.name ?? "").trim() || model,
|
|
73588
|
-
contextWindow,
|
|
73589
|
-
maxTokens: maxTokens ?? options.defaultMaxTokens ?? 64e3,
|
|
73590
|
-
reasoning,
|
|
73591
|
-
...reasoning ? { efforts } : { efforts: [] },
|
|
73592
|
-
...defaultEffort === void 0 ? {} : { defaultEffort },
|
|
73593
|
-
input: input.length > 0 ? input : ["text"],
|
|
73594
|
-
...shipped?.pricing === void 0 ? {} : { pricing: shipped.pricing }
|
|
73595
|
-
};
|
|
73596
|
-
}
|
|
73597
|
-
//#endregion
|
|
73598
73956
|
//#region src/host/opendesign/client.ts
|
|
73599
73957
|
/**
|
|
73600
73958
|
* Transport for the OpenDesign link gateway. Transport only: no account or
|
|
@@ -75019,27 +75377,6 @@ var OpenDesignAdapter = class extends LlmAdapter {
|
|
|
75019
75377
|
settings() {
|
|
75020
75378
|
return this.modelSettings.read();
|
|
75021
75379
|
}
|
|
75022
|
-
/** Every id the line knows: the shipped table plus the synced live catalog. */
|
|
75023
|
-
knownModelIds(settings) {
|
|
75024
|
-
const ids = new Set(DEFAULT_MODELS$1);
|
|
75025
|
-
for (const model of settings.catalogModels) ids.add(model.id);
|
|
75026
|
-
return [...ids];
|
|
75027
|
-
}
|
|
75028
|
-
/**
|
|
75029
|
-
* The ids the route advertises, narrowed by the user's selection and hidden
|
|
75030
|
-
* list.
|
|
75031
|
-
*
|
|
75032
|
-
* The selection is captured against this same id vocabulary (the gateway's
|
|
75033
|
-
* own wire ids), so a cold start filters the shipped table through it without
|
|
75034
|
-
* the empty-intersection trap a foreign-vocabulary whitelist would spring —
|
|
75035
|
-
* and an explicit "everything off" (`[]`) still wins, because that intent
|
|
75036
|
-
* does not depend on a vocabulary at all.
|
|
75037
|
-
*/
|
|
75038
|
-
visibleModelIds(settings) {
|
|
75039
|
-
const hidden = new Set(settings.hiddenModels);
|
|
75040
|
-
const enabled = settings.enabledModelIds === null ? null : new Set(settings.enabledModelIds);
|
|
75041
|
-
return this.knownModelIds(settings).filter((id) => !hidden.has(id) && (enabled === null || enabled.has(id)));
|
|
75042
|
-
}
|
|
75043
75380
|
/** One model's resolved descriptor, from the shipped table plus live data. */
|
|
75044
75381
|
async modelInfo(modelId) {
|
|
75045
75382
|
const settings = await this.settings();
|
|
@@ -75054,7 +75391,7 @@ var OpenDesignAdapter = class extends LlmAdapter {
|
|
|
75054
75391
|
const prov = provider || "opendesign";
|
|
75055
75392
|
const settings = await this.settings();
|
|
75056
75393
|
if (settings.enabled === false) return [];
|
|
75057
|
-
const ids =
|
|
75394
|
+
const ids = openDesignVisibleModelIds(settings);
|
|
75058
75395
|
return await Promise.all(ids.map(async (id) => {
|
|
75059
75396
|
const info = await this.modelInfo(id);
|
|
75060
75397
|
return {
|
|
@@ -75131,28 +75468,33 @@ var OpenDesignAdapter = class extends LlmAdapter {
|
|
|
75131
75468
|
try {
|
|
75132
75469
|
let response;
|
|
75133
75470
|
let transportError;
|
|
75134
|
-
for (let transportTry = 0; transportTry <= TRANSPORT_RETRIES; transportTry += 1)
|
|
75135
|
-
|
|
75136
|
-
|
|
75137
|
-
|
|
75138
|
-
|
|
75139
|
-
|
|
75140
|
-
|
|
75141
|
-
|
|
75142
|
-
|
|
75143
|
-
|
|
75144
|
-
|
|
75145
|
-
|
|
75146
|
-
|
|
75147
|
-
|
|
75148
|
-
|
|
75149
|
-
|
|
75150
|
-
|
|
75151
|
-
|
|
75471
|
+
for (let transportTry = 0; transportTry <= TRANSPORT_RETRIES; transportTry += 1) {
|
|
75472
|
+
const headerDeadline = withAttemptTimeout(upstream, this.options.headerTimeoutMs);
|
|
75473
|
+
try {
|
|
75474
|
+
response = await openChatStream$1({
|
|
75475
|
+
fetchFn: this.options.fetchFn,
|
|
75476
|
+
baseURL,
|
|
75477
|
+
apiKey,
|
|
75478
|
+
body,
|
|
75479
|
+
signal: headerDeadline.signal
|
|
75480
|
+
});
|
|
75481
|
+
headerDeadline.clear();
|
|
75482
|
+
break;
|
|
75483
|
+
} catch (error) {
|
|
75484
|
+
headerDeadline.clear();
|
|
75485
|
+
if (signal.aborted) throw new LlmError("OpenDesign request aborted by caller", "ABORTED", { cause: error });
|
|
75486
|
+
transportError = error;
|
|
75487
|
+
if (transportTry < TRANSPORT_RETRIES && isPreConnectionFailure(error)) {
|
|
75488
|
+
try {
|
|
75489
|
+
await setTimeout$1(TRANSPORT_RETRY_BASE_MS * (transportTry + 1), void 0, { signal: upstream });
|
|
75490
|
+
} catch (waitError) {
|
|
75491
|
+
if (signal.aborted) throw new LlmError("OpenDesign request aborted by caller", "ABORTED", { cause: waitError });
|
|
75492
|
+
throw waitError;
|
|
75493
|
+
}
|
|
75494
|
+
continue;
|
|
75152
75495
|
}
|
|
75153
|
-
|
|
75496
|
+
break;
|
|
75154
75497
|
}
|
|
75155
|
-
break;
|
|
75156
75498
|
}
|
|
75157
75499
|
if (response === void 0) {
|
|
75158
75500
|
lastError = new LlmError(`${errorChain$1(transportError)} The request never reached the gateway: ${baseURL} failed during TLS/connect. Check the network or proxy.`, "TRANSPORT", { cause: transportError });
|
|
@@ -75220,9 +75562,31 @@ var OpenDesignAdapter = class extends LlmAdapter {
|
|
|
75220
75562
|
await this.accountPool.markCooldown(accountId, POOL_COOLDOWN_MS$1, `${PROVIDER_NAME$2} credits exhausted`).catch(() => void 0);
|
|
75221
75563
|
}
|
|
75222
75564
|
};
|
|
75223
|
-
/**
|
|
75224
|
-
|
|
75225
|
-
|
|
75565
|
+
/**
|
|
75566
|
+
* A caller signal combined with the attempt's CLEARABLE header deadline.
|
|
75567
|
+
*
|
|
75568
|
+
* The clear is not a nicety, it is the difference between a header deadline and
|
|
75569
|
+
* a request deadline. `AbortSignal.timeout` is absolute and cannot be disarmed,
|
|
75570
|
+
* so attaching one to the fetch signal and leaving it there keeps counting
|
|
75571
|
+
* through the whole response body — a model that thinks for more than
|
|
75572
|
+
* `ATTEMPT_TIMEOUT_MS` then dies mid-answer with "The operation was aborted due
|
|
75573
|
+
* to timeout", is retried by the host, and dies again at the same wall clock.
|
|
75574
|
+
* That is exactly what happened here: two attempts streamed reasoning for 155s
|
|
75575
|
+
* and 173s and both were cut 180s after their request went out.
|
|
75576
|
+
*
|
|
75577
|
+
* Cleared after the headers (see the transport loop), at which point the stream
|
|
75578
|
+
* idle watchdog owns liveness: it watches for SILENCE and resets on every
|
|
75579
|
+
* chunk, so a long thinking pass is fine and a dead socket is still caught.
|
|
75580
|
+
*/
|
|
75581
|
+
function withAttemptTimeout(signal, timeoutMs = ATTEMPT_TIMEOUT_MS) {
|
|
75582
|
+
const deadline = new AbortController();
|
|
75583
|
+
const timer = setTimeout(() => {
|
|
75584
|
+
deadline.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
|
|
75585
|
+
}, timeoutMs);
|
|
75586
|
+
return {
|
|
75587
|
+
signal: AbortSignal.any([signal, deadline.signal]),
|
|
75588
|
+
clear: () => clearTimeout(timer)
|
|
75589
|
+
};
|
|
75226
75590
|
}
|
|
75227
75591
|
/**
|
|
75228
75592
|
* Whether a transport failure happened before any request was sent.
|
|
@@ -75540,6 +75904,15 @@ const OPENDESIGN_ROUTE_PREFIX = "/opendesign/api";
|
|
|
75540
75904
|
const OPENDESIGN_CONSOLE_URL = "https://open-design.ai/cloud";
|
|
75541
75905
|
//#endregion
|
|
75542
75906
|
//#region src/host/opendesign/routes.ts
|
|
75907
|
+
/**
|
|
75908
|
+
* Accounts one catalog sync asks at most.
|
|
75909
|
+
*
|
|
75910
|
+
* Each account costs up to two upstream reads, so this is what stands between a
|
|
75911
|
+
* twenty-key pool and forty requests behind one button. It bounds the SCAN, not
|
|
75912
|
+
* the pool: an account beyond the cap contributes nothing to the union, because
|
|
75913
|
+
* the cap is a hard bound on the measurement rather than a rotating window.
|
|
75914
|
+
*/
|
|
75915
|
+
const MAX_SYNC_ACCOUNTS = 8;
|
|
75543
75916
|
function sendJson(response, status, body) {
|
|
75544
75917
|
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
75545
75918
|
response.end(JSON.stringify(body));
|
|
@@ -75592,17 +75965,20 @@ function formatModelHint(contextWindow, input, reasoning, efforts, pricing) {
|
|
|
75592
75965
|
const base = formatModelCapacity(contextWindow) + " · " + input.join("+") + (ladder === "" ? "" : " · " + ladder);
|
|
75593
75966
|
return pricing === void 0 ? base : `${base} · $${pricing.input}/$${pricing.output}`;
|
|
75594
75967
|
}
|
|
75595
|
-
/**
|
|
75596
|
-
|
|
75597
|
-
|
|
75598
|
-
|
|
75599
|
-
|
|
75600
|
-
|
|
75601
|
-
|
|
75968
|
+
/**
|
|
75969
|
+
* Every model the card should render, whether or not it is advertised.
|
|
75970
|
+
*
|
|
75971
|
+
* `enabled` is the state of the card's checklist row, and it is read from the ONE
|
|
75972
|
+
* visibility rule (`openDesignVisibleModelIds` in ./line.ts) — the very function
|
|
75973
|
+
* the adapter offers the picker with. A row that reads "checked" while no picker
|
|
75974
|
+
* offers it is the contradiction this projection must not be able to produce, so
|
|
75975
|
+
* the two are not two calculations: a model the plan withholds reads as unchecked
|
|
75976
|
+
* until the user forces it, because that is what the picker does with it.
|
|
75977
|
+
*/
|
|
75602
75978
|
function projectModels(settings) {
|
|
75603
|
-
const known =
|
|
75604
|
-
const
|
|
75605
|
-
const
|
|
75979
|
+
const known = openDesignKnownModelIds(settings);
|
|
75980
|
+
const visible = new Set(openDesignVisibleModelIds(settings));
|
|
75981
|
+
const forcedVisible = new Set(settings.forceVisibleModels);
|
|
75606
75982
|
return known.map((id) => {
|
|
75607
75983
|
const base = resolveModelInfo(id, {
|
|
75608
75984
|
synced: settings.catalogModels,
|
|
@@ -75615,6 +75991,7 @@ function projectModels(settings) {
|
|
|
75615
75991
|
defaultMaxTokens: settings.defaultMaxTokens,
|
|
75616
75992
|
contextWindowOverride: settings.contextWindowOverrides[id]
|
|
75617
75993
|
});
|
|
75994
|
+
const served = isModelServed(id, settings.servedModelIds);
|
|
75618
75995
|
return {
|
|
75619
75996
|
id,
|
|
75620
75997
|
name: info.displayName,
|
|
@@ -75624,7 +76001,9 @@ function projectModels(settings) {
|
|
|
75624
76001
|
maxTokens: info.maxTokens,
|
|
75625
76002
|
reasoning: info.reasoning,
|
|
75626
76003
|
input: [...info.input],
|
|
75627
|
-
enabled:
|
|
76004
|
+
enabled: visible.has(id),
|
|
76005
|
+
served,
|
|
76006
|
+
forcedVisible: forcedVisible.has(id),
|
|
75628
76007
|
...info.pricing === void 0 ? {} : { pricing: info.pricing },
|
|
75629
76008
|
...settings.contextWindowOverrides[id] === void 0 ? {} : { contextWindowOverride: settings.contextWindowOverrides[id] }
|
|
75630
76009
|
};
|
|
@@ -75717,16 +76096,22 @@ function registerOpenDesignRoutes(ctx, options) {
|
|
|
75717
76096
|
};
|
|
75718
76097
|
const status = async () => await getOpenDesignWebStatus(options, usageStore);
|
|
75719
76098
|
/**
|
|
75720
|
-
* Sync the live catalog with
|
|
76099
|
+
* Sync the live catalog with every usable account's key.
|
|
76100
|
+
*
|
|
76101
|
+
* The gateway's listing is the authority on what a key's plan serves, and a plan
|
|
76102
|
+
* belongs to an ACCOUNT rather than to the line: two pooled keys can serve two
|
|
76103
|
+
* different model sets, so every account is asked and the union is what gets
|
|
76104
|
+
* remembered. That union is persisted as `servedModelIds` — the fact the picker
|
|
76105
|
+
* needs and the one this sync used to throw away — and it is written only when a
|
|
76106
|
+
* scan really succeeded, so a network failure cannot switch the filter off.
|
|
75721
76107
|
*
|
|
75722
|
-
*
|
|
75723
|
-
*
|
|
75724
|
-
*
|
|
75725
|
-
*
|
|
75726
|
-
* stay silent.
|
|
76108
|
+
* A failed or refused read changes nothing (the shipped table still stands in),
|
|
76109
|
+
* and the sync's answer says which happened. Announces only a real change: the
|
|
76110
|
+
* picker rebuilds on `llm/adapters-updated`, so a no-change sync must stay
|
|
76111
|
+
* silent.
|
|
75727
76112
|
*/
|
|
75728
76113
|
const syncCatalog = async () => {
|
|
75729
|
-
const known =
|
|
76114
|
+
const known = openDesignKnownModelIds(await settingsStore.read());
|
|
75730
76115
|
const data = await accountPool.read().catch(() => null);
|
|
75731
76116
|
const now = Date.now();
|
|
75732
76117
|
const accounts = (data?.accounts ?? []).filter((account) => isUsable(account, now));
|
|
@@ -75737,27 +76122,22 @@ function registerOpenDesignRoutes(ctx, options) {
|
|
|
75737
76122
|
};
|
|
75738
76123
|
const scans = [];
|
|
75739
76124
|
let controlRefused = false;
|
|
75740
|
-
|
|
76125
|
+
const settings = await settingsStore.read();
|
|
76126
|
+
for (const account of accounts.slice(0, MAX_SYNC_ACCOUNTS)) {
|
|
75741
76127
|
const controlKey = controlKeyFor(account.credentials);
|
|
75742
|
-
if (controlKey
|
|
75743
|
-
|
|
75744
|
-
|
|
75745
|
-
|
|
75746
|
-
|
|
75747
|
-
|
|
75748
|
-
|
|
75749
|
-
|
|
75750
|
-
continue;
|
|
76128
|
+
if (controlKey !== void 0) {
|
|
76129
|
+
const scan = await fetchControlPlaneCatalog({
|
|
76130
|
+
fetchFn,
|
|
76131
|
+
controlKey,
|
|
76132
|
+
knownIds: known
|
|
76133
|
+
});
|
|
76134
|
+
if (scan === null) controlRefused = true;
|
|
76135
|
+
else if (scan.ids.length > 0) scans.push(scan);
|
|
75751
76136
|
}
|
|
75752
|
-
if (scan.ids.length > 0) scans.push(scan);
|
|
75753
|
-
break;
|
|
75754
|
-
}
|
|
75755
|
-
{
|
|
75756
|
-
const settings = await settingsStore.read();
|
|
75757
76137
|
const gateway = await fetchGatewayCatalog$1({
|
|
75758
76138
|
fetchFn,
|
|
75759
76139
|
baseURL: settings.baseURL,
|
|
75760
|
-
apiKey: bearerFor(
|
|
76140
|
+
apiKey: bearerFor(account.credentials)
|
|
75761
76141
|
});
|
|
75762
76142
|
if (gateway !== null && gateway.ids.length > 0) scans.push(gateway);
|
|
75763
76143
|
}
|
|
@@ -75787,9 +76167,10 @@ function registerOpenDesignRoutes(ctx, options) {
|
|
|
75787
76167
|
const merged = [...existing.values()].filter((entry) => {
|
|
75788
76168
|
return !current.catalogModels.some((old) => old.id === entry.id) || syncedIds.has(entry.id);
|
|
75789
76169
|
});
|
|
76170
|
+
const served = [...new Set(scans.flatMap((scan) => scan.ids))];
|
|
75790
76171
|
const fingerprintBefore = current.catalogModels.map((entry) => entry.id).join("\n");
|
|
75791
76172
|
const fingerprintAfter = merged.map((entry) => entry.id).join("\n");
|
|
75792
|
-
await settingsStore.storeCatalog(merged);
|
|
76173
|
+
await settingsStore.storeCatalog(merged, served);
|
|
75793
76174
|
if (fingerprintBefore !== fingerprintAfter) emitModelsUpdated();
|
|
75794
76175
|
return {
|
|
75795
76176
|
added,
|
|
@@ -76033,8 +76414,7 @@ function registerOpenDesignRoutes(ctx, options) {
|
|
|
76033
76414
|
const body = await readRequestJson(request).catch(() => ({}));
|
|
76034
76415
|
let touched = false;
|
|
76035
76416
|
if (body.enabledModelIds === null || Array.isArray(body.enabledModelIds)) {
|
|
76036
|
-
|
|
76037
|
-
await settingsStore.update({ enabledModelIds: ids });
|
|
76417
|
+
await settingsStore.selectModels(body.enabledModelIds === null ? null : body.enabledModelIds.filter((id) => typeof id === "string"));
|
|
76038
76418
|
touched = true;
|
|
76039
76419
|
}
|
|
76040
76420
|
if (typeof body.enabled === "boolean") {
|
|
@@ -76055,16 +76435,24 @@ function registerOpenDesignRoutes(ctx, options) {
|
|
|
76055
76435
|
});
|
|
76056
76436
|
const body = await readRequestJson(request).catch(() => ({}));
|
|
76057
76437
|
const current = await settingsStore.read();
|
|
76058
|
-
const known =
|
|
76438
|
+
const known = openDesignKnownModelIds(current);
|
|
76059
76439
|
const hidden = new Set(current.hiddenModels);
|
|
76440
|
+
const forced = new Set(current.forceVisibleModels);
|
|
76060
76441
|
if (path === "models/visibility") {
|
|
76061
76442
|
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
76062
76443
|
if (!known.includes(model)) return sendJson(response, 400, {
|
|
76063
76444
|
ok: false,
|
|
76064
76445
|
error: `${JSON.stringify(model)} is not one of this route's models`
|
|
76065
76446
|
});
|
|
76066
|
-
|
|
76067
|
-
|
|
76447
|
+
const served = isModelServed(model, current.servedModelIds);
|
|
76448
|
+
if (body.visible === false) {
|
|
76449
|
+
if (served) hidden.add(model);
|
|
76450
|
+
forced.delete(model);
|
|
76451
|
+
} else {
|
|
76452
|
+
hidden.delete(model);
|
|
76453
|
+
if (served) forced.delete(model);
|
|
76454
|
+
else forced.add(model);
|
|
76455
|
+
}
|
|
76068
76456
|
} else {
|
|
76069
76457
|
const mode = body.mode;
|
|
76070
76458
|
if (mode !== "all" && mode !== "none" && mode !== "invert") return sendJson(response, 400, {
|
|
@@ -76077,7 +76465,11 @@ function registerOpenDesignRoutes(ctx, options) {
|
|
|
76077
76465
|
const wasHidden = current.hiddenModels.includes(id);
|
|
76078
76466
|
return mode === "all" ? false : mode === "none" ? true : !wasHidden;
|
|
76079
76467
|
}) : [...hidden];
|
|
76080
|
-
|
|
76468
|
+
const nextForced = path !== "models/visibility-all" ? [...forced] : body.mode === "invert" ? current.forceVisibleModels : [];
|
|
76469
|
+
await settingsStore.update({
|
|
76470
|
+
hiddenModels: nextHidden,
|
|
76471
|
+
forceVisibleModels: nextForced
|
|
76472
|
+
});
|
|
76081
76473
|
emitModelsUpdated();
|
|
76082
76474
|
return sendJson(response, 200, {
|
|
76083
76475
|
ok: true,
|
|
@@ -76285,18 +76677,30 @@ function openDesignKnownModelIds(settings) {
|
|
|
76285
76677
|
return [...ids];
|
|
76286
76678
|
}
|
|
76287
76679
|
/**
|
|
76288
|
-
* Every id the line advertises right now.
|
|
76680
|
+
* Every id the line advertises right now — THE one visibility rule.
|
|
76681
|
+
*
|
|
76682
|
+
* The adapter (what the picker is offered), the status route (what each checklist
|
|
76683
|
+
* row reports as checked) and the hub card (how many models the line advertises)
|
|
76684
|
+
* all read this function and nothing else. Three private copies is how the hub
|
|
76685
|
+
* card ended up counting models the picker refused to show, which is exactly the
|
|
76686
|
+
* contradiction this rule exists to make impossible.
|
|
76687
|
+
*
|
|
76688
|
+
* Three layers, applied in this order:
|
|
76289
76689
|
*
|
|
76290
|
-
*
|
|
76291
|
-
*
|
|
76292
|
-
*
|
|
76293
|
-
*
|
|
76294
|
-
*
|
|
76690
|
+
* 1. the user's hidden list;
|
|
76691
|
+
* 2. the user's tick list — unset means every known model, an explicit `[]` means
|
|
76692
|
+
* everything off (the tick list and the shipped table speak the same wire ids,
|
|
76693
|
+
* so a cold-start intersection cannot empty the picker);
|
|
76694
|
+
* 3. the PLAN, once somebody measured it: an id the last successful scan did not
|
|
76695
|
+
* list is not served, and only a forced reveal (`forceVisibleModels`) keeps it.
|
|
76696
|
+
* Before that first scan `servedModelIds` is `null` and this layer hides
|
|
76697
|
+
* nothing — an unmeasured plan is not a denial.
|
|
76295
76698
|
*/
|
|
76296
76699
|
function openDesignVisibleModelIds(settings) {
|
|
76297
76700
|
const hidden = new Set(settings.hiddenModels);
|
|
76298
76701
|
const enabled = settings.enabledModelIds === null ? null : new Set(settings.enabledModelIds);
|
|
76299
|
-
|
|
76702
|
+
const forced = new Set(settings.forceVisibleModels);
|
|
76703
|
+
return openDesignKnownModelIds(settings).filter((id) => !hidden.has(id) && (enabled === null || enabled.has(id)) && (isModelServed(id, settings.servedModelIds) || forced.has(id)));
|
|
76300
76704
|
}
|
|
76301
76705
|
/**
|
|
76302
76706
|
* Build the line.
|