mirror of
https://github.com/openai/codex.git
synced 2026-06-03 03:41:58 +00:00
Compare commits
2 Commits
fcoury/mul
...
dev/efraze
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad1facce2b | ||
|
|
d322741267 |
@@ -1421,6 +1421,25 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GroupSpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"details": {
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"details",
|
||||
"groupId",
|
||||
"groupName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GuardianApprovalReview": {
|
||||
"description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
|
||||
"properties": {
|
||||
@@ -2598,6 +2617,16 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spendControl": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -2854,6 +2883,137 @@
|
||||
"description": "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.",
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remaining": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAfterSeconds": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAt": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
},
|
||||
"usedPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remaining",
|
||||
"remainingPercent",
|
||||
"resetAfterSeconds",
|
||||
"resetAt",
|
||||
"used",
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitType": {
|
||||
"enum": [
|
||||
"individual",
|
||||
"group_default",
|
||||
"workspace_default",
|
||||
"role_budget",
|
||||
"group_shared",
|
||||
"workspace_shared"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SpendControlSnapshot": {
|
||||
"properties": {
|
||||
"groupDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"groupSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reached": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reachedLimitType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roleBudgetLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reached"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -9226,6 +9226,25 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GroupSpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"details": {
|
||||
"$ref": "#/definitions/v2/SpendControlLimitSnapshot"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"details",
|
||||
"groupId",
|
||||
"groupName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GuardianApprovalReview": {
|
||||
"description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
|
||||
"properties": {
|
||||
@@ -13028,6 +13047,16 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spendControl": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/SpendControlSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -14971,6 +15000,137 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remaining": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAfterSeconds": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAt": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
},
|
||||
"usedPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remaining",
|
||||
"remainingPercent",
|
||||
"resetAfterSeconds",
|
||||
"resetAt",
|
||||
"used",
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitType": {
|
||||
"enum": [
|
||||
"individual",
|
||||
"group_default",
|
||||
"workspace_default",
|
||||
"role_budget",
|
||||
"group_shared",
|
||||
"workspace_shared"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SpendControlSnapshot": {
|
||||
"properties": {
|
||||
"groupDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"groupSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reached": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reachedLimitType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/SpendControlLimitType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roleBudgetLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reached"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -5726,6 +5726,25 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GroupSpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"details": {
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"details",
|
||||
"groupId",
|
||||
"groupName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GuardianApprovalReview": {
|
||||
"description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
|
||||
"properties": {
|
||||
@@ -9577,6 +9596,16 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spendControl": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -12795,6 +12824,137 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remaining": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAfterSeconds": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAt": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
},
|
||||
"usedPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remaining",
|
||||
"remainingPercent",
|
||||
"resetAfterSeconds",
|
||||
"resetAt",
|
||||
"used",
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitType": {
|
||||
"enum": [
|
||||
"individual",
|
||||
"group_default",
|
||||
"workspace_default",
|
||||
"role_budget",
|
||||
"group_shared",
|
||||
"workspace_shared"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SpendControlSnapshot": {
|
||||
"properties": {
|
||||
"groupDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"groupSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reached": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reachedLimitType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roleBudgetLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reached"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -22,6 +22,25 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GroupSpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"details": {
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"details",
|
||||
"groupId",
|
||||
"groupName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PlanType": {
|
||||
"enum": [
|
||||
"free",
|
||||
@@ -112,6 +131,16 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spendControl": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -141,6 +170,137 @@
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remaining": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAfterSeconds": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAt": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
},
|
||||
"usedPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remaining",
|
||||
"remainingPercent",
|
||||
"resetAfterSeconds",
|
||||
"resetAt",
|
||||
"used",
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitType": {
|
||||
"enum": [
|
||||
"individual",
|
||||
"group_default",
|
||||
"workspace_default",
|
||||
"role_budget",
|
||||
"group_shared",
|
||||
"workspace_shared"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SpendControlSnapshot": {
|
||||
"properties": {
|
||||
"groupDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"groupSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reached": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reachedLimitType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roleBudgetLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reached"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -22,6 +22,25 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GroupSpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"details": {
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"details",
|
||||
"groupId",
|
||||
"groupName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PlanType": {
|
||||
"enum": [
|
||||
"free",
|
||||
@@ -112,6 +131,16 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spendControl": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -141,6 +170,137 @@
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remaining": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAfterSeconds": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetAt": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
},
|
||||
"usedPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remaining",
|
||||
"remainingPercent",
|
||||
"resetAfterSeconds",
|
||||
"resetAt",
|
||||
"used",
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitType": {
|
||||
"enum": [
|
||||
"individual",
|
||||
"group_default",
|
||||
"workspace_default",
|
||||
"role_budget",
|
||||
"group_shared",
|
||||
"workspace_shared"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SpendControlSnapshot": {
|
||||
"properties": {
|
||||
"groupDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"groupSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GroupSpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reached": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reachedLimitType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roleBudgetLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceDefaultLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspaceSharedLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reached"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/GroupSpendControlLimitSnapshot.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/GroupSpendControlLimitSnapshot.ts
generated
Normal file
@@ -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 { SpendControlLimitSnapshot } from "./SpendControlLimitSnapshot";
|
||||
|
||||
export type GroupSpendControlLimitSnapshot = { groupId: string, groupName: string, details: SpendControlLimitSnapshot, };
|
||||
@@ -5,5 +5,6 @@ import type { PlanType } from "../PlanType";
|
||||
import type { CreditsSnapshot } from "./CreditsSnapshot";
|
||||
import type { RateLimitReachedType } from "./RateLimitReachedType";
|
||||
import type { RateLimitWindow } from "./RateLimitWindow";
|
||||
import type { SpendControlSnapshot } from "./SpendControlSnapshot";
|
||||
|
||||
export type RateLimitSnapshot = { limitId: string | null, limitName: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, planType: PlanType | null, rateLimitReachedType: RateLimitReachedType | null, };
|
||||
export type RateLimitSnapshot = { limitId: string | null, limitName: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, spendControl: SpendControlSnapshot | null, planType: PlanType | null, rateLimitReachedType: RateLimitReachedType | null, };
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/SpendControlLimitSnapshot.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/SpendControlLimitSnapshot.ts
generated
Normal file
@@ -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 SpendControlLimitSnapshot = { limit: string, used: string, remaining: string, usedPercent: number, remainingPercent: number, resetAfterSeconds: number, resetAt: number, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/SpendControlLimitType.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/SpendControlLimitType.ts
generated
Normal file
@@ -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 SpendControlLimitType = "individual" | "group_default" | "workspace_default" | "role_budget" | "group_shared" | "workspace_shared";
|
||||
8
codex-rs/app-server-protocol/schema/typescript/v2/SpendControlSnapshot.ts
generated
Normal file
8
codex-rs/app-server-protocol/schema/typescript/v2/SpendControlSnapshot.ts
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
// 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 { GroupSpendControlLimitSnapshot } from "./GroupSpendControlLimitSnapshot";
|
||||
import type { SpendControlLimitSnapshot } from "./SpendControlLimitSnapshot";
|
||||
import type { SpendControlLimitType } from "./SpendControlLimitType";
|
||||
|
||||
export type SpendControlSnapshot = { reached: boolean, reachedLimitType: SpendControlLimitType | null, individualLimit: SpendControlLimitSnapshot | null, groupDefaultLimit: GroupSpendControlLimitSnapshot | null, workspaceDefaultLimit: SpendControlLimitSnapshot | null, roleBudgetLimit: SpendControlLimitSnapshot | null, groupSharedLimit: GroupSpendControlLimitSnapshot | null, workspaceSharedLimit: SpendControlLimitSnapshot | null, };
|
||||
@@ -138,6 +138,7 @@ export type { GetAccountRateLimitsResponse } from "./GetAccountRateLimitsRespons
|
||||
export type { GetAccountResponse } from "./GetAccountResponse";
|
||||
export type { GitInfo } from "./GitInfo";
|
||||
export type { GrantedPermissionProfile } from "./GrantedPermissionProfile";
|
||||
export type { GroupSpendControlLimitSnapshot } from "./GroupSpendControlLimitSnapshot";
|
||||
export type { GuardianApprovalReview } from "./GuardianApprovalReview";
|
||||
export type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewAction";
|
||||
export type { GuardianApprovalReviewStatus } from "./GuardianApprovalReviewStatus";
|
||||
@@ -344,6 +345,9 @@ export type { SkillsListEntry } from "./SkillsListEntry";
|
||||
export type { SkillsListParams } from "./SkillsListParams";
|
||||
export type { SkillsListResponse } from "./SkillsListResponse";
|
||||
export type { SortDirection } from "./SortDirection";
|
||||
export type { SpendControlLimitSnapshot } from "./SpendControlLimitSnapshot";
|
||||
export type { SpendControlLimitType } from "./SpendControlLimitType";
|
||||
export type { SpendControlSnapshot } from "./SpendControlSnapshot";
|
||||
export type { SubagentMigration } from "./SubagentMigration";
|
||||
export type { TerminalInteractionNotification } from "./TerminalInteractionNotification";
|
||||
export type { TextElement } from "./TextElement";
|
||||
|
||||
@@ -3,9 +3,13 @@ use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::account::ProviderAccount;
|
||||
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
||||
use codex_protocol::protocol::GroupSpendControlLimitSnapshot as CoreGroupSpendControlLimitSnapshot;
|
||||
use codex_protocol::protocol::RateLimitReachedType as CoreRateLimitReachedType;
|
||||
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
|
||||
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
|
||||
use codex_protocol::protocol::SpendControlLimitSnapshot as CoreSpendControlLimitSnapshot;
|
||||
use codex_protocol::protocol::SpendControlLimitType as CoreSpendControlLimitType;
|
||||
use codex_protocol::protocol::SpendControlSnapshot as CoreSpendControlSnapshot;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -260,6 +264,7 @@ pub struct RateLimitSnapshot {
|
||||
pub primary: Option<RateLimitWindow>,
|
||||
pub secondary: Option<RateLimitWindow>,
|
||||
pub credits: Option<CreditsSnapshot>,
|
||||
pub spend_control: Option<SpendControlSnapshot>,
|
||||
pub plan_type: Option<PlanType>,
|
||||
pub rate_limit_reached_type: Option<RateLimitReachedType>,
|
||||
}
|
||||
@@ -272,6 +277,7 @@ impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
|
||||
primary: value.primary.map(RateLimitWindow::from),
|
||||
secondary: value.secondary.map(RateLimitWindow::from),
|
||||
credits: value.credits.map(CreditsSnapshot::from),
|
||||
spend_control: value.spend_control.map(SpendControlSnapshot::from),
|
||||
plan_type: value.plan_type,
|
||||
rate_limit_reached_type: value
|
||||
.rate_limit_reached_type
|
||||
@@ -371,6 +377,114 @@ impl From<CoreCreditsSnapshot> for CreditsSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SpendControlSnapshot {
|
||||
pub reached: bool,
|
||||
pub reached_limit_type: Option<SpendControlLimitType>,
|
||||
pub individual_limit: Option<SpendControlLimitSnapshot>,
|
||||
pub group_default_limit: Option<GroupSpendControlLimitSnapshot>,
|
||||
pub workspace_default_limit: Option<SpendControlLimitSnapshot>,
|
||||
pub role_budget_limit: Option<SpendControlLimitSnapshot>,
|
||||
pub group_shared_limit: Option<GroupSpendControlLimitSnapshot>,
|
||||
pub workspace_shared_limit: Option<SpendControlLimitSnapshot>,
|
||||
}
|
||||
|
||||
impl From<CoreSpendControlSnapshot> for SpendControlSnapshot {
|
||||
fn from(value: CoreSpendControlSnapshot) -> Self {
|
||||
Self {
|
||||
reached: value.reached,
|
||||
reached_limit_type: value.reached_limit_type.map(SpendControlLimitType::from),
|
||||
individual_limit: value.individual_limit.map(SpendControlLimitSnapshot::from),
|
||||
group_default_limit: value
|
||||
.group_default_limit
|
||||
.map(GroupSpendControlLimitSnapshot::from),
|
||||
workspace_default_limit: value
|
||||
.workspace_default_limit
|
||||
.map(SpendControlLimitSnapshot::from),
|
||||
role_budget_limit: value.role_budget_limit.map(SpendControlLimitSnapshot::from),
|
||||
group_shared_limit: value
|
||||
.group_shared_limit
|
||||
.map(GroupSpendControlLimitSnapshot::from),
|
||||
workspace_shared_limit: value
|
||||
.workspace_shared_limit
|
||||
.map(SpendControlLimitSnapshot::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
||||
pub enum SpendControlLimitType {
|
||||
Individual,
|
||||
GroupDefault,
|
||||
WorkspaceDefault,
|
||||
RoleBudget,
|
||||
GroupShared,
|
||||
WorkspaceShared,
|
||||
}
|
||||
|
||||
impl From<CoreSpendControlLimitType> for SpendControlLimitType {
|
||||
fn from(value: CoreSpendControlLimitType) -> Self {
|
||||
match value {
|
||||
CoreSpendControlLimitType::Individual => Self::Individual,
|
||||
CoreSpendControlLimitType::GroupDefault => Self::GroupDefault,
|
||||
CoreSpendControlLimitType::WorkspaceDefault => Self::WorkspaceDefault,
|
||||
CoreSpendControlLimitType::RoleBudget => Self::RoleBudget,
|
||||
CoreSpendControlLimitType::GroupShared => Self::GroupShared,
|
||||
CoreSpendControlLimitType::WorkspaceShared => Self::WorkspaceShared,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SpendControlLimitSnapshot {
|
||||
pub limit: String,
|
||||
pub used: String,
|
||||
pub remaining: String,
|
||||
pub used_percent: i32,
|
||||
pub remaining_percent: i32,
|
||||
pub reset_after_seconds: i32,
|
||||
pub reset_at: i32,
|
||||
}
|
||||
|
||||
impl From<CoreSpendControlLimitSnapshot> for SpendControlLimitSnapshot {
|
||||
fn from(value: CoreSpendControlLimitSnapshot) -> Self {
|
||||
Self {
|
||||
limit: value.limit,
|
||||
used: value.used,
|
||||
remaining: value.remaining,
|
||||
used_percent: value.used_percent,
|
||||
remaining_percent: value.remaining_percent,
|
||||
reset_after_seconds: value.reset_after_seconds,
|
||||
reset_at: value.reset_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GroupSpendControlLimitSnapshot {
|
||||
pub group_id: String,
|
||||
pub group_name: String,
|
||||
pub details: SpendControlLimitSnapshot,
|
||||
}
|
||||
|
||||
impl From<CoreGroupSpendControlLimitSnapshot> for GroupSpendControlLimitSnapshot {
|
||||
fn from(value: CoreGroupSpendControlLimitSnapshot) -> Self {
|
||||
Self {
|
||||
group_id: value.group_id,
|
||||
group_name: value.group_name,
|
||||
details: SpendControlLimitSnapshot::from(value.details),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
@@ -788,6 +788,7 @@ mod tests {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
|
||||
@@ -181,6 +181,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
|
||||
resets_at: Some(secondary_reset_timestamp),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached),
|
||||
},
|
||||
@@ -202,6 +203,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
|
||||
resets_at: Some(secondary_reset_timestamp),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: Some(
|
||||
RateLimitReachedType::WorkspaceMemberUsageLimitReached,
|
||||
@@ -220,6 +222,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
|
||||
@@ -12,9 +12,13 @@ use codex_login::CodexAuth;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use codex_protocol::protocol::CreditsSnapshot;
|
||||
use codex_protocol::protocol::GroupSpendControlLimitSnapshot;
|
||||
use codex_protocol::protocol::RateLimitReachedType;
|
||||
use codex_protocol::protocol::RateLimitSnapshot;
|
||||
use codex_protocol::protocol::RateLimitWindow;
|
||||
use codex_protocol::protocol::SpendControlLimitSnapshot;
|
||||
use codex_protocol::protocol::SpendControlLimitType;
|
||||
use codex_protocol::protocol::SpendControlSnapshot;
|
||||
use reqwest::StatusCode;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use reqwest::header::HeaderMap;
|
||||
@@ -457,6 +461,7 @@ impl Client {
|
||||
/*limit_name*/ None,
|
||||
payload.rate_limit.flatten().map(|details| *details),
|
||||
payload.credits.flatten().map(|details| *details),
|
||||
payload.spend_control.flatten().map(|details| *details),
|
||||
plan_type,
|
||||
rate_limit_reached_type,
|
||||
)];
|
||||
@@ -467,6 +472,7 @@ impl Client {
|
||||
Some(details.limit_name),
|
||||
details.rate_limit.flatten().map(|rate_limit| *rate_limit),
|
||||
/*credits*/ None,
|
||||
/*spend_control*/ None,
|
||||
plan_type,
|
||||
/*rate_limit_reached_type*/ None,
|
||||
)
|
||||
@@ -480,6 +486,7 @@ impl Client {
|
||||
limit_name: Option<String>,
|
||||
rate_limit: Option<crate::types::RateLimitStatusDetails>,
|
||||
credits: Option<crate::types::CreditStatusDetails>,
|
||||
spend_control: Option<crate::types::SpendControlStatusDetails>,
|
||||
plan_type: Option<AccountPlanType>,
|
||||
rate_limit_reached_type: Option<RateLimitReachedType>,
|
||||
) -> RateLimitSnapshot {
|
||||
@@ -496,6 +503,7 @@ impl Client {
|
||||
primary,
|
||||
secondary,
|
||||
credits: Self::map_credits(credits),
|
||||
spend_control: Self::map_spend_control(spend_control),
|
||||
plan_type,
|
||||
rate_limit_reached_type,
|
||||
}
|
||||
@@ -564,6 +572,87 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
fn map_spend_control(
|
||||
spend_control: Option<crate::types::SpendControlStatusDetails>,
|
||||
) -> Option<SpendControlSnapshot> {
|
||||
let details = spend_control?;
|
||||
|
||||
Some(SpendControlSnapshot {
|
||||
reached: details.reached,
|
||||
reached_limit_type: details
|
||||
.reached_limit_type
|
||||
.flatten()
|
||||
.map(Self::map_spend_control_limit_type),
|
||||
individual_limit: details
|
||||
.individual_limit
|
||||
.flatten()
|
||||
.map(|limit| Self::map_spend_control_limit(*limit)),
|
||||
group_default_limit: details
|
||||
.group_default_limit
|
||||
.flatten()
|
||||
.map(|limit| Self::map_group_spend_control_limit(*limit)),
|
||||
workspace_default_limit: details
|
||||
.workspace_default_limit
|
||||
.flatten()
|
||||
.map(|limit| Self::map_spend_control_limit(*limit)),
|
||||
role_budget_limit: details
|
||||
.role_budget_limit
|
||||
.flatten()
|
||||
.map(|limit| Self::map_spend_control_limit(*limit)),
|
||||
group_shared_limit: details
|
||||
.group_shared_limit
|
||||
.flatten()
|
||||
.map(|limit| Self::map_group_spend_control_limit(*limit)),
|
||||
workspace_shared_limit: details
|
||||
.workspace_shared_limit
|
||||
.flatten()
|
||||
.map(|limit| Self::map_spend_control_limit(*limit)),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_group_spend_control_limit(
|
||||
limit: crate::types::GroupSpendControlLimitDetails,
|
||||
) -> GroupSpendControlLimitSnapshot {
|
||||
GroupSpendControlLimitSnapshot {
|
||||
group_id: limit.group_id,
|
||||
group_name: limit.group_name,
|
||||
details: Self::map_spend_control_limit(*limit.details),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_spend_control_limit(
|
||||
limit: crate::types::SpendControlLimitDetails,
|
||||
) -> SpendControlLimitSnapshot {
|
||||
SpendControlLimitSnapshot {
|
||||
limit: limit.limit,
|
||||
used: limit.used,
|
||||
remaining: limit.remaining,
|
||||
used_percent: limit.used_percent,
|
||||
remaining_percent: limit.remaining_percent,
|
||||
reset_after_seconds: limit.reset_after_seconds,
|
||||
reset_at: limit.reset_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_spend_control_limit_type(
|
||||
limit_type: crate::types::SpendControlLimitType,
|
||||
) -> SpendControlLimitType {
|
||||
match limit_type {
|
||||
crate::types::SpendControlLimitType::Individual => SpendControlLimitType::Individual,
|
||||
crate::types::SpendControlLimitType::GroupDefault => {
|
||||
SpendControlLimitType::GroupDefault
|
||||
}
|
||||
crate::types::SpendControlLimitType::WorkspaceDefault => {
|
||||
SpendControlLimitType::WorkspaceDefault
|
||||
}
|
||||
crate::types::SpendControlLimitType::RoleBudget => SpendControlLimitType::RoleBudget,
|
||||
crate::types::SpendControlLimitType::GroupShared => SpendControlLimitType::GroupShared,
|
||||
crate::types::SpendControlLimitType::WorkspaceShared => {
|
||||
SpendControlLimitType::WorkspaceShared
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_plan_type(plan_type: crate::types::PlanType) -> AccountPlanType {
|
||||
match plan_type {
|
||||
crate::types::PlanType::Free => AccountPlanType::Free,
|
||||
@@ -658,6 +747,7 @@ mod tests {
|
||||
balance: Some(Some("9.99".to_string())),
|
||||
..Default::default()
|
||||
}))),
|
||||
spend_control: None,
|
||||
rate_limit_reached_type: Some(Some(BackendRateLimitReachedType {
|
||||
kind: RateLimitReachedKind::WorkspaceMemberCreditsDepleted,
|
||||
})),
|
||||
@@ -712,6 +802,7 @@ mod tests {
|
||||
rate_limit: None,
|
||||
}])),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
|
||||
@@ -724,6 +815,59 @@ mod tests {
|
||||
assert_eq!(snapshots[1].limit_name.as_deref(), Some("codex_other"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_payload_maps_spend_controls_v2_details() {
|
||||
let payload = RateLimitStatusPayload {
|
||||
plan_type: crate::types::PlanType::Enterprise,
|
||||
rate_limit: None,
|
||||
additional_rate_limits: None,
|
||||
credits: None,
|
||||
spend_control: Some(Some(Box::new(crate::types::SpendControlStatusDetails {
|
||||
reached: true,
|
||||
reached_limit_type: Some(Some(crate::types::SpendControlLimitType::GroupShared)),
|
||||
individual_limit: None,
|
||||
group_default_limit: None,
|
||||
workspace_default_limit: None,
|
||||
role_budget_limit: None,
|
||||
group_shared_limit: Some(Some(Box::new(
|
||||
crate::types::GroupSpendControlLimitDetails {
|
||||
group_id: "group-1".to_string(),
|
||||
group_name: "Engineering".to_string(),
|
||||
details: Box::new(crate::types::SpendControlLimitDetails {
|
||||
limit: "100".to_string(),
|
||||
used: "82".to_string(),
|
||||
remaining: "18".to_string(),
|
||||
used_percent: 82,
|
||||
remaining_percent: 18,
|
||||
reset_after_seconds: 60,
|
||||
reset_at: 120,
|
||||
}),
|
||||
},
|
||||
))),
|
||||
workspace_shared_limit: None,
|
||||
}))),
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
|
||||
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
|
||||
let spend_control = snapshots[0]
|
||||
.spend_control
|
||||
.as_ref()
|
||||
.expect("spend control should be mapped");
|
||||
assert!(spend_control.reached);
|
||||
assert_eq!(
|
||||
spend_control.reached_limit_type,
|
||||
Some(SpendControlLimitType::GroupShared)
|
||||
);
|
||||
let group_shared_limit = spend_control
|
||||
.group_shared_limit
|
||||
.as_ref()
|
||||
.expect("group shared limit should be mapped");
|
||||
assert_eq!(group_shared_limit.group_id, "group-1");
|
||||
assert_eq!(group_shared_limit.group_name, "Engineering");
|
||||
assert_eq!(group_shared_limit.details.remaining_percent, 18);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_snapshot_selection_matches_get_rate_limits_behavior() {
|
||||
let snapshots = [
|
||||
@@ -737,6 +881,7 @@ mod tests {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
@@ -750,6 +895,7 @@ mod tests {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
@@ -795,6 +941,7 @@ mod tests {
|
||||
rate_limit: None,
|
||||
credits: None,
|
||||
additional_rate_limits: None,
|
||||
spend_control: None,
|
||||
rate_limit_reached_type: Some(Some(BackendRateLimitReachedType { kind })),
|
||||
};
|
||||
|
||||
@@ -810,6 +957,7 @@ mod tests {
|
||||
rate_limit: None,
|
||||
credits: None,
|
||||
additional_rate_limits: None,
|
||||
spend_control: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
pub use codex_backend_openapi_models::models::ConfigFileResponse;
|
||||
pub use codex_backend_openapi_models::models::CreditStatusDetails;
|
||||
pub use codex_backend_openapi_models::models::GroupSpendControlLimitDetails;
|
||||
pub use codex_backend_openapi_models::models::PaginatedListTaskListItem;
|
||||
pub use codex_backend_openapi_models::models::PlanType;
|
||||
pub use codex_backend_openapi_models::models::RateLimitReachedKind;
|
||||
pub use codex_backend_openapi_models::models::RateLimitStatusDetails;
|
||||
pub use codex_backend_openapi_models::models::RateLimitStatusPayload;
|
||||
pub use codex_backend_openapi_models::models::RateLimitWindowSnapshot;
|
||||
pub use codex_backend_openapi_models::models::SpendControlLimitDetails;
|
||||
pub use codex_backend_openapi_models::models::SpendControlLimitType;
|
||||
pub use codex_backend_openapi_models::models::SpendControlStatusDetails;
|
||||
pub use codex_backend_openapi_models::models::TaskListItem;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -92,6 +92,7 @@ pub fn parse_rate_limit_for_limit(
|
||||
primary,
|
||||
secondary,
|
||||
credits,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
})
|
||||
@@ -156,6 +157,7 @@ pub fn parse_rate_limit_event(payload: &str) -> Option<RateLimitSnapshot> {
|
||||
primary,
|
||||
secondary,
|
||||
credits,
|
||||
spend_control: None,
|
||||
plan_type: event.plan_type,
|
||||
rate_limit_reached_type: None,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* codex-backend
|
||||
*
|
||||
* codex-backend
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.1
|
||||
*
|
||||
* Generated by: https://openapi-generator.tech
|
||||
*/
|
||||
|
||||
use crate::models;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct GroupSpendControlLimitDetails {
|
||||
#[serde(rename = "group_id")]
|
||||
pub group_id: String,
|
||||
#[serde(rename = "group_name")]
|
||||
pub group_name: String,
|
||||
#[serde(rename = "details")]
|
||||
pub details: Box<models::SpendControlLimitDetails>,
|
||||
}
|
||||
|
||||
impl GroupSpendControlLimitDetails {
|
||||
pub fn new(
|
||||
group_id: String,
|
||||
group_name: String,
|
||||
details: models::SpendControlLimitDetails,
|
||||
) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
group_name,
|
||||
details: Box::new(details),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,3 +44,13 @@ pub use self::rate_limit_window_snapshot::RateLimitWindowSnapshot;
|
||||
|
||||
pub(crate) mod credit_status_details;
|
||||
pub use self::credit_status_details::CreditStatusDetails;
|
||||
|
||||
pub(crate) mod spend_control_limit_details;
|
||||
pub use self::spend_control_limit_details::SpendControlLimitDetails;
|
||||
|
||||
pub(crate) mod group_spend_control_limit_details;
|
||||
pub use self::group_spend_control_limit_details::GroupSpendControlLimitDetails;
|
||||
|
||||
pub(crate) mod spend_control_status_details;
|
||||
pub use self::spend_control_status_details::SpendControlLimitType;
|
||||
pub use self::spend_control_status_details::SpendControlStatusDetails;
|
||||
|
||||
@@ -37,6 +37,13 @@ pub struct RateLimitStatusPayload {
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub additional_rate_limits: Option<Option<Vec<models::AdditionalRateLimitDetails>>>,
|
||||
#[serde(
|
||||
rename = "spend_control",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub spend_control: Option<Option<Box<models::SpendControlStatusDetails>>>,
|
||||
#[serde(
|
||||
rename = "rate_limit_reached_type",
|
||||
default,
|
||||
@@ -53,6 +60,7 @@ impl RateLimitStatusPayload {
|
||||
rate_limit: None,
|
||||
credits: None,
|
||||
additional_rate_limits: None,
|
||||
spend_control: None,
|
||||
rate_limit_reached_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* codex-backend
|
||||
*
|
||||
* codex-backend
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.1
|
||||
*
|
||||
* Generated by: https://openapi-generator.tech
|
||||
*/
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SpendControlLimitDetails {
|
||||
#[serde(rename = "limit")]
|
||||
pub limit: String,
|
||||
#[serde(rename = "used")]
|
||||
pub used: String,
|
||||
#[serde(rename = "remaining")]
|
||||
pub remaining: String,
|
||||
#[serde(rename = "used_percent")]
|
||||
pub used_percent: i32,
|
||||
#[serde(rename = "remaining_percent")]
|
||||
pub remaining_percent: i32,
|
||||
#[serde(rename = "reset_after_seconds")]
|
||||
pub reset_after_seconds: i32,
|
||||
#[serde(rename = "reset_at")]
|
||||
pub reset_at: i32,
|
||||
}
|
||||
|
||||
impl SpendControlLimitDetails {
|
||||
pub fn new(
|
||||
limit: String,
|
||||
used: String,
|
||||
remaining: String,
|
||||
used_percent: i32,
|
||||
remaining_percent: i32,
|
||||
reset_after_seconds: i32,
|
||||
reset_at: i32,
|
||||
) -> Self {
|
||||
Self {
|
||||
limit,
|
||||
used,
|
||||
remaining,
|
||||
used_percent,
|
||||
remaining_percent,
|
||||
reset_after_seconds,
|
||||
reset_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* codex-backend
|
||||
*
|
||||
* codex-backend
|
||||
*
|
||||
* The version of the OpenAPI document: 0.0.1
|
||||
*
|
||||
* Generated by: https://openapi-generator.tech
|
||||
*/
|
||||
|
||||
use crate::models;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
|
||||
pub enum SpendControlLimitType {
|
||||
#[serde(rename = "individual")]
|
||||
Individual,
|
||||
#[serde(rename = "group_default")]
|
||||
GroupDefault,
|
||||
#[serde(rename = "workspace_default")]
|
||||
WorkspaceDefault,
|
||||
#[serde(rename = "role_budget")]
|
||||
RoleBudget,
|
||||
#[serde(rename = "group_shared")]
|
||||
GroupShared,
|
||||
#[serde(rename = "workspace_shared")]
|
||||
WorkspaceShared,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SpendControlStatusDetails {
|
||||
#[serde(rename = "reached")]
|
||||
pub reached: bool,
|
||||
#[serde(
|
||||
rename = "reached_limit_type",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub reached_limit_type: Option<Option<SpendControlLimitType>>,
|
||||
#[serde(
|
||||
rename = "individual_limit",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub individual_limit: Option<Option<Box<models::SpendControlLimitDetails>>>,
|
||||
#[serde(
|
||||
rename = "group_default_limit",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub group_default_limit: Option<Option<Box<models::GroupSpendControlLimitDetails>>>,
|
||||
#[serde(
|
||||
rename = "workspace_default_limit",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub workspace_default_limit: Option<Option<Box<models::SpendControlLimitDetails>>>,
|
||||
#[serde(
|
||||
rename = "role_budget_limit",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub role_budget_limit: Option<Option<Box<models::SpendControlLimitDetails>>>,
|
||||
#[serde(
|
||||
rename = "group_shared_limit",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub group_shared_limit: Option<Option<Box<models::GroupSpendControlLimitDetails>>>,
|
||||
#[serde(
|
||||
rename = "workspace_shared_limit",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub workspace_shared_limit: Option<Option<Box<models::SpendControlLimitDetails>>>,
|
||||
}
|
||||
|
||||
impl SpendControlStatusDetails {
|
||||
pub fn new(reached: bool) -> Self {
|
||||
Self {
|
||||
reached,
|
||||
reached_limit_type: None,
|
||||
individual_limit: None,
|
||||
group_default_limit: None,
|
||||
workspace_default_limit: None,
|
||||
role_budget_limit: None,
|
||||
group_shared_limit: None,
|
||||
workspace_shared_limit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ fn rate_limit_snapshot() -> RateLimitSnapshot {
|
||||
resets_at: Some(secondary_reset_at),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}
|
||||
|
||||
@@ -2083,6 +2083,7 @@ pub struct RateLimitSnapshot {
|
||||
pub primary: Option<RateLimitWindow>,
|
||||
pub secondary: Option<RateLimitWindow>,
|
||||
pub credits: Option<CreditsSnapshot>,
|
||||
pub spend_control: Option<SpendControlSnapshot>,
|
||||
pub plan_type: Option<crate::account::PlanType>,
|
||||
pub rate_limit_reached_type: Option<RateLimitReachedType>,
|
||||
}
|
||||
@@ -2117,6 +2118,48 @@ pub struct CreditsSnapshot {
|
||||
pub balance: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
pub struct SpendControlSnapshot {
|
||||
pub reached: bool,
|
||||
pub reached_limit_type: Option<SpendControlLimitType>,
|
||||
pub individual_limit: Option<SpendControlLimitSnapshot>,
|
||||
pub group_default_limit: Option<GroupSpendControlLimitSnapshot>,
|
||||
pub workspace_default_limit: Option<SpendControlLimitSnapshot>,
|
||||
pub role_budget_limit: Option<SpendControlLimitSnapshot>,
|
||||
pub group_shared_limit: Option<GroupSpendControlLimitSnapshot>,
|
||||
pub workspace_shared_limit: Option<SpendControlLimitSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case")]
|
||||
pub enum SpendControlLimitType {
|
||||
Individual,
|
||||
GroupDefault,
|
||||
WorkspaceDefault,
|
||||
RoleBudget,
|
||||
GroupShared,
|
||||
WorkspaceShared,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
pub struct SpendControlLimitSnapshot {
|
||||
pub limit: String,
|
||||
pub used: String,
|
||||
pub remaining: String,
|
||||
pub used_percent: i32,
|
||||
pub remaining_percent: i32,
|
||||
pub reset_after_seconds: i32,
|
||||
pub reset_at: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
pub struct GroupSpendControlLimitSnapshot {
|
||||
pub group_id: String,
|
||||
pub group_name: String,
|
||||
pub details: SpendControlLimitSnapshot,
|
||||
}
|
||||
|
||||
// Includes prompts, tools and space to call compact.
|
||||
const BASELINE_TOKENS: i64 = 12000;
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ pub(super) fn snapshot(percent: f64) -> RateLimitSnapshot {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}
|
||||
|
||||
@@ -531,6 +531,7 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() {
|
||||
unlimited: false,
|
||||
balance: Some("17.5".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
@@ -551,6 +552,7 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
@@ -590,6 +592,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
|
||||
resets_at: None,
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
@@ -609,6 +612,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
|
||||
resets_at: Some(234),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(PlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
@@ -628,6 +632,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
|
||||
resets_at: Some(567),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
@@ -652,6 +657,7 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() {
|
||||
unlimited: false,
|
||||
balance: Some("5.00".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: Some(PlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
@@ -666,6 +672,7 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: Some(PlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
@@ -719,6 +726,7 @@ async fn rate_limit_switch_prompt_skips_non_codex_limit() {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
|
||||
@@ -14,8 +14,12 @@ use chrono::Duration as ChronoDuration;
|
||||
use chrono::Local;
|
||||
use chrono::Utc;
|
||||
use codex_app_server_protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
||||
use codex_app_server_protocol::GroupSpendControlLimitSnapshot;
|
||||
use codex_app_server_protocol::RateLimitSnapshot;
|
||||
use codex_app_server_protocol::RateLimitWindow;
|
||||
use codex_app_server_protocol::SpendControlLimitSnapshot;
|
||||
use codex_app_server_protocol::SpendControlLimitType;
|
||||
use codex_app_server_protocol::SpendControlSnapshot;
|
||||
|
||||
const STATUS_LIMIT_BAR_SEGMENTS: usize = 20;
|
||||
const STATUS_LIMIT_BAR_FILLED: &str = "█";
|
||||
@@ -98,6 +102,8 @@ pub(crate) struct RateLimitSnapshotDisplay {
|
||||
pub secondary: Option<RateLimitWindowDisplay>,
|
||||
/// Optional credits metadata when available.
|
||||
pub credits: Option<CreditsSnapshotDisplay>,
|
||||
/// Optional spend-control metadata when available.
|
||||
pub spend_control: Option<SpendControlSnapshotDisplay>,
|
||||
}
|
||||
|
||||
/// Display-ready credits state extracted from protocol snapshots.
|
||||
@@ -111,6 +117,28 @@ pub(crate) struct CreditsSnapshotDisplay {
|
||||
pub balance: Option<String>,
|
||||
}
|
||||
|
||||
/// Display-ready spend-control state extracted from protocol snapshots.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SpendControlSnapshotDisplay {
|
||||
/// Whether the backend says a spend control blocked the current request path.
|
||||
pub reached: bool,
|
||||
/// The concrete control type that was reached, when one is known.
|
||||
pub reached_limit_type: Option<SpendControlLimitType>,
|
||||
/// The most relevant active limit to summarize for `/status`.
|
||||
pub active_limit: Option<SpendControlLimitDisplay>,
|
||||
}
|
||||
|
||||
/// Display-ready view of one configured spend-control limit.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SpendControlLimitDisplay {
|
||||
/// Friendly control label for status output.
|
||||
pub label: String,
|
||||
/// Percent remaining according to the backend snapshot.
|
||||
pub remaining_percent: f64,
|
||||
/// Human-readable local reset time.
|
||||
pub resets_at: Option<String>,
|
||||
}
|
||||
|
||||
/// Converts a protocol snapshot into UI-friendly display data.
|
||||
///
|
||||
/// Pass the timestamp from the same observation point as `snapshot`; supplying a significantly
|
||||
@@ -140,6 +168,9 @@ pub(crate) fn rate_limit_snapshot_display_for_limit(
|
||||
.as_ref()
|
||||
.map(|window| RateLimitWindowDisplay::from_window(window, captured_at)),
|
||||
credits: snapshot.credits.as_ref().map(CreditsSnapshotDisplay::from),
|
||||
spend_control: snapshot.spend_control.as_ref().map(|spend_control| {
|
||||
SpendControlSnapshotDisplay::from_snapshot(spend_control, captured_at)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +184,16 @@ impl From<&CoreCreditsSnapshot> for CreditsSnapshotDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
impl SpendControlSnapshotDisplay {
|
||||
fn from_snapshot(snapshot: &SpendControlSnapshot, captured_at: DateTime<Local>) -> Self {
|
||||
Self {
|
||||
reached: snapshot.reached,
|
||||
reached_limit_type: snapshot.reached_limit_type,
|
||||
active_limit: active_spend_control_limit(snapshot, captured_at),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds display rows from a snapshot and marks stale data by capture age.
|
||||
///
|
||||
/// Callers should pass `Local::now()` for `now` at render time; using a cached timestamp can make
|
||||
@@ -268,6 +309,9 @@ pub(crate) fn compose_rate_limit_data_many(
|
||||
{
|
||||
rows.push(row);
|
||||
}
|
||||
if let Some(spend_control) = snapshot.spend_control.as_ref() {
|
||||
rows.extend(spend_control_status_rows(spend_control));
|
||||
}
|
||||
}
|
||||
|
||||
if rows.is_empty() {
|
||||
@@ -279,6 +323,107 @@ pub(crate) fn compose_rate_limit_data_many(
|
||||
}
|
||||
}
|
||||
|
||||
fn spend_control_status_rows(
|
||||
spend_control: &SpendControlSnapshotDisplay,
|
||||
) -> Vec<StatusRateLimitRow> {
|
||||
let mut rows = Vec::new();
|
||||
if let Some(active_limit) = spend_control.active_limit.as_ref() {
|
||||
rows.push(StatusRateLimitRow {
|
||||
label: active_limit.label.clone(),
|
||||
value: StatusRateLimitValue::Window {
|
||||
percent_used: (100.0 - active_limit.remaining_percent).clamp(0.0, 100.0),
|
||||
resets_at: active_limit.resets_at.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
if spend_control.reached
|
||||
&& let Some(reached_limit_type) = spend_control.reached_limit_type
|
||||
{
|
||||
rows.push(StatusRateLimitRow {
|
||||
label: "Spend control reached".to_string(),
|
||||
value: StatusRateLimitValue::Text(spend_control_limit_type_label(reached_limit_type)),
|
||||
});
|
||||
}
|
||||
rows
|
||||
}
|
||||
|
||||
fn active_spend_control_limit(
|
||||
snapshot: &SpendControlSnapshot,
|
||||
captured_at: DateTime<Local>,
|
||||
) -> Option<SpendControlLimitDisplay> {
|
||||
snapshot
|
||||
.individual_limit
|
||||
.as_ref()
|
||||
.map(|limit| spend_control_limit_display("Individual spend control", limit, captured_at))
|
||||
.or_else(|| {
|
||||
snapshot.group_default_limit.as_ref().map(|limit| {
|
||||
group_spend_control_limit_display("Group default spend control", limit, captured_at)
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
snapshot.workspace_default_limit.as_ref().map(|limit| {
|
||||
spend_control_limit_display("Workspace default spend control", limit, captured_at)
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
snapshot
|
||||
.role_budget_limit
|
||||
.as_ref()
|
||||
.map(|limit| spend_control_limit_display("Role budget", limit, captured_at))
|
||||
})
|
||||
.or_else(|| {
|
||||
snapshot.group_shared_limit.as_ref().map(|limit| {
|
||||
group_spend_control_limit_display("Group shared spend pool", limit, captured_at)
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
snapshot.workspace_shared_limit.as_ref().map(|limit| {
|
||||
spend_control_limit_display("Workspace shared spend pool", limit, captured_at)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn spend_control_limit_display(
|
||||
label: &str,
|
||||
limit: &SpendControlLimitSnapshot,
|
||||
captured_at: DateTime<Local>,
|
||||
) -> SpendControlLimitDisplay {
|
||||
SpendControlLimitDisplay {
|
||||
label: label.to_string(),
|
||||
remaining_percent: f64::from(limit.remaining_percent),
|
||||
resets_at: format_spend_control_reset(limit.reset_at, captured_at),
|
||||
}
|
||||
}
|
||||
|
||||
fn group_spend_control_limit_display(
|
||||
label: &str,
|
||||
limit: &GroupSpendControlLimitSnapshot,
|
||||
captured_at: DateTime<Local>,
|
||||
) -> SpendControlLimitDisplay {
|
||||
SpendControlLimitDisplay {
|
||||
label: format!("{label}: {}", limit.group_name),
|
||||
remaining_percent: f64::from(limit.details.remaining_percent),
|
||||
resets_at: format_spend_control_reset(limit.details.reset_at, captured_at),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_spend_control_reset(reset_at: i32, captured_at: DateTime<Local>) -> Option<String> {
|
||||
DateTime::<Utc>::from_timestamp(i64::from(reset_at), 0)
|
||||
.map(|dt| dt.with_timezone(&Local))
|
||||
.map(|dt| format_reset_timestamp(dt, captured_at))
|
||||
}
|
||||
|
||||
fn spend_control_limit_type_label(limit_type: SpendControlLimitType) -> String {
|
||||
match limit_type {
|
||||
SpendControlLimitType::Individual => "Individual spend control".to_string(),
|
||||
SpendControlLimitType::GroupDefault => "Group default spend control".to_string(),
|
||||
SpendControlLimitType::WorkspaceDefault => "Workspace default spend control".to_string(),
|
||||
SpendControlLimitType::RoleBudget => "Role budget".to_string(),
|
||||
SpendControlLimitType::GroupShared => "Group shared spend pool".to_string(),
|
||||
SpendControlLimitType::WorkspaceShared => "Workspace shared spend pool".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a fixed-width progress bar from remaining percentage.
|
||||
///
|
||||
/// This function expects a remaining value in the `0..=100` range and clamps out-of-range input.
|
||||
@@ -349,9 +494,12 @@ mod tests {
|
||||
use super::CreditsSnapshotDisplay;
|
||||
use super::RateLimitSnapshotDisplay;
|
||||
use super::RateLimitWindowDisplay;
|
||||
use super::SpendControlLimitDisplay;
|
||||
use super::SpendControlSnapshotDisplay;
|
||||
use super::StatusRateLimitData;
|
||||
use super::compose_rate_limit_data_many;
|
||||
use chrono::Local;
|
||||
use codex_app_server_protocol::SpendControlLimitType;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn window(used_percent: f64) -> RateLimitWindowDisplay {
|
||||
@@ -375,6 +523,7 @@ mod tests {
|
||||
unlimited: false,
|
||||
balance: Some("25".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
};
|
||||
let other = RateLimitSnapshotDisplay {
|
||||
limit_name: "codex-other".to_string(),
|
||||
@@ -386,6 +535,7 @@ mod tests {
|
||||
unlimited: false,
|
||||
balance: Some("99".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
};
|
||||
|
||||
let rows = match compose_rate_limit_data_many(&[codex, other], now) {
|
||||
@@ -423,6 +573,7 @@ mod tests {
|
||||
window_minutes: None,
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
};
|
||||
|
||||
let rows = match compose_rate_limit_data_many(&[other], now) {
|
||||
@@ -439,4 +590,39 @@ mod tests {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_control_rows_show_active_limit_and_reached_type() {
|
||||
let now = Local::now();
|
||||
let codex = RateLimitSnapshotDisplay {
|
||||
limit_name: "codex".to_string(),
|
||||
captured_at: now,
|
||||
primary: None,
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: Some(SpendControlSnapshotDisplay {
|
||||
reached: true,
|
||||
reached_limit_type: Some(SpendControlLimitType::GroupShared),
|
||||
active_limit: Some(SpendControlLimitDisplay {
|
||||
label: "Group shared spend pool: Engineering".to_string(),
|
||||
remaining_percent: 18.0,
|
||||
resets_at: Some("soon".to_string()),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
let rows = match compose_rate_limit_data_many(&[codex], now) {
|
||||
StatusRateLimitData::Available(rows) => rows,
|
||||
other => panic!("unexpected status: {other:?}"),
|
||||
};
|
||||
|
||||
let labels: Vec<String> = rows.iter().map(|row| row.label.clone()).collect();
|
||||
assert_eq!(
|
||||
labels,
|
||||
vec![
|
||||
"Group shared spend pool: Engineering".to_string(),
|
||||
"Spend control reached".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,15 @@ use codex_app_server_protocol::FileSystemAccessMode;
|
||||
use codex_app_server_protocol::FileSystemPath;
|
||||
use codex_app_server_protocol::FileSystemSandboxEntry;
|
||||
use codex_app_server_protocol::FileSystemSpecialPath;
|
||||
use codex_app_server_protocol::GroupSpendControlLimitSnapshot;
|
||||
use codex_app_server_protocol::PermissionProfile as AppServerPermissionProfile;
|
||||
use codex_app_server_protocol::PermissionProfileFileSystemPermissions;
|
||||
use codex_app_server_protocol::PermissionProfileNetworkPermissions;
|
||||
use codex_app_server_protocol::RateLimitSnapshot;
|
||||
use codex_app_server_protocol::RateLimitWindow;
|
||||
use codex_app_server_protocol::SpendControlLimitSnapshot;
|
||||
use codex_app_server_protocol::SpendControlLimitType;
|
||||
use codex_app_server_protocol::SpendControlSnapshot;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_model_provider_info::ModelProviderAwsAuthInfo;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
@@ -236,6 +240,7 @@ async fn status_snapshot_includes_reasoning_details() {
|
||||
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_200)),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -829,6 +834,87 @@ async fn status_snapshot_includes_monthly_limit() {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
|
||||
let model_slug = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
||||
let token_info = token_info_for(&model_slug, &config, &usage);
|
||||
let composite = new_status_output(
|
||||
&config,
|
||||
account_display.as_ref(),
|
||||
Some(&token_info),
|
||||
&usage,
|
||||
&None,
|
||||
/*thread_name*/ None,
|
||||
/*forked_from*/ None,
|
||||
Some(&rate_display),
|
||||
None,
|
||||
captured_at,
|
||||
&model_slug,
|
||||
/*collaboration_mode*/ None,
|
||||
/*reasoning_effort_override*/ None,
|
||||
);
|
||||
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
|
||||
if cfg!(windows) {
|
||||
for line in &mut rendered_lines {
|
||||
*line = line.replace('\\', "/");
|
||||
}
|
||||
}
|
||||
let sanitized = sanitize_directory(rendered_lines).join("\n");
|
||||
assert_snapshot!(sanitized);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_snapshot_includes_group_shared_spend_control() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
|
||||
|
||||
let account_display = test_status_account_display();
|
||||
let usage = TokenUsage {
|
||||
input_tokens: 800,
|
||||
cached_input_tokens: 0,
|
||||
output_tokens: 400,
|
||||
reasoning_output_tokens: 0,
|
||||
total_tokens: 1_200,
|
||||
};
|
||||
let captured_at = chrono::Local
|
||||
.with_ymd_and_hms(2024, 5, 6, 7, 8, 9)
|
||||
.single()
|
||||
.expect("timestamp");
|
||||
let snapshot = RateLimitSnapshot {
|
||||
limit_id: None,
|
||||
limit_name: None,
|
||||
primary: None,
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: Some(SpendControlSnapshot {
|
||||
reached: true,
|
||||
reached_limit_type: Some(SpendControlLimitType::GroupShared),
|
||||
individual_limit: None,
|
||||
group_default_limit: None,
|
||||
workspace_default_limit: None,
|
||||
role_budget_limit: None,
|
||||
group_shared_limit: Some(GroupSpendControlLimitSnapshot {
|
||||
group_id: "group-1".to_string(),
|
||||
group_name: "Engineering".to_string(),
|
||||
details: SpendControlLimitSnapshot {
|
||||
limit: "200".to_string(),
|
||||
used: "190".to_string(),
|
||||
remaining: "10".to_string(),
|
||||
used_percent: 95,
|
||||
remaining_percent: 5,
|
||||
reset_after_seconds: 86_400,
|
||||
reset_at: reset_at_from(&captured_at, /*seconds*/ 86_400) as i32,
|
||||
},
|
||||
}),
|
||||
workspace_shared_limit: None,
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -881,6 +967,7 @@ async fn status_snapshot_shows_unlimited_credits() {
|
||||
unlimited: true,
|
||||
balance: None,
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -931,6 +1018,7 @@ async fn status_snapshot_shows_positive_credits() {
|
||||
unlimited: false,
|
||||
balance: Some("12.5".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -981,6 +1069,7 @@ async fn status_snapshot_hides_zero_credits() {
|
||||
unlimited: false,
|
||||
balance: Some("0".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1029,6 +1118,7 @@ async fn status_snapshot_hides_when_has_no_credits_flag() {
|
||||
unlimited: true,
|
||||
balance: None,
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1135,6 +1225,7 @@ async fn status_snapshot_truncates_in_narrow_terminal() {
|
||||
}),
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1300,6 +1391,7 @@ async fn status_snapshot_shows_refreshing_limits_notice() {
|
||||
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 2_700)),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1371,6 +1463,7 @@ async fn status_snapshot_includes_credits_and_limits() {
|
||||
unlimited: false,
|
||||
balance: Some("37.5".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1425,6 +1518,7 @@ async fn status_snapshot_shows_unavailable_limits_message() {
|
||||
primary: None,
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1482,6 +1576,7 @@ async fn status_snapshot_treats_refreshing_empty_limits_as_unavailable() {
|
||||
primary: None,
|
||||
secondary: None,
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1553,6 +1648,7 @@ async fn status_snapshot_shows_stale_limits_message() {
|
||||
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_800)),
|
||||
}),
|
||||
credits: None,
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
@@ -1624,6 +1720,7 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() {
|
||||
unlimited: false,
|
||||
balance: Some("80".to_string()),
|
||||
}),
|
||||
spend_control: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user