mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(app-server): expose rate-limit reset credits (#28143)
## Why Codex users can earn personal rate-limit reset credits, but app-server clients do not currently have an API for reading or redeeming them. This adds the backend and protocol foundation used by the `/usage` TUI flow in #28154. ## What changed - Extend `account/rateLimits/read` with a nullable `rateLimitResetCredits` summary sourced from the existing usage response. - Add backend-client and app-server support for consuming a reset with a caller-generated idempotency key. A UUID is recommended, and clients reuse the same key when retrying the same logical reset. - Return only the consume `outcome`; clients refetch `account/rateLimits/read` for updated window state. - Document the response field and each consume outcome, and regenerate the JSON and TypeScript schema fixtures. - Clarify in `AGENTS.md` that new app-server string enum values use camelCase on the wire. - Update the existing TUI response fixture for the expanded protocol shape. - Add coverage for authentication, response mapping, backend failures, consume outcomes, and request timeout behavior. ## Validation - `just test -p codex-app-server-protocol` — 231 passed. - `just test -p codex-backend-client` — 14 passed. - Focused `codex-app-server` reset-credit tests — 5 passed. - Focused `codex-tui` protocol response fixture test — passed. - `just fix -p codex-backend-client -p codex-app-server-protocol -p codex-app-server` — passed. - `just fmt` — passed.
This commit is contained in:
@@ -554,6 +554,18 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ConsumeAccountRateLimitResetCreditParams": {
|
||||
"properties": {
|
||||
"idempotencyKey": {
|
||||
"description": "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"idempotencyKey"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ContentItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -6179,6 +6191,30 @@
|
||||
"title": "Account/rateLimits/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"account/rateLimitResetCredit/consume"
|
||||
],
|
||||
"title": "Account/rateLimitResetCredit/consumeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ConsumeAccountRateLimitResetCreditParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Account/rateLimitResetCredit/consumeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
+105
@@ -1857,6 +1857,30 @@
|
||||
"title": "Account/rateLimits/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"account/rateLimitResetCredit/consume"
|
||||
],
|
||||
"title": "Account/rateLimitResetCredit/consumeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ConsumeAccountRateLimitResetCreditParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Account/rateLimitResetCredit/consumeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -8453,6 +8477,65 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ConsumeAccountRateLimitResetCreditOutcome": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A reset credit was consumed and the eligible rate-limit windows were reset.",
|
||||
"enum": [
|
||||
"reset"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "No current rate-limit window is eligible for a reset.",
|
||||
"enum": [
|
||||
"nothingToReset"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The account has no earned reset credits available.",
|
||||
"enum": [
|
||||
"noCredit"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The same idempotency key already completed a reset successfully.",
|
||||
"enum": [
|
||||
"alreadyRedeemed"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ConsumeAccountRateLimitResetCreditParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"idempotencyKey": {
|
||||
"description": "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"idempotencyKey"
|
||||
],
|
||||
"title": "ConsumeAccountRateLimitResetCreditParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ConsumeAccountRateLimitResetCreditResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"outcome": {
|
||||
"$ref": "#/definitions/v2/ConsumeAccountRateLimitResetCreditOutcome"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"outcome"
|
||||
],
|
||||
"title": "ConsumeAccountRateLimitResetCreditResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ContentItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -9990,6 +10073,16 @@
|
||||
"GetAccountRateLimitsResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"rateLimitResetCredits": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/RateLimitResetCreditsSummary"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimits": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -13893,6 +13986,18 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitResetCreditsSummary": {
|
||||
"properties": {
|
||||
"availableCount": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"availableCount"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
|
||||
+105
@@ -2841,6 +2841,30 @@
|
||||
"title": "Account/rateLimits/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"account/rateLimitResetCredit/consume"
|
||||
],
|
||||
"title": "Account/rateLimitResetCredit/consumeRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ConsumeAccountRateLimitResetCreditParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Account/rateLimitResetCredit/consumeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -4766,6 +4790,65 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ConsumeAccountRateLimitResetCreditOutcome": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A reset credit was consumed and the eligible rate-limit windows were reset.",
|
||||
"enum": [
|
||||
"reset"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "No current rate-limit window is eligible for a reset.",
|
||||
"enum": [
|
||||
"nothingToReset"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The account has no earned reset credits available.",
|
||||
"enum": [
|
||||
"noCredit"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The same idempotency key already completed a reset successfully.",
|
||||
"enum": [
|
||||
"alreadyRedeemed"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ConsumeAccountRateLimitResetCreditParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"idempotencyKey": {
|
||||
"description": "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"idempotencyKey"
|
||||
],
|
||||
"title": "ConsumeAccountRateLimitResetCreditParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ConsumeAccountRateLimitResetCreditResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"outcome": {
|
||||
"$ref": "#/definitions/ConsumeAccountRateLimitResetCreditOutcome"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"outcome"
|
||||
],
|
||||
"title": "ConsumeAccountRateLimitResetCreditResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ContentItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -6414,6 +6497,16 @@
|
||||
"GetAccountRateLimitsResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"rateLimitResetCredits": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RateLimitResetCreditsSummary"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimits": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -10366,6 +10459,18 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitResetCreditsSummary": {
|
||||
"properties": {
|
||||
"availableCount": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"availableCount"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"idempotencyKey": {
|
||||
"description": "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"idempotencyKey"
|
||||
],
|
||||
"title": "ConsumeAccountRateLimitResetCreditParams",
|
||||
"type": "object"
|
||||
}
|
||||
Generated
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"ConsumeAccountRateLimitResetCreditOutcome": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A reset credit was consumed and the eligible rate-limit windows were reset.",
|
||||
"enum": [
|
||||
"reset"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "No current rate-limit window is eligible for a reset.",
|
||||
"enum": [
|
||||
"nothingToReset"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The account has no earned reset credits available.",
|
||||
"enum": [
|
||||
"noCredit"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "The same idempotency key already completed a reset successfully.",
|
||||
"enum": [
|
||||
"alreadyRedeemed"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"outcome": {
|
||||
"$ref": "#/definitions/ConsumeAccountRateLimitResetCreditOutcome"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"outcome"
|
||||
],
|
||||
"title": "ConsumeAccountRateLimitResetCreditResponse",
|
||||
"type": "object"
|
||||
}
|
||||
+22
@@ -49,6 +49,18 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitResetCreditsSummary": {
|
||||
"properties": {
|
||||
"availableCount": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"availableCount"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
@@ -179,6 +191,16 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"rateLimitResetCredits": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RateLimitResetCreditsSummary"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimits": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
Generated
+5
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ConsumeAccountRateLimitResetCreditOutcome = "reset" | "nothingToReset" | "noCredit" | "alreadyRedeemed";
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ConsumeAccountRateLimitResetCreditParams = {
|
||||
/**
|
||||
* Identifies one logical reset attempt. A UUID is recommended; reuse the same value when
|
||||
* retrying that attempt.
|
||||
*/
|
||||
idempotencyKey: string, };
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ConsumeAccountRateLimitResetCreditOutcome } from "./ConsumeAccountRateLimitResetCreditOutcome";
|
||||
|
||||
export type ConsumeAccountRateLimitResetCreditResponse = { outcome: ConsumeAccountRateLimitResetCreditOutcome, };
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { RateLimitResetCreditsSummary } from "./RateLimitResetCreditsSummary";
|
||||
import type { RateLimitSnapshot } from "./RateLimitSnapshot";
|
||||
|
||||
export type GetAccountRateLimitsResponse = {
|
||||
@@ -11,4 +12,4 @@ rateLimits: RateLimitSnapshot,
|
||||
/**
|
||||
* Multi-bucket view keyed by metered `limit_id` (for example, `codex`).
|
||||
*/
|
||||
rateLimitsByLimitId: { [key in string]?: RateLimitSnapshot } | null, };
|
||||
rateLimitsByLimitId: { [key in string]?: RateLimitSnapshot } | null, rateLimitResetCredits: RateLimitResetCreditsSummary | null, };
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type RateLimitResetCreditsSummary = { availableCount: bigint, };
|
||||
@@ -85,6 +85,9 @@ export type { ConfigWarningNotification } from "./ConfigWarningNotification";
|
||||
export type { ConfigWriteResponse } from "./ConfigWriteResponse";
|
||||
export type { ConfiguredHookHandler } from "./ConfiguredHookHandler";
|
||||
export type { ConfiguredHookMatcherGroup } from "./ConfiguredHookMatcherGroup";
|
||||
export type { ConsumeAccountRateLimitResetCreditOutcome } from "./ConsumeAccountRateLimitResetCreditOutcome";
|
||||
export type { ConsumeAccountRateLimitResetCreditParams } from "./ConsumeAccountRateLimitResetCreditParams";
|
||||
export type { ConsumeAccountRateLimitResetCreditResponse } from "./ConsumeAccountRateLimitResetCreditResponse";
|
||||
export type { ContextCompactedNotification } from "./ContextCompactedNotification";
|
||||
export type { CreditsSnapshot } from "./CreditsSnapshot";
|
||||
export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification";
|
||||
@@ -323,6 +326,7 @@ export type { ProcessOutputDeltaNotification } from "./ProcessOutputDeltaNotific
|
||||
export type { ProcessOutputStream } from "./ProcessOutputStream";
|
||||
export type { ProcessTerminalSize } from "./ProcessTerminalSize";
|
||||
export type { RateLimitReachedType } from "./RateLimitReachedType";
|
||||
export type { RateLimitResetCreditsSummary } from "./RateLimitResetCreditsSummary";
|
||||
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
|
||||
export type { RateLimitWindow } from "./RateLimitWindow";
|
||||
export type { RawResponseItemCompletedNotification } from "./RawResponseItemCompletedNotification";
|
||||
|
||||
@@ -1004,6 +1004,12 @@ client_request_definitions! {
|
||||
response: v2::GetAccountRateLimitsResponse,
|
||||
},
|
||||
|
||||
ConsumeAccountRateLimitResetCredit => "account/rateLimitResetCredit/consume" {
|
||||
params: v2::ConsumeAccountRateLimitResetCreditParams,
|
||||
serialization: global("account-auth"),
|
||||
response: v2::ConsumeAccountRateLimitResetCreditResponse,
|
||||
},
|
||||
|
||||
GetAccountTokenUsage => "account/usage/read" {
|
||||
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
|
||||
serialization: None,
|
||||
|
||||
@@ -256,6 +256,44 @@ pub struct GetAccountRateLimitsResponse {
|
||||
pub rate_limits: RateLimitSnapshot,
|
||||
/// Multi-bucket view keyed by metered `limit_id` (for example, `codex`).
|
||||
pub rate_limits_by_limit_id: Option<HashMap<String, RateLimitSnapshot>>,
|
||||
pub rate_limit_reset_credits: Option<RateLimitResetCreditsSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct RateLimitResetCreditsSummary {
|
||||
pub available_count: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConsumeAccountRateLimitResetCreditParams {
|
||||
/// Identifies one logical reset attempt. A UUID is recommended; reuse the same value when
|
||||
/// retrying that attempt.
|
||||
pub idempotency_key: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConsumeAccountRateLimitResetCreditResponse {
|
||||
pub outcome: ConsumeAccountRateLimitResetCreditOutcome,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/", rename_all = "camelCase")]
|
||||
pub enum ConsumeAccountRateLimitResetCreditOutcome {
|
||||
/// A reset credit was consumed and the eligible rate-limit windows were reset.
|
||||
Reset,
|
||||
/// No current rate-limit window is eligible for a reset.
|
||||
NothingToReset,
|
||||
/// The account has no earned reset credits available.
|
||||
NoCredit,
|
||||
/// The same idempotency key already completed a reset successfully.
|
||||
AlreadyRedeemed,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
|
||||
Reference in New Issue
Block a user