@promptbook/node 0.114.0-20 → 0.114.0-22
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/esm/index.es.js +458 -1
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +15 -0
- package/esm/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeOutputEvents.d.ts +3 -0
- package/esm/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeSubscriptionUsage.d.ts +11 -0
- package/esm/scripts/run-codex-prompts/runners/openai-codex/OpenAiCodexRunner.d.ts +8 -0
- package/esm/scripts/run-codex-prompts/runners/openai-codex/getCodexSubscriptionUsage.d.ts +19 -0
- package/esm/scripts/run-codex-prompts/runners/types/HarnessSubscriptionUsage.d.ts +43 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +461 -5
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +15 -0
- package/umd/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeOutputEvents.d.ts +3 -0
- package/umd/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeSubscriptionUsage.d.ts +11 -0
- package/umd/scripts/run-codex-prompts/runners/openai-codex/OpenAiCodexRunner.d.ts +8 -0
- package/umd/scripts/run-codex-prompts/runners/openai-codex/getCodexSubscriptionUsage.d.ts +19 -0
- package/umd/scripts/run-codex-prompts/runners/types/HarnessSubscriptionUsage.d.ts +43 -0
- package/umd/src/version.d.ts +1 -1
package/esm/index.es.js
CHANGED
|
@@ -3,6 +3,7 @@ import { unlink, mkdir, appendFile, writeFile, readFile, stat, access, constants
|
|
|
3
3
|
import _spaceTrim, { spaceTrim as spaceTrim$1 } from 'spacetrim';
|
|
4
4
|
import colors from 'colors';
|
|
5
5
|
import { spawn, spawnSync } from 'child_process';
|
|
6
|
+
import { createInterface } from 'readline';
|
|
6
7
|
import { fileURLToPath } from 'url';
|
|
7
8
|
import CryptoJS from 'crypto-js';
|
|
8
9
|
import hexEncoder from 'crypto-js/enc-hex';
|
|
@@ -29,7 +30,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
29
30
|
* @generated
|
|
30
31
|
* @see https://github.com/webgptorg/promptbook
|
|
31
32
|
*/
|
|
32
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-
|
|
33
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-22';
|
|
33
34
|
/**
|
|
34
35
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
35
36
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -3407,6 +3408,29 @@ async function waitForSkippableWorldTimeDeadline(options) {
|
|
|
3407
3408
|
}
|
|
3408
3409
|
}
|
|
3409
3410
|
|
|
3411
|
+
/**
|
|
3412
|
+
* Merges a newly reported subset of subscription windows into the latest complete snapshot.
|
|
3413
|
+
*
|
|
3414
|
+
* Some harnesses emit an update only for the limit whose state changed. Keeping the previous windows means a
|
|
3415
|
+
* five-hour update cannot make a still-valid weekly limit disappear from the dashboard. Existing order is preserved
|
|
3416
|
+
* and newly discovered windows are appended, which keeps the terminal presentation stable between refreshes.
|
|
3417
|
+
*
|
|
3418
|
+
* @private internal utility of `ptbk coder`
|
|
3419
|
+
*/
|
|
3420
|
+
function mergeHarnessSubscriptionUsage(previousSubscriptionUsage, latestSubscriptionUsage) {
|
|
3421
|
+
if (previousSubscriptionUsage === undefined) {
|
|
3422
|
+
return latestSubscriptionUsage;
|
|
3423
|
+
}
|
|
3424
|
+
const latestLimitsByLabel = new Map(latestSubscriptionUsage.limits.map((latestLimit) => [latestLimit.label, latestLimit]));
|
|
3425
|
+
const previousLimitLabels = new Set(previousSubscriptionUsage.limits.map((previousLimit) => previousLimit.label));
|
|
3426
|
+
return {
|
|
3427
|
+
limits: [
|
|
3428
|
+
...previousSubscriptionUsage.limits.map((previousLimit) => { var _a; return (_a = latestLimitsByLabel.get(previousLimit.label)) !== null && _a !== void 0 ? _a : previousLimit; }),
|
|
3429
|
+
...latestSubscriptionUsage.limits.filter((latestLimit) => !previousLimitLabels.has(latestLimit.label)),
|
|
3430
|
+
],
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3410
3434
|
/**
|
|
3411
3435
|
* Base delimiter used for passing large prompts through stdin.
|
|
3412
3436
|
*/
|
|
@@ -3740,6 +3764,122 @@ function parseClaudeCodeJsonOutput(output) {
|
|
|
3740
3764
|
}
|
|
3741
3765
|
}
|
|
3742
3766
|
|
|
3767
|
+
/**
|
|
3768
|
+
* Human-readable labels for known Claude Code rolling subscription-limit windows.
|
|
3769
|
+
*/
|
|
3770
|
+
const CLAUDE_CODE_RATE_LIMIT_LABELS = {
|
|
3771
|
+
five_hour: '5h',
|
|
3772
|
+
seven_day: '7d',
|
|
3773
|
+
seven_day_opus: '7d Opus',
|
|
3774
|
+
seven_day_sonnet: '7d Sonnet',
|
|
3775
|
+
overage: 'Overage',
|
|
3776
|
+
};
|
|
3777
|
+
/**
|
|
3778
|
+
* Fallback label used when a newer Claude Code release adds a rate-limit window unknown to Promptbook.
|
|
3779
|
+
*/
|
|
3780
|
+
const UNKNOWN_CLAUDE_CODE_RATE_LIMIT_LABEL = 'Limit';
|
|
3781
|
+
/**
|
|
3782
|
+
* Converts Claude Code stream-json rate-limit events into the shared subscription-usage snapshot.
|
|
3783
|
+
*
|
|
3784
|
+
* Claude Code can emit one event for each applicable limit, so the parser preserves every distinct window instead of
|
|
3785
|
+
* assuming the familiar 5-hour and seven-day pair. This lets a new vendor-side limit appear in the dashboard without
|
|
3786
|
+
* changing its renderer.
|
|
3787
|
+
*
|
|
3788
|
+
* @private internal utility of the Claude Code runner
|
|
3789
|
+
*/
|
|
3790
|
+
function parseClaudeCodeSubscriptionUsage(output) {
|
|
3791
|
+
const limitsByType = new Map();
|
|
3792
|
+
for (const event of parseClaudeCodeOutputEvents(output)) {
|
|
3793
|
+
const limit = parseClaudeCodeSubscriptionUsageLimit(event);
|
|
3794
|
+
if (limit) {
|
|
3795
|
+
limitsByType.set(resolveClaudeCodeRateLimitType(event), limit);
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
const limits = [...limitsByType.values()];
|
|
3799
|
+
return limits.length === 0 ? undefined : { limits };
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Parses one Claude Code stream event when it contains a usable subscription-limit update.
|
|
3803
|
+
*
|
|
3804
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
3805
|
+
*/
|
|
3806
|
+
function parseClaudeCodeSubscriptionUsageLimit(event) {
|
|
3807
|
+
if (event.type !== 'rate_limit_event' || !event.rate_limit_info) {
|
|
3808
|
+
return undefined;
|
|
3809
|
+
}
|
|
3810
|
+
const rateLimitType = resolveClaudeCodeRateLimitType(event);
|
|
3811
|
+
const usedPercentage = resolveClaudeCodeUsedPercentage(event.rate_limit_info);
|
|
3812
|
+
if (usedPercentage === undefined) {
|
|
3813
|
+
return undefined;
|
|
3814
|
+
}
|
|
3815
|
+
const resetsAt = resolveClaudeCodeResetTimestamp(event.rate_limit_info);
|
|
3816
|
+
return {
|
|
3817
|
+
label: formatClaudeCodeRateLimitTypeLabel(rateLimitType),
|
|
3818
|
+
usedPercentage,
|
|
3819
|
+
...(resetsAt !== undefined && { resetsAt }),
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
/**
|
|
3823
|
+
* Formats known and newly introduced Claude Code rate-limit types for a stable terminal label.
|
|
3824
|
+
*
|
|
3825
|
+
* A newer limit type remains distinct in the dashboard instead of making several unrelated windows all appear as
|
|
3826
|
+
* a generic `Limit` row.
|
|
3827
|
+
*
|
|
3828
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
3829
|
+
*/
|
|
3830
|
+
function formatClaudeCodeRateLimitTypeLabel(rateLimitType) {
|
|
3831
|
+
const knownLabel = CLAUDE_CODE_RATE_LIMIT_LABELS[rateLimitType];
|
|
3832
|
+
if (knownLabel) {
|
|
3833
|
+
return knownLabel;
|
|
3834
|
+
}
|
|
3835
|
+
const words = rateLimitType.split(/[\s_-]+/u).filter(Boolean);
|
|
3836
|
+
if (words.length === 0) {
|
|
3837
|
+
return UNKNOWN_CLAUDE_CODE_RATE_LIMIT_LABEL;
|
|
3838
|
+
}
|
|
3839
|
+
return words.map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`).join(' ');
|
|
3840
|
+
}
|
|
3841
|
+
/**
|
|
3842
|
+
* Resolves the stable rate-limit type key emitted by Claude Code across camel- and snake-case payload versions.
|
|
3843
|
+
*
|
|
3844
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
3845
|
+
*/
|
|
3846
|
+
function resolveClaudeCodeRateLimitType(event) {
|
|
3847
|
+
var _a;
|
|
3848
|
+
const rateLimitInfo = event.rate_limit_info;
|
|
3849
|
+
const rateLimitType = (_a = rateLimitInfo === null || rateLimitInfo === void 0 ? void 0 : rateLimitInfo.rateLimitType) !== null && _a !== void 0 ? _a : rateLimitInfo === null || rateLimitInfo === void 0 ? void 0 : rateLimitInfo.rate_limit_type;
|
|
3850
|
+
return typeof rateLimitType === 'string' && rateLimitType.trim() !== ''
|
|
3851
|
+
? rateLimitType.trim()
|
|
3852
|
+
: UNKNOWN_CLAUDE_CODE_RATE_LIMIT_LABEL;
|
|
3853
|
+
}
|
|
3854
|
+
/**
|
|
3855
|
+
* Converts Claude's fractional utilization value to a displayed percentage.
|
|
3856
|
+
*
|
|
3857
|
+
* A rejected limit event may omit utilization, but it still conveys a precise zero-remaining state and is therefore
|
|
3858
|
+
* rendered as 100 percent consumed.
|
|
3859
|
+
*
|
|
3860
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
3861
|
+
*/
|
|
3862
|
+
function resolveClaudeCodeUsedPercentage(rateLimitInfo) {
|
|
3863
|
+
const utilization = rateLimitInfo.utilization;
|
|
3864
|
+
if (typeof utilization === 'number' && Number.isFinite(utilization)) {
|
|
3865
|
+
const usedPercentage = utilization <= 1 ? utilization * 100 : utilization;
|
|
3866
|
+
if (usedPercentage >= 0 && usedPercentage <= 100) {
|
|
3867
|
+
return usedPercentage;
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
return rateLimitInfo.status === 'rejected' ? 100 : undefined;
|
|
3871
|
+
}
|
|
3872
|
+
/**
|
|
3873
|
+
* Reads Claude's optional Unix reset timestamp from either current or earlier stream payload naming.
|
|
3874
|
+
*
|
|
3875
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
3876
|
+
*/
|
|
3877
|
+
function resolveClaudeCodeResetTimestamp(rateLimitInfo) {
|
|
3878
|
+
var _a;
|
|
3879
|
+
const resetsAt = (_a = rateLimitInfo.resetsAt) !== null && _a !== void 0 ? _a : rateLimitInfo.resets_at;
|
|
3880
|
+
return typeof resetsAt === 'number' && Number.isFinite(resetsAt) && resetsAt > 0 ? resetsAt : undefined;
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3743
3883
|
/**
|
|
3744
3884
|
* Polling interval used while waiting for Claude Code session limits to reset.
|
|
3745
3885
|
*/
|
|
@@ -3755,6 +3895,14 @@ class ClaudeCodeRunner {
|
|
|
3755
3895
|
this.options = options;
|
|
3756
3896
|
this.name = 'claude-code';
|
|
3757
3897
|
}
|
|
3898
|
+
/**
|
|
3899
|
+
* Returns the latest subscription-limit snapshot emitted by this Claude Code session.
|
|
3900
|
+
*
|
|
3901
|
+
* Claude exposes these values in the normal stream after a response, so no separate quota-only model call is made.
|
|
3902
|
+
*/
|
|
3903
|
+
async getSubscriptionUsage() {
|
|
3904
|
+
return this.subscriptionUsage;
|
|
3905
|
+
}
|
|
3758
3906
|
/**
|
|
3759
3907
|
* Runs the prompt using Claude Code and parses usage output.
|
|
3760
3908
|
*/
|
|
@@ -3768,6 +3916,7 @@ class ClaudeCodeRunner {
|
|
|
3768
3916
|
prompt,
|
|
3769
3917
|
resumeSessionId,
|
|
3770
3918
|
}).catch(async (error) => {
|
|
3919
|
+
this.updateSubscriptionUsage(error instanceof Error ? error.message : String(error));
|
|
3771
3920
|
const sessionLimit = extractClaudeCodeSessionLimitFromError(error);
|
|
3772
3921
|
if (!sessionLimit) {
|
|
3773
3922
|
throw error;
|
|
@@ -3782,6 +3931,7 @@ class ClaudeCodeRunner {
|
|
|
3782
3931
|
continue;
|
|
3783
3932
|
}
|
|
3784
3933
|
const sessionLimit = extractClaudeCodeSessionLimitFromOutput(output);
|
|
3934
|
+
this.updateSubscriptionUsage(output);
|
|
3785
3935
|
if (sessionLimit) {
|
|
3786
3936
|
resurrectionCount++;
|
|
3787
3937
|
await waitForClaudeCodeSessionLimitReset(sessionLimit, resurrectionCount, options);
|
|
@@ -3811,6 +3961,18 @@ class ClaudeCodeRunner {
|
|
|
3811
3961
|
preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
|
|
3812
3962
|
});
|
|
3813
3963
|
}
|
|
3964
|
+
/**
|
|
3965
|
+
* Keeps the newest usable Claude subscription-limit values reported by the stream.
|
|
3966
|
+
*
|
|
3967
|
+
* A stream can omit these values for API-key users or unsupported plan types; retaining the prior snapshot avoids
|
|
3968
|
+
* a temporary omission erasing a still-valid dashboard value while a long queue is running.
|
|
3969
|
+
*/
|
|
3970
|
+
updateSubscriptionUsage(output) {
|
|
3971
|
+
const subscriptionUsage = parseClaudeCodeSubscriptionUsage(output);
|
|
3972
|
+
if (subscriptionUsage) {
|
|
3973
|
+
this.subscriptionUsage = mergeHarnessSubscriptionUsage(this.subscriptionUsage, subscriptionUsage);
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3814
3976
|
}
|
|
3815
3977
|
/**
|
|
3816
3978
|
* Waits until the Claude Code session can be resumed, keeping terminal status clear.
|
|
@@ -4777,6 +4939,292 @@ function resolveCodexPricing(modelName) {
|
|
|
4777
4939
|
return (_c = OPENAI_MODELS.find((model) => model.modelName === CODEX_FALLBACK_PRICING_MODEL)) === null || _c === void 0 ? void 0 : _c.pricing;
|
|
4778
4940
|
}
|
|
4779
4941
|
|
|
4942
|
+
/**
|
|
4943
|
+
* Arguments which start the Codex app server over JSON-RPC stdio.
|
|
4944
|
+
*/
|
|
4945
|
+
const CODEX_APP_SERVER_ARGUMENTS = ['app-server', '--stdio'];
|
|
4946
|
+
/**
|
|
4947
|
+
* Maximum time spent waiting for the optional Codex account-rate-limit snapshot.
|
|
4948
|
+
*/
|
|
4949
|
+
const CODEX_SUBSCRIPTION_USAGE_REQUEST_TIMEOUT_MS = 10 * 1000;
|
|
4950
|
+
/**
|
|
4951
|
+
* JSON-RPC request id used for the required app-server initialization handshake.
|
|
4952
|
+
*/
|
|
4953
|
+
const INITIALIZE_REQUEST_ID = 1;
|
|
4954
|
+
/**
|
|
4955
|
+
* JSON-RPC request id used for the Codex account rate-limit snapshot.
|
|
4956
|
+
*/
|
|
4957
|
+
const RATE_LIMITS_REQUEST_ID = 2;
|
|
4958
|
+
/**
|
|
4959
|
+
* Client name sent to the Codex app server while Promptbook reads subscription limits.
|
|
4960
|
+
*/
|
|
4961
|
+
const PROMPTBOOK_CODEX_APP_SERVER_CLIENT_NAME = 'ptbk-coder';
|
|
4962
|
+
/**
|
|
4963
|
+
* Client version sent to the Codex app server while Promptbook reads subscription limits.
|
|
4964
|
+
*
|
|
4965
|
+
* The app-server protocol only requires a non-empty version string, and the CLI's own version is intentionally not
|
|
4966
|
+
* coupled to Promptbook's package version.
|
|
4967
|
+
*/
|
|
4968
|
+
const PROMPTBOOK_CODEX_APP_SERVER_CLIENT_VERSION = '1';
|
|
4969
|
+
/**
|
|
4970
|
+
* Reads the current Codex subscription limit windows through its local app-server protocol.
|
|
4971
|
+
*
|
|
4972
|
+
* Codex versions without this optional protocol, API-key sessions, and transient account failures simply provide no
|
|
4973
|
+
* snapshot. Subscription usage is contextual UI information, so it must never make a coding prompt fail.
|
|
4974
|
+
*
|
|
4975
|
+
* @private internal utility of the OpenAI Codex runner
|
|
4976
|
+
*/
|
|
4977
|
+
async function getCodexSubscriptionUsage(codexCommand) {
|
|
4978
|
+
try {
|
|
4979
|
+
return await requestCodexSubscriptionUsage(codexCommand);
|
|
4980
|
+
}
|
|
4981
|
+
catch (_a) {
|
|
4982
|
+
return undefined;
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
/**
|
|
4986
|
+
* Performs the small Codex app-server JSON-RPC exchange which reads rate limits.
|
|
4987
|
+
*
|
|
4988
|
+
* The app server requires a completed `initialize` handshake before it accepts account methods. Closing standard input
|
|
4989
|
+
* after the response lets the short-lived helper exit without sharing the lifecycle of the coding process itself.
|
|
4990
|
+
*
|
|
4991
|
+
* @private helper of `getCodexSubscriptionUsage`
|
|
4992
|
+
*/
|
|
4993
|
+
function requestCodexSubscriptionUsage(codexCommand) {
|
|
4994
|
+
return new Promise((resolve) => {
|
|
4995
|
+
const codexAppServerProcess = spawn(codexCommand, CODEX_APP_SERVER_ARGUMENTS, {
|
|
4996
|
+
shell: process.platform === 'win32',
|
|
4997
|
+
stdio: 'pipe',
|
|
4998
|
+
});
|
|
4999
|
+
const outputReader = createInterface({ input: codexAppServerProcess.stdout });
|
|
5000
|
+
let requestTimeout;
|
|
5001
|
+
let isSettled = false;
|
|
5002
|
+
// Draining stderr prevents a protocol diagnostic from blocking the short-lived child process. The response is
|
|
5003
|
+
// deliberately not surfaced because missing usage must not distract from an otherwise healthy coding run.
|
|
5004
|
+
codexAppServerProcess.stderr.resume();
|
|
5005
|
+
codexAppServerProcess.stdin.on('error', () => undefined);
|
|
5006
|
+
/**
|
|
5007
|
+
* Stops the helper process and settles this optional request exactly once.
|
|
5008
|
+
*/
|
|
5009
|
+
const settle = (subscriptionUsage) => {
|
|
5010
|
+
if (isSettled) {
|
|
5011
|
+
return;
|
|
5012
|
+
}
|
|
5013
|
+
isSettled = true;
|
|
5014
|
+
if (requestTimeout) {
|
|
5015
|
+
clearTimeout(requestTimeout);
|
|
5016
|
+
}
|
|
5017
|
+
outputReader.close();
|
|
5018
|
+
codexAppServerProcess.stdin.end();
|
|
5019
|
+
if (codexAppServerProcess.exitCode === null && !codexAppServerProcess.killed) {
|
|
5020
|
+
codexAppServerProcess.kill();
|
|
5021
|
+
}
|
|
5022
|
+
resolve(subscriptionUsage);
|
|
5023
|
+
};
|
|
5024
|
+
/**
|
|
5025
|
+
* Sends one JSON-RPC message to the Codex app server.
|
|
5026
|
+
*/
|
|
5027
|
+
const sendJsonRpcMessage = (message) => {
|
|
5028
|
+
try {
|
|
5029
|
+
codexAppServerProcess.stdin.write(`${JSON.stringify(message)}\n`);
|
|
5030
|
+
}
|
|
5031
|
+
catch (_a) {
|
|
5032
|
+
settle(undefined);
|
|
5033
|
+
}
|
|
5034
|
+
};
|
|
5035
|
+
requestTimeout = setTimeout(() => settle(undefined), CODEX_SUBSCRIPTION_USAGE_REQUEST_TIMEOUT_MS);
|
|
5036
|
+
codexAppServerProcess.on('error', () => settle(undefined));
|
|
5037
|
+
codexAppServerProcess.on('close', () => settle(undefined));
|
|
5038
|
+
outputReader.on('line', (line) => {
|
|
5039
|
+
const response = parseJsonRecord(line);
|
|
5040
|
+
if (!response) {
|
|
5041
|
+
return;
|
|
5042
|
+
}
|
|
5043
|
+
if (response.id === INITIALIZE_REQUEST_ID) {
|
|
5044
|
+
if (!isJsonRecord(response.result)) {
|
|
5045
|
+
settle(undefined);
|
|
5046
|
+
return;
|
|
5047
|
+
}
|
|
5048
|
+
sendJsonRpcMessage({ jsonrpc: '2.0', method: 'initialized' });
|
|
5049
|
+
sendJsonRpcMessage({
|
|
5050
|
+
jsonrpc: '2.0',
|
|
5051
|
+
id: RATE_LIMITS_REQUEST_ID,
|
|
5052
|
+
method: 'account/rateLimits/read',
|
|
5053
|
+
});
|
|
5054
|
+
return;
|
|
5055
|
+
}
|
|
5056
|
+
if (response.id === RATE_LIMITS_REQUEST_ID) {
|
|
5057
|
+
settle(buildCodexSubscriptionUsage(response.result));
|
|
5058
|
+
}
|
|
5059
|
+
});
|
|
5060
|
+
sendJsonRpcMessage({
|
|
5061
|
+
jsonrpc: '2.0',
|
|
5062
|
+
id: INITIALIZE_REQUEST_ID,
|
|
5063
|
+
method: 'initialize',
|
|
5064
|
+
params: {
|
|
5065
|
+
clientInfo: {
|
|
5066
|
+
name: PROMPTBOOK_CODEX_APP_SERVER_CLIENT_NAME,
|
|
5067
|
+
version: PROMPTBOOK_CODEX_APP_SERVER_CLIENT_VERSION,
|
|
5068
|
+
},
|
|
5069
|
+
capabilities: {
|
|
5070
|
+
experimentalApi: true,
|
|
5071
|
+
},
|
|
5072
|
+
},
|
|
5073
|
+
});
|
|
5074
|
+
});
|
|
5075
|
+
}
|
|
5076
|
+
/**
|
|
5077
|
+
* Converts the raw Codex app-server rate-limit response into Promptbook's harness-neutral subscription snapshot.
|
|
5078
|
+
*
|
|
5079
|
+
* Modern Codex versions can report several metered buckets, while older versions expose a single compatibility
|
|
5080
|
+
* bucket. In both cases every primary and secondary rolling window is preserved for the terminal dashboard.
|
|
5081
|
+
*
|
|
5082
|
+
* @private internal utility of the OpenAI Codex runner
|
|
5083
|
+
*/
|
|
5084
|
+
function buildCodexSubscriptionUsage(response) {
|
|
5085
|
+
if (!isJsonRecord(response)) {
|
|
5086
|
+
return undefined;
|
|
5087
|
+
}
|
|
5088
|
+
const snapshots = resolveCodexRateLimitSnapshots(response);
|
|
5089
|
+
const hasMultipleSnapshots = snapshots.length > 1;
|
|
5090
|
+
const limits = snapshots.flatMap(({ identifier, value }) => buildCodexSubscriptionUsageLimits({
|
|
5091
|
+
snapshot: value,
|
|
5092
|
+
identifier,
|
|
5093
|
+
hasMultipleSnapshots,
|
|
5094
|
+
}));
|
|
5095
|
+
return limits.length === 0 ? undefined : { limits };
|
|
5096
|
+
}
|
|
5097
|
+
/**
|
|
5098
|
+
* Resolves either Codex's multi-bucket rate-limit response or its compatible single-bucket fallback.
|
|
5099
|
+
*
|
|
5100
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5101
|
+
*/
|
|
5102
|
+
function resolveCodexRateLimitSnapshots(response) {
|
|
5103
|
+
const rateLimitsByLimitId = response.rateLimitsByLimitId;
|
|
5104
|
+
if (isJsonRecord(rateLimitsByLimitId)) {
|
|
5105
|
+
const snapshots = Object.entries(rateLimitsByLimitId)
|
|
5106
|
+
.filter(([, value]) => isJsonRecord(value))
|
|
5107
|
+
.map(([identifier, value]) => ({ identifier, value: value }));
|
|
5108
|
+
if (snapshots.length > 0) {
|
|
5109
|
+
return snapshots;
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
return isJsonRecord(response.rateLimits) ? [{ value: response.rateLimits }] : [];
|
|
5113
|
+
}
|
|
5114
|
+
/**
|
|
5115
|
+
* Creates every displayed rolling window from one Codex rate-limit bucket.
|
|
5116
|
+
*
|
|
5117
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5118
|
+
*/
|
|
5119
|
+
function buildCodexSubscriptionUsageLimits(options) {
|
|
5120
|
+
var _a;
|
|
5121
|
+
const { snapshot, identifier, hasMultipleSnapshots } = options;
|
|
5122
|
+
const bucketLabel = hasMultipleSnapshots ? (_a = readString(snapshot.limitName)) !== null && _a !== void 0 ? _a : identifier : undefined;
|
|
5123
|
+
return [
|
|
5124
|
+
buildCodexSubscriptionUsageLimit(snapshot.primary, bucketLabel, 'Primary'),
|
|
5125
|
+
buildCodexSubscriptionUsageLimit(snapshot.secondary, bucketLabel, 'Secondary'),
|
|
5126
|
+
].filter((limit) => limit !== undefined);
|
|
5127
|
+
}
|
|
5128
|
+
/**
|
|
5129
|
+
* Converts one raw Codex rate-limit window to a displayable subscription usage limit.
|
|
5130
|
+
*
|
|
5131
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5132
|
+
*/
|
|
5133
|
+
function buildCodexSubscriptionUsageLimit(rawWindow, bucketLabel, fallbackWindowLabel) {
|
|
5134
|
+
var _a;
|
|
5135
|
+
if (!isJsonRecord(rawWindow)) {
|
|
5136
|
+
return undefined;
|
|
5137
|
+
}
|
|
5138
|
+
const usedPercentage = readPercentage(rawWindow.usedPercent);
|
|
5139
|
+
if (usedPercentage === undefined) {
|
|
5140
|
+
return undefined;
|
|
5141
|
+
}
|
|
5142
|
+
const windowLabel = (_a = formatCodexRateLimitWindowDuration(rawWindow.windowDurationMins)) !== null && _a !== void 0 ? _a : fallbackWindowLabel;
|
|
5143
|
+
const label = bucketLabel ? `${bucketLabel} ${windowLabel}` : windowLabel;
|
|
5144
|
+
const resetsAt = readUnixTimestamp(rawWindow.resetsAt);
|
|
5145
|
+
return {
|
|
5146
|
+
label,
|
|
5147
|
+
usedPercentage,
|
|
5148
|
+
...(resetsAt !== undefined && { resetsAt }),
|
|
5149
|
+
};
|
|
5150
|
+
}
|
|
5151
|
+
/**
|
|
5152
|
+
* Formats the duration of one Codex rolling rate-limit window for compact terminal display.
|
|
5153
|
+
*
|
|
5154
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5155
|
+
*/
|
|
5156
|
+
function formatCodexRateLimitWindowDuration(value) {
|
|
5157
|
+
const durationMinutes = readNonNegativeFiniteNumber(value);
|
|
5158
|
+
if (durationMinutes === undefined || durationMinutes === 0) {
|
|
5159
|
+
return undefined;
|
|
5160
|
+
}
|
|
5161
|
+
const MINUTES_PER_HOUR = 60;
|
|
5162
|
+
const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
|
|
5163
|
+
if (durationMinutes % MINUTES_PER_DAY === 0) {
|
|
5164
|
+
return `${durationMinutes / MINUTES_PER_DAY}d`;
|
|
5165
|
+
}
|
|
5166
|
+
if (durationMinutes % MINUTES_PER_HOUR === 0) {
|
|
5167
|
+
return `${durationMinutes / MINUTES_PER_HOUR}h`;
|
|
5168
|
+
}
|
|
5169
|
+
return `${durationMinutes}m`;
|
|
5170
|
+
}
|
|
5171
|
+
/**
|
|
5172
|
+
* Parses one JSON-RPC line when it is an object.
|
|
5173
|
+
*
|
|
5174
|
+
* @private helper of `getCodexSubscriptionUsage`
|
|
5175
|
+
*/
|
|
5176
|
+
function parseJsonRecord(line) {
|
|
5177
|
+
try {
|
|
5178
|
+
const value = JSON.parse(line);
|
|
5179
|
+
return isJsonRecord(value) ? value : undefined;
|
|
5180
|
+
}
|
|
5181
|
+
catch (_a) {
|
|
5182
|
+
return undefined;
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
/**
|
|
5186
|
+
* Checks whether a value is a JSON object.
|
|
5187
|
+
*
|
|
5188
|
+
* @private helper of `getCodexSubscriptionUsage`
|
|
5189
|
+
*/
|
|
5190
|
+
function isJsonRecord(value) {
|
|
5191
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5192
|
+
}
|
|
5193
|
+
/**
|
|
5194
|
+
* Reads one non-empty string from an untrusted JSON object.
|
|
5195
|
+
*
|
|
5196
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5197
|
+
*/
|
|
5198
|
+
function readString(value) {
|
|
5199
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
5200
|
+
}
|
|
5201
|
+
/**
|
|
5202
|
+
* Reads a finite number which cannot be negative.
|
|
5203
|
+
*
|
|
5204
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5205
|
+
*/
|
|
5206
|
+
function readNonNegativeFiniteNumber(value) {
|
|
5207
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
5208
|
+
}
|
|
5209
|
+
/**
|
|
5210
|
+
* Reads a valid percentage from the Codex app-server response.
|
|
5211
|
+
*
|
|
5212
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5213
|
+
*/
|
|
5214
|
+
function readPercentage(value) {
|
|
5215
|
+
const percentage = readNonNegativeFiniteNumber(value);
|
|
5216
|
+
return percentage !== undefined && percentage <= 100 ? percentage : undefined;
|
|
5217
|
+
}
|
|
5218
|
+
/**
|
|
5219
|
+
* Reads a Unix timestamp in seconds from the Codex app-server response.
|
|
5220
|
+
*
|
|
5221
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
5222
|
+
*/
|
|
5223
|
+
function readUnixTimestamp(value) {
|
|
5224
|
+
const timestamp = readNonNegativeFiniteNumber(value);
|
|
5225
|
+
return timestamp !== undefined && timestamp > 0 ? timestamp : undefined;
|
|
5226
|
+
}
|
|
5227
|
+
|
|
4780
5228
|
/**
|
|
4781
5229
|
* Detects which login method Codex used from the captured CLI output.
|
|
4782
5230
|
*
|
|
@@ -4964,6 +5412,15 @@ class OpenAiCodexRunner {
|
|
|
4964
5412
|
jitterRatio: RATE_LIMIT_BACKOFF_JITTER_RATIO,
|
|
4965
5413
|
});
|
|
4966
5414
|
}
|
|
5415
|
+
/**
|
|
5416
|
+
* Reads the current ChatGPT subscription quota snapshot through Codex's local app server.
|
|
5417
|
+
*
|
|
5418
|
+
* API-key sessions and unsupported Codex versions simply return no snapshot, which keeps this optional dashboard
|
|
5419
|
+
* information from affecting the actual prompt execution.
|
|
5420
|
+
*/
|
|
5421
|
+
async getSubscriptionUsage() {
|
|
5422
|
+
return await getCodexSubscriptionUsage(this.options.codexCommand);
|
|
5423
|
+
}
|
|
4967
5424
|
/**
|
|
4968
5425
|
* Runs the Codex prompt in a temporary script and waits for completion output.
|
|
4969
5426
|
*/
|