dsh-codex-subscription 1.10.0 → 1.11.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/AGENTS.md +4 -4
- package/README.md +33 -42
- package/README.zh-CN.md +32 -41
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/lib/client.js +350 -200
- package/lib/index.js +401 -18
- package/package.json +5 -1
package/lib/index.js
CHANGED
|
@@ -11,9 +11,13 @@ import { promisify } from "node:util";
|
|
|
11
11
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
12
12
|
import { openaiCodexProvider as createOpenAICodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
13
13
|
import { createModels } from "@earendil-works/pi-ai";
|
|
14
|
-
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
15
15
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
16
16
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
17
|
+
import { constants } from "node:fs";
|
|
18
|
+
import { lstat, mkdir, open, readFile, rename, rm } from "node:fs/promises";
|
|
19
|
+
import { dirname, join, resolve } from "node:path";
|
|
20
|
+
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
17
21
|
//#region src/credential-store.js
|
|
18
22
|
const PROVIDER$1 = "openai-codex";
|
|
19
23
|
const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
|
|
@@ -725,6 +729,7 @@ const SETTINGS_NAMESPACE = "codex-subscription";
|
|
|
725
729
|
const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
|
|
726
730
|
const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
|
|
727
731
|
const QUICK_QUOTA_MODE_PERCENT = "percent";
|
|
732
|
+
const QUICK_QUOTA_MODE_FORECAST = "forecast";
|
|
728
733
|
const SEARCH_PROVIDER_FIELD = "searchProvider";
|
|
729
734
|
const SEARCH_PROVIDER_AUTO = "auto";
|
|
730
735
|
const SEARCH_PROVIDER_CODEX = "codex";
|
|
@@ -816,7 +821,8 @@ function contextModelGroups(models) {
|
|
|
816
821
|
const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
|
|
817
822
|
"off",
|
|
818
823
|
"percent",
|
|
819
|
-
"bar"
|
|
824
|
+
"bar",
|
|
825
|
+
"forecast"
|
|
820
826
|
].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
|
|
821
827
|
const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
|
|
822
828
|
//#endregion
|
|
@@ -930,7 +936,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
930
936
|
}
|
|
931
937
|
//#endregion
|
|
932
938
|
//#region src/version.js
|
|
933
|
-
const PACKAGE_VERSION = "1.
|
|
939
|
+
const PACKAGE_VERSION = "1.11.0";
|
|
934
940
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
935
941
|
//#endregion
|
|
936
942
|
//#region src/model-catalog.js
|
|
@@ -946,7 +952,7 @@ const LEVELS = [
|
|
|
946
952
|
];
|
|
947
953
|
const record$4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
948
954
|
const nonEmpty$2 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
949
|
-
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
955
|
+
const positiveInteger$1 = (value) => Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
950
956
|
function reasoningMap(levels) {
|
|
951
957
|
const supported = new Set((Array.isArray(levels) ? levels : []).map((level) => nonEmpty$2(record$4(level) ? level.effort : void 0)).filter(Boolean));
|
|
952
958
|
const map = Object.fromEntries(LEVELS.map((level) => [level, null]));
|
|
@@ -966,7 +972,7 @@ function visibleModel(value) {
|
|
|
966
972
|
description: nonEmpty$2(value.description),
|
|
967
973
|
priority: Number.isFinite(value.priority) ? value.priority : 0,
|
|
968
974
|
input: input.length > 0 ? input : ["text"],
|
|
969
|
-
contextWindow: positiveInteger(value.context_window) ?? positiveInteger(value.max_context_window),
|
|
975
|
+
contextWindow: positiveInteger$1(value.context_window) ?? positiveInteger$1(value.max_context_window),
|
|
970
976
|
reasoning: supported.length > 0,
|
|
971
977
|
thinkingLevelMap: reasoningMap(supported),
|
|
972
978
|
supportVerbosity: value.support_verbosity === true,
|
|
@@ -1192,6 +1198,42 @@ function createCodexAutoSearchProvider(options) {
|
|
|
1192
1198
|
}
|
|
1193
1199
|
});
|
|
1194
1200
|
}
|
|
1201
|
+
const ORIGINAL_IMAGE_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
1202
|
+
const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
|
|
1203
|
+
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
|
1204
|
+
function decodeOriginalImageRef(value) {
|
|
1205
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(value.assetId) || value.mediaType !== "image/png" || !positiveInteger(value.bytes) || value.bytes > 48 * 1024 * 1024 || !positiveInteger(value.width) || !positiveInteger(value.height) || typeof value.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.name) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.sha256)) return void 0;
|
|
1206
|
+
return {
|
|
1207
|
+
assetId: value.assetId,
|
|
1208
|
+
mediaType: value.mediaType,
|
|
1209
|
+
bytes: value.bytes,
|
|
1210
|
+
width: value.width,
|
|
1211
|
+
height: value.height,
|
|
1212
|
+
name: value.name,
|
|
1213
|
+
sha256: value.sha256
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
function decodeImagePresentation(value) {
|
|
1217
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
|
|
1218
|
+
const original = decodeOriginalImageRef(value.original);
|
|
1219
|
+
return original === void 0 ? void 0 : { original };
|
|
1220
|
+
}
|
|
1221
|
+
function originalImageRefsEqual(left, right) {
|
|
1222
|
+
const a = decodeOriginalImageRef(left);
|
|
1223
|
+
const b = decodeOriginalImageRef(right);
|
|
1224
|
+
return a !== void 0 && b !== void 0 && a.assetId === b.assetId && a.mediaType === b.mediaType && a.bytes === b.bytes && a.width === b.width && a.height === b.height && a.name === b.name && a.sha256 === b.sha256;
|
|
1225
|
+
}
|
|
1226
|
+
/** Resolve only an exact original reference copied into a DSH fork prefix. */
|
|
1227
|
+
function inheritedOriginalImageRef(session, assetId) {
|
|
1228
|
+
const parentSession = session?.header?.parentSession;
|
|
1229
|
+
const seedLength = session?.header?.seedLength;
|
|
1230
|
+
if (typeof parentSession !== "string" || parentSession.length === 0 || !Number.isSafeInteger(seedLength) || seedLength < 0 || !Array.isArray(session?.events) || !ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) return void 0;
|
|
1231
|
+
for (const event of session.events) {
|
|
1232
|
+
if (!Number.isSafeInteger(event?.seq) || event.seq < 0 || event.seq >= seedLength || event.type !== "tool/result") continue;
|
|
1233
|
+
const original = decodeImagePresentation(event.data?.meta)?.original;
|
|
1234
|
+
if (original?.assetId === assetId) return original;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1195
1237
|
//#endregion
|
|
1196
1238
|
//#region src/codex-images.js
|
|
1197
1239
|
const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
|
|
@@ -1360,6 +1402,42 @@ function imageOutputSchema() {
|
|
|
1360
1402
|
name: { type: "string" }
|
|
1361
1403
|
}
|
|
1362
1404
|
},
|
|
1405
|
+
original: {
|
|
1406
|
+
type: "object",
|
|
1407
|
+
required: true,
|
|
1408
|
+
additionalProperties: false,
|
|
1409
|
+
properties: {
|
|
1410
|
+
assetId: {
|
|
1411
|
+
type: "string",
|
|
1412
|
+
required: true
|
|
1413
|
+
},
|
|
1414
|
+
mediaType: {
|
|
1415
|
+
type: "string",
|
|
1416
|
+
enum: ["image/png"],
|
|
1417
|
+
required: true
|
|
1418
|
+
},
|
|
1419
|
+
bytes: {
|
|
1420
|
+
type: "integer",
|
|
1421
|
+
required: true
|
|
1422
|
+
},
|
|
1423
|
+
width: {
|
|
1424
|
+
type: "integer",
|
|
1425
|
+
required: true
|
|
1426
|
+
},
|
|
1427
|
+
height: {
|
|
1428
|
+
type: "integer",
|
|
1429
|
+
required: true
|
|
1430
|
+
},
|
|
1431
|
+
name: {
|
|
1432
|
+
type: "string",
|
|
1433
|
+
required: true
|
|
1434
|
+
},
|
|
1435
|
+
sha256: {
|
|
1436
|
+
type: "string",
|
|
1437
|
+
required: true
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1363
1441
|
background: { type: "string" },
|
|
1364
1442
|
quality: { type: "string" },
|
|
1365
1443
|
size: { type: "string" }
|
|
@@ -1447,7 +1525,12 @@ function createCodexImageTool(options) {
|
|
|
1447
1525
|
},
|
|
1448
1526
|
output: {
|
|
1449
1527
|
schema: imageOutputSchema(),
|
|
1450
|
-
render: (_args, value) => imageContent(value)
|
|
1528
|
+
render: (_args, value) => imageContent(value),
|
|
1529
|
+
presentationMeta: (_args, value) => ({
|
|
1530
|
+
kind: "codex-subscription-image",
|
|
1531
|
+
schemaVersion: 1,
|
|
1532
|
+
original: value.original
|
|
1533
|
+
})
|
|
1451
1534
|
},
|
|
1452
1535
|
timeoutMs: 300 * 1e3,
|
|
1453
1536
|
isConcurrencySafe: () => false,
|
|
@@ -1499,12 +1582,23 @@ function createCodexImageTool(options) {
|
|
|
1499
1582
|
}
|
|
1500
1583
|
const metadata = responseMetadata(await readJsonWithin(response, encodedLimit(maximumBytes) + RESPONSE_ENVELOPE_BYTES));
|
|
1501
1584
|
const data = decodeCodexPng(metadata.encoded, maximumBytes);
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1585
|
+
const sessionId = exec.agent?.id;
|
|
1586
|
+
if (sessionId === void 0) throw new Error("Codex image generation requires a session-owned tool call");
|
|
1587
|
+
const original = await options.originalImages.save(String(sessionId), data);
|
|
1588
|
+
let ref;
|
|
1589
|
+
try {
|
|
1590
|
+
ref = await attachments.saveImage({
|
|
1504
1591
|
data,
|
|
1505
1592
|
mediaType: "image/png",
|
|
1506
1593
|
name: "codex-generated.png"
|
|
1507
|
-
})
|
|
1594
|
+
});
|
|
1595
|
+
} catch (error) {
|
|
1596
|
+
await options.originalImages.remove(original);
|
|
1597
|
+
throw error;
|
|
1598
|
+
}
|
|
1599
|
+
const result = {
|
|
1600
|
+
image: imageReference(ref),
|
|
1601
|
+
original,
|
|
1508
1602
|
...metadata.background === void 0 ? {} : { background: metadata.background },
|
|
1509
1603
|
...metadata.quality === void 0 ? {} : { quality: metadata.quality },
|
|
1510
1604
|
...metadata.size === void 0 ? {} : { size: metadata.size }
|
|
@@ -1521,6 +1615,142 @@ function createCodexImageTool(options) {
|
|
|
1521
1615
|
});
|
|
1522
1616
|
}
|
|
1523
1617
|
//#endregion
|
|
1618
|
+
//#region src/image-original-store.js
|
|
1619
|
+
const ORIGINAL_IMAGE_DIRECTORY = "dsh-codex-subscription/images/v1";
|
|
1620
|
+
const METADATA_VERSION = 1;
|
|
1621
|
+
const digest = (data) => createHash("sha256").update(data).digest("hex");
|
|
1622
|
+
const validSessionId = (value) => typeof value === "string" && value.length > 0 && value.length <= 512;
|
|
1623
|
+
function pngDimensions(data) {
|
|
1624
|
+
if (!(data instanceof Uint8Array) || data.byteLength < 24 || Buffer.from(data.subarray(0, 8)).toString("hex") !== "89504e470d0a1a0a" || Buffer.from(data.subarray(12, 16)).toString("ascii") !== "IHDR") throw new TypeError("invalid PNG dimensions");
|
|
1625
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1626
|
+
const width = view.getUint32(16, false);
|
|
1627
|
+
const height = view.getUint32(20, false);
|
|
1628
|
+
if (width === 0 || height === 0) throw new TypeError("invalid PNG dimensions");
|
|
1629
|
+
return {
|
|
1630
|
+
width,
|
|
1631
|
+
height
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
async function writeExclusive(filename, data) {
|
|
1635
|
+
await mkdir(dirname(filename), {
|
|
1636
|
+
recursive: true,
|
|
1637
|
+
mode: 448
|
|
1638
|
+
});
|
|
1639
|
+
const handle = await open(filename, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
|
|
1640
|
+
try {
|
|
1641
|
+
await handle.writeFile(data);
|
|
1642
|
+
await handle.sync();
|
|
1643
|
+
} finally {
|
|
1644
|
+
await handle.close();
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async function assertPrivateFile(filename) {
|
|
1648
|
+
const stat = await lstat(filename);
|
|
1649
|
+
if (!stat.isFile()) throw new Error("not a regular file");
|
|
1650
|
+
if (process.platform !== "win32" && (stat.mode & 63) !== 0) throw new Error("file is not owner-only");
|
|
1651
|
+
}
|
|
1652
|
+
function parseMetadata(text) {
|
|
1653
|
+
let value;
|
|
1654
|
+
try {
|
|
1655
|
+
value = JSON.parse(text);
|
|
1656
|
+
} catch {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
if (value?.version !== METADATA_VERSION || !validSessionId(value.sessionId)) return void 0;
|
|
1660
|
+
const image = decodeOriginalImageRef(value.image);
|
|
1661
|
+
return image === void 0 ? void 0 : {
|
|
1662
|
+
sessionId: value.sessionId,
|
|
1663
|
+
image
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
var OriginalImageStore = class {
|
|
1667
|
+
constructor(dshHome) {
|
|
1668
|
+
this.root = resolve(join(resolveDshHome(dshHome), ORIGINAL_IMAGE_DIRECTORY));
|
|
1669
|
+
}
|
|
1670
|
+
directory(assetId) {
|
|
1671
|
+
if (!ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) throw new TypeError("invalid original image asset id");
|
|
1672
|
+
return join(this.root, assetId.slice(4, 6), assetId);
|
|
1673
|
+
}
|
|
1674
|
+
async save(sessionId, data, name = "codex-generated-original.png") {
|
|
1675
|
+
if (!validSessionId(sessionId) || !(data instanceof Uint8Array) || data.byteLength === 0 || data.byteLength > 48 * 1024 * 1024) throw new TypeError("invalid original image input");
|
|
1676
|
+
const { width, height } = pngDimensions(data);
|
|
1677
|
+
const assetId = `img_${randomBytes(16).toString("hex")}`;
|
|
1678
|
+
const directory = this.directory(assetId);
|
|
1679
|
+
const ref = {
|
|
1680
|
+
assetId,
|
|
1681
|
+
mediaType: "image/png",
|
|
1682
|
+
bytes: data.byteLength,
|
|
1683
|
+
width,
|
|
1684
|
+
height,
|
|
1685
|
+
name,
|
|
1686
|
+
sha256: digest(data)
|
|
1687
|
+
};
|
|
1688
|
+
try {
|
|
1689
|
+
await mkdir(dirname(directory), {
|
|
1690
|
+
recursive: true,
|
|
1691
|
+
mode: 448
|
|
1692
|
+
});
|
|
1693
|
+
await mkdir(directory, {
|
|
1694
|
+
recursive: false,
|
|
1695
|
+
mode: 448
|
|
1696
|
+
});
|
|
1697
|
+
await writeExclusive(join(directory, "original"), data);
|
|
1698
|
+
const temporary = join(directory, `metadata.${randomBytes(8).toString("hex")}.tmp`);
|
|
1699
|
+
await writeExclusive(temporary, Buffer.from(`${JSON.stringify({
|
|
1700
|
+
version: METADATA_VERSION,
|
|
1701
|
+
sessionId,
|
|
1702
|
+
image: ref
|
|
1703
|
+
}, null, 2)}\n`));
|
|
1704
|
+
await rename(temporary, join(directory, "metadata.json"));
|
|
1705
|
+
return ref;
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
await rm(directory, {
|
|
1708
|
+
recursive: true,
|
|
1709
|
+
force: true
|
|
1710
|
+
}).catch(() => void 0);
|
|
1711
|
+
throw error;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
async remove(ref) {
|
|
1715
|
+
if (ref !== void 0 && ORIGINAL_IMAGE_ID_PATTERN.test(ref.assetId)) await rm(this.directory(ref.assetId), {
|
|
1716
|
+
recursive: true,
|
|
1717
|
+
force: true
|
|
1718
|
+
}).catch(() => void 0);
|
|
1719
|
+
}
|
|
1720
|
+
async read(sessionId, assetId, inherited) {
|
|
1721
|
+
if (!validSessionId(sessionId) || !ORIGINAL_IMAGE_ID_PATTERN.test(assetId)) return void 0;
|
|
1722
|
+
try {
|
|
1723
|
+
const directory = this.directory(assetId);
|
|
1724
|
+
const metadataFile = join(directory, "metadata.json");
|
|
1725
|
+
const originalFile = join(directory, "original");
|
|
1726
|
+
await Promise.all([assertPrivateFile(metadataFile), assertPrivateFile(originalFile)]);
|
|
1727
|
+
const metadata = parseMetadata(await readFile(metadataFile, "utf8"));
|
|
1728
|
+
if (metadata === void 0 || metadata.image.assetId !== assetId || metadata.sessionId !== sessionId && !originalImageRefsEqual(metadata.image, inherited)) return void 0;
|
|
1729
|
+
const data = new Uint8Array(await readFile(originalFile));
|
|
1730
|
+
const dimensions = pngDimensions(data);
|
|
1731
|
+
if (data.byteLength !== metadata.image.bytes || digest(data) !== metadata.image.sha256 || dimensions.width !== metadata.image.width || dimensions.height !== metadata.image.height) return void 0;
|
|
1732
|
+
return {
|
|
1733
|
+
ref: metadata.image,
|
|
1734
|
+
data
|
|
1735
|
+
};
|
|
1736
|
+
} catch {
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
async chunk(sessionId, assetId, offset, inherited) {
|
|
1741
|
+
if (!Number.isSafeInteger(offset) || offset < 0) return void 0;
|
|
1742
|
+
const stored = await this.read(sessionId, assetId, inherited);
|
|
1743
|
+
if (stored === void 0 || offset >= stored.data.byteLength || offset % 4194304 !== 0) return void 0;
|
|
1744
|
+
const end = Math.min(stored.data.byteLength, offset + ORIGINAL_IMAGE_CHUNK_BYTES);
|
|
1745
|
+
return {
|
|
1746
|
+
ref: stored.ref,
|
|
1747
|
+
offset,
|
|
1748
|
+
encoded: Buffer.from(stored.data.subarray(offset, end)).toString("base64"),
|
|
1749
|
+
done: end === stored.data.byteLength
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
//#endregion
|
|
1524
1754
|
//#region src/diagnostics.js
|
|
1525
1755
|
const requestAreas = /* @__PURE__ */ new Set([
|
|
1526
1756
|
"login",
|
|
@@ -1789,6 +2019,136 @@ function createCodexUsageReader(options) {
|
|
|
1789
2019
|
});
|
|
1790
2020
|
}
|
|
1791
2021
|
//#endregion
|
|
2022
|
+
//#region src/quota-forecast.js
|
|
2023
|
+
const HOUR_MS = 3600 * 1e3;
|
|
2024
|
+
const HISTORY_MS = 24 * HOUR_MS;
|
|
2025
|
+
const MIN_SPAN_MS = 1800 * 1e3;
|
|
2026
|
+
const MIN_CONSUMED_PERCENT = 1;
|
|
2027
|
+
const PLATEAU_SAMPLE_MS = 900 * 1e3;
|
|
2028
|
+
const finite = (value) => Number.isFinite(Number(value));
|
|
2029
|
+
const clampPercent = (value) => Math.max(0, Math.min(100, Number(value)));
|
|
2030
|
+
const keyFor = (window) => `codex:${Number(window.windowSeconds) || "limit"}`;
|
|
2031
|
+
function observeQuotaForecast(state, windows, now = Date.now()) {
|
|
2032
|
+
const next = { windows: { ...state?.windows ?? {} } };
|
|
2033
|
+
let changed = false;
|
|
2034
|
+
for (const window of windows ?? []) {
|
|
2035
|
+
if (!finite(window?.remainingPercent)) continue;
|
|
2036
|
+
const key = keyFor(window);
|
|
2037
|
+
const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
|
|
2038
|
+
const remainingPercent = Math.round(clampPercent(window.remainingPercent) * 1e4) / 1e4;
|
|
2039
|
+
const previous = next.windows[key];
|
|
2040
|
+
const resetChanged = previous !== void 0 && (previous.resetsAt === null !== (resetsAt === null) || previous.resetsAt !== null && Math.abs(previous.resetsAt - resetsAt) > 300);
|
|
2041
|
+
const last = previous?.samples?.at(-1);
|
|
2042
|
+
const quotaIncreased = last !== void 0 && remainingPercent > last.remainingPercent + .5;
|
|
2043
|
+
const record = resetChanged || quotaIncreased ? {
|
|
2044
|
+
resetsAt,
|
|
2045
|
+
samples: []
|
|
2046
|
+
} : {
|
|
2047
|
+
resetsAt,
|
|
2048
|
+
samples: [...previous?.samples ?? []]
|
|
2049
|
+
};
|
|
2050
|
+
const latest = record.samples.at(-1);
|
|
2051
|
+
if (latest === void 0 || now > latest.at && (Math.abs(remainingPercent - latest.remainingPercent) >= .001 || now - latest.at >= PLATEAU_SAMPLE_MS)) {
|
|
2052
|
+
record.samples.push({
|
|
2053
|
+
at: now,
|
|
2054
|
+
remainingPercent
|
|
2055
|
+
});
|
|
2056
|
+
record.samples = record.samples.filter((sample) => sample.at >= now - HISTORY_MS).slice(-192);
|
|
2057
|
+
changed = true;
|
|
2058
|
+
}
|
|
2059
|
+
next.windows[key] = record;
|
|
2060
|
+
}
|
|
2061
|
+
return {
|
|
2062
|
+
state: next,
|
|
2063
|
+
changed
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
function estimateQuotaForecast(state, window, now = Date.now()) {
|
|
2067
|
+
if (!finite(window?.remainingPercent)) return { status: "calibrating" };
|
|
2068
|
+
const record = state?.windows?.[keyFor(window)];
|
|
2069
|
+
if (record === void 0) return { status: "calibrating" };
|
|
2070
|
+
const resetsAt = finite(window.resetsAt) ? Number(window.resetsAt) : null;
|
|
2071
|
+
if (record.resetsAt === null !== (resetsAt === null) || resetsAt !== null && Math.abs(record.resetsAt - resetsAt) > 300) return { status: "calibrating" };
|
|
2072
|
+
const samples = record.samples.filter((sample) => sample.at >= now - HISTORY_MS && sample.at <= now + 6e4);
|
|
2073
|
+
if (samples.length < 3) return {
|
|
2074
|
+
status: "calibrating",
|
|
2075
|
+
sampleCount: samples.length
|
|
2076
|
+
};
|
|
2077
|
+
const first = samples[0];
|
|
2078
|
+
const last = samples.at(-1);
|
|
2079
|
+
const spanMs = last.at - first.at;
|
|
2080
|
+
const consumedPercent = Math.max(0, first.remainingPercent - last.remainingPercent);
|
|
2081
|
+
if (spanMs < MIN_SPAN_MS || consumedPercent < MIN_CONSUMED_PERCENT) return {
|
|
2082
|
+
status: "calibrating",
|
|
2083
|
+
sampleCount: samples.length,
|
|
2084
|
+
observedSpanMs: spanMs,
|
|
2085
|
+
consumedPercent
|
|
2086
|
+
};
|
|
2087
|
+
const firstAt = first.at;
|
|
2088
|
+
const weighted = samples.map((sample) => ({
|
|
2089
|
+
x: (sample.at - firstAt) / HOUR_MS,
|
|
2090
|
+
y: first.remainingPercent - sample.remainingPercent,
|
|
2091
|
+
weight: Math.exp((sample.at - last.at) / (6 * HOUR_MS))
|
|
2092
|
+
}));
|
|
2093
|
+
const totalWeight = weighted.reduce((sum, point) => sum + point.weight, 0);
|
|
2094
|
+
const meanX = weighted.reduce((sum, point) => sum + point.x * point.weight, 0) / totalWeight;
|
|
2095
|
+
const meanY = weighted.reduce((sum, point) => sum + point.y * point.weight, 0) / totalWeight;
|
|
2096
|
+
const numerator = weighted.reduce((sum, point) => sum + point.weight * (point.x - meanX) * (point.y - meanY), 0);
|
|
2097
|
+
const denominator = weighted.reduce((sum, point) => sum + point.weight * (point.x - meanX) ** 2, 0);
|
|
2098
|
+
const pacePerHour = denominator > 0 ? numerator / denominator : 0;
|
|
2099
|
+
if (!Number.isFinite(pacePerHour) || pacePerHour < .02) return {
|
|
2100
|
+
status: "idle",
|
|
2101
|
+
pacePerHour: 0
|
|
2102
|
+
};
|
|
2103
|
+
const runwaySeconds = clampPercent(window.remainingPercent) / pacePerHour * 3600;
|
|
2104
|
+
const resetSeconds = resetsAt === null ? null : Math.max(0, resetsAt - now / 1e3);
|
|
2105
|
+
return {
|
|
2106
|
+
status: "ready",
|
|
2107
|
+
pacePerHour,
|
|
2108
|
+
runwaySeconds,
|
|
2109
|
+
survivesReset: resetSeconds !== null && runwaySeconds >= resetSeconds,
|
|
2110
|
+
sampleCount: samples.length,
|
|
2111
|
+
observedSpanMs: spanMs,
|
|
2112
|
+
consumedPercent
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
function forecastUsage(usage, state = { windows: {} }, now = Date.now()) {
|
|
2116
|
+
const observed = observeQuotaForecast(state, usage?.rateLimits?.find((limit) => limit.id === "codex")?.windows ?? [], now);
|
|
2117
|
+
return {
|
|
2118
|
+
state: observed.state,
|
|
2119
|
+
changed: observed.changed,
|
|
2120
|
+
usage: {
|
|
2121
|
+
...usage,
|
|
2122
|
+
rateLimits: (usage?.rateLimits ?? []).map((limit) => limit.id !== "codex" ? limit : {
|
|
2123
|
+
...limit,
|
|
2124
|
+
windows: limit.windows.map((window) => ({
|
|
2125
|
+
...window,
|
|
2126
|
+
forecast: estimateQuotaForecast(observed.state, window, now)
|
|
2127
|
+
}))
|
|
2128
|
+
})
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
function createQuotaForecastReader({ reader, enabled, now = Date.now }) {
|
|
2133
|
+
let state = { windows: {} };
|
|
2134
|
+
return Object.freeze({
|
|
2135
|
+
async read(options) {
|
|
2136
|
+
const usage = await reader.read(options);
|
|
2137
|
+
if (!enabled()) {
|
|
2138
|
+
state = { windows: {} };
|
|
2139
|
+
return usage;
|
|
2140
|
+
}
|
|
2141
|
+
const forecast = forecastUsage(usage, state, now());
|
|
2142
|
+
state = forecast.state;
|
|
2143
|
+
return forecast.usage;
|
|
2144
|
+
},
|
|
2145
|
+
clear() {
|
|
2146
|
+
state = { windows: {} };
|
|
2147
|
+
reader.clear();
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
//#endregion
|
|
1792
2152
|
//#region src/reset-credits.js
|
|
1793
2153
|
const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
1794
2154
|
const CODEX_RESET_CONSUME_URL = `${CODEX_RESET_CREDITS_URL}/consume`;
|
|
@@ -2011,8 +2371,22 @@ const publicError = (code, message) => ({
|
|
|
2011
2371
|
details: { issues: [] }
|
|
2012
2372
|
}
|
|
2013
2373
|
});
|
|
2014
|
-
function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, diagnosticsReader, modelCatalog }) {
|
|
2374
|
+
function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal }) {
|
|
2015
2375
|
return async (endpoint, payload, signal) => {
|
|
2376
|
+
if (endpoint === "image/original/chunk") try {
|
|
2377
|
+
signal.throwIfAborted();
|
|
2378
|
+
if (typeof payload?.sessionId !== "string" || payload.sessionId.length === 0 || payload.sessionId.length > 512 || typeof payload?.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(payload.assetId) || !Number.isSafeInteger(payload?.offset) || payload.offset < 0 || payload.offset % 4194304 !== 0) return publicError("invalid-input", "Invalid original image request");
|
|
2379
|
+
const inherited = resolveInheritedOriginal?.(payload.sessionId, payload.assetId);
|
|
2380
|
+
const chunk = await originalImages?.chunk(payload.sessionId, payload.assetId, payload.offset, inherited);
|
|
2381
|
+
if (chunk === void 0) return publicError("not-found", "Original image is unavailable");
|
|
2382
|
+
return {
|
|
2383
|
+
ok: true,
|
|
2384
|
+
value: chunk
|
|
2385
|
+
};
|
|
2386
|
+
} catch (error) {
|
|
2387
|
+
if (signal.aborted) throw error;
|
|
2388
|
+
return publicError("internal", "Could not read the original image");
|
|
2389
|
+
}
|
|
2016
2390
|
if (endpoint === "diagnostics") try {
|
|
2017
2391
|
signal.throwIfAborted();
|
|
2018
2392
|
return {
|
|
@@ -2031,7 +2405,8 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
2031
2405
|
if (![
|
|
2032
2406
|
"off",
|
|
2033
2407
|
"percent",
|
|
2034
|
-
"bar"
|
|
2408
|
+
"bar",
|
|
2409
|
+
"forecast"
|
|
2035
2410
|
].includes(payload["quickQuotaMode"])) return publicError("internal", "Invalid quick quota preference");
|
|
2036
2411
|
patch[QUICK_QUOTA_MODE_FIELD] = payload[QUICK_QUOTA_MODE_FIELD];
|
|
2037
2412
|
}
|
|
@@ -2165,7 +2540,8 @@ function apply(ctx) {
|
|
|
2165
2540
|
[QUICK_QUOTA_MODE_FIELD]: z.union([
|
|
2166
2541
|
"off",
|
|
2167
2542
|
QUICK_QUOTA_MODE_PERCENT,
|
|
2168
|
-
"bar"
|
|
2543
|
+
"bar",
|
|
2544
|
+
QUICK_QUOTA_MODE_FORECAST
|
|
2169
2545
|
]),
|
|
2170
2546
|
[LEGACY_QUICK_QUOTA_FIELD]: z.boolean(),
|
|
2171
2547
|
[SEARCH_PROVIDER_FIELD]: z.union([
|
|
@@ -2190,6 +2566,7 @@ function apply(ctx) {
|
|
|
2190
2566
|
}));
|
|
2191
2567
|
const searchProvider = createSearchProviderSwitcher(ctx.loader);
|
|
2192
2568
|
const network = createCodexNetworkTransport();
|
|
2569
|
+
const originalImages = new OriginalImageStore();
|
|
2193
2570
|
const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF, [LEGACY_CREDENTIAL_REF]);
|
|
2194
2571
|
const baseProvider = createOpenAICodexProvider();
|
|
2195
2572
|
let resolveAuth = async () => void 0;
|
|
@@ -2265,6 +2642,7 @@ function apply(ctx) {
|
|
|
2265
2642
|
getAuth: resolveAuth,
|
|
2266
2643
|
readCredential: (options) => store.read(PROVIDER, options),
|
|
2267
2644
|
attachments: ctx.attachments,
|
|
2645
|
+
originalImages,
|
|
2268
2646
|
fetch: (input, init) => network.fetch("image", input, init)
|
|
2269
2647
|
}));
|
|
2270
2648
|
const adapter = new PiAiAdapter({
|
|
@@ -2313,10 +2691,13 @@ function apply(ctx) {
|
|
|
2313
2691
|
}, "codex-subscription: search provider selection");
|
|
2314
2692
|
const auth = createCodexAuthService(authModels, store, { runLogin: (operation) => network.run("login", operation) });
|
|
2315
2693
|
const coordinator = new CodexLoginCoordinator(auth);
|
|
2316
|
-
const usageReader =
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2694
|
+
const usageReader = createQuotaForecastReader({
|
|
2695
|
+
reader: createCodexUsageReader({
|
|
2696
|
+
getAuth: resolveAuth,
|
|
2697
|
+
readCredential: (options) => store.read(PROVIDER, options),
|
|
2698
|
+
fetch: (input, init) => network.fetch("quota", input, init)
|
|
2699
|
+
}),
|
|
2700
|
+
enabled: () => normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]) === QUICK_QUOTA_MODE_FORECAST
|
|
2320
2701
|
});
|
|
2321
2702
|
const resetCreditService = createCodexResetCreditService({
|
|
2322
2703
|
getAuth: resolveAuth,
|
|
@@ -2335,12 +2716,14 @@ function apply(ctx) {
|
|
|
2335
2716
|
login: coordinator.supportState(),
|
|
2336
2717
|
network
|
|
2337
2718
|
}),
|
|
2338
|
-
modelCatalog
|
|
2719
|
+
modelCatalog,
|
|
2720
|
+
originalImages,
|
|
2721
|
+
resolveInheritedOriginal: (sessionId, assetId) => inheritedOriginalImageRef(ctx.get?.("sessions")?.get?.(sessionId), assetId)
|
|
2339
2722
|
});
|
|
2340
2723
|
ctx.effect(() => {
|
|
2341
2724
|
modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
|
|
2342
2725
|
}, "codex-subscription: official model catalog");
|
|
2343
|
-
ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "
|
|
2726
|
+
ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "trusted-host" }), "codex-subscription: DSH-trusted account RPC");
|
|
2344
2727
|
}
|
|
2345
2728
|
//#endregion
|
|
2346
2729
|
export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexResetCreditService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionDiagnostics, createSubscriptionRpcHandler, decodeCodexPng, inject, name, normalizeContextMode, normalizeCustomContextWindow, openCodexAuthUrl, parseCodexUsage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-codex-subscription",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
114
114
|
"@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
|
|
115
115
|
"@deepseek-ai/dsh-settings": "0.1.1-rc.2",
|
|
116
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.2",
|
|
116
117
|
"@deepseek-ai/dsh-tools": "0.1.1-rc.2",
|
|
117
118
|
"@deepseek-ai/dsh-web": "0.1.1-rc.2",
|
|
118
119
|
"@deepseek-ai/schemastery": "3.18.1",
|
|
@@ -123,10 +124,13 @@
|
|
|
123
124
|
"tsdown": "0.22.2"
|
|
124
125
|
},
|
|
125
126
|
"dependencies": {
|
|
127
|
+
"@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
|
|
126
128
|
"https-proxy-agent": "7.0.6"
|
|
127
129
|
},
|
|
128
130
|
"scripts": {
|
|
129
131
|
"build": "tsdown --config tsdown.config.mjs",
|
|
132
|
+
"test:behavior": "node --test tests/auth-security.test.mjs tests/cache-contract.test.mjs tests/codex-images.test.mjs tests/codex-search.test.mjs tests/image-edit.test.mjs tests/model-catalog.test.mjs tests/oauth-network.test.mjs tests/pi-ai-runtime.test.mjs tests/plugin-integration.test.mjs tests/reset-credits.test.mjs tests/sidebar-quota.test.mjs tests/transport-contract.test.mjs tests/usage.test.mjs",
|
|
133
|
+
"test:delivery": "node --test tests/ci-change-plan.test.mjs tests/client-contract.test.mjs tests/prepare-compat-release.test.mjs tests/publish-idempotency.test.mjs tests/release-contract.test.mjs tests/release-notes.test.mjs tests/support-intake.test.mjs",
|
|
130
134
|
"test": "node --test tests/*.test.mjs",
|
|
131
135
|
"check": "pnpm run test && pnpm run build && pnpm pack --pack-destination .artifacts"
|
|
132
136
|
}
|