dsh-connect-trae 3.9.2 → 3.10.1
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 +134 -0
- package/lib/client.js +378 -186
- package/lib/index.d.ts +196 -1
- package/lib/index.js +530 -27
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -6,15 +6,15 @@ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { readFile, rm, stat } from "node:fs/promises";
|
|
10
|
-
import { createDecipheriv, createHash, randomBytes, randomUUID, sign, timingSafeEqual } from "node:crypto";
|
|
9
|
+
import { copyFile, readFile, rm, stat } from "node:fs/promises";
|
|
10
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID, sign, timingSafeEqual } from "node:crypto";
|
|
11
11
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
12
12
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
13
13
|
import { cpus, homedir, hostname, release, userInfo } from "node:os";
|
|
14
|
-
import { createServer } from "node:http";
|
|
15
|
-
import { Readable } from "node:stream";
|
|
16
14
|
import { execFile } from "node:child_process";
|
|
17
15
|
import { promisify } from "node:util";
|
|
16
|
+
import { createServer } from "node:http";
|
|
17
|
+
import { Readable } from "node:stream";
|
|
18
18
|
//#region src/catalog.ts
|
|
19
19
|
/**
|
|
20
20
|
* Bootstrap catalog: identity only where current Trae metadata has not been
|
|
@@ -966,11 +966,6 @@ function regionOfCredential(credential) {
|
|
|
966
966
|
}
|
|
967
967
|
//#endregion
|
|
968
968
|
//#region src/status-paths.ts
|
|
969
|
-
/**
|
|
970
|
-
* Node-free constants and types shared by the Host and browser halves.
|
|
971
|
-
* Mirrors the `dsh-workbuddy-connect` status-route pattern so the plugin card
|
|
972
|
-
* stays consistent with that project's external presentation.
|
|
973
|
-
*/
|
|
974
969
|
/** Plugin-owned usage endpoint consumed by its browser half. */
|
|
975
970
|
const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
976
971
|
/** Plugin-owned live model refresh endpoint. */
|
|
@@ -1038,7 +1033,7 @@ function poolActionOf(url) {
|
|
|
1038
1033
|
const at = url.indexOf("?");
|
|
1039
1034
|
if (at === -1) return void 0;
|
|
1040
1035
|
const value = new URLSearchParams(url.slice(at + 1)).get(TRAE_POOL_ACTION_PARAM);
|
|
1041
|
-
return value === "checkin" || value === "test" || value === "remove" ? value : void 0;
|
|
1036
|
+
return value === "checkin" || value === "test" || value === "remove" || value === "delete" ? value : void 0;
|
|
1042
1037
|
}
|
|
1043
1038
|
/**
|
|
1044
1039
|
* Read the ONE account a per-row pool request addresses, from the QUERY string.
|
|
@@ -1579,6 +1574,54 @@ function decryptTraeStorageValue(encoded) {
|
|
|
1579
1574
|
if (!expected.equals(actual)) throw new Error("Trae auth integrity check failed");
|
|
1580
1575
|
return plaintext.toString("utf8");
|
|
1581
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Re-encrypts one `iCubeAuthInfo` value the way Trae itself stores it.
|
|
1579
|
+
*
|
|
1580
|
+
* Needed only to rewrite the app's own `storage.json` after removing an account:
|
|
1581
|
+
* dropping a key from the plaintext JSON is useless unless the result is sealed
|
|
1582
|
+
* again with the same scheme, because Trae will fail to decrypt anything else.
|
|
1583
|
+
* The format is [6-byte header][32-byte random][AES-128-CBC ciphertext], where
|
|
1584
|
+
* the key/IV come from `sha512(sha512(random) || salt)` and the ciphertext holds
|
|
1585
|
+
* `sha512(body) || body`. A fresh random is drawn per call, so encrypting the
|
|
1586
|
+
* same plaintext twice yields different bytes — exactly like the app.
|
|
1587
|
+
*
|
|
1588
|
+
* `aes-private` (SALT_C ^ SALT_D) is supported because a value can be read in
|
|
1589
|
+
* that shape; the default matches every value observed in shipped storage files.
|
|
1590
|
+
*/
|
|
1591
|
+
function encryptTraeStorageValue(plaintext, type = "aes") {
|
|
1592
|
+
const body = Buffer.from(plaintext, "utf8");
|
|
1593
|
+
const digest = createHash("sha512").update(body).digest();
|
|
1594
|
+
const random = randomBytes(32);
|
|
1595
|
+
const salt = type === "aes-private" ? xor(SALT_C, SALT_D) : xor(SALT_A, SALT_B);
|
|
1596
|
+
const first = createHash("sha512").update(random).digest();
|
|
1597
|
+
const derived = createHash("sha512").update(Buffer.concat([first, salt])).digest();
|
|
1598
|
+
const cipher = createCipheriv("aes-128-cbc", derived.subarray(0, 16), derived.subarray(16, 32));
|
|
1599
|
+
const encrypted = Buffer.concat([cipher.update(Buffer.concat([digest, body])), cipher.final()]);
|
|
1600
|
+
const header = type === "aes-private" ? Buffer.from([
|
|
1601
|
+
18,
|
|
1602
|
+
57,
|
|
1603
|
+
32,
|
|
1604
|
+
32,
|
|
1605
|
+
2,
|
|
1606
|
+
3
|
|
1607
|
+
]) : Buffer.from([
|
|
1608
|
+
116,
|
|
1609
|
+
99,
|
|
1610
|
+
5,
|
|
1611
|
+
16,
|
|
1612
|
+
0,
|
|
1613
|
+
0
|
|
1614
|
+
]);
|
|
1615
|
+
return Buffer.concat([
|
|
1616
|
+
header,
|
|
1617
|
+
random,
|
|
1618
|
+
encrypted
|
|
1619
|
+
]).toString("base64");
|
|
1620
|
+
}
|
|
1621
|
+
/** The encryption family of an encoded value, for a byte-faithful rewrite. */
|
|
1622
|
+
function traeStorageValueType(encoded) {
|
|
1623
|
+
return encryptionType(Buffer.from(encoded, "base64").subarray(0, 6));
|
|
1624
|
+
}
|
|
1582
1625
|
function parseTraeAuthValue(value) {
|
|
1583
1626
|
const trimmed = value.trim();
|
|
1584
1627
|
if (trimmed === "") throw new Error("Trae auth value is empty");
|
|
@@ -1634,6 +1677,7 @@ function parseTraeCliToken(text) {
|
|
|
1634
1677
|
}
|
|
1635
1678
|
//#endregion
|
|
1636
1679
|
//#region src/paths.ts
|
|
1680
|
+
/** The macOS app-support directory name each edition writes, per edition. */
|
|
1637
1681
|
const APP_NAMES = {
|
|
1638
1682
|
cn: "Trae CN",
|
|
1639
1683
|
sg: "Trae",
|
|
@@ -1764,6 +1808,80 @@ function traeCliCandidates(platform = process.platform, home = homedir(), env =
|
|
|
1764
1808
|
return result;
|
|
1765
1809
|
}
|
|
1766
1810
|
//#endregion
|
|
1811
|
+
//#region src/trae-process.ts
|
|
1812
|
+
const run = promisify(execFile);
|
|
1813
|
+
/**
|
|
1814
|
+
* Why this exists at all: Trae keeps `storage.json` in memory and flushes it over
|
|
1815
|
+
* the file. Rewriting that file while the app runs is silently undone on its next
|
|
1816
|
+
* write — the account reappears and the user is told the deletion failed twice
|
|
1817
|
+
* for two different reasons. So the delete refuses while the matching app is up
|
|
1818
|
+
* and says which app to quit.
|
|
1819
|
+
*
|
|
1820
|
+
* This is a helper, never a gate on correctness: if the probe itself fails
|
|
1821
|
+
* (`ps` missing, sandbox denial) the answer is "nothing is running", and the
|
|
1822
|
+
* write proceeds. A false negative costs a retry; a false positive would block a
|
|
1823
|
+
* deletion that would have worked.
|
|
1824
|
+
*/
|
|
1825
|
+
const probeRunningTraeApps = async () => {
|
|
1826
|
+
try {
|
|
1827
|
+
if (process.platform === "darwin") {
|
|
1828
|
+
const { stdout } = await run("ps", ["-Ao", "command"], { maxBuffer: 16777216 });
|
|
1829
|
+
return traeEditionsInCommands(stdout, "Application Support");
|
|
1830
|
+
}
|
|
1831
|
+
if (process.platform === "linux") {
|
|
1832
|
+
const { stdout } = await run("ps", ["-Ao", "args"], { maxBuffer: 16777216 });
|
|
1833
|
+
return traeEditionsInCommands(stdout, ".config");
|
|
1834
|
+
}
|
|
1835
|
+
if (process.platform === "win32") {
|
|
1836
|
+
const { stdout } = await run("tasklist", [
|
|
1837
|
+
"/FO",
|
|
1838
|
+
"CSV",
|
|
1839
|
+
"/NH"
|
|
1840
|
+
], { maxBuffer: 16777216 });
|
|
1841
|
+
return /trae/i.test(stdout) ? /* @__PURE__ */ new Set([
|
|
1842
|
+
"cn",
|
|
1843
|
+
"sg",
|
|
1844
|
+
"solo",
|
|
1845
|
+
"solo-sg"
|
|
1846
|
+
]) : /* @__PURE__ */ new Set();
|
|
1847
|
+
}
|
|
1848
|
+
} catch {}
|
|
1849
|
+
return /* @__PURE__ */ new Set();
|
|
1850
|
+
};
|
|
1851
|
+
/**
|
|
1852
|
+
* Editions whose app-support directory appears in a process listing.
|
|
1853
|
+
*
|
|
1854
|
+
* The names are prefixes of each other (`Trae` vs `Trae CN`), so the match is
|
|
1855
|
+
* made longest-first at each occurrence and consumes the name it claimed. A
|
|
1856
|
+
* plain `includes('Trae')` would report the international app running whenever
|
|
1857
|
+
* only `Trae CN` was, and the delete would then refuse for the wrong edition.
|
|
1858
|
+
*/
|
|
1859
|
+
function traeEditionsInCommands(commands, supportDir) {
|
|
1860
|
+
const running = /* @__PURE__ */ new Set();
|
|
1861
|
+
const editions = [
|
|
1862
|
+
"cn",
|
|
1863
|
+
"sg",
|
|
1864
|
+
"solo",
|
|
1865
|
+
"solo-sg"
|
|
1866
|
+
].map((edition) => ({
|
|
1867
|
+
edition,
|
|
1868
|
+
name: APP_NAMES[edition]
|
|
1869
|
+
})).sort((a, b) => b.name.length - a.name.length);
|
|
1870
|
+
const marker = `${supportDir}/`;
|
|
1871
|
+
let cursor = commands.indexOf(marker);
|
|
1872
|
+
while (cursor !== -1) {
|
|
1873
|
+
let consumed = 0;
|
|
1874
|
+
for (const { edition, name } of editions) {
|
|
1875
|
+
if (!commands.startsWith(name, cursor + marker.length)) continue;
|
|
1876
|
+
running.add(edition);
|
|
1877
|
+
consumed = name.length;
|
|
1878
|
+
break;
|
|
1879
|
+
}
|
|
1880
|
+
cursor = commands.indexOf(marker, cursor + marker.length + consumed);
|
|
1881
|
+
}
|
|
1882
|
+
return running;
|
|
1883
|
+
}
|
|
1884
|
+
//#endregion
|
|
1767
1885
|
//#region src/auth.ts
|
|
1768
1886
|
/** Host used for CLI tokens, which carry no host claim of their own. */
|
|
1769
1887
|
const CLI_DEFAULT_HOST = "https://api.trae.cn";
|
|
@@ -1812,6 +1930,20 @@ function traeOwnAuthPath(region) {
|
|
|
1812
1930
|
function legacyTraeOwnAuthPath() {
|
|
1813
1931
|
return join(resolveDshHome(), TRAE_AUTH_FILENAME);
|
|
1814
1932
|
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Where the "this copy was deleted on purpose" tombstones live.
|
|
1935
|
+
*
|
|
1936
|
+
* The store mirrors every local Trae login into its own copy on each refresh
|
|
1937
|
+
* (see `mirrorLocalLogins`). Without a tombstone the mirror would recreate a
|
|
1938
|
+
* copy the user just deleted on the very next poll — "delete" would look broken
|
|
1939
|
+
* again, which is the exact complaint this mechanism exists to fix. A tombstone
|
|
1940
|
+
* therefore suppresses the mirror for one account id, and is dropped as soon as
|
|
1941
|
+
* that id is no longer signed in to the app: a later sign-in is a NEW login and
|
|
1942
|
+
* is mirrored again.
|
|
1943
|
+
*/
|
|
1944
|
+
function traeDeletedAccountsPath(region) {
|
|
1945
|
+
return join(resolveDshHome(), region === void 0 ? ".trae-deleted.json" : `${TRAE_OWN_PREFIX}.deleted.${region}.json`);
|
|
1946
|
+
}
|
|
1815
1947
|
function optionalString(value) {
|
|
1816
1948
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
1817
1949
|
}
|
|
@@ -1951,6 +2083,42 @@ async function writeOwn(path, credentials) {
|
|
|
1951
2083
|
dirMode: 448
|
|
1952
2084
|
});
|
|
1953
2085
|
}
|
|
2086
|
+
/** The key Trae stores the per-user region map under, beside the login itself. */
|
|
2087
|
+
const TRAE_USERTAG_STORAGE_KEY = "iCubeAuthInfo://usertag";
|
|
2088
|
+
/**
|
|
2089
|
+
* Rewrites one Trae `storage.json` so it no longer carries the given login.
|
|
2090
|
+
*
|
|
2091
|
+
* Everything else in the document is preserved byte-for-byte at the JSON level:
|
|
2092
|
+
* only the auth value is dropped and the usertag map loses this one user. Both
|
|
2093
|
+
* the backup and the atomic write exist because this is the one operation in the
|
|
2094
|
+
* plugin that edits a file belonging to another application.
|
|
2095
|
+
*/
|
|
2096
|
+
async function eraseDesktopLogin(path, document, claims, encoded) {
|
|
2097
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2098
|
+
try {
|
|
2099
|
+
await copyFile(path, `${path}.dsh-backup-${stamp}`);
|
|
2100
|
+
} catch {
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
const next = { ...document };
|
|
2104
|
+
delete next[TRAE_AUTH_STORAGE_KEY];
|
|
2105
|
+
const userTag = document[TRAE_USERTAG_STORAGE_KEY];
|
|
2106
|
+
const userId = typeof claims["userId"] === "string" || typeof claims["userId"] === "number" ? String(claims["userId"]) : void 0;
|
|
2107
|
+
if (userId !== void 0 && typeof userTag === "string") try {
|
|
2108
|
+
const decoded = parseTraeAuthValue(userTag);
|
|
2109
|
+
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
2110
|
+
const tags = { ...decoded };
|
|
2111
|
+
delete tags[userId];
|
|
2112
|
+
next[TRAE_USERTAG_STORAGE_KEY] = encryptTraeStorageValue(JSON.stringify(tags), traeStorageValueType(userTag));
|
|
2113
|
+
}
|
|
2114
|
+
} catch {}
|
|
2115
|
+
try {
|
|
2116
|
+
await writeFileAtomic(path, `${JSON.stringify(next, null, 2)}\n`, { mode: 384 });
|
|
2117
|
+
return !(await readFile(path, "utf8")).includes(encoded);
|
|
2118
|
+
} catch {
|
|
2119
|
+
return false;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
1954
2122
|
var TraeCredentialStore = class {
|
|
1955
2123
|
storagePathOverride;
|
|
1956
2124
|
edition;
|
|
@@ -1963,6 +2131,7 @@ var TraeCredentialStore = class {
|
|
|
1963
2131
|
legacyOwnPathExplicit;
|
|
1964
2132
|
refresh;
|
|
1965
2133
|
refreshMarginMs;
|
|
2134
|
+
runningApps;
|
|
1966
2135
|
inflight;
|
|
1967
2136
|
/**
|
|
1968
2137
|
* In-flight refreshes of individual accounts, one per account id.
|
|
@@ -2007,6 +2176,7 @@ var TraeCredentialStore = class {
|
|
|
2007
2176
|
this.legacyOwnPathExplicit = options.legacyOwnPath;
|
|
2008
2177
|
this.refresh = options.refresh;
|
|
2009
2178
|
this.refreshMarginMs = options.refreshMarginMs ?? 3e5;
|
|
2179
|
+
this.runningApps = options.runningApps ?? probeRunningTraeApps;
|
|
2010
2180
|
}
|
|
2011
2181
|
/** Whether a credential's own claim belongs to this store's region. */
|
|
2012
2182
|
matchesRegion(credential) {
|
|
@@ -2040,6 +2210,93 @@ var TraeCredentialStore = class {
|
|
|
2040
2210
|
traeOwnAuthPath("ai")
|
|
2041
2211
|
];
|
|
2042
2212
|
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Where the "deleted copy" tombstones live for this store.
|
|
2215
|
+
*
|
|
2216
|
+
* Derived from the store's own copy path, so a test that pins `ownPath` also
|
|
2217
|
+
* pins the tombstones and can never touch the real `~/.dsh` file.
|
|
2218
|
+
*/
|
|
2219
|
+
deletedPath() {
|
|
2220
|
+
if (this.ownPathExplicit !== void 0) return `${this.ownPathExplicit}.deleted`;
|
|
2221
|
+
if (this.legacyOwnPathExplicit !== void 0) return `${this.legacyOwnPathExplicit}.deleted`;
|
|
2222
|
+
return traeDeletedAccountsPath(this.region);
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* The account ids whose copy the user deleted on purpose.
|
|
2226
|
+
*
|
|
2227
|
+
* Read defensively: a missing, unreadable, or hand-edited file means "no
|
|
2228
|
+
* tombstones", never a failing store — losing the list only re-mirrors an
|
|
2229
|
+
* account, it never locks the user out.
|
|
2230
|
+
*/
|
|
2231
|
+
async readTombstones() {
|
|
2232
|
+
const text = await readFile(this.deletedPath(), "utf8").catch(() => "");
|
|
2233
|
+
if (text.trim() === "") return /* @__PURE__ */ new Set();
|
|
2234
|
+
try {
|
|
2235
|
+
const parsed = JSON.parse(text);
|
|
2236
|
+
const ids = Array.isArray(parsed) ? parsed : typeof parsed === "object" && parsed !== null && Array.isArray(parsed.ids) ? parsed.ids : [];
|
|
2237
|
+
return new Set(ids.filter((id) => typeof id === "string" && TRAE_ACCOUNT_ID_RE.test(id)));
|
|
2238
|
+
} catch {
|
|
2239
|
+
return /* @__PURE__ */ new Set();
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
async writeTombstones(ids) {
|
|
2243
|
+
const path = this.deletedPath();
|
|
2244
|
+
await withFileLock(path, async () => {
|
|
2245
|
+
await writeFileAtomic(path, `${JSON.stringify({
|
|
2246
|
+
version: 1,
|
|
2247
|
+
ids: [...ids]
|
|
2248
|
+
}, null, 2)}\n`, {
|
|
2249
|
+
mode: 384,
|
|
2250
|
+
dirMode: 448
|
|
2251
|
+
});
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Copy every local Trae sign-in into this store's own file, once.
|
|
2256
|
+
*
|
|
2257
|
+
* The plugin presents COPIES, not the app's live login: the app has a single
|
|
2258
|
+
* sign-in slot that the next Trae login overwrites, so an account could only
|
|
2259
|
+
* ever be billed until the user switched apps. Keeping a copy per account is
|
|
2260
|
+
* what makes a multi-account pool survive those switches.
|
|
2261
|
+
*
|
|
2262
|
+
* This runs on every `accounts()` read — the card's refresh, the pool's member
|
|
2263
|
+
* read, the "detect accounts" press — and is additive:
|
|
2264
|
+
* - an account that already has a copy is left exactly as it is (the copy
|
|
2265
|
+
* may hold a token this plugin refreshed, which is fresher than the app's);
|
|
2266
|
+
* - an account the user DELETED stays deleted while it is still signed in to
|
|
2267
|
+
* the app, via a tombstone that is dropped once the app no longer holds it
|
|
2268
|
+
* (so a later sign-in is mirrored again).
|
|
2269
|
+
*/
|
|
2270
|
+
async mirrorLocalLogins() {
|
|
2271
|
+
const { credentials: discovered } = await this.readDesktopAll();
|
|
2272
|
+
const own = await this.readOwns();
|
|
2273
|
+
const ownIds = new Set(own.map((credential) => traeAccountId(credential)));
|
|
2274
|
+
const tombstones = await this.readTombstones();
|
|
2275
|
+
const liveIds = new Set(discovered.map((credential) => traeAccountId(credential)));
|
|
2276
|
+
let tombstonesChanged = false;
|
|
2277
|
+
for (const id of [...tombstones]) {
|
|
2278
|
+
if (liveIds.has(id)) continue;
|
|
2279
|
+
tombstones.delete(id);
|
|
2280
|
+
tombstonesChanged = true;
|
|
2281
|
+
}
|
|
2282
|
+
const missing = discovered.filter((credential) => this.matchesRegion(credential) && !ownIds.has(traeAccountId(credential)) && !tombstones.has(traeAccountId(credential)));
|
|
2283
|
+
if (tombstonesChanged) await this.writeTombstones(tombstones);
|
|
2284
|
+
if (missing.length === 0) return;
|
|
2285
|
+
const ownPath = this.ownAuthPath();
|
|
2286
|
+
await withFileLock(ownPath, async () => {
|
|
2287
|
+
const existing = await readFile(ownPath, "utf8").then(parseOwn, () => []);
|
|
2288
|
+
const byId = new Map(existing.map((credential) => [traeAccountId(credential), credential]));
|
|
2289
|
+
for (const credential of missing) {
|
|
2290
|
+
const id = traeAccountId(credential);
|
|
2291
|
+
if (byId.has(id)) continue;
|
|
2292
|
+
byId.set(id, {
|
|
2293
|
+
...credential,
|
|
2294
|
+
source: "dsh"
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
await writeOwn(ownPath, [...byId.values()]);
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2043
2300
|
setSource(storagePath, edition = "auto", accountId) {
|
|
2044
2301
|
this.storagePathOverride = storagePath;
|
|
2045
2302
|
this.edition = edition;
|
|
@@ -2137,8 +2394,15 @@ var TraeCredentialStore = class {
|
|
|
2137
2394
|
return eligible.length > 0 ? eligible : discovered;
|
|
2138
2395
|
}
|
|
2139
2396
|
async accounts() {
|
|
2397
|
+
await this.mirrorLocalLogins();
|
|
2140
2398
|
const discovered = await this.readAll();
|
|
2141
|
-
const
|
|
2399
|
+
const copyIds = /* @__PURE__ */ new Set();
|
|
2400
|
+
for (const credential of await this.readOwns()) {
|
|
2401
|
+
if (!this.matchesRegion(credential)) continue;
|
|
2402
|
+
copyIds.add(traeAccountId(credential));
|
|
2403
|
+
}
|
|
2404
|
+
const visible = discovered.filter((credential) => copyIds.has(traeAccountId(credential)));
|
|
2405
|
+
const offered = this.offered(visible);
|
|
2142
2406
|
const inUse = this.inUse(discovered, offered);
|
|
2143
2407
|
/**
|
|
2144
2408
|
* Never hide the account we are actually billing.
|
|
@@ -2159,7 +2423,7 @@ var TraeCredentialStore = class {
|
|
|
2159
2423
|
edition: credential.edition,
|
|
2160
2424
|
enterprise: isEnterpriseAccount(credential),
|
|
2161
2425
|
region: regionOfCredential(credential),
|
|
2162
|
-
source:
|
|
2426
|
+
source: "dsh",
|
|
2163
2427
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
2164
2428
|
...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
|
|
2165
2429
|
selected: inUseId !== void 0 && traeAccountId(credential) === inUseId
|
|
@@ -2269,6 +2533,21 @@ var TraeCredentialStore = class {
|
|
|
2269
2533
|
return (await this.readAll()).find((credential) => traeAccountId(credential) === accountId);
|
|
2270
2534
|
}
|
|
2271
2535
|
/**
|
|
2536
|
+
* The PLUGIN'S OWN COPY of one account — never the Trae app's credential.
|
|
2537
|
+
*
|
|
2538
|
+
* {@link credentialOf} answers from the merged view (copies plus the app's
|
|
2539
|
+
* logins) and prefers whichever is fresher. That is right for placing a
|
|
2540
|
+
* request, but wrong for HANDING CREDENTIALS OUT: an export must carry the
|
|
2541
|
+
* copies this plugin owns and refreshes, not whatever the app happens to have
|
|
2542
|
+
* on disk. The two are normally identical — the copy is mirrored from the app
|
|
2543
|
+
* — but only the copy is ours, and an account with no copy is not ours to
|
|
2544
|
+
* export at all (undefined, no fallback to the app's login).
|
|
2545
|
+
*/
|
|
2546
|
+
async copyOf(accountId) {
|
|
2547
|
+
if (accountId === "") return void 0;
|
|
2548
|
+
return (await this.readOwns()).find((credential) => traeAccountId(credential) === accountId);
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2272
2551
|
* A SPECIFIC account's credential, refreshed when its access token is due.
|
|
2273
2552
|
*
|
|
2274
2553
|
* {@link credentialOf} is a pure lookup and hands back an expired token as-is,
|
|
@@ -2385,6 +2664,133 @@ var TraeCredentialStore = class {
|
|
|
2385
2664
|
}
|
|
2386
2665
|
}
|
|
2387
2666
|
/**
|
|
2667
|
+
* Remove ONE account from the plugin-owned copy (per-region file, legacy
|
|
2668
|
+
* single file), where this store can rewrite the shape it wrote.
|
|
2669
|
+
*
|
|
2670
|
+
* The desktop storage files are deliberately untouched: their credential is
|
|
2671
|
+
* a Trae app login that only Trae can rewrite, so a plugin removing it would
|
|
2672
|
+
* corrupt an application state it does not own. `deleteAccount` therefore
|
|
2673
|
+
* only ever commits what this plugin persisted — the refreshed copy the
|
|
2674
|
+
* store owns — and reports what it did.
|
|
2675
|
+
*
|
|
2676
|
+
* `stillSignedIn` is true when the SAME account id still resolves to a
|
|
2677
|
+
* desktop storage copy, i.e. the account will keep appearing (from Trae's own
|
|
2678
|
+
* login) even after the plugin's copy is gone. The card then shows the user
|
|
2679
|
+
* where the account really lives instead of implying a deletion that did not
|
|
2680
|
+
* take: the account is still signed in Trae, and Trae is the only writer that
|
|
2681
|
+
* can remove it.
|
|
2682
|
+
*
|
|
2683
|
+
* @returns `{ deleted: boolean; stillSignedIn: boolean }` — `deleted` is
|
|
2684
|
+
* whether a plugin-owned copy of this account was actually removed;
|
|
2685
|
+
* `stillSignedIn` reports a TRAE-owned login that survives it.
|
|
2686
|
+
*/
|
|
2687
|
+
/**
|
|
2688
|
+
* Erases this account's login from Trae's OWN storage files.
|
|
2689
|
+
*
|
|
2690
|
+
* Deleting only the plugin's copy of an account the app itself is signed in to
|
|
2691
|
+
* changes nothing the user can see: the app keeps reporting the account, the
|
|
2692
|
+
* row comes back on the next poll, and "delete" looks broken. The only real
|
|
2693
|
+
* deletion therefore has to rewrite the app's `storage.json`, which means
|
|
2694
|
+
* (a) re-encrypting the values that stay, with Trae's own scheme, and (b)
|
|
2695
|
+
* refusing while that app runs — its in-memory state would be flushed back
|
|
2696
|
+
* over the file and undo the edit.
|
|
2697
|
+
*
|
|
2698
|
+
* A backup of every rewritten file is left beside it (`.dsh-backup-<stamp>`),
|
|
2699
|
+
* because signing a user's editor out is not something to do without a way back.
|
|
2700
|
+
*/
|
|
2701
|
+
async deleteDesktopLogins(accountId) {
|
|
2702
|
+
const signedOut = [];
|
|
2703
|
+
const blocked = [];
|
|
2704
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2705
|
+
for (const candidate of this.candidates()) {
|
|
2706
|
+
if (candidate.source !== "desktop" || seen.has(candidate.path)) continue;
|
|
2707
|
+
seen.add(candidate.path);
|
|
2708
|
+
const document = await readFile(candidate.path, "utf8").then((text) => JSON.parse(text)).catch(() => void 0);
|
|
2709
|
+
if (document === void 0) continue;
|
|
2710
|
+
const encoded = document[TRAE_AUTH_STORAGE_KEY];
|
|
2711
|
+
if (typeof encoded !== "string") continue;
|
|
2712
|
+
let claims;
|
|
2713
|
+
try {
|
|
2714
|
+
const parsed = parseTraeAuthValue(encoded);
|
|
2715
|
+
if (typeof parsed !== "object" || parsed === null) continue;
|
|
2716
|
+
claims = parsed;
|
|
2717
|
+
} catch {
|
|
2718
|
+
continue;
|
|
2719
|
+
}
|
|
2720
|
+
const credential = normalizeTraeCredential(claims, candidate.edition, "desktop");
|
|
2721
|
+
if (credential === void 0 || traeAccountId(credential) !== accountId) continue;
|
|
2722
|
+
if ((await this.runningApps().catch(() => /* @__PURE__ */ new Set())).has(candidate.edition)) {
|
|
2723
|
+
blocked.push(candidate.edition);
|
|
2724
|
+
continue;
|
|
2725
|
+
}
|
|
2726
|
+
if (await eraseDesktopLogin(candidate.path, document, claims, encoded)) signedOut.push(candidate.edition);
|
|
2727
|
+
else blocked.push(candidate.edition);
|
|
2728
|
+
}
|
|
2729
|
+
return {
|
|
2730
|
+
signedOut: [...new Set(signedOut)],
|
|
2731
|
+
blocked: [...new Set(blocked)]
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Delete this store's COPY of one account.
|
|
2736
|
+
*
|
|
2737
|
+
* What the plugin shows and bills is the copy, so that is the first thing
|
|
2738
|
+
* "delete" removes. The copy alone is not enough: the same login usually also
|
|
2739
|
+
* lives in the app's own `storage.json`, and the app keeps reporting it, so
|
|
2740
|
+
* the delete then erases that login too (see `deleteDesktopLogins`, which
|
|
2741
|
+
* refuses while the app runs and leaves a backup beside every file it
|
|
2742
|
+
* rewrites).
|
|
2743
|
+
*
|
|
2744
|
+
* The catch is the mirror: `accounts()` recreates a copy from any local login
|
|
2745
|
+
* that lacks one, so deleting the copy of an account still signed in to Trae
|
|
2746
|
+
* would resurrect it on the next poll. A TOMBSTONE records the deletion for
|
|
2747
|
+
* exactly that id; the mirror skips it while the app still holds the login and
|
|
2748
|
+
* drops the tombstone once it does not, so signing in again later mirrors it
|
|
2749
|
+
* again. That is why `stillSignedIn` is reported: it explains that the account
|
|
2750
|
+
* still lives in Trae even though the plugin no longer shows it.
|
|
2751
|
+
*
|
|
2752
|
+
* @returns `{ deleted, stillSignedIn, hadCopy, signedOutApps, blockedApps }` —
|
|
2753
|
+
* `deleted` is "this machine no longer carries the account anywhere" (true
|
|
2754
|
+
* even when there was never a copy, so a ghost member can be cleared);
|
|
2755
|
+
* `stillSignedIn` answers "is it still a live Trae login here?"; `hadCopy` is
|
|
2756
|
+
* "did a file actually change?"; `signedOutApps` names the apps whose login
|
|
2757
|
+
* was erased; `blockedApps` names the apps that were running, in which case
|
|
2758
|
+
* nothing was erased and `deleted` is false.
|
|
2759
|
+
*/
|
|
2760
|
+
async deleteAccount(accountId) {
|
|
2761
|
+
let mutated = false;
|
|
2762
|
+
for (const path of this.ownCandidates()) {
|
|
2763
|
+
const dropped = await withFileLock(path, async () => {
|
|
2764
|
+
const existing = await readFile(path, "utf8").then(parseOwn, () => null);
|
|
2765
|
+
if (existing === null || !existing.some((credential) => traeAccountId(credential) === accountId)) return false;
|
|
2766
|
+
const next = existing.filter((credential) => traeAccountId(credential) !== accountId);
|
|
2767
|
+
await writeOwn(path, next);
|
|
2768
|
+
return true;
|
|
2769
|
+
}).catch(() => false);
|
|
2770
|
+
mutated = mutated || dropped;
|
|
2771
|
+
}
|
|
2772
|
+
const tombstones = await this.readTombstones();
|
|
2773
|
+
if (!tombstones.has(accountId)) {
|
|
2774
|
+
tombstones.add(accountId);
|
|
2775
|
+
await this.writeTombstones(tombstones).catch(() => void 0);
|
|
2776
|
+
}
|
|
2777
|
+
if (this.accountId === accountId) {
|
|
2778
|
+
this.accountId = void 0;
|
|
2779
|
+
this.inflight = void 0;
|
|
2780
|
+
}
|
|
2781
|
+
const { signedOut, blocked } = await this.deleteDesktopLogins(accountId);
|
|
2782
|
+
const stillInOwn = (await this.readOwns()).some((credential) => traeAccountId(credential) === accountId);
|
|
2783
|
+
const { credentials: discovered } = await this.readDesktopAll();
|
|
2784
|
+
const stillSignedIn = discovered.some((credential) => traeAccountId(credential) === accountId);
|
|
2785
|
+
return {
|
|
2786
|
+
deleted: !stillInOwn && !stillSignedIn,
|
|
2787
|
+
stillSignedIn,
|
|
2788
|
+
hadCopy: mutated || signedOut.length > 0,
|
|
2789
|
+
signedOutApps: signedOut,
|
|
2790
|
+
blockedApps: blocked
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2388
2794
|
* Every local credential of this store's region, deduplicated by account id.
|
|
2389
2795
|
* A region-scoped store sees only its own region's credentials: the other
|
|
2390
2796
|
* region's accounts are invisible to selection, refresh, and status alike,
|
|
@@ -4271,6 +4677,39 @@ async function bridgeTraeSoloStreamOrFail(response, model) {
|
|
|
4271
4677
|
response: bridgeTraeSource(opened.source, model)
|
|
4272
4678
|
};
|
|
4273
4679
|
}
|
|
4680
|
+
/**
|
|
4681
|
+
* The startup wire map for one region's merged directory.
|
|
4682
|
+
*
|
|
4683
|
+
* EVERY row is registered, not only the ones whose wire config_name differs
|
|
4684
|
+
* from the display id. The runtime catalog is filtered by `enabledModelIds`, so
|
|
4685
|
+
* a pool that targets a model the user never enabled for chat would otherwise
|
|
4686
|
+
* reach the bridge with no catalog row AND no mapping — and the request would
|
|
4687
|
+
* carry the raw id under the DEFAULT function, which answers `4001 config_name
|
|
4688
|
+
* rejected` (Doubao-Seed-Code is served only by `solo_agent_remote`;
|
|
4689
|
+
* `solo_work_lite` rejects it). The identity mapping is what keeps the
|
|
4690
|
+
* discovered function alive for those ids.
|
|
4691
|
+
*
|
|
4692
|
+
* First row wins a display name: two directory ids can share one (`Seed-Code`),
|
|
4693
|
+
* and overwriting would answer with the later row's config_name. Ids are
|
|
4694
|
+
* unique, so `byId` needs no such guard.
|
|
4695
|
+
*/
|
|
4696
|
+
function wireTargetsOf(models) {
|
|
4697
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4698
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4699
|
+
for (const model of models) {
|
|
4700
|
+
const target = {
|
|
4701
|
+
configName: model.wireConfigName ?? model.id,
|
|
4702
|
+
...model.wireFunction === void 0 ? {} : { function: model.wireFunction }
|
|
4703
|
+
};
|
|
4704
|
+
byId.set(model.id, target);
|
|
4705
|
+
const nameKey = model.name.trim().toLowerCase();
|
|
4706
|
+
if (!byName.has(nameKey)) byName.set(nameKey, target);
|
|
4707
|
+
}
|
|
4708
|
+
return {
|
|
4709
|
+
byId,
|
|
4710
|
+
byName
|
|
4711
|
+
};
|
|
4712
|
+
}
|
|
4274
4713
|
/** Native SOLO client wrapper used by the loopback OpenAI adapter. */
|
|
4275
4714
|
var TraeSoloBridge = class {
|
|
4276
4715
|
upstream;
|
|
@@ -4294,7 +4733,23 @@ var TraeSoloBridge = class {
|
|
|
4294
4733
|
...entry.wireFunction === void 0 ? {} : { function: entry.wireFunction }
|
|
4295
4734
|
};
|
|
4296
4735
|
const fromResolver = this.wireResolver?.(model) ?? this.wireResolver?.(entry?.name ?? "");
|
|
4297
|
-
|
|
4736
|
+
/**
|
|
4737
|
+
* The catalog row wins the config_name, but it may not know the function.
|
|
4738
|
+
*
|
|
4739
|
+
* A row persisted by an older build -- or one whose `wireFunction` the
|
|
4740
|
+
* settings schema dropped -- carries the name mapping only, and asking
|
|
4741
|
+
* without the function is a `4001 config_name rejected` for every model
|
|
4742
|
+
* that a non-default function serves (`Doubao-Seed-Code` lives on
|
|
4743
|
+
* `solo_agent_remote` alone). The resolver is built from the same
|
|
4744
|
+
* directory, so borrowing its function is safe exactly when both sources
|
|
4745
|
+
* agree on the wire config_name; a disagreement means the resolver found
|
|
4746
|
+
* a different row and its function would be wrong.
|
|
4747
|
+
*/
|
|
4748
|
+
const sameWireConfig = fromCatalog !== void 0 && fromResolver !== void 0 && fromCatalog.configName.toLowerCase() === fromResolver.configName.toLowerCase();
|
|
4749
|
+
const target = fromCatalog !== void 0 && fromCatalog.function === void 0 && sameWireConfig && fromResolver !== void 0 ? {
|
|
4750
|
+
configName: fromCatalog.configName,
|
|
4751
|
+
...fromResolver.function === void 0 ? {} : { function: fromResolver.function }
|
|
4752
|
+
} : fromCatalog ?? fromResolver;
|
|
4298
4753
|
/**
|
|
4299
4754
|
* Last line of defence: the discovered row's own id.
|
|
4300
4755
|
*
|
|
@@ -6635,7 +7090,7 @@ async function traeWebPool(deps, panel) {
|
|
|
6635
7090
|
const credits = entry?.credits;
|
|
6636
7091
|
const checkin = checkinState[row.account.id];
|
|
6637
7092
|
const excludedBy = excludedById.get(row.account.id);
|
|
6638
|
-
const accountName = row.account.accountName !== "" ? row.account.accountName : memberNames[row.account.id] ?? measured?.accountName ??
|
|
7093
|
+
const accountName = row.account.accountName !== "" ? row.account.accountName : memberNames[row.account.id] ?? measured?.accountName ?? row.account.id;
|
|
6639
7094
|
return {
|
|
6640
7095
|
accountId: row.account.id,
|
|
6641
7096
|
accountName,
|
|
@@ -6679,9 +7134,12 @@ async function traeWebPool(deps, panel) {
|
|
|
6679
7134
|
else if (ranked.every((row) => row.excludedBy !== void 0)) noCandidate = new Set(ranked.map((row) => row.excludedBy)).size === 1 ? ranked[0]?.excludedBy ?? "unusable" : "unusable";
|
|
6680
7135
|
}
|
|
6681
7136
|
const effectiveMemberAccountIds = effectiveMembersOf(rows.filter((row) => row.member).map((row) => row.account.id), new Set(members.map((member) => member.account.id)));
|
|
7137
|
+
const copies = await deps.copiesOf?.(panel).catch(() => void 0);
|
|
6682
7138
|
return {
|
|
6683
7139
|
enabled: preferences.enabled === true,
|
|
6684
7140
|
checkinSupported: pool.checkin !== void 0 && regionOfPanel(panel) === "cn",
|
|
7141
|
+
...deps.deleteAccountOf === void 0 ? {} : { deleteSupported: true },
|
|
7142
|
+
...copies === void 0 ? {} : { copies },
|
|
6685
7143
|
...target.modelId === void 0 ? {} : { targetModelId: target.modelId },
|
|
6686
7144
|
targetModelSource: target.source,
|
|
6687
7145
|
probeInputTokens: Math.max(0, preferences.probeInputTokens ?? 0),
|
|
@@ -6878,7 +7336,7 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
6878
7336
|
const records = [];
|
|
6879
7337
|
for (const account of await store.accounts()) {
|
|
6880
7338
|
if (!wanted.has(account.id)) continue;
|
|
6881
|
-
const credential = await store.
|
|
7339
|
+
const credential = await store.copyOf(account.id).catch(() => void 0);
|
|
6882
7340
|
if (credential === void 0) continue;
|
|
6883
7341
|
records.push(traeTransferRecordOf(credential, {
|
|
6884
7342
|
auth: {
|
|
@@ -7091,6 +7549,19 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
7091
7549
|
}
|
|
7092
7550
|
return;
|
|
7093
7551
|
}
|
|
7552
|
+
if (action === "delete") {
|
|
7553
|
+
if (accountId === void 0) return json(res, 400, { error: "delete needs an accountId" });
|
|
7554
|
+
if (deps.deleteAccountOf === void 0) return json(res, 503, { error: "account deletion unavailable" });
|
|
7555
|
+
try {
|
|
7556
|
+
json(res, 200, {
|
|
7557
|
+
accountId,
|
|
7558
|
+
...await deps.deleteAccountOf(panel, accountId)
|
|
7559
|
+
});
|
|
7560
|
+
} catch (error) {
|
|
7561
|
+
json(res, 500, { error: safeMessage(error) });
|
|
7562
|
+
}
|
|
7563
|
+
return;
|
|
7564
|
+
}
|
|
7094
7565
|
/**
|
|
7095
7566
|
* Both remaining actions need a NON-EMPTY effective pool.
|
|
7096
7567
|
*
|
|
@@ -8116,6 +8587,23 @@ function apply(ctx, config) {
|
|
|
8116
8587
|
byId: /* @__PURE__ */ new Map(),
|
|
8117
8588
|
byName: /* @__PURE__ */ new Map()
|
|
8118
8589
|
};
|
|
8590
|
+
/**
|
|
8591
|
+
* Warm the wire map before anything spends quota.
|
|
8592
|
+
*
|
|
8593
|
+
* The bridge resolves a model's directory function from the wire map,
|
|
8594
|
+
* falling back to the catalog row. Right after a restart neither may be
|
|
8595
|
+
* populated yet — discovery only runs when the host refreshes its model list
|
|
8596
|
+
* — and a probe sent without the function is answered `4001` for every model
|
|
8597
|
+
* that a non-default function serves (`Doubao-Seed-Code` lives on
|
|
8598
|
+
* `solo_agent_remote`). One discovery here is cheaper than telling the user
|
|
8599
|
+
* that a working account is unavailable.
|
|
8600
|
+
*/
|
|
8601
|
+
const warmWireMap = async (panel) => {
|
|
8602
|
+
if (wireState[panel].resolved) return;
|
|
8603
|
+
try {
|
|
8604
|
+
await stacks[panel].discoverModels();
|
|
8605
|
+
} catch {}
|
|
8606
|
+
};
|
|
8119
8607
|
const dropDeadModels = (rows, panel) => {
|
|
8120
8608
|
const wire = wireState[panel];
|
|
8121
8609
|
if (!wire.resolved) return rows;
|
|
@@ -8423,16 +8911,9 @@ function apply(ctx, config) {
|
|
|
8423
8911
|
wire.callableKeys.add(model.name.trim().toLowerCase());
|
|
8424
8912
|
}
|
|
8425
8913
|
wire.resolved = merged.length > 0;
|
|
8426
|
-
|
|
8427
|
-
wire.
|
|
8428
|
-
|
|
8429
|
-
const target = {
|
|
8430
|
-
configName: model.wireConfigName,
|
|
8431
|
-
...model.wireFunction === void 0 ? {} : { function: model.wireFunction }
|
|
8432
|
-
};
|
|
8433
|
-
wire.byId.set(model.id, target);
|
|
8434
|
-
wire.byName.set(model.name.trim().toLowerCase(), target);
|
|
8435
|
-
}
|
|
8914
|
+
const wireTargets = wireTargetsOf(merged);
|
|
8915
|
+
wire.byId = wireTargets.byId;
|
|
8916
|
+
wire.byName = wireTargets.byName;
|
|
8436
8917
|
latestDiscovery.length = 0;
|
|
8437
8918
|
latestDiscovery.push(...merged);
|
|
8438
8919
|
return merged;
|
|
@@ -8470,6 +8951,18 @@ function apply(ctx, config) {
|
|
|
8470
8951
|
routeOf: (provider) => routeOfTraeProvider(provider) ?? "solo"
|
|
8471
8952
|
};
|
|
8472
8953
|
}
|
|
8954
|
+
/**
|
|
8955
|
+
* Mirror each region's Trae login into the plugin's OWN copy as soon as the
|
|
8956
|
+
* plugin loads.
|
|
8957
|
+
*
|
|
8958
|
+
* `accounts()` performs the mirror, so a card read creates the copy — but only
|
|
8959
|
+
* for the tab the user actually opens. Signing in to Trae and never opening
|
|
8960
|
+
* the panel would leave the copy missing until the next visit, which reads as
|
|
8961
|
+
* "the plugin never made a copy". This fire-and-forget pass makes the copies
|
|
8962
|
+
* exist from start-up. Failures are swallowed on purpose: the next card read
|
|
8963
|
+
* retries, and a read-only file system must not break the plugin load.
|
|
8964
|
+
*/
|
|
8965
|
+
for (const panel of TRAE_PANELS) stacks[panel].store.accounts().catch(() => void 0);
|
|
8473
8966
|
/** This panel's saved pool preferences, unwrapped and defaulted to OFF. */
|
|
8474
8967
|
const poolPreferencesOf = (panel) => unwrapVolatileDeep(regionStateOf(current(), panel).pool) ?? {};
|
|
8475
8968
|
/**
|
|
@@ -8767,6 +9260,7 @@ function apply(ctx, config) {
|
|
|
8767
9260
|
};
|
|
8768
9261
|
/** One account's row, through the same bridge and probe the batch uses. */
|
|
8769
9262
|
const testWith = async (target, modelId, probeInputTokens) => {
|
|
9263
|
+
await warmWireMap(panel);
|
|
8770
9264
|
const [row] = await testAllAccounts([target], modelId, (chatFor, credential, id) => {
|
|
8771
9265
|
if (credential === void 0) return Promise.resolve({
|
|
8772
9266
|
id,
|
|
@@ -8817,6 +9311,7 @@ function apply(ctx, config) {
|
|
|
8817
9311
|
return state;
|
|
8818
9312
|
},
|
|
8819
9313
|
test: async (stored, modelId, onRow) => {
|
|
9314
|
+
await warmWireMap(stored);
|
|
8820
9315
|
const probeInputTokens = probeInputTokensOf(stored);
|
|
8821
9316
|
const rows = await testAllAccounts(await poolTargetsOf(stored), modelId, (chatFor, credential, id) => {
|
|
8822
9317
|
if (credential === void 0) return Promise.resolve({
|
|
@@ -8897,9 +9392,17 @@ function apply(ctx, config) {
|
|
|
8897
9392
|
enabledModelIds: (panel) => regionStateOf(current(), panel).enabledModelIds ?? [],
|
|
8898
9393
|
regionEnabled: (panel) => regionEnabled(current(), panel),
|
|
8899
9394
|
discoverModels: (panel, signal) => stacks[panel].discoverModels(signal),
|
|
8900
|
-
probeModels: (panel, ids) =>
|
|
9395
|
+
probeModels: async (panel, ids) => {
|
|
9396
|
+
await warmWireMap(panel);
|
|
9397
|
+
return stacks[panel].probeModels(ids);
|
|
9398
|
+
},
|
|
8901
9399
|
rawDiagnostic: (panel) => stacks[panel].rawDiagnostic(),
|
|
8902
9400
|
creditsOfAccount: (panel, accountId) => stacks[panel].creditsOfAccount(accountId),
|
|
9401
|
+
deleteAccountOf: (panel, accountId) => stacks[panel].store.deleteAccount(accountId),
|
|
9402
|
+
copiesOf: async (panel) => ({
|
|
9403
|
+
path: stacks[panel].store.ownAuthPath(),
|
|
9404
|
+
count: (await stacks[panel].store.accounts()).length
|
|
9405
|
+
}),
|
|
8903
9406
|
accountStateOf: (panel, accountId) => stacks[panel].accountStateOf(accountId),
|
|
8904
9407
|
claimCheckinOf: (panel, accountId) => stacks[panel].claimCheckinOf(accountId),
|
|
8905
9408
|
liveLogin: (panel) => liveLoginOf(panel),
|
|
@@ -9158,4 +9661,4 @@ function apply(ctx, config) {
|
|
|
9158
9661
|
});
|
|
9159
9662
|
}
|
|
9160
9663
|
//#endregion
|
|
9161
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, MAX_PROBE_INPUT_TOKENS, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_ENTERPRISE_PROVIDER, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PANELS, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_PROVIDER_IDS, TRAE_PROVIDER_ID_DISPLAY_NAMES, TRAE_PROVIDER_ROUTES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STATE_DB_FILENAME, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, creditPosition, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, describeTraeFailure, discoveredCatalog, displayNameOfTraeProvider, fallbackModelsFor, followAppLoginEnabled, generationOf, hashTraeRawChatArg, identityHeaders, inject, isEnterpriseAccount, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, panelOfKey, panelOfProvider, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, providerOfPanel, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionCreditPositionOf, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfPanel, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, routeOfTraeProvider, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withContextAdaptation, withTraeRegion };
|
|
9664
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, MAX_PROBE_INPUT_TOKENS, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_ENTERPRISE_PROVIDER, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PANELS, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_PROVIDER_IDS, TRAE_PROVIDER_ID_DISPLAY_NAMES, TRAE_PROVIDER_ROUTES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STATE_DB_FILENAME, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, creditPosition, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, describeTraeFailure, discoveredCatalog, displayNameOfTraeProvider, fallbackModelsFor, followAppLoginEnabled, generationOf, hashTraeRawChatArg, identityHeaders, inject, isEnterpriseAccount, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, panelOfKey, panelOfProvider, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, providerOfPanel, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionCreditPositionOf, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfPanel, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, routeOfTraeProvider, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, wireTargetsOf, withContextAdaptation, withTraeRegion };
|