dsh-connect-trae 3.0.1 → 3.4.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 +198 -0
- package/README.en.md +4 -2
- package/README.md +4 -2
- package/lib/client.js +1051 -378
- package/lib/index.d.ts +148 -7
- package/lib/index.js +453 -56
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -4,11 +4,11 @@ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completio
|
|
|
4
4
|
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
6
6
|
import { readFile, rm, stat } from "node:fs/promises";
|
|
7
|
-
import { createDecipheriv, createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
7
|
+
import { createDecipheriv, createHash, randomBytes, randomUUID, sign, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
10
10
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
11
|
-
import { cpus, homedir, hostname, release } from "node:os";
|
|
11
|
+
import { cpus, homedir, hostname, release, userInfo } from "node:os";
|
|
12
12
|
import { createServer } from "node:http";
|
|
13
13
|
import { Readable } from "node:stream";
|
|
14
14
|
import { execFile } from "node:child_process";
|
|
@@ -478,8 +478,19 @@ const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
|
|
|
478
478
|
*/
|
|
479
479
|
const TRAE_ACCOUNT_CREDITS_PATH = "/plugins/dsh-connect-trae/account-credits";
|
|
480
480
|
/**
|
|
481
|
+
* Per-account status (check-in / credits / subscription) for the account table.
|
|
482
|
+
*
|
|
483
|
+
* Each row of the account table reads its OWN state through this route — a CN
|
|
484
|
+
* row carries its check-in state and balance, an ai row its subscription.
|
|
485
|
+
* POST with the accountId in the body: one upstream read per account, paid
|
|
486
|
+
* only when the table is actually shown.
|
|
487
|
+
*/
|
|
488
|
+
const TRAE_ACCOUNT_STATUS_PATH = "/plugins/dsh-connect-trae/account-status";
|
|
489
|
+
/**
|
|
481
490
|
* Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
|
|
482
|
-
* route in this plugin that changes upstream account state.
|
|
491
|
+
* route in this plugin that changes upstream account state. The accountId in
|
|
492
|
+
* the body names WHICH account to claim for, so the button can sit in each
|
|
493
|
+
* account row.
|
|
483
494
|
*/
|
|
484
495
|
const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
|
|
485
496
|
/** Query parameter naming the region a card request addresses. */
|
|
@@ -1636,26 +1647,35 @@ var TraeCredentialStore = class {
|
|
|
1636
1647
|
};
|
|
1637
1648
|
//#endregion
|
|
1638
1649
|
//#region src/refresh.ts
|
|
1639
|
-
|
|
1650
|
+
/** Path of the current token-exchange endpoint, relative to the credential host. */
|
|
1651
|
+
const TRAE_EXCHANGE_TOKEN_PATH = "/trae/api/v3/oauth/ExchangeToken";
|
|
1652
|
+
/** Path of the superseded endpoint; only reachable as the fallback. */
|
|
1653
|
+
const TRAE_LEGACY_EXCHANGE_TOKEN_PATH = "/cloudide/api/v3/trae/oauth/ExchangeToken";
|
|
1654
|
+
/** Shared desktop ClientID; the current contract also uses it for `cn` / `sg`. */
|
|
1655
|
+
const DESKTOP_CLIENT_ID = "ono9krqynydwx5";
|
|
1656
|
+
/** SOLO ClientID; every SOLO package uses it, CN and international alike. */
|
|
1657
|
+
const SOLO_CLIENT_ID = "en1oxy7wnw8j9n";
|
|
1658
|
+
/** The current contract: `/trae/api/v3`, per-edition ClientID, always proofed. */
|
|
1659
|
+
const CURRENT_CONTRACT = {
|
|
1640
1660
|
cn: {
|
|
1641
|
-
path:
|
|
1642
|
-
clientId:
|
|
1643
|
-
|
|
1661
|
+
path: TRAE_EXCHANGE_TOKEN_PATH,
|
|
1662
|
+
clientId: DESKTOP_CLIENT_ID,
|
|
1663
|
+
platformCode: "IDE_PC"
|
|
1644
1664
|
},
|
|
1645
1665
|
sg: {
|
|
1646
|
-
path:
|
|
1647
|
-
clientId:
|
|
1648
|
-
|
|
1666
|
+
path: TRAE_EXCHANGE_TOKEN_PATH,
|
|
1667
|
+
clientId: DESKTOP_CLIENT_ID,
|
|
1668
|
+
platformCode: "IDE_PC"
|
|
1649
1669
|
},
|
|
1650
1670
|
solo: {
|
|
1651
|
-
path:
|
|
1652
|
-
clientId:
|
|
1653
|
-
|
|
1671
|
+
path: TRAE_EXCHANGE_TOKEN_PATH,
|
|
1672
|
+
clientId: SOLO_CLIENT_ID,
|
|
1673
|
+
platformCode: "SOLO_PC"
|
|
1654
1674
|
},
|
|
1655
1675
|
"solo-sg": {
|
|
1656
|
-
path:
|
|
1657
|
-
clientId:
|
|
1658
|
-
|
|
1676
|
+
path: TRAE_EXCHANGE_TOKEN_PATH,
|
|
1677
|
+
clientId: SOLO_CLIENT_ID,
|
|
1678
|
+
platformCode: "SOLO_PC"
|
|
1659
1679
|
}
|
|
1660
1680
|
};
|
|
1661
1681
|
function normalizeHost(host) {
|
|
@@ -1663,49 +1683,173 @@ function normalizeHost(host) {
|
|
|
1663
1683
|
if (value === "") throw new Error("Trae refresh host is missing");
|
|
1664
1684
|
return value.replace(/\/$/, "");
|
|
1665
1685
|
}
|
|
1686
|
+
/** `OSInfo` value the official client sends (a bare platform word). */
|
|
1687
|
+
function osInfo(platform = process.platform) {
|
|
1688
|
+
return platform === "darwin" ? "mac" : platform === "win32" ? "windows" : platform;
|
|
1689
|
+
}
|
|
1666
1690
|
/**
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1691
|
+
* Build the exact string the official client signs.
|
|
1692
|
+
*
|
|
1693
|
+
* The separator is a NEWLINE, not a space: the shipped bundle joins the six
|
|
1694
|
+
* fields with `.join("\n")`. This is load-bearing rather than cosmetic — a
|
|
1695
|
+
* space-joined message of identical fields is rejected with `20403 Token device
|
|
1696
|
+
* not match`, which is indistinguishable from a wrong key and sends debugging
|
|
1697
|
+
* in entirely the wrong direction.
|
|
1671
1698
|
*/
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1699
|
+
function traeDeviceProofMessage(input) {
|
|
1700
|
+
return [
|
|
1701
|
+
"POST",
|
|
1702
|
+
input.path,
|
|
1703
|
+
input.clientId,
|
|
1704
|
+
input.refreshToken,
|
|
1705
|
+
String(input.timestamp),
|
|
1706
|
+
input.nonce
|
|
1707
|
+
].join("\n");
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Sign a device proof with the install's own private key.
|
|
1711
|
+
*
|
|
1712
|
+
* `timestamp`/`nonce` are injectable so tests can pin a deterministic signature;
|
|
1713
|
+
* the defaults are the whole-second timestamp and 16 random bytes the official
|
|
1714
|
+
* client uses.
|
|
1715
|
+
*/
|
|
1716
|
+
function signTraeDeviceProof(input) {
|
|
1717
|
+
const timestamp = input.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
1718
|
+
const nonce = input.nonce ?? randomBytes(16).toString("hex");
|
|
1719
|
+
const message = traeDeviceProofMessage({
|
|
1720
|
+
path: input.path,
|
|
1721
|
+
clientId: input.clientId,
|
|
1722
|
+
refreshToken: input.refreshToken,
|
|
1723
|
+
timestamp,
|
|
1724
|
+
nonce
|
|
1725
|
+
});
|
|
1726
|
+
return {
|
|
1727
|
+
Signature: sign("sha256", Buffer.from(message), input.privateKeyPEM).toString("base64"),
|
|
1728
|
+
Timestamp: timestamp,
|
|
1729
|
+
Nonce: nonce
|
|
1681
1730
|
};
|
|
1682
|
-
|
|
1731
|
+
}
|
|
1732
|
+
/** The `DeviceInfo` block the current contract sends. */
|
|
1733
|
+
function traeDeviceInfoBody(contract, device, platform) {
|
|
1734
|
+
const os = osInfo(platform);
|
|
1735
|
+
return {
|
|
1683
1736
|
DeviceID: device.deviceId,
|
|
1684
1737
|
MachineID: device.machineId,
|
|
1685
|
-
PlatformCode:
|
|
1738
|
+
PlatformCode: contract.platformCode,
|
|
1686
1739
|
DeviceType: "PC",
|
|
1687
|
-
DeviceName:
|
|
1740
|
+
...device.deviceName === void 0 ? {} : { DeviceName: device.deviceName },
|
|
1741
|
+
...device.deviceModel === void 0 ? {} : { DeviceModel: device.deviceModel },
|
|
1742
|
+
...device.appVersion === void 0 ? {} : { ClientVersion: device.appVersion },
|
|
1743
|
+
...device.deviceKeyPair === void 0 ? {} : { DevicePublicKey: device.deviceKeyPair.publicKeyPEM },
|
|
1744
|
+
...device.deviceBrand === void 0 ? {} : { DeviceBrand: device.deviceBrand },
|
|
1745
|
+
...device.deviceCpu === void 0 ? {} : { DeviceCPU: device.deviceCpu },
|
|
1746
|
+
OSInfo: os,
|
|
1747
|
+
OSVersion: device.osVersion ?? `${os} ${hostname()}`
|
|
1688
1748
|
};
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1749
|
+
}
|
|
1750
|
+
/** The current contract's complete request body. */
|
|
1751
|
+
function currentBody(credential, contract, device) {
|
|
1752
|
+
return {
|
|
1753
|
+
ClientID: contract.clientId,
|
|
1754
|
+
ClientSecret: "",
|
|
1755
|
+
RefreshToken: credential.refreshToken,
|
|
1756
|
+
DeviceInfo: traeDeviceInfoBody(contract, device),
|
|
1757
|
+
DeviceProof: signTraeDeviceProof({
|
|
1758
|
+
path: contract.path,
|
|
1759
|
+
clientId: contract.clientId,
|
|
1760
|
+
refreshToken: credential.refreshToken ?? "",
|
|
1761
|
+
privateKeyPEM: device.deviceKeyPair.privateKeyPEM
|
|
1762
|
+
}),
|
|
1763
|
+
...device.appVersion === void 0 ? {} : { IDEVersion: device.appVersion }
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* The superseded shape, kept as a fallback for installs the current contract
|
|
1768
|
+
* cannot serve: a machine whose `storage.json` carries no device keypair (an
|
|
1769
|
+
* older install that has never refreshed on the new contract, or a CLI-only
|
|
1770
|
+
* home) has nothing to sign a proof with.
|
|
1771
|
+
*
|
|
1772
|
+
* The ClientID split is the SAME one the current contract uses, and that is not
|
|
1773
|
+
* a coincidence — it is enforced by the OAuth CLIENT, not by the endpoint: a
|
|
1774
|
+
* SOLO-package refresh token is refused by the DESKTOP client with `10101
|
|
1775
|
+
* refresh token is not matched to the client`, and vice versa, on this path
|
|
1776
|
+
* exactly as on the current one. Verified 2026-10-11 across all four editions:
|
|
1777
|
+
* each answers 200 here with its own ClientID and 400 with the other one.
|
|
1778
|
+
*
|
|
1779
|
+
* The pre-3.1.0 code hardcoded the desktop ClientID on this path, which is why
|
|
1780
|
+
* an international SOLO account had no working fallback at all.
|
|
1781
|
+
*/
|
|
1782
|
+
function legacyBody(credential, clientId) {
|
|
1783
|
+
return {
|
|
1784
|
+
ClientID: clientId,
|
|
1785
|
+
ClientSecret: "-",
|
|
1786
|
+
RefreshToken: credential.refreshToken,
|
|
1787
|
+
UserID: credential.userId
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
/** Parse one ExchangeToken response into an outcome, or throw with the reason. */
|
|
1791
|
+
function outcomeOf(payload, label) {
|
|
1792
|
+
const result = payload.Result;
|
|
1697
1793
|
const accessToken = typeof result?.["Token"] === "string" ? result["Token"] : "";
|
|
1698
|
-
if (accessToken === "") throw new Error(
|
|
1794
|
+
if (accessToken === "") throw new Error(`${label} returned no token`);
|
|
1699
1795
|
const expiry = result?.["TokenExpireAt"];
|
|
1700
1796
|
const expiresAtMs = typeof expiry === "number" ? expiry : typeof expiry === "string" ? Date.parse(expiry) : NaN;
|
|
1701
|
-
if (!Number.isFinite(expiresAtMs)) throw new Error(
|
|
1797
|
+
if (!Number.isFinite(expiresAtMs)) throw new Error(`${label} returned an invalid expiry`);
|
|
1702
1798
|
const refreshToken = typeof result?.["RefreshToken"] === "string" && result["RefreshToken"] !== "" ? result["RefreshToken"] : void 0;
|
|
1799
|
+
const refreshExpiry = result?.["RefreshExpireAt"];
|
|
1800
|
+
const parsedRefreshExpiry = typeof refreshExpiry === "string" ? Date.parse(refreshExpiry) : NaN;
|
|
1801
|
+
const refreshExpiresAtMs = typeof refreshExpiry === "number" && Number.isFinite(refreshExpiry) && refreshExpiry > 0 ? refreshExpiry : Number.isFinite(parsedRefreshExpiry) ? parsedRefreshExpiry : void 0;
|
|
1703
1802
|
return {
|
|
1704
1803
|
accessToken,
|
|
1705
1804
|
...refreshToken === void 0 ? {} : { refreshToken },
|
|
1706
|
-
expiresAtMs
|
|
1805
|
+
expiresAtMs,
|
|
1806
|
+
...refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs }
|
|
1707
1807
|
};
|
|
1708
1808
|
}
|
|
1809
|
+
/** POST one refresh body and parse it, labelling failures with the path tried. */
|
|
1810
|
+
async function postExchangeToken(host, path, body, signal, label) {
|
|
1811
|
+
const response = await fetch(`${normalizeHost(host)}${path}`, {
|
|
1812
|
+
method: "POST",
|
|
1813
|
+
headers: { "Content-Type": "application/json" },
|
|
1814
|
+
body: JSON.stringify(body),
|
|
1815
|
+
signal: signal ?? AbortSignal.timeout(3e4)
|
|
1816
|
+
});
|
|
1817
|
+
if (!response.ok) throw new Error(`${label} failed (http ${response.status})`);
|
|
1818
|
+
return outcomeOf(await response.json(), label);
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Exchange a refresh token for a fresh access token.
|
|
1822
|
+
*
|
|
1823
|
+
* The CURRENT contract is attempted first whenever it is possible at all — it is
|
|
1824
|
+
* what the shipped clients use. It needs the install's device keypair; without
|
|
1825
|
+
* one there is nothing to sign, so the request is skipped rather than sent to be
|
|
1826
|
+
* rejected with `20405`.
|
|
1827
|
+
*
|
|
1828
|
+
* The SUPERSEDED contract is then tried as a fallback, which is what keeps older
|
|
1829
|
+
* installs and CLI-only machines working; it carries no device block but the
|
|
1830
|
+
* SAME per-edition ClientID, because that split belongs to the OAuth client
|
|
1831
|
+
* rather than to the endpoint (see {@link legacyBody}). When both are impossible
|
|
1832
|
+
* or both fail, the error names every attempt so a "sign in again" verdict is
|
|
1833
|
+
* diagnosable rather than mysterious.
|
|
1834
|
+
*/
|
|
1835
|
+
async function refreshTraeCredential(credential, signal, device) {
|
|
1836
|
+
const contract = CURRENT_CONTRACT[credential.edition];
|
|
1837
|
+
if (contract === void 0) throw new Error(`Trae ${credential.edition} refresh contract is not verified`);
|
|
1838
|
+
if (credential.refreshToken === void 0) throw new Error("Trae refresh token is missing");
|
|
1839
|
+
const attempts = [];
|
|
1840
|
+
if (device?.deviceKeyPair === void 0) attempts.push(`${contract.path}: no device keypair in this install's storage`);
|
|
1841
|
+
else try {
|
|
1842
|
+
return await postExchangeToken(credential.host, contract.path, currentBody(credential, contract, device), signal, "Trae token refresh");
|
|
1843
|
+
} catch (error) {
|
|
1844
|
+
attempts.push(`${contract.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1845
|
+
}
|
|
1846
|
+
try {
|
|
1847
|
+
return await postExchangeToken(credential.host, TRAE_LEGACY_EXCHANGE_TOKEN_PATH, legacyBody(credential, contract.clientId), signal, "Trae token refresh (legacy)");
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
attempts.push(`${TRAE_LEGACY_EXCHANGE_TOKEN_PATH}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1850
|
+
}
|
|
1851
|
+
throw new Error(`Trae token refresh failed — ${attempts.join("; ")}`);
|
|
1852
|
+
}
|
|
1709
1853
|
//#endregion
|
|
1710
1854
|
//#region src/identity.ts
|
|
1711
1855
|
function nonEmpty(value) {
|
|
@@ -1716,6 +1860,33 @@ function deviceCenterId(storage) {
|
|
|
1716
1860
|
const ids = Object.keys(storage).filter((key) => key.startsWith(prefix)).map((key) => key.slice(25)).filter(Boolean);
|
|
1717
1861
|
return ids.length === 1 ? ids[0] : void 0;
|
|
1718
1862
|
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Read the install's device keypair out of `storage.json`.
|
|
1865
|
+
*
|
|
1866
|
+
* Tolerant on purpose: an older install (or one that has never refreshed on the
|
|
1867
|
+
* new contract) simply has no keypair, and the caller must still be able to
|
|
1868
|
+
* resolve an identity for the chat/usage paths — only a refresh then degrades.
|
|
1869
|
+
* A malformed entry is treated as absent rather than failing the read.
|
|
1870
|
+
*/
|
|
1871
|
+
function deviceKeyPairOf(storage) {
|
|
1872
|
+
const key = Object.keys(storage).find((name) => name.startsWith("iCubeAuthInfo://icube-dc:"));
|
|
1873
|
+
if (key === void 0) return void 0;
|
|
1874
|
+
const encoded = storage[key];
|
|
1875
|
+
if (typeof encoded !== "string" || encoded.trim() === "") return void 0;
|
|
1876
|
+
try {
|
|
1877
|
+
const plaintext = parseTraeAuthValue(encoded);
|
|
1878
|
+
if (typeof plaintext !== "object" || plaintext === null || Array.isArray(plaintext)) return void 0;
|
|
1879
|
+
const value = plaintext;
|
|
1880
|
+
const privateKeyPEM = nonEmpty(value["privateKeyPEM"]);
|
|
1881
|
+
const publicKeyPEM = nonEmpty(value["publicKeyPEM"]);
|
|
1882
|
+
return privateKeyPEM === void 0 || publicKeyPEM === void 0 ? void 0 : {
|
|
1883
|
+
privateKeyPEM,
|
|
1884
|
+
publicKeyPEM
|
|
1885
|
+
};
|
|
1886
|
+
} catch {
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1719
1890
|
/** Read stable identity from Trae-owned files without generating impersonated IDs. */
|
|
1720
1891
|
async function readTraeIdentity(candidate, options = {}) {
|
|
1721
1892
|
const platform = options.platform ?? process.platform;
|
|
@@ -1754,6 +1925,15 @@ async function readTraeIdentity(candidate, options = {}) {
|
|
|
1754
1925
|
const deviceBrand = platform === "darwin" ? nonEmpty(env["TRAE_DEVICE_BRAND"]) : void 0;
|
|
1755
1926
|
const deviceCpu = cpus()[0]?.model.split(" ")[0];
|
|
1756
1927
|
const osVersion = `${platform === "darwin" ? "macOS" : platform === "win32" ? "Windows" : platform} ${release()}`;
|
|
1928
|
+
const deviceName = (() => {
|
|
1929
|
+
try {
|
|
1930
|
+
return nonEmpty(userInfo().username);
|
|
1931
|
+
} catch {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
})();
|
|
1935
|
+
const deviceModel = nonEmpty(env["TRAE_DEVICE_MODEL"]);
|
|
1936
|
+
const deviceKeyPair = deviceKeyPairOf(storage);
|
|
1757
1937
|
return {
|
|
1758
1938
|
edition: candidate.edition,
|
|
1759
1939
|
machineId,
|
|
@@ -1762,6 +1942,9 @@ async function readTraeIdentity(candidate, options = {}) {
|
|
|
1762
1942
|
...buildVersion === void 0 ? {} : { buildVersion },
|
|
1763
1943
|
...deviceBrand === void 0 ? {} : { deviceBrand },
|
|
1764
1944
|
...deviceCpu === void 0 ? {} : { deviceCpu },
|
|
1945
|
+
...deviceName === void 0 ? {} : { deviceName },
|
|
1946
|
+
...deviceModel === void 0 ? {} : { deviceModel },
|
|
1947
|
+
...deviceKeyPair === void 0 ? {} : { deviceKeyPair },
|
|
1765
1948
|
osVersion,
|
|
1766
1949
|
platform
|
|
1767
1950
|
};
|
|
@@ -3920,6 +4103,21 @@ const TRAE_PAY_BASE = "https://api.trae.cn";
|
|
|
3920
4103
|
function asNumber(value) {
|
|
3921
4104
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3922
4105
|
}
|
|
4106
|
+
/**
|
|
4107
|
+
* Normalize an upstream epoch to milliseconds.
|
|
4108
|
+
*
|
|
4109
|
+
* The pack's `end_time` arrives in SECONDS while {@link TraeUsagePack.endTimeMs}
|
|
4110
|
+
* names milliseconds. Measured against a live CN account: `1788340660` is
|
|
4111
|
+
* 2026-09-02, and reading it as milliseconds would place the pack in 1970 — a
|
|
4112
|
+
* date that sorts ahead of every real one and reads as "long expired". The
|
|
4113
|
+
* magnitude test is the same one `auth.ts` applies to stored expiry fields, so
|
|
4114
|
+
* an upstream that ever switches to milliseconds is passed through instead of
|
|
4115
|
+
* being multiplied into the year 57000.
|
|
4116
|
+
*/
|
|
4117
|
+
function epochToMs(value) {
|
|
4118
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) return 0;
|
|
4119
|
+
return value > 0xe8d4a51000 ? value : value * 1e3;
|
|
4120
|
+
}
|
|
3923
4121
|
function parseUsageSnapshot(payload) {
|
|
3924
4122
|
const summaryRaw = payload["usage_summary"];
|
|
3925
4123
|
const summary = {
|
|
@@ -3943,7 +4141,7 @@ function parseUsageSnapshot(payload) {
|
|
|
3943
4141
|
packs.push({
|
|
3944
4142
|
displayDesc: typeof pack["display_desc"] === "string" ? pack["display_desc"] : "",
|
|
3945
4143
|
entitlementId: typeof base?.["entitlement_id"] === "string" ? base["entitlement_id"] : "",
|
|
3946
|
-
endTimeMs: asNumber(base?.["end_time"])
|
|
4144
|
+
endTimeMs: epochToMs(asNumber(base?.["end_time"])),
|
|
3947
4145
|
currency: asNumber(base?.["currency"]) ?? 0,
|
|
3948
4146
|
...availableEndpoint === void 0 ? {} : { availableEndpoint },
|
|
3949
4147
|
...creditsLimit === void 0 ? {} : { creditsLimit },
|
|
@@ -4220,6 +4418,32 @@ async function readModelTestBody(req) {
|
|
|
4220
4418
|
}
|
|
4221
4419
|
return ids;
|
|
4222
4420
|
}
|
|
4421
|
+
/**
|
|
4422
|
+
* Read the accountId a per-account route needs from a POST body. A bounded,
|
|
4423
|
+
* single-string reader — the same families of guards the model-test body uses,
|
|
4424
|
+
* because the claim route here is a state-changing endpoint.
|
|
4425
|
+
*/
|
|
4426
|
+
async function readAccountBody(req) {
|
|
4427
|
+
const chunks = [];
|
|
4428
|
+
let size = 0;
|
|
4429
|
+
for await (const chunk of req) {
|
|
4430
|
+
const buffer = chunk;
|
|
4431
|
+
size += buffer.byteLength;
|
|
4432
|
+
if (size > 16384) throw new Error("request body too large");
|
|
4433
|
+
chunks.push(buffer);
|
|
4434
|
+
}
|
|
4435
|
+
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
4436
|
+
if (text === "") throw new Error("empty body");
|
|
4437
|
+
let parsed;
|
|
4438
|
+
try {
|
|
4439
|
+
parsed = JSON.parse(text);
|
|
4440
|
+
} catch {
|
|
4441
|
+
throw new Error("invalid JSON body");
|
|
4442
|
+
}
|
|
4443
|
+
const accountId = typeof parsed === "object" && parsed !== null ? parsed["accountId"] : void 0;
|
|
4444
|
+
if (typeof accountId !== "string" || accountId.trim() === "") throw new Error("body must carry an accountId string");
|
|
4445
|
+
return accountId.trim();
|
|
4446
|
+
}
|
|
4223
4447
|
/** Loopback browser origins only; other devices are refused until trusted origins exist. */
|
|
4224
4448
|
function loopbackOrigin(req) {
|
|
4225
4449
|
const origin = req.headers.origin;
|
|
@@ -4232,6 +4456,17 @@ function loopbackOrigin(req) {
|
|
|
4232
4456
|
}
|
|
4233
4457
|
}
|
|
4234
4458
|
/**
|
|
4459
|
+
* Whether a pack's expiry is one the card can actually use.
|
|
4460
|
+
*
|
|
4461
|
+
* One predicate, shared by the per-pack projection and the bucket summary, so
|
|
4462
|
+
* the two can never disagree about which packs count as dated. `0` and the
|
|
4463
|
+
* non-finite values are excluded: a `0` here would read as 1970 and sort ahead
|
|
4464
|
+
* of every real date, and `NaN`/`Infinity` are not dates at all.
|
|
4465
|
+
*/
|
|
4466
|
+
function usableExpiry(endTimeMs) {
|
|
4467
|
+
return typeof endTimeMs === "number" && Number.isFinite(endTimeMs) && endTimeMs > 0;
|
|
4468
|
+
}
|
|
4469
|
+
/**
|
|
4235
4470
|
* Map the credit snapshot to the card's compact credit document.
|
|
4236
4471
|
*
|
|
4237
4472
|
* Exported so the "is another account usable?" probe (issue #25) derives its
|
|
@@ -4239,13 +4474,14 @@ function loopbackOrigin(req) {
|
|
|
4239
4474
|
* — a second implementation of the Work/general split would be free to disagree
|
|
4240
4475
|
* with the number shown next to it.
|
|
4241
4476
|
*/
|
|
4242
|
-
function toCredits(snapshot) {
|
|
4477
|
+
function toCredits(snapshot, nowMs = Date.now()) {
|
|
4243
4478
|
const { totalAmount, consumedAmount } = snapshot.summary;
|
|
4244
4479
|
const credit = (value) => Math.round(value * 1e4) / 1e4;
|
|
4245
4480
|
const accounts = snapshot.packs.map((pack) => ({
|
|
4246
4481
|
displayDesc: pack.displayDesc,
|
|
4247
4482
|
remain: credit(Math.max(0, (pack.creditsLimit ?? 0) - (pack.consumedCredits ?? 0))),
|
|
4248
|
-
size: pack.creditsLimit ?? 0
|
|
4483
|
+
size: pack.creditsLimit ?? 0,
|
|
4484
|
+
...usableExpiry(pack.endTimeMs) ? { expiresAtMs: pack.endTimeMs } : {}
|
|
4249
4485
|
}));
|
|
4250
4486
|
const remaining = (endpoint) => snapshot.packs.filter((pack) => pack.availableEndpoint === endpoint).reduce((sum, pack) => credit(sum + Math.max(0, (pack.creditsLimit ?? 0) - (pack.consumedCredits ?? 0))), 0);
|
|
4251
4487
|
return {
|
|
@@ -4254,9 +4490,74 @@ function toCredits(snapshot) {
|
|
|
4254
4490
|
available: totalAmount - consumedAmount,
|
|
4255
4491
|
workAvailable: remaining(1),
|
|
4256
4492
|
generalAvailable: remaining(0),
|
|
4257
|
-
accounts
|
|
4493
|
+
accounts,
|
|
4494
|
+
...summarisePackExpiry(accounts, nowMs)
|
|
4258
4495
|
};
|
|
4259
4496
|
}
|
|
4497
|
+
/** Empty bucket, used whenever no pack qualifies. */
|
|
4498
|
+
const NO_EXPIRING_CREDIT = {
|
|
4499
|
+
total: 0,
|
|
4500
|
+
packs: 0
|
|
4501
|
+
};
|
|
4502
|
+
/**
|
|
4503
|
+
* Sum the credit lapsing inside `horizonMs` of `nowMs`.
|
|
4504
|
+
*
|
|
4505
|
+
* Only positive balances count: a lapsed pack with nothing left is not a loss,
|
|
4506
|
+
* and including it would make every long-lived account look permanently at
|
|
4507
|
+
* risk. Only *future* expiries count too — see {@link expiredPacks}.
|
|
4508
|
+
*/
|
|
4509
|
+
function expiringWithin(packs, nowMs, horizonMs) {
|
|
4510
|
+
return sumExpiring(packs, (expiresAt) => expiresAt > nowMs && expiresAt - nowMs <= horizonMs);
|
|
4511
|
+
}
|
|
4512
|
+
/**
|
|
4513
|
+
* Sum the credit whose pack has already lapsed but still carries a balance.
|
|
4514
|
+
*
|
|
4515
|
+
* Kept apart from {@link expiringWithin} because a lapsed pack satisfies every
|
|
4516
|
+
* forward-looking horizon at once (`expiresAt - now` is negative, hence "within
|
|
4517
|
+
* 24 hours"), which would make a long-dead pack look permanently imminent.
|
|
4518
|
+
*/
|
|
4519
|
+
function expiredPacks(packs, nowMs) {
|
|
4520
|
+
return sumExpiring(packs, (expiresAt) => expiresAt <= nowMs);
|
|
4521
|
+
}
|
|
4522
|
+
/** Shared accumulator over packs whose expiry passes `matches`. */
|
|
4523
|
+
function sumExpiring(packs, matches) {
|
|
4524
|
+
let total = 0;
|
|
4525
|
+
let count = 0;
|
|
4526
|
+
let earliestExpiresAtMs;
|
|
4527
|
+
for (const pack of packs) {
|
|
4528
|
+
const expiresAt = pack.expiresAtMs;
|
|
4529
|
+
if (!usableExpiry(expiresAt) || pack.remain <= 0) continue;
|
|
4530
|
+
if (!matches(expiresAt)) continue;
|
|
4531
|
+
total += pack.remain;
|
|
4532
|
+
count += 1;
|
|
4533
|
+
if (earliestExpiresAtMs === void 0 || expiresAt < earliestExpiresAtMs) earliestExpiresAtMs = expiresAt;
|
|
4534
|
+
}
|
|
4535
|
+
if (count === 0) return NO_EXPIRING_CREDIT;
|
|
4536
|
+
return {
|
|
4537
|
+
total,
|
|
4538
|
+
packs: count,
|
|
4539
|
+
...earliestExpiresAtMs === void 0 ? {} : { earliestExpiresAtMs }
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
/**
|
|
4543
|
+
* Bucket the card's credit by how soon it lapses.
|
|
4544
|
+
*
|
|
4545
|
+
* Returns no `expiry` at all when not one pack carried a usable end time, so a
|
|
4546
|
+
* caller can tell "nothing is expiring" apart from "we were never told when
|
|
4547
|
+
* anything expires" — the second must not render as reassurance. Uses the same
|
|
4548
|
+
* {@link usableExpiry} predicate as the per-pack projection, so a summary is
|
|
4549
|
+
* never produced from a set of dates the card itself would not carry.
|
|
4550
|
+
*/
|
|
4551
|
+
function summarisePackExpiry(accounts, nowMs) {
|
|
4552
|
+
if (!accounts.some((pack) => usableExpiry(pack.expiresAtMs))) return {};
|
|
4553
|
+
const DAY = 864e5;
|
|
4554
|
+
return { expiry: {
|
|
4555
|
+
within24h: expiringWithin(accounts, nowMs, DAY),
|
|
4556
|
+
within3d: expiringWithin(accounts, nowMs, 3 * DAY),
|
|
4557
|
+
within7d: expiringWithin(accounts, nowMs, 7 * DAY),
|
|
4558
|
+
expired: expiredPacks(accounts, nowMs)
|
|
4559
|
+
} };
|
|
4560
|
+
}
|
|
4260
4561
|
/** Map the check-in answer to the card's compact document. */
|
|
4261
4562
|
function toCheckin(status) {
|
|
4262
4563
|
return {
|
|
@@ -4471,6 +4772,33 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
4471
4772
|
}
|
|
4472
4773
|
}
|
|
4473
4774
|
});
|
|
4775
|
+
/**
|
|
4776
|
+
* Per-account status for one row of the card's account table (CN:
|
|
4777
|
+
* check-in + balances; ai: subscription). Read-only, one upstream call per
|
|
4778
|
+
* account, fetched once per table render. `accountId` rides in the query
|
|
4779
|
+
* (a GET, like every other read-only sibling). Absent `accountStateOf`
|
|
4780
|
+
* the route answers 503 so the table degrades to bare rows.
|
|
4781
|
+
*/
|
|
4782
|
+
const disposeAccountStatus = ctx.webServer.register({
|
|
4783
|
+
kind: "exact",
|
|
4784
|
+
path: TRAE_ACCOUNT_STATUS_PATH,
|
|
4785
|
+
handler: async (req, res) => {
|
|
4786
|
+
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
4787
|
+
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
4788
|
+
if (deps.accountStateOf === void 0) return json(res, 503, { error: "account status unavailable" });
|
|
4789
|
+
const region = requestRegion(req, res);
|
|
4790
|
+
if (region === void 0) return;
|
|
4791
|
+
const accountId = new URL(req.url ?? "/", "http://localhost").searchParams.get("accountId");
|
|
4792
|
+
if (accountId === null || accountId.trim() === "") return json(res, 400, { error: "missing accountId" });
|
|
4793
|
+
try {
|
|
4794
|
+
const state = await deps.accountStateOf(region, accountId);
|
|
4795
|
+
if (state === void 0) return json(res, 404, { error: "unknown account" });
|
|
4796
|
+
json(res, 200, state);
|
|
4797
|
+
} catch (error) {
|
|
4798
|
+
json(res, 500, { error: safeMessage(error) });
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
});
|
|
4474
4802
|
const disposeAccounts = ctx.webServer.register({
|
|
4475
4803
|
kind: "exact",
|
|
4476
4804
|
path: TRAE_ACCOUNTS_REFRESH_PATH,
|
|
@@ -4566,6 +4894,27 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
4566
4894
|
if (region === void 0) return;
|
|
4567
4895
|
if (region !== "cn") return json(res, 404, { error: "check-in is not available for the international region" });
|
|
4568
4896
|
try {
|
|
4897
|
+
let accountId;
|
|
4898
|
+
try {
|
|
4899
|
+
accountId = await readAccountBody(req);
|
|
4900
|
+
} catch {
|
|
4901
|
+
accountId = void 0;
|
|
4902
|
+
}
|
|
4903
|
+
if (accountId !== void 0) {
|
|
4904
|
+
if (deps.claimCheckinOf === void 0) return json(res, 503, { error: "check-in by account unavailable" });
|
|
4905
|
+
const outcome = await deps.claimCheckinOf(region, accountId);
|
|
4906
|
+
if (outcome === void 0) return json(res, 404, { error: "unknown account" });
|
|
4907
|
+
const { claim, checkin: after } = outcome;
|
|
4908
|
+
const deviceClaimed = claim.code === CHECKIN_DEVICE_ALREADY_CLAIMED;
|
|
4909
|
+
return json(res, 200, {
|
|
4910
|
+
claimed: claim.claimed,
|
|
4911
|
+
alreadyCheckedIn: after.checkedIn,
|
|
4912
|
+
...deviceClaimed ? { deviceCheckedIn: true } : {},
|
|
4913
|
+
code: claim.code,
|
|
4914
|
+
message: claim.message,
|
|
4915
|
+
checkin: toCheckin(after)
|
|
4916
|
+
});
|
|
4917
|
+
}
|
|
4569
4918
|
const client = deps.client(region);
|
|
4570
4919
|
const current = await client.checkinStatus();
|
|
4571
4920
|
if (!current.enabled) return json(res, 409, { error: "check-in is not enabled for this account" });
|
|
@@ -4605,6 +4954,7 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
4605
4954
|
disposeModelsTest();
|
|
4606
4955
|
disposeAccounts();
|
|
4607
4956
|
disposeAccountCredits();
|
|
4957
|
+
disposeAccountStatus();
|
|
4608
4958
|
disposeUsage();
|
|
4609
4959
|
};
|
|
4610
4960
|
}, "dsh-connect-trae: Web usage route");
|
|
@@ -5026,16 +5376,27 @@ function apply(ctx, config) {
|
|
|
5026
5376
|
}], hint ?? (region === "ai" ? "solo-sg" : "cn"));
|
|
5027
5377
|
};
|
|
5028
5378
|
/**
|
|
5029
|
-
*
|
|
5030
|
-
*
|
|
5031
|
-
*
|
|
5379
|
+
* The current refresh contract signs a DeviceProof with the install's own
|
|
5380
|
+
* registered keypair, so every field the server reads must come from the
|
|
5381
|
+
* SAME installation the credential belongs to.
|
|
5382
|
+
*
|
|
5383
|
+
* Resolution failures leave `deviceKeyPair` absent, and the refresh then
|
|
5384
|
+
* degrades to the superseded contract instead of sending an unsigned
|
|
5385
|
+
* request the server answers with `20405 Device proof required`.
|
|
5032
5386
|
*/
|
|
5033
5387
|
const refreshDeviceSafe = async () => {
|
|
5034
5388
|
try {
|
|
5035
5389
|
const value = await identity();
|
|
5036
5390
|
return {
|
|
5037
5391
|
deviceId: value.deviceId,
|
|
5038
|
-
machineId: value.machineId
|
|
5392
|
+
machineId: value.machineId,
|
|
5393
|
+
...value.deviceName === void 0 ? {} : { deviceName: value.deviceName },
|
|
5394
|
+
...value.deviceModel === void 0 ? {} : { deviceModel: value.deviceModel },
|
|
5395
|
+
...value.deviceBrand === void 0 ? {} : { deviceBrand: value.deviceBrand },
|
|
5396
|
+
...value.deviceCpu === void 0 ? {} : { deviceCpu: value.deviceCpu },
|
|
5397
|
+
...value.osVersion === void 0 ? {} : { osVersion: value.osVersion },
|
|
5398
|
+
...value.appVersion === void 0 ? {} : { appVersion: value.appVersion },
|
|
5399
|
+
...value.deviceKeyPair === void 0 ? {} : { deviceKeyPair: value.deviceKeyPair }
|
|
5039
5400
|
};
|
|
5040
5401
|
} catch {
|
|
5041
5402
|
return;
|
|
@@ -5093,16 +5454,17 @@ function apply(ctx, config) {
|
|
|
5093
5454
|
ctx.logger.warn("dsh-connect-trae: Raw gateway unavailable; continuing with native SOLO tool-call channel", error);
|
|
5094
5455
|
}
|
|
5095
5456
|
})();
|
|
5457
|
+
const usageClient = new TraeUsageClient({
|
|
5458
|
+
credential: () => store.resolve(),
|
|
5459
|
+
deviceId: async () => (await identity()).deviceId
|
|
5460
|
+
});
|
|
5096
5461
|
stacks[region] = {
|
|
5097
5462
|
region,
|
|
5098
5463
|
store,
|
|
5099
5464
|
catalog,
|
|
5100
5465
|
shim,
|
|
5101
5466
|
delegating,
|
|
5102
|
-
usageClient
|
|
5103
|
-
credential: () => store.resolve(),
|
|
5104
|
-
deviceId: async () => (await identity()).deviceId
|
|
5105
|
-
}),
|
|
5467
|
+
usageClient,
|
|
5106
5468
|
/**
|
|
5107
5469
|
* Credit balance of one named account, WITHOUT rebinding the store
|
|
5108
5470
|
* (issue #25). The card asks this only after it saw the bound account
|
|
@@ -5119,6 +5481,39 @@ function apply(ctx, config) {
|
|
|
5119
5481
|
if (credential === void 0) return void 0;
|
|
5120
5482
|
return toCredits(await new TraeUsageClient({ credential: async () => credential }).snapshot());
|
|
5121
5483
|
},
|
|
5484
|
+
accountStateOf: async (accountId) => {
|
|
5485
|
+
const state = { id: accountId };
|
|
5486
|
+
const selectedCredential = await store.current();
|
|
5487
|
+
const client = selectedCredential !== void 0 && traeAccountId(selectedCredential) === accountId ? usageClient : (() => {
|
|
5488
|
+
const credential = store.credentialOf(accountId);
|
|
5489
|
+
return new TraeUsageClient({
|
|
5490
|
+
credential: async () => credential,
|
|
5491
|
+
deviceId: async () => (await identity()).deviceId
|
|
5492
|
+
});
|
|
5493
|
+
})();
|
|
5494
|
+
if (region === "ai") {
|
|
5495
|
+
state.payStatus = await client.payStatus();
|
|
5496
|
+
return state;
|
|
5497
|
+
}
|
|
5498
|
+
const [snapshot, checkin] = await Promise.allSettled([client.snapshot(), client.checkinStatus()]);
|
|
5499
|
+
if (snapshot.status === "fulfilled") state.credits = toCredits(snapshot.value);
|
|
5500
|
+
if (checkin.status === "fulfilled") state.checkin = toCheckin(checkin.value);
|
|
5501
|
+
return state;
|
|
5502
|
+
},
|
|
5503
|
+
claimCheckinOf: async (accountId) => {
|
|
5504
|
+
const selectedCredential = await store.current();
|
|
5505
|
+
const client = selectedCredential !== void 0 && traeAccountId(selectedCredential) === accountId ? usageClient : (() => {
|
|
5506
|
+
const credential = store.credentialOf(accountId);
|
|
5507
|
+
return new TraeUsageClient({
|
|
5508
|
+
credential: async () => credential,
|
|
5509
|
+
deviceId: async () => (await identity()).deviceId
|
|
5510
|
+
});
|
|
5511
|
+
})();
|
|
5512
|
+
return {
|
|
5513
|
+
claim: await client.claimCheckin(),
|
|
5514
|
+
checkin: await client.checkinStatus()
|
|
5515
|
+
};
|
|
5516
|
+
},
|
|
5122
5517
|
discoverModels: async (signal) => {
|
|
5123
5518
|
const merged = mergeTraeModelSources(await withDirectoryRetry(() => remoteCatalog.fetchModels(signal)), await solo.fetchModels(signal));
|
|
5124
5519
|
wire.callableKeys.clear();
|
|
@@ -5222,6 +5617,8 @@ function apply(ctx, config) {
|
|
|
5222
5617
|
probeModels: (region, ids) => stacks[region].probeModels(ids),
|
|
5223
5618
|
rawDiagnostic: (region) => stacks[region].rawDiagnostic(),
|
|
5224
5619
|
creditsOfAccount: (region, accountId) => stacks[region].creditsOfAccount(accountId),
|
|
5620
|
+
accountStateOf: (region, accountId) => stacks[region].accountStateOf(accountId),
|
|
5621
|
+
claimCheckinOf: (region, accountId) => stacks[region].claimCheckinOf(accountId),
|
|
5225
5622
|
liveLogin: (region) => liveLoginOf(region)
|
|
5226
5623
|
}));
|
|
5227
5624
|
/**
|
|
@@ -5412,4 +5809,4 @@ function apply(ctx, config) {
|
|
|
5412
5809
|
});
|
|
5413
5810
|
}
|
|
5414
5811
|
//#endregion
|
|
5415
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, 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, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
|
5812
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, 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, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|